summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2021-07-26 12:41:06 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2021-07-26 12:41:06 +0000
commit68b5961e84bafb57d11f3d7f3375cc4c12b62d82 (patch)
tree94d16ce53d309ec2dd8aa1c562815001785dd7dd
parentInitial commit. (diff)
downloadiproute2-68b5961e84bafb57d11f3d7f3375cc4c12b62d82.tar.xz
iproute2-68b5961e84bafb57d11f3d7f3375cc4c12b62d82.zip
Adding upstream version 5.13.0.upstream/5.13.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
-rw-r--r--.clang-format130
-rw-r--r--.gitignore41
-rw-r--r--.mailmap22
-rw-r--r--COPYING340
-rw-r--r--Makefile126
-rw-r--r--README42
-rw-r--r--README.devel18
-rw-r--r--bash-completion/devlink1006
-rw-r--r--bash-completion/tc809
-rw-r--r--bridge/.gitignore1
-rw-r--r--bridge/Makefile15
-rw-r--r--bridge/br_common.h29
-rw-r--r--bridge/bridge.c193
-rw-r--r--bridge/fdb.c690
-rw-r--r--bridge/link.c585
-rw-r--r--bridge/mdb.c585
-rw-r--r--bridge/monitor.c160
-rw-r--r--bridge/vlan.c918
-rwxr-xr-xconfigure518
-rw-r--r--dcb/.gitignore1
-rw-r--r--dcb/Makefile31
-rw-r--r--dcb/dcb.c610
-rw-r--r--dcb/dcb.h81
-rw-r--r--dcb/dcb_app.c795
-rw-r--r--dcb/dcb_buffer.c235
-rw-r--r--dcb/dcb_dcbx.c192
-rw-r--r--dcb/dcb_ets.c435
-rw-r--r--dcb/dcb_maxrate.c182
-rw-r--r--dcb/dcb_pfc.c286
-rw-r--r--devlink/.gitignore1
-rw-r--r--devlink/Makefile25
-rw-r--r--devlink/devlink.c8697
-rw-r--r--devlink/mnlg.c155
-rw-r--r--devlink/mnlg.h23
-rw-r--r--doc/actions/actions-general256
-rw-r--r--doc/actions/gact-usage78
-rw-r--r--doc/actions/ifb-README125
-rw-r--r--doc/actions/mirred-usage164
-rw-r--r--etc/iproute2/bpf_pinning6
-rw-r--r--etc/iproute2/ematch_map8
-rw-r--r--etc/iproute2/group2
-rw-r--r--etc/iproute2/nl_protos23
-rw-r--r--etc/iproute2/rt_dsfield26
-rw-r--r--etc/iproute2/rt_protos25
-rw-r--r--etc/iproute2/rt_protos.d/README2
-rw-r--r--etc/iproute2/rt_realms13
-rw-r--r--etc/iproute2/rt_scopes11
-rw-r--r--etc/iproute2/rt_tables11
-rw-r--r--etc/iproute2/rt_tables.d/README2
-rw-r--r--examples/bpf/README18
-rw-r--r--examples/bpf/bpf_graft.c66
-rw-r--r--examples/bpf/bpf_map_in_map.c55
-rw-r--r--examples/bpf/bpf_shared.c53
-rw-r--r--examples/bpf/legacy/bpf_cyclic.c35
-rw-r--r--examples/bpf/legacy/bpf_graft.c66
-rw-r--r--examples/bpf/legacy/bpf_map_in_map.c56
-rw-r--r--examples/bpf/legacy/bpf_shared.c53
-rw-r--r--examples/bpf/legacy/bpf_tailcall.c117
-rw-r--r--genl/.gitignore1
-rw-r--r--genl/Makefile42
-rw-r--r--genl/ctrl.c379
-rw-r--r--genl/genl.c149
-rw-r--r--genl/genl_utils.h15
-rw-r--r--genl/static-syms.c15
-rw-r--r--include/bpf_api.h275
-rw-r--r--include/bpf_elf.h53
-rw-r--r--include/bpf_scm.h77
-rw-r--r--include/bpf_util.h327
-rw-r--r--include/cg_map.h6
-rw-r--r--include/color.h29
-rw-r--r--include/dlfcn.h41
-rw-r--r--include/ip6tables.h21
-rw-r--r--include/iptables.h26
-rw-r--r--include/iptables/internal.h14
-rw-r--r--include/json_print.h107
-rw-r--r--include/json_writer.h76
-rw-r--r--include/libgenl.h29
-rw-r--r--include/libiptc/ipt_kernel_headers.h16
-rw-r--r--include/libiptc/libip6tc.h162
-rw-r--r--include/libiptc/libiptc.h173
-rw-r--r--include/libiptc/libxtc.h34
-rw-r--r--include/libiptc/xtcshared.h21
-rw-r--r--include/libnetlink.h304
-rw-r--r--include/list.h134
-rw-r--r--include/ll_map.h17
-rw-r--r--include/mnl_utils.h33
-rw-r--r--include/names.h26
-rw-r--r--include/namespace.h61
-rw-r--r--include/netinet/tcp.h231
-rw-r--r--include/rt_names.h42
-rw-r--r--include/rtm_map.h10
-rw-r--r--include/uapi/asm-generic/sockios.h14
-rw-r--r--include/uapi/linux/atm.h242
-rw-r--r--include/uapi/linux/atmapi.h30
-rw-r--r--include/uapi/linux/atmarp.h42
-rw-r--r--include/uapi/linux/atmdev.h216
-rw-r--r--include/uapi/linux/atmioc.h42
-rw-r--r--include/uapi/linux/atmsap.h163
-rw-r--r--include/uapi/linux/bpf.h6084
-rw-r--r--include/uapi/linux/bpf_common.h57
-rw-r--r--include/uapi/linux/btf.h173
-rw-r--r--include/uapi/linux/can.h230
-rw-r--r--include/uapi/linux/can/netlink.h145
-rw-r--r--include/uapi/linux/can/vxcan.h13
-rw-r--r--include/uapi/linux/const.h36
-rw-r--r--include/uapi/linux/dcbnl.h769
-rw-r--r--include/uapi/linux/devlink.h611
-rw-r--r--include/uapi/linux/elf-em.h70
-rw-r--r--include/uapi/linux/fib_rules.h90
-rw-r--r--include/uapi/linux/filter.h90
-rw-r--r--include/uapi/linux/fou.h48
-rw-r--r--include/uapi/linux/gen_stats.h78
-rw-r--r--include/uapi/linux/genetlink.h102
-rw-r--r--include/uapi/linux/hdlc/ioctl.h94
-rw-r--r--include/uapi/linux/icmpv6.h178
-rw-r--r--include/uapi/linux/if.h296
-rw-r--r--include/uapi/linux/if_addr.h70
-rw-r--r--include/uapi/linux/if_addrlabel.h33
-rw-r--r--include/uapi/linux/if_alg.h60
-rw-r--r--include/uapi/linux/if_arp.h164
-rw-r--r--include/uapi/linux/if_bonding.h155
-rw-r--r--include/uapi/linux/if_bridge.h739
-rw-r--r--include/uapi/linux/if_ether.h173
-rw-r--r--include/uapi/linux/if_infiniband.h30
-rw-r--r--include/uapi/linux/if_link.h1252
-rw-r--r--include/uapi/linux/if_macsec.h192
-rw-r--r--include/uapi/linux/if_packet.h316
-rw-r--r--include/uapi/linux/if_tun.h114
-rw-r--r--include/uapi/linux/if_tunnel.h183
-rw-r--r--include/uapi/linux/if_vlan.h66
-rw-r--r--include/uapi/linux/ife.h19
-rw-r--r--include/uapi/linux/ila.h68
-rw-r--r--include/uapi/linux/in.h314
-rw-r--r--include/uapi/linux/in6.h301
-rw-r--r--include/uapi/linux/in_route.h33
-rw-r--r--include/uapi/linux/inet_diag.h239
-rw-r--r--include/uapi/linux/ip.h177
-rw-r--r--include/uapi/linux/ip6_tunnel.h56
-rw-r--r--include/uapi/linux/ipsec.h48
-rw-r--r--include/uapi/linux/kernel.h8
-rw-r--r--include/uapi/linux/l2tp.h205
-rw-r--r--include/uapi/linux/libc-compat.h267
-rw-r--r--include/uapi/linux/limits.h21
-rw-r--r--include/uapi/linux/lwtunnel.h113
-rw-r--r--include/uapi/linux/magic.h101
-rw-r--r--include/uapi/linux/mpls.h77
-rw-r--r--include/uapi/linux/mpls_iptunnel.h31
-rw-r--r--include/uapi/linux/mptcp.h194
-rw-r--r--include/uapi/linux/neighbour.h199
-rw-r--r--include/uapi/linux/net.h58
-rw-r--r--include/uapi/linux/net_namespace.h26
-rw-r--r--include/uapi/linux/netconf.h30
-rw-r--r--include/uapi/linux/netdevice.h66
-rw-r--r--include/uapi/linux/netfilter.h75
-rw-r--r--include/uapi/linux/netfilter/ipset/ip_set.h314
-rw-r--r--include/uapi/linux/netfilter/x_tables.h186
-rw-r--r--include/uapi/linux/netfilter/xt_set.h94
-rw-r--r--include/uapi/linux/netfilter/xt_tcpudp.h37
-rw-r--r--include/uapi/linux/netfilter_ipv4.h53
-rw-r--r--include/uapi/linux/netfilter_ipv4/ip_tables.h229
-rw-r--r--include/uapi/linux/netfilter_ipv6.h50
-rw-r--r--include/uapi/linux/netfilter_ipv6/ip6_tables.h270
-rw-r--r--include/uapi/linux/netlink.h355
-rw-r--r--include/uapi/linux/netlink_diag.h65
-rw-r--r--include/uapi/linux/nexthop.h104
-rw-r--r--include/uapi/linux/packet_diag.h81
-rw-r--r--include/uapi/linux/param.h7
-rw-r--r--include/uapi/linux/pfkeyv2.h384
-rw-r--r--include/uapi/linux/pkt_cls.h778
-rw-r--r--include/uapi/linux/pkt_sched.h1266
-rw-r--r--include/uapi/linux/posix_types.h38
-rw-r--r--include/uapi/linux/rpl.h48
-rw-r--r--include/uapi/linux/rpl_iptunnel.h21
-rw-r--r--include/uapi/linux/rtnetlink.h808
-rw-r--r--include/uapi/linux/sctp.h1210
-rw-r--r--include/uapi/linux/seg6.h55
-rw-r--r--include/uapi/linux/seg6_genl.h33
-rw-r--r--include/uapi/linux/seg6_hmac.h23
-rw-r--r--include/uapi/linux/seg6_iptunnel.h40
-rw-r--r--include/uapi/linux/seg6_local.h111
-rw-r--r--include/uapi/linux/snmp.h348
-rw-r--r--include/uapi/linux/sock_diag.h65
-rw-r--r--include/uapi/linux/socket.h29
-rw-r--r--include/uapi/linux/sockios.h174
-rw-r--r--include/uapi/linux/stddef.h6
-rw-r--r--include/uapi/linux/sysinfo.h25
-rw-r--r--include/uapi/linux/tc_act/tc_bpf.h35
-rw-r--r--include/uapi/linux/tc_act/tc_connmark.h22
-rw-r--r--include/uapi/linux/tc_act/tc_csum.h33
-rw-r--r--include/uapi/linux/tc_act/tc_ct.h41
-rw-r--r--include/uapi/linux/tc_act/tc_ctinfo.h29
-rw-r--r--include/uapi/linux/tc_act/tc_defact.h21
-rw-r--r--include/uapi/linux/tc_act/tc_gact.h33
-rw-r--r--include/uapi/linux/tc_act/tc_gate.h47
-rw-r--r--include/uapi/linux/tc_act/tc_ife.h32
-rw-r--r--include/uapi/linux/tc_act/tc_ipt.h20
-rw-r--r--include/uapi/linux/tc_act/tc_mirred.h28
-rw-r--r--include/uapi/linux/tc_act/tc_mpls.h34
-rw-r--r--include/uapi/linux/tc_act/tc_nat.h27
-rw-r--r--include/uapi/linux/tc_act/tc_pedit.h70
-rw-r--r--include/uapi/linux/tc_act/tc_sample.h25
-rw-r--r--include/uapi/linux/tc_act/tc_skbedit.h52
-rw-r--r--include/uapi/linux/tc_act/tc_skbmod.h38
-rw-r--r--include/uapi/linux/tc_act/tc_tunnel_key.h99
-rw-r--r--include/uapi/linux/tc_act/tc_vlan.h41
-rw-r--r--include/uapi/linux/tc_ematch/tc_em_cmp.h26
-rw-r--r--include/uapi/linux/tc_ematch/tc_em_ipt.h20
-rw-r--r--include/uapi/linux/tc_ematch/tc_em_meta.h93
-rw-r--r--include/uapi/linux/tc_ematch/tc_em_nbyte.h14
-rw-r--r--include/uapi/linux/tcp.h362
-rw-r--r--include/uapi/linux/tcp_metrics.h61
-rw-r--r--include/uapi/linux/tipc.h315
-rw-r--r--include/uapi/linux/tipc_netlink.h341
-rw-r--r--include/uapi/linux/tipc_sockets_diag.h17
-rw-r--r--include/uapi/linux/tls.h142
-rw-r--r--include/uapi/linux/types.h50
-rw-r--r--include/uapi/linux/udp.h47
-rw-r--r--include/uapi/linux/unix_diag.h61
-rw-r--r--include/uapi/linux/vdpa.h40
-rw-r--r--include/uapi/linux/veth.h13
-rw-r--r--include/uapi/linux/virtio_ids.h60
-rw-r--r--include/uapi/linux/vm_sockets_diag.h34
-rw-r--r--include/uapi/linux/xdp_diag.h83
-rw-r--r--include/uapi/linux/xfrm.h541
-rw-r--r--include/utils.h372
-rw-r--r--include/version.h1
-rw-r--r--include/xt-internal.h67
-rw-r--r--include/xtables.h598
-rw-r--r--ip/.gitignore2
-rw-r--r--ip/Makefile55
-rwxr-xr-xip/ifcfg150
-rw-r--r--ip/ila_common.h106
-rw-r--r--ip/ip.c328
-rw-r--r--ip/ip6tunnel.c450
-rw-r--r--ip/ip_common.h173
-rw-r--r--ip/ipaddress.c2521
-rw-r--r--ip/ipaddrlabel.c273
-rw-r--r--ip/ipfou.c354
-rw-r--r--ip/ipila.c308
-rw-r--r--ip/ipl2tp.c850
-rw-r--r--ip/iplink.c1780
-rw-r--r--ip/iplink_bareudp.c152
-rw-r--r--ip/iplink_bond.c832
-rw-r--r--ip/iplink_bond_slave.c189
-rw-r--r--ip/iplink_bridge.c897
-rw-r--r--ip/iplink_bridge_slave.c435
-rw-r--r--ip/iplink_can.c604
-rw-r--r--ip/iplink_dummy.c17
-rw-r--r--ip/iplink_geneve.c386
-rw-r--r--ip/iplink_hsr.c170
-rw-r--r--ip/iplink_ifb.c17
-rw-r--r--ip/iplink_ipoib.c145
-rw-r--r--ip/iplink_ipvlan.c133
-rw-r--r--ip/iplink_macvlan.c303
-rw-r--r--ip/iplink_netdevsim.c16
-rw-r--r--ip/iplink_nlmon.c17
-rw-r--r--ip/iplink_rmnet.c81
-rw-r--r--ip/iplink_team.c26
-rw-r--r--ip/iplink_vcan.c17
-rw-r--r--ip/iplink_vlan.c280
-rw-r--r--ip/iplink_vrf.c226
-rw-r--r--ip/iplink_vxcan.c88
-rw-r--r--ip/iplink_vxlan.c657
-rw-r--r--ip/iplink_xdp.c199
-rw-r--r--ip/iplink_xstats.c82
-rw-r--r--ip/ipmacsec.c1451
-rw-r--r--ip/ipmaddr.c374
-rw-r--r--ip/ipmonitor.c362
-rw-r--r--ip/ipmptcp.c561
-rw-r--r--ip/ipmroute.c340
-rw-r--r--ip/ipneigh.c706
-rw-r--r--ip/ipnetconf.c250
-rw-r--r--ip/ipnetns.c1103
-rw-r--r--ip/ipnexthop.c1031
-rw-r--r--ip/ipntable.c701
-rw-r--r--ip/ipprefix.c98
-rw-r--r--ip/iproute.c2360
-rw-r--r--ip/iproute_lwtunnel.c1691
-rw-r--r--ip/iprule.c1071
-rw-r--r--ip/ipseg6.c253
-rw-r--r--ip/iptoken.c208
-rw-r--r--ip/iptunnel.c597
-rw-r--r--ip/iptuntap.c564
-rw-r--r--ip/ipvrf.c652
-rw-r--r--ip/ipxfrm.c1564
-rw-r--r--ip/link_gre.c581
-rw-r--r--ip/link_gre6.c639
-rw-r--r--ip/link_ip6tnl.c466
-rw-r--r--ip/link_iptnl.c497
-rw-r--r--ip/link_veth.c86
-rw-r--r--ip/link_vti.c216
-rw-r--r--ip/link_vti6.c218
-rw-r--r--ip/link_xfrm.c78
-rwxr-xr-xip/routef10
-rwxr-xr-xip/routel72
-rw-r--r--ip/rtm_map.c129
-rw-r--r--ip/rtmon.c184
-rwxr-xr-xip/rtpr5
-rw-r--r--ip/static-syms.c15
-rw-r--r--ip/tcp_metrics.c542
-rw-r--r--ip/tunnel.c452
-rw-r--r--ip/tunnel.h62
-rw-r--r--ip/xfrm.h145
-rw-r--r--ip/xfrm_monitor.c426
-rw-r--r--ip/xfrm_policy.c1204
-rw-r--r--ip/xfrm_state.c1497
-rw-r--r--lib/Makefile32
-rw-r--r--lib/bpf_glue.c89
-rw-r--r--lib/bpf_legacy.c3335
-rw-r--r--lib/bpf_libbpf.c349
-rw-r--r--lib/cg_map.c134
-rw-r--r--lib/color.c183
-rw-r--r--lib/coverity_model.c17
-rw-r--r--lib/exec.c46
-rw-r--r--lib/fs.c342
-rw-r--r--lib/inet_proto.c69
-rw-r--r--lib/json_print.c353
-rw-r--r--lib/json_print_math.c37
-rw-r--r--lib/json_writer.c389
-rw-r--r--lib/libgenl.c159
-rw-r--r--lib/libnetlink.c1562
-rw-r--r--lib/ll_addr.c89
-rw-r--r--lib/ll_map.c410
-rw-r--r--lib/ll_proto.c118
-rw-r--r--lib/ll_types.c122
-rw-r--r--lib/mnl_utils.c248
-rw-r--r--lib/mpls_ntop.c52
-rw-r--r--lib/mpls_pton.c63
-rw-r--r--lib/names.c152
-rw-r--r--lib/namespace.c145
-rw-r--r--lib/rt_names.c788
-rw-r--r--lib/utils.c1945
-rw-r--r--lib/utils_math.c123
-rw-r--r--man/Makefile20
-rw-r--r--man/man3/Makefile18
-rw-r--r--man/man3/libnetlink.3200
-rw-r--r--man/man7/Makefile18
-rw-r--r--man/man7/tc-hfsc.7563
-rw-r--r--man/man8/.gitignore5
-rw-r--r--man/man8/Makefile28
-rw-r--r--man/man8/arpd.869
-rw-r--r--man/man8/bridge.8944
-rw-r--r--man/man8/ctstat.81
-rw-r--r--man/man8/dcb-app.8237
-rw-r--r--man/man8/dcb-buffer.8126
-rw-r--r--man/man8/dcb-dcbx.8108
-rw-r--r--man/man8/dcb-ets.8194
-rw-r--r--man/man8/dcb-maxrate.894
-rw-r--r--man/man8/dcb-pfc.8127
-rw-r--r--man/man8/dcb.8156
-rw-r--r--man/man8/devlink-dev.8308
-rw-r--r--man/man8/devlink-dpipe.899
-rw-r--r--man/man8/devlink-health.8256
-rw-r--r--man/man8/devlink-monitor.839
-rw-r--r--man/man8/devlink-port.8340
-rw-r--r--man/man8/devlink-region.8132
-rw-r--r--man/man8/devlink-resource.879
-rw-r--r--man/man8/devlink-sb.8324
-rw-r--r--man/man8/devlink-trap.8195
-rw-r--r--man/man8/devlink.8139
-rw-r--r--man/man8/genl.877
-rw-r--r--man/man8/ifcfg.848
-rw-r--r--man/man8/ifstat.877
-rw-r--r--man/man8/ip-address.8.in465
-rw-r--r--man/man8/ip-addrlabel.856
-rw-r--r--man/man8/ip-fou.8126
-rw-r--r--man/man8/ip-gue.81
-rw-r--r--man/man8/ip-l2tp.8412
-rw-r--r--man/man8/ip-link.8.in2652
-rw-r--r--man/man8/ip-macsec.8133
-rw-r--r--man/man8/ip-maddress.859
-rw-r--r--man/man8/ip-monitor.8133
-rw-r--r--man/man8/ip-mptcp.8157
-rw-r--r--man/man8/ip-mroute.858
-rw-r--r--man/man8/ip-neighbour.8281
-rw-r--r--man/man8/ip-netconf.836
-rw-r--r--man/man8/ip-netns.8.in271
-rw-r--r--man/man8/ip-nexthop.8327
-rw-r--r--man/man8/ip-ntable.8106
-rw-r--r--man/man8/ip-route.8.in1177
-rw-r--r--man/man8/ip-rule.8358
-rw-r--r--man/man8/ip-sr.858
-rw-r--r--man/man8/ip-tcp_metrics.8143
-rw-r--r--man/man8/ip-token.875
-rw-r--r--man/man8/ip-tunnel.8281
-rw-r--r--man/man8/ip-vrf.8111
-rw-r--r--man/man8/ip-xfrm.8748
-rw-r--r--man/man8/ip.8433
-rw-r--r--man/man8/lnstat.8262
-rw-r--r--man/man8/nstat.81
-rw-r--r--man/man8/rdma-dev.898
-rw-r--r--man/man8/rdma-link.8104
-rw-r--r--man/man8/rdma-resource.8115
-rw-r--r--man/man8/rdma-statistic.8200
-rw-r--r--man/man8/rdma-system.882
-rw-r--r--man/man8/rdma.8137
-rw-r--r--man/man8/routef.81
-rw-r--r--man/man8/routel.827
-rw-r--r--man/man8/rtacct.861
-rw-r--r--man/man8/rtmon.868
-rw-r--r--man/man8/rtpr.825
-rw-r--r--man/man8/rtstat.81
-rw-r--r--man/man8/ss.8584
-rw-r--r--man/man8/tc-actions.8288
-rw-r--r--man/man8/tc-basic.834
-rw-r--r--man/man8/tc-bfifo.874
-rw-r--r--man/man8/tc-bpf.8986
-rw-r--r--man/man8/tc-cake.8726
-rw-r--r--man/man8/tc-cbq-details.8423
-rw-r--r--man/man8/tc-cbq.8351
-rw-r--r--man/man8/tc-cbs.8124
-rw-r--r--man/man8/tc-cgroup.880
-rw-r--r--man/man8/tc-choke.863
-rw-r--r--man/man8/tc-codel.8122
-rw-r--r--man/man8/tc-connmark.855
-rw-r--r--man/man8/tc-csum.872
-rw-r--r--man/man8/tc-ct.8107
-rw-r--r--man/man8/tc-ctinfo.8171
-rw-r--r--man/man8/tc-drr.894
-rw-r--r--man/man8/tc-ematch.8160
-rw-r--r--man/man8/tc-etf.8151
-rw-r--r--man/man8/tc-ets.8192
-rw-r--r--man/man8/tc-flow.8267
-rw-r--r--man/man8/tc-flower.8483
-rw-r--r--man/man8/tc-fq.8107
-rw-r--r--man/man8/tc-fq_codel.8124
-rw-r--r--man/man8/tc-fq_pie.8166
-rw-r--r--man/man8/tc-fw.866
-rw-r--r--man/man8/tc-gate.8123
-rw-r--r--man/man8/tc-hfsc.861
-rw-r--r--man/man8/tc-htb.8173
-rw-r--r--man/man8/tc-ife.8143
-rw-r--r--man/man8/tc-matchall.887
-rw-r--r--man/man8/tc-mirred.899
-rw-r--r--man/man8/tc-mpls.8194
-rw-r--r--man/man8/tc-mqprio.8150
-rw-r--r--man/man8/tc-nat.878
-rw-r--r--man/man8/tc-netem.8236
-rw-r--r--man/man8/tc-pedit.8396
-rw-r--r--man/man8/tc-pfifo.81
-rw-r--r--man/man8/tc-pfifo_fast.857
-rw-r--r--man/man8/tc-pie.8148
-rw-r--r--man/man8/tc-police.8168
-rw-r--r--man/man8/tc-prio.8185
-rw-r--r--man/man8/tc-red.8180
-rw-r--r--man/man8/tc-route.874
-rw-r--r--man/man8/tc-sample.8122
-rw-r--r--man/man8/tc-sfb.8213
-rw-r--r--man/man8/tc-sfq.8222
-rw-r--r--man/man8/tc-simple.898
-rw-r--r--man/man8/tc-skbedit.874
-rw-r--r--man/man8/tc-skbmod.8138
-rw-r--r--man/man8/tc-skbprio.870
-rw-r--r--man/man8/tc-stab.8163
-rw-r--r--man/man8/tc-taprio.8223
-rw-r--r--man/man8/tc-tbf.8141
-rw-r--r--man/man8/tc-tcindex.858
-rw-r--r--man/man8/tc-tunnel_key.8172
-rw-r--r--man/man8/tc-u32.8674
-rw-r--r--man/man8/tc-vlan.8164
-rw-r--r--man/man8/tc-xt.842
-rw-r--r--man/man8/tc.8915
-rw-r--r--man/man8/tipc-bearer.8250
-rw-r--r--man/man8/tipc-link.8383
-rw-r--r--man/man8/tipc-media.887
-rw-r--r--man/man8/tipc-nametable.8110
-rw-r--r--man/man8/tipc-node.872
-rw-r--r--man/man8/tipc-peer.852
-rw-r--r--man/man8/tipc-socket.859
-rw-r--r--man/man8/tipc.8109
-rw-r--r--man/man8/vdpa-dev.896
-rw-r--r--man/man8/vdpa-mgmtdev.853
-rw-r--r--man/man8/vdpa.876
-rw-r--r--misc/.gitignore7
-rw-r--r--misc/Makefile42
-rw-r--r--misc/arpd.c837
-rw-r--r--misc/ifstat.c1034
-rw-r--r--misc/lnstat.c378
-rw-r--r--misc/lnstat.h43
-rw-r--r--misc/lnstat_util.c330
-rw-r--r--misc/nstat.c774
-rw-r--r--misc/rtacct.c627
-rw-r--r--misc/ss.c5742
-rw-r--r--misc/ss_util.h22
-rw-r--r--misc/ssfilter.h33
-rw-r--r--misc/ssfilter.y368
-rw-r--r--misc/ssfilter_check.c103
-rw-r--r--netem/.gitignore5
-rw-r--r--netem/Makefile32
-rw-r--r--netem/README.distribution97
-rw-r--r--netem/experimental.dat13448
-rw-r--r--netem/maketable.c234
-rw-r--r--netem/normal.c51
-rw-r--r--netem/pareto.c41
-rw-r--r--netem/paretonormal.c81
-rw-r--r--netem/stats.c77
-rw-r--r--rdma/.gitignore1
-rw-r--r--rdma/Makefile26
-rw-r--r--rdma/dev.c367
-rw-r--r--rdma/include/uapi/rdma/ib_user_sa.h77
-rw-r--r--rdma/include/uapi/rdma/ib_user_verbs.h1304
-rw-r--r--rdma/include/uapi/rdma/rdma_netlink.h574
-rw-r--r--rdma/include/uapi/rdma/rdma_user_cm.h328
-rw-r--r--rdma/link.c384
-rw-r--r--rdma/rdma.c170
-rw-r--r--rdma/rdma.h146
-rw-r--r--rdma/res-cmid.c254
-rw-r--r--rdma/res-cq.c181
-rw-r--r--rdma/res-mr.c144
-rw-r--r--rdma/res-pd.c131
-rw-r--r--rdma/res-qp.c236
-rw-r--r--rdma/res.c250
-rw-r--r--rdma/res.h168
-rw-r--r--rdma/stat-mr.c86
-rw-r--r--rdma/stat.c793
-rw-r--r--rdma/stat.h26
-rw-r--r--rdma/sys.c135
-rw-r--r--rdma/utils.c945
-rw-r--r--schema/bridge_fdb_schema.json62
-rw-r--r--tc/.gitignore5
-rw-r--r--tc/Makefile205
-rw-r--r--tc/e_bpf.c180
-rw-r--r--tc/em_canid.c190
-rw-r--r--tc/em_cmp.c184
-rw-r--r--tc/em_ipset.c263
-rw-r--r--tc/em_ipt.c210
-rw-r--r--tc/em_meta.c545
-rw-r--r--tc/em_nbyte.c140
-rw-r--r--tc/em_u32.c175
-rw-r--r--tc/emp_ematch.l146
-rw-r--r--tc/emp_ematch.y95
-rw-r--r--tc/f_basic.c149
-rw-r--r--tc/f_bpf.c270
-rw-r--r--tc/f_cgroup.c114
-rw-r--r--tc/f_flow.c358
-rw-r--r--tc/f_flower.c2799
-rw-r--r--tc/f_fw.c165
-rw-r--r--tc/f_matchall.c172
-rw-r--r--tc/f_route.c179
-rw-r--r--tc/f_rsvp.c422
-rw-r--r--tc/f_tcindex.c185
-rw-r--r--tc/f_u32.c1340
-rw-r--r--tc/m_action.c851
-rw-r--r--tc/m_bpf.c219
-rw-r--r--tc/m_connmark.c149
-rw-r--r--tc/m_csum.c229
-rw-r--r--tc/m_ct.c496
-rw-r--r--tc/m_ctinfo.c268
-rw-r--r--tc/m_ematch.c569
-rw-r--r--tc/m_ematch.h110
-rw-r--r--tc/m_estimator.c64
-rw-r--r--tc/m_gact.c222
-rw-r--r--tc/m_gate.c578
-rw-r--r--tc/m_ife.c336
-rw-r--r--tc/m_ipt.c516
-rw-r--r--tc/m_mirred.c330
-rw-r--r--tc/m_mpls.c295
-rw-r--r--tc/m_nat.c197
-rw-r--r--tc/m_pedit.c856
-rw-r--r--tc/m_pedit.h77
-rw-r--r--tc/m_police.c364
-rw-r--r--tc/m_sample.c190
-rw-r--r--tc/m_simple.c206
-rw-r--r--tc/m_skbedit.c278
-rw-r--r--tc/m_skbmod.c234
-rw-r--r--tc/m_tunnel_key.c736
-rw-r--r--tc/m_vlan.c313
-rw-r--r--tc/m_xt.c405
-rw-r--r--tc/m_xt_old.c437
-rw-r--r--tc/p_eth.c73
-rw-r--r--tc/p_icmp.c60
-rw-r--r--tc/p_ip.c161
-rw-r--r--tc/p_ip6.c104
-rw-r--r--tc/p_tcp.c72
-rw-r--r--tc/p_udp.c66
-rw-r--r--tc/q_atm.c250
-rw-r--r--tc/q_cake.c829
-rw-r--r--tc/q_cbq.c594
-rw-r--r--tc/q_cbs.c141
-rw-r--r--tc/q_choke.c238
-rw-r--r--tc/q_clsact.c34
-rw-r--r--tc/q_codel.c230
-rw-r--r--tc/q_drr.c119
-rw-r--r--tc/q_dsmark.c166
-rw-r--r--tc/q_etf.c193
-rw-r--r--tc/q_ets.c342
-rw-r--r--tc/q_fifo.c99
-rw-r--r--tc/q_fq.c449
-rw-r--r--tc/q_fq_codel.c315
-rw-r--r--tc/q_fq_pie.c315
-rw-r--r--tc/q_gred.c507
-rw-r--r--tc/q_hfsc.c416
-rw-r--r--tc/q_hhf.c210
-rw-r--r--tc/q_htb.c382
-rw-r--r--tc/q_ingress.c51
-rw-r--r--tc/q_mqprio.c324
-rw-r--r--tc/q_multiq.c82
-rw-r--r--tc/q_netem.c838
-rw-r--r--tc/q_pie.c252
-rw-r--r--tc/q_plug.c76
-rw-r--r--tc/q_prio.c129
-rw-r--r--tc/q_qfq.c115
-rw-r--r--tc/q_red.c283
-rw-r--r--tc/q_rr.c119
-rw-r--r--tc/q_sfb.c219
-rw-r--r--tc/q_sfq.c284
-rw-r--r--tc/q_skbprio.c85
-rw-r--r--tc/q_taprio.c511
-rw-r--r--tc/q_tbf.c356
-rw-r--r--tc/static-syms.c15
-rw-r--r--tc/tc.c365
-rw-r--r--tc/tc_cbq.c58
-rw-r--r--tc/tc_cbq.h10
-rw-r--r--tc/tc_class.c490
-rw-r--r--tc/tc_common.h30
-rw-r--r--tc/tc_core.c258
-rw-r--r--tc/tc_core.h36
-rw-r--r--tc/tc_estimator.c45
-rw-r--r--tc/tc_exec.c108
-rw-r--r--tc/tc_filter.c799
-rw-r--r--tc/tc_monitor.c120
-rw-r--r--tc/tc_qdisc.c546
-rw-r--r--tc/tc_qevent.c218
-rw-r--r--tc/tc_qevent.h51
-rw-r--r--tc/tc_red.c124
-rw-r--r--tc/tc_red.h11
-rw-r--r--tc/tc_stab.c149
-rw-r--r--tc/tc_util.c861
-rw-r--r--tc/tc_util.h136
-rw-r--r--testsuite/Makefile97
-rw-r--r--testsuite/iproute2/Makefile34
-rw-r--r--testsuite/lib/generic.sh134
-rwxr-xr-xtestsuite/tests/bridge/vlan/show.t30
-rwxr-xr-xtestsuite/tests/bridge/vlan/tunnelshow.t33
-rwxr-xr-xtestsuite/tests/ip/link/add_type_bareudp.t86
-rwxr-xr-xtestsuite/tests/ip/link/add_type_xfrm.t32
-rwxr-xr-xtestsuite/tests/ip/link/new_link.t15
-rwxr-xr-xtestsuite/tests/ip/link/show_dev_wo_vf_rate.t6
-rwxr-xr-xtestsuite/tests/ip/netns/set_nsid.t22
-rwxr-xr-xtestsuite/tests/ip/netns/set_nsid_batch.t18
-rwxr-xr-xtestsuite/tests/ip/route/add_default_route.t35
-rwxr-xr-xtestsuite/tests/ip/tunnel/add_tunnel.t27
-rw-r--r--testsuite/tests/ss/ss1.dumpbin0 -> 720 bytes
-rwxr-xr-xtestsuite/tests/ss/ssfilter.t48
-rwxr-xr-xtestsuite/tests/tc/batch.t23
-rwxr-xr-xtestsuite/tests/tc/cbq.t10
-rwxr-xr-xtestsuite/tests/tc/dsmark.t31
-rwxr-xr-xtestsuite/tests/tc/flower_mpls.t82
-rwxr-xr-xtestsuite/tests/tc/mpls.t69
-rwxr-xr-xtestsuite/tests/tc/pedit.t217
-rwxr-xr-xtestsuite/tests/tc/policer.t13
-rwxr-xr-xtestsuite/tests/tc/vlan.t86
-rw-r--r--testsuite/tools/Makefile9
-rw-r--r--testsuite/tools/generate_nlmsg.c116
-rw-r--r--tipc/.gitignore1
-rw-r--r--tipc/Makefile30
-rw-r--r--tipc/README63
-rw-r--r--tipc/bearer.c1098
-rw-r--r--tipc/bearer.h26
-rw-r--r--tipc/cmdl.c134
-rw-r--r--tipc/cmdl.h58
-rw-r--r--tipc/link.c1241
-rw-r--r--tipc/link.h21
-rw-r--r--tipc/media.c277
-rw-r--r--tipc/media.h21
-rw-r--r--tipc/misc.c169
-rw-r--r--tipc/misc.h23
-rw-r--r--tipc/msg.c52
-rw-r--r--tipc/msg.h20
-rw-r--r--tipc/nametable.c122
-rw-r--r--tipc/nametable.h21
-rw-r--r--tipc/node.c504
-rw-r--r--tipc/node.h21
-rw-r--r--tipc/peer.c146
-rw-r--r--tipc/peer.h21
-rw-r--r--tipc/socket.c137
-rw-r--r--tipc/socket.h21
-rw-r--r--tipc/tipc.c134
-rw-r--r--vdpa/.gitignore1
-rw-r--r--vdpa/Makefile25
-rw-r--r--vdpa/include/uapi/linux/vdpa.h40
-rw-r--r--vdpa/include/uapi/linux/virtio_ids.h58
-rw-r--r--vdpa/vdpa.c675
683 files changed, 196384 insertions, 0 deletions
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..901467b
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,130 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# clang-format configuration file. Intended for clang-format >= 4.
+#
+# For more information, see:
+#
+# Documentation/process/clang-format.rst
+# https://clang.llvm.org/docs/ClangFormat.html
+# https://clang.llvm.org/docs/ClangFormatStyleOptions.html
+#
+---
+AccessModifierOffset: -4
+AlignAfterOpenBracket: Align
+AlignConsecutiveAssignments: false
+AlignConsecutiveDeclarations: false
+#AlignEscapedNewlines: Left # Unknown to clang-format-4.0
+AlignOperands: true
+AlignTrailingComments: false
+AllowAllParametersOfDeclarationOnNextLine: false
+AllowShortBlocksOnASingleLine: false
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: None
+AllowShortIfStatementsOnASingleLine: false
+AllowShortLoopsOnASingleLine: false
+AlwaysBreakAfterDefinitionReturnType: None
+AlwaysBreakAfterReturnType: None
+AlwaysBreakBeforeMultilineStrings: false
+AlwaysBreakTemplateDeclarations: false
+BinPackArguments: true
+BinPackParameters: true
+BraceWrapping:
+ AfterClass: false
+ AfterControlStatement: false
+ AfterEnum: false
+ AfterFunction: true
+ AfterNamespace: true
+ AfterObjCDeclaration: false
+ AfterStruct: false
+ AfterUnion: false
+ #AfterExternBlock: false # Unknown to clang-format-5.0
+ BeforeCatch: false
+ BeforeElse: false
+ IndentBraces: false
+ #SplitEmptyFunction: true # Unknown to clang-format-4.0
+ #SplitEmptyRecord: true # Unknown to clang-format-4.0
+ #SplitEmptyNamespace: true # Unknown to clang-format-4.0
+BreakBeforeBinaryOperators: None
+BreakBeforeBraces: Custom
+#BreakBeforeInheritanceComma: false # Unknown to clang-format-4.0
+BreakBeforeTernaryOperators: false
+BreakConstructorInitializersBeforeComma: false
+#BreakConstructorInitializers: BeforeComma # Unknown to clang-format-4.0
+BreakAfterJavaFieldAnnotations: false
+BreakStringLiterals: false
+ColumnLimit: 80
+CommentPragmas: '^ IWYU pragma:'
+#CompactNamespaces: false # Unknown to clang-format-4.0
+ConstructorInitializerAllOnOneLineOrOnePerLine: false
+ConstructorInitializerIndentWidth: 8
+ContinuationIndentWidth: 8
+Cpp11BracedListStyle: false
+DerivePointerAlignment: false
+DisableFormat: false
+ExperimentalAutoDetectBinPacking: false
+#FixNamespaceComments: false # Unknown to clang-format-4.0
+
+# Taken from:
+# git grep -h '^#define [^[:space:]]*for_each[^[:space:]]*(' include/ \
+# | sed "s,^#define \([^[:space:]]*for_each[^[:space:]]*\)(.*$, - '\1'," \
+# | sort | uniq
+ForEachMacros:
+ - 'list_for_each_entry'
+ - 'list_for_each_entry_safe'
+ - 'mnl_attr_for_each_nested'
+ - 'hlist_for_each'
+ - 'hlist_for_each_safe'
+ - 'hlist_for_each_entry'
+
+#IncludeBlocks: Preserve # Unknown to clang-format-5.0
+IncludeCategories:
+ - Regex: '.*'
+ Priority: 1
+IncludeIsMainRegex: '(Test)?$'
+IndentCaseLabels: false
+#IndentPPDirectives: None # Unknown to clang-format-5.0
+IndentWidth: 8
+IndentWrappedFunctionNames: false
+JavaScriptQuotes: Leave
+JavaScriptWrapImports: true
+KeepEmptyLinesAtTheStartOfBlocks: false
+MacroBlockBegin: ''
+MacroBlockEnd: ''
+MaxEmptyLinesToKeep: 1
+NamespaceIndentation: Inner
+#ObjCBinPackProtocolList: Auto # Unknown to clang-format-5.0
+ObjCBlockIndentWidth: 8
+ObjCSpaceAfterProperty: true
+ObjCSpaceBeforeProtocolList: true
+
+# Taken from git's rules
+#PenaltyBreakAssignment: 10 # Unknown to clang-format-4.0
+PenaltyBreakBeforeFirstCallParameter: 30
+PenaltyBreakComment: 10
+PenaltyBreakFirstLessLess: 0
+PenaltyBreakString: 10
+PenaltyExcessCharacter: 100
+PenaltyReturnTypeOnItsOwnLine: 60
+
+PointerAlignment: Right
+ReflowComments: false
+SortIncludes: false
+#SortUsingDeclarations: false # Unknown to clang-format-4.0
+SpaceAfterCStyleCast: false
+SpaceAfterTemplateKeyword: true
+SpaceBeforeAssignmentOperators: true
+#SpaceBeforeCtorInitializerColon: true # Unknown to clang-format-5.0
+#SpaceBeforeInheritanceColon: true # Unknown to clang-format-5.0
+SpaceBeforeParens: ControlStatements
+#SpaceBeforeRangeBasedForLoopColon: true # Unknown to clang-format-5.0
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 1
+SpacesInAngles: false
+SpacesInContainerLiterals: false
+SpacesInCStyleCastParentheses: false
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+Standard: Cpp03
+TabWidth: 8
+UseTab: Always
+...
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e5234a3
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,41 @@
+# locally generated
+Config
+static-syms.h
+config.*
+*.o
+*.a
+*.so
+*~
+\#*#
+
+# cscope
+cscope.*
+ncscope.*
+tags
+TAGS
+
+# git files that we don't want to ignore even it they are dot-files
+!.gitignore
+!.mailmap
+
+# for patch generation
+*.diff
+*.patch
+*.orig
+*.rej
+
+# for quilt
+.pc
+patches
+series
+
+# for gdb
+.gdbinit
+.gdb_history
+*.gdb
+
+# tests
+testsuite/results
+testsuite/iproute2/iproute2-this
+testsuite/tools/generate_nlmsg
+testsuite/tests/ip/link/dev_wo_vf_rate.nl
diff --git a/.mailmap b/.mailmap
new file mode 100644
index 0000000..fd40c91
--- /dev/null
+++ b/.mailmap
@@ -0,0 +1,22 @@
+#
+# This list is used by git-shortlog to fix a few botched name translations
+# in the git archive, either because the author's full name was messed up
+# and/or not always written the same way, making contributions from the
+# same person appearing not to be so or badly displayed.
+#
+# Format
+# Full name <goodaddress> <badaddress>
+Steve Wise <larrystevenwise@gmail.com> <swise@opengridcomputing.com>
+Steve Wise <larrystevenwise@gmail.com> <swise@chelsio.com>
+
+Stephen Hemminger <stephen@networkplumber.org> <sthemmin@microsoft.com>
+Stephen Hemminger <stephen@networkplumber.org> <shemming@brocade.com>
+Stephen Hemminger <stephen@networkplumber.org> <stephen.hemminger@vyatta.com>
+Stephen Hemminger <stephen@networkplumber.org> <shemminger@vyatta.com>
+Stephen Hemminger <stephen@networkplumber.org> <shemminger>
+Stephen Hemminger <stephen@networkplumber.org> <shemminger@linux-foundation.org>
+Stephen Hemminger <stephen@networkplumber.org> <shemminger@osdl.org>
+Stephen Hemminger <stephen@networkplumber.org> <osdl.org!shemminger>
+Stephen Hemminger <stephen@networkplumber.org> <osdl.net!shemminger>
+
+David Ahern <dsahern@gmail.com> <dsa@cumulusnetworks.com>
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..3912109
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,340 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..19bd163
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,126 @@
+# SPDX-License-Identifier: GPL-2.0
+# Top level Makefile for iproute2
+
+ifeq ("$(origin V)", "command line")
+VERBOSE = $(V)
+endif
+ifndef VERBOSE
+VERBOSE = 0
+endif
+
+ifeq ($(VERBOSE),0)
+MAKEFLAGS += --no-print-directory
+endif
+
+PREFIX?=/usr
+LIBDIR?=$(PREFIX)/lib
+SBINDIR?=/sbin
+CONFDIR?=/etc/iproute2
+NETNS_RUN_DIR?=/var/run/netns
+NETNS_ETC_DIR?=/etc/netns
+DATADIR?=$(PREFIX)/share
+HDRDIR?=$(PREFIX)/include/iproute2
+DOCDIR?=$(DATADIR)/doc/iproute2
+MANDIR?=$(DATADIR)/man
+ARPDDIR?=/var/lib/arpd
+KERNEL_INCLUDE?=/usr/include
+BASH_COMPDIR?=$(DATADIR)/bash-completion/completions
+
+# Path to db_185.h include
+DBM_INCLUDE:=$(DESTDIR)/usr/include
+
+SHARED_LIBS = y
+
+DEFINES= -DRESOLVE_HOSTNAMES -DLIBDIR=\"$(LIBDIR)\"
+ifneq ($(SHARED_LIBS),y)
+DEFINES+= -DNO_SHARED_LIBS
+endif
+
+DEFINES+=-DCONFDIR=\"$(CONFDIR)\" \
+ -DNETNS_RUN_DIR=\"$(NETNS_RUN_DIR)\" \
+ -DNETNS_ETC_DIR=\"$(NETNS_ETC_DIR)\"
+
+#options for mpls
+ADDLIB+=mpls_ntop.o mpls_pton.o
+
+CC := gcc
+HOSTCC ?= $(CC)
+DEFINES += -D_GNU_SOURCE
+# Turn on transparent support for LFS
+DEFINES += -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE
+CCOPTS = -O2 -pipe
+WFLAGS := -Wall -Wstrict-prototypes -Wmissing-prototypes
+WFLAGS += -Wmissing-declarations -Wold-style-definition -Wformat=2
+
+CFLAGS := $(WFLAGS) $(CCOPTS) -I../include -I../include/uapi $(DEFINES) $(CFLAGS)
+YACCFLAGS = -d -t -v
+
+SUBDIRS=lib ip tc bridge misc netem genl tipc devlink rdma dcb man vdpa
+
+LIBNETLINK=../lib/libutil.a ../lib/libnetlink.a
+LDLIBS += $(LIBNETLINK)
+
+all: config.mk
+ @set -e; \
+ for i in $(SUBDIRS); \
+ do echo; echo $$i; $(MAKE) -C $$i; done
+
+.PHONY: clean clobber distclean check cscope version
+
+help:
+ @echo "Make Targets:"
+ @echo " all - build binaries"
+ @echo " clean - remove products of build"
+ @echo " distclean - remove configuration and build"
+ @echo " install - install binaries on local machine"
+ @echo " check - run tests"
+ @echo " cscope - build cscope database"
+ @echo " version - update version"
+ @echo ""
+ @echo "Make Arguments:"
+ @echo " V=[0|1] - set build verbosity level"
+
+config.mk:
+ sh configure $(KERNEL_INCLUDE)
+
+install: all
+ install -m 0755 -d $(DESTDIR)$(SBINDIR)
+ install -m 0755 -d $(DESTDIR)$(CONFDIR)
+ install -m 0755 -d $(DESTDIR)$(ARPDDIR)
+ install -m 0755 -d $(DESTDIR)$(HDRDIR)
+ @for i in $(SUBDIRS); do $(MAKE) -C $$i install; done
+ install -m 0644 $(shell find etc/iproute2 -maxdepth 1 -type f) $(DESTDIR)$(CONFDIR)
+ install -m 0755 -d $(DESTDIR)$(BASH_COMPDIR)
+ install -m 0644 bash-completion/tc $(DESTDIR)$(BASH_COMPDIR)
+ install -m 0644 bash-completion/devlink $(DESTDIR)$(BASH_COMPDIR)
+ install -m 0644 include/bpf_elf.h $(DESTDIR)$(HDRDIR)
+
+version:
+ echo "static const char version[] = \""`git describe --tags --long`"\";" \
+ > include/version.h
+
+clean:
+ @for i in $(SUBDIRS) testsuite; \
+ do $(MAKE) -C $$i clean; done
+
+clobber:
+ touch config.mk
+ $(MAKE) clean
+ rm -f config.mk cscope.*
+
+distclean: clobber
+
+check: all
+ $(MAKE) -C testsuite
+ $(MAKE) -C testsuite alltests
+ @if command -v man >/dev/null 2>&1; then \
+ echo "Checking manpages for syntax errors..."; \
+ $(MAKE) -C man check; \
+ else \
+ echo "man not installed, skipping checks for syntax errors."; \
+ fi
+
+cscope:
+ cscope -b -q -R -Iinclude -sip -slib -smisc -snetem -stc
+
+.EXPORT_ALL_VARIABLES:
diff --git a/README b/README
new file mode 100644
index 0000000..fa0c786
--- /dev/null
+++ b/README
@@ -0,0 +1,42 @@
+This is a set of utilities for Linux networking.
+
+Information:
+ https://wiki.linuxfoundation.org/networking/iproute2
+
+Download:
+ http://www.kernel.org/pub/linux/utils/net/iproute2/
+
+Stable version repository:
+ git://git.kernel.org/pub/scm/network/iproute2/iproute2.git
+
+Development repository:
+ git://git.kernel.org/pub/scm/network/iproute2/iproute2-next.git
+
+How to compile this.
+--------------------
+1. libdbm
+
+arpd needs to have the berkeleydb development libraries. For Debian
+users this is the package with a name like libdbX.X-dev.
+DBM_INCLUDE points to the directory with db_185.h which
+is the include file used by arpd to get to the old format Berkeley
+database routines. Often this is in the db-devel package.
+
+2. make
+
+The makefile will automatically build a config.mk file which
+contains definitions of libraries that may or may not be available
+on the system such as: ATM, ELF, MNL, and SELINUX.
+
+3. include/uapi
+
+This package includes matching sanitized kernel headers because
+the build environment may not have up to date versions. See Makefile
+if you have special requirements and need to point at different
+kernel include files.
+
+Stephen Hemminger
+stephen@networkplumber.org
+
+Alexey Kuznetsov
+kuznet@ms2.inr.ac.ru
diff --git a/README.devel b/README.devel
new file mode 100644
index 0000000..60c9546
--- /dev/null
+++ b/README.devel
@@ -0,0 +1,18 @@
+Iproute2 development is closely tied to Linux kernel networking
+development. Most new features require a kernel and a utility component.
+
+Please submit both to the Linux networking mailing list
+ <netdev@vger.kernel.org>
+
+The current source for the stable version is in the git repository:
+ git://git.kernel.org/pub/scm/network/iproute2/iproute2.git
+
+The development git repository is available at the following address:
+ git://git.kernel.org/pub/scm/network/iproute2/iproute2-next.git
+
+The stable repository contains the source corresponding to the
+current code in the Linux networking tree (net), which in turn is
+aligned on the mainline Linux kernel (ie follows Linus).
+The iproute2-next repository tracks the code intended for the next
+release; it corresponds with networking development tree (net-next)
+in the kernel.
diff --git a/bash-completion/devlink b/bash-completion/devlink
new file mode 100644
index 0000000..361be9f
--- /dev/null
+++ b/bash-completion/devlink
@@ -0,0 +1,1006 @@
+# bash completion for devlink(8) -*- shell-script -*-
+
+# Get all the optional commands for devlink
+_devlink_get_optional_commands()
+{
+ local object=$1; shift
+
+ local filter_options=""
+ local options="$(devlink $object help 2>&1 \
+ | command sed -n -e "s/^.*devlink $object //p" \
+ | cut -d " " -f 1)"
+
+ # Remove duplicate options from "devlink $OBJECT help" command
+ local opt
+ for opt in $options; do
+ if [[ $filter_options =~ $opt ]]; then
+ continue
+ else
+ filter_options="$filter_options $opt"
+ fi
+ done
+
+ echo $filter_options
+}
+
+# Complete based on given word, for when an argument or an option name has
+# but a few possible arguments.
+_devlink_direct_complete()
+{
+ local dev port region value
+
+ case $1 in
+ dev)
+ value=$(devlink dev show 2>/dev/null)
+ ;;
+ param_name)
+ dev=${words[4]}
+ value=$(devlink -j dev param show 2>/dev/null \
+ | jq ".param[\"$dev\"][].name")
+ ;;
+ port)
+ value=$(devlink -j port show 2>/dev/null \
+ | jq '.port as $ports | $ports | keys[] as $key
+ | ($ports[$key].netdev // $key)')
+ ;;
+ region)
+ value=$(devlink -j region show 2>/dev/null \
+ | jq '.regions' | jq 'keys[]')
+ ;;
+ snapshot)
+ region=${words[3]}
+ value=$(devlink -j region show 2>/dev/null \
+ | jq ".regions[\"$region\"].snapshot[]")
+ ;;
+ trap)
+ dev=${words[3]}
+ value=$(devlink -j trap show 2>/dev/null \
+ | jq ".trap[\"$dev\"][].name")
+ ;;
+ trap_group)
+ dev=${words[4]}
+ value=$(devlink -j trap group show 2>/dev/null \
+ | jq ".trap_group[\"$dev\"][].name")
+ ;;
+ trap_policer)
+ dev=${words[4]}
+ value=$(devlink -j trap policer show 2>/dev/null \
+ | jq ".trap_policer[\"$dev\"][].policer")
+ ;;
+ health_dev)
+ value=$(devlink -j health show 2>/dev/null | jq '.health' \
+ | jq 'keys[]')
+ ;;
+ reporter)
+ dev=${words[cword - 2]}
+ value=$(devlink -j health show 2>/dev/null \
+ | jq ".health[\"$dev\"][].reporter")
+ ;;
+ pool)
+ dev=$pprev
+ value=$(devlink -j sb pool show 2>/dev/null \
+ | jq ".pool[\"$dev\"][].pool")
+ ;;
+ port_pool)
+ port=${words[5]}
+ value=$(devlink -j sb port pool show 2>/dev/null \
+ | jq ".port_pool[\"$port\"][].pool")
+ ;;
+ tc)
+ port=$pprev
+ value=$(devlink -j sb tc bind show 2>/dev/null \
+ | jq ".tc_bind[\"$port\"][].tc")
+ ;;
+ esac
+
+ COMPREPLY+=( $( compgen -W "$value" -- "$cur" ) )
+ # Remove colon containing prefix from COMPREPLY items in order to avoid
+ # wordbreaks with colon.
+ __ltrim_colon_completions "$cur"
+}
+
+# Completion for devlink dev eswitch set
+_devlink_dev_eswitch_set()
+{
+ local -A settings=(
+ [mode]=notseen
+ [inline-mode]=notseen
+ [encap-mode]=notseen
+ )
+
+ if [[ $cword -eq 5 ]]; then
+ COMPREPLY=( $( compgen -W "mode inline-mode encap-mode" -- "$cur" ) )
+ fi
+
+ # Mark seen settings
+ local word
+ for word in "${words[@]:5:${#words[@]}-1}"; do
+ if [[ -n $word ]]; then
+ if [[ "${settings[$word]}" ]]; then
+ settings[$word]=seen
+ fi
+ fi
+ done
+
+ case $prev in
+ mode)
+ COMPREPLY=( $( compgen -W "legacy switchdev" -- "$cur" ) )
+ return
+ ;;
+ inline-mode)
+ COMPREPLY=( $( compgen -W "none link network transport" -- \
+ "$cur" ) )
+ return
+ ;;
+ encap-mode)
+ COMPREPLY=( $( compgen -W "none basic" -- "$cur" ) )
+ return
+ ;;
+ esac
+
+ local -a comp_words=()
+
+ # Add settings not seen to completions
+ local setting
+ for setting in "${!settings[@]}"; do
+ if [ "${settings[$setting]}" = notseen ]; then
+ comp_words+=( "$setting" )
+ fi
+ done
+
+ COMPREPLY=( $( compgen -W "${comp_words[*]}" -- "$cur" ) )
+}
+
+# Completion for devlink dev eswitch
+_devlink_dev_eswitch()
+{
+ case "$cword" in
+ 3)
+ COMPREPLY=( $( compgen -W "show set" -- "$cur" ) )
+ return
+ ;;
+ 4)
+ _devlink_direct_complete "dev"
+ return
+ ;;
+ esac
+
+ case "${words[3]}" in
+ set)
+ _devlink_dev_eswitch_set
+ return
+ ;;
+ show)
+ return
+ ;;
+ esac
+}
+
+# Completion for devlink dev param set
+_devlink_dev_param_set()
+{
+ case $cword in
+ 7)
+ COMPREPLY=( $( compgen -W "value" -- "$cur" ) )
+ return
+ ;;
+ 8)
+ # String argument
+ return
+ ;;
+ 9)
+ COMPREPLY=( $( compgen -W "cmode" -- "$cur" ) )
+ return
+ ;;
+ 10)
+ COMPREPLY=( $( compgen -W "runtime driverinit permanent" -- \
+ "$cur" ) )
+ return
+ ;;
+ esac
+}
+
+# Completion for devlink dev param
+_devlink_dev_param()
+{
+ case "$cword" in
+ 3)
+ COMPREPLY=( $( compgen -W "show set" -- "$cur" ) )
+ return
+ ;;
+ 4)
+ _devlink_direct_complete "dev"
+ return
+ ;;
+ 5)
+ COMPREPLY=( $( compgen -W "name" -- "$cur" ) )
+ return
+ ;;
+ 6)
+ _devlink_direct_complete "param_name"
+ return
+ ;;
+ esac
+
+ if [[ "${words[3]}" == "set" ]]; then
+ _devlink_dev_param_set
+ fi
+}
+
+# Completion for devlink dev reload
+_devlink_dev_reload()
+{
+ case "$cword" in
+ 4)
+ COMPREPLY=( $( compgen -W "netns" -- "$cur" ) )
+ return
+ ;;
+ 5)
+ local nslist=$( ip netns list 2>/dev/null )
+ COMPREPLY=( $( compgen -W "$nslist" -- "$cur" ) )
+ return
+ ;;
+ esac
+}
+
+# Completion for devlink dev flash
+_devlink_dev_flash()
+{
+ case "$cword" in
+ 4)
+ COMPREPLY=( $( compgen -W "file" -- "$cur" ) )
+ return
+ ;;
+ 5)
+ _filedir
+ return
+ ;;
+ 6)
+ COMPREPLY=( $( compgen -W "component" -- "$cur" ) )
+ return
+ ;;
+ esac
+}
+
+# Completion for devlink dev
+_devlink_dev()
+{
+ case $command in
+ show|reload|info|flash)
+ if [[ $cword -le 3 ]]; then
+ _devlink_direct_complete "dev"
+ elif [[ $command == "reload" || $command == "flash" ]];then
+ _devlink_dev_$command
+ fi
+ return
+ ;;
+ eswitch|param)
+ _devlink_dev_$command
+ return
+ ;;
+ esac
+}
+
+# Completion for devlink port set
+_devlink_port_set()
+{
+ case "$cword" in
+ 3)
+ _devlink_direct_complete "port"
+ return
+ ;;
+ 4)
+ COMPREPLY=( $( compgen -W "type" -- "$cur" ) )
+ return
+ ;;
+ 5)
+ COMPREPLY=( $( compgen -W "eth ib auto" -- "$cur" ) )
+ return
+ ;;
+ esac
+}
+
+# Completion for devlink port split
+_devlink_port_split()
+{
+ case "$cword" in
+ 3)
+ _devlink_direct_complete "port"
+ return
+ ;;
+ 4)
+ COMPREPLY=( $( compgen -W "count" -- "$cur" ) )
+ return
+ ;;
+ 5)
+ # Integer argument
+ return
+ ;;
+ esac
+}
+
+# Completion for devlink port param set
+_devlink_port_param_set()
+{
+ case $cword in
+ 7)
+ COMPREPLY=( $( compgen -W "value" -- "$cur" ) )
+ return
+ ;;
+ 8)
+ # String argument
+ return
+ ;;
+ 9)
+ COMPREPLY=( $( compgen -W "cmode" -- "$cur" ) )
+ return
+ ;;
+ 10)
+ COMPREPLY=( $( compgen -W "runtime driverinit permanent" -- \
+ "$cur" ) )
+ return
+ ;;
+ esac
+}
+
+# Completion for devlink port param
+_devlink_port_param()
+{
+ case "$cword" in
+ 3)
+ COMPREPLY=( $( compgen -W "show set" -- "$cur" ) )
+ return
+ ;;
+ 4)
+ _devlink_direct_complete "port"
+ return
+ ;;
+ 5)
+ COMPREPLY=( $( compgen -W "name" -- "$cur" ) )
+ return
+ ;;
+ 6)
+ _devlink_direct_complete "param_name"
+ return
+ ;;
+ esac
+
+ if [[ "${words[3]}" == "set" ]]; then
+ _devlink_port_param_set
+ fi
+}
+
+# Completion for devlink port
+_devlink_port()
+{
+ case $command in
+ set)
+ _devlink_port_set
+ return
+ ;;
+ split)
+ _devlink_port_split
+ return
+ ;;
+ param)
+ _devlink_port_param
+ return
+ ;;
+ show|unsplit)
+ if [[ $cword -eq 3 ]]; then
+ _devlink_direct_complete "port"
+ fi
+ return
+ ;;
+ esac
+}
+
+# Completion for devlink dpipe
+_devlink_dpipe()
+{
+ local options="$(devlink dpipe help 2>&1 \
+ | command sed -e '/OBJECT-LIST := /!d' \
+ -e 's/.*{ //' -e 's/}.*//' -e 's/|//g' )"
+
+ if [[ $cword -eq 2 ]]; then
+ COMPREPLY+=( $( compgen -W "$options" -- "$cur" ) )
+ fi
+}
+
+# Completion for devlink monitor
+_devlink_monitor()
+{
+ local options="$(devlink monitor help 2>&1 \
+ | command sed -e '/OBJECT-LIST := /!d' \
+ -e 's/.*{ //' -e 's/}.*//' -e 's/|//g' )"
+
+ if [[ $cword -eq 2 ]]; then
+ COMPREPLY+=( $( compgen -W "all $options" -- "$cur" ) )
+ fi
+}
+
+# Completion for the rest of devlink sb $command
+_devlink_sb_command_options()
+{
+ local subcmd
+
+ case $command in
+ pool)
+ subcmd=${words[3]}
+ if [[ $cword -eq 5 ]]; then
+ COMPREPLY=( $( compgen -W "pool" -- "$cur" ) )
+ fi
+ if [[ $subcmd == "set" ]]; then
+ case $cword in
+ 7)
+ COMPREPLY+=( $( compgen -W "size" -- "$cur" ) )
+ ;;
+ 9)
+ COMPREPLY+=( $( compgen -W "thtype" -- "$cur" ) )
+ ;;
+ esac
+ fi
+ ;;
+ port)
+ subcmd=${words[4]}
+ if [[ $cword -eq 6 ]]; then
+ COMPREPLY+=( $( compgen -W "pool" -- "$cur" ) )
+ fi
+ if [[ $subcmd == "set" ]]; then
+ case $cword in
+ 8)
+ COMPREPLY+=( $( compgen -W "th" -- "$cur" ) )
+ ;;
+ esac
+ fi
+ ;;
+ tc)
+ subcmd=${words[4]}
+ case $cword in
+ 6)
+ COMPREPLY+=( $( compgen -W "tc" -- "$cur" ) )
+ ;;
+ 8)
+ COMPREPLY+=( $( compgen -W "type" -- "$cur" ) )
+ ;;
+ esac
+ if [[ $subcmd == "set" ]]; then
+ case $cword in
+ 10)
+ COMPREPLY+=( $( compgen -W "pool" -- "$cur" ) )
+ ;;
+ 12)
+ COMPREPLY+=( $( compgen -W "th" -- "$cur" ) )
+ ;;
+ esac
+ fi
+ ;;
+ esac
+}
+
+# Completion for devlink sb
+_devlink_sb()
+{
+ case $prev in
+ bind)
+ COMPREPLY=( $( compgen -W "set show" -- "$cur" ) )
+ ;;
+ occupancy)
+ COMPREPLY=( $( compgen -W "show snapshot clearmax" -- "$cur" ) )
+ ;;
+ pool)
+ if [[ $cword -eq 3 || $cword -eq 4 ]]; then
+ COMPREPLY=( $( compgen -W "set show" -- "$cur" ) )
+ elif [[ $command == "port" || $command == "tc" ]]; then
+ _devlink_direct_complete "port_pool"
+ else
+ _devlink_direct_complete "pool"
+ fi
+ ;;
+ port)
+ if [[ $cword -eq 3 ]]; then
+ COMPREPLY=( $( compgen -W "pool" -- "$cur" ) )
+ fi
+ ;;
+ show|set|snapshot|clearmax)
+ case $command in
+ show|pool|occupancy)
+ _devlink_direct_complete "dev"
+ if [[ $command == "occupancy" && $prev == "show" ]];then
+ _devlink_direct_complete "port"
+ fi
+ ;;
+ port|tc)
+ _devlink_direct_complete "port"
+ ;;
+ esac
+ ;;
+ size)
+ # Integer argument
+ ;;
+ thtype)
+ COMPREPLY=( $( compgen -W "static dynamic" -- "$cur" ) )
+ ;;
+ th)
+ # Integer argument
+ ;;
+ tc)
+ if [[ $cword -eq 3 ]]; then
+ COMPREPLY=( $( compgen -W "bind" -- "$cur" ) )
+ else
+ _devlink_direct_complete "tc"
+ fi
+ ;;
+ type)
+ COMPREPLY=( $( compgen -W "ingress egress" -- "$cur" ) )
+ ;;
+ esac
+
+ _devlink_sb_command_options
+ return
+}
+
+# Completion for devlink resource set path argument
+_devlink_resource_path()
+{
+ local path parents parent all_path
+ local dev=${words[3]}
+ local -a path
+
+ local all_path=$(
+ devlink resource show $dev \
+ | sed -E '# Of resource lines, keep only the name itself.
+ s/name ([^ ]*) .*/\1/
+ # Drop headers.
+ /:$/d
+ # First layer is not aligned enough, align it.
+ s/^/ /
+ # Use slashes as unary code for resource depth.
+ s, ,/,g
+ # Separate tally count from resource name.
+ s,/*,&\t,' \
+ | while read d name; do
+ while ((${#path[@]} > ${#d})); do
+ unset path[$((${#path[@]} - 1))]
+ done
+ path[$((${#d} - 1))]=$name
+ echo ${path[@]}
+ done \
+ | sed '# Convert paths to slash-separated
+ s,^,/,;s, ,/,g;s,$,/,'
+ )
+ COMPREPLY=( ${COMPREPLY[@]:-} $( compgen -W "$all_path" -- "$cur" ) )
+}
+
+# Completion for devlink resource set
+_devlink_resource_set()
+{
+ case "$cword" in
+ 3)
+ _devlink_direct_complete "dev"
+ return
+ ;;
+ 4)
+ COMPREPLY=( $( compgen -W "path" -- "$cur" ) )
+ return
+ ;;
+ 5)
+ _devlink_resource_path
+ return
+ ;;
+ 6)
+ COMPREPLY=( $( compgen -W "size" -- "$cur" ) )
+ return
+ ;;
+ 7)
+ # Integer argument
+ return
+ ;;
+ esac
+}
+
+# Completion for devlink resource
+_devlink_resource()
+{
+ case $command in
+ show)
+ if [[ $cword -eq 3 ]]; then
+ _devlink_direct_complete "dev"
+ fi
+ return
+ ;;
+ set)
+ _devlink_resource_set
+ return
+ ;;
+ esac
+}
+
+# Completion for devlink region read
+_devlink_region_read()
+{
+ case "$cword" in
+ 6)
+ COMPREPLY=( $( compgen -W "address" -- "$cur" ) )
+ return
+ ;;
+ 7)
+ # Address argument, for example: 0x10
+ return
+ ;;
+ 8)
+ COMPREPLY=( $( compgen -W "length" -- "$cur" ) )
+ return
+ ;;
+ 9)
+ # Integer argument
+ return
+ ;;
+ esac
+}
+
+# Completion for devlink region
+_devlink_region()
+{
+ if [[ $cword -eq 3 && $command != "help" ]]; then
+ _devlink_direct_complete "region"
+ fi
+
+ case $command in
+ show)
+ return
+ ;;
+ del|dump|read)
+ case "$cword" in
+ 4)
+ COMPREPLY=( $( compgen -W "snapshot" -- "$cur" ) )
+ ;;
+ 5)
+ _devlink_direct_complete "snapshot"
+ ;;
+ esac
+
+ if [[ $command == "read" ]]; then
+ _devlink_region_read
+ fi
+ return
+ ;;
+ esac
+}
+
+# Completion reporter for devlink health
+_devlink_health_reporter()
+{
+ local i=$1; shift
+
+ case $cword in
+ $((3 + $i)))
+ _devlink_direct_complete "health_dev"
+ ;;
+ $((4 + $i)))
+ COMPREPLY=( $( compgen -W "reporter" -- "$cur" ) )
+ ;;
+ $((5 + $i)))
+ _devlink_direct_complete "reporter"
+ ;;
+ esac
+}
+
+# Completion for devlink health
+_devlink_health()
+{
+ case $command in
+ show|recover|diagnose|set|test)
+ _devlink_health_reporter 0
+ if [[ $command == "set" ]]; then
+ case $cword in
+ 6)
+ COMPREPLY=( $( compgen -W "grace_period auto_recover" \
+ -- "$cur" ) )
+ ;;
+ 7)
+ case $prev in
+ grace_period)
+ # Integer argument- msec
+ ;;
+ auto_recover)
+ COMPREPLY=( $( compgen -W "true false" -- \
+ "$cur" ) )
+ ;;
+ esac
+ esac
+ fi
+ return
+ ;;
+ dump)
+ if [[ $cword -eq 3 ]]; then
+ COMPREPLY=( $( compgen -W "show clear" -- "$cur" ) )
+ fi
+
+ _devlink_health_reporter 1
+ return
+ ;;
+ esac
+}
+
+# Completion for action in devlink trap set
+_devlink_trap_set_action()
+{
+ local i=$1; shift
+
+ case $cword in
+ $((6 + $i)))
+ COMPREPLY=( $( compgen -W "action" -- "$cur" ) )
+ ;;
+ $((7 + $i)))
+ COMPREPLY=( $( compgen -W "trap drop mirror" -- "$cur" ) )
+ ;;
+ esac
+}
+
+# Completion for devlink trap group set
+_devlink_trap_group_set()
+{
+ local -A settings=(
+ [action]=notseen
+ [policer]=notseen
+ [nopolicer]=notseen
+ )
+
+ if [[ $cword -eq 7 ]]; then
+ COMPREPLY=( $( compgen -W "action policer nopolicer" -- "$cur" ) )
+ fi
+
+ # Mark seen settings
+ local word
+ for word in "${words[@]:7:${#words[@]}-1}"; do
+ if [[ -n $word ]]; then
+ if [[ "${settings[$word]}" ]]; then
+ settings[$word]=seen
+ fi
+ fi
+ done
+
+ case $prev in
+ action)
+ COMPREPLY=( $( compgen -W "trap drop mirror" -- "$cur" ) )
+ return
+ ;;
+ policer)
+ _devlink_direct_complete "trap_policer"
+ return
+ ;;
+ esac
+
+ local -a comp_words=()
+
+ # Add settings not seen to completions
+ local setting
+ for setting in "${!settings[@]}"; do
+ if [ "${settings[$setting]}" = notseen ]; then
+ comp_words+=( "$setting" )
+ fi
+ done
+
+ COMPREPLY=( $( compgen -W "${comp_words[*]}" -- "$cur" ) )
+}
+
+# Completion for devlink trap group
+_devlink_trap_group()
+{
+ case $cword in
+ 3)
+ COMPREPLY=( $( compgen -W "set show" -- "$cur" ) )
+ return
+ ;;
+ 4)
+ _devlink_direct_complete "dev"
+ return
+ ;;
+ 5)
+ COMPREPLY=( $( compgen -W "group" -- "$cur" ) )
+ return
+ ;;
+ 6)
+ _devlink_direct_complete "trap_group"
+ return
+ ;;
+ esac
+
+ if [[ ${words[3]} == "set" ]]; then
+ _devlink_trap_group_set
+ fi
+}
+
+# Completion for devlink trap policer set
+_devlink_trap_policer_set()
+{
+ local -A settings=(
+ [rate]=notseen
+ [burst]=notseen
+ )
+
+ if [[ $cword -eq 7 ]]; then
+ COMPREPLY=( $( compgen -W "rate burst" -- "$cur" ) )
+ fi
+
+ # Mark seen settings
+ local word
+ for word in "${words[@]:7:${#words[@]}-1}"; do
+ if [[ -n $word ]]; then
+ if [[ "${settings[$word]}" ]]; then
+ settings[$word]=seen
+ fi
+ fi
+ done
+
+ case $prev in
+ rate)
+ # Integer argument
+ return
+ ;;
+ burst)
+ # Integer argument
+ return
+ ;;
+ esac
+
+ local -a comp_words=()
+
+ # Add settings not seen to completions
+ local setting
+ for setting in "${!settings[@]}"; do
+ if [ "${settings[$setting]}" = notseen ]; then
+ comp_words+=( "$setting" )
+ fi
+ done
+
+ COMPREPLY=( $( compgen -W "${comp_words[*]}" -- "$cur" ) )
+}
+
+# Completion for devlink trap policer
+_devlink_trap_policer()
+{
+ case $cword in
+ 3)
+ COMPREPLY=( $( compgen -W "set show" -- "$cur" ) )
+ return
+ ;;
+ 4)
+ _devlink_direct_complete "dev"
+ return
+ ;;
+ 5)
+ COMPREPLY=( $( compgen -W "policer" -- "$cur" ) )
+ return
+ ;;
+ 6)
+ _devlink_direct_complete "trap_policer"
+ return
+ ;;
+ esac
+
+ if [[ ${words[3]} == "set" ]]; then
+ _devlink_trap_policer_set
+ fi
+}
+
+# Completion for devlink trap
+_devlink_trap()
+{
+ case $command in
+ show|set)
+ case $cword in
+ 3)
+ _devlink_direct_complete "dev"
+ ;;
+ 4)
+ COMPREPLY=( $( compgen -W "trap" -- "$cur" ) )
+ ;;
+ 5)
+ _devlink_direct_complete "trap"
+ ;;
+ esac
+
+ if [[ $command == "set" ]]; then
+ _devlink_trap_set_action 0
+ fi
+ return
+ ;;
+ group)
+ _devlink_trap_$command
+ return
+ ;;
+ policer)
+ _devlink_trap_$command
+ return
+ ;;
+ esac
+}
+
+# Complete any devlink command
+_devlink()
+{
+ local cur prev words cword
+ local opt='--Version --no-nice-names --json --pretty --verbose \
+ --statistics --force --Netns --batch'
+ local objects="$(devlink help 2>&1 | command sed -e '/OBJECT := /!d' \
+ -e 's/.*{//' -e 's/}.*//' -e \ 's/|//g' )"
+
+ _init_completion || return
+ # Gets the word-to-complete without considering the colon as word breaks
+ _get_comp_words_by_ref -n : cur prev words cword
+
+ if [[ $cword -eq 1 ]]; then
+ case $cur in
+ -*)
+ COMPREPLY=( $( compgen -W "$opt" -- "$cur" ) )
+ return 0
+ ;;
+ *)
+ COMPREPLY=( $( compgen -W "$objects" -- "$cur" ) )
+ return 0
+ ;;
+ esac
+ fi
+
+ # Deal with options
+ if [[ $prev == -* ]]; then
+ case $prev in
+ -V|--Version)
+ return 0
+ ;;
+ -b|--batch)
+ _filedir
+ return 0
+ ;;
+ --force)
+ COMPREPLY=( $( compgen -W "--batch" -- "$cur" ) )
+ return 0
+ ;;
+ -N|--Netns)
+ local nslist=$( ip netns list 2>/dev/null )
+ COMPREPLY=( $( compgen -W "$nslist" -- "$cur" ) )
+ return 0
+ ;;
+ -j|--json)
+ COMPREPLY=( $( compgen -W "--pretty $objects" -- "$cur" ) )
+ return 0
+ ;;
+ *)
+ COMPREPLY=( $( compgen -W "$objects" -- "$cur" ) )
+ return 0
+ ;;
+ esac
+ fi
+
+ # Remove all options so completions don't have to deal with them.
+ local i
+ for (( i=1; i < ${#words[@]}; )); do
+ if [[ ${words[i]::1} == - ]]; then
+ words=( "${words[@]:0:i}" "${words[@]:i+1}" )
+ [[ $i -le $cword ]] && cword=$(( cword - 1 ))
+ else
+ i=$(( ++i ))
+ fi
+ done
+
+ local object=${words[1]}
+ local command=${words[2]}
+ local pprev=${words[cword - 2]}
+
+ if [[ $objects =~ $object ]]; then
+ if [[ $cword -eq 2 ]]; then
+ COMPREPLY=( $( compgen -W "help" -- "$cur") )
+ if [[ $object != "monitor" && $object != "dpipe" ]]; then
+ COMPREPLY+=( $( compgen -W \
+ "$(_devlink_get_optional_commands $object)" -- "$cur" ) )
+ fi
+ fi
+ "_devlink_$object"
+ fi
+
+} &&
+complete -F _devlink devlink
+
+# ex: ts=4 sw=4 et filetype=sh
diff --git a/bash-completion/tc b/bash-completion/tc
new file mode 100644
index 0000000..086cb7f
--- /dev/null
+++ b/bash-completion/tc
@@ -0,0 +1,809 @@
+# tc(8) completion -*- shell-script -*-
+# Copyright 2016 6WIND S.A.
+# Copyright 2016 Quentin Monnet <quentin.monnet@6wind.com>
+
+QDISC_KIND=' choke codel bfifo pfifo pfifo_head_drop fq fq_codel gred hhf \
+ mqprio multiq netem pfifo_fast pie fq_pie red rr sfb sfq tbf atm \
+ cbq drr dsmark hfsc htb prio qfq '
+FILTER_KIND=' basic bpf cgroup flow flower fw route rsvp tcindex u32 matchall '
+ACTION_KIND=' gact mirred bpf sample '
+
+# Takes a list of words in argument; each one of them is added to COMPREPLY if
+# it is not already present on the command line. Returns no value.
+_tc_once_attr()
+{
+ local w subcword found
+ for w in $*; do
+ found=0
+ for (( subcword=3; subcword < ${#words[@]}-1; subcword++ )); do
+ if [[ $w == ${words[subcword]} ]]; then
+ found=1
+ break
+ fi
+ done
+ [[ $found -eq 0 ]] && \
+ COMPREPLY+=( $( compgen -W "$w" -- "$cur" ) )
+ done
+}
+
+# Takes a list of words in argument; each one of them is added to COMPREPLY if
+# it is not already present on the command line from the provided index. Returns
+# no value.
+_tc_once_attr_from()
+{
+ local w subcword found from=$1
+ shift
+ for w in $*; do
+ found=0
+ for (( subcword=$from; subcword < ${#words[@]}-1; subcword++ )); do
+ if [[ $w == ${words[subcword]} ]]; then
+ found=1
+ break
+ fi
+ done
+ [[ $found -eq 0 ]] && \
+ COMPREPLY+=( $( compgen -W "$w" -- "$cur" ) )
+ done
+}
+
+# Takes a list of words in argument; adds them all to COMPREPLY if none of them
+# is already present on the command line. Returns no value.
+_tc_one_of_list()
+{
+ local w subcword
+ for w in $*; do
+ for (( subcword=3; subcword < ${#words[@]}-1; subcword++ )); do
+ [[ $w == ${words[subcword]} ]] && return 1
+ done
+ done
+ COMPREPLY+=( $( compgen -W "$*" -- "$cur" ) )
+}
+
+# Takes a list of words in argument; adds them all to COMPREPLY if none of them
+# is already present on the command line from the provided index. Returns no
+# value.
+_tc_one_of_list_from()
+{
+ local w subcword from=$1
+ shift
+ for w in $*; do
+ for (( subcword=$from; subcword < ${#words[@]}-1; subcword++ )); do
+ [[ $w == ${words[subcword]} ]] && return 1
+ done
+ done
+ COMPREPLY+=( $( compgen -W "$*" -- "$cur" ) )
+}
+
+# Returns "$cur ${cur}arg1 ${cur}arg2 ..."
+_tc_expand_units()
+{
+ [[ $cur =~ ^[0-9]+ ]] || return 1
+ local value=${cur%%[^0-9]*}
+ [[ $cur == $value ]] && echo $cur
+ echo ${@/#/$value}
+}
+
+# Complete based on given word, usually $prev (or possibly the word before),
+# for when an argument or an option name has but a few possible arguments (so
+# tc does not take particular commands into account here).
+# Returns 0 is completion should stop after running this function, 1 otherwise.
+_tc_direct_complete()
+{
+ case $1 in
+ # Command options
+ dev)
+ _available_interfaces
+ return 0
+ ;;
+ classid)
+ return 0
+ ;;
+ estimator)
+ local list=$( _tc_expand_units 'secs' 'msecs' 'usecs' )
+ COMPREPLY+=( $( compgen -W "$list" -- "$cur" ) )
+ return 0
+ ;;
+ handle)
+ return 0
+ ;;
+ parent|flowid)
+ local i iface ids cmd
+ for (( i=3; i < ${#words[@]}-2; i++ )); do
+ [[ ${words[i]} == dev ]] && iface=${words[i+1]}
+ break
+ done
+ for cmd in qdisc class; do
+ if [[ -n $iface ]]; then
+ ids+=$( tc $cmd show dev $iface 2>/dev/null | \
+ cut -d\ -f 3 )" "
+ else
+ ids+=$( tc $cmd show 2>/dev/null | cut -d\ -f 3 )
+ fi
+ done
+ [[ $ids != " " ]] && \
+ COMPREPLY+=( $( compgen -W "$ids" -- "$cur" ) )
+ return 0
+ ;;
+ protocol) # list comes from lib/ll_proto.c
+ COMPREPLY+=( $( compgen -W ' 802.1Q 802.1ad 802_2 802_3 LLDP aarp \
+ all aoe arp atalk atmfate atmmpoa ax25 bpq can control cust \
+ ddcmp dec diag dna_dl dna_rc dna_rt econet ieeepup ieeepupat \
+ ip ipv4 ipv6 ipx irda lat localtalk loop mobitex ppp_disc \
+ ppp_mp ppp_ses ppptalk pup pupat rarp sca snap tipc tr_802_2 \
+ wan_ppp x25' -- "$cur" ) )
+ return 0
+ ;;
+ prio)
+ return 0
+ ;;
+ stab)
+ COMPREPLY+=( $( compgen -W 'mtu tsize mpu overhead
+ linklayer' -- "$cur" ) )
+ ;;
+
+ # Qdiscs and classes options
+ alpha|bands|beta|buckets|corrupt|debug|decrement|default|\
+ default_index|depth|direct_qlen|divisor|duplicate|ewma|flow_limit|\
+ flows|hh_limit|increment|indices|linklayer|non_hh_weight|num_tc|\
+ penalty_burst|penalty_rate|prio|priomap|probability|queues|r2q|\
+ reorder|vq|vqs)
+ return 0
+ ;;
+ setup)
+ COMPREPLY+=( $( compgen -W 'vqs' -- "$cur" ) )
+ return 0
+ ;;
+ hw)
+ COMPREPLY+=( $( compgen -W '1 0' -- "$cur" ) )
+ return 0
+ ;;
+ distribution)
+ COMPREPLY+=( $( compgen -W 'uniform normal pareto
+ paretonormal' -- "$cur" ) )
+ return 0
+ ;;
+ loss)
+ COMPREPLY+=( $( compgen -W 'random state gmodel' -- "$cur" ) )
+ return 0
+ ;;
+
+ # Qdiscs and classes options options
+ gap|gmodel|state)
+ return 0
+ ;;
+
+ # Filters options
+ map)
+ COMPREPLY+=( $( compgen -W 'key' -- "$cur" ) )
+ return 0
+ ;;
+ hash)
+ COMPREPLY+=( $( compgen -W 'keys' -- "$cur" ) )
+ return 0
+ ;;
+ indev)
+ _available_interfaces
+ return 0
+ ;;
+ eth_type)
+ COMPREPLY+=( $( compgen -W 'ipv4 ipv6' -- "$cur" ) )
+ return 0
+ ;;
+ ip_proto)
+ COMPREPLY+=( $( compgen -W 'tcp udp' -- "$cur" ) )
+ return 0
+ ;;
+
+ # Filters options options
+ key|keys)
+ [[ ${words[@]} =~ graft ]] && return 1
+ COMPREPLY+=( $( compgen -W 'src dst proto proto-src proto-dst iif \
+ priority mark nfct nfct-src nfct-dst nfct-proto-src \
+ nfct-proto-dst rt-classid sk-uid sk-gid vlan-tag rxhash' -- \
+ "$cur" ) )
+ return 0
+ ;;
+
+ # BPF options - used for filters, actions, and exec
+ export|bytecode|bytecode-file|object-file)
+ _filedir
+ return 0
+ ;;
+ object-pinned|graft) # Pinned object is probably under /sys/fs/bpf/
+ [[ -n "$cur" ]] && _filedir && return 0
+ COMPREPLY=( $( compgen -G "/sys/fs/bpf/*" -- "$cur" ) ) || _filedir
+ compopt -o nospace
+ return 0
+ ;;
+ section)
+ if (type objdump > /dev/null 2>&1) ; then
+ local fword objfile section_list
+ for (( fword=3; fword < ${#words[@]}-3; fword++ )); do
+ if [[ ${words[fword]} == object-file ]]; then
+ objfile=${words[fword+1]}
+ break
+ fi
+ done
+ section_list=$( objdump -h $objfile 2>/dev/null | \
+ sed -n 's/^ *[0-9]\+ \([^ ]*\) *.*/\1/p' )
+ COMPREPLY+=( $( compgen -W "$section_list" -- "$cur" ) )
+ fi
+ return 0
+ ;;
+ import|run)
+ _filedir
+ return 0
+ ;;
+ type)
+ COMPREPLY+=( $( compgen -W 'cls act' -- "$cur" ) )
+ return 0
+ ;;
+
+ # Actions options
+ random)
+ _tc_one_of_list 'netrand determ'
+ return 0
+ ;;
+
+ # Units for option arguments
+ bandwidth|maxrate|peakrate|rate)
+ local list=$( _tc_expand_units 'bit' \
+ 'kbit' 'kibit' 'kbps' 'kibps' \
+ 'mbit' 'mibit' 'mbps' 'mibps' \
+ 'gbit' 'gibit' 'gbps' 'gibps' \
+ 'tbit' 'tibit' 'tbps' 'tibps' )
+ COMPREPLY+=( $( compgen -W "$list" -- "$cur" ) )
+ ;;
+ admit_bytes|avpkt|burst|cell|initial_quantum|limit|max|min|mtu|mpu|\
+ overhead|quantum|redflowlist)
+ local list=$( _tc_expand_units \
+ 'b' 'kbit' 'k' 'mbit' 'm' 'gbit' 'g' )
+ COMPREPLY+=( $( compgen -W "$list" -- "$cur" ) )
+ ;;
+ db|delay|evict_timeout|interval|latency|perturb|rehash|reset_timeout|\
+ target|tupdate)
+ local list=$( _tc_expand_units 'secs' 'msecs' 'usecs' )
+ COMPREPLY+=( $( compgen -W "$list" -- "$cur" ) )
+ ;;
+ esac
+ return 1
+}
+
+# Complete with options names for qdiscs. Each qdisc has its own set of options
+# and it seems we cannot really parse it from anywhere, so we add it manually
+# in this function.
+# Returns 0 is completion should stop after running this function, 1 otherwise.
+_tc_qdisc_options()
+{
+ case $1 in
+ choke)
+ _tc_once_attr 'limit bandwidth ecn min max burst'
+ return 0
+ ;;
+ codel)
+ _tc_once_attr 'limit target interval'
+ _tc_one_of_list 'ecn noecn'
+ return 0
+ ;;
+ bfifo|pfifo|pfifo_head_drop)
+ _tc_once_attr 'limit'
+ return 0
+ ;;
+ fq)
+ _tc_once_attr 'limit flow_limit quantum initial_quantum maxrate \
+ buckets'
+ _tc_one_of_list 'pacing nopacing'
+ return 0
+ ;;
+ fq_codel)
+ _tc_once_attr 'limit flows target interval quantum'
+ _tc_one_of_list 'ecn noecn'
+ return 0
+ ;;
+ gred)
+ _tc_once_attr 'setup vqs default grio vq prio limit min max avpkt \
+ burst probability bandwidth ecn harddrop'
+ return 0
+ ;;
+ hhf)
+ _tc_once_attr 'limit quantum hh_limit reset_timeout admit_bytes \
+ evict_timeout non_hh_weight'
+ return 0
+ ;;
+ mqprio)
+ _tc_once_attr 'num_tc map queues hw'
+ return 0
+ ;;
+ netem)
+ _tc_once_attr 'delay distribution corrupt duplicate loss ecn \
+ reorder rate'
+ return 0
+ ;;
+ pie)
+ _tc_once_attr 'limit target tupdate alpha beta'
+ _tc_one_of_list 'bytemode nobytemode'
+ _tc_one_of_list 'ecn noecn'
+ _tc_one_of_list 'dq_rate_estimator no_dq_rate_estimator'
+ return 0
+ ;;
+ fq_pie)
+ _tc_once_attr 'limit flows target tupdate \
+ alpha beta quantum memory_limit ecn_prob'
+ _tc_one_of_list 'ecn noecn'
+ _tc_one_of_list 'bytemode nobytemode'
+ _tc_one_of_list 'dq_rate_estimator no_dq_rate_estimator'
+ return 0
+ ;;
+ red)
+ _tc_once_attr 'limit min max avpkt burst adaptive probability \
+ bandwidth ecn harddrop'
+ return 0
+ ;;
+ rr|prio)
+ _tc_once_attr 'bands priomap multiqueue'
+ return 0
+ ;;
+ sfb)
+ _tc_once_attr 'rehash db limit max target increment decrement \
+ penalty_rate penalty_burst'
+ return 0
+ ;;
+ sfq)
+ _tc_once_attr 'limit perturb quantum divisor flows depth headdrop \
+ redflowlimit min max avpkt burst probability ecn harddrop'
+ return 0
+ ;;
+ tbf)
+ _tc_once_attr 'limit burst rate mtu peakrate latency overhead \
+ linklayer'
+ return 0
+ ;;
+ cbq)
+ _tc_once_attr 'bandwidth avpkt mpu cell ewma'
+ return 0
+ ;;
+ dsmark)
+ _tc_once_attr 'indices default_index set_tc_index'
+ return 0
+ ;;
+ hfsc)
+ _tc_once_attr 'default'
+ return 0
+ ;;
+ htb)
+ _tc_once_attr 'default r2q direct_qlen debug'
+ return 0
+ ;;
+ multiq|pfifo_fast|atm|drr|qfq)
+ return 0
+ ;;
+ esac
+ return 1
+}
+
+# Complete with options names for BPF filters or actions.
+# Returns 0 is completion should stop after running this function, 1 otherwise.
+_tc_bpf_options()
+{
+ [[ ${words[${#words[@]}-3]} == object-file ]] && \
+ _tc_once_attr 'section export'
+ [[ ${words[${#words[@]}-5]} == object-file ]] && \
+ [[ ${words[${#words[@]}-3]} =~ (section|export) ]] && \
+ _tc_once_attr 'section export'
+ _tc_one_of_list 'bytecode bytecode-file object-file object-pinned'
+ _tc_once_attr 'verbose index direct-action action classid'
+ return 0
+}
+
+# Complete with options names for filter actions.
+# This function is recursive, thus allowing multiple actions statement to be
+# parsed.
+# Returns 0 is completion should stop after running this function, 1 otherwise.
+_tc_filter_action_options()
+{
+ for ((acwd=$1; acwd < ${#words[@]}-1; acwd++));
+ do
+ if [[ action == ${words[acwd]} ]]; then
+ _tc_filter_action_options $((acwd+1)) && return 0
+ fi
+ done
+
+ local action acwd
+ for ((acwd=$1; acwd < ${#words[@]}-1; acwd++)); do
+ if [[ $ACTION_KIND =~ ' '${words[acwd]}' ' ]]; then
+ _tc_one_of_list_from $acwd action
+ _tc_action_options $acwd && return 0
+ fi
+ done
+ _tc_one_of_list_from $acwd $ACTION_KIND
+ return 0
+}
+
+# Complete with options names for filters.
+# Returns 0 is completion should stop after running this function, 1 otherwise.
+_tc_filter_options()
+{
+
+ for ((acwd=$1; acwd < ${#words[@]}-1; acwd++));
+ do
+ if [[ action == ${words[acwd]} ]]; then
+ _tc_filter_action_options $((acwd+1)) && return 0
+ fi
+ done
+
+ filter=${words[$1]}
+ case $filter in
+ basic)
+ _tc_once_attr 'match action classid'
+ return 0
+ ;;
+ bpf)
+ _tc_bpf_options
+ return 0
+ ;;
+ cgroup)
+ _tc_once_attr 'match action'
+ return 0
+ ;;
+ flow)
+ local i
+ for (( i=5; i < ${#words[@]}-1; i++ )); do
+ if [[ ${words[i]} =~ ^keys?$ ]]; then
+ _tc_direct_complete 'key'
+ COMPREPLY+=( $( compgen -W 'or and xor rshift addend' -- \
+ "$cur" ) )
+ break
+ fi
+ done
+ _tc_once_attr 'map hash divisor baseclass match action'
+ return 0
+ ;;
+ matchall)
+ _tc_once_attr 'action classid skip_sw skip_hw'
+ return 0
+ ;;
+ flower)
+ _tc_once_attr 'action classid indev dst_mac src_mac eth_type \
+ ip_proto dst_ip src_ip dst_port src_port'
+ return 0
+ ;;
+ fw)
+ _tc_once_attr 'action classid'
+ return 0
+ ;;
+ route)
+ _tc_one_of_list 'from fromif'
+ _tc_once_attr 'to classid action'
+ return 0
+ ;;
+ rsvp)
+ _tc_once_attr 'ipproto session sender classid action tunnelid \
+ tunnel flowlabel spi/ah spi/esp u8 u16 u32'
+ [[ ${words[${#words[@]}-3]} == tunnel ]] && \
+ COMPREPLY+=( $( compgen -W 'skip' -- "$cur" ) )
+ [[ ${words[${#words[@]}-3]} =~ u(8|16|32) ]] && \
+ COMPREPLY+=( $( compgen -W 'mask' -- "$cur" ) )
+ [[ ${words[${#words[@]}-3]} == mask ]] && \
+ COMPREPLY+=( $( compgen -W 'at' -- "$cur" ) )
+ return 0
+ ;;
+ tcindex)
+ _tc_once_attr 'hash mask shift classid action'
+ _tc_one_of_list 'pass_on fall_through'
+ return 0
+ ;;
+ u32)
+ _tc_once_attr 'match link classid action offset ht hashkey sample'
+ COMPREPLY+=( $( compgen -W 'ip ip6 udp tcp icmp u8 u16 u32 mark \
+ divisor' -- "$cur" ) )
+ return 0
+ ;;
+ esac
+ return 1
+}
+
+# Complete with options names for actions.
+# Returns 0 is completion should stop after running this function, 1 otherwise.
+_tc_action_options()
+{
+ local from=$1
+ local action=${words[from]}
+ case $action in
+ bpf)
+ _tc_bpf_options
+ return 0
+ ;;
+ mirred)
+ _tc_one_of_list_from $from 'ingress egress'
+ _tc_one_of_list_from $from 'mirror redirect'
+ _tc_once_attr_from $from 'index dev'
+ return 0
+ ;;
+ sample)
+ _tc_once_attr_from $from 'rate'
+ _tc_once_attr_from $from 'trunc'
+ _tc_once_attr_from $from 'group'
+ return 0
+ ;;
+ gact)
+ _tc_one_of_list_from $from 'reclassify drop continue pass'
+ _tc_once_attr_from $from 'random'
+ return 0
+ ;;
+ esac
+ return 1
+}
+
+# Complete with options names for exec.
+# Returns 0 is completion should stop after running this function, 1 otherwise.
+_tc_exec_options()
+{
+ case $1 in
+ import)
+ [[ ${words[${#words[@]}-3]} == import ]] && \
+ _tc_once_attr 'run'
+ return 0
+ ;;
+ graft)
+ COMPREPLY+=( $( compgen -W 'key type' -- "$cur" ) )
+ [[ ${words[${#words[@]}-3]} == object-file ]] && \
+ _tc_once_attr 'type'
+ _tc_bpf_options
+ return 0
+ ;;
+ esac
+ return 1
+}
+
+# Main completion function
+# Logic is as follows:
+# 1. Check if previous word is a global option; if so, propose arguments.
+# 2. Check if current word is a global option; if so, propose completion.
+# 3. Check for the presence of a main command (qdisc|class|filter|...). If
+# there is one, first call _tc_direct_complete to see if previous word is
+# waiting for a particular completion. If so, propose completion and exit.
+# 4. Extract main command and -- if available -- its subcommand
+# (add|delete|show|...).
+# 5. Propose completion based on main and sub- command in use. Additional
+# functions may be called for qdiscs, classes or filter options.
+_tc()
+{
+ local cur prev words cword
+ _init_completion || return
+
+ case $prev in
+ -V|-Version)
+ return 0
+ ;;
+ -b|-batch|-cf|-conf)
+ _filedir
+ return 0
+ ;;
+ -force)
+ COMPREPLY=( $( compgen -W '-batch' -- "$cur" ) )
+ return 0
+ ;;
+ -nm|name)
+ [[ -r /etc/iproute2/tc_cls ]] || \
+ COMPREPLY=( $( compgen -W '-conf' -- "$cur" ) )
+ return 0
+ ;;
+ -n|-net|-netns)
+ local nslist=$( ip netns list 2>/dev/null )
+ COMPREPLY+=( $( compgen -W "$nslist" -- "$cur" ) )
+ return 0
+ ;;
+ -tshort)
+ _tc_once_attr '-statistics'
+ COMPREPLY+=( $( compgen -W 'monitor' -- "$cur" ) )
+ return 0
+ ;;
+ -timestamp)
+ _tc_once_attr '-statistics -tshort'
+ COMPREPLY+=( $( compgen -W 'monitor' -- "$cur" ) )
+ return 0
+ ;;
+ esac
+
+ # Search for main commands
+ local subcword cmd subcmd
+ for (( subcword=1; subcword < ${#words[@]}-1; subcword++ )); do
+ [[ ${words[subcword]} == -b?(atch) ]] && return 0
+ [[ -n $cmd ]] && subcmd=${words[subcword]} && break
+ [[ ${words[subcword]} != -* && \
+ ${words[subcword-1]} != -@(n?(et?(ns))|c?(on)f) ]] && \
+ cmd=${words[subcword]}
+ done
+
+ if [[ -z $cmd ]]; then
+ case $cur in
+ -*)
+ local c='-Version -statistics -details -raw -pretty \
+ -iec -graphe -batch -name -netns -timestamp'
+ [[ $cword -eq 1 ]] && c+=' -force'
+ COMPREPLY=( $( compgen -W "$c" -- "$cur" ) )
+ return 0
+ ;;
+ *)
+ COMPREPLY=( $( compgen -W "help $( tc help 2>&1 | \
+ command sed \
+ -e '/OBJECT := /!d' \
+ -e 's/.*{//' \
+ -e 's/}.*//' \
+ -e \ 's/|//g' )" -- "$cur" ) )
+ return 0
+ ;;
+ esac
+ fi
+
+ [[ $subcmd == help ]] && return 0
+
+ # For this set of commands we may create COMPREPLY just by analysing the
+ # previous word, if it expects for a specific list of options or values.
+ if [[ $cmd =~ (qdisc|class|filter|action|exec) ]]; then
+ _tc_direct_complete $prev && return 0
+ if [[ ${words[${#words[@]}-3]} == estimator ]]; then
+ local list=$( _tc_expand_units 'secs' 'msecs' 'usecs' )
+ COMPREPLY+=( $( compgen -W "$list" -- "$cur" ) ) && return 0
+ fi
+ fi
+
+ # Completion depends on main command and subcommand in use.
+ case $cmd in
+ qdisc)
+ case $subcmd in
+ add|change|replace|link|del|delete)
+ if [[ $(($cword-$subcword)) -eq 1 ]]; then
+ COMPREPLY=( $( compgen -W 'dev' -- "$cur" ) )
+ return 0
+ fi
+ local qdisc qdwd
+ for ((qdwd=$subcword; qdwd < ${#words[@]}-1; qdwd++)); do
+ if [[ $QDISC_KIND =~ ' '${words[qdwd]}' ' ]]; then
+ qdisc=${words[qdwd]}
+ _tc_qdisc_options $qdisc && return 0
+ fi
+ done
+ _tc_one_of_list $QDISC_KIND
+ _tc_one_of_list 'root ingress parent clsact'
+ _tc_once_attr 'handle estimator stab'
+ ;;
+ show)
+ _tc_once_attr 'dev'
+ _tc_one_of_list 'ingress clsact'
+ _tc_once_attr '-statistics -details -raw -pretty -iec \
+ -graph -name'
+ ;;
+ help)
+ return 0
+ ;;
+ *)
+ [[ $cword -eq $subcword ]] && \
+ COMPREPLY=( $( compgen -W 'help add delete change \
+ replace link show' -- "$cur" ) )
+ ;;
+ esac
+ ;;
+
+ class)
+ case $subcmd in
+ add|change|replace|del|delete)
+ if [[ $(($cword-$subcword)) -eq 1 ]]; then
+ COMPREPLY=( $( compgen -W 'dev' -- "$cur" ) )
+ return 0
+ fi
+ local qdisc qdwd
+ for ((qdwd=$subcword; qdwd < ${#words[@]}-1; qdwd++)); do
+ if [[ $QDISC_KIND =~ ' '${words[qdwd]}' ' ]]; then
+ qdisc=${words[qdwd]}
+ _tc_qdisc_options $qdisc && return 0
+ fi
+ done
+ _tc_one_of_list $QDISC_KIND
+ _tc_one_of_list 'root parent'
+ _tc_once_attr 'classid'
+ ;;
+ show)
+ _tc_once_attr 'dev'
+ _tc_one_of_list 'root parent'
+ _tc_once_attr '-statistics -details -raw -pretty -iec \
+ -graph -name'
+ ;;
+ help)
+ return 0
+ ;;
+ *)
+ [[ $cword -eq $subcword ]] && \
+ COMPREPLY=( $( compgen -W 'help add delete change \
+ replace show' -- "$cur" ) )
+ ;;
+ esac
+ ;;
+
+ filter)
+ case $subcmd in
+ add|change|replace|del|delete)
+ if [[ $(($cword-$subcword)) -eq 1 ]]; then
+ COMPREPLY=( $( compgen -W 'dev' -- "$cur" ) )
+ return 0
+ fi
+ local filter fltwd
+ for ((fltwd=$subcword; fltwd < ${#words[@]}-1; fltwd++));
+ do
+ if [[ $FILTER_KIND =~ ' '${words[fltwd]}' ' ]]; then
+ _tc_filter_options $fltwd && return 0
+ fi
+ done
+ _tc_one_of_list $FILTER_KIND
+ _tc_one_of_list 'root ingress egress parent'
+ _tc_once_attr 'handle estimator pref protocol'
+ ;;
+ show)
+ _tc_once_attr 'dev'
+ _tc_one_of_list 'root ingress egress parent'
+ _tc_once_attr '-statistics -details -raw -pretty -iec \
+ -graph -name'
+ ;;
+ help)
+ return 0
+ ;;
+ *)
+ [[ $cword -eq $subcword ]] && \
+ COMPREPLY=( $( compgen -W 'help add delete change \
+ replace show' -- "$cur" ) )
+ ;;
+ esac
+ ;;
+
+ action)
+ case $subcmd in
+ add|change|replace)
+ local action acwd
+ for ((acwd=$subcword; acwd < ${#words[@]}-1; acwd++)); do
+ if [[ $ACTION_KIND =~ ' '${words[acwd]}' ' ]]; then
+ _tc_action_options $acwd && return 0
+ fi
+ done
+ _tc_one_of_list $ACTION_KIND
+ ;;
+ get|del|delete)
+ _tc_once_attr 'index'
+ ;;
+ lst|list|flush|show)
+ _tc_one_of_list $ACTION_KIND
+ ;;
+ *)
+ [[ $cword -eq $subcword ]] && \
+ COMPREPLY=( $( compgen -W 'help add delete change \
+ replace show list flush action' -- "$cur" ) )
+ ;;
+ esac
+ ;;
+
+ monitor)
+ COMPREPLY=( $( compgen -W 'help' -- "$cur" ) )
+ ;;
+
+ exec)
+ case $subcmd in
+ bpf)
+ local excmd exwd EXEC_KIND=' import debug graft '
+ for ((exwd=$subcword; exwd < ${#words[@]}-1; exwd++)); do
+ if [[ $EXEC_KIND =~ ' '${words[exwd]}' ' ]]; then
+ excmd=${words[exwd]}
+ _tc_exec_options $excmd && return 0
+ fi
+ done
+ _tc_one_of_list $EXEC_KIND
+ ;;
+ *)
+ [[ $cword -eq $subcword ]] && \
+ COMPREPLY=( $( compgen -W 'bpf' -- "$cur" ) )
+ ;;
+ esac
+ ;;
+ esac
+} &&
+complete -F _tc tc
+
+# ex: ts=4 sw=4 et filetype=sh
diff --git a/bridge/.gitignore b/bridge/.gitignore
new file mode 100644
index 0000000..7096907
--- /dev/null
+++ b/bridge/.gitignore
@@ -0,0 +1 @@
+bridge
diff --git a/bridge/Makefile b/bridge/Makefile
new file mode 100644
index 0000000..c6b7d08
--- /dev/null
+++ b/bridge/Makefile
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: GPL-2.0
+BROBJ = bridge.o fdb.o monitor.o link.o mdb.o vlan.o
+
+include ../config.mk
+
+all: bridge
+
+bridge: $(BROBJ) $(LIBNETLINK)
+ $(QUIET_LINK)$(CC) $^ $(LDFLAGS) $(LDLIBS) -o $@
+
+install: all
+ install -m 0755 bridge $(DESTDIR)$(SBINDIR)
+
+clean:
+ rm -f $(BROBJ) bridge
diff --git a/bridge/br_common.h b/bridge/br_common.h
new file mode 100644
index 0000000..b9adafd
--- /dev/null
+++ b/bridge/br_common.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#define MDB_RTA(r) \
+ ((struct rtattr *)(((char *)(r)) + RTA_ALIGN(sizeof(struct br_mdb_entry))))
+
+#define MDB_RTR_RTA(r) \
+ ((struct rtattr *)(((char *)(r)) + RTA_ALIGN(sizeof(__u32))))
+
+void print_vlan_info(struct rtattr *tb, int ifindex);
+int print_linkinfo(struct nlmsghdr *n, void *arg);
+int print_mdb_mon(struct nlmsghdr *n, void *arg);
+int print_fdb(struct nlmsghdr *n, void *arg);
+void print_stp_state(__u8 state);
+int parse_stp_state(const char *arg);
+int print_vlan_rtm(struct nlmsghdr *n, void *arg, bool monitor);
+
+int do_fdb(int argc, char **argv);
+int do_mdb(int argc, char **argv);
+int do_monitor(int argc, char **argv);
+int do_vlan(int argc, char **argv);
+int do_link(int argc, char **argv);
+
+extern int preferred_family;
+extern int show_stats;
+extern int show_details;
+extern int timestamp;
+extern int compress_vlans;
+extern int json;
+extern struct rtnl_handle rth;
diff --git a/bridge/bridge.c b/bridge/bridge.c
new file mode 100644
index 0000000..f7bfe0b
--- /dev/null
+++ b/bridge/bridge.c
@@ -0,0 +1,193 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Get/set/delete bridge with netlink
+ *
+ * Authors: Stephen Hemminger <shemminger@vyatta.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <string.h>
+#include <errno.h>
+
+#include "version.h"
+#include "utils.h"
+#include "br_common.h"
+#include "namespace.h"
+#include "color.h"
+
+struct rtnl_handle rth = { .fd = -1 };
+int preferred_family = AF_UNSPEC;
+int oneline;
+int show_stats;
+int show_details;
+static int color;
+int compress_vlans;
+int json;
+int timestamp;
+static const char *batch_file;
+int force;
+
+static void usage(void) __attribute__((noreturn));
+
+static void usage(void)
+{
+ fprintf(stderr,
+"Usage: bridge [ OPTIONS ] OBJECT { COMMAND | help }\n"
+" bridge [ -force ] -batch filename\n"
+"where OBJECT := { link | fdb | mdb | vlan | monitor }\n"
+" OPTIONS := { -V[ersion] | -s[tatistics] | -d[etails] |\n"
+" -o[neline] | -t[imestamp] | -n[etns] name |\n"
+" -c[ompressvlans] -color -p[retty] -j[son] }\n");
+ exit(-1);
+}
+
+static int do_help(int argc, char **argv)
+{
+ usage();
+}
+
+
+static const struct cmd {
+ const char *cmd;
+ int (*func)(int argc, char **argv);
+} cmds[] = {
+ { "link", do_link },
+ { "fdb", do_fdb },
+ { "mdb", do_mdb },
+ { "vlan", do_vlan },
+ { "monitor", do_monitor },
+ { "help", do_help },
+ { 0 }
+};
+
+static int do_cmd(const char *argv0, int argc, char **argv)
+{
+ const struct cmd *c;
+
+ for (c = cmds; c->cmd; ++c) {
+ if (matches(argv0, c->cmd) == 0)
+ return c->func(argc-1, argv+1);
+ }
+
+ fprintf(stderr,
+ "Object \"%s\" is unknown, try \"bridge help\".\n", argv0);
+ return -1;
+}
+
+static int br_batch_cmd(int argc, char *argv[], void *data)
+{
+ return do_cmd(argv[0], argc, argv);
+}
+
+static int batch(const char *name)
+{
+ int ret;
+
+ if (rtnl_open(&rth, 0) < 0) {
+ fprintf(stderr, "Cannot open rtnetlink\n");
+ return EXIT_FAILURE;
+ }
+
+ rtnl_set_strict_dump(&rth);
+
+ ret = do_batch(name, force, br_batch_cmd, NULL);
+
+ rtnl_close(&rth);
+ return ret;
+}
+
+int
+main(int argc, char **argv)
+{
+ while (argc > 1) {
+ const char *opt = argv[1];
+
+ if (strcmp(opt, "--") == 0) {
+ argc--; argv++;
+ break;
+ }
+ if (opt[0] != '-')
+ break;
+ if (opt[1] == '-')
+ opt++;
+
+ if (matches(opt, "-help") == 0) {
+ usage();
+ } else if (matches(opt, "-Version") == 0) {
+ printf("bridge utility, %s\n", version);
+ exit(0);
+ } else if (matches(opt, "-stats") == 0 ||
+ matches(opt, "-statistics") == 0) {
+ ++show_stats;
+ } else if (matches(opt, "-details") == 0) {
+ ++show_details;
+ } else if (matches(opt, "-oneline") == 0) {
+ ++oneline;
+ } else if (matches(opt, "-timestamp") == 0) {
+ ++timestamp;
+ } else if (matches(opt, "-family") == 0) {
+ argc--;
+ argv++;
+ if (argc <= 1)
+ usage();
+ if (strcmp(argv[1], "inet") == 0)
+ preferred_family = AF_INET;
+ else if (strcmp(argv[1], "inet6") == 0)
+ preferred_family = AF_INET6;
+ else if (strcmp(argv[1], "help") == 0)
+ usage();
+ else
+ 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 (matches(opt, "-netns") == 0) {
+ NEXT_ARG();
+ if (netns_switch(argv[1]))
+ exit(-1);
+ } else if (matches(opt, "-compressvlans") == 0) {
+ ++compress_vlans;
+ } else if (matches_color(opt, &color)) {
+ } else if (matches(opt, "-force") == 0) {
+ ++force;
+ } else if (matches(opt, "-json") == 0) {
+ ++json;
+ } else if (matches(opt, "-pretty") == 0) {
+ ++pretty;
+ } else if (matches(opt, "-batch") == 0) {
+ argc--;
+ argv++;
+ if (argc <= 1)
+ usage();
+ batch_file = argv[1];
+ } else {
+ fprintf(stderr,
+ "Option \"%s\" is unknown, try \"bridge 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 (argc > 1)
+ return do_cmd(argv[1], argc-1, argv+1);
+
+ rtnl_close(&rth);
+ usage();
+}
diff --git a/bridge/fdb.c b/bridge/fdb.c
new file mode 100644
index 0000000..37465e4
--- /dev/null
+++ b/bridge/fdb.c
@@ -0,0 +1,690 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Get/set/delete fdb table with netlink
+ *
+ * TODO: merge/replace this with ip neighbour
+ *
+ * Authors: Stephen Hemminger <shemminger@vyatta.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <netdb.h>
+#include <time.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <linux/if_bridge.h>
+#include <linux/if_ether.h>
+#include <linux/neighbour.h>
+#include <string.h>
+#include <limits.h>
+#include <stdbool.h>
+
+#include "json_print.h"
+#include "libnetlink.h"
+#include "br_common.h"
+#include "rt_names.h"
+#include "utils.h"
+
+static unsigned int filter_index, filter_dynamic, filter_master,
+ filter_state, filter_vlan;
+
+static void usage(void)
+{
+ fprintf(stderr,
+ "Usage: bridge fdb { add | append | del | replace } ADDR dev DEV\n"
+ " [ self ] [ master ] [ use ] [ router ] [ extern_learn ]\n"
+ " [ sticky ] [ local | static | dynamic ] [ vlan VID ]\n"
+ " { [ dst IPADDR ] [ port PORT] [ vni VNI ] | [ nhid NHID ] }\n"
+ " [ via DEV ] [ src_vni VNI ]\n"
+ " bridge fdb [ show [ br BRDEV ] [ brport DEV ] [ vlan VID ]\n"
+ " [ state STATE ] [ dynamic ] ]\n"
+ " bridge fdb get [ to ] LLADDR [ br BRDEV ] { brport | dev } DEV\n"
+ " [ vlan VID ] [ vni VNI ] [ self ] [ master ] [ dynamic ]\n");
+ exit(-1);
+}
+
+static const char *state_n2a(unsigned int s)
+{
+ static char buf[32];
+
+ if (s & NUD_PERMANENT)
+ return "permanent";
+
+ if (s & NUD_NOARP)
+ return "static";
+
+ if (s & NUD_STALE)
+ return "stale";
+
+ if (s & NUD_REACHABLE)
+ return "";
+
+ if (is_json_context())
+ sprintf(buf, "%#x", s);
+ else
+ sprintf(buf, "state=%#x", s);
+ return buf;
+}
+
+static int state_a2n(unsigned int *s, const char *arg)
+{
+ if (matches(arg, "permanent") == 0)
+ *s = NUD_PERMANENT;
+ else if (matches(arg, "static") == 0 || matches(arg, "temp") == 0)
+ *s = NUD_NOARP;
+ else if (matches(arg, "stale") == 0)
+ *s = NUD_STALE;
+ else if (matches(arg, "reachable") == 0 || matches(arg, "dynamic") == 0)
+ *s = NUD_REACHABLE;
+ else if (strcmp(arg, "all") == 0)
+ *s = ~0;
+ else if (get_unsigned(s, arg, 0))
+ return -1;
+
+ return 0;
+}
+
+static void fdb_print_flags(FILE *fp, unsigned int flags)
+{
+ open_json_array(PRINT_JSON,
+ is_json_context() ? "flags" : "");
+
+ if (flags & NTF_SELF)
+ print_string(PRINT_ANY, NULL, "%s ", "self");
+
+ if (flags & NTF_ROUTER)
+ print_string(PRINT_ANY, NULL, "%s ", "router");
+
+ if (flags & NTF_EXT_LEARNED)
+ print_string(PRINT_ANY, NULL, "%s ", "extern_learn");
+
+ if (flags & NTF_OFFLOADED)
+ print_string(PRINT_ANY, NULL, "%s ", "offload");
+
+ if (flags & NTF_MASTER)
+ print_string(PRINT_ANY, NULL, "%s ", "master");
+
+ if (flags & NTF_STICKY)
+ print_string(PRINT_ANY, NULL, "%s ", "sticky");
+
+ close_json_array(PRINT_JSON, NULL);
+}
+
+static void fdb_print_stats(FILE *fp, const struct nda_cacheinfo *ci)
+{
+ static int hz;
+
+ if (!hz)
+ hz = get_user_hz();
+
+ if (is_json_context()) {
+ print_uint(PRINT_JSON, "used", NULL,
+ ci->ndm_used / hz);
+ print_uint(PRINT_JSON, "updated", NULL,
+ ci->ndm_updated / hz);
+ } else {
+ fprintf(fp, "used %d/%d ", ci->ndm_used / hz,
+ ci->ndm_updated / hz);
+
+ }
+}
+
+int print_fdb(struct nlmsghdr *n, void *arg)
+{
+ FILE *fp = arg;
+ struct ndmsg *r = NLMSG_DATA(n);
+ int len = n->nlmsg_len;
+ struct rtattr *tb[NDA_MAX+1];
+ __u16 vid = 0;
+
+ if (n->nlmsg_type != RTM_NEWNEIGH && n->nlmsg_type != RTM_DELNEIGH) {
+ 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 (r->ndm_family != AF_BRIDGE)
+ return 0;
+
+ if (filter_index && filter_index != r->ndm_ifindex)
+ return 0;
+
+ if (filter_state && !(r->ndm_state & filter_state))
+ return 0;
+
+ parse_rtattr(tb, NDA_MAX, NDA_RTA(r),
+ n->nlmsg_len - NLMSG_LENGTH(sizeof(*r)));
+
+ if (tb[NDA_VLAN])
+ vid = rta_getattr_u16(tb[NDA_VLAN]);
+
+ if (filter_vlan && filter_vlan != vid)
+ return 0;
+
+ if (filter_dynamic && (r->ndm_state & NUD_PERMANENT))
+ return 0;
+
+ open_json_object(NULL);
+ if (n->nlmsg_type == RTM_DELNEIGH)
+ print_bool(PRINT_ANY, "deleted", "Deleted ", true);
+
+ 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,
+ "mac", "%s ", lladdr);
+ }
+
+ if (!filter_index && r->ndm_ifindex)
+ print_color_string(PRINT_ANY, COLOR_IFNAME,
+ "ifname", "dev %s ",
+ ll_index_to_name(r->ndm_ifindex));
+
+ if (tb[NDA_DST]) {
+ int family = AF_INET;
+ const char *dst;
+
+ if (RTA_PAYLOAD(tb[NDA_DST]) == sizeof(struct in6_addr))
+ family = AF_INET6;
+
+ dst = format_host(family,
+ RTA_PAYLOAD(tb[NDA_DST]),
+ RTA_DATA(tb[NDA_DST]));
+
+ print_color_string(PRINT_ANY,
+ ifa_family_color(family),
+ "dst", "dst %s ", dst);
+ }
+
+ if (vid)
+ print_uint(PRINT_ANY,
+ "vlan", "vlan %hu ", vid);
+
+ if (tb[NDA_PORT])
+ print_uint(PRINT_ANY,
+ "port", "port %u ",
+ rta_getattr_be16(tb[NDA_PORT]));
+
+ if (tb[NDA_VNI])
+ print_uint(PRINT_ANY,
+ "vni", "vni %u ",
+ rta_getattr_u32(tb[NDA_VNI]));
+
+ if (tb[NDA_SRC_VNI])
+ print_uint(PRINT_ANY,
+ "src_vni", "src_vni %u ",
+ rta_getattr_u32(tb[NDA_SRC_VNI]));
+
+ if (tb[NDA_IFINDEX]) {
+ unsigned int ifindex = rta_getattr_u32(tb[NDA_IFINDEX]);
+
+ if (tb[NDA_LINK_NETNSID])
+ print_uint(PRINT_ANY,
+ "viaIfIndex", "via ifindex %u ",
+ ifindex);
+ else
+ print_string(PRINT_ANY,
+ "viaIf", "via %s ",
+ ll_index_to_name(ifindex));
+ }
+
+ if (tb[NDA_NH_ID])
+ print_uint(PRINT_ANY, "nhid", "nhid %u ",
+ rta_getattr_u32(tb[NDA_NH_ID]));
+
+ if (tb[NDA_LINK_NETNSID])
+ print_uint(PRINT_ANY,
+ "linkNetNsId", "link-netnsid %d ",
+ rta_getattr_u32(tb[NDA_LINK_NETNSID]));
+
+ if (show_stats && tb[NDA_CACHEINFO])
+ fdb_print_stats(fp, RTA_DATA(tb[NDA_CACHEINFO]));
+
+ fdb_print_flags(fp, r->ndm_flags);
+
+
+ if (tb[NDA_MASTER])
+ print_string(PRINT_ANY, "master", "master %s ",
+ ll_index_to_name(rta_getattr_u32(tb[NDA_MASTER])));
+
+ print_string(PRINT_ANY, "state", "%s\n",
+ state_n2a(r->ndm_state));
+ close_json_object();
+ fflush(fp);
+ return 0;
+}
+
+static int fdb_linkdump_filter(struct nlmsghdr *nlh, int reqlen)
+{
+ int err;
+
+ if (filter_index) {
+ struct ifinfomsg *ifm = NLMSG_DATA(nlh);
+
+ ifm->ifi_index = filter_index;
+ }
+
+ if (filter_master) {
+ err = addattr32(nlh, reqlen, IFLA_MASTER, filter_master);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
+static int fdb_dump_filter(struct nlmsghdr *nlh, int reqlen)
+{
+ int err;
+
+ if (filter_index) {
+ struct ndmsg *ndm = NLMSG_DATA(nlh);
+
+ ndm->ndm_ifindex = filter_index;
+ }
+
+ if (filter_master) {
+ err = addattr32(nlh, reqlen, NDA_MASTER, filter_master);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
+static int fdb_show(int argc, char **argv)
+{
+ char *filter_dev = NULL;
+ char *br = NULL;
+ int rc;
+
+ while (argc > 0) {
+ if ((strcmp(*argv, "brport") == 0) || strcmp(*argv, "dev") == 0) {
+ NEXT_ARG();
+ filter_dev = *argv;
+ } else if (strcmp(*argv, "br") == 0) {
+ NEXT_ARG();
+ br = *argv;
+ } else if (strcmp(*argv, "vlan") == 0) {
+ NEXT_ARG();
+ if (filter_vlan)
+ duparg("vlan", *argv);
+ filter_vlan = atoi(*argv);
+ } else if (strcmp(*argv, "state") == 0) {
+ unsigned int state;
+
+ NEXT_ARG();
+ if (state_a2n(&state, *argv))
+ invarg("invalid state", *argv);
+ filter_state |= state;
+ } else if (strcmp(*argv, "dynamic") == 0) {
+ filter_dynamic = 1;
+ } else {
+ if (matches(*argv, "help") == 0)
+ usage();
+ }
+ argc--; argv++;
+ }
+
+ if (br) {
+ int br_ifindex = ll_name_to_index(br);
+
+ if (br_ifindex == 0) {
+ fprintf(stderr, "Cannot find bridge device \"%s\"\n", br);
+ return -1;
+ }
+ filter_master = br_ifindex;
+ }
+
+ /*we'll keep around filter_dev for older kernels */
+ if (filter_dev) {
+ filter_index = ll_name_to_index(filter_dev);
+ if (!filter_index)
+ return nodev(filter_dev);
+ }
+
+ if (rth.flags & RTNL_HANDLE_F_STRICT_CHK)
+ rc = rtnl_neighdump_req(&rth, PF_BRIDGE, fdb_dump_filter);
+ else
+ rc = rtnl_fdb_linkdump_req_filter_fn(&rth, fdb_linkdump_filter);
+ if (rc < 0) {
+ perror("Cannot send dump request");
+ exit(1);
+ }
+
+ new_json_obj(json);
+ if (rtnl_dump_filter(&rth, print_fdb, stdout) < 0) {
+ fprintf(stderr, "Dump terminated\n");
+ exit(1);
+ }
+ delete_json_obj();
+ fflush(stdout);
+
+ return 0;
+}
+
+static int fdb_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 = PF_BRIDGE,
+ .ndm.ndm_state = NUD_NOARP,
+ };
+ char *addr = NULL;
+ char *d = NULL;
+ char abuf[ETH_ALEN];
+ int dst_ok = 0;
+ inet_prefix dst;
+ unsigned long port = 0;
+ unsigned long vni = ~0;
+ unsigned long src_vni = ~0;
+ unsigned int via = 0;
+ char *endptr;
+ short vid = -1;
+ __u32 nhid = 0;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "dev") == 0) {
+ NEXT_ARG();
+ d = *argv;
+ } else if (strcmp(*argv, "dst") == 0) {
+ NEXT_ARG();
+ if (dst_ok)
+ duparg2("dst", *argv);
+ get_addr(&dst, *argv, preferred_family);
+ dst_ok = 1;
+ } else if (strcmp(*argv, "nhid") == 0) {
+ NEXT_ARG();
+ if (get_u32(&nhid, *argv, 0))
+ invarg("\"id\" value is invalid\n", *argv);
+ } else if (strcmp(*argv, "port") == 0) {
+
+ NEXT_ARG();
+ port = strtoul(*argv, &endptr, 0);
+ if (endptr && *endptr) {
+ struct servent *pse;
+
+ pse = getservbyname(*argv, "udp");
+ if (!pse)
+ invarg("invalid port\n", *argv);
+ port = ntohs(pse->s_port);
+ } else if (port > 0xffff)
+ invarg("invalid port\n", *argv);
+ } else if (strcmp(*argv, "vni") == 0) {
+ NEXT_ARG();
+ vni = strtoul(*argv, &endptr, 0);
+ if ((endptr && *endptr) ||
+ (vni >> 24) || vni == ULONG_MAX)
+ invarg("invalid VNI\n", *argv);
+ } else if (strcmp(*argv, "src_vni") == 0) {
+ NEXT_ARG();
+ src_vni = strtoul(*argv, &endptr, 0);
+ if ((endptr && *endptr) ||
+ (src_vni >> 24) || src_vni == ULONG_MAX)
+ invarg("invalid src VNI\n", *argv);
+ } else if (strcmp(*argv, "via") == 0) {
+ NEXT_ARG();
+ via = ll_name_to_index(*argv);
+ if (!via)
+ exit(nodev(*argv));
+ } else if (strcmp(*argv, "self") == 0) {
+ req.ndm.ndm_flags |= NTF_SELF;
+ } else if (matches(*argv, "master") == 0) {
+ req.ndm.ndm_flags |= NTF_MASTER;
+ } else if (matches(*argv, "router") == 0) {
+ req.ndm.ndm_flags |= NTF_ROUTER;
+ } else if (matches(*argv, "local") == 0 ||
+ matches(*argv, "permanent") == 0) {
+ req.ndm.ndm_state |= NUD_PERMANENT;
+ } else if (matches(*argv, "temp") == 0 ||
+ matches(*argv, "static") == 0) {
+ req.ndm.ndm_state |= NUD_REACHABLE;
+ } else if (matches(*argv, "dynamic") == 0) {
+ req.ndm.ndm_state |= NUD_REACHABLE;
+ req.ndm.ndm_state &= ~NUD_NOARP;
+ } else if (matches(*argv, "vlan") == 0) {
+ if (vid >= 0)
+ duparg2("vlan", *argv);
+ NEXT_ARG();
+ vid = atoi(*argv);
+ } else if (matches(*argv, "use") == 0) {
+ req.ndm.ndm_flags |= NTF_USE;
+ } else if (matches(*argv, "extern_learn") == 0) {
+ req.ndm.ndm_flags |= NTF_EXT_LEARNED;
+ } else if (matches(*argv, "sticky") == 0) {
+ req.ndm.ndm_flags |= NTF_STICKY;
+ } else {
+ if (strcmp(*argv, "to") == 0)
+ NEXT_ARG();
+
+ if (matches(*argv, "help") == 0)
+ usage();
+ if (addr)
+ duparg2("to", *argv);
+ addr = *argv;
+ }
+ argc--; argv++;
+ }
+
+ if (d == NULL || addr == NULL) {
+ fprintf(stderr, "Device and address are required arguments.\n");
+ return -1;
+ }
+
+ if (nhid && (dst_ok || port || vni != ~0)) {
+ fprintf(stderr, "dst, port, vni are mutually exclusive with nhid\n");
+ return -1;
+ }
+
+ /* Assume self */
+ if (!(req.ndm.ndm_flags&(NTF_SELF|NTF_MASTER)))
+ req.ndm.ndm_flags |= NTF_SELF;
+
+ /* Assume permanent */
+ if (!(req.ndm.ndm_state&(NUD_PERMANENT|NUD_REACHABLE)))
+ req.ndm.ndm_state |= NUD_PERMANENT;
+
+ if (sscanf(addr, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx",
+ abuf, abuf+1, abuf+2,
+ abuf+3, abuf+4, abuf+5) != 6) {
+ fprintf(stderr, "Invalid mac address %s\n", addr);
+ return -1;
+ }
+
+ addattr_l(&req.n, sizeof(req), NDA_LLADDR, abuf, ETH_ALEN);
+ if (dst_ok)
+ addattr_l(&req.n, sizeof(req), NDA_DST, &dst.data, dst.bytelen);
+
+ if (vid >= 0)
+ addattr16(&req.n, sizeof(req), NDA_VLAN, vid);
+ if (nhid > 0)
+ addattr32(&req.n, sizeof(req), NDA_NH_ID, nhid);
+
+ if (port) {
+ unsigned short dport;
+
+ dport = htons((unsigned short)port);
+ addattr16(&req.n, sizeof(req), NDA_PORT, dport);
+ }
+ if (vni != ~0)
+ addattr32(&req.n, sizeof(req), NDA_VNI, vni);
+ if (src_vni != ~0)
+ addattr32(&req.n, sizeof(req), NDA_SRC_VNI, src_vni);
+ if (via)
+ addattr32(&req.n, sizeof(req), NDA_IFINDEX, via);
+
+ req.ndm.ndm_ifindex = ll_name_to_index(d);
+ if (!req.ndm.ndm_ifindex)
+ return nodev(d);
+
+ if (rtnl_talk(&rth, &req.n, NULL) < 0)
+ return -1;
+
+ return 0;
+}
+
+static int fdb_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 = AF_BRIDGE,
+ };
+ char *d = NULL, *br = NULL;
+ struct nlmsghdr *answer;
+ unsigned long vni = ~0;
+ char abuf[ETH_ALEN];
+ int br_ifindex = 0;
+ char *addr = NULL;
+ short vlan = -1;
+ char *endptr;
+
+ while (argc > 0) {
+ if ((strcmp(*argv, "brport") == 0) || strcmp(*argv, "dev") == 0) {
+ NEXT_ARG();
+ d = *argv;
+ } else if (strcmp(*argv, "br") == 0) {
+ NEXT_ARG();
+ br = *argv;
+ } else if (strcmp(*argv, "dev") == 0) {
+ NEXT_ARG();
+ d = *argv;
+ } else if (strcmp(*argv, "vni") == 0) {
+ NEXT_ARG();
+ vni = strtoul(*argv, &endptr, 0);
+ if ((endptr && *endptr) ||
+ (vni >> 24) || vni == ULONG_MAX)
+ invarg("invalid VNI\n", *argv);
+ } else if (strcmp(*argv, "self") == 0) {
+ req.ndm.ndm_flags |= NTF_SELF;
+ } else if (matches(*argv, "master") == 0) {
+ req.ndm.ndm_flags |= NTF_MASTER;
+ } else if (matches(*argv, "vlan") == 0) {
+ if (vlan >= 0)
+ duparg2("vlan", *argv);
+ NEXT_ARG();
+ vlan = atoi(*argv);
+ } else if (matches(*argv, "dynamic") == 0) {
+ filter_dynamic = 1;
+ } else {
+ if (strcmp(*argv, "to") == 0)
+ NEXT_ARG();
+
+ if (matches(*argv, "help") == 0)
+ usage();
+ if (addr)
+ duparg2("to", *argv);
+ addr = *argv;
+ }
+ argc--; argv++;
+ }
+
+ if ((d == NULL && br == NULL) || addr == NULL) {
+ fprintf(stderr, "Device or master and address are required arguments.\n");
+ return -1;
+ }
+
+ if (sscanf(addr, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx",
+ abuf, abuf+1, abuf+2,
+ abuf+3, abuf+4, abuf+5) != 6) {
+ fprintf(stderr, "Invalid mac address %s\n", addr);
+ return -1;
+ }
+
+ addattr_l(&req.n, sizeof(req), NDA_LLADDR, abuf, ETH_ALEN);
+
+ if (vlan >= 0)
+ addattr16(&req.n, sizeof(req), NDA_VLAN, vlan);
+
+ if (vni != ~0)
+ addattr32(&req.n, sizeof(req), NDA_VNI, vni);
+
+ 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 (br) {
+ br_ifindex = ll_name_to_index(br);
+ if (!br_ifindex) {
+ fprintf(stderr, "Cannot find bridge device \"%s\"\n", br);
+ return -1;
+ }
+ addattr32(&req.n, sizeof(req), NDA_MASTER, br_ifindex);
+ }
+
+ if (rtnl_talk(&rth, &req.n, &answer) < 0)
+ return -2;
+
+ /*
+ * Initialize a json_writer and open an array object
+ * if -json was specified.
+ */
+ new_json_obj(json);
+ if (print_fdb(answer, stdout) < 0) {
+ fprintf(stderr, "An error :-)\n");
+ return -1;
+ }
+ delete_json_obj();
+
+ return 0;
+}
+
+int do_fdb(int argc, char **argv)
+{
+ ll_init_map(&rth);
+
+ if (argc > 0) {
+ if (matches(*argv, "add") == 0)
+ return fdb_modify(RTM_NEWNEIGH, NLM_F_CREATE|NLM_F_EXCL, argc-1, argv+1);
+ if (matches(*argv, "append") == 0)
+ return fdb_modify(RTM_NEWNEIGH, NLM_F_CREATE|NLM_F_APPEND, argc-1, argv+1);
+ if (matches(*argv, "replace") == 0)
+ return fdb_modify(RTM_NEWNEIGH, NLM_F_CREATE|NLM_F_REPLACE, argc-1, argv+1);
+ if (matches(*argv, "delete") == 0)
+ return fdb_modify(RTM_DELNEIGH, 0, argc-1, argv+1);
+ if (matches(*argv, "get") == 0)
+ return fdb_get(argc-1, argv+1);
+ if (matches(*argv, "show") == 0 ||
+ matches(*argv, "lst") == 0 ||
+ matches(*argv, "list") == 0)
+ return fdb_show(argc-1, argv+1);
+ if (matches(*argv, "help") == 0)
+ usage();
+ } else
+ return fdb_show(0, NULL);
+
+ fprintf(stderr, "Command \"%s\" is unknown, try \"bridge fdb help\".\n", *argv);
+ exit(-1);
+}
diff --git a/bridge/link.c b/bridge/link.c
new file mode 100644
index 0000000..205a2fe
--- /dev/null
+++ b/bridge/link.c
@@ -0,0 +1,585 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <time.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <netinet/in.h>
+#include <linux/if.h>
+#include <linux/if_bridge.h>
+#include <string.h>
+#include <stdbool.h>
+
+#include "json_print.h"
+#include "libnetlink.h"
+#include "utils.h"
+#include "br_common.h"
+
+static unsigned int filter_index;
+
+static const char *stp_states[] = {
+ [BR_STATE_DISABLED] = "disabled",
+ [BR_STATE_LISTENING] = "listening",
+ [BR_STATE_LEARNING] = "learning",
+ [BR_STATE_FORWARDING] = "forwarding",
+ [BR_STATE_BLOCKING] = "blocking",
+};
+
+static const char *hw_mode[] = {
+ "VEB", "VEPA"
+};
+
+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, "> ");
+}
+
+void print_stp_state(__u8 state)
+{
+ if (state <= BR_STATE_BLOCKING)
+ print_string(PRINT_ANY, "state",
+ "state %s ", stp_states[state]);
+ else
+ print_uint(PRINT_ANY, "state",
+ "state (%d) ", state);
+}
+
+int parse_stp_state(const char *arg)
+{
+ size_t nstates = ARRAY_SIZE(stp_states);
+ int state;
+
+ for (state = 0; state < nstates; state++)
+ if (strcmp(stp_states[state], arg) == 0)
+ break;
+
+ if (state == nstates)
+ state = -1;
+
+ return state;
+}
+
+static void print_hwmode(__u16 mode)
+{
+ if (mode >= ARRAY_SIZE(hw_mode))
+ print_0xhex(PRINT_ANY, "hwmode",
+ "hwmode %#llx ", mode);
+ else
+ print_string(PRINT_ANY, "hwmode",
+ "hwmode %s ", hw_mode[mode]);
+}
+
+static void print_protinfo(FILE *fp, struct rtattr *attr)
+{
+ if (attr->rta_type & NLA_F_NESTED) {
+ struct rtattr *prtb[IFLA_BRPORT_MAX + 1];
+
+ parse_rtattr_nested(prtb, IFLA_BRPORT_MAX, attr);
+
+ if (prtb[IFLA_BRPORT_STATE])
+ print_stp_state(rta_getattr_u8(prtb[IFLA_BRPORT_STATE]));
+
+ if (prtb[IFLA_BRPORT_PRIORITY])
+ print_uint(PRINT_ANY, "priority",
+ "priority %u ",
+ rta_getattr_u16(prtb[IFLA_BRPORT_PRIORITY]));
+
+ if (prtb[IFLA_BRPORT_COST])
+ print_uint(PRINT_ANY, "cost",
+ "cost %u ",
+ rta_getattr_u32(prtb[IFLA_BRPORT_COST]));
+
+ if (!show_details)
+ return;
+
+ if (!is_json_context())
+ fprintf(fp, "%s ", _SL_);
+
+ if (prtb[IFLA_BRPORT_MODE])
+ print_on_off(PRINT_ANY, "hairpin", "hairpin %s ",
+ rta_getattr_u8(prtb[IFLA_BRPORT_MODE]));
+ if (prtb[IFLA_BRPORT_GUARD])
+ print_on_off(PRINT_ANY, "guard", "guard %s ",
+ rta_getattr_u8(prtb[IFLA_BRPORT_GUARD]));
+ if (prtb[IFLA_BRPORT_PROTECT])
+ print_on_off(PRINT_ANY, "root_block", "root_block %s ",
+ rta_getattr_u8(prtb[IFLA_BRPORT_PROTECT]));
+ if (prtb[IFLA_BRPORT_FAST_LEAVE])
+ print_on_off(PRINT_ANY, "fastleave", "fastleave %s ",
+ rta_getattr_u8(prtb[IFLA_BRPORT_FAST_LEAVE]));
+ if (prtb[IFLA_BRPORT_LEARNING])
+ print_on_off(PRINT_ANY, "learning", "learning %s ",
+ rta_getattr_u8(prtb[IFLA_BRPORT_LEARNING]));
+ if (prtb[IFLA_BRPORT_LEARNING_SYNC])
+ print_on_off(PRINT_ANY, "learning_sync", "learning_sync %s ",
+ rta_getattr_u8(prtb[IFLA_BRPORT_LEARNING_SYNC]));
+ if (prtb[IFLA_BRPORT_UNICAST_FLOOD])
+ print_on_off(PRINT_ANY, "flood", "flood %s ",
+ rta_getattr_u8(prtb[IFLA_BRPORT_UNICAST_FLOOD]));
+ if (prtb[IFLA_BRPORT_MCAST_FLOOD])
+ print_on_off(PRINT_ANY, "mcast_flood", "mcast_flood %s ",
+ rta_getattr_u8(prtb[IFLA_BRPORT_MCAST_FLOOD]));
+ if (prtb[IFLA_BRPORT_MCAST_TO_UCAST])
+ print_on_off(PRINT_ANY, "mcast_to_unicast", "mcast_to_unicast %s ",
+ rta_getattr_u8(prtb[IFLA_BRPORT_MCAST_TO_UCAST]));
+ if (prtb[IFLA_BRPORT_NEIGH_SUPPRESS])
+ print_on_off(PRINT_ANY, "neigh_suppress", "neigh_suppress %s ",
+ rta_getattr_u8(prtb[IFLA_BRPORT_NEIGH_SUPPRESS]));
+ if (prtb[IFLA_BRPORT_VLAN_TUNNEL])
+ print_on_off(PRINT_ANY, "vlan_tunnel", "vlan_tunnel %s ",
+ rta_getattr_u8(prtb[IFLA_BRPORT_VLAN_TUNNEL]));
+
+ if (prtb[IFLA_BRPORT_BACKUP_PORT]) {
+ int ifidx;
+
+ ifidx = rta_getattr_u32(prtb[IFLA_BRPORT_BACKUP_PORT]);
+ print_string(PRINT_ANY,
+ "backup_port", "backup_port %s ",
+ ll_index_to_name(ifidx));
+ }
+
+ if (prtb[IFLA_BRPORT_ISOLATED])
+ print_on_off(PRINT_ANY, "isolated", "isolated %s ",
+ rta_getattr_u8(prtb[IFLA_BRPORT_ISOLATED]));
+ } else
+ print_stp_state(rta_getattr_u8(attr));
+}
+
+
+/*
+ * This is reported by HW devices that have some bridging
+ * capabilities.
+ */
+static void print_af_spec(struct rtattr *attr, int ifindex)
+{
+ struct rtattr *aftb[IFLA_BRIDGE_MAX+1];
+
+ parse_rtattr_nested(aftb, IFLA_BRIDGE_MAX, attr);
+
+ if (aftb[IFLA_BRIDGE_MODE])
+ print_hwmode(rta_getattr_u16(aftb[IFLA_BRIDGE_MODE]));
+
+ if (!show_details)
+ return;
+
+ if (aftb[IFLA_BRIDGE_VLAN_INFO])
+ print_vlan_info(aftb[IFLA_BRIDGE_VLAN_INFO], ifindex);
+}
+
+int print_linkinfo(struct nlmsghdr *n, void *arg)
+{
+ FILE *fp = arg;
+ struct ifinfomsg *ifi = NLMSG_DATA(n);
+ struct rtattr *tb[IFLA_MAX+1];
+ unsigned int m_flag = 0;
+ int len = n->nlmsg_len;
+ const char *name;
+
+ len -= NLMSG_LENGTH(sizeof(*ifi));
+ if (len < 0) {
+ fprintf(stderr, "Message too short!\n");
+ return -1;
+ }
+
+ if (!(ifi->ifi_family == AF_BRIDGE || ifi->ifi_family == AF_UNSPEC))
+ return 0;
+
+ if (filter_index && filter_index != ifi->ifi_index)
+ return 0;
+
+ 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;
+
+ open_json_object(NULL);
+ if (n->nlmsg_type == RTM_DELLINK)
+ print_bool(PRINT_ANY, "deleted", "Deleted ", true);
+
+ 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_MASTER]) {
+ int master = rta_getattr_u32(tb[IFLA_MASTER]);
+
+ print_string(PRINT_ANY, "master", "master %s ",
+ ll_index_to_name(master));
+ }
+
+ if (tb[IFLA_PROTINFO])
+ print_protinfo(fp, tb[IFLA_PROTINFO]);
+
+ if (tb[IFLA_AF_SPEC])
+ print_af_spec(tb[IFLA_AF_SPEC], ifi->ifi_index);
+
+ print_string(PRINT_FP, NULL, "%s", "\n");
+ close_json_object();
+ fflush(fp);
+ return 0;
+}
+
+static void usage(void)
+{
+ fprintf(stderr,
+ "Usage: bridge link set dev DEV [ cost COST ] [ priority PRIO ] [ state STATE ]\n"
+ " [ guard {on | off} ]\n"
+ " [ hairpin {on | off} ]\n"
+ " [ fastleave {on | off} ]\n"
+ " [ root_block {on | off} ]\n"
+ " [ learning {on | off} ]\n"
+ " [ learning_sync {on | off} ]\n"
+ " [ flood {on | off} ]\n"
+ " [ mcast_flood {on | off} ]\n"
+ " [ mcast_to_unicast {on | off} ]\n"
+ " [ neigh_suppress {on | off} ]\n"
+ " [ vlan_tunnel {on | off} ]\n"
+ " [ isolated {on | off} ]\n"
+ " [ hwmode {vepa | veb} ]\n"
+ " [ backup_port DEVICE ] [ nobackup_port ]\n"
+ " [ self ] [ master ]\n"
+ " bridge link show [dev DEV]\n");
+ exit(-1);
+}
+
+static int brlink_modify(int argc, char **argv)
+{
+ struct {
+ struct nlmsghdr n;
+ struct ifinfomsg ifm;
+ char buf[512];
+ } req = {
+ .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)),
+ .n.nlmsg_flags = NLM_F_REQUEST,
+ .n.nlmsg_type = RTM_SETLINK,
+ .ifm.ifi_family = PF_BRIDGE,
+ };
+ char *d = NULL;
+ int backup_port_idx = -1;
+ __s8 neigh_suppress = -1;
+ __s8 learning = -1;
+ __s8 learning_sync = -1;
+ __s8 flood = -1;
+ __s8 vlan_tunnel = -1;
+ __s8 mcast_flood = -1;
+ __s8 mcast_to_unicast = -1;
+ __s8 isolated = -1;
+ __s8 hairpin = -1;
+ __s8 bpdu_guard = -1;
+ __s8 fast_leave = -1;
+ __s8 root_block = -1;
+ __u32 cost = 0;
+ __s16 priority = -1;
+ __s8 state = -1;
+ __s16 mode = -1;
+ __u16 flags = 0;
+ struct rtattr *nest;
+ int ret;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "dev") == 0) {
+ NEXT_ARG();
+ d = *argv;
+ } else if (strcmp(*argv, "guard") == 0) {
+ NEXT_ARG();
+ bpdu_guard = parse_on_off("guard", *argv, &ret);
+ if (ret)
+ return ret;
+ } else if (strcmp(*argv, "hairpin") == 0) {
+ NEXT_ARG();
+ hairpin = parse_on_off("hairpin", *argv, &ret);
+ if (ret)
+ return ret;
+ } else if (strcmp(*argv, "fastleave") == 0) {
+ NEXT_ARG();
+ fast_leave = parse_on_off("fastleave", *argv, &ret);
+ if (ret)
+ return ret;
+ } else if (strcmp(*argv, "root_block") == 0) {
+ NEXT_ARG();
+ root_block = parse_on_off("root_block", *argv, &ret);
+ if (ret)
+ return ret;
+ } else if (strcmp(*argv, "learning") == 0) {
+ NEXT_ARG();
+ learning = parse_on_off("learning", *argv, &ret);
+ if (ret)
+ return ret;
+ } else if (strcmp(*argv, "learning_sync") == 0) {
+ NEXT_ARG();
+ learning_sync = parse_on_off("learning_sync", *argv, &ret);
+ if (ret)
+ return ret;
+ } else if (strcmp(*argv, "flood") == 0) {
+ NEXT_ARG();
+ flood = parse_on_off("flood", *argv, &ret);
+ if (ret)
+ return ret;
+ } else if (strcmp(*argv, "mcast_flood") == 0) {
+ NEXT_ARG();
+ mcast_flood = parse_on_off("mcast_flood", *argv, &ret);
+ if (ret)
+ return ret;
+ } else if (strcmp(*argv, "mcast_to_unicast") == 0) {
+ NEXT_ARG();
+ mcast_to_unicast = parse_on_off("mcast_to_unicast", *argv, &ret);
+ if (ret)
+ return ret;
+ } else if (strcmp(*argv, "cost") == 0) {
+ NEXT_ARG();
+ cost = atoi(*argv);
+ } else if (strcmp(*argv, "priority") == 0) {
+ NEXT_ARG();
+ priority = atoi(*argv);
+ } else if (strcmp(*argv, "state") == 0) {
+ NEXT_ARG();
+ char *endptr;
+
+ state = strtol(*argv, &endptr, 10);
+ if (!(**argv != '\0' && *endptr == '\0')) {
+ state = parse_stp_state(*argv);
+ if (state == -1) {
+ fprintf(stderr,
+ "Error: invalid STP port state\n");
+ return -1;
+ }
+ }
+ } else if (strcmp(*argv, "hwmode") == 0) {
+ NEXT_ARG();
+ flags = BRIDGE_FLAGS_SELF;
+ if (strcmp(*argv, "vepa") == 0)
+ mode = BRIDGE_MODE_VEPA;
+ else if (strcmp(*argv, "veb") == 0)
+ mode = BRIDGE_MODE_VEB;
+ else {
+ fprintf(stderr,
+ "Mode argument must be \"vepa\" or \"veb\".\n");
+ return -1;
+ }
+ } else if (strcmp(*argv, "self") == 0) {
+ flags |= BRIDGE_FLAGS_SELF;
+ } else if (strcmp(*argv, "master") == 0) {
+ flags |= BRIDGE_FLAGS_MASTER;
+ } else if (strcmp(*argv, "neigh_suppress") == 0) {
+ NEXT_ARG();
+ neigh_suppress = parse_on_off("neigh_suppress", *argv, &ret);
+ if (ret)
+ return ret;
+ } else if (strcmp(*argv, "vlan_tunnel") == 0) {
+ NEXT_ARG();
+ vlan_tunnel = parse_on_off("vlan_tunnel", *argv, &ret);
+ if (ret)
+ return ret;
+ } else if (strcmp(*argv, "isolated") == 0) {
+ NEXT_ARG();
+ isolated = parse_on_off("isolated", *argv, &ret);
+ if (ret)
+ return ret;
+ } else if (strcmp(*argv, "backup_port") == 0) {
+ NEXT_ARG();
+ backup_port_idx = ll_name_to_index(*argv);
+ if (!backup_port_idx) {
+ fprintf(stderr, "Error: device %s does not exist\n",
+ *argv);
+ return -1;
+ }
+ } else if (strcmp(*argv, "nobackup_port") == 0) {
+ backup_port_idx = 0;
+ } else {
+ usage();
+ }
+ argc--; argv++;
+ }
+ if (d == NULL) {
+ fprintf(stderr, "Device is a required argument.\n");
+ return -1;
+ }
+
+
+ req.ifm.ifi_index = ll_name_to_index(d);
+ if (req.ifm.ifi_index == 0) {
+ fprintf(stderr, "Cannot find bridge device \"%s\"\n", d);
+ return -1;
+ }
+
+ /* Nested PROTINFO attribute. Contains: port flags, cost, priority and
+ * state.
+ */
+ nest = addattr_nest(&req.n, sizeof(req),
+ IFLA_PROTINFO | NLA_F_NESTED);
+ /* Flags first */
+ if (bpdu_guard >= 0)
+ addattr8(&req.n, sizeof(req), IFLA_BRPORT_GUARD, bpdu_guard);
+ if (hairpin >= 0)
+ addattr8(&req.n, sizeof(req), IFLA_BRPORT_MODE, hairpin);
+ if (fast_leave >= 0)
+ addattr8(&req.n, sizeof(req), IFLA_BRPORT_FAST_LEAVE,
+ fast_leave);
+ if (root_block >= 0)
+ addattr8(&req.n, sizeof(req), IFLA_BRPORT_PROTECT, root_block);
+ if (flood >= 0)
+ addattr8(&req.n, sizeof(req), IFLA_BRPORT_UNICAST_FLOOD, flood);
+ if (mcast_flood >= 0)
+ addattr8(&req.n, sizeof(req), IFLA_BRPORT_MCAST_FLOOD,
+ mcast_flood);
+ if (mcast_to_unicast >= 0)
+ addattr8(&req.n, sizeof(req), IFLA_BRPORT_MCAST_TO_UCAST,
+ mcast_to_unicast);
+ if (learning >= 0)
+ addattr8(&req.n, sizeof(req), IFLA_BRPORT_LEARNING, learning);
+ if (learning_sync >= 0)
+ addattr8(&req.n, sizeof(req), IFLA_BRPORT_LEARNING_SYNC,
+ learning_sync);
+
+ if (cost > 0)
+ addattr32(&req.n, sizeof(req), IFLA_BRPORT_COST, cost);
+
+ if (priority >= 0)
+ addattr16(&req.n, sizeof(req), IFLA_BRPORT_PRIORITY, priority);
+
+ if (state >= 0)
+ addattr8(&req.n, sizeof(req), IFLA_BRPORT_STATE, state);
+
+ if (neigh_suppress != -1)
+ addattr8(&req.n, sizeof(req), IFLA_BRPORT_NEIGH_SUPPRESS,
+ neigh_suppress);
+ if (vlan_tunnel != -1)
+ addattr8(&req.n, sizeof(req), IFLA_BRPORT_VLAN_TUNNEL,
+ vlan_tunnel);
+ if (isolated != -1)
+ addattr8(&req.n, sizeof(req), IFLA_BRPORT_ISOLATED, isolated);
+
+ if (backup_port_idx != -1)
+ addattr32(&req.n, sizeof(req), IFLA_BRPORT_BACKUP_PORT,
+ backup_port_idx);
+
+ addattr_nest_end(&req.n, nest);
+
+ /* IFLA_AF_SPEC nested attribute. Contains IFLA_BRIDGE_FLAGS that
+ * designates master or self operation and IFLA_BRIDGE_MODE
+ * for hw 'vepa' or 'veb' operation modes. The hwmodes are
+ * only valid in 'self' mode on some devices so far.
+ */
+ if (mode >= 0 || flags > 0) {
+ nest = addattr_nest(&req.n, sizeof(req), IFLA_AF_SPEC);
+
+ if (flags > 0)
+ addattr16(&req.n, sizeof(req), IFLA_BRIDGE_FLAGS, flags);
+
+ if (mode >= 0)
+ addattr16(&req.n, sizeof(req), IFLA_BRIDGE_MODE, mode);
+
+ addattr_nest_end(&req.n, nest);
+ }
+
+ if (rtnl_talk(&rth, &req.n, NULL) < 0)
+ return -1;
+
+ return 0;
+}
+
+static int brlink_show(int argc, char **argv)
+{
+ char *filter_dev = NULL;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "dev") == 0) {
+ NEXT_ARG();
+ if (filter_dev)
+ duparg("dev", *argv);
+ filter_dev = *argv;
+ }
+ argc--; argv++;
+ }
+
+ if (filter_dev) {
+ filter_index = ll_name_to_index(filter_dev);
+ if (!filter_index)
+ return nodev(filter_dev);
+ }
+
+ if (show_details) {
+ if (rtnl_linkdump_req_filter(&rth, PF_BRIDGE,
+ (compress_vlans ?
+ RTEXT_FILTER_BRVLAN_COMPRESSED :
+ RTEXT_FILTER_BRVLAN)) < 0) {
+ perror("Cannon send dump request");
+ exit(1);
+ }
+ } else {
+ if (rtnl_linkdump_req(&rth, PF_BRIDGE) < 0) {
+ perror("Cannon send dump request");
+ exit(1);
+ }
+ }
+
+ new_json_obj(json);
+ if (rtnl_dump_filter(&rth, print_linkinfo, stdout) < 0) {
+ fprintf(stderr, "Dump terminated\n");
+ exit(1);
+ }
+
+ delete_json_obj();
+ fflush(stdout);
+ return 0;
+}
+
+int do_link(int argc, char **argv)
+{
+ ll_init_map(&rth);
+ if (argc > 0) {
+ if (matches(*argv, "set") == 0 ||
+ matches(*argv, "change") == 0)
+ return brlink_modify(argc-1, argv+1);
+ if (matches(*argv, "show") == 0 ||
+ matches(*argv, "lst") == 0 ||
+ matches(*argv, "list") == 0)
+ return brlink_show(argc-1, argv+1);
+ if (matches(*argv, "help") == 0)
+ usage();
+ } else
+ return brlink_show(0, NULL);
+
+ fprintf(stderr, "Command \"%s\" is unknown, try \"bridge link help\".\n", *argv);
+ exit(-1);
+}
diff --git a/bridge/mdb.c b/bridge/mdb.c
new file mode 100644
index 0000000..b427d87
--- /dev/null
+++ b/bridge/mdb.c
@@ -0,0 +1,585 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Get mdb table with netlink
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <linux/if_bridge.h>
+#include <linux/if_ether.h>
+#include <string.h>
+#include <arpa/inet.h>
+
+#include "libnetlink.h"
+#include "utils.h"
+#include "br_common.h"
+#include "rt_names.h"
+#include "json_print.h"
+
+#ifndef MDBA_RTA
+#define MDBA_RTA(r) \
+ ((struct rtattr *)(((char *)(r)) + NLMSG_ALIGN(sizeof(struct br_port_msg))))
+#endif
+
+static unsigned int filter_index, filter_vlan;
+
+static void usage(void)
+{
+ fprintf(stderr,
+ "Usage: bridge mdb { add | del } dev DEV port PORT grp GROUP [src SOURCE] [permanent | temp] [vid VID]\n"
+ " bridge mdb {show} [ dev DEV ] [ vid VID ]\n");
+ exit(-1);
+}
+
+static bool is_temp_mcast_rtr(__u8 type)
+{
+ return type == MDB_RTR_TYPE_TEMP_QUERY || type == MDB_RTR_TYPE_TEMP;
+}
+
+static const char *format_timer(__u32 ticks, int align)
+{
+ struct timeval tv;
+ static char tbuf[32];
+
+ __jiffies_to_tv(&tv, ticks);
+ if (align)
+ snprintf(tbuf, sizeof(tbuf), "%4lu.%.2lu",
+ (unsigned long)tv.tv_sec,
+ (unsigned long)tv.tv_usec / 10000);
+ else
+ snprintf(tbuf, sizeof(tbuf), "%lu.%.2lu",
+ (unsigned long)tv.tv_sec,
+ (unsigned long)tv.tv_usec / 10000);
+
+ return tbuf;
+}
+
+static void __print_router_port_stats(FILE *f, struct rtattr *pattr)
+{
+ struct rtattr *tb[MDBA_ROUTER_PATTR_MAX + 1];
+
+ parse_rtattr(tb, MDBA_ROUTER_PATTR_MAX, MDB_RTR_RTA(RTA_DATA(pattr)),
+ RTA_PAYLOAD(pattr) - RTA_ALIGN(sizeof(uint32_t)));
+
+ if (tb[MDBA_ROUTER_PATTR_TIMER]) {
+ __u32 timer = rta_getattr_u32(tb[MDBA_ROUTER_PATTR_TIMER]);
+
+ print_string(PRINT_ANY, "timer", " %s",
+ format_timer(timer, 1));
+ }
+
+ if (tb[MDBA_ROUTER_PATTR_TYPE]) {
+ __u8 type = rta_getattr_u8(tb[MDBA_ROUTER_PATTR_TYPE]);
+
+ print_string(PRINT_ANY, "type", " %s",
+ is_temp_mcast_rtr(type) ? "temp" : "permanent");
+ }
+}
+
+static void br_print_router_ports(FILE *f, struct rtattr *attr,
+ const char *brifname)
+{
+ int rem = RTA_PAYLOAD(attr);
+ struct rtattr *i;
+
+ if (is_json_context())
+ open_json_array(PRINT_JSON, brifname);
+ else if (!show_stats)
+ fprintf(f, "router ports on %s: ", brifname);
+
+ for (i = RTA_DATA(attr); RTA_OK(i, rem); i = RTA_NEXT(i, rem)) {
+ uint32_t *port_ifindex = RTA_DATA(i);
+ const char *port_ifname = ll_index_to_name(*port_ifindex);
+
+ if (is_json_context()) {
+ open_json_object(NULL);
+ print_string(PRINT_JSON, "port", NULL, port_ifname);
+
+ if (show_stats)
+ __print_router_port_stats(f, i);
+ close_json_object();
+ } else if (show_stats) {
+ fprintf(f, "router ports on %s: %s",
+ brifname, port_ifname);
+
+ __print_router_port_stats(f, i);
+ fprintf(f, "\n");
+ } else {
+ fprintf(f, "%s ", port_ifname);
+ }
+ }
+
+ if (!show_stats)
+ print_nl();
+
+ close_json_array(PRINT_JSON, NULL);
+}
+
+static void print_src_entry(struct rtattr *src_attr, int af, const char *sep)
+{
+ struct rtattr *stb[MDBA_MDB_SRCATTR_MAX + 1];
+ SPRINT_BUF(abuf);
+ const char *addr;
+ __u32 timer_val;
+
+ parse_rtattr_nested(stb, MDBA_MDB_SRCATTR_MAX, src_attr);
+ if (!stb[MDBA_MDB_SRCATTR_ADDRESS] || !stb[MDBA_MDB_SRCATTR_TIMER])
+ return;
+
+ addr = inet_ntop(af, RTA_DATA(stb[MDBA_MDB_SRCATTR_ADDRESS]), abuf,
+ sizeof(abuf));
+ if (!addr)
+ return;
+ timer_val = rta_getattr_u32(stb[MDBA_MDB_SRCATTR_TIMER]);
+
+ open_json_object(NULL);
+ print_string(PRINT_FP, NULL, "%s", sep);
+ print_color_string(PRINT_ANY, ifa_family_color(af),
+ "address", "%s", addr);
+ print_string(PRINT_ANY, "timer", "/%s", format_timer(timer_val, 0));
+ close_json_object();
+}
+
+static void print_mdb_entry(FILE *f, int ifindex, const struct br_mdb_entry *e,
+ struct nlmsghdr *n, struct rtattr **tb)
+{
+ const void *grp, *src;
+ const char *addr;
+ SPRINT_BUF(abuf);
+ const char *dev;
+ int af;
+
+ if (filter_vlan && e->vid != filter_vlan)
+ return;
+
+ if (!e->addr.proto) {
+ af = AF_PACKET;
+ grp = &e->addr.u.mac_addr;
+ } else if (e->addr.proto == htons(ETH_P_IP)) {
+ af = AF_INET;
+ grp = &e->addr.u.ip4;
+ } else {
+ af = AF_INET6;
+ grp = &e->addr.u.ip6;
+ }
+ dev = ll_index_to_name(ifindex);
+
+ open_json_object(NULL);
+
+ print_int(PRINT_JSON, "index", NULL, ifindex);
+ print_color_string(PRINT_ANY, COLOR_IFNAME, "dev", "dev %s", dev);
+ print_string(PRINT_ANY, "port", " port %s",
+ ll_index_to_name(e->ifindex));
+
+ /* The ETH_ALEN argument is ignored for all cases but AF_PACKET */
+ addr = rt_addr_n2a_r(af, ETH_ALEN, grp, abuf, sizeof(abuf));
+ if (!addr)
+ return;
+
+ print_color_string(PRINT_ANY, ifa_family_color(af),
+ "grp", " grp %s", addr);
+
+ if (tb && tb[MDBA_MDB_EATTR_SOURCE]) {
+ src = (const void *)RTA_DATA(tb[MDBA_MDB_EATTR_SOURCE]);
+ print_color_string(PRINT_ANY, ifa_family_color(af),
+ "src", " src %s",
+ inet_ntop(af, src, abuf, sizeof(abuf)));
+ }
+ print_string(PRINT_ANY, "state", " %s",
+ (e->state & MDB_PERMANENT) ? "permanent" : "temp");
+ if (show_details && tb) {
+ if (tb[MDBA_MDB_EATTR_GROUP_MODE]) {
+ __u8 mode = rta_getattr_u8(tb[MDBA_MDB_EATTR_GROUP_MODE]);
+
+ print_string(PRINT_ANY, "filter_mode", " filter_mode %s",
+ mode == MCAST_INCLUDE ? "include" :
+ "exclude");
+ }
+ if (tb[MDBA_MDB_EATTR_SRC_LIST]) {
+ struct rtattr *i, *attr = tb[MDBA_MDB_EATTR_SRC_LIST];
+ const char *sep = " ";
+ int rem;
+
+ open_json_array(PRINT_ANY, is_json_context() ?
+ "source_list" :
+ " source_list");
+ rem = RTA_PAYLOAD(attr);
+ for (i = RTA_DATA(attr); RTA_OK(i, rem);
+ i = RTA_NEXT(i, rem)) {
+ print_src_entry(i, af, sep);
+ sep = ",";
+ }
+ close_json_array(PRINT_JSON, NULL);
+ }
+ if (tb[MDBA_MDB_EATTR_RTPROT]) {
+ __u8 rtprot = rta_getattr_u8(tb[MDBA_MDB_EATTR_RTPROT]);
+ SPRINT_BUF(rtb);
+
+ print_string(PRINT_ANY, "protocol", " proto %s ",
+ rtnl_rtprot_n2a(rtprot, rtb, sizeof(rtb)));
+ }
+ }
+
+ open_json_array(PRINT_JSON, "flags");
+ if (e->flags & MDB_FLAGS_OFFLOAD)
+ print_string(PRINT_ANY, NULL, " %s", "offload");
+ if (e->flags & MDB_FLAGS_FAST_LEAVE)
+ print_string(PRINT_ANY, NULL, " %s", "fast_leave");
+ if (e->flags & MDB_FLAGS_STAR_EXCL)
+ print_string(PRINT_ANY, NULL, " %s", "added_by_star_ex");
+ if (e->flags & MDB_FLAGS_BLOCKED)
+ print_string(PRINT_ANY, NULL, " %s", "blocked");
+ close_json_array(PRINT_JSON, NULL);
+
+ if (e->vid)
+ print_uint(PRINT_ANY, "vid", " vid %u", e->vid);
+
+ if (show_stats && tb && tb[MDBA_MDB_EATTR_TIMER]) {
+ __u32 timer = rta_getattr_u32(tb[MDBA_MDB_EATTR_TIMER]);
+
+ print_string(PRINT_ANY, "timer", " %s",
+ format_timer(timer, 1));
+ }
+
+ print_nl();
+ close_json_object();
+}
+
+static void br_print_mdb_entry(FILE *f, int ifindex, struct rtattr *attr,
+ struct nlmsghdr *n)
+{
+ struct rtattr *etb[MDBA_MDB_EATTR_MAX + 1];
+ struct br_mdb_entry *e;
+ struct rtattr *i;
+ int rem;
+
+ rem = RTA_PAYLOAD(attr);
+ for (i = RTA_DATA(attr); RTA_OK(i, rem); i = RTA_NEXT(i, rem)) {
+ e = RTA_DATA(i);
+ parse_rtattr_flags(etb, MDBA_MDB_EATTR_MAX, MDB_RTA(RTA_DATA(i)),
+ RTA_PAYLOAD(i) - RTA_ALIGN(sizeof(*e)),
+ NLA_F_NESTED);
+ print_mdb_entry(f, ifindex, e, n, etb);
+ }
+}
+
+static void print_mdb_entries(FILE *fp, struct nlmsghdr *n,
+ int ifindex, struct rtattr *mdb)
+{
+ int rem = RTA_PAYLOAD(mdb);
+ struct rtattr *i;
+
+ for (i = RTA_DATA(mdb); RTA_OK(i, rem); i = RTA_NEXT(i, rem))
+ br_print_mdb_entry(fp, ifindex, i, n);
+}
+
+static void print_router_entries(FILE *fp, struct nlmsghdr *n,
+ int ifindex, struct rtattr *router)
+{
+ const char *brifname = ll_index_to_name(ifindex);
+
+ if (n->nlmsg_type == RTM_GETMDB) {
+ if (show_details)
+ br_print_router_ports(fp, router, brifname);
+ } else {
+ struct rtattr *i = RTA_DATA(router);
+ uint32_t *port_ifindex = RTA_DATA(i);
+ const char *port_name = ll_index_to_name(*port_ifindex);
+
+ if (is_json_context()) {
+ open_json_array(PRINT_JSON, brifname);
+ open_json_object(NULL);
+
+ print_string(PRINT_JSON, "port", NULL,
+ port_name);
+ close_json_object();
+ close_json_array(PRINT_JSON, NULL);
+ } else {
+ fprintf(fp, "router port dev %s master %s\n",
+ port_name, brifname);
+ }
+ }
+}
+
+static int __parse_mdb_nlmsg(struct nlmsghdr *n, struct rtattr **tb)
+{
+ struct br_port_msg *r = NLMSG_DATA(n);
+ int len = n->nlmsg_len;
+
+ if (n->nlmsg_type != RTM_GETMDB &&
+ n->nlmsg_type != RTM_NEWMDB &&
+ n->nlmsg_type != RTM_DELMDB) {
+ fprintf(stderr,
+ "Not RTM_GETMDB, RTM_NEWMDB or RTM_DELMDB: %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_index && filter_index != r->ifindex)
+ return 0;
+
+ parse_rtattr(tb, MDBA_MAX, MDBA_RTA(r), n->nlmsg_len - NLMSG_LENGTH(sizeof(*r)));
+
+ return 1;
+}
+
+static int print_mdbs(struct nlmsghdr *n, void *arg)
+{
+ struct br_port_msg *r = NLMSG_DATA(n);
+ struct rtattr *tb[MDBA_MAX+1];
+ FILE *fp = arg;
+ int ret;
+
+ ret = __parse_mdb_nlmsg(n, tb);
+ if (ret != 1)
+ return ret;
+
+ if (tb[MDBA_MDB])
+ print_mdb_entries(fp, n, r->ifindex, tb[MDBA_MDB]);
+
+ return 0;
+}
+
+static int print_rtrs(struct nlmsghdr *n, void *arg)
+{
+ struct br_port_msg *r = NLMSG_DATA(n);
+ struct rtattr *tb[MDBA_MAX+1];
+ FILE *fp = arg;
+ int ret;
+
+ ret = __parse_mdb_nlmsg(n, tb);
+ if (ret != 1)
+ return ret;
+
+ if (tb[MDBA_ROUTER])
+ print_router_entries(fp, n, r->ifindex, tb[MDBA_ROUTER]);
+
+ return 0;
+}
+
+int print_mdb_mon(struct nlmsghdr *n, void *arg)
+{
+ struct br_port_msg *r = NLMSG_DATA(n);
+ struct rtattr *tb[MDBA_MAX+1];
+ FILE *fp = arg;
+ int ret;
+
+ ret = __parse_mdb_nlmsg(n, tb);
+ if (ret != 1)
+ return ret;
+
+ if (n->nlmsg_type == RTM_DELMDB)
+ print_bool(PRINT_ANY, "deleted", "Deleted ", true);
+
+ if (tb[MDBA_MDB])
+ print_mdb_entries(fp, n, r->ifindex, tb[MDBA_MDB]);
+
+ if (tb[MDBA_ROUTER])
+ print_router_entries(fp, n, r->ifindex, tb[MDBA_ROUTER]);
+
+ return 0;
+}
+
+static int mdb_show(int argc, char **argv)
+{
+ char *filter_dev = NULL;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "dev") == 0) {
+ NEXT_ARG();
+ if (filter_dev)
+ duparg("dev", *argv);
+ filter_dev = *argv;
+ } else if (strcmp(*argv, "vid") == 0) {
+ NEXT_ARG();
+ if (filter_vlan)
+ duparg("vid", *argv);
+ filter_vlan = atoi(*argv);
+ }
+ argc--; argv++;
+ }
+
+ if (filter_dev) {
+ filter_index = ll_name_to_index(filter_dev);
+ if (!filter_index)
+ return nodev(filter_dev);
+ }
+
+ new_json_obj(json);
+ open_json_object(NULL);
+
+ /* get mdb entries */
+ if (rtnl_mdbdump_req(&rth, PF_BRIDGE) < 0) {
+ perror("Cannot send dump request");
+ return -1;
+ }
+
+ open_json_array(PRINT_JSON, "mdb");
+ if (rtnl_dump_filter(&rth, print_mdbs, stdout) < 0) {
+ fprintf(stderr, "Dump terminated\n");
+ return -1;
+ }
+ close_json_array(PRINT_JSON, NULL);
+
+ /* get router ports */
+ if (rtnl_mdbdump_req(&rth, PF_BRIDGE) < 0) {
+ perror("Cannot send dump request");
+ return -1;
+ }
+
+ open_json_object("router");
+ if (rtnl_dump_filter(&rth, print_rtrs, stdout) < 0) {
+ fprintf(stderr, "Dump terminated\n");
+ return -1;
+ }
+ close_json_object();
+
+ close_json_object();
+ delete_json_obj();
+ fflush(stdout);
+
+ return 0;
+}
+
+static int mdb_parse_grp(const char *grp, struct br_mdb_entry *e)
+{
+ if (inet_pton(AF_INET, grp, &e->addr.u.ip4)) {
+ e->addr.proto = htons(ETH_P_IP);
+ return 0;
+ }
+ if (inet_pton(AF_INET6, grp, &e->addr.u.ip6)) {
+ e->addr.proto = htons(ETH_P_IPV6);
+ return 0;
+ }
+ if (ll_addr_a2n((char *)e->addr.u.mac_addr, sizeof(e->addr.u.mac_addr),
+ grp) == ETH_ALEN) {
+ e->addr.proto = 0;
+ return 0;
+ }
+
+ return -1;
+}
+
+static int mdb_modify(int cmd, int flags, int argc, char **argv)
+{
+ struct {
+ struct nlmsghdr n;
+ struct br_port_msg bpm;
+ char buf[1024];
+ } req = {
+ .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct br_port_msg)),
+ .n.nlmsg_flags = NLM_F_REQUEST | flags,
+ .n.nlmsg_type = cmd,
+ .bpm.family = PF_BRIDGE,
+ };
+ char *d = NULL, *p = NULL, *grp = NULL, *src = NULL;
+ struct br_mdb_entry entry = {};
+ short vid = 0;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "dev") == 0) {
+ NEXT_ARG();
+ d = *argv;
+ } else if (strcmp(*argv, "grp") == 0) {
+ NEXT_ARG();
+ grp = *argv;
+ } else if (strcmp(*argv, "port") == 0) {
+ NEXT_ARG();
+ p = *argv;
+ } else if (strcmp(*argv, "permanent") == 0) {
+ if (cmd == RTM_NEWMDB)
+ entry.state |= MDB_PERMANENT;
+ } else if (strcmp(*argv, "temp") == 0) {
+ ;/* nothing */
+ } else if (strcmp(*argv, "vid") == 0) {
+ NEXT_ARG();
+ vid = atoi(*argv);
+ } else if (strcmp(*argv, "src") == 0) {
+ NEXT_ARG();
+ src = *argv;
+ } else {
+ if (matches(*argv, "help") == 0)
+ usage();
+ }
+ argc--; argv++;
+ }
+
+ if (d == NULL || grp == NULL || p == NULL) {
+ fprintf(stderr, "Device, group address and port name are required arguments.\n");
+ return -1;
+ }
+
+ req.bpm.ifindex = ll_name_to_index(d);
+ if (!req.bpm.ifindex)
+ return nodev(d);
+
+ entry.ifindex = ll_name_to_index(p);
+ if (!entry.ifindex)
+ return nodev(p);
+
+ if (mdb_parse_grp(grp, &entry)) {
+ fprintf(stderr, "Invalid address \"%s\"\n", grp);
+ return -1;
+ }
+
+ entry.vid = vid;
+ addattr_l(&req.n, sizeof(req), MDBA_SET_ENTRY, &entry, sizeof(entry));
+ if (src) {
+ struct rtattr *nest = addattr_nest(&req.n, sizeof(req),
+ MDBA_SET_ENTRY_ATTRS);
+ struct in6_addr src_ip6;
+ __be32 src_ip4;
+
+ nest->rta_type |= NLA_F_NESTED;
+ if (!inet_pton(AF_INET, src, &src_ip4)) {
+ if (!inet_pton(AF_INET6, src, &src_ip6)) {
+ fprintf(stderr, "Invalid source address \"%s\"\n", src);
+ return -1;
+ }
+ addattr_l(&req.n, sizeof(req), MDBE_ATTR_SOURCE, &src_ip6, sizeof(src_ip6));
+ } else {
+ addattr32(&req.n, sizeof(req), MDBE_ATTR_SOURCE, src_ip4);
+ }
+ addattr_nest_end(&req.n, nest);
+ }
+
+ if (rtnl_talk(&rth, &req.n, NULL) < 0)
+ return -1;
+
+ return 0;
+}
+
+int do_mdb(int argc, char **argv)
+{
+ ll_init_map(&rth);
+
+ if (argc > 0) {
+ if (matches(*argv, "add") == 0)
+ return mdb_modify(RTM_NEWMDB, NLM_F_CREATE|NLM_F_EXCL, argc-1, argv+1);
+ if (matches(*argv, "delete") == 0)
+ return mdb_modify(RTM_DELMDB, 0, argc-1, argv+1);
+
+ if (matches(*argv, "show") == 0 ||
+ matches(*argv, "lst") == 0 ||
+ matches(*argv, "list") == 0)
+ return mdb_show(argc-1, argv+1);
+ if (matches(*argv, "help") == 0)
+ usage();
+ } else
+ return mdb_show(0, NULL);
+
+ fprintf(stderr, "Command \"%s\" is unknown, try \"bridge mdb help\".\n", *argv);
+ exit(-1);
+}
diff --git a/bridge/monitor.c b/bridge/monitor.c
new file mode 100644
index 0000000..88f52f5
--- /dev/null
+++ b/bridge/monitor.c
@@ -0,0 +1,160 @@
+/*
+ * brmonitor.c "bridge monitor"
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Authors: Stephen Hemminger <shemminger@vyatta.com>
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <time.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <linux/if_bridge.h>
+#include <linux/neighbour.h>
+#include <string.h>
+
+#include "utils.h"
+#include "br_common.h"
+
+
+static void usage(void) __attribute__((noreturn));
+static int prefix_banner;
+
+static void usage(void)
+{
+ fprintf(stderr, "Usage: bridge monitor [file | link | fdb | mdb | vlan | all]\n");
+ exit(-1);
+}
+
+static int accept_msg(struct rtnl_ctrl_data *ctrl,
+ struct nlmsghdr *n, void *arg)
+{
+ FILE *fp = arg;
+
+ if (timestamp)
+ print_timestamp(fp);
+
+ switch (n->nlmsg_type) {
+ case RTM_NEWLINK:
+ case RTM_DELLINK:
+ if (prefix_banner)
+ fprintf(fp, "[LINK]");
+
+ return print_linkinfo(n, arg);
+
+ case RTM_NEWNEIGH:
+ case RTM_DELNEIGH:
+ if (prefix_banner)
+ fprintf(fp, "[NEIGH]");
+ return print_fdb(n, arg);
+
+ case RTM_NEWMDB:
+ case RTM_DELMDB:
+ if (prefix_banner)
+ fprintf(fp, "[MDB]");
+ return print_mdb_mon(n, arg);
+
+ case NLMSG_TSTAMP:
+ print_nlmsg_timestamp(fp, n);
+ return 0;
+
+ case RTM_NEWVLAN:
+ case RTM_DELVLAN:
+ if (prefix_banner)
+ fprintf(fp, "[VLAN]");
+ return print_vlan_rtm(n, arg, true);
+
+ default:
+ return 0;
+ }
+}
+
+int do_monitor(int argc, char **argv)
+{
+ char *file = NULL;
+ unsigned int groups = ~RTMGRP_TC;
+ int llink = 0;
+ int lneigh = 0;
+ int lmdb = 0;
+ int lvlan = 0;
+
+ rtnl_close(&rth);
+
+ while (argc > 0) {
+ if (matches(*argv, "file") == 0) {
+ NEXT_ARG();
+ file = *argv;
+ } else if (matches(*argv, "link") == 0) {
+ llink = 1;
+ groups = 0;
+ } else if (matches(*argv, "fdb") == 0) {
+ lneigh = 1;
+ groups = 0;
+ } else if (matches(*argv, "mdb") == 0) {
+ lmdb = 1;
+ groups = 0;
+ } else if (matches(*argv, "vlan") == 0) {
+ lvlan = 1;
+ groups = 0;
+ } else if (strcmp(*argv, "all") == 0) {
+ groups = ~RTMGRP_TC;
+ lvlan = 1;
+ prefix_banner = 1;
+ } else if (matches(*argv, "help") == 0) {
+ usage();
+ } else {
+ fprintf(stderr, "Argument \"%s\" is unknown, try \"bridge monitor help\".\n", *argv);
+ exit(-1);
+ }
+ argc--; argv++;
+ }
+
+ if (llink)
+ groups |= nl_mgrp(RTNLGRP_LINK);
+
+ if (lneigh) {
+ groups |= nl_mgrp(RTNLGRP_NEIGH);
+ }
+
+ if (lmdb) {
+ groups |= nl_mgrp(RTNLGRP_MDB);
+ }
+
+ 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 (lvlan && rtnl_add_nl_group(&rth, RTNLGRP_BRVLAN) < 0) {
+ fprintf(stderr, "Failed to add bridge vlan group to list\n");
+ exit(1);
+ }
+
+ ll_init_map(&rth);
+
+ if (rtnl_listen(&rth, accept_msg, stdout) < 0)
+ exit(2);
+
+ return 0;
+}
diff --git a/bridge/vlan.c b/bridge/vlan.c
new file mode 100644
index 0000000..9b6511f
--- /dev/null
+++ b/bridge/vlan.c
@@ -0,0 +1,918 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <linux/if_bridge.h>
+#include <linux/if_ether.h>
+#include <string.h>
+
+#include "json_print.h"
+#include "libnetlink.h"
+#include "br_common.h"
+#include "utils.h"
+
+static unsigned int filter_index, filter_vlan;
+static int vlan_rtm_cur_ifidx = -1;
+
+enum vlan_show_subject {
+ VLAN_SHOW_VLAN,
+ VLAN_SHOW_TUNNELINFO,
+};
+
+#define VLAN_ID_LEN 9
+
+#define __stringify_1(x...) #x
+#define __stringify(x...) __stringify_1(x)
+
+static void usage(void)
+{
+ fprintf(stderr,
+ "Usage: bridge vlan { add | del } vid VLAN_ID dev DEV [ tunnel_info id TUNNEL_ID ]\n"
+ " [ pvid ] [ untagged ]\n"
+ " [ self ] [ master ]\n"
+ " bridge vlan { set } vid VLAN_ID dev DEV [ state STP_STATE ]\n"
+ " bridge vlan { show } [ dev DEV ] [ vid VLAN_ID ]\n"
+ " bridge vlan { tunnelshow } [ dev DEV ] [ vid VLAN_ID ]\n");
+ exit(-1);
+}
+
+static int parse_tunnel_info(int *argcp, char ***argvp, __u32 *tun_id_start,
+ __u32 *tun_id_end)
+{
+ char **argv = *argvp;
+ int argc = *argcp;
+ char *t;
+
+ NEXT_ARG();
+ if (!matches(*argv, "id")) {
+ NEXT_ARG();
+ t = strchr(*argv, '-');
+ if (t) {
+ *t = '\0';
+ if (get_u32(tun_id_start, *argv, 0) ||
+ *tun_id_start >= 1u << 24)
+ invarg("invalid tun id", *argv);
+ if (get_u32(tun_id_end, t + 1, 0) ||
+ *tun_id_end >= 1u << 24)
+ invarg("invalid tun id", *argv);
+
+ } else {
+ if (get_u32(tun_id_start, *argv, 0) ||
+ *tun_id_start >= 1u << 24)
+ invarg("invalid tun id", *argv);
+ }
+ } else {
+ invarg("tunnel id expected", *argv);
+ }
+
+ *argcp = argc;
+ *argvp = argv;
+
+ return 0;
+}
+
+static int add_tunnel_info(struct nlmsghdr *n, int reqsize,
+ __u16 vid, __u32 tun_id, __u16 flags)
+{
+ struct rtattr *tinfo;
+
+ tinfo = addattr_nest(n, reqsize, IFLA_BRIDGE_VLAN_TUNNEL_INFO);
+ addattr32(n, reqsize, IFLA_BRIDGE_VLAN_TUNNEL_ID, tun_id);
+ addattr16(n, reqsize, IFLA_BRIDGE_VLAN_TUNNEL_VID, vid);
+ addattr16(n, reqsize, IFLA_BRIDGE_VLAN_TUNNEL_FLAGS, flags);
+
+ addattr_nest_end(n, tinfo);
+
+ return 0;
+}
+
+static int add_tunnel_info_range(struct nlmsghdr *n, int reqsize,
+ __u16 vid_start, int16_t vid_end,
+ __u32 tun_id_start, __u32 tun_id_end)
+{
+ if (vid_end != -1 && (vid_end - vid_start) > 0) {
+ add_tunnel_info(n, reqsize, vid_start, tun_id_start,
+ BRIDGE_VLAN_INFO_RANGE_BEGIN);
+
+ add_tunnel_info(n, reqsize, vid_end, tun_id_end,
+ BRIDGE_VLAN_INFO_RANGE_END);
+ } else {
+ add_tunnel_info(n, reqsize, vid_start, tun_id_start, 0);
+ }
+
+ return 0;
+}
+
+static int add_vlan_info_range(struct nlmsghdr *n, int reqsize, __u16 vid_start,
+ int16_t vid_end, __u16 flags)
+{
+ struct bridge_vlan_info vinfo = {};
+
+ vinfo.flags = flags;
+ vinfo.vid = vid_start;
+ if (vid_end != -1) {
+ /* send vlan range start */
+ addattr_l(n, reqsize, IFLA_BRIDGE_VLAN_INFO, &vinfo,
+ sizeof(vinfo));
+ vinfo.flags &= ~BRIDGE_VLAN_INFO_RANGE_BEGIN;
+
+ /* Now send the vlan range end */
+ vinfo.flags |= BRIDGE_VLAN_INFO_RANGE_END;
+ vinfo.vid = vid_end;
+ addattr_l(n, reqsize, IFLA_BRIDGE_VLAN_INFO, &vinfo,
+ sizeof(vinfo));
+ } else {
+ addattr_l(n, reqsize, IFLA_BRIDGE_VLAN_INFO, &vinfo,
+ sizeof(vinfo));
+ }
+
+ return 0;
+}
+
+static int vlan_modify(int cmd, int argc, char **argv)
+{
+ struct {
+ struct nlmsghdr n;
+ struct ifinfomsg ifm;
+ char buf[1024];
+ } req = {
+ .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)),
+ .n.nlmsg_flags = NLM_F_REQUEST,
+ .n.nlmsg_type = cmd,
+ .ifm.ifi_family = PF_BRIDGE,
+ };
+ char *d = NULL;
+ short vid = -1;
+ short vid_end = -1;
+ struct rtattr *afspec;
+ struct bridge_vlan_info vinfo = {};
+ bool tunnel_info_set = false;
+ unsigned short flags = 0;
+ __u32 tun_id_start = 0;
+ __u32 tun_id_end = 0;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "dev") == 0) {
+ NEXT_ARG();
+ d = *argv;
+ } else if (strcmp(*argv, "vid") == 0) {
+ char *p;
+
+ NEXT_ARG();
+ p = strchr(*argv, '-');
+ if (p) {
+ *p = '\0';
+ p++;
+ vid = atoi(*argv);
+ vid_end = atoi(p);
+ vinfo.flags |= BRIDGE_VLAN_INFO_RANGE_BEGIN;
+ } else {
+ vid = atoi(*argv);
+ }
+ } else if (strcmp(*argv, "self") == 0) {
+ flags |= BRIDGE_FLAGS_SELF;
+ } else if (strcmp(*argv, "master") == 0) {
+ flags |= BRIDGE_FLAGS_MASTER;
+ } else if (strcmp(*argv, "pvid") == 0) {
+ vinfo.flags |= BRIDGE_VLAN_INFO_PVID;
+ } else if (strcmp(*argv, "untagged") == 0) {
+ vinfo.flags |= BRIDGE_VLAN_INFO_UNTAGGED;
+ } else if (strcmp(*argv, "tunnel_info") == 0) {
+ if (parse_tunnel_info(&argc, &argv,
+ &tun_id_start,
+ &tun_id_end))
+ return -1;
+ tunnel_info_set = true;
+ } else {
+ if (matches(*argv, "help") == 0)
+ NEXT_ARG();
+ }
+ argc--; argv++;
+ }
+
+ if (d == NULL || vid == -1) {
+ fprintf(stderr, "Device and VLAN ID are required arguments.\n");
+ return -1;
+ }
+
+ req.ifm.ifi_index = ll_name_to_index(d);
+ if (req.ifm.ifi_index == 0) {
+ fprintf(stderr, "Cannot find bridge device \"%s\"\n", d);
+ return -1;
+ }
+
+ if (vid >= 4096) {
+ fprintf(stderr, "Invalid VLAN ID \"%hu\"\n", vid);
+ return -1;
+ }
+
+ if (vinfo.flags & BRIDGE_VLAN_INFO_RANGE_BEGIN) {
+ if (vid_end == -1 || vid_end >= 4096 || vid >= vid_end) {
+ fprintf(stderr, "Invalid VLAN range \"%hu-%hu\"\n",
+ vid, vid_end);
+ return -1;
+ }
+ if (vinfo.flags & BRIDGE_VLAN_INFO_PVID) {
+ fprintf(stderr,
+ "pvid cannot be configured for a vlan range\n");
+ return -1;
+ }
+ }
+
+ afspec = addattr_nest(&req.n, sizeof(req), IFLA_AF_SPEC);
+
+ if (flags)
+ addattr16(&req.n, sizeof(req), IFLA_BRIDGE_FLAGS, flags);
+
+ if (tunnel_info_set)
+ add_tunnel_info_range(&req.n, sizeof(req), vid, vid_end,
+ tun_id_start, tun_id_end);
+ else
+ add_vlan_info_range(&req.n, sizeof(req), vid, vid_end,
+ vinfo.flags);
+
+ addattr_nest_end(&req.n, afspec);
+
+ if (rtnl_talk(&rth, &req.n, NULL) < 0)
+ return -1;
+
+ return 0;
+}
+
+static int vlan_option_set(int argc, char **argv)
+{
+ struct {
+ struct nlmsghdr n;
+ struct br_vlan_msg bvm;
+ char buf[1024];
+ } req = {
+ .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct br_vlan_msg)),
+ .n.nlmsg_flags = NLM_F_REQUEST,
+ .n.nlmsg_type = RTM_NEWVLAN,
+ .bvm.family = PF_BRIDGE,
+ };
+ struct bridge_vlan_info vinfo = {};
+ struct rtattr *afspec;
+ short vid_end = -1;
+ char *d = NULL;
+ short vid = -1;
+ int state = -1;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "dev") == 0) {
+ NEXT_ARG();
+ d = *argv;
+ } else if (strcmp(*argv, "vid") == 0) {
+ char *p;
+
+ NEXT_ARG();
+ p = strchr(*argv, '-');
+ if (p) {
+ *p = '\0';
+ p++;
+ vid = atoi(*argv);
+ vid_end = atoi(p);
+ if (vid >= vid_end || vid_end >= 4096) {
+ fprintf(stderr, "Invalid VLAN range \"%hu-%hu\"\n",
+ vid, vid_end);
+ return -1;
+ }
+ } else {
+ vid = atoi(*argv);
+ }
+ } else if (strcmp(*argv, "state") == 0) {
+ char *endptr;
+
+ NEXT_ARG();
+ state = strtol(*argv, &endptr, 10);
+ if (!(**argv != '\0' && *endptr == '\0'))
+ state = parse_stp_state(*argv);
+ if (state == -1) {
+ fprintf(stderr, "Error: invalid STP state\n");
+ return -1;
+ }
+ } else {
+ if (matches(*argv, "help") == 0)
+ NEXT_ARG();
+ }
+ argc--; argv++;
+ }
+
+ if (d == NULL || vid == -1) {
+ fprintf(stderr, "Device and VLAN ID are required arguments.\n");
+ return -1;
+ }
+
+ req.bvm.ifindex = ll_name_to_index(d);
+ if (req.bvm.ifindex == 0) {
+ fprintf(stderr, "Cannot find network device \"%s\"\n", d);
+ return -1;
+ }
+
+ if (vid >= 4096) {
+ fprintf(stderr, "Invalid VLAN ID \"%hu\"\n", vid);
+ return -1;
+ }
+ afspec = addattr_nest(&req.n, sizeof(req), BRIDGE_VLANDB_ENTRY);
+ afspec->rta_type |= NLA_F_NESTED;
+
+ vinfo.flags = BRIDGE_VLAN_INFO_ONLY_OPTS;
+ vinfo.vid = vid;
+ addattr_l(&req.n, sizeof(req), BRIDGE_VLANDB_ENTRY_INFO, &vinfo,
+ sizeof(vinfo));
+ if (vid_end != -1)
+ addattr16(&req.n, sizeof(req), BRIDGE_VLANDB_ENTRY_RANGE,
+ vid_end);
+ if (state >= 0)
+ addattr8(&req.n, sizeof(req), BRIDGE_VLANDB_ENTRY_STATE, state);
+ addattr_nest_end(&req.n, afspec);
+
+ if (rtnl_talk(&rth, &req.n, NULL) < 0)
+ return -1;
+
+ return 0;
+}
+
+/* In order to use this function for both filtering and non-filtering cases
+ * we need to make it a tristate:
+ * return -1 - if filtering we've gone over so don't continue
+ * return 0 - skip entry and continue (applies to range start or to entries
+ * which are less than filter_vlan)
+ * return 1 - print the entry and continue
+ */
+static int filter_vlan_check(__u16 vid, __u16 flags)
+{
+ /* if we're filtering we should stop on the first greater entry */
+ if (filter_vlan && vid > filter_vlan &&
+ !(flags & BRIDGE_VLAN_INFO_RANGE_END))
+ return -1;
+ if ((flags & BRIDGE_VLAN_INFO_RANGE_BEGIN) ||
+ vid < filter_vlan)
+ return 0;
+
+ return 1;
+}
+
+static void open_vlan_port(int ifi_index, enum vlan_show_subject subject)
+{
+ open_json_object(NULL);
+ print_color_string(PRINT_ANY, COLOR_IFNAME, "ifname",
+ "%-" __stringify(IFNAMSIZ) "s ",
+ ll_index_to_name(ifi_index));
+ open_json_array(PRINT_JSON,
+ subject == VLAN_SHOW_VLAN ? "vlans": "tunnels");
+}
+
+static void close_vlan_port(void)
+{
+ close_json_array(PRINT_JSON, NULL);
+ close_json_object();
+}
+
+static unsigned int print_range(const char *name, __u32 start, __u32 id)
+{
+ char end[64];
+ int width;
+
+ snprintf(end, sizeof(end), "%sEnd", name);
+
+ width = print_uint(PRINT_ANY, name, "%u", start);
+ if (start != id)
+ width += print_uint(PRINT_ANY, end, "-%u", id);
+
+ return width;
+}
+
+static void print_vlan_tunnel_info(struct rtattr *tb, int ifindex)
+{
+ struct rtattr *i, *list = tb;
+ int rem = RTA_PAYLOAD(list);
+ __u16 last_vid_start = 0;
+ __u32 last_tunid_start = 0;
+ bool opened = false;
+
+ for (i = RTA_DATA(list); RTA_OK(i, rem); i = RTA_NEXT(i, rem)) {
+ struct rtattr *ttb[IFLA_BRIDGE_VLAN_TUNNEL_MAX+1];
+ __u32 tunnel_id = 0;
+ __u16 tunnel_vid = 0;
+ __u16 tunnel_flags = 0;
+ unsigned int width;
+ int vcheck_ret;
+
+ if (i->rta_type != IFLA_BRIDGE_VLAN_TUNNEL_INFO)
+ continue;
+
+ parse_rtattr(ttb, IFLA_BRIDGE_VLAN_TUNNEL_MAX,
+ RTA_DATA(i), RTA_PAYLOAD(i));
+
+ if (ttb[IFLA_BRIDGE_VLAN_TUNNEL_VID])
+ tunnel_vid =
+ rta_getattr_u16(ttb[IFLA_BRIDGE_VLAN_TUNNEL_VID]);
+ else
+ continue;
+
+ if (ttb[IFLA_BRIDGE_VLAN_TUNNEL_ID])
+ tunnel_id =
+ rta_getattr_u32(ttb[IFLA_BRIDGE_VLAN_TUNNEL_ID]);
+
+ if (ttb[IFLA_BRIDGE_VLAN_TUNNEL_FLAGS])
+ tunnel_flags =
+ rta_getattr_u16(ttb[IFLA_BRIDGE_VLAN_TUNNEL_FLAGS]);
+
+ if (!(tunnel_flags & BRIDGE_VLAN_INFO_RANGE_END)) {
+ last_vid_start = tunnel_vid;
+ last_tunid_start = tunnel_id;
+ }
+
+ vcheck_ret = filter_vlan_check(tunnel_vid, tunnel_flags);
+ if (vcheck_ret == -1)
+ break;
+ else if (vcheck_ret == 0)
+ continue;
+
+ if (!opened) {
+ open_vlan_port(ifindex, VLAN_SHOW_TUNNELINFO);
+ opened = true;
+ } else {
+ print_string(PRINT_FP, NULL,
+ "%-" __stringify(IFNAMSIZ) "s ", "");
+ }
+
+ open_json_object(NULL);
+ width = print_range("vlan", last_vid_start, tunnel_vid);
+ if (width <= VLAN_ID_LEN) {
+ char buf[VLAN_ID_LEN + 1];
+
+ snprintf(buf, sizeof(buf), "%-*s",
+ VLAN_ID_LEN - width, "");
+ print_string(PRINT_FP, NULL, "%s ", buf);
+ } else {
+ fprintf(stderr, "BUG: vlan range too wide, %u\n",
+ width);
+ }
+ print_range("tunid", last_tunid_start, tunnel_id);
+ close_json_object();
+ print_nl();
+ }
+
+ if (opened)
+ close_vlan_port();
+}
+
+static int print_vlan(struct nlmsghdr *n, void *arg)
+{
+ enum vlan_show_subject *subject = arg;
+ struct ifinfomsg *ifm = NLMSG_DATA(n);
+ int len = n->nlmsg_len;
+ struct rtattr *tb[IFLA_MAX+1];
+
+ if (n->nlmsg_type != RTM_NEWLINK) {
+ fprintf(stderr, "Not RTM_NEWLINK: %08x %08x %08x\n",
+ n->nlmsg_len, n->nlmsg_type, n->nlmsg_flags);
+ return 0;
+ }
+
+ len -= NLMSG_LENGTH(sizeof(*ifm));
+ if (len < 0) {
+ fprintf(stderr, "BUG: wrong nlmsg len %d\n", len);
+ return -1;
+ }
+
+ if (ifm->ifi_family != AF_BRIDGE)
+ return 0;
+
+ if (filter_index && filter_index != ifm->ifi_index)
+ return 0;
+
+ parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifm), len);
+ if (!tb[IFLA_AF_SPEC])
+ return 0;
+
+ switch (*subject) {
+ case VLAN_SHOW_VLAN:
+ print_vlan_info(tb[IFLA_AF_SPEC], ifm->ifi_index);
+ break;
+ case VLAN_SHOW_TUNNELINFO:
+ print_vlan_tunnel_info(tb[IFLA_AF_SPEC], ifm->ifi_index);
+ break;
+ }
+
+ return 0;
+}
+
+static void print_vlan_flags(__u16 flags)
+{
+ if (flags == 0)
+ return;
+
+ open_json_array(PRINT_JSON, "flags");
+ if (flags & BRIDGE_VLAN_INFO_PVID)
+ print_string(PRINT_ANY, NULL, " %s", "PVID");
+
+ if (flags & BRIDGE_VLAN_INFO_UNTAGGED)
+ print_string(PRINT_ANY, NULL, " %s", "Egress Untagged");
+ close_json_array(PRINT_JSON, NULL);
+}
+
+static void __print_one_vlan_stats(const struct bridge_vlan_xstats *vstats)
+{
+ print_string(PRINT_FP, NULL, "%-" __stringify(IFNAMSIZ) "s ", "");
+ print_lluint(PRINT_ANY, "rx_bytes", "RX: %llu bytes",
+ vstats->rx_bytes);
+ print_lluint(PRINT_ANY, "rx_packets", " %llu packets\n",
+ vstats->rx_packets);
+
+ print_string(PRINT_FP, NULL, "%-" __stringify(IFNAMSIZ) "s ", "");
+ print_lluint(PRINT_ANY, "tx_bytes", "TX: %llu bytes",
+ vstats->tx_bytes);
+ print_lluint(PRINT_ANY, "tx_packets", " %llu packets\n",
+ vstats->tx_packets);
+}
+
+static void print_one_vlan_stats(const struct bridge_vlan_xstats *vstats)
+{
+ open_json_object(NULL);
+
+ print_hu(PRINT_ANY, "vid", "%hu", vstats->vid);
+ print_vlan_flags(vstats->flags);
+ print_nl();
+ __print_one_vlan_stats(vstats);
+
+ close_json_object();
+}
+
+static void print_vlan_stats_attr(struct rtattr *attr, int ifindex)
+{
+ struct rtattr *brtb[LINK_XSTATS_TYPE_MAX+1];
+ struct rtattr *i, *list;
+ bool found_vlan = false;
+ 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);
+
+ for (i = RTA_DATA(list); RTA_OK(i, rem); i = RTA_NEXT(i, rem)) {
+ const struct bridge_vlan_xstats *vstats = RTA_DATA(i);
+
+ if (i->rta_type != BRIDGE_XSTATS_VLAN)
+ continue;
+
+ if (filter_vlan && filter_vlan != vstats->vid)
+ continue;
+
+ /* skip pure port entries, they'll be dumped via the slave stats call */
+ if ((vstats->flags & BRIDGE_VLAN_INFO_MASTER) &&
+ !(vstats->flags & BRIDGE_VLAN_INFO_BRENTRY))
+ continue;
+
+ /* found vlan stats, first time print the interface name */
+ if (!found_vlan) {
+ open_vlan_port(ifindex, VLAN_SHOW_VLAN);
+ found_vlan = true;
+ } else {
+ print_string(PRINT_FP, NULL,
+ "%-" __stringify(IFNAMSIZ) "s ", "");
+ }
+ print_one_vlan_stats(vstats);
+ }
+
+ /* vlan_port is opened only if there are any vlan stats */
+ if (found_vlan)
+ close_vlan_port();
+}
+
+static int print_vlan_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;
+ FILE *fp = arg;
+
+ 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);
+
+ /* We have to check if any of the two attrs are usable */
+ if (tb[IFLA_STATS_LINK_XSTATS])
+ print_vlan_stats_attr(tb[IFLA_STATS_LINK_XSTATS],
+ ifsm->ifindex);
+
+ if (tb[IFLA_STATS_LINK_XSTATS_SLAVE])
+ print_vlan_stats_attr(tb[IFLA_STATS_LINK_XSTATS_SLAVE],
+ ifsm->ifindex);
+
+ fflush(fp);
+ return 0;
+}
+
+int print_vlan_rtm(struct nlmsghdr *n, void *arg, bool monitor)
+{
+ struct rtattr *vtb[BRIDGE_VLANDB_ENTRY_MAX + 1], *a;
+ struct br_vlan_msg *bvm = NLMSG_DATA(n);
+ int len = n->nlmsg_len;
+ int rem;
+
+ if (n->nlmsg_type != RTM_NEWVLAN && n->nlmsg_type != RTM_DELVLAN &&
+ n->nlmsg_type != RTM_GETVLAN) {
+ fprintf(stderr, "Unknown vlan rtm message: %08x %08x %08x\n",
+ n->nlmsg_len, n->nlmsg_type, n->nlmsg_flags);
+ return 0;
+ }
+
+ len -= NLMSG_LENGTH(sizeof(*bvm));
+ if (len < 0) {
+ fprintf(stderr, "BUG: wrong nlmsg len %d\n", len);
+ return -1;
+ }
+
+ if (bvm->family != AF_BRIDGE)
+ return 0;
+
+ if (filter_index && filter_index != bvm->ifindex)
+ return 0;
+
+ if (n->nlmsg_type == RTM_DELVLAN)
+ print_bool(PRINT_ANY, "deleted", "Deleted ", true);
+
+ if (monitor)
+ vlan_rtm_cur_ifidx = -1;
+
+ if (vlan_rtm_cur_ifidx != -1 && vlan_rtm_cur_ifidx != bvm->ifindex) {
+ close_vlan_port();
+ vlan_rtm_cur_ifidx = -1;
+ }
+
+ rem = len;
+ for (a = BRVLAN_RTA(bvm); RTA_OK(a, rem); a = RTA_NEXT(a, rem)) {
+ struct bridge_vlan_xstats vstats;
+ struct bridge_vlan_info *vinfo;
+ __u32 vrange = 0;
+ __u8 state = 0;
+
+ parse_rtattr_flags(vtb, BRIDGE_VLANDB_ENTRY_MAX, RTA_DATA(a),
+ RTA_PAYLOAD(a), NLA_F_NESTED);
+ vinfo = RTA_DATA(vtb[BRIDGE_VLANDB_ENTRY_INFO]);
+
+ memset(&vstats, 0, sizeof(vstats));
+ if (vtb[BRIDGE_VLANDB_ENTRY_RANGE])
+ vrange = rta_getattr_u16(vtb[BRIDGE_VLANDB_ENTRY_RANGE]);
+ else
+ vrange = vinfo->vid;
+
+ if (vtb[BRIDGE_VLANDB_ENTRY_STATE])
+ state = rta_getattr_u8(vtb[BRIDGE_VLANDB_ENTRY_STATE]);
+
+ if (vtb[BRIDGE_VLANDB_ENTRY_STATS]) {
+ struct rtattr *stb[BRIDGE_VLANDB_STATS_MAX+1];
+ struct rtattr *attr;
+
+ attr = vtb[BRIDGE_VLANDB_ENTRY_STATS];
+ parse_rtattr(stb, BRIDGE_VLANDB_STATS_MAX, RTA_DATA(attr),
+ RTA_PAYLOAD(attr));
+
+ if (stb[BRIDGE_VLANDB_STATS_RX_BYTES]) {
+ attr = stb[BRIDGE_VLANDB_STATS_RX_BYTES];
+ vstats.rx_bytes = rta_getattr_u64(attr);
+ }
+ if (stb[BRIDGE_VLANDB_STATS_RX_PACKETS]) {
+ attr = stb[BRIDGE_VLANDB_STATS_RX_PACKETS];
+ vstats.rx_packets = rta_getattr_u64(attr);
+ }
+ if (stb[BRIDGE_VLANDB_STATS_TX_PACKETS]) {
+ attr = stb[BRIDGE_VLANDB_STATS_TX_PACKETS];
+ vstats.tx_packets = rta_getattr_u64(attr);
+ }
+ if (stb[BRIDGE_VLANDB_STATS_TX_BYTES]) {
+ attr = stb[BRIDGE_VLANDB_STATS_TX_BYTES];
+ vstats.tx_bytes = rta_getattr_u64(attr);
+ }
+ }
+ if (vlan_rtm_cur_ifidx != bvm->ifindex) {
+ open_vlan_port(bvm->ifindex, VLAN_SHOW_VLAN);
+ open_json_object(NULL);
+ vlan_rtm_cur_ifidx = bvm->ifindex;
+ } else {
+ open_json_object(NULL);
+ print_string(PRINT_FP, NULL, "%-" __stringify(IFNAMSIZ) "s ", "");
+ }
+ print_range("vlan", vinfo->vid, vrange);
+ print_vlan_flags(vinfo->flags);
+ print_nl();
+ print_string(PRINT_FP, NULL, "%-" __stringify(IFNAMSIZ) "s ", "");
+ print_stp_state(state);
+ print_nl();
+ if (show_stats)
+ __print_one_vlan_stats(&vstats);
+ close_json_object();
+ }
+
+ return 0;
+}
+
+static int print_vlan_rtm_filter(struct nlmsghdr *n, void *arg)
+{
+ return print_vlan_rtm(n, arg, false);
+}
+
+static int vlan_show(int argc, char **argv, int subject)
+{
+ char *filter_dev = NULL;
+ int ret = 0;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "dev") == 0) {
+ NEXT_ARG();
+ if (filter_dev)
+ duparg("dev", *argv);
+ filter_dev = *argv;
+ } else if (strcmp(*argv, "vid") == 0) {
+ NEXT_ARG();
+ if (filter_vlan)
+ duparg("vid", *argv);
+ filter_vlan = atoi(*argv);
+ }
+ argc--; argv++;
+ }
+
+ if (filter_dev) {
+ filter_index = ll_name_to_index(filter_dev);
+ if (!filter_index)
+ return nodev(filter_dev);
+ }
+
+ new_json_obj(json);
+
+ /* if show_details is true then use the new bridge vlan dump format */
+ if (show_details && subject == VLAN_SHOW_VLAN) {
+ __u32 dump_flags = show_stats ? BRIDGE_VLANDB_DUMPF_STATS : 0;
+
+ if (rtnl_brvlandump_req(&rth, PF_BRIDGE, dump_flags) < 0) {
+ perror("Cannot send dump request");
+ exit(1);
+ }
+
+ if (!is_json_context()) {
+ printf("%-" __stringify(IFNAMSIZ) "s %-"
+ __stringify(VLAN_ID_LEN) "s", "port",
+ "vlan-id");
+ printf("\n");
+ }
+
+ ret = rtnl_dump_filter(&rth, print_vlan_rtm_filter, &subject);
+ if (ret < 0) {
+ fprintf(stderr, "Dump terminated\n");
+ exit(1);
+ }
+
+ if (vlan_rtm_cur_ifidx != -1)
+ close_vlan_port();
+
+ goto out;
+ }
+
+ if (!show_stats) {
+ if (rtnl_linkdump_req_filter(&rth, PF_BRIDGE,
+ (compress_vlans ?
+ RTEXT_FILTER_BRVLAN_COMPRESSED :
+ RTEXT_FILTER_BRVLAN)) < 0) {
+ perror("Cannot send dump request");
+ exit(1);
+ }
+
+ if (!is_json_context()) {
+ printf("%-" __stringify(IFNAMSIZ) "s %-"
+ __stringify(VLAN_ID_LEN) "s", "port",
+ "vlan-id");
+ if (subject == VLAN_SHOW_TUNNELINFO)
+ printf(" tunnel-id");
+ printf("\n");
+ }
+
+ ret = rtnl_dump_filter(&rth, print_vlan, &subject);
+ if (ret < 0) {
+ fprintf(stderr, "Dump terminated\n");
+ exit(1);
+ }
+ } else {
+ __u32 filt_mask;
+
+ filt_mask = IFLA_STATS_FILTER_BIT(IFLA_STATS_LINK_XSTATS);
+ if (rtnl_statsdump_req_filter(&rth, AF_UNSPEC, filt_mask) < 0) {
+ perror("Cannot send dump request");
+ exit(1);
+ }
+
+ if (!is_json_context())
+ printf("%-" __stringify(IFNAMSIZ) "s vlan-id\n",
+ "port");
+
+ if (rtnl_dump_filter(&rth, print_vlan_stats, stdout) < 0) {
+ fprintf(stderr, "Dump terminated\n");
+ exit(1);
+ }
+
+ filt_mask = IFLA_STATS_FILTER_BIT(IFLA_STATS_LINK_XSTATS_SLAVE);
+ if (rtnl_statsdump_req_filter(&rth, AF_UNSPEC, filt_mask) < 0) {
+ perror("Cannot send slave dump request");
+ exit(1);
+ }
+
+ if (rtnl_dump_filter(&rth, print_vlan_stats, stdout) < 0) {
+ fprintf(stderr, "Dump terminated\n");
+ exit(1);
+ }
+ }
+
+out:
+ delete_json_obj();
+ fflush(stdout);
+ return 0;
+}
+
+void print_vlan_info(struct rtattr *tb, int ifindex)
+{
+ struct rtattr *i, *list = tb;
+ int rem = RTA_PAYLOAD(list);
+ __u16 last_vid_start = 0;
+ bool opened = false;
+
+ for (i = RTA_DATA(list); RTA_OK(i, rem); i = RTA_NEXT(i, rem)) {
+ struct bridge_vlan_info *vinfo;
+ int vcheck_ret;
+
+ if (i->rta_type != IFLA_BRIDGE_VLAN_INFO)
+ continue;
+
+ vinfo = RTA_DATA(i);
+
+ if (!(vinfo->flags & BRIDGE_VLAN_INFO_RANGE_END))
+ last_vid_start = vinfo->vid;
+ vcheck_ret = filter_vlan_check(vinfo->vid, vinfo->flags);
+ if (vcheck_ret == -1)
+ break;
+ else if (vcheck_ret == 0)
+ continue;
+
+ if (!opened) {
+ open_vlan_port(ifindex, VLAN_SHOW_VLAN);
+ opened = true;
+ } else {
+ print_string(PRINT_FP, NULL, "%-"
+ __stringify(IFNAMSIZ) "s ", "");
+ }
+
+ open_json_object(NULL);
+ print_range("vlan", last_vid_start, vinfo->vid);
+
+ print_vlan_flags(vinfo->flags);
+ close_json_object();
+ print_nl();
+ }
+
+ if (opened)
+ close_vlan_port();
+}
+
+int do_vlan(int argc, char **argv)
+{
+ ll_init_map(&rth);
+
+ if (argc > 0) {
+ if (matches(*argv, "add") == 0)
+ return vlan_modify(RTM_SETLINK, argc-1, argv+1);
+ if (matches(*argv, "delete") == 0)
+ return vlan_modify(RTM_DELLINK, argc-1, argv+1);
+ if (matches(*argv, "show") == 0 ||
+ matches(*argv, "lst") == 0 ||
+ matches(*argv, "list") == 0)
+ return vlan_show(argc-1, argv+1, VLAN_SHOW_VLAN);
+ if (matches(*argv, "tunnelshow") == 0) {
+ return vlan_show(argc-1, argv+1, VLAN_SHOW_TUNNELINFO);
+ }
+ if (matches(*argv, "set") == 0)
+ return vlan_option_set(argc-1, argv+1);
+ if (matches(*argv, "help") == 0)
+ usage();
+ } else {
+ return vlan_show(0, NULL, VLAN_SHOW_VLAN);
+ }
+
+ fprintf(stderr, "Command \"%s\" is unknown, try \"bridge vlan help\".\n", *argv);
+ exit(-1);
+}
diff --git a/configure b/configure
new file mode 100755
index 0000000..2c363d3
--- /dev/null
+++ b/configure
@@ -0,0 +1,518 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+# This is not an autoconf generated configure
+#
+# Influential LIBBPF environment variables:
+# LIBBPF_FORCE={on,off} on: require link against libbpf;
+# off: disable libbpf probing
+# LIBBPF_DIR Path to libbpf DESTDIR to use
+
+INCLUDE=${1:-"$PWD/include"}
+
+# Output file which is input to Makefile
+CONFIG=config.mk
+
+# Make a temp directory in build tree.
+TMPDIR=$(mktemp -d config.XXXXXX)
+trap 'status=$?; rm -rf $TMPDIR; exit $status' EXIT HUP INT QUIT TERM
+
+check_toolchain()
+{
+ : ${PKG_CONFIG:=pkg-config}
+ : ${AR=ar}
+ : ${CC=gcc}
+ : ${YACC=bison}
+ echo "PKG_CONFIG:=${PKG_CONFIG}" >>$CONFIG
+ echo "AR:=${AR}" >>$CONFIG
+ echo "CC:=${CC}" >>$CONFIG
+ echo "YACC:=${YACC}" >>$CONFIG
+}
+
+check_atm()
+{
+ cat >$TMPDIR/atmtest.c <<EOF
+#include <atm.h>
+int main(int argc, char **argv) {
+ struct atm_qos qos;
+ (void) text2qos("aal5,ubr:sdu=9180,rx:none",&qos,0);
+ return 0;
+}
+EOF
+
+ if $CC -I$INCLUDE -o $TMPDIR/atmtest $TMPDIR/atmtest.c -latm >/dev/null 2>&1; then
+ echo "TC_CONFIG_ATM:=y" >>$CONFIG
+ echo yes
+ else
+ echo no
+ fi
+ rm -f $TMPDIR/atmtest.c $TMPDIR/atmtest
+}
+
+check_xtables()
+{
+ if ! ${PKG_CONFIG} xtables --exists; then
+ echo "TC_CONFIG_NO_XT:=y" >>$CONFIG
+ fi
+}
+
+check_xt()
+{
+ #check if we have xtables from iptables >= 1.4.5.
+ cat >$TMPDIR/ipttest.c <<EOF
+#include <xtables.h>
+#include <linux/netfilter.h>
+static struct xtables_globals test_globals = {
+ .option_offset = 0,
+ .program_name = "tc-ipt",
+ .program_version = XTABLES_VERSION,
+ .orig_opts = NULL,
+ .opts = NULL,
+ .exit_err = NULL,
+};
+
+int main(int argc, char **argv)
+{
+ xtables_init_all(&test_globals, NFPROTO_IPV4);
+ return 0;
+}
+EOF
+
+ if $CC -I$INCLUDE $IPTC -o $TMPDIR/ipttest $TMPDIR/ipttest.c $IPTL \
+ $(${PKG_CONFIG} xtables --cflags --libs) -ldl >/dev/null 2>&1; then
+ echo "TC_CONFIG_XT:=y" >>$CONFIG
+ echo "using xtables"
+ fi
+ rm -f $TMPDIR/ipttest.c $TMPDIR/ipttest
+}
+
+check_xt_old()
+{
+ # bail if previous XT checks has already succeeded.
+ grep -q TC_CONFIG_XT $CONFIG && return
+
+ #check if we don't need our internal header ..
+ cat >$TMPDIR/ipttest.c <<EOF
+#include <xtables.h>
+char *lib_dir;
+unsigned int global_option_offset = 0;
+const char *program_version = XTABLES_VERSION;
+const char *program_name = "tc-ipt";
+struct afinfo afinfo = {
+ .libprefix = "libxt_",
+};
+
+void exit_error(enum exittype status, const char *msg, ...)
+{
+}
+
+int main(int argc, char **argv) {
+
+ return 0;
+}
+
+EOF
+
+ if $CC -I$INCLUDE $IPTC -o $TMPDIR/ipttest $TMPDIR/ipttest.c $IPTL -ldl >/dev/null 2>&1; then
+ echo "TC_CONFIG_XT_OLD:=y" >>$CONFIG
+ echo "using old xtables (no need for xt-internal.h)"
+ fi
+ rm -f $TMPDIR/ipttest.c $TMPDIR/ipttest
+}
+
+check_xt_old_internal_h()
+{
+ # bail if previous XT checks has already succeeded.
+ grep -q TC_CONFIG_XT $CONFIG && return
+
+ #check if we need our own internal.h
+ cat >$TMPDIR/ipttest.c <<EOF
+#include <xtables.h>
+#include "xt-internal.h"
+char *lib_dir;
+unsigned int global_option_offset = 0;
+const char *program_version = XTABLES_VERSION;
+const char *program_name = "tc-ipt";
+struct afinfo afinfo = {
+ .libprefix = "libxt_",
+};
+
+void exit_error(enum exittype status, const char *msg, ...)
+{
+}
+
+int main(int argc, char **argv) {
+
+ return 0;
+}
+
+EOF
+ if $CC -I$INCLUDE $IPTC -o $TMPDIR/ipttest $TMPDIR/ipttest.c $IPTL -ldl >/dev/null 2>&1; then
+ echo "using old xtables with xt-internal.h"
+ echo "TC_CONFIG_XT_OLD_H:=y" >>$CONFIG
+ fi
+ rm -f $TMPDIR/ipttest.c $TMPDIR/ipttest
+}
+
+check_ipt()
+{
+ if ! grep TC_CONFIG_XT $CONFIG > /dev/null; then
+ echo "using iptables"
+ fi
+}
+
+check_ipt_lib_dir()
+{
+ IPT_LIB_DIR=$(${PKG_CONFIG} --variable=xtlibdir xtables)
+ if [ -n "$IPT_LIB_DIR" ]; then
+ echo $IPT_LIB_DIR
+ echo "IPT_LIB_DIR:=$IPT_LIB_DIR" >> $CONFIG
+ return
+ fi
+
+ for dir in /lib /usr/lib /usr/local/lib; do
+ for file in "xtables" "iptables"; do
+ file="$dir/$file/lib*t_*so"
+ if [ -f $file ]; then
+ echo ${file%/*}
+ echo "IPT_LIB_DIR:=${file%/*}" >> $CONFIG
+ return
+ fi
+ done
+ done
+ echo "not found!"
+}
+
+check_setns()
+{
+ cat >$TMPDIR/setnstest.c <<EOF
+#include <sched.h>
+int main(int argc, char **argv)
+{
+ (void)setns(0,0);
+ return 0;
+}
+EOF
+ if $CC -I$INCLUDE -o $TMPDIR/setnstest $TMPDIR/setnstest.c >/dev/null 2>&1; then
+ echo "IP_CONFIG_SETNS:=y" >>$CONFIG
+ echo "yes"
+ echo "CFLAGS += -DHAVE_SETNS" >>$CONFIG
+ else
+ echo "no"
+ fi
+ rm -f $TMPDIR/setnstest.c $TMPDIR/setnstest
+}
+
+check_ipset()
+{
+ cat >$TMPDIR/ipsettest.c <<EOF
+#include <linux/netfilter/ipset/ip_set.h>
+#ifndef IP_SET_INVALID
+#define IPSET_DIM_MAX 3
+typedef unsigned short ip_set_id_t;
+#endif
+#include <linux/netfilter/xt_set.h>
+
+struct xt_set_info info;
+#if IPSET_PROTOCOL == 6 || IPSET_PROTOCOL == 7
+int main(void)
+{
+ return IPSET_MAXNAMELEN;
+}
+#else
+#error unknown ipset version
+#endif
+EOF
+
+ if $CC -I$INCLUDE -o $TMPDIR/ipsettest $TMPDIR/ipsettest.c >/dev/null 2>&1; then
+ echo "TC_CONFIG_IPSET:=y" >>$CONFIG
+ echo "yes"
+ else
+ echo "no"
+ fi
+ rm -f $TMPDIR/ipsettest.c $TMPDIR/ipsettest
+}
+
+check_elf()
+{
+ if ${PKG_CONFIG} libelf --exists; then
+ echo "HAVE_ELF:=y" >>$CONFIG
+ echo "yes"
+
+ echo 'CFLAGS += -DHAVE_ELF' `${PKG_CONFIG} libelf --cflags` >> $CONFIG
+ echo 'LDLIBS += ' `${PKG_CONFIG} libelf --libs` >>$CONFIG
+ else
+ echo "no"
+ fi
+}
+
+have_libbpf_basic()
+{
+ cat >$TMPDIR/libbpf_test.c <<EOF
+#include <bpf/libbpf.h>
+int main(int argc, char **argv) {
+ bpf_program__set_autoload(NULL, false);
+ bpf_map__ifindex(NULL);
+ bpf_map__set_pin_path(NULL, NULL);
+ bpf_object__open_file(NULL, NULL);
+ return 0;
+}
+EOF
+
+ $CC -o $TMPDIR/libbpf_test $TMPDIR/libbpf_test.c $LIBBPF_CFLAGS $LIBBPF_LDLIBS >/dev/null 2>&1
+ local ret=$?
+
+ rm -f $TMPDIR/libbpf_test.c $TMPDIR/libbpf_test
+ return $ret
+}
+
+have_libbpf_sec_name()
+{
+ cat >$TMPDIR/libbpf_sec_test.c <<EOF
+#include <bpf/libbpf.h>
+int main(int argc, char **argv) {
+ void *ptr;
+ bpf_program__section_name(NULL);
+ return 0;
+}
+EOF
+
+ $CC -o $TMPDIR/libbpf_sec_test $TMPDIR/libbpf_sec_test.c $LIBBPF_CFLAGS $LIBBPF_LDLIBS >/dev/null 2>&1
+ local ret=$?
+
+ rm -f $TMPDIR/libbpf_sec_test.c $TMPDIR/libbpf_sec_test
+ return $ret
+}
+
+check_force_libbpf_on()
+{
+ # if set LIBBPF_FORCE=on but no libbpf support, just exist the config
+ # process to make sure we don't build without libbpf.
+ if [ "$LIBBPF_FORCE" = on ]; then
+ echo " LIBBPF_FORCE=on set, but couldn't find a usable libbpf"
+ exit 1
+ fi
+}
+
+check_libbpf()
+{
+ # if set LIBBPF_FORCE=off, disable libbpf entirely
+ if [ "$LIBBPF_FORCE" = off ]; then
+ echo "no"
+ return
+ fi
+
+ if ! ${PKG_CONFIG} libbpf --exists && [ -z "$LIBBPF_DIR" ] ; then
+ echo "no"
+ check_force_libbpf_on
+ return
+ fi
+
+ if [ $(uname -m) = x86_64 ]; then
+ local LIBBPF_LIBDIR="${LIBBPF_DIR}/usr/lib64"
+ else
+ local LIBBPF_LIBDIR="${LIBBPF_DIR}/usr/lib"
+ fi
+
+ if [ -n "$LIBBPF_DIR" ]; then
+ LIBBPF_CFLAGS="-I${LIBBPF_DIR}/usr/include"
+ LIBBPF_LDLIBS="${LIBBPF_LIBDIR}/libbpf.a -lz -lelf"
+ LIBBPF_VERSION=$(PKG_CONFIG_LIBDIR=${LIBBPF_LIBDIR}/pkgconfig ${PKG_CONFIG} libbpf --modversion)
+ else
+ LIBBPF_CFLAGS=$(${PKG_CONFIG} libbpf --cflags)
+ LIBBPF_LDLIBS=$(${PKG_CONFIG} libbpf --libs)
+ LIBBPF_VERSION=$(${PKG_CONFIG} libbpf --modversion)
+ fi
+
+ if ! have_libbpf_basic; then
+ echo "no"
+ echo " libbpf version $LIBBPF_VERSION is too low, please update it to at least 0.1.0"
+ check_force_libbpf_on
+ return
+ else
+ echo "HAVE_LIBBPF:=y" >> $CONFIG
+ echo 'CFLAGS += -DHAVE_LIBBPF ' $LIBBPF_CFLAGS >> $CONFIG
+ echo "CFLAGS += -DLIBBPF_VERSION=\\\"$LIBBPF_VERSION\\\"" >> $CONFIG
+ echo 'LDLIBS += ' $LIBBPF_LDLIBS >> $CONFIG
+
+ if [ -z "$LIBBPF_DIR" ]; then
+ echo "CFLAGS += -DLIBBPF_DYNAMIC" >> $CONFIG
+ fi
+ fi
+
+ # bpf_program__title() is deprecated since libbpf 0.2.0, use
+ # bpf_program__section_name() instead if we support
+ if have_libbpf_sec_name; then
+ echo "HAVE_LIBBPF_SECTION_NAME:=y" >> $CONFIG
+ echo 'CFLAGS += -DHAVE_LIBBPF_SECTION_NAME ' >> $CONFIG
+ fi
+
+ echo "yes"
+ echo " libbpf version $LIBBPF_VERSION"
+}
+
+check_selinux()
+# SELinux is a compile time option in the ss utility
+{
+ if ${PKG_CONFIG} libselinux --exists; then
+ echo "HAVE_SELINUX:=y" >>$CONFIG
+ echo "yes"
+
+ echo 'LDLIBS +=' `${PKG_CONFIG} --libs libselinux` >>$CONFIG
+ echo 'CFLAGS += -DHAVE_SELINUX' `${PKG_CONFIG} --cflags libselinux` >>$CONFIG
+ else
+ echo "no"
+ fi
+}
+
+check_mnl()
+{
+ if ${PKG_CONFIG} libmnl --exists; then
+ echo "HAVE_MNL:=y" >>$CONFIG
+ echo "yes"
+
+ echo 'CFLAGS += -DHAVE_LIBMNL' `${PKG_CONFIG} libmnl --cflags` >>$CONFIG
+ echo 'LDLIBS +=' `${PKG_CONFIG} libmnl --libs` >> $CONFIG
+ else
+ echo "no"
+ fi
+}
+
+check_berkeley_db()
+{
+ cat >$TMPDIR/dbtest.c <<EOF
+#include <fcntl.h>
+#include <stdlib.h>
+#include <db_185.h>
+int main(int argc, char **argv) {
+ dbopen("/tmp/xxx_test_db.db", O_CREAT|O_RDWR, 0644, DB_HASH, NULL);
+ return 0;
+}
+EOF
+ if $CC -I$INCLUDE -o $TMPDIR/dbtest $TMPDIR/dbtest.c -ldb >/dev/null 2>&1; then
+ echo "HAVE_BERKELEY_DB:=y" >>$CONFIG
+ echo "yes"
+ else
+ echo "no"
+ fi
+ rm -f $TMPDIR/dbtest.c $TMPDIR/dbtest
+}
+
+check_strlcpy()
+{
+ cat >$TMPDIR/strtest.c <<EOF
+#include <string.h>
+int main(int argc, char **argv) {
+ char dst[10];
+ strlcpy(dst, "test", sizeof(dst));
+ return 0;
+}
+EOF
+ if $CC -I$INCLUDE -o $TMPDIR/strtest $TMPDIR/strtest.c >/dev/null 2>&1; then
+ echo "no"
+ else
+ if ${PKG_CONFIG} libbsd --exists; then
+ echo 'CFLAGS += -DHAVE_LIBBSD' `${PKG_CONFIG} libbsd --cflags` >>$CONFIG
+ echo 'LDLIBS +=' `${PKG_CONFIG} libbsd --libs` >> $CONFIG
+ echo "no"
+ else
+ echo 'CFLAGS += -DNEED_STRLCPY' >>$CONFIG
+ echo "yes"
+ fi
+ fi
+ rm -f $TMPDIR/strtest.c $TMPDIR/strtest
+}
+
+check_cap()
+{
+ if ${PKG_CONFIG} libcap --exists; then
+ echo "HAVE_CAP:=y" >>$CONFIG
+ echo "yes"
+
+ echo 'CFLAGS += -DHAVE_LIBCAP' `${PKG_CONFIG} libcap --cflags` >>$CONFIG
+ echo 'LDLIBS +=' `${PKG_CONFIG} libcap --libs` >> $CONFIG
+ else
+ echo "no"
+ fi
+}
+
+quiet_config()
+{
+ cat <<EOF
+# user can control verbosity similar to kernel builds (e.g., V=1)
+ifeq ("\$(origin V)", "command line")
+ VERBOSE = \$(V)
+endif
+ifndef VERBOSE
+ VERBOSE = 0
+endif
+ifeq (\$(VERBOSE),1)
+ Q =
+else
+ Q = @
+endif
+
+ifeq (\$(VERBOSE), 0)
+ QUIET_CC = @echo ' CC '\$@;
+ QUIET_AR = @echo ' AR '\$@;
+ QUIET_LINK = @echo ' LINK '\$@;
+ QUIET_YACC = @echo ' YACC '\$@;
+ QUIET_LEX = @echo ' LEX '\$@;
+endif
+EOF
+}
+
+echo "# Generated config based on" $INCLUDE >$CONFIG
+quiet_config >> $CONFIG
+
+check_toolchain
+
+echo "TC schedulers"
+
+echo -n " ATM "
+check_atm
+
+check_xtables
+if ! grep -q TC_CONFIG_NO_XT $CONFIG; then
+ echo -n " IPT "
+ check_xt
+ check_xt_old
+ check_xt_old_internal_h
+ check_ipt
+
+ echo -n " IPSET "
+ check_ipset
+fi
+
+echo
+if ! grep -q TC_CONFIG_NO_XT $CONFIG; then
+ echo -n "iptables modules directory: "
+ check_ipt_lib_dir
+fi
+
+echo -n "libc has setns: "
+check_setns
+
+echo -n "SELinux support: "
+check_selinux
+
+echo -n "libbpf support: "
+check_libbpf
+
+echo -n "ELF support: "
+check_elf
+
+echo -n "libmnl support: "
+check_mnl
+
+echo -n "Berkeley DB: "
+check_berkeley_db
+
+echo -n "need for strlcpy: "
+check_strlcpy
+
+echo -n "libcap support: "
+check_cap
+
+echo >> $CONFIG
+echo "%.o: %.c" >> $CONFIG
+echo ' $(QUIET_CC)$(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(CPPFLAGS) -c -o $@ $<' >> $CONFIG
diff --git a/dcb/.gitignore b/dcb/.gitignore
new file mode 100644
index 0000000..3f26856
--- /dev/null
+++ b/dcb/.gitignore
@@ -0,0 +1 @@
+dcb
diff --git a/dcb/Makefile b/dcb/Makefile
new file mode 100644
index 0000000..3a2e5d4
--- /dev/null
+++ b/dcb/Makefile
@@ -0,0 +1,31 @@
+# SPDX-License-Identifier: GPL-2.0
+include ../config.mk
+
+TARGETS :=
+
+ifeq ($(HAVE_MNL),y)
+
+DCBOBJ = dcb.o \
+ dcb_app.o \
+ dcb_buffer.o \
+ dcb_dcbx.o \
+ dcb_ets.o \
+ dcb_maxrate.o \
+ dcb_pfc.o
+TARGETS += dcb
+LDLIBS += -lm
+
+endif
+
+all: $(TARGETS) $(LIBS)
+
+dcb: $(DCBOBJ) $(LIBNETLINK)
+ $(QUIET_LINK)$(CC) $^ $(LDFLAGS) $(LDLIBS) -o $@
+
+install: all
+ for i in $(TARGETS); \
+ do install -m 0755 $$i $(DESTDIR)$(SBINDIR); \
+ done
+
+clean:
+ rm -f $(DCBOBJ) $(TARGETS)
diff --git a/dcb/dcb.c b/dcb/dcb.c
new file mode 100644
index 0000000..64a9ef0
--- /dev/null
+++ b/dcb/dcb.c
@@ -0,0 +1,610 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+#include <inttypes.h>
+#include <stdio.h>
+#include <linux/dcbnl.h>
+#include <libmnl/libmnl.h>
+#include <getopt.h>
+
+#include "dcb.h"
+#include "mnl_utils.h"
+#include "namespace.h"
+#include "utils.h"
+#include "version.h"
+
+static int dcb_init(struct dcb *dcb)
+{
+ dcb->buf = malloc(MNL_SOCKET_BUFFER_SIZE);
+ if (dcb->buf == NULL) {
+ perror("Netlink buffer allocation");
+ return -1;
+ }
+
+ dcb->nl = mnlu_socket_open(NETLINK_ROUTE);
+ if (dcb->nl == NULL) {
+ perror("Open netlink socket");
+ goto err_socket_open;
+ }
+
+ new_json_obj_plain(dcb->json_output);
+ return 0;
+
+err_socket_open:
+ free(dcb->buf);
+ return -1;
+}
+
+static void dcb_fini(struct dcb *dcb)
+{
+ delete_json_obj_plain();
+ mnl_socket_close(dcb->nl);
+ free(dcb->buf);
+}
+
+static struct dcb *dcb_alloc(void)
+{
+ struct dcb *dcb;
+
+ dcb = calloc(1, sizeof(*dcb));
+ if (!dcb)
+ return NULL;
+ return dcb;
+}
+
+static void dcb_free(struct dcb *dcb)
+{
+ free(dcb);
+}
+
+struct dcb_get_attribute {
+ struct dcb *dcb;
+ int attr;
+ void *payload;
+ __u16 payload_len;
+};
+
+static int dcb_get_attribute_attr_ieee_cb(const struct nlattr *attr, void *data)
+{
+ struct dcb_get_attribute *ga = data;
+
+ if (mnl_attr_get_type(attr) != ga->attr)
+ return MNL_CB_OK;
+
+ ga->payload = mnl_attr_get_payload(attr);
+ ga->payload_len = mnl_attr_get_payload_len(attr);
+ return MNL_CB_STOP;
+}
+
+static int dcb_get_attribute_attr_cb(const struct nlattr *attr, void *data)
+{
+ if (mnl_attr_get_type(attr) != DCB_ATTR_IEEE)
+ return MNL_CB_OK;
+
+ return mnl_attr_parse_nested(attr, dcb_get_attribute_attr_ieee_cb, data);
+}
+
+static int dcb_get_attribute_cb(const struct nlmsghdr *nlh, void *data)
+{
+ return mnl_attr_parse(nlh, sizeof(struct dcbmsg), dcb_get_attribute_attr_cb, data);
+}
+
+static int dcb_get_attribute_bare_cb(const struct nlmsghdr *nlh, void *data)
+{
+ /* Bare attributes (e.g. DCB_ATTR_DCBX) are not wrapped inside an IEEE
+ * container, so this does not have to go through unpacking in
+ * dcb_get_attribute_attr_cb().
+ */
+ return mnl_attr_parse(nlh, sizeof(struct dcbmsg),
+ dcb_get_attribute_attr_ieee_cb, data);
+}
+
+struct dcb_set_attribute_response {
+ int response_attr;
+};
+
+static int dcb_set_attribute_attr_cb(const struct nlattr *attr, void *data)
+{
+ struct dcb_set_attribute_response *resp = data;
+ uint16_t len;
+ uint8_t err;
+
+ if (mnl_attr_get_type(attr) != resp->response_attr)
+ return MNL_CB_OK;
+
+ len = mnl_attr_get_payload_len(attr);
+ if (len != 1) {
+ fprintf(stderr, "Response attribute expected to have size 1, not %d\n", len);
+ return MNL_CB_ERROR;
+ }
+
+ err = mnl_attr_get_u8(attr);
+ if (err) {
+ fprintf(stderr, "Error when attempting to set attribute: %s\n",
+ strerror(err));
+ return MNL_CB_ERROR;
+ }
+
+ return MNL_CB_STOP;
+}
+
+static int dcb_set_attribute_cb(const struct nlmsghdr *nlh, void *data)
+{
+ return mnl_attr_parse(nlh, sizeof(struct dcbmsg), dcb_set_attribute_attr_cb, data);
+}
+
+static int dcb_talk(struct dcb *dcb, struct nlmsghdr *nlh, mnl_cb_t cb, void *data)
+{
+ int ret;
+
+ ret = mnl_socket_sendto(dcb->nl, nlh, nlh->nlmsg_len);
+ if (ret < 0) {
+ perror("mnl_socket_sendto");
+ return -1;
+ }
+
+ return mnlu_socket_recv_run(dcb->nl, nlh->nlmsg_seq, dcb->buf, MNL_SOCKET_BUFFER_SIZE,
+ cb, data);
+}
+
+static struct nlmsghdr *dcb_prepare(struct dcb *dcb, const char *dev,
+ uint32_t nlmsg_type, uint8_t dcb_cmd)
+{
+ struct dcbmsg dcbm = {
+ .cmd = dcb_cmd,
+ };
+ struct nlmsghdr *nlh;
+
+ nlh = mnlu_msg_prepare(dcb->buf, nlmsg_type, NLM_F_REQUEST, &dcbm, sizeof(dcbm));
+ mnl_attr_put_strz(nlh, DCB_ATTR_IFNAME, dev);
+ return nlh;
+}
+
+static int __dcb_get_attribute(struct dcb *dcb, int command,
+ const char *dev, int attr,
+ void **payload_p, __u16 *payload_len_p,
+ int (*get_attribute_cb)(const struct nlmsghdr *nlh,
+ void *data))
+{
+ struct dcb_get_attribute ga;
+ struct nlmsghdr *nlh;
+ int ret;
+
+ nlh = dcb_prepare(dcb, dev, RTM_GETDCB, command);
+
+ ga = (struct dcb_get_attribute) {
+ .dcb = dcb,
+ .attr = attr,
+ .payload = NULL,
+ };
+ ret = dcb_talk(dcb, nlh, get_attribute_cb, &ga);
+ if (ret) {
+ perror("Attribute read");
+ return ret;
+ }
+ if (ga.payload == NULL) {
+ perror("Attribute not found");
+ return -ENOENT;
+ }
+
+ *payload_p = ga.payload;
+ *payload_len_p = ga.payload_len;
+ return 0;
+}
+
+int dcb_get_attribute_va(struct dcb *dcb, const char *dev, int attr,
+ void **payload_p, __u16 *payload_len_p)
+{
+ return __dcb_get_attribute(dcb, DCB_CMD_IEEE_GET, dev, attr,
+ payload_p, payload_len_p,
+ dcb_get_attribute_cb);
+}
+
+int dcb_get_attribute_bare(struct dcb *dcb, int cmd, const char *dev, int attr,
+ void **payload_p, __u16 *payload_len_p)
+{
+ return __dcb_get_attribute(dcb, cmd, dev, attr,
+ payload_p, payload_len_p,
+ dcb_get_attribute_bare_cb);
+}
+
+int dcb_get_attribute(struct dcb *dcb, const char *dev, int attr, void *data, size_t data_len)
+{
+ __u16 payload_len;
+ void *payload;
+ int ret;
+
+ ret = dcb_get_attribute_va(dcb, dev, attr, &payload, &payload_len);
+ if (ret)
+ return ret;
+
+ if (payload_len != data_len) {
+ fprintf(stderr, "Wrong len %d, expected %zd\n", payload_len, data_len);
+ return -EINVAL;
+ }
+
+ memcpy(data, payload, data_len);
+ return 0;
+}
+
+static int __dcb_set_attribute(struct dcb *dcb, int command, const char *dev,
+ int (*cb)(struct dcb *, struct nlmsghdr *, void *),
+ void *data, int response_attr)
+{
+ struct dcb_set_attribute_response resp = {
+ .response_attr = response_attr,
+ };
+ struct nlmsghdr *nlh;
+ int ret;
+
+ nlh = dcb_prepare(dcb, dev, RTM_SETDCB, command);
+
+ ret = cb(dcb, nlh, data);
+ if (ret)
+ return ret;
+
+ ret = dcb_talk(dcb, nlh, dcb_set_attribute_cb, &resp);
+ if (ret) {
+ perror("Attribute write");
+ return ret;
+ }
+ return 0;
+}
+
+struct dcb_set_attribute_ieee_cb {
+ int (*cb)(struct dcb *dcb, struct nlmsghdr *nlh, void *data);
+ void *data;
+};
+
+static int dcb_set_attribute_ieee_cb(struct dcb *dcb, struct nlmsghdr *nlh, void *data)
+{
+ struct dcb_set_attribute_ieee_cb *ieee_data = data;
+ struct nlattr *nest;
+ int ret;
+
+ nest = mnl_attr_nest_start(nlh, DCB_ATTR_IEEE);
+ ret = ieee_data->cb(dcb, nlh, ieee_data->data);
+ if (ret)
+ return ret;
+ mnl_attr_nest_end(nlh, nest);
+
+ return 0;
+}
+
+int dcb_set_attribute_va(struct dcb *dcb, int command, const char *dev,
+ int (*cb)(struct dcb *dcb, struct nlmsghdr *nlh, void *data),
+ void *data)
+{
+ struct dcb_set_attribute_ieee_cb ieee_data = {
+ .cb = cb,
+ .data = data,
+ };
+
+ return __dcb_set_attribute(dcb, command, dev,
+ &dcb_set_attribute_ieee_cb, &ieee_data,
+ DCB_ATTR_IEEE);
+}
+
+struct dcb_set_attribute {
+ int attr;
+ const void *data;
+ size_t data_len;
+};
+
+static int dcb_set_attribute_put(struct dcb *dcb, struct nlmsghdr *nlh, void *data)
+{
+ struct dcb_set_attribute *dsa = data;
+
+ mnl_attr_put(nlh, dsa->attr, dsa->data_len, dsa->data);
+ return 0;
+}
+
+int dcb_set_attribute(struct dcb *dcb, const char *dev, int attr, const void *data, size_t data_len)
+{
+ struct dcb_set_attribute dsa = {
+ .attr = attr,
+ .data = data,
+ .data_len = data_len,
+ };
+
+ return dcb_set_attribute_va(dcb, DCB_CMD_IEEE_SET, dev,
+ &dcb_set_attribute_put, &dsa);
+}
+
+int dcb_set_attribute_bare(struct dcb *dcb, int command, const char *dev,
+ int attr, const void *data, size_t data_len,
+ int response_attr)
+{
+ struct dcb_set_attribute dsa = {
+ .attr = attr,
+ .data = data,
+ .data_len = data_len,
+ };
+
+ return __dcb_set_attribute(dcb, command, dev,
+ &dcb_set_attribute_put, &dsa, response_attr);
+}
+
+void dcb_print_array_u8(const __u8 *array, size_t size)
+{
+ SPRINT_BUF(b);
+ size_t i;
+
+ for (i = 0; i < size; i++) {
+ snprintf(b, sizeof(b), "%zd:%%d ", i);
+ print_uint(PRINT_ANY, NULL, b, array[i]);
+ }
+}
+
+void dcb_print_array_u64(const __u64 *array, size_t size)
+{
+ SPRINT_BUF(b);
+ size_t i;
+
+ for (i = 0; i < size; i++) {
+ snprintf(b, sizeof(b), "%zd:%%" PRIu64 " ", i);
+ print_u64(PRINT_ANY, NULL, b, array[i]);
+ }
+}
+
+void dcb_print_array_on_off(const __u8 *array, size_t size)
+{
+ SPRINT_BUF(b);
+ size_t i;
+
+ for (i = 0; i < size; i++) {
+ snprintf(b, sizeof(b), "%zd:%%s ", i);
+ print_on_off(PRINT_ANY, NULL, b, array[i]);
+ }
+}
+
+void dcb_print_array_kw(const __u8 *array, size_t array_size,
+ const char *const kw[], size_t kw_size)
+{
+ SPRINT_BUF(b);
+ size_t i;
+
+ for (i = 0; i < array_size; i++) {
+ __u8 emt = array[i];
+
+ snprintf(b, sizeof(b), "%zd:%%s ", i);
+ if (emt < kw_size && kw[emt])
+ print_string(PRINT_ANY, NULL, b, kw[emt]);
+ else
+ print_string(PRINT_ANY, NULL, b, "???");
+ }
+}
+
+void dcb_print_named_array(const char *json_name, const char *fp_name,
+ const __u8 *array, size_t size,
+ void (*print_array)(const __u8 *, size_t))
+{
+ open_json_array(PRINT_JSON, json_name);
+ print_string(PRINT_FP, NULL, "%s ", fp_name);
+ print_array(array, size);
+ close_json_array(PRINT_JSON, json_name);
+}
+
+int dcb_parse_mapping(const char *what_key, __u32 key, __u32 max_key,
+ const char *what_value, __u64 value, __u64 max_value,
+ void (*set_array)(__u32 index, __u64 value, void *data),
+ void *set_array_data)
+{
+ bool is_all = key == (__u32) -1;
+
+ if (!is_all && key > max_key) {
+ fprintf(stderr, "In %s:%s mapping, %s is expected to be 0..%d\n",
+ what_key, what_value, what_key, max_key);
+ return -EINVAL;
+ }
+
+ if (value > max_value) {
+ fprintf(stderr, "In %s:%s mapping, %s is expected to be 0..%llu\n",
+ what_key, what_value, what_value, max_value);
+ return -EINVAL;
+ }
+
+ if (is_all) {
+ for (key = 0; key <= max_key; key++)
+ set_array(key, value, set_array_data);
+ } else {
+ set_array(key, value, set_array_data);
+ }
+
+ return 0;
+}
+
+void dcb_set_u8(__u32 key, __u64 value, void *data)
+{
+ __u8 *array = data;
+
+ array[key] = value;
+}
+
+void dcb_set_u32(__u32 key, __u64 value, void *data)
+{
+ __u32 *array = data;
+
+ array[key] = value;
+}
+
+void dcb_set_u64(__u32 key, __u64 value, void *data)
+{
+ __u64 *array = data;
+
+ array[key] = value;
+}
+
+int dcb_cmd_parse_dev(struct dcb *dcb, int argc, char **argv,
+ int (*and_then)(struct dcb *dcb, const char *dev,
+ int argc, char **argv),
+ void (*help)(void))
+{
+ const char *dev;
+
+ if (!argc || matches(*argv, "help") == 0) {
+ help();
+ return 0;
+ } else if (matches(*argv, "dev") == 0) {
+ NEXT_ARG();
+ dev = *argv;
+ if (check_ifname(dev)) {
+ invarg("not a valid ifname", *argv);
+ return -EINVAL;
+ }
+ NEXT_ARG_FWD();
+ return and_then(dcb, dev, argc, argv);
+ } else {
+ fprintf(stderr, "Expected `dev DEV', not `%s'", *argv);
+ help();
+ return -EINVAL;
+ }
+}
+
+static void dcb_help(void)
+{
+ fprintf(stderr,
+ "Usage: dcb [ OPTIONS ] OBJECT { COMMAND | help }\n"
+ " dcb [ -f | --force ] { -b | --batch } filename [ -n | --netns ] netnsname\n"
+ "where OBJECT := { app | buffer | dcbx | ets | maxrate | pfc }\n"
+ " OPTIONS := [ -V | --Version | -i | --iec | -j | --json\n"
+ " | -N | --Numeric | -p | --pretty\n"
+ " | -s | --statistics | -v | --verbose]\n");
+}
+
+static int dcb_cmd(struct dcb *dcb, int argc, char **argv)
+{
+ if (!argc || matches(*argv, "help") == 0) {
+ dcb_help();
+ return 0;
+ } else if (matches(*argv, "app") == 0) {
+ return dcb_cmd_app(dcb, argc - 1, argv + 1);
+ } else if (matches(*argv, "buffer") == 0) {
+ return dcb_cmd_buffer(dcb, argc - 1, argv + 1);
+ } else if (matches(*argv, "dcbx") == 0) {
+ return dcb_cmd_dcbx(dcb, argc - 1, argv + 1);
+ } else if (matches(*argv, "ets") == 0) {
+ return dcb_cmd_ets(dcb, argc - 1, argv + 1);
+ } else if (matches(*argv, "maxrate") == 0) {
+ return dcb_cmd_maxrate(dcb, argc - 1, argv + 1);
+ } else if (matches(*argv, "pfc") == 0) {
+ return dcb_cmd_pfc(dcb, argc - 1, argv + 1);
+ }
+
+ fprintf(stderr, "Object \"%s\" is unknown\n", *argv);
+ return -ENOENT;
+}
+
+static int dcb_batch_cmd(int argc, char *argv[], void *data)
+{
+ struct dcb *dcb = data;
+
+ return dcb_cmd(dcb, argc, argv);
+}
+
+static int dcb_batch(struct dcb *dcb, const char *name, bool force)
+{
+ return do_batch(name, force, dcb_batch_cmd, dcb);
+}
+
+int main(int argc, char **argv)
+{
+ static const struct option long_options[] = {
+ { "Version", no_argument, NULL, 'V' },
+ { "force", no_argument, NULL, 'f' },
+ { "batch", required_argument, NULL, 'b' },
+ { "iec", no_argument, NULL, 'i' },
+ { "json", no_argument, NULL, 'j' },
+ { "Numeric", no_argument, NULL, 'N' },
+ { "pretty", no_argument, NULL, 'p' },
+ { "statistics", no_argument, NULL, 's' },
+ { "netns", required_argument, NULL, 'n' },
+ { "help", no_argument, NULL, 'h' },
+ { NULL, 0, NULL, 0 }
+ };
+ const char *batch_file = NULL;
+ bool force = false;
+ struct dcb *dcb;
+ int opt;
+ int err;
+ int ret;
+
+ dcb = dcb_alloc();
+ if (!dcb) {
+ fprintf(stderr, "Failed to allocate memory for dcb\n");
+ return EXIT_FAILURE;
+ }
+
+ while ((opt = getopt_long(argc, argv, "b:fhijn:psvNV",
+ long_options, NULL)) >= 0) {
+
+ switch (opt) {
+ case 'V':
+ printf("dcb utility, iproute2-%s\n", version);
+ ret = EXIT_SUCCESS;
+ goto dcb_free;
+ case 'f':
+ force = true;
+ break;
+ case 'b':
+ batch_file = optarg;
+ break;
+ case 'j':
+ dcb->json_output = true;
+ break;
+ case 'N':
+ dcb->numeric = true;
+ break;
+ case 'p':
+ pretty = true;
+ break;
+ case 's':
+ dcb->stats = true;
+ break;
+ case 'n':
+ if (netns_switch(optarg)) {
+ ret = EXIT_FAILURE;
+ goto dcb_free;
+ }
+ break;
+ case 'i':
+ dcb->use_iec = true;
+ break;
+ case 'h':
+ dcb_help();
+ return 0;
+ default:
+ fprintf(stderr, "Unknown option.\n");
+ dcb_help();
+ ret = EXIT_FAILURE;
+ goto dcb_free;
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ err = dcb_init(dcb);
+ if (err) {
+ ret = EXIT_FAILURE;
+ goto dcb_free;
+ }
+
+ if (batch_file)
+ err = dcb_batch(dcb, batch_file, force);
+ else
+ err = dcb_cmd(dcb, argc, argv);
+
+ if (err) {
+ ret = EXIT_FAILURE;
+ goto dcb_fini;
+ }
+
+ ret = EXIT_SUCCESS;
+
+dcb_fini:
+ dcb_fini(dcb);
+dcb_free:
+ dcb_free(dcb);
+
+ return ret;
+}
diff --git a/dcb/dcb.h b/dcb/dcb.h
new file mode 100644
index 0000000..244c3d3
--- /dev/null
+++ b/dcb/dcb.h
@@ -0,0 +1,81 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __DCB_H__
+#define __DCB_H__ 1
+
+#include <libmnl/libmnl.h>
+#include <stdbool.h>
+#include <stddef.h>
+
+/* dcb.c */
+
+struct dcb {
+ char *buf;
+ struct mnl_socket *nl;
+ bool json_output;
+ bool stats;
+ bool use_iec;
+ bool numeric;
+};
+
+int dcb_parse_mapping(const char *what_key, __u32 key, __u32 max_key,
+ const char *what_value, __u64 value, __u64 max_value,
+ void (*set_array)(__u32 index, __u64 value, void *data),
+ void *set_array_data);
+int dcb_cmd_parse_dev(struct dcb *dcb, int argc, char **argv,
+ int (*and_then)(struct dcb *dcb, const char *dev,
+ int argc, char **argv),
+ void (*help)(void));
+
+void dcb_set_u8(__u32 key, __u64 value, void *data);
+void dcb_set_u32(__u32 key, __u64 value, void *data);
+void dcb_set_u64(__u32 key, __u64 value, void *data);
+
+int dcb_get_attribute(struct dcb *dcb, const char *dev, int attr,
+ void *data, size_t data_len);
+int dcb_set_attribute(struct dcb *dcb, const char *dev, int attr,
+ const void *data, size_t data_len);
+int dcb_get_attribute_va(struct dcb *dcb, const char *dev, int attr,
+ void **payload_p, __u16 *payload_len_p);
+int dcb_set_attribute_va(struct dcb *dcb, int command, const char *dev,
+ int (*cb)(struct dcb *dcb, struct nlmsghdr *nlh, void *data),
+ void *data);
+int dcb_get_attribute_bare(struct dcb *dcb, int cmd, const char *dev, int attr,
+ void **payload_p, __u16 *payload_len_p);
+int dcb_set_attribute_bare(struct dcb *dcb, int command, const char *dev,
+ int attr, const void *data, size_t data_len,
+ int response_attr);
+
+void dcb_print_named_array(const char *json_name, const char *fp_name,
+ const __u8 *array, size_t size,
+ void (*print_array)(const __u8 *, size_t));
+void dcb_print_array_u8(const __u8 *array, size_t size);
+void dcb_print_array_u64(const __u64 *array, size_t size);
+void dcb_print_array_on_off(const __u8 *array, size_t size);
+void dcb_print_array_kw(const __u8 *array, size_t array_size,
+ const char *const kw[], size_t kw_size);
+
+/* dcb_app.c */
+
+int dcb_cmd_app(struct dcb *dcb, int argc, char **argv);
+
+/* dcb_buffer.c */
+
+int dcb_cmd_buffer(struct dcb *dcb, int argc, char **argv);
+
+/* dcb_dcbx.c */
+
+int dcb_cmd_dcbx(struct dcb *dcb, int argc, char **argv);
+
+/* dcb_ets.c */
+
+int dcb_cmd_ets(struct dcb *dcb, int argc, char **argv);
+
+/* dcb_maxrate.c */
+
+int dcb_cmd_maxrate(struct dcb *dcb, int argc, char **argv);
+
+/* dcb_pfc.c */
+
+int dcb_cmd_pfc(struct dcb *dcb, int argc, char **argv);
+
+#endif /* __DCB_H__ */
diff --git a/dcb/dcb_app.c b/dcb/dcb_app.c
new file mode 100644
index 0000000..c4816bc
--- /dev/null
+++ b/dcb/dcb_app.c
@@ -0,0 +1,795 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+#include <errno.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <libmnl/libmnl.h>
+#include <linux/dcbnl.h>
+
+#include "dcb.h"
+#include "utils.h"
+#include "rt_names.h"
+
+static void dcb_app_help_add(void)
+{
+ fprintf(stderr,
+ "Usage: dcb app { add | del | replace } dev STRING\n"
+ " [ default-prio PRIO ]\n"
+ " [ ethtype-prio ET:PRIO ]\n"
+ " [ stream-port-prio PORT:PRIO ]\n"
+ " [ dgram-port-prio PORT:PRIO ]\n"
+ " [ port-prio PORT:PRIO ]\n"
+ " [ dscp-prio INTEGER:PRIO ]\n"
+ "\n"
+ " where PRIO := { 0 .. 7 }\n"
+ " ET := { 0x600 .. 0xffff }\n"
+ " PORT := { 1 .. 65535 }\n"
+ " DSCP := { 0 .. 63 }\n"
+ "\n"
+ );
+}
+
+static void dcb_app_help_show_flush(void)
+{
+ fprintf(stderr,
+ "Usage: dcb app { show | flush } dev STRING\n"
+ " [ default-prio ]\n"
+ " [ ethtype-prio ]\n"
+ " [ stream-port-prio ]\n"
+ " [ dgram-port-prio ]\n"
+ " [ port-prio ]\n"
+ " [ dscp-prio ]\n"
+ "\n"
+ );
+}
+
+static void dcb_app_help(void)
+{
+ fprintf(stderr,
+ "Usage: dcb app help\n"
+ "\n"
+ );
+ dcb_app_help_show_flush();
+ dcb_app_help_add();
+}
+
+struct dcb_app_table {
+ struct dcb_app *apps;
+ size_t n_apps;
+};
+
+static void dcb_app_table_fini(struct dcb_app_table *tab)
+{
+ free(tab->apps);
+}
+
+static int dcb_app_table_push(struct dcb_app_table *tab, struct dcb_app *app)
+{
+ struct dcb_app *apps = realloc(tab->apps, (tab->n_apps + 1) * sizeof(*tab->apps));
+
+ if (apps == NULL) {
+ perror("Cannot allocate APP table");
+ return -ENOMEM;
+ }
+
+ tab->apps = apps;
+ tab->apps[tab->n_apps++] = *app;
+ return 0;
+}
+
+static void dcb_app_table_remove_existing(struct dcb_app_table *a,
+ const struct dcb_app_table *b)
+{
+ size_t ia, ja;
+ size_t ib;
+
+ for (ia = 0, ja = 0; ia < a->n_apps; ia++) {
+ struct dcb_app *aa = &a->apps[ia];
+ bool found = false;
+
+ for (ib = 0; ib < b->n_apps; ib++) {
+ const struct dcb_app *ab = &b->apps[ib];
+
+ if (aa->selector == ab->selector &&
+ aa->protocol == ab->protocol &&
+ aa->priority == ab->priority) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found)
+ a->apps[ja++] = *aa;
+ }
+
+ a->n_apps = ja;
+}
+
+static void dcb_app_table_remove_replaced(struct dcb_app_table *a,
+ const struct dcb_app_table *b)
+{
+ size_t ia, ja;
+ size_t ib;
+
+ for (ia = 0, ja = 0; ia < a->n_apps; ia++) {
+ struct dcb_app *aa = &a->apps[ia];
+ bool present = false;
+ bool found = false;
+
+ for (ib = 0; ib < b->n_apps; ib++) {
+ const struct dcb_app *ab = &b->apps[ib];
+
+ if (aa->selector == ab->selector &&
+ aa->protocol == ab->protocol)
+ present = true;
+ else
+ continue;
+
+ if (aa->priority == ab->priority) {
+ found = true;
+ break;
+ }
+ }
+
+ /* Entries that remain in A will be removed, so keep in the
+ * table only APP entries whose sel/pid is mentioned in B,
+ * but that do not have the full sel/pid/prio match.
+ */
+ if (present && !found)
+ a->apps[ja++] = *aa;
+ }
+
+ a->n_apps = ja;
+}
+
+static int dcb_app_table_copy(struct dcb_app_table *a,
+ const struct dcb_app_table *b)
+{
+ size_t i;
+ int ret;
+
+ for (i = 0; i < b->n_apps; i++) {
+ ret = dcb_app_table_push(a, &b->apps[i]);
+ if (ret != 0)
+ return ret;
+ }
+ return 0;
+}
+
+static int dcb_app_cmp(const struct dcb_app *a, const struct dcb_app *b)
+{
+ if (a->protocol < b->protocol)
+ return -1;
+ if (a->protocol > b->protocol)
+ return 1;
+ return a->priority - b->priority;
+}
+
+static int dcb_app_cmp_cb(const void *a, const void *b)
+{
+ return dcb_app_cmp(a, b);
+}
+
+static void dcb_app_table_sort(struct dcb_app_table *tab)
+{
+ qsort(tab->apps, tab->n_apps, sizeof(*tab->apps), dcb_app_cmp_cb);
+}
+
+struct dcb_app_parse_mapping {
+ __u8 selector;
+ struct dcb_app_table *tab;
+ int err;
+};
+
+static void dcb_app_parse_mapping_cb(__u32 key, __u64 value, void *data)
+{
+ struct dcb_app_parse_mapping *pm = data;
+ struct dcb_app app = {
+ .selector = pm->selector,
+ .priority = value,
+ .protocol = key,
+ };
+
+ if (pm->err)
+ return;
+
+ pm->err = dcb_app_table_push(pm->tab, &app);
+}
+
+static int dcb_app_parse_mapping_ethtype_prio(__u32 key, char *value, void *data)
+{
+ __u8 prio;
+
+ if (key < 0x600) {
+ fprintf(stderr, "Protocol IDs < 0x600 are reserved for EtherType\n");
+ return -EINVAL;
+ }
+
+ if (get_u8(&prio, value, 0))
+ return -EINVAL;
+
+ return dcb_parse_mapping("ETHTYPE", key, 0xffff,
+ "PRIO", prio, IEEE_8021QAZ_MAX_TCS - 1,
+ dcb_app_parse_mapping_cb, data);
+}
+
+static int dcb_app_parse_dscp(__u32 *key, const char *arg)
+{
+ if (parse_mapping_num_all(key, arg) == 0)
+ return 0;
+
+ if (rtnl_dsfield_a2n(key, arg) != 0)
+ return -1;
+
+ if (*key & 0x03) {
+ fprintf(stderr, "The values `%s' uses non-DSCP bits.\n", arg);
+ return -1;
+ }
+
+ /* Unshift the value to convert it from dsfield to DSCP. */
+ *key >>= 2;
+ return 0;
+}
+
+static int dcb_app_parse_mapping_dscp_prio(__u32 key, char *value, void *data)
+{
+ __u8 prio;
+
+ if (get_u8(&prio, value, 0))
+ return -EINVAL;
+
+ return dcb_parse_mapping("DSCP", key, 63,
+ "PRIO", prio, IEEE_8021QAZ_MAX_TCS - 1,
+ dcb_app_parse_mapping_cb, data);
+}
+
+static int dcb_app_parse_mapping_port_prio(__u32 key, char *value, void *data)
+{
+ __u8 prio;
+
+ if (key == 0) {
+ fprintf(stderr, "Port ID of 0 is invalid\n");
+ return -EINVAL;
+ }
+
+ if (get_u8(&prio, value, 0))
+ return -EINVAL;
+
+ return dcb_parse_mapping("PORT", key, 0xffff,
+ "PRIO", prio, IEEE_8021QAZ_MAX_TCS - 1,
+ dcb_app_parse_mapping_cb, data);
+}
+
+static int dcb_app_parse_default_prio(int *argcp, char ***argvp, struct dcb_app_table *tab)
+{
+ int argc = *argcp;
+ char **argv = *argvp;
+ int ret = 0;
+
+ while (argc > 0) {
+ struct dcb_app app;
+ __u8 prio;
+
+ if (get_u8(&prio, *argv, 0)) {
+ ret = 1;
+ break;
+ }
+
+ app = (struct dcb_app){
+ .selector = IEEE_8021QAZ_APP_SEL_ETHERTYPE,
+ .protocol = 0,
+ .priority = prio,
+ };
+ ret = dcb_app_table_push(tab, &app);
+ if (ret != 0)
+ break;
+
+ argc--, argv++;
+ }
+
+ *argcp = argc;
+ *argvp = argv;
+ return ret;
+}
+
+static bool dcb_app_is_ethtype(const struct dcb_app *app)
+{
+ return app->selector == IEEE_8021QAZ_APP_SEL_ETHERTYPE &&
+ app->protocol != 0;
+}
+
+static bool dcb_app_is_default(const struct dcb_app *app)
+{
+ return app->selector == IEEE_8021QAZ_APP_SEL_ETHERTYPE &&
+ app->protocol == 0;
+}
+
+static bool dcb_app_is_dscp(const struct dcb_app *app)
+{
+ return app->selector == IEEE_8021QAZ_APP_SEL_DSCP;
+}
+
+static bool dcb_app_is_stream_port(const struct dcb_app *app)
+{
+ return app->selector == IEEE_8021QAZ_APP_SEL_STREAM;
+}
+
+static bool dcb_app_is_dgram_port(const struct dcb_app *app)
+{
+ return app->selector == IEEE_8021QAZ_APP_SEL_DGRAM;
+}
+
+static bool dcb_app_is_port(const struct dcb_app *app)
+{
+ return app->selector == IEEE_8021QAZ_APP_SEL_ANY;
+}
+
+static int dcb_app_print_key_dec(__u16 protocol)
+{
+ return print_uint(PRINT_ANY, NULL, "%d:", protocol);
+}
+
+static int dcb_app_print_key_hex(__u16 protocol)
+{
+ return print_uint(PRINT_ANY, NULL, "%x:", protocol);
+}
+
+static int dcb_app_print_key_dscp(__u16 protocol)
+{
+ const char *name = rtnl_dsfield_get_name(protocol << 2);
+
+
+ if (!is_json_context() && name != NULL)
+ return print_string(PRINT_FP, NULL, "%s:", name);
+ return print_uint(PRINT_ANY, NULL, "%d:", protocol);
+}
+
+static void dcb_app_print_filtered(const struct dcb_app_table *tab,
+ bool (*filter)(const struct dcb_app *),
+ int (*print_key)(__u16 protocol),
+ const char *json_name,
+ const char *fp_name)
+{
+ bool first = true;
+ size_t i;
+
+ for (i = 0; i < tab->n_apps; i++) {
+ struct dcb_app *app = &tab->apps[i];
+
+ if (!filter(app))
+ continue;
+ if (first) {
+ open_json_array(PRINT_JSON, json_name);
+ print_string(PRINT_FP, NULL, "%s ", fp_name);
+ first = false;
+ }
+
+ open_json_array(PRINT_JSON, NULL);
+ print_key(app->protocol);
+ print_uint(PRINT_ANY, NULL, "%d ", app->priority);
+ close_json_array(PRINT_JSON, NULL);
+ }
+
+ if (!first) {
+ close_json_array(PRINT_JSON, json_name);
+ print_nl();
+ }
+}
+
+static void dcb_app_print_ethtype_prio(const struct dcb_app_table *tab)
+{
+ dcb_app_print_filtered(tab, dcb_app_is_ethtype, dcb_app_print_key_hex,
+ "ethtype_prio", "ethtype-prio");
+}
+
+static void dcb_app_print_dscp_prio(const struct dcb *dcb,
+ const struct dcb_app_table *tab)
+{
+ dcb_app_print_filtered(tab, dcb_app_is_dscp,
+ dcb->numeric ? dcb_app_print_key_dec
+ : dcb_app_print_key_dscp,
+ "dscp_prio", "dscp-prio");
+}
+
+static void dcb_app_print_stream_port_prio(const struct dcb_app_table *tab)
+{
+ dcb_app_print_filtered(tab, dcb_app_is_stream_port, dcb_app_print_key_dec,
+ "stream_port_prio", "stream-port-prio");
+}
+
+static void dcb_app_print_dgram_port_prio(const struct dcb_app_table *tab)
+{
+ dcb_app_print_filtered(tab, dcb_app_is_dgram_port, dcb_app_print_key_dec,
+ "dgram_port_prio", "dgram-port-prio");
+}
+
+static void dcb_app_print_port_prio(const struct dcb_app_table *tab)
+{
+ dcb_app_print_filtered(tab, dcb_app_is_port, dcb_app_print_key_dec,
+ "port_prio", "port-prio");
+}
+
+static void dcb_app_print_default_prio(const struct dcb_app_table *tab)
+{
+ bool first = true;
+ size_t i;
+
+ for (i = 0; i < tab->n_apps; i++) {
+ if (!dcb_app_is_default(&tab->apps[i]))
+ continue;
+ if (first) {
+ open_json_array(PRINT_JSON, "default_prio");
+ print_string(PRINT_FP, NULL, "default-prio ", NULL);
+ first = false;
+ }
+ print_uint(PRINT_ANY, NULL, "%d ", tab->apps[i].priority);
+ }
+
+ if (!first) {
+ close_json_array(PRINT_JSON, "default_prio");
+ print_nl();
+ }
+}
+
+static void dcb_app_print(const struct dcb *dcb, const struct dcb_app_table *tab)
+{
+ dcb_app_print_ethtype_prio(tab);
+ dcb_app_print_default_prio(tab);
+ dcb_app_print_dscp_prio(dcb, tab);
+ dcb_app_print_stream_port_prio(tab);
+ dcb_app_print_dgram_port_prio(tab);
+ dcb_app_print_port_prio(tab);
+}
+
+static int dcb_app_get_table_attr_cb(const struct nlattr *attr, void *data)
+{
+ struct dcb_app_table *tab = data;
+ struct dcb_app *app;
+ int ret;
+
+ if (mnl_attr_get_type(attr) != DCB_ATTR_IEEE_APP) {
+ fprintf(stderr, "Unknown attribute in DCB_ATTR_IEEE_APP_TABLE: %d\n",
+ mnl_attr_get_type(attr));
+ return MNL_CB_OK;
+ }
+ if (mnl_attr_get_payload_len(attr) < sizeof(struct dcb_app)) {
+ fprintf(stderr, "DCB_ATTR_IEEE_APP payload expected to have size %zd, not %d\n",
+ sizeof(struct dcb_app), mnl_attr_get_payload_len(attr));
+ return MNL_CB_OK;
+ }
+
+ app = mnl_attr_get_payload(attr);
+ ret = dcb_app_table_push(tab, app);
+ if (ret != 0)
+ return MNL_CB_ERROR;
+
+ return MNL_CB_OK;
+}
+
+static int dcb_app_get(struct dcb *dcb, const char *dev, struct dcb_app_table *tab)
+{
+ uint16_t payload_len;
+ void *payload;
+ int ret;
+
+ ret = dcb_get_attribute_va(dcb, dev, DCB_ATTR_IEEE_APP_TABLE, &payload, &payload_len);
+ if (ret != 0)
+ return ret;
+
+ ret = mnl_attr_parse_payload(payload, payload_len, dcb_app_get_table_attr_cb, tab);
+ if (ret != MNL_CB_OK)
+ return -EINVAL;
+
+ return 0;
+}
+
+struct dcb_app_add_del {
+ const struct dcb_app_table *tab;
+ bool (*filter)(const struct dcb_app *app);
+};
+
+static int dcb_app_add_del_cb(struct dcb *dcb, struct nlmsghdr *nlh, void *data)
+{
+ struct dcb_app_add_del *add_del = data;
+ struct nlattr *nest;
+ size_t i;
+
+ nest = mnl_attr_nest_start(nlh, DCB_ATTR_IEEE_APP_TABLE);
+
+ for (i = 0; i < add_del->tab->n_apps; i++) {
+ const struct dcb_app *app = &add_del->tab->apps[i];
+
+ if (add_del->filter == NULL || add_del->filter(app))
+ mnl_attr_put(nlh, DCB_ATTR_IEEE_APP, sizeof(*app), app);
+ }
+
+ mnl_attr_nest_end(nlh, nest);
+ return 0;
+}
+
+static int dcb_app_add_del(struct dcb *dcb, const char *dev, int command,
+ const struct dcb_app_table *tab,
+ bool (*filter)(const struct dcb_app *))
+{
+ struct dcb_app_add_del add_del = {
+ .tab = tab,
+ .filter = filter,
+ };
+
+ if (tab->n_apps == 0)
+ return 0;
+
+ return dcb_set_attribute_va(dcb, command, dev, dcb_app_add_del_cb, &add_del);
+}
+
+static int dcb_cmd_app_parse_add_del(struct dcb *dcb, const char *dev,
+ int argc, char **argv, struct dcb_app_table *tab)
+{
+ struct dcb_app_parse_mapping pm = {
+ .tab = tab,
+ };
+ int ret;
+
+ if (!argc) {
+ dcb_app_help_add();
+ return 0;
+ }
+
+ do {
+ if (matches(*argv, "help") == 0) {
+ dcb_app_help_add();
+ return 0;
+ } else if (matches(*argv, "ethtype-prio") == 0) {
+ NEXT_ARG();
+ pm.selector = IEEE_8021QAZ_APP_SEL_ETHERTYPE;
+ ret = parse_mapping(&argc, &argv, false,
+ &dcb_app_parse_mapping_ethtype_prio,
+ &pm);
+ } else if (matches(*argv, "default-prio") == 0) {
+ NEXT_ARG();
+ ret = dcb_app_parse_default_prio(&argc, &argv, pm.tab);
+ if (ret != 0) {
+ fprintf(stderr, "Invalid default priority %s\n", *argv);
+ return ret;
+ }
+ } else if (matches(*argv, "dscp-prio") == 0) {
+ NEXT_ARG();
+ pm.selector = IEEE_8021QAZ_APP_SEL_DSCP;
+ ret = parse_mapping_gen(&argc, &argv,
+ &dcb_app_parse_dscp,
+ &dcb_app_parse_mapping_dscp_prio,
+ &pm);
+ } else if (matches(*argv, "stream-port-prio") == 0) {
+ NEXT_ARG();
+ pm.selector = IEEE_8021QAZ_APP_SEL_STREAM;
+ ret = parse_mapping(&argc, &argv, false,
+ &dcb_app_parse_mapping_port_prio,
+ &pm);
+ } else if (matches(*argv, "dgram-port-prio") == 0) {
+ NEXT_ARG();
+ pm.selector = IEEE_8021QAZ_APP_SEL_DGRAM;
+ ret = parse_mapping(&argc, &argv, false,
+ &dcb_app_parse_mapping_port_prio,
+ &pm);
+ } else if (matches(*argv, "port-prio") == 0) {
+ NEXT_ARG();
+ pm.selector = IEEE_8021QAZ_APP_SEL_ANY;
+ ret = parse_mapping(&argc, &argv, false,
+ &dcb_app_parse_mapping_port_prio,
+ &pm);
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ dcb_app_help_add();
+ return -EINVAL;
+ }
+
+ if (ret != 0) {
+ fprintf(stderr, "Invalid mapping %s\n", *argv);
+ return ret;
+ }
+ if (pm.err)
+ return pm.err;
+ } while (argc > 0);
+
+ return 0;
+}
+
+static int dcb_cmd_app_add(struct dcb *dcb, const char *dev, int argc, char **argv)
+{
+ struct dcb_app_table tab = {};
+ int ret;
+
+ ret = dcb_cmd_app_parse_add_del(dcb, dev, argc, argv, &tab);
+ if (ret != 0)
+ return ret;
+
+ ret = dcb_app_add_del(dcb, dev, DCB_CMD_IEEE_SET, &tab, NULL);
+ dcb_app_table_fini(&tab);
+ return ret;
+}
+
+static int dcb_cmd_app_del(struct dcb *dcb, const char *dev, int argc, char **argv)
+{
+ struct dcb_app_table tab = {};
+ int ret;
+
+ ret = dcb_cmd_app_parse_add_del(dcb, dev, argc, argv, &tab);
+ if (ret != 0)
+ return ret;
+
+ ret = dcb_app_add_del(dcb, dev, DCB_CMD_IEEE_DEL, &tab, NULL);
+ dcb_app_table_fini(&tab);
+ return ret;
+}
+
+static int dcb_cmd_app_show(struct dcb *dcb, const char *dev, int argc, char **argv)
+{
+ struct dcb_app_table tab = {};
+ int ret;
+
+ ret = dcb_app_get(dcb, dev, &tab);
+ if (ret != 0)
+ return ret;
+
+ dcb_app_table_sort(&tab);
+
+ open_json_object(NULL);
+
+ if (!argc) {
+ dcb_app_print(dcb, &tab);
+ goto out;
+ }
+
+ do {
+ if (matches(*argv, "help") == 0) {
+ dcb_app_help_show_flush();
+ goto out;
+ } else if (matches(*argv, "ethtype-prio") == 0) {
+ dcb_app_print_ethtype_prio(&tab);
+ } else if (matches(*argv, "dscp-prio") == 0) {
+ dcb_app_print_dscp_prio(dcb, &tab);
+ } else if (matches(*argv, "stream-port-prio") == 0) {
+ dcb_app_print_stream_port_prio(&tab);
+ } else if (matches(*argv, "dgram-port-prio") == 0) {
+ dcb_app_print_dgram_port_prio(&tab);
+ } else if (matches(*argv, "port-prio") == 0) {
+ dcb_app_print_port_prio(&tab);
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ dcb_app_help_show_flush();
+ ret = -EINVAL;
+ goto out;
+ }
+
+ NEXT_ARG_FWD();
+ } while (argc > 0);
+
+out:
+ close_json_object();
+ dcb_app_table_fini(&tab);
+ return 0;
+}
+
+static int dcb_cmd_app_flush(struct dcb *dcb, const char *dev, int argc, char **argv)
+{
+ struct dcb_app_table tab = {};
+ int ret;
+
+ ret = dcb_app_get(dcb, dev, &tab);
+ if (ret != 0)
+ return ret;
+
+ if (!argc) {
+ ret = dcb_app_add_del(dcb, dev, DCB_CMD_IEEE_DEL, &tab, NULL);
+ goto out;
+ }
+
+ do {
+ if (matches(*argv, "help") == 0) {
+ dcb_app_help_show_flush();
+ goto out;
+ } else if (matches(*argv, "ethtype-prio") == 0) {
+ ret = dcb_app_add_del(dcb, dev, DCB_CMD_IEEE_DEL, &tab,
+ &dcb_app_is_ethtype);
+ if (ret != 0)
+ goto out;
+ } else if (matches(*argv, "default-prio") == 0) {
+ ret = dcb_app_add_del(dcb, dev, DCB_CMD_IEEE_DEL, &tab,
+ &dcb_app_is_default);
+ if (ret != 0)
+ goto out;
+ } else if (matches(*argv, "dscp-prio") == 0) {
+ ret = dcb_app_add_del(dcb, dev, DCB_CMD_IEEE_DEL, &tab,
+ &dcb_app_is_dscp);
+ if (ret != 0)
+ goto out;
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ dcb_app_help_show_flush();
+ ret = -EINVAL;
+ goto out;
+ }
+
+ NEXT_ARG_FWD();
+ } while (argc > 0);
+
+out:
+ dcb_app_table_fini(&tab);
+ return ret;
+}
+
+static int dcb_cmd_app_replace(struct dcb *dcb, const char *dev, int argc, char **argv)
+{
+ struct dcb_app_table orig = {};
+ struct dcb_app_table tab = {};
+ struct dcb_app_table new = {};
+ int ret;
+
+ ret = dcb_app_get(dcb, dev, &orig);
+ if (ret != 0)
+ return ret;
+
+ ret = dcb_cmd_app_parse_add_del(dcb, dev, argc, argv, &tab);
+ if (ret != 0)
+ goto out;
+
+ /* Attempts to add an existing entry would be rejected, so drop
+ * these entries from tab.
+ */
+ ret = dcb_app_table_copy(&new, &tab);
+ if (ret != 0)
+ goto out;
+ dcb_app_table_remove_existing(&new, &orig);
+
+ ret = dcb_app_add_del(dcb, dev, DCB_CMD_IEEE_SET, &new, NULL);
+ if (ret != 0) {
+ fprintf(stderr, "Could not add new APP entries\n");
+ goto out;
+ }
+
+ /* Remove the obsolete entries. */
+ dcb_app_table_remove_replaced(&orig, &tab);
+ ret = dcb_app_add_del(dcb, dev, DCB_CMD_IEEE_DEL, &orig, NULL);
+ if (ret != 0) {
+ fprintf(stderr, "Could not remove replaced APP entries\n");
+ goto out;
+ }
+
+out:
+ dcb_app_table_fini(&new);
+ dcb_app_table_fini(&tab);
+ dcb_app_table_fini(&orig);
+ return 0;
+}
+
+int dcb_cmd_app(struct dcb *dcb, int argc, char **argv)
+{
+ if (!argc || matches(*argv, "help") == 0) {
+ dcb_app_help();
+ return 0;
+ } else if (matches(*argv, "show") == 0) {
+ NEXT_ARG_FWD();
+ return dcb_cmd_parse_dev(dcb, argc, argv,
+ dcb_cmd_app_show, dcb_app_help_show_flush);
+ } else if (matches(*argv, "flush") == 0) {
+ NEXT_ARG_FWD();
+ return dcb_cmd_parse_dev(dcb, argc, argv,
+ dcb_cmd_app_flush, dcb_app_help_show_flush);
+ } else if (matches(*argv, "add") == 0) {
+ NEXT_ARG_FWD();
+ return dcb_cmd_parse_dev(dcb, argc, argv,
+ dcb_cmd_app_add, dcb_app_help_add);
+ } else if (matches(*argv, "del") == 0) {
+ NEXT_ARG_FWD();
+ return dcb_cmd_parse_dev(dcb, argc, argv,
+ dcb_cmd_app_del, dcb_app_help_add);
+ } else if (matches(*argv, "replace") == 0) {
+ NEXT_ARG_FWD();
+ return dcb_cmd_parse_dev(dcb, argc, argv,
+ dcb_cmd_app_replace, dcb_app_help_add);
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ dcb_app_help();
+ return -EINVAL;
+ }
+}
diff --git a/dcb/dcb_buffer.c b/dcb/dcb_buffer.c
new file mode 100644
index 0000000..e6a88a0
--- /dev/null
+++ b/dcb/dcb_buffer.c
@@ -0,0 +1,235 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+#include <errno.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <linux/dcbnl.h>
+
+#include "dcb.h"
+#include "utils.h"
+
+static void dcb_buffer_help_set(void)
+{
+ fprintf(stderr,
+ "Usage: dcb buffer set dev STRING\n"
+ " [ prio-buffer PRIO-MAP ]\n"
+ " [ buffer-size SIZE-MAP ]\n"
+ "\n"
+ " where PRIO-MAP := [ PRIO-MAP ] PRIO-MAPPING\n"
+ " PRIO-MAPPING := { all | PRIO }:BUFFER\n"
+ " SIZE-MAP := [ SIZE-MAP ] SIZE-MAPPING\n"
+ " SIZE-MAPPING := { all | BUFFER }:INTEGER\n"
+ " PRIO := { 0 .. 7 }\n"
+ " BUFFER := { 0 .. 7 }\n"
+ "\n"
+ );
+}
+
+static void dcb_buffer_help_show(void)
+{
+ fprintf(stderr,
+ "Usage: dcb buffer show dev STRING\n"
+ " [ prio-buffer ] [ buffer-size ] [ total-size ]\n"
+ "\n"
+ );
+}
+
+static void dcb_buffer_help(void)
+{
+ fprintf(stderr,
+ "Usage: dcb buffer help\n"
+ "\n"
+ );
+ dcb_buffer_help_show();
+ dcb_buffer_help_set();
+}
+
+static int dcb_buffer_parse_mapping_prio_buffer(__u32 key, char *value, void *data)
+{
+ struct dcbnl_buffer *buffer = data;
+ __u8 buf;
+
+ if (get_u8(&buf, value, 0))
+ return -EINVAL;
+
+ return dcb_parse_mapping("PRIO", key, IEEE_8021Q_MAX_PRIORITIES - 1,
+ "BUFFER", buf, DCBX_MAX_BUFFERS - 1,
+ dcb_set_u8, buffer->prio2buffer);
+}
+
+static int dcb_buffer_parse_mapping_buffer_size(__u32 key, char *value, void *data)
+{
+ struct dcbnl_buffer *buffer = data;
+ unsigned int size;
+
+ if (get_size(&size, value)) {
+ fprintf(stderr, "%d:%s: Illegal value for buffer size\n", key, value);
+ return -EINVAL;
+ }
+
+ return dcb_parse_mapping("BUFFER", key, DCBX_MAX_BUFFERS - 1,
+ "INTEGER", size, -1,
+ dcb_set_u32, buffer->buffer_size);
+}
+
+static void dcb_buffer_print_total_size(const struct dcbnl_buffer *buffer)
+{
+ print_size(PRINT_ANY, "total_size", "total-size %s ", buffer->total_size);
+}
+
+static void dcb_buffer_print_prio_buffer(const struct dcbnl_buffer *buffer)
+{
+ dcb_print_named_array("prio_buffer", "prio-buffer",
+ buffer->prio2buffer, ARRAY_SIZE(buffer->prio2buffer),
+ dcb_print_array_u8);
+}
+
+static void dcb_buffer_print_buffer_size(const struct dcbnl_buffer *buffer)
+{
+ size_t size = ARRAY_SIZE(buffer->buffer_size);
+ SPRINT_BUF(b);
+ size_t i;
+
+ open_json_array(PRINT_JSON, "buffer_size");
+ print_string(PRINT_FP, NULL, "buffer-size ", NULL);
+
+ for (i = 0; i < size; i++) {
+ snprintf(b, sizeof(b), "%zd:%%s ", i);
+ print_size(PRINT_ANY, NULL, b, buffer->buffer_size[i]);
+ }
+
+ close_json_array(PRINT_JSON, "buffer_size");
+}
+
+static void dcb_buffer_print(const struct dcbnl_buffer *buffer)
+{
+ dcb_buffer_print_prio_buffer(buffer);
+ print_nl();
+
+ dcb_buffer_print_buffer_size(buffer);
+ print_nl();
+
+ dcb_buffer_print_total_size(buffer);
+ print_nl();
+}
+
+static int dcb_buffer_get(struct dcb *dcb, const char *dev, struct dcbnl_buffer *buffer)
+{
+ return dcb_get_attribute(dcb, dev, DCB_ATTR_DCB_BUFFER, buffer, sizeof(*buffer));
+}
+
+static int dcb_buffer_set(struct dcb *dcb, const char *dev, const struct dcbnl_buffer *buffer)
+{
+ return dcb_set_attribute(dcb, dev, DCB_ATTR_DCB_BUFFER, buffer, sizeof(*buffer));
+}
+
+static int dcb_cmd_buffer_set(struct dcb *dcb, const char *dev, int argc, char **argv)
+{
+ struct dcbnl_buffer buffer;
+ int ret;
+
+ if (!argc) {
+ dcb_buffer_help_set();
+ return 0;
+ }
+
+ ret = dcb_buffer_get(dcb, dev, &buffer);
+ if (ret)
+ return ret;
+
+ do {
+ if (matches(*argv, "help") == 0) {
+ dcb_buffer_help_set();
+ return 0;
+ } else if (matches(*argv, "prio-buffer") == 0) {
+ NEXT_ARG();
+ ret = parse_mapping(&argc, &argv, true,
+ &dcb_buffer_parse_mapping_prio_buffer, &buffer);
+ if (ret) {
+ fprintf(stderr, "Invalid priority mapping %s\n", *argv);
+ return ret;
+ }
+ continue;
+ } else if (matches(*argv, "buffer-size") == 0) {
+ NEXT_ARG();
+ ret = parse_mapping(&argc, &argv, true,
+ &dcb_buffer_parse_mapping_buffer_size, &buffer);
+ if (ret) {
+ fprintf(stderr, "Invalid buffer size mapping %s\n", *argv);
+ return ret;
+ }
+ continue;
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ dcb_buffer_help_set();
+ return -EINVAL;
+ }
+
+ NEXT_ARG_FWD();
+ } while (argc > 0);
+
+ return dcb_buffer_set(dcb, dev, &buffer);
+}
+
+static int dcb_cmd_buffer_show(struct dcb *dcb, const char *dev, int argc, char **argv)
+{
+ struct dcbnl_buffer buffer;
+ int ret;
+
+ ret = dcb_buffer_get(dcb, dev, &buffer);
+ if (ret)
+ return ret;
+
+ open_json_object(NULL);
+
+ if (!argc) {
+ dcb_buffer_print(&buffer);
+ goto out;
+ }
+
+ do {
+ if (matches(*argv, "help") == 0) {
+ dcb_buffer_help_show();
+ return 0;
+ } else if (matches(*argv, "prio-buffer") == 0) {
+ dcb_buffer_print_prio_buffer(&buffer);
+ print_nl();
+ } else if (matches(*argv, "buffer-size") == 0) {
+ dcb_buffer_print_buffer_size(&buffer);
+ print_nl();
+ } else if (matches(*argv, "total-size") == 0) {
+ dcb_buffer_print_total_size(&buffer);
+ print_nl();
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ dcb_buffer_help_show();
+ return -EINVAL;
+ }
+
+ NEXT_ARG_FWD();
+ } while (argc > 0);
+
+out:
+ close_json_object();
+ return 0;
+}
+
+int dcb_cmd_buffer(struct dcb *dcb, int argc, char **argv)
+{
+ if (!argc || matches(*argv, "help") == 0) {
+ dcb_buffer_help();
+ return 0;
+ } else if (matches(*argv, "show") == 0) {
+ NEXT_ARG_FWD();
+ return dcb_cmd_parse_dev(dcb, argc, argv,
+ dcb_cmd_buffer_show, dcb_buffer_help_show);
+ } else if (matches(*argv, "set") == 0) {
+ NEXT_ARG_FWD();
+ return dcb_cmd_parse_dev(dcb, argc, argv,
+ dcb_cmd_buffer_set, dcb_buffer_help_set);
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ dcb_buffer_help();
+ return -EINVAL;
+ }
+}
diff --git a/dcb/dcb_dcbx.c b/dcb/dcb_dcbx.c
new file mode 100644
index 0000000..244b671
--- /dev/null
+++ b/dcb/dcb_dcbx.c
@@ -0,0 +1,192 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+#include <errno.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <linux/dcbnl.h>
+
+#include "dcb.h"
+#include "utils.h"
+
+static void dcb_dcbx_help_set(void)
+{
+ fprintf(stderr,
+ "Usage: dcb dcbx set dev STRING\n"
+ " [ host | lld-managed ]\n"
+ " [ cee | ieee ] [ static ]\n"
+ "\n"
+ );
+}
+
+static void dcb_dcbx_help_show(void)
+{
+ fprintf(stderr,
+ "Usage: dcb dcbx show dev STRING\n"
+ "\n"
+ );
+}
+
+static void dcb_dcbx_help(void)
+{
+ fprintf(stderr,
+ "Usage: dcb dcbx help\n"
+ "\n"
+ );
+ dcb_dcbx_help_show();
+ dcb_dcbx_help_set();
+}
+
+struct dcb_dcbx_flag {
+ __u8 value;
+ const char *key_fp;
+ const char *key_json;
+};
+
+static struct dcb_dcbx_flag dcb_dcbx_flags[] = {
+ {DCB_CAP_DCBX_HOST, "host"},
+ {DCB_CAP_DCBX_LLD_MANAGED, "lld-managed", "lld_managed"},
+ {DCB_CAP_DCBX_VER_CEE, "cee"},
+ {DCB_CAP_DCBX_VER_IEEE, "ieee"},
+ {DCB_CAP_DCBX_STATIC, "static"},
+};
+
+static void dcb_dcbx_print(__u8 dcbx)
+{
+ int bit;
+ int i;
+
+ while ((bit = ffs(dcbx))) {
+ bool found = false;
+
+ bit--;
+ for (i = 0; i < ARRAY_SIZE(dcb_dcbx_flags); i++) {
+ struct dcb_dcbx_flag *flag = &dcb_dcbx_flags[i];
+
+ if (flag->value == 1 << bit) {
+ print_bool(PRINT_JSON, flag->key_json ?: flag->key_fp,
+ NULL, true);
+ print_string(PRINT_FP, NULL, "%s ", flag->key_fp);
+ found = true;
+ break;
+ }
+ }
+
+ if (!found)
+ fprintf(stderr, "Unknown DCBX bit %#x.\n", 1 << bit);
+
+ dcbx &= ~(1 << bit);
+ }
+
+ print_nl();
+}
+
+static int dcb_dcbx_get(struct dcb *dcb, const char *dev, __u8 *dcbx)
+{
+ __u16 payload_len;
+ void *payload;
+ int err;
+
+ err = dcb_get_attribute_bare(dcb, DCB_CMD_IEEE_GET, dev, DCB_ATTR_DCBX,
+ &payload, &payload_len);
+ if (err != 0)
+ return err;
+
+ if (payload_len != 1) {
+ fprintf(stderr, "DCB_ATTR_DCBX payload has size %d, expected 1.\n",
+ payload_len);
+ return -EINVAL;
+ }
+ *dcbx = *(__u8 *) payload;
+ return 0;
+}
+
+static int dcb_dcbx_set(struct dcb *dcb, const char *dev, __u8 dcbx)
+{
+ return dcb_set_attribute_bare(dcb, DCB_CMD_SDCBX, dev, DCB_ATTR_DCBX,
+ &dcbx, 1, DCB_ATTR_DCBX);
+}
+
+static int dcb_cmd_dcbx_set(struct dcb *dcb, const char *dev, int argc, char **argv)
+{
+ __u8 dcbx = 0;
+ __u8 i;
+
+ if (!argc) {
+ dcb_dcbx_help_set();
+ return 0;
+ }
+
+ do {
+ if (matches(*argv, "help") == 0) {
+ dcb_dcbx_help_set();
+ return 0;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(dcb_dcbx_flags); i++) {
+ struct dcb_dcbx_flag *flag = &dcb_dcbx_flags[i];
+
+ if (matches(*argv, flag->key_fp) == 0) {
+ dcbx |= flag->value;
+ NEXT_ARG_FWD();
+ goto next;
+ }
+ }
+
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ dcb_dcbx_help_set();
+ return -EINVAL;
+
+next:
+ ;
+ } while (argc > 0);
+
+ return dcb_dcbx_set(dcb, dev, dcbx);
+}
+
+static int dcb_cmd_dcbx_show(struct dcb *dcb, const char *dev, int argc, char **argv)
+{
+ __u8 dcbx;
+ int ret;
+
+ ret = dcb_dcbx_get(dcb, dev, &dcbx);
+ if (ret != 0)
+ return ret;
+
+ while (argc > 0) {
+ if (matches(*argv, "help") == 0) {
+ dcb_dcbx_help_show();
+ return 0;
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ dcb_dcbx_help_show();
+ return -EINVAL;
+ }
+
+ NEXT_ARG_FWD();
+ }
+
+ open_json_object(NULL);
+ dcb_dcbx_print(dcbx);
+ close_json_object();
+ return 0;
+}
+
+int dcb_cmd_dcbx(struct dcb *dcb, int argc, char **argv)
+{
+ if (!argc || matches(*argv, "help") == 0) {
+ dcb_dcbx_help();
+ return 0;
+ } else if (matches(*argv, "show") == 0) {
+ NEXT_ARG_FWD();
+ return dcb_cmd_parse_dev(dcb, argc, argv,
+ dcb_cmd_dcbx_show, dcb_dcbx_help_show);
+ } else if (matches(*argv, "set") == 0) {
+ NEXT_ARG_FWD();
+ return dcb_cmd_parse_dev(dcb, argc, argv,
+ dcb_cmd_dcbx_set, dcb_dcbx_help_set);
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ dcb_dcbx_help();
+ return -EINVAL;
+ }
+}
diff --git a/dcb/dcb_ets.c b/dcb/dcb_ets.c
new file mode 100644
index 0000000..c208810
--- /dev/null
+++ b/dcb/dcb_ets.c
@@ -0,0 +1,435 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+#include <errno.h>
+#include <stdio.h>
+#include <linux/dcbnl.h>
+
+#include "dcb.h"
+#include "utils.h"
+
+static void dcb_ets_help_set(void)
+{
+ fprintf(stderr,
+ "Usage: dcb ets set dev STRING\n"
+ " [ willing { on | off } ]\n"
+ " [ { tc-tsa | reco-tc-tsa } TSA-MAP ]\n"
+ " [ { pg-bw | tc-bw | reco-tc-bw } BW-MAP ]\n"
+ " [ { prio-tc | reco-prio-tc } PRIO-MAP ]\n"
+ "\n"
+ " where TSA-MAP := [ TSA-MAP ] TSA-MAPPING\n"
+ " TSA-MAPPING := { all | TC }:{ strict | cbs | ets | vendor }\n"
+ " BW-MAP := [ BW-MAP ] BW-MAPPING\n"
+ " BW-MAPPING := { all | TC }:INTEGER\n"
+ " PRIO-MAP := [ PRIO-MAP ] PRIO-MAPPING\n"
+ " PRIO-MAPPING := { all | PRIO }:TC\n"
+ " TC := { 0 .. 7 }\n"
+ " PRIO := { 0 .. 7 }\n"
+ "\n"
+ );
+}
+
+static void dcb_ets_help_show(void)
+{
+ fprintf(stderr,
+ "Usage: dcb ets show dev STRING\n"
+ " [ willing ] [ ets-cap ] [ cbs ] [ tc-tsa ]\n"
+ " [ reco-tc-tsa ] [ pg-bw ] [ tc-bw ] [ reco-tc-bw ]\n"
+ " [ prio-tc ] [ reco-prio-tc ]\n"
+ "\n"
+ );
+}
+
+static void dcb_ets_help(void)
+{
+ fprintf(stderr,
+ "Usage: dcb ets help\n"
+ "\n"
+ );
+ dcb_ets_help_show();
+ dcb_ets_help_set();
+}
+
+static const char *const tsa_names[] = {
+ [IEEE_8021QAZ_TSA_STRICT] = "strict",
+ [IEEE_8021QAZ_TSA_CB_SHAPER] = "cbs",
+ [IEEE_8021QAZ_TSA_ETS] = "ets",
+ [IEEE_8021QAZ_TSA_VENDOR] = "vendor",
+};
+
+static int dcb_ets_parse_mapping_tc_tsa(__u32 key, char *value, void *data)
+{
+ __u8 tsa;
+ int ret;
+
+ tsa = parse_one_of("TSA", value, tsa_names, ARRAY_SIZE(tsa_names), &ret);
+ if (ret)
+ return ret;
+
+ return dcb_parse_mapping("TC", key, IEEE_8021QAZ_MAX_TCS - 1,
+ "TSA", tsa, -1U,
+ dcb_set_u8, data);
+}
+
+static int dcb_ets_parse_mapping_tc_bw(__u32 key, char *value, void *data)
+{
+ __u8 bw;
+
+ if (get_u8(&bw, value, 0))
+ return -EINVAL;
+
+ return dcb_parse_mapping("TC", key, IEEE_8021QAZ_MAX_TCS - 1,
+ "BW", bw, 100,
+ dcb_set_u8, data);
+}
+
+static int dcb_ets_parse_mapping_prio_tc(unsigned int key, char *value, void *data)
+{
+ __u8 tc;
+
+ if (get_u8(&tc, value, 0))
+ return -EINVAL;
+
+ return dcb_parse_mapping("PRIO", key, IEEE_8021QAZ_MAX_TCS - 1,
+ "TC", tc, IEEE_8021QAZ_MAX_TCS - 1,
+ dcb_set_u8, data);
+}
+
+static void dcb_print_array_tsa(const __u8 *array, size_t size)
+{
+ dcb_print_array_kw(array, size, tsa_names, ARRAY_SIZE(tsa_names));
+}
+
+static void dcb_ets_print_willing(const struct ieee_ets *ets)
+{
+ print_on_off(PRINT_ANY, "willing", "willing %s ", ets->willing);
+}
+
+static void dcb_ets_print_ets_cap(const struct ieee_ets *ets)
+{
+ print_uint(PRINT_ANY, "ets_cap", "ets-cap %d ", ets->ets_cap);
+}
+
+static void dcb_ets_print_cbs(const struct ieee_ets *ets)
+{
+ print_on_off(PRINT_ANY, "cbs", "cbs %s ", ets->cbs);
+}
+
+static void dcb_ets_print_tc_bw(const struct ieee_ets *ets)
+{
+ dcb_print_named_array("tc_bw", "tc-bw",
+ ets->tc_tx_bw, ARRAY_SIZE(ets->tc_tx_bw),
+ dcb_print_array_u8);
+}
+
+static void dcb_ets_print_pg_bw(const struct ieee_ets *ets)
+{
+ dcb_print_named_array("pg_bw", "pg-bw",
+ ets->tc_rx_bw, ARRAY_SIZE(ets->tc_rx_bw),
+ dcb_print_array_u8);
+}
+
+static void dcb_ets_print_tc_tsa(const struct ieee_ets *ets)
+{
+ dcb_print_named_array("tc_tsa", "tc-tsa",
+ ets->tc_tsa, ARRAY_SIZE(ets->tc_tsa),
+ dcb_print_array_tsa);
+}
+
+static void dcb_ets_print_prio_tc(const struct ieee_ets *ets)
+{
+ dcb_print_named_array("prio_tc", "prio-tc",
+ ets->prio_tc, ARRAY_SIZE(ets->prio_tc),
+ dcb_print_array_u8);
+}
+
+static void dcb_ets_print_reco_tc_bw(const struct ieee_ets *ets)
+{
+ dcb_print_named_array("reco_tc_bw", "reco-tc-bw",
+ ets->tc_reco_bw, ARRAY_SIZE(ets->tc_reco_bw),
+ dcb_print_array_u8);
+}
+
+static void dcb_ets_print_reco_tc_tsa(const struct ieee_ets *ets)
+{
+ dcb_print_named_array("reco_tc_tsa", "reco-tc-tsa",
+ ets->tc_reco_tsa, ARRAY_SIZE(ets->tc_reco_tsa),
+ dcb_print_array_tsa);
+}
+
+static void dcb_ets_print_reco_prio_tc(const struct ieee_ets *ets)
+{
+ dcb_print_named_array("reco_prio_tc", "reco-prio-tc",
+ ets->reco_prio_tc, ARRAY_SIZE(ets->reco_prio_tc),
+ dcb_print_array_u8);
+}
+
+static void dcb_ets_print(const struct ieee_ets *ets)
+{
+ dcb_ets_print_willing(ets);
+ dcb_ets_print_ets_cap(ets);
+ dcb_ets_print_cbs(ets);
+ print_nl();
+
+ dcb_ets_print_tc_bw(ets);
+ print_nl();
+
+ dcb_ets_print_pg_bw(ets);
+ print_nl();
+
+ dcb_ets_print_tc_tsa(ets);
+ print_nl();
+
+ dcb_ets_print_prio_tc(ets);
+ print_nl();
+
+ dcb_ets_print_reco_tc_bw(ets);
+ print_nl();
+
+ dcb_ets_print_reco_tc_tsa(ets);
+ print_nl();
+
+ dcb_ets_print_reco_prio_tc(ets);
+ print_nl();
+}
+
+static int dcb_ets_get(struct dcb *dcb, const char *dev, struct ieee_ets *ets)
+{
+ return dcb_get_attribute(dcb, dev, DCB_ATTR_IEEE_ETS, ets, sizeof(*ets));
+}
+
+static int dcb_ets_validate_bw(const __u8 bw[], const __u8 tsa[], const char *what)
+{
+ bool has_ets = false;
+ unsigned int total = 0;
+ unsigned int tc;
+
+ for (tc = 0; tc < IEEE_8021QAZ_MAX_TCS; tc++) {
+ if (tsa[tc] == IEEE_8021QAZ_TSA_ETS) {
+ has_ets = true;
+ break;
+ }
+ }
+
+ /* TC bandwidth is only intended for ETS, but 802.1Q-2018 only requires
+ * that the sum be 100, and individual entries 0..100. It explicitly
+ * notes that non-ETS TCs can have non-0 TC bandwidth during
+ * reconfiguration.
+ */
+ for (tc = 0; tc < IEEE_8021QAZ_MAX_TCS; tc++) {
+ if (bw[tc] > 100) {
+ fprintf(stderr, "%d%% for TC %d of %s is not a valid bandwidth percentage, expected 0..100%%\n",
+ bw[tc], tc, what);
+ return -EINVAL;
+ }
+ total += bw[tc];
+ }
+
+ /* This is what 802.1Q-2018 requires. */
+ if (total == 100)
+ return 0;
+
+ /* But this requirement does not make sense for all-strict
+ * configurations. Anything else than 0 does not make sense: either BW
+ * has not been reconfigured for the all-strict allocation yet, at which
+ * point we expect sum of 100. Or it has already been reconfigured, at
+ * which point accept 0.
+ */
+ if (!has_ets && total == 0)
+ return 0;
+
+ fprintf(stderr, "Bandwidth percentages in %s sum to %d%%, expected %d%%\n",
+ what, total, has_ets ? 100 : 0);
+ return -EINVAL;
+}
+
+static int dcb_ets_set(struct dcb *dcb, const char *dev, const struct ieee_ets *ets)
+{
+ /* Do not validate pg-bw, which is not standard and has unclear
+ * meaning.
+ */
+ if (dcb_ets_validate_bw(ets->tc_tx_bw, ets->tc_tsa, "tc-bw") ||
+ dcb_ets_validate_bw(ets->tc_reco_bw, ets->tc_reco_tsa, "reco-tc-bw"))
+ return -EINVAL;
+
+ return dcb_set_attribute(dcb, dev, DCB_ATTR_IEEE_ETS, ets, sizeof(*ets));
+}
+
+static int dcb_cmd_ets_set(struct dcb *dcb, const char *dev, int argc, char **argv)
+{
+ struct ieee_ets ets;
+ int ret;
+
+ if (!argc) {
+ dcb_ets_help_set();
+ return 1;
+ }
+
+ ret = dcb_ets_get(dcb, dev, &ets);
+ if (ret)
+ return ret;
+
+ do {
+ if (matches(*argv, "help") == 0) {
+ dcb_ets_help_set();
+ return 0;
+ } else if (matches(*argv, "willing") == 0) {
+ NEXT_ARG();
+ ets.willing = parse_on_off("willing", *argv, &ret);
+ if (ret)
+ return ret;
+ } else if (matches(*argv, "tc-tsa") == 0) {
+ NEXT_ARG();
+ ret = parse_mapping(&argc, &argv, true, &dcb_ets_parse_mapping_tc_tsa,
+ ets.tc_tsa);
+ if (ret) {
+ fprintf(stderr, "Invalid tc-tsa mapping %s\n", *argv);
+ return ret;
+ }
+ continue;
+ } else if (matches(*argv, "reco-tc-tsa") == 0) {
+ NEXT_ARG();
+ ret = parse_mapping(&argc, &argv, true, &dcb_ets_parse_mapping_tc_tsa,
+ ets.tc_reco_tsa);
+ if (ret) {
+ fprintf(stderr, "Invalid reco-tc-tsa mapping %s\n", *argv);
+ return ret;
+ }
+ continue;
+ } else if (matches(*argv, "tc-bw") == 0) {
+ NEXT_ARG();
+ ret = parse_mapping(&argc, &argv, true, &dcb_ets_parse_mapping_tc_bw,
+ ets.tc_tx_bw);
+ if (ret) {
+ fprintf(stderr, "Invalid tc-bw mapping %s\n", *argv);
+ return ret;
+ }
+ continue;
+ } else if (matches(*argv, "pg-bw") == 0) {
+ NEXT_ARG();
+ ret = parse_mapping(&argc, &argv, true, &dcb_ets_parse_mapping_tc_bw,
+ ets.tc_rx_bw);
+ if (ret) {
+ fprintf(stderr, "Invalid pg-bw mapping %s\n", *argv);
+ return ret;
+ }
+ continue;
+ } else if (matches(*argv, "reco-tc-bw") == 0) {
+ NEXT_ARG();
+ ret = parse_mapping(&argc, &argv, true, &dcb_ets_parse_mapping_tc_bw,
+ ets.tc_reco_bw);
+ if (ret) {
+ fprintf(stderr, "Invalid reco-tc-bw mapping %s\n", *argv);
+ return ret;
+ }
+ continue;
+ } else if (matches(*argv, "prio-tc") == 0) {
+ NEXT_ARG();
+ ret = parse_mapping(&argc, &argv, true, &dcb_ets_parse_mapping_prio_tc,
+ ets.prio_tc);
+ if (ret) {
+ fprintf(stderr, "Invalid prio-tc mapping %s\n", *argv);
+ return ret;
+ }
+ continue;
+ } else if (matches(*argv, "reco-prio-tc") == 0) {
+ NEXT_ARG();
+ ret = parse_mapping(&argc, &argv, true, &dcb_ets_parse_mapping_prio_tc,
+ ets.reco_prio_tc);
+ if (ret) {
+ fprintf(stderr, "Invalid reco-prio-tc mapping %s\n", *argv);
+ return ret;
+ }
+ continue;
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ dcb_ets_help_set();
+ return -EINVAL;
+ }
+
+ NEXT_ARG_FWD();
+ } while (argc > 0);
+
+ return dcb_ets_set(dcb, dev, &ets);
+}
+
+static int dcb_cmd_ets_show(struct dcb *dcb, const char *dev, int argc, char **argv)
+{
+ struct ieee_ets ets;
+ int ret;
+
+ ret = dcb_ets_get(dcb, dev, &ets);
+ if (ret)
+ return ret;
+
+ open_json_object(NULL);
+
+ if (!argc) {
+ dcb_ets_print(&ets);
+ goto out;
+ }
+
+ do {
+ if (matches(*argv, "help") == 0) {
+ dcb_ets_help_show();
+ return 0;
+ } else if (matches(*argv, "willing") == 0) {
+ dcb_ets_print_willing(&ets);
+ print_nl();
+ } else if (matches(*argv, "ets-cap") == 0) {
+ dcb_ets_print_ets_cap(&ets);
+ print_nl();
+ } else if (matches(*argv, "cbs") == 0) {
+ dcb_ets_print_cbs(&ets);
+ print_nl();
+ } else if (matches(*argv, "tc-tsa") == 0) {
+ dcb_ets_print_tc_tsa(&ets);
+ print_nl();
+ } else if (matches(*argv, "reco-tc-tsa") == 0) {
+ dcb_ets_print_reco_tc_tsa(&ets);
+ print_nl();
+ } else if (matches(*argv, "tc-bw") == 0) {
+ dcb_ets_print_tc_bw(&ets);
+ print_nl();
+ } else if (matches(*argv, "pg-bw") == 0) {
+ dcb_ets_print_pg_bw(&ets);
+ print_nl();
+ } else if (matches(*argv, "reco-tc-bw") == 0) {
+ dcb_ets_print_reco_tc_bw(&ets);
+ print_nl();
+ } else if (matches(*argv, "prio-tc") == 0) {
+ dcb_ets_print_prio_tc(&ets);
+ print_nl();
+ } else if (matches(*argv, "reco-prio-tc") == 0) {
+ dcb_ets_print_reco_prio_tc(&ets);
+ print_nl();
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ dcb_ets_help_show();
+ return -EINVAL;
+ }
+
+ NEXT_ARG_FWD();
+ } while (argc > 0);
+
+out:
+ close_json_object();
+ return 0;
+}
+
+int dcb_cmd_ets(struct dcb *dcb, int argc, char **argv)
+{
+ if (!argc || matches(*argv, "help") == 0) {
+ dcb_ets_help();
+ return 0;
+ } else if (matches(*argv, "show") == 0) {
+ NEXT_ARG_FWD();
+ return dcb_cmd_parse_dev(dcb, argc, argv, dcb_cmd_ets_show, dcb_ets_help_show);
+ } else if (matches(*argv, "set") == 0) {
+ NEXT_ARG_FWD();
+ return dcb_cmd_parse_dev(dcb, argc, argv, dcb_cmd_ets_set, dcb_ets_help_set);
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ dcb_ets_help();
+ return -EINVAL;
+ }
+}
diff --git a/dcb/dcb_maxrate.c b/dcb/dcb_maxrate.c
new file mode 100644
index 0000000..1538c6d
--- /dev/null
+++ b/dcb/dcb_maxrate.c
@@ -0,0 +1,182 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+#include <errno.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <linux/dcbnl.h>
+
+#include "dcb.h"
+#include "utils.h"
+
+static void dcb_maxrate_help_set(void)
+{
+ fprintf(stderr,
+ "Usage: dcb maxrate set dev STRING\n"
+ " [ tc-maxrate RATE-MAP ]\n"
+ "\n"
+ " where RATE-MAP := [ RATE-MAP ] RATE-MAPPING\n"
+ " RATE-MAPPING := { all | TC }:RATE\n"
+ " TC := { 0 .. 7 }\n"
+ "\n"
+ );
+}
+
+static void dcb_maxrate_help_show(void)
+{
+ fprintf(stderr,
+ "Usage: dcb [ -i ] maxrate show dev STRING\n"
+ " [ tc-maxrate ]\n"
+ "\n"
+ );
+}
+
+static void dcb_maxrate_help(void)
+{
+ fprintf(stderr,
+ "Usage: dcb maxrate help\n"
+ "\n"
+ );
+ dcb_maxrate_help_show();
+ dcb_maxrate_help_set();
+}
+
+static int dcb_maxrate_parse_mapping_tc_maxrate(__u32 key, char *value, void *data)
+{
+ __u64 rate;
+
+ if (get_rate64(&rate, value))
+ return -EINVAL;
+
+ return dcb_parse_mapping("TC", key, IEEE_8021QAZ_MAX_TCS - 1,
+ "RATE", rate, -1,
+ dcb_set_u64, data);
+}
+
+static void dcb_maxrate_print_tc_maxrate(struct dcb *dcb, const struct ieee_maxrate *maxrate)
+{
+ size_t size = ARRAY_SIZE(maxrate->tc_maxrate);
+ SPRINT_BUF(b);
+ size_t i;
+
+ open_json_array(PRINT_JSON, "tc_maxrate");
+ print_string(PRINT_FP, NULL, "tc-maxrate ", NULL);
+
+ for (i = 0; i < size; i++) {
+ snprintf(b, sizeof(b), "%zd:%%s ", i);
+ print_rate(dcb->use_iec, PRINT_ANY, NULL, b, maxrate->tc_maxrate[i]);
+ }
+
+ close_json_array(PRINT_JSON, "tc_maxrate");
+}
+
+static void dcb_maxrate_print(struct dcb *dcb, const struct ieee_maxrate *maxrate)
+{
+ dcb_maxrate_print_tc_maxrate(dcb, maxrate);
+ print_nl();
+}
+
+static int dcb_maxrate_get(struct dcb *dcb, const char *dev, struct ieee_maxrate *maxrate)
+{
+ return dcb_get_attribute(dcb, dev, DCB_ATTR_IEEE_MAXRATE, maxrate, sizeof(*maxrate));
+}
+
+static int dcb_maxrate_set(struct dcb *dcb, const char *dev, const struct ieee_maxrate *maxrate)
+{
+ return dcb_set_attribute(dcb, dev, DCB_ATTR_IEEE_MAXRATE, maxrate, sizeof(*maxrate));
+}
+
+static int dcb_cmd_maxrate_set(struct dcb *dcb, const char *dev, int argc, char **argv)
+{
+ struct ieee_maxrate maxrate;
+ int ret;
+
+ if (!argc) {
+ dcb_maxrate_help_set();
+ return 0;
+ }
+
+ ret = dcb_maxrate_get(dcb, dev, &maxrate);
+ if (ret)
+ return ret;
+
+ do {
+ if (matches(*argv, "help") == 0) {
+ dcb_maxrate_help_set();
+ return 0;
+ } else if (matches(*argv, "tc-maxrate") == 0) {
+ NEXT_ARG();
+ ret = parse_mapping(&argc, &argv, true,
+ &dcb_maxrate_parse_mapping_tc_maxrate, &maxrate);
+ if (ret) {
+ fprintf(stderr, "Invalid mapping %s\n", *argv);
+ return ret;
+ }
+ continue;
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ dcb_maxrate_help_set();
+ return -EINVAL;
+ }
+
+ NEXT_ARG_FWD();
+ } while (argc > 0);
+
+ return dcb_maxrate_set(dcb, dev, &maxrate);
+}
+
+static int dcb_cmd_maxrate_show(struct dcb *dcb, const char *dev, int argc, char **argv)
+{
+ struct ieee_maxrate maxrate;
+ int ret;
+
+ ret = dcb_maxrate_get(dcb, dev, &maxrate);
+ if (ret)
+ return ret;
+
+ open_json_object(NULL);
+
+ if (!argc) {
+ dcb_maxrate_print(dcb, &maxrate);
+ goto out;
+ }
+
+ do {
+ if (matches(*argv, "help") == 0) {
+ dcb_maxrate_help_show();
+ return 0;
+ } else if (matches(*argv, "tc-maxrate") == 0) {
+ dcb_maxrate_print_tc_maxrate(dcb, &maxrate);
+ print_nl();
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ dcb_maxrate_help_show();
+ return -EINVAL;
+ }
+
+ NEXT_ARG_FWD();
+ } while (argc > 0);
+
+out:
+ close_json_object();
+ return 0;
+}
+
+int dcb_cmd_maxrate(struct dcb *dcb, int argc, char **argv)
+{
+ if (!argc || matches(*argv, "help") == 0) {
+ dcb_maxrate_help();
+ return 0;
+ } else if (matches(*argv, "show") == 0) {
+ NEXT_ARG_FWD();
+ return dcb_cmd_parse_dev(dcb, argc, argv,
+ dcb_cmd_maxrate_show, dcb_maxrate_help_show);
+ } else if (matches(*argv, "set") == 0) {
+ NEXT_ARG_FWD();
+ return dcb_cmd_parse_dev(dcb, argc, argv,
+ dcb_cmd_maxrate_set, dcb_maxrate_help_set);
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ dcb_maxrate_help();
+ return -EINVAL;
+ }
+}
diff --git a/dcb/dcb_pfc.c b/dcb/dcb_pfc.c
new file mode 100644
index 0000000..aaa0902
--- /dev/null
+++ b/dcb/dcb_pfc.c
@@ -0,0 +1,286 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+#include <errno.h>
+#include <stdio.h>
+#include <linux/dcbnl.h>
+
+#include "dcb.h"
+#include "utils.h"
+
+static void dcb_pfc_help_set(void)
+{
+ fprintf(stderr,
+ "Usage: dcb pfc set dev STRING\n"
+ " [ prio-pfc PFC-MAP ]\n"
+ " [ macsec-bypass { on | off } ]\n"
+ " [ delay INTEGER ]\n"
+ "\n"
+ " where PFC-MAP := [ PFC-MAP ] PFC-MAPPING\n"
+ " PFC-MAPPING := { all | TC }:PFC\n"
+ " TC := { 0 .. 7 }\n"
+ " PFC := { on | off }\n"
+ "\n"
+ );
+}
+
+static void dcb_pfc_help_show(void)
+{
+ fprintf(stderr,
+ "Usage: dcb [ -s ] pfc show dev STRING\n"
+ " [ pfc-cap ] [ prio-pfc ] [ macsec-bypass ]\n"
+ " [ delay ] [ requests ] [ indications ]\n"
+ "\n"
+ );
+}
+
+static void dcb_pfc_help(void)
+{
+ fprintf(stderr,
+ "Usage: dcb pfc help\n"
+ "\n"
+ );
+ dcb_pfc_help_show();
+ dcb_pfc_help_set();
+}
+
+static void dcb_pfc_to_array(__u8 array[IEEE_8021QAZ_MAX_TCS], __u8 pfc_en)
+{
+ int i;
+
+ for (i = 0; i < IEEE_8021QAZ_MAX_TCS; i++)
+ array[i] = !!(pfc_en & (1 << i));
+}
+
+static void dcb_pfc_from_array(__u8 array[IEEE_8021QAZ_MAX_TCS], __u8 *pfc_en_p)
+{
+ __u8 pfc_en = 0;
+ int i;
+
+ for (i = 0; i < IEEE_8021QAZ_MAX_TCS; i++) {
+ if (array[i])
+ pfc_en |= 1 << i;
+ }
+
+ *pfc_en_p = pfc_en;
+}
+
+static int dcb_pfc_parse_mapping_prio_pfc(__u32 key, char *value, void *data)
+{
+ struct ieee_pfc *pfc = data;
+ __u8 pfc_en[IEEE_8021QAZ_MAX_TCS];
+ bool enabled;
+ int ret;
+
+ dcb_pfc_to_array(pfc_en, pfc->pfc_en);
+
+ enabled = parse_on_off("PFC", value, &ret);
+ if (ret)
+ return ret;
+
+ ret = dcb_parse_mapping("PRIO", key, IEEE_8021QAZ_MAX_TCS - 1,
+ "PFC", enabled, -1,
+ dcb_set_u8, pfc_en);
+ if (ret)
+ return ret;
+
+ dcb_pfc_from_array(pfc_en, &pfc->pfc_en);
+ return 0;
+}
+
+static void dcb_pfc_print_pfc_cap(const struct ieee_pfc *pfc)
+{
+ print_uint(PRINT_ANY, "pfc_cap", "pfc-cap %d ", pfc->pfc_cap);
+}
+
+static void dcb_pfc_print_macsec_bypass(const struct ieee_pfc *pfc)
+{
+ print_on_off(PRINT_ANY, "macsec_bypass", "macsec-bypass %s ", pfc->mbc);
+}
+
+static void dcb_pfc_print_delay(const struct ieee_pfc *pfc)
+{
+ print_uint(PRINT_ANY, "delay", "delay %d ", pfc->delay);
+}
+
+static void dcb_pfc_print_prio_pfc(const struct ieee_pfc *pfc)
+{
+ __u8 pfc_en[IEEE_8021QAZ_MAX_TCS];
+
+ dcb_pfc_to_array(pfc_en, pfc->pfc_en);
+ dcb_print_named_array("prio_pfc", "prio-pfc",
+ pfc_en, ARRAY_SIZE(pfc_en), &dcb_print_array_on_off);
+}
+
+static void dcb_pfc_print_requests(const struct ieee_pfc *pfc)
+{
+ open_json_array(PRINT_JSON, "requests");
+ print_string(PRINT_FP, NULL, "requests ", NULL);
+ dcb_print_array_u64(pfc->requests, ARRAY_SIZE(pfc->requests));
+ close_json_array(PRINT_JSON, "requests");
+}
+
+static void dcb_pfc_print_indications(const struct ieee_pfc *pfc)
+{
+ open_json_array(PRINT_JSON, "indications");
+ print_string(PRINT_FP, NULL, "indications ", NULL);
+ dcb_print_array_u64(pfc->indications, ARRAY_SIZE(pfc->indications));
+ close_json_array(PRINT_JSON, "indications");
+}
+
+static void dcb_pfc_print(const struct dcb *dcb, const struct ieee_pfc *pfc)
+{
+ dcb_pfc_print_pfc_cap(pfc);
+ dcb_pfc_print_macsec_bypass(pfc);
+ dcb_pfc_print_delay(pfc);
+ print_nl();
+
+ dcb_pfc_print_prio_pfc(pfc);
+ print_nl();
+
+ if (dcb->stats) {
+ dcb_pfc_print_requests(pfc);
+ print_nl();
+
+ dcb_pfc_print_indications(pfc);
+ print_nl();
+ }
+}
+
+static int dcb_pfc_get(struct dcb *dcb, const char *dev, struct ieee_pfc *pfc)
+{
+ return dcb_get_attribute(dcb, dev, DCB_ATTR_IEEE_PFC, pfc, sizeof(*pfc));
+}
+
+static int dcb_pfc_set(struct dcb *dcb, const char *dev, const struct ieee_pfc *pfc)
+{
+ return dcb_set_attribute(dcb, dev, DCB_ATTR_IEEE_PFC, pfc, sizeof(*pfc));
+}
+
+static int dcb_cmd_pfc_set(struct dcb *dcb, const char *dev, int argc, char **argv)
+{
+ struct ieee_pfc pfc;
+ int ret;
+
+ if (!argc) {
+ dcb_pfc_help_set();
+ return 0;
+ }
+
+ ret = dcb_pfc_get(dcb, dev, &pfc);
+ if (ret)
+ return ret;
+
+ do {
+ if (matches(*argv, "help") == 0) {
+ dcb_pfc_help_set();
+ return 0;
+ } else if (matches(*argv, "prio-pfc") == 0) {
+ NEXT_ARG();
+ ret = parse_mapping(&argc, &argv, true,
+ &dcb_pfc_parse_mapping_prio_pfc, &pfc);
+ if (ret) {
+ fprintf(stderr, "Invalid pfc mapping %s\n", *argv);
+ return ret;
+ }
+ continue;
+ } else if (matches(*argv, "macsec-bypass") == 0) {
+ NEXT_ARG();
+ pfc.mbc = parse_on_off("macsec-bypass", *argv, &ret);
+ if (ret)
+ return ret;
+ } else if (matches(*argv, "delay") == 0) {
+ NEXT_ARG();
+ /* Do not support the size notations for delay.
+ * Delay is specified in "bit times", not bits, so
+ * it is not applicable. At the same time it would
+ * be confusing that 10Kbit does not mean 10240,
+ * but 1280.
+ */
+ if (get_u16(&pfc.delay, *argv, 0)) {
+ fprintf(stderr, "Invalid delay `%s', expected an integer 0..65535\n",
+ *argv);
+ return -EINVAL;
+ }
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ dcb_pfc_help_set();
+ return -EINVAL;
+ }
+
+ NEXT_ARG_FWD();
+ } while (argc > 0);
+
+ return dcb_pfc_set(dcb, dev, &pfc);
+}
+
+static int dcb_cmd_pfc_show(struct dcb *dcb, const char *dev, int argc, char **argv)
+{
+ struct ieee_pfc pfc;
+ int ret;
+
+ ret = dcb_pfc_get(dcb, dev, &pfc);
+ if (ret)
+ return ret;
+
+ open_json_object(NULL);
+
+ if (!argc) {
+ dcb_pfc_print(dcb, &pfc);
+ goto out;
+ }
+
+ do {
+ if (matches(*argv, "help") == 0) {
+ dcb_pfc_help_show();
+ return 0;
+ } else if (matches(*argv, "prio-pfc") == 0) {
+ dcb_pfc_print_prio_pfc(&pfc);
+ print_nl();
+ } else if (matches(*argv, "pfc-cap") == 0) {
+ dcb_pfc_print_pfc_cap(&pfc);
+ print_nl();
+ } else if (matches(*argv, "macsec-bypass") == 0) {
+ dcb_pfc_print_macsec_bypass(&pfc);
+ print_nl();
+ } else if (matches(*argv, "delay") == 0) {
+ dcb_pfc_print_delay(&pfc);
+ print_nl();
+ } else if (matches(*argv, "requests") == 0) {
+ dcb_pfc_print_requests(&pfc);
+ print_nl();
+ } else if (matches(*argv, "indications") == 0) {
+ dcb_pfc_print_indications(&pfc);
+ print_nl();
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ dcb_pfc_help_show();
+ return -EINVAL;
+ }
+
+ NEXT_ARG_FWD();
+ } while (argc > 0);
+
+out:
+ close_json_object();
+ return 0;
+}
+
+int dcb_cmd_pfc(struct dcb *dcb, int argc, char **argv)
+{
+ if (!argc || matches(*argv, "help") == 0) {
+ dcb_pfc_help();
+ return 0;
+ } else if (matches(*argv, "show") == 0) {
+ NEXT_ARG_FWD();
+ return dcb_cmd_parse_dev(dcb, argc, argv,
+ dcb_cmd_pfc_show, dcb_pfc_help_show);
+ } else if (matches(*argv, "set") == 0) {
+ NEXT_ARG_FWD();
+ return dcb_cmd_parse_dev(dcb, argc, argv,
+ dcb_cmd_pfc_set, dcb_pfc_help_set);
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ dcb_pfc_help();
+ return -EINVAL;
+ }
+}
diff --git a/devlink/.gitignore b/devlink/.gitignore
new file mode 100644
index 0000000..08d175f
--- /dev/null
+++ b/devlink/.gitignore
@@ -0,0 +1 @@
+devlink
diff --git a/devlink/Makefile b/devlink/Makefile
new file mode 100644
index 0000000..d37a4b4
--- /dev/null
+++ b/devlink/Makefile
@@ -0,0 +1,25 @@
+# SPDX-License-Identifier: GPL-2.0
+include ../config.mk
+
+TARGETS :=
+
+ifeq ($(HAVE_MNL),y)
+
+DEVLINKOBJ = devlink.o mnlg.o
+TARGETS += devlink
+LDLIBS += -lm
+
+endif
+
+all: $(TARGETS) $(LIBS)
+
+devlink: $(DEVLINKOBJ) $(LIBNETLINK)
+ $(QUIET_LINK)$(CC) $^ $(LDFLAGS) $(LDLIBS) -o $@
+
+install: all
+ for i in $(TARGETS); \
+ do install -m 0755 $$i $(DESTDIR)$(SBINDIR); \
+ done
+
+clean:
+ rm -f $(DEVLINKOBJ) $(TARGETS)
diff --git a/devlink/devlink.c b/devlink/devlink.c
new file mode 100644
index 0000000..5db709c
--- /dev/null
+++ b/devlink/devlink.c
@@ -0,0 +1,8697 @@
+/*
+ * devlink.c Devlink tool
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Authors: Jiri Pirko <jiri@mellanox.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <limits.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <signal.h>
+#include <time.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <sys/sysinfo.h>
+#define _LINUX_SYSINFO_H /* avoid collision with musl header */
+#include <linux/genetlink.h>
+#include <linux/devlink.h>
+#include <linux/netlink.h>
+#include <libmnl/libmnl.h>
+#include <netinet/ether.h>
+#include <sys/select.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <rt_names.h>
+
+#include "version.h"
+#include "list.h"
+#include "mnlg.h"
+#include "mnl_utils.h"
+#include "json_print.h"
+#include "utils.h"
+#include "namespace.h"
+
+#define ESWITCH_MODE_LEGACY "legacy"
+#define ESWITCH_MODE_SWITCHDEV "switchdev"
+#define ESWITCH_INLINE_MODE_NONE "none"
+#define ESWITCH_INLINE_MODE_LINK "link"
+#define ESWITCH_INLINE_MODE_NETWORK "network"
+#define ESWITCH_INLINE_MODE_TRANSPORT "transport"
+
+#define ESWITCH_ENCAP_MODE_NONE "none"
+#define ESWITCH_ENCAP_MODE_BASIC "basic"
+
+#define PARAM_CMODE_RUNTIME_STR "runtime"
+#define PARAM_CMODE_DRIVERINIT_STR "driverinit"
+#define PARAM_CMODE_PERMANENT_STR "permanent"
+#define DL_ARGS_REQUIRED_MAX_ERR_LEN 80
+
+#define HEALTH_REPORTER_STATE_HEALTHY_STR "healthy"
+#define HEALTH_REPORTER_STATE_ERROR_STR "error"
+#define HEALTH_REPORTER_TIMESTAMP_FMT_LEN 80
+
+static int g_new_line_count;
+static int g_indent_level;
+static bool g_indent_newline;
+
+#define INDENT_STR_STEP 2
+#define INDENT_STR_MAXLEN 32
+static char g_indent_str[INDENT_STR_MAXLEN + 1] = "";
+
+static void __attribute__((format(printf, 1, 2)))
+pr_err(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+}
+
+static void __attribute__((format(printf, 1, 2)))
+pr_out(const char *fmt, ...)
+{
+ va_list ap;
+
+ if (g_indent_newline) {
+ printf("%s", g_indent_str);
+ g_indent_newline = false;
+ }
+ va_start(ap, fmt);
+ vprintf(fmt, ap);
+ va_end(ap);
+ g_new_line_count = 0;
+}
+
+static void __attribute__((format(printf, 2, 3)))
+pr_out_sp(unsigned int num, const char *fmt, ...)
+{
+ va_list ap;
+ int ret;
+
+ va_start(ap, fmt);
+ ret = vprintf(fmt, ap);
+ va_end(ap);
+
+ if (ret < num)
+ printf("%*s", num - ret, "");
+ g_new_line_count = 0; \
+}
+
+static void __attribute__((format(printf, 1, 2)))
+pr_out_tty(const char *fmt, ...)
+{
+ va_list ap;
+
+ if (!isatty(STDOUT_FILENO))
+ return;
+ va_start(ap, fmt);
+ vprintf(fmt, ap);
+ va_end(ap);
+}
+
+static void __pr_out_indent_inc(void)
+{
+ if (g_indent_level + INDENT_STR_STEP > INDENT_STR_MAXLEN)
+ return;
+ g_indent_level += INDENT_STR_STEP;
+ memset(g_indent_str, ' ', sizeof(g_indent_str));
+ g_indent_str[g_indent_level] = '\0';
+}
+
+static void __pr_out_indent_dec(void)
+{
+ if (g_indent_level - INDENT_STR_STEP < 0)
+ return;
+ g_indent_level -= INDENT_STR_STEP;
+ g_indent_str[g_indent_level] = '\0';
+}
+
+static void __pr_out_newline(void)
+{
+ if (g_new_line_count < 1) {
+ pr_out("\n");
+ g_indent_newline = true;
+ }
+ g_new_line_count++;
+}
+
+static void dummy_signal_handler(int signum)
+{
+}
+
+static int _mnlg_socket_recv_run_intr(struct mnlu_gen_socket *nlg,
+ mnl_cb_t data_cb, void *data)
+{
+ struct sigaction act, oact;
+ int err;
+
+ act.sa_handler = dummy_signal_handler;
+ sigemptyset(&act.sa_mask);
+ act.sa_flags = SA_NODEFER;
+
+ sigaction(SIGINT, &act, &oact);
+ err = mnlu_gen_socket_recv_run(nlg, data_cb, data);
+ sigaction(SIGINT, &oact, NULL);
+ if (err < 0 && errno != EINTR) {
+ pr_err("devlink answers: %s\n", strerror(errno));
+ return -errno;
+ }
+ return 0;
+}
+
+static int _mnlg_socket_send(struct mnlu_gen_socket *nlg,
+ const struct nlmsghdr *nlh)
+{
+ int err;
+
+ err = mnlg_socket_send(nlg, nlh);
+ if (err < 0) {
+ pr_err("Failed to call mnlg_socket_send\n");
+ return -errno;
+ }
+ return 0;
+}
+
+static int _mnlg_socket_group_add(struct mnlu_gen_socket *nlg,
+ const char *group_name)
+{
+ int err;
+
+ err = mnlg_socket_group_add(nlg, group_name);
+ if (err < 0) {
+ pr_err("Failed to call mnlg_socket_group_add\n");
+ return -errno;
+ }
+ return 0;
+}
+
+struct ifname_map {
+ struct list_head list;
+ char *bus_name;
+ char *dev_name;
+ uint32_t port_index;
+ char *ifname;
+};
+
+static struct ifname_map *ifname_map_alloc(const char *bus_name,
+ const char *dev_name,
+ uint32_t port_index,
+ const char *ifname)
+{
+ struct ifname_map *ifname_map;
+
+ ifname_map = calloc(1, sizeof(*ifname_map));
+ if (!ifname_map)
+ return NULL;
+ ifname_map->bus_name = strdup(bus_name);
+ ifname_map->dev_name = strdup(dev_name);
+ ifname_map->port_index = port_index;
+ ifname_map->ifname = strdup(ifname);
+ if (!ifname_map->bus_name || !ifname_map->dev_name ||
+ !ifname_map->ifname) {
+ free(ifname_map->ifname);
+ free(ifname_map->dev_name);
+ free(ifname_map->bus_name);
+ free(ifname_map);
+ return NULL;
+ }
+ return ifname_map;
+}
+
+static void ifname_map_free(struct ifname_map *ifname_map)
+{
+ free(ifname_map->ifname);
+ free(ifname_map->dev_name);
+ free(ifname_map->bus_name);
+ free(ifname_map);
+}
+
+#define DL_OPT_HANDLE BIT(0)
+#define DL_OPT_HANDLEP BIT(1)
+#define DL_OPT_PORT_TYPE BIT(2)
+#define DL_OPT_PORT_COUNT BIT(3)
+#define DL_OPT_SB BIT(4)
+#define DL_OPT_SB_POOL BIT(5)
+#define DL_OPT_SB_SIZE BIT(6)
+#define DL_OPT_SB_TYPE BIT(7)
+#define DL_OPT_SB_THTYPE BIT(8)
+#define DL_OPT_SB_TH BIT(9)
+#define DL_OPT_SB_TC BIT(10)
+#define DL_OPT_ESWITCH_MODE BIT(11)
+#define DL_OPT_ESWITCH_INLINE_MODE BIT(12)
+#define DL_OPT_DPIPE_TABLE_NAME BIT(13)
+#define DL_OPT_DPIPE_TABLE_COUNTERS BIT(14)
+#define DL_OPT_ESWITCH_ENCAP_MODE BIT(15)
+#define DL_OPT_RESOURCE_PATH BIT(16)
+#define DL_OPT_RESOURCE_SIZE BIT(17)
+#define DL_OPT_PARAM_NAME BIT(18)
+#define DL_OPT_PARAM_VALUE BIT(19)
+#define DL_OPT_PARAM_CMODE BIT(20)
+#define DL_OPT_HANDLE_REGION BIT(21)
+#define DL_OPT_REGION_SNAPSHOT_ID BIT(22)
+#define DL_OPT_REGION_ADDRESS BIT(23)
+#define DL_OPT_REGION_LENGTH BIT(24)
+#define DL_OPT_FLASH_FILE_NAME BIT(25)
+#define DL_OPT_FLASH_COMPONENT BIT(26)
+#define DL_OPT_HEALTH_REPORTER_NAME BIT(27)
+#define DL_OPT_HEALTH_REPORTER_GRACEFUL_PERIOD BIT(28)
+#define DL_OPT_HEALTH_REPORTER_AUTO_RECOVER BIT(29)
+#define DL_OPT_TRAP_NAME BIT(30)
+#define DL_OPT_TRAP_ACTION BIT(31)
+#define DL_OPT_TRAP_GROUP_NAME BIT(32)
+#define DL_OPT_NETNS BIT(33)
+#define DL_OPT_TRAP_POLICER_ID BIT(34)
+#define DL_OPT_TRAP_POLICER_RATE BIT(35)
+#define DL_OPT_TRAP_POLICER_BURST BIT(36)
+#define DL_OPT_HEALTH_REPORTER_AUTO_DUMP BIT(37)
+#define DL_OPT_PORT_FUNCTION_HW_ADDR BIT(38)
+#define DL_OPT_FLASH_OVERWRITE BIT(39)
+#define DL_OPT_RELOAD_ACTION BIT(40)
+#define DL_OPT_RELOAD_LIMIT BIT(41)
+#define DL_OPT_PORT_FLAVOUR BIT(42)
+#define DL_OPT_PORT_PFNUMBER BIT(43)
+#define DL_OPT_PORT_SFNUMBER BIT(44)
+#define DL_OPT_PORT_FUNCTION_STATE BIT(45)
+
+struct dl_opts {
+ uint64_t present; /* flags of present items */
+ char *bus_name;
+ char *dev_name;
+ uint32_t port_index;
+ enum devlink_port_type port_type;
+ uint32_t port_count;
+ uint32_t sb_index;
+ uint16_t sb_pool_index;
+ uint32_t sb_pool_size;
+ enum devlink_sb_pool_type sb_pool_type;
+ enum devlink_sb_threshold_type sb_pool_thtype;
+ uint32_t sb_threshold;
+ uint16_t sb_tc_index;
+ enum devlink_eswitch_mode eswitch_mode;
+ enum devlink_eswitch_inline_mode eswitch_inline_mode;
+ const char *dpipe_table_name;
+ bool dpipe_counters_enabled;
+ enum devlink_eswitch_encap_mode eswitch_encap_mode;
+ const char *resource_path;
+ uint64_t resource_size;
+ uint32_t resource_id;
+ bool resource_id_valid;
+ const char *param_name;
+ const char *param_value;
+ enum devlink_param_cmode cmode;
+ char *region_name;
+ uint32_t region_snapshot_id;
+ uint64_t region_address;
+ uint64_t region_length;
+ const char *flash_file_name;
+ const char *flash_component;
+ const char *reporter_name;
+ uint64_t reporter_graceful_period;
+ bool reporter_auto_recover;
+ bool reporter_auto_dump;
+ const char *trap_name;
+ const char *trap_group_name;
+ enum devlink_trap_action trap_action;
+ bool netns_is_pid;
+ uint32_t netns;
+ uint32_t trap_policer_id;
+ uint64_t trap_policer_rate;
+ uint64_t trap_policer_burst;
+ char port_function_hw_addr[MAX_ADDR_LEN];
+ uint32_t port_function_hw_addr_len;
+ uint32_t overwrite_mask;
+ enum devlink_reload_action reload_action;
+ enum devlink_reload_limit reload_limit;
+ uint32_t port_sfnumber;
+ uint16_t port_flavour;
+ uint16_t port_pfnumber;
+ uint8_t port_fn_state;
+};
+
+struct dl {
+ struct mnlu_gen_socket nlg;
+ struct list_head ifname_map_list;
+ int argc;
+ char **argv;
+ bool no_nice_names;
+ struct dl_opts opts;
+ bool json_output;
+ bool pretty_output;
+ bool verbose;
+ bool stats;
+ struct {
+ bool present;
+ char *bus_name;
+ char *dev_name;
+ uint32_t port_index;
+ } arr_last;
+};
+
+static int dl_argc(struct dl *dl)
+{
+ return dl->argc;
+}
+
+static char *dl_argv(struct dl *dl)
+{
+ if (dl_argc(dl) == 0)
+ return NULL;
+ return *dl->argv;
+}
+
+static void dl_arg_inc(struct dl *dl)
+{
+ if (dl_argc(dl) == 0)
+ return;
+ dl->argc--;
+ dl->argv++;
+}
+
+static void dl_arg_dec(struct dl *dl)
+{
+ dl->argc++;
+ dl->argv--;
+}
+
+static char *dl_argv_next(struct dl *dl)
+{
+ char *ret;
+
+ if (dl_argc(dl) == 0)
+ return NULL;
+
+ ret = *dl->argv;
+ dl_arg_inc(dl);
+ return ret;
+}
+
+static char *dl_argv_index(struct dl *dl, unsigned int index)
+{
+ if (index >= dl_argc(dl))
+ return NULL;
+ return dl->argv[index];
+}
+
+static int strcmpx(const char *str1, const char *str2)
+{
+ if (strlen(str1) > strlen(str2))
+ return -1;
+ return strncmp(str1, str2, strlen(str1));
+}
+
+static bool dl_argv_match(struct dl *dl, const char *pattern)
+{
+ if (dl_argc(dl) == 0)
+ return false;
+ return strcmpx(dl_argv(dl), pattern) == 0;
+}
+
+static bool dl_no_arg(struct dl *dl)
+{
+ return dl_argc(dl) == 0;
+}
+
+static void __pr_out_indent_newline(struct dl *dl)
+{
+ if (!g_indent_newline && !dl->json_output)
+ pr_out(" ");
+}
+
+static bool is_binary_eol(int i)
+{
+ return !(i%16);
+}
+
+static void pr_out_binary_value(struct dl *dl, uint8_t *data, uint32_t len)
+{
+ int i = 0;
+
+ while (i < len) {
+ if (dl->json_output)
+ print_int(PRINT_JSON, NULL, NULL, data[i]);
+ else
+ pr_out("%02x ", data[i]);
+ i++;
+ if (!dl->json_output && is_binary_eol(i))
+ __pr_out_newline();
+ }
+ if (!dl->json_output && !is_binary_eol(i))
+ __pr_out_newline();
+}
+
+static void pr_out_name(struct dl *dl, const char *name)
+{
+ __pr_out_indent_newline(dl);
+ if (dl->json_output)
+ print_string(PRINT_JSON, name, NULL, NULL);
+ else
+ pr_out("%s:", name);
+}
+
+static void pr_out_u64(struct dl *dl, const char *name, uint64_t val)
+{
+ __pr_out_indent_newline(dl);
+ if (val == (uint64_t) -1)
+ return print_string_name_value(name, "unlimited");
+
+ if (dl->json_output)
+ print_u64(PRINT_JSON, name, NULL, val);
+ else
+ pr_out("%s %"PRIu64, name, val);
+}
+
+static void pr_out_section_start(struct dl *dl, const char *name)
+{
+ if (dl->json_output) {
+ open_json_object(NULL);
+ open_json_object(name);
+ }
+}
+
+static void pr_out_section_end(struct dl *dl)
+{
+ if (dl->json_output) {
+ if (dl->arr_last.present)
+ close_json_array(PRINT_JSON, NULL);
+ close_json_object();
+ close_json_object();
+ }
+}
+
+static void pr_out_array_start(struct dl *dl, const char *name)
+{
+ if (dl->json_output) {
+ open_json_array(PRINT_JSON, name);
+ } else {
+ __pr_out_indent_inc();
+ __pr_out_newline();
+ pr_out("%s:", name);
+ __pr_out_indent_inc();
+ __pr_out_newline();
+ }
+}
+
+static void pr_out_array_end(struct dl *dl)
+{
+ if (dl->json_output) {
+ close_json_array(PRINT_JSON, NULL);
+ } else {
+ __pr_out_indent_dec();
+ __pr_out_indent_dec();
+ }
+}
+
+static void pr_out_object_start(struct dl *dl, const char *name)
+{
+ if (dl->json_output) {
+ open_json_object(name);
+ } else {
+ __pr_out_indent_inc();
+ __pr_out_newline();
+ pr_out("%s:", name);
+ __pr_out_indent_inc();
+ __pr_out_newline();
+ }
+}
+
+static void pr_out_object_end(struct dl *dl)
+{
+ if (dl->json_output) {
+ close_json_object();
+ } else {
+ __pr_out_indent_dec();
+ __pr_out_indent_dec();
+ }
+}
+
+static void pr_out_entry_start(struct dl *dl)
+{
+ if (dl->json_output)
+ open_json_object(NULL);
+}
+
+static void pr_out_entry_end(struct dl *dl)
+{
+ if (dl->json_output)
+ close_json_object();
+ else
+ __pr_out_newline();
+}
+
+static void check_indent_newline(struct dl *dl)
+{
+ __pr_out_indent_newline(dl);
+
+ if (g_indent_newline && !is_json_context()) {
+ printf("%s", g_indent_str);
+ g_indent_newline = false;
+ }
+ g_new_line_count = 0;
+}
+
+static const enum mnl_attr_data_type devlink_policy[DEVLINK_ATTR_MAX + 1] = {
+ [DEVLINK_ATTR_BUS_NAME] = MNL_TYPE_NUL_STRING,
+ [DEVLINK_ATTR_DEV_NAME] = MNL_TYPE_NUL_STRING,
+ [DEVLINK_ATTR_PORT_INDEX] = MNL_TYPE_U32,
+ [DEVLINK_ATTR_PORT_TYPE] = MNL_TYPE_U16,
+ [DEVLINK_ATTR_PORT_DESIRED_TYPE] = MNL_TYPE_U16,
+ [DEVLINK_ATTR_PORT_NETDEV_IFINDEX] = MNL_TYPE_U32,
+ [DEVLINK_ATTR_PORT_NETDEV_NAME] = MNL_TYPE_NUL_STRING,
+ [DEVLINK_ATTR_PORT_IBDEV_NAME] = MNL_TYPE_NUL_STRING,
+ [DEVLINK_ATTR_PORT_LANES] = MNL_TYPE_U32,
+ [DEVLINK_ATTR_PORT_SPLITTABLE] = MNL_TYPE_U8,
+ [DEVLINK_ATTR_SB_INDEX] = MNL_TYPE_U32,
+ [DEVLINK_ATTR_SB_SIZE] = MNL_TYPE_U32,
+ [DEVLINK_ATTR_SB_INGRESS_POOL_COUNT] = MNL_TYPE_U16,
+ [DEVLINK_ATTR_SB_EGRESS_POOL_COUNT] = MNL_TYPE_U16,
+ [DEVLINK_ATTR_SB_INGRESS_TC_COUNT] = MNL_TYPE_U16,
+ [DEVLINK_ATTR_SB_EGRESS_TC_COUNT] = MNL_TYPE_U16,
+ [DEVLINK_ATTR_SB_POOL_INDEX] = MNL_TYPE_U16,
+ [DEVLINK_ATTR_SB_POOL_TYPE] = MNL_TYPE_U8,
+ [DEVLINK_ATTR_SB_POOL_SIZE] = MNL_TYPE_U32,
+ [DEVLINK_ATTR_SB_POOL_THRESHOLD_TYPE] = MNL_TYPE_U8,
+ [DEVLINK_ATTR_SB_THRESHOLD] = MNL_TYPE_U32,
+ [DEVLINK_ATTR_SB_TC_INDEX] = MNL_TYPE_U16,
+ [DEVLINK_ATTR_SB_OCC_CUR] = MNL_TYPE_U32,
+ [DEVLINK_ATTR_SB_OCC_MAX] = MNL_TYPE_U32,
+ [DEVLINK_ATTR_ESWITCH_MODE] = MNL_TYPE_U16,
+ [DEVLINK_ATTR_ESWITCH_INLINE_MODE] = MNL_TYPE_U8,
+ [DEVLINK_ATTR_ESWITCH_ENCAP_MODE] = MNL_TYPE_U8,
+ [DEVLINK_ATTR_DPIPE_TABLES] = MNL_TYPE_NESTED,
+ [DEVLINK_ATTR_DPIPE_TABLE] = MNL_TYPE_NESTED,
+ [DEVLINK_ATTR_DPIPE_TABLE_NAME] = MNL_TYPE_STRING,
+ [DEVLINK_ATTR_DPIPE_TABLE_SIZE] = MNL_TYPE_U64,
+ [DEVLINK_ATTR_DPIPE_TABLE_MATCHES] = MNL_TYPE_NESTED,
+ [DEVLINK_ATTR_DPIPE_TABLE_ACTIONS] = MNL_TYPE_NESTED,
+ [DEVLINK_ATTR_DPIPE_TABLE_COUNTERS_ENABLED] = MNL_TYPE_U8,
+ [DEVLINK_ATTR_DPIPE_ENTRIES] = MNL_TYPE_NESTED,
+ [DEVLINK_ATTR_DPIPE_ENTRY] = MNL_TYPE_NESTED,
+ [DEVLINK_ATTR_DPIPE_ENTRY_INDEX] = MNL_TYPE_U64,
+ [DEVLINK_ATTR_DPIPE_ENTRY_MATCH_VALUES] = MNL_TYPE_NESTED,
+ [DEVLINK_ATTR_DPIPE_ENTRY_ACTION_VALUES] = MNL_TYPE_NESTED,
+ [DEVLINK_ATTR_DPIPE_ENTRY_COUNTER] = MNL_TYPE_U64,
+ [DEVLINK_ATTR_DPIPE_MATCH] = MNL_TYPE_NESTED,
+ [DEVLINK_ATTR_DPIPE_MATCH_VALUE] = MNL_TYPE_NESTED,
+ [DEVLINK_ATTR_DPIPE_MATCH_TYPE] = MNL_TYPE_U32,
+ [DEVLINK_ATTR_DPIPE_ACTION] = MNL_TYPE_NESTED,
+ [DEVLINK_ATTR_DPIPE_ACTION_VALUE] = MNL_TYPE_NESTED,
+ [DEVLINK_ATTR_DPIPE_ACTION_TYPE] = MNL_TYPE_U32,
+ [DEVLINK_ATTR_DPIPE_VALUE_MAPPING] = MNL_TYPE_U32,
+ [DEVLINK_ATTR_DPIPE_HEADERS] = MNL_TYPE_NESTED,
+ [DEVLINK_ATTR_DPIPE_HEADER] = MNL_TYPE_NESTED,
+ [DEVLINK_ATTR_DPIPE_HEADER_NAME] = MNL_TYPE_STRING,
+ [DEVLINK_ATTR_DPIPE_HEADER_ID] = MNL_TYPE_U32,
+ [DEVLINK_ATTR_DPIPE_HEADER_FIELDS] = MNL_TYPE_NESTED,
+ [DEVLINK_ATTR_DPIPE_HEADER_GLOBAL] = MNL_TYPE_U8,
+ [DEVLINK_ATTR_DPIPE_HEADER_INDEX] = MNL_TYPE_U32,
+ [DEVLINK_ATTR_DPIPE_FIELD] = MNL_TYPE_NESTED,
+ [DEVLINK_ATTR_DPIPE_FIELD_NAME] = MNL_TYPE_STRING,
+ [DEVLINK_ATTR_DPIPE_FIELD_ID] = MNL_TYPE_U32,
+ [DEVLINK_ATTR_DPIPE_FIELD_BITWIDTH] = MNL_TYPE_U32,
+ [DEVLINK_ATTR_DPIPE_FIELD_MAPPING_TYPE] = MNL_TYPE_U32,
+ [DEVLINK_ATTR_PARAM] = MNL_TYPE_NESTED,
+ [DEVLINK_ATTR_PARAM_NAME] = MNL_TYPE_STRING,
+ [DEVLINK_ATTR_PARAM_TYPE] = MNL_TYPE_U8,
+ [DEVLINK_ATTR_PARAM_VALUES_LIST] = MNL_TYPE_NESTED,
+ [DEVLINK_ATTR_PARAM_VALUE] = MNL_TYPE_NESTED,
+ [DEVLINK_ATTR_PARAM_VALUE_CMODE] = MNL_TYPE_U8,
+ [DEVLINK_ATTR_REGION_NAME] = MNL_TYPE_STRING,
+ [DEVLINK_ATTR_REGION_SIZE] = MNL_TYPE_U64,
+ [DEVLINK_ATTR_REGION_SNAPSHOTS] = MNL_TYPE_NESTED,
+ [DEVLINK_ATTR_REGION_SNAPSHOT] = MNL_TYPE_NESTED,
+ [DEVLINK_ATTR_REGION_SNAPSHOT_ID] = MNL_TYPE_U32,
+ [DEVLINK_ATTR_REGION_CHUNKS] = MNL_TYPE_NESTED,
+ [DEVLINK_ATTR_REGION_CHUNK] = MNL_TYPE_NESTED,
+ [DEVLINK_ATTR_REGION_CHUNK_DATA] = MNL_TYPE_BINARY,
+ [DEVLINK_ATTR_REGION_CHUNK_ADDR] = MNL_TYPE_U64,
+ [DEVLINK_ATTR_REGION_CHUNK_LEN] = MNL_TYPE_U64,
+ [DEVLINK_ATTR_INFO_DRIVER_NAME] = MNL_TYPE_STRING,
+ [DEVLINK_ATTR_INFO_SERIAL_NUMBER] = MNL_TYPE_STRING,
+ [DEVLINK_ATTR_INFO_BOARD_SERIAL_NUMBER] = MNL_TYPE_STRING,
+ [DEVLINK_ATTR_INFO_VERSION_FIXED] = MNL_TYPE_NESTED,
+ [DEVLINK_ATTR_INFO_VERSION_RUNNING] = MNL_TYPE_NESTED,
+ [DEVLINK_ATTR_INFO_VERSION_STORED] = MNL_TYPE_NESTED,
+ [DEVLINK_ATTR_INFO_VERSION_NAME] = MNL_TYPE_STRING,
+ [DEVLINK_ATTR_INFO_VERSION_VALUE] = MNL_TYPE_STRING,
+ [DEVLINK_ATTR_HEALTH_REPORTER] = MNL_TYPE_NESTED,
+ [DEVLINK_ATTR_HEALTH_REPORTER_NAME] = MNL_TYPE_STRING,
+ [DEVLINK_ATTR_HEALTH_REPORTER_STATE] = MNL_TYPE_U8,
+ [DEVLINK_ATTR_HEALTH_REPORTER_ERR_COUNT] = MNL_TYPE_U64,
+ [DEVLINK_ATTR_HEALTH_REPORTER_RECOVER_COUNT] = MNL_TYPE_U64,
+ [DEVLINK_ATTR_HEALTH_REPORTER_DUMP_TS] = MNL_TYPE_U64,
+ [DEVLINK_ATTR_HEALTH_REPORTER_GRACEFUL_PERIOD] = MNL_TYPE_U64,
+ [DEVLINK_ATTR_FLASH_UPDATE_COMPONENT] = MNL_TYPE_STRING,
+ [DEVLINK_ATTR_FLASH_UPDATE_STATUS_MSG] = MNL_TYPE_STRING,
+ [DEVLINK_ATTR_FLASH_UPDATE_STATUS_DONE] = MNL_TYPE_U64,
+ [DEVLINK_ATTR_FLASH_UPDATE_STATUS_TOTAL] = MNL_TYPE_U64,
+ [DEVLINK_ATTR_STATS] = MNL_TYPE_NESTED,
+ [DEVLINK_ATTR_TRAP_NAME] = MNL_TYPE_STRING,
+ [DEVLINK_ATTR_TRAP_ACTION] = MNL_TYPE_U8,
+ [DEVLINK_ATTR_TRAP_TYPE] = MNL_TYPE_U8,
+ [DEVLINK_ATTR_TRAP_GENERIC] = MNL_TYPE_FLAG,
+ [DEVLINK_ATTR_TRAP_METADATA] = MNL_TYPE_NESTED,
+ [DEVLINK_ATTR_TRAP_GROUP_NAME] = MNL_TYPE_STRING,
+ [DEVLINK_ATTR_RELOAD_FAILED] = MNL_TYPE_U8,
+ [DEVLINK_ATTR_DEV_STATS] = MNL_TYPE_NESTED,
+ [DEVLINK_ATTR_RELOAD_STATS] = MNL_TYPE_NESTED,
+ [DEVLINK_ATTR_RELOAD_STATS_ENTRY] = MNL_TYPE_NESTED,
+ [DEVLINK_ATTR_RELOAD_ACTION] = MNL_TYPE_U8,
+ [DEVLINK_ATTR_RELOAD_STATS_LIMIT] = MNL_TYPE_U8,
+ [DEVLINK_ATTR_RELOAD_STATS_VALUE] = MNL_TYPE_U32,
+ [DEVLINK_ATTR_REMOTE_RELOAD_STATS] = MNL_TYPE_NESTED,
+ [DEVLINK_ATTR_RELOAD_ACTION_INFO] = MNL_TYPE_NESTED,
+ [DEVLINK_ATTR_RELOAD_ACTION_STATS] = MNL_TYPE_NESTED,
+ [DEVLINK_ATTR_TRAP_POLICER_ID] = MNL_TYPE_U32,
+ [DEVLINK_ATTR_TRAP_POLICER_RATE] = MNL_TYPE_U64,
+ [DEVLINK_ATTR_TRAP_POLICER_BURST] = MNL_TYPE_U64,
+};
+
+static const enum mnl_attr_data_type
+devlink_stats_policy[DEVLINK_ATTR_STATS_MAX + 1] = {
+ [DEVLINK_ATTR_STATS_RX_PACKETS] = MNL_TYPE_U64,
+ [DEVLINK_ATTR_STATS_RX_BYTES] = MNL_TYPE_U64,
+ [DEVLINK_ATTR_STATS_RX_DROPPED] = MNL_TYPE_U64,
+};
+
+static int attr_cb(const struct nlattr *attr, void *data)
+{
+ const struct nlattr **tb = data;
+ int type;
+
+ if (mnl_attr_type_valid(attr, DEVLINK_ATTR_MAX) < 0)
+ return MNL_CB_OK;
+
+ type = mnl_attr_get_type(attr);
+ if (mnl_attr_validate(attr, devlink_policy[type]) < 0)
+ return MNL_CB_ERROR;
+
+ tb[type] = attr;
+ return MNL_CB_OK;
+}
+
+static int attr_stats_cb(const struct nlattr *attr, void *data)
+{
+ const struct nlattr **tb = data;
+ int type;
+
+ /* Allow the tool to work on top of newer kernels that might contain
+ * more attributes.
+ */
+ if (mnl_attr_type_valid(attr, DEVLINK_ATTR_STATS_MAX) < 0)
+ return MNL_CB_OK;
+
+ type = mnl_attr_get_type(attr);
+ if (mnl_attr_validate(attr, devlink_stats_policy[type]) < 0)
+ return MNL_CB_ERROR;
+
+ tb[type] = attr;
+ return MNL_CB_OK;
+}
+
+static const enum mnl_attr_data_type
+devlink_function_policy[DEVLINK_PORT_FUNCTION_ATTR_MAX + 1] = {
+ [DEVLINK_PORT_FUNCTION_ATTR_HW_ADDR ] = MNL_TYPE_BINARY,
+ [DEVLINK_PORT_FN_ATTR_STATE] = MNL_TYPE_U8,
+};
+
+static int function_attr_cb(const struct nlattr *attr, void *data)
+{
+ const struct nlattr **tb = data;
+ int type;
+
+ /* Allow the tool to work on top of newer kernels that might contain
+ * more attributes.
+ */
+ if (mnl_attr_type_valid(attr, DEVLINK_PORT_FUNCTION_ATTR_MAX) < 0)
+ return MNL_CB_OK;
+
+ type = mnl_attr_get_type(attr);
+ if (mnl_attr_validate(attr, devlink_function_policy[type]) < 0)
+ return MNL_CB_ERROR;
+
+ tb[type] = attr;
+ return MNL_CB_OK;
+}
+
+static int ifname_map_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct nlattr *tb[DEVLINK_ATTR_MAX + 1] = {};
+ struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+ struct dl *dl = data;
+ struct ifname_map *ifname_map;
+ const char *bus_name;
+ const char *dev_name;
+ uint32_t port_ifindex;
+ const char *port_ifname;
+
+ mnl_attr_parse(nlh, sizeof(*genl), attr_cb, tb);
+ if (!tb[DEVLINK_ATTR_BUS_NAME] || !tb[DEVLINK_ATTR_DEV_NAME] ||
+ !tb[DEVLINK_ATTR_PORT_INDEX])
+ return MNL_CB_ERROR;
+
+ if (!tb[DEVLINK_ATTR_PORT_NETDEV_NAME])
+ return MNL_CB_OK;
+
+ bus_name = mnl_attr_get_str(tb[DEVLINK_ATTR_BUS_NAME]);
+ dev_name = mnl_attr_get_str(tb[DEVLINK_ATTR_DEV_NAME]);
+ port_ifindex = mnl_attr_get_u32(tb[DEVLINK_ATTR_PORT_INDEX]);
+ port_ifname = mnl_attr_get_str(tb[DEVLINK_ATTR_PORT_NETDEV_NAME]);
+ ifname_map = ifname_map_alloc(bus_name, dev_name,
+ port_ifindex, port_ifname);
+ if (!ifname_map)
+ return MNL_CB_ERROR;
+ list_add(&ifname_map->list, &dl->ifname_map_list);
+
+ return MNL_CB_OK;
+}
+
+static void ifname_map_fini(struct dl *dl)
+{
+ struct ifname_map *ifname_map, *tmp;
+
+ list_for_each_entry_safe(ifname_map, tmp,
+ &dl->ifname_map_list, list) {
+ list_del(&ifname_map->list);
+ ifname_map_free(ifname_map);
+ }
+}
+
+static int ifname_map_init(struct dl *dl)
+{
+ struct nlmsghdr *nlh;
+ int err;
+
+ INIT_LIST_HEAD(&dl->ifname_map_list);
+
+
+ nlh = mnlu_gen_socket_cmd_prepare(&dl->nlg, DEVLINK_CMD_PORT_GET,
+ NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP);
+
+ err = mnlu_gen_socket_sndrcv(&dl->nlg, nlh, ifname_map_cb, dl);
+ if (err) {
+ ifname_map_fini(dl);
+ return err;
+ }
+ return 0;
+}
+
+static int ifname_map_lookup(struct dl *dl, const char *ifname,
+ char **p_bus_name, char **p_dev_name,
+ uint32_t *p_port_index)
+{
+ struct ifname_map *ifname_map;
+
+ list_for_each_entry(ifname_map, &dl->ifname_map_list, list) {
+ if (strcmp(ifname, ifname_map->ifname) == 0) {
+ *p_bus_name = ifname_map->bus_name;
+ *p_dev_name = ifname_map->dev_name;
+ *p_port_index = ifname_map->port_index;
+ return 0;
+ }
+ }
+ return -ENOENT;
+}
+
+static int ifname_map_rev_lookup(struct dl *dl, const char *bus_name,
+ const char *dev_name, uint32_t port_index,
+ char **p_ifname)
+{
+ struct ifname_map *ifname_map;
+
+ list_for_each_entry(ifname_map, &dl->ifname_map_list, list) {
+ if (strcmp(bus_name, ifname_map->bus_name) == 0 &&
+ strcmp(dev_name, ifname_map->dev_name) == 0 &&
+ port_index == ifname_map->port_index) {
+ *p_ifname = ifname_map->ifname;
+ return 0;
+ }
+ }
+ return -ENOENT;
+}
+
+static int strtouint64_t(const char *str, uint64_t *p_val)
+{
+ char *endptr;
+ unsigned long long int val;
+
+ val = strtoull(str, &endptr, 10);
+ if (endptr == str || *endptr != '\0')
+ return -EINVAL;
+ if (val > ULONG_MAX)
+ return -ERANGE;
+ *p_val = val;
+ return 0;
+}
+
+static int strtouint32_t(const char *str, uint32_t *p_val)
+{
+ char *endptr;
+ unsigned long int val;
+
+ val = strtoul(str, &endptr, 10);
+ if (endptr == str || *endptr != '\0')
+ return -EINVAL;
+ if (val > UINT_MAX)
+ return -ERANGE;
+ *p_val = val;
+ return 0;
+}
+
+static int strtouint16_t(const char *str, uint16_t *p_val)
+{
+ char *endptr;
+ unsigned long int val;
+
+ val = strtoul(str, &endptr, 10);
+ if (endptr == str || *endptr != '\0')
+ return -EINVAL;
+ if (val > USHRT_MAX)
+ return -ERANGE;
+ *p_val = val;
+ return 0;
+}
+
+static int strtouint8_t(const char *str, uint8_t *p_val)
+{
+ char *endptr;
+ unsigned long int val;
+
+ val = strtoul(str, &endptr, 10);
+ if (endptr == str || *endptr != '\0')
+ return -EINVAL;
+ if (val > UCHAR_MAX)
+ return -ERANGE;
+ *p_val = val;
+ return 0;
+}
+
+static int strtobool(const char *str, bool *p_val)
+{
+ bool val;
+
+ if (!strcmp(str, "true") || !strcmp(str, "1") ||
+ !strcmp(str, "enable"))
+ val = true;
+ else if (!strcmp(str, "false") || !strcmp(str, "0") ||
+ !strcmp(str, "disable"))
+ val = false;
+ else
+ return -EINVAL;
+ *p_val = val;
+ return 0;
+}
+
+static int __dl_argv_handle(char *str, char **p_bus_name, char **p_dev_name)
+{
+ int err;
+
+ err = str_split_by_char(str, p_bus_name, p_dev_name, '/');
+ if (err) {
+ pr_err("Devlink identification (\"bus_name/dev_name\") \"%s\" is invalid\n", str);
+ return err;
+ }
+ return 0;
+}
+
+static int dl_argv_handle(struct dl *dl, char **p_bus_name, char **p_dev_name)
+{
+ char *str = dl_argv_next(dl);
+
+ if (!str) {
+ pr_err("Devlink identification (\"bus_name/dev_name\") expected\n");
+ return -EINVAL;
+ }
+ if (get_str_char_count(str, '/') != 1) {
+ pr_err("Wrong devlink identification string format.\n");
+ pr_err("Expected \"bus_name/dev_name\".\n");
+ return -EINVAL;
+ }
+ return __dl_argv_handle(str, p_bus_name, p_dev_name);
+}
+
+static int __dl_argv_handle_port(char *str,
+ char **p_bus_name, char **p_dev_name,
+ uint32_t *p_port_index)
+{
+ char *handlestr;
+ char *portstr;
+ int err;
+
+ err = str_split_by_char(str, &handlestr, &portstr, '/');
+ if (err) {
+ pr_err("Port identification \"%s\" is invalid\n", str);
+ return err;
+ }
+ err = strtouint32_t(portstr, p_port_index);
+ if (err) {
+ pr_err("Port index \"%s\" is not a number or not within range\n",
+ portstr);
+ return err;
+ }
+ err = str_split_by_char(handlestr, p_bus_name, p_dev_name, '/');
+ if (err) {
+ pr_err("Port identification \"%s\" is invalid\n", str);
+ return err;
+ }
+ return 0;
+}
+
+static int __dl_argv_handle_port_ifname(struct dl *dl, char *str,
+ char **p_bus_name, char **p_dev_name,
+ uint32_t *p_port_index)
+{
+ int err;
+
+ err = ifname_map_lookup(dl, str, p_bus_name, p_dev_name,
+ p_port_index);
+ if (err) {
+ pr_err("Netdevice \"%s\" not found\n", str);
+ return err;
+ }
+ return 0;
+}
+
+static int dl_argv_handle_port(struct dl *dl, char **p_bus_name,
+ char **p_dev_name, uint32_t *p_port_index)
+{
+ char *str = dl_argv_next(dl);
+ unsigned int slash_count;
+
+ if (!str) {
+ pr_err("Port identification (\"bus_name/dev_name/port_index\" or \"netdev ifname\") expected.\n");
+ return -EINVAL;
+ }
+ slash_count = get_str_char_count(str, '/');
+ switch (slash_count) {
+ case 0:
+ return __dl_argv_handle_port_ifname(dl, str, p_bus_name,
+ p_dev_name, p_port_index);
+ case 2:
+ return __dl_argv_handle_port(str, p_bus_name,
+ p_dev_name, p_port_index);
+ default:
+ pr_err("Wrong port identification string format.\n");
+ pr_err("Expected \"bus_name/dev_name/port_index\" or \"netdev_ifname\".\n");
+ return -EINVAL;
+ }
+}
+
+static int dl_argv_handle_both(struct dl *dl, char **p_bus_name,
+ char **p_dev_name, uint32_t *p_port_index,
+ uint64_t *p_handle_bit)
+{
+ char *str = dl_argv_next(dl);
+ unsigned int slash_count;
+ int err;
+
+ if (!str) {
+ pr_err("One of following identifications expected:\n"
+ "Devlink identification (\"bus_name/dev_name\")\n"
+ "Port identification (\"bus_name/dev_name/port_index\" or \"netdev ifname\")\n");
+ return -EINVAL;
+ }
+ slash_count = get_str_char_count(str, '/');
+ if (slash_count == 1) {
+ err = __dl_argv_handle(str, p_bus_name, p_dev_name);
+ if (err)
+ return err;
+ *p_handle_bit = DL_OPT_HANDLE;
+ } else if (slash_count == 2) {
+ err = __dl_argv_handle_port(str, p_bus_name,
+ p_dev_name, p_port_index);
+ if (err)
+ return err;
+ *p_handle_bit = DL_OPT_HANDLEP;
+ } else if (slash_count == 0) {
+ err = __dl_argv_handle_port_ifname(dl, str, p_bus_name,
+ p_dev_name, p_port_index);
+ if (err)
+ return err;
+ *p_handle_bit = DL_OPT_HANDLEP;
+ } else {
+ pr_err("Wrong port identification string format.\n");
+ pr_err("Expected \"bus_name/dev_name\" or \"bus_name/dev_name/port_index\" or \"netdev_ifname\".\n");
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int __dl_argv_handle_region(char *str, char **p_bus_name,
+ char **p_dev_name, char **p_region)
+{
+ char *handlestr;
+ int err;
+
+ err = str_split_by_char(str, &handlestr, p_region, '/');
+ if (err) {
+ pr_err("Region identification \"%s\" is invalid\n", str);
+ return err;
+ }
+ err = str_split_by_char(handlestr, p_bus_name, p_dev_name, '/');
+ if (err) {
+ pr_err("Region identification \"%s\" is invalid\n", str);
+ return err;
+ }
+ return 0;
+}
+
+static int dl_argv_handle_region(struct dl *dl, char **p_bus_name,
+ char **p_dev_name, char **p_region)
+{
+ char *str = dl_argv_next(dl);
+ unsigned int slash_count;
+
+ if (!str) {
+ pr_err("Expected \"bus_name/dev_name/region\" identification.\n");
+ return -EINVAL;
+ }
+
+ slash_count = get_str_char_count(str, '/');
+ if (slash_count != 2) {
+ pr_err("Wrong region identification string format.\n");
+ pr_err("Expected \"bus_name/dev_name/region\" identification.\n"".\n");
+ return -EINVAL;
+ }
+
+ return __dl_argv_handle_region(str, p_bus_name, p_dev_name, p_region);
+}
+
+static int dl_argv_uint64_t(struct dl *dl, uint64_t *p_val)
+{
+ char *str = dl_argv_next(dl);
+ int err;
+
+ if (!str) {
+ pr_err("Unsigned number argument expected\n");
+ return -EINVAL;
+ }
+
+ err = strtouint64_t(str, p_val);
+ if (err) {
+ pr_err("\"%s\" is not a number or not within range\n", str);
+ return err;
+ }
+ return 0;
+}
+
+static int dl_argv_uint32_t(struct dl *dl, uint32_t *p_val)
+{
+ char *str = dl_argv_next(dl);
+ int err;
+
+ if (!str) {
+ pr_err("Unsigned number argument expected\n");
+ return -EINVAL;
+ }
+
+ err = strtouint32_t(str, p_val);
+ if (err) {
+ pr_err("\"%s\" is not a number or not within range\n", str);
+ return err;
+ }
+ return 0;
+}
+
+static int dl_argv_uint16_t(struct dl *dl, uint16_t *p_val)
+{
+ char *str = dl_argv_next(dl);
+ int err;
+
+ if (!str) {
+ pr_err("Unsigned number argument expected\n");
+ return -EINVAL;
+ }
+
+ err = strtouint16_t(str, p_val);
+ if (err) {
+ pr_err("\"%s\" is not a number or not within range\n", str);
+ return err;
+ }
+ return 0;
+}
+
+static int dl_argv_bool(struct dl *dl, bool *p_val)
+{
+ char *str = dl_argv_next(dl);
+ int err;
+
+ if (!str) {
+ pr_err("Boolean argument expected\n");
+ return -EINVAL;
+ }
+
+ err = strtobool(str, p_val);
+ if (err) {
+ pr_err("\"%s\" is not a valid boolean value\n", str);
+ return err;
+ }
+ return 0;
+}
+
+static int dl_argv_str(struct dl *dl, const char **p_str)
+{
+ const char *str = dl_argv_next(dl);
+
+ if (!str) {
+ pr_err("String parameter expected\n");
+ return -EINVAL;
+ }
+ *p_str = str;
+ return 0;
+}
+
+static int port_type_get(const char *typestr, enum devlink_port_type *p_type)
+{
+ if (strcmp(typestr, "auto") == 0) {
+ *p_type = DEVLINK_PORT_TYPE_AUTO;
+ } else if (strcmp(typestr, "eth") == 0) {
+ *p_type = DEVLINK_PORT_TYPE_ETH;
+ } else if (strcmp(typestr, "ib") == 0) {
+ *p_type = DEVLINK_PORT_TYPE_IB;
+ } else {
+ pr_err("Unknown port type \"%s\"\n", typestr);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int pool_type_get(const char *typestr, enum devlink_sb_pool_type *p_type)
+{
+ if (strcmp(typestr, "ingress") == 0) {
+ *p_type = DEVLINK_SB_POOL_TYPE_INGRESS;
+ } else if (strcmp(typestr, "egress") == 0) {
+ *p_type = DEVLINK_SB_POOL_TYPE_EGRESS;
+ } else {
+ pr_err("Unknown pool type \"%s\"\n", typestr);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int threshold_type_get(const char *typestr,
+ enum devlink_sb_threshold_type *p_type)
+{
+ if (strcmp(typestr, "static") == 0) {
+ *p_type = DEVLINK_SB_THRESHOLD_TYPE_STATIC;
+ } else if (strcmp(typestr, "dynamic") == 0) {
+ *p_type = DEVLINK_SB_THRESHOLD_TYPE_DYNAMIC;
+ } else {
+ pr_err("Unknown threshold type \"%s\"\n", typestr);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int eswitch_mode_get(const char *typestr,
+ enum devlink_eswitch_mode *p_mode)
+{
+ if (strcmp(typestr, ESWITCH_MODE_LEGACY) == 0) {
+ *p_mode = DEVLINK_ESWITCH_MODE_LEGACY;
+ } else if (strcmp(typestr, ESWITCH_MODE_SWITCHDEV) == 0) {
+ *p_mode = DEVLINK_ESWITCH_MODE_SWITCHDEV;
+ } else {
+ pr_err("Unknown eswitch mode \"%s\"\n", typestr);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int eswitch_inline_mode_get(const char *typestr,
+ enum devlink_eswitch_inline_mode *p_mode)
+{
+ if (strcmp(typestr, ESWITCH_INLINE_MODE_NONE) == 0) {
+ *p_mode = DEVLINK_ESWITCH_INLINE_MODE_NONE;
+ } else if (strcmp(typestr, ESWITCH_INLINE_MODE_LINK) == 0) {
+ *p_mode = DEVLINK_ESWITCH_INLINE_MODE_LINK;
+ } else if (strcmp(typestr, ESWITCH_INLINE_MODE_NETWORK) == 0) {
+ *p_mode = DEVLINK_ESWITCH_INLINE_MODE_NETWORK;
+ } else if (strcmp(typestr, ESWITCH_INLINE_MODE_TRANSPORT) == 0) {
+ *p_mode = DEVLINK_ESWITCH_INLINE_MODE_TRANSPORT;
+ } else {
+ pr_err("Unknown eswitch inline mode \"%s\"\n", typestr);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int
+eswitch_encap_mode_get(const char *typestr,
+ enum devlink_eswitch_encap_mode *p_encap_mode)
+{
+ /* The initial implementation incorrectly accepted "enable"/"disable".
+ * Carry it to maintain backward compatibility.
+ */
+ if (strcmp(typestr, "disable") == 0 ||
+ strcmp(typestr, ESWITCH_ENCAP_MODE_NONE) == 0) {
+ *p_encap_mode = DEVLINK_ESWITCH_ENCAP_MODE_NONE;
+ } else if (strcmp(typestr, "enable") == 0 ||
+ strcmp(typestr, ESWITCH_ENCAP_MODE_BASIC) == 0) {
+ *p_encap_mode = DEVLINK_ESWITCH_ENCAP_MODE_BASIC;
+ } else {
+ pr_err("Unknown eswitch encap mode \"%s\"\n", typestr);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int flash_overwrite_section_get(const char *sectionstr, uint32_t *mask)
+{
+ if (strcmp(sectionstr, "settings") == 0) {
+ *mask |= DEVLINK_FLASH_OVERWRITE_SETTINGS;
+ } else if (strcmp(sectionstr, "identifiers") == 0) {
+ *mask |= DEVLINK_FLASH_OVERWRITE_IDENTIFIERS;
+ } else {
+ pr_err("Unknown overwrite section \"%s\"\n", sectionstr);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int param_cmode_get(const char *cmodestr,
+ enum devlink_param_cmode *cmode)
+{
+ if (strcmp(cmodestr, PARAM_CMODE_RUNTIME_STR) == 0) {
+ *cmode = DEVLINK_PARAM_CMODE_RUNTIME;
+ } else if (strcmp(cmodestr, PARAM_CMODE_DRIVERINIT_STR) == 0) {
+ *cmode = DEVLINK_PARAM_CMODE_DRIVERINIT;
+ } else if (strcmp(cmodestr, PARAM_CMODE_PERMANENT_STR) == 0) {
+ *cmode = DEVLINK_PARAM_CMODE_PERMANENT;
+ } else {
+ pr_err("Unknown configuration mode \"%s\"\n", cmodestr);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int trap_action_get(const char *actionstr,
+ enum devlink_trap_action *p_action)
+{
+ if (strcmp(actionstr, "drop") == 0) {
+ *p_action = DEVLINK_TRAP_ACTION_DROP;
+ } else if (strcmp(actionstr, "trap") == 0) {
+ *p_action = DEVLINK_TRAP_ACTION_TRAP;
+ } else if (strcmp(actionstr, "mirror") == 0) {
+ *p_action = DEVLINK_TRAP_ACTION_MIRROR;
+ } else {
+ pr_err("Unknown trap action \"%s\"\n", actionstr);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int hw_addr_parse(const char *addrstr, char *hw_addr, uint32_t *len)
+{
+ int alen;
+
+ alen = ll_addr_a2n(hw_addr, MAX_ADDR_LEN, addrstr);
+ if (alen < 0)
+ return -EINVAL;
+ *len = alen;
+ return 0;
+}
+
+static int reload_action_get(struct dl *dl, const char *actionstr,
+ enum devlink_reload_action *action)
+{
+ if (strcmp(actionstr, "driver_reinit") == 0) {
+ *action = DEVLINK_RELOAD_ACTION_DRIVER_REINIT;
+ } else if (strcmp(actionstr, "fw_activate") == 0) {
+ *action = DEVLINK_RELOAD_ACTION_FW_ACTIVATE;
+ } else {
+ pr_err("Unknown reload action \"%s\"\n", actionstr);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int reload_limit_get(struct dl *dl, const char *limitstr,
+ enum devlink_reload_limit *limit)
+{
+ if (strcmp(limitstr, "no_reset") == 0) {
+ *limit = DEVLINK_RELOAD_LIMIT_NO_RESET;
+ } else {
+ pr_err("Unknown reload limit \"%s\"\n", limitstr);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static struct str_num_map port_flavour_map[] = {
+ { .str = "physical", .num = DEVLINK_PORT_FLAVOUR_PHYSICAL },
+ { .str = "cpu", .num = DEVLINK_PORT_FLAVOUR_CPU },
+ { .str = "dsa", .num = DEVLINK_PORT_FLAVOUR_DSA },
+ { .str = "pcipf", .num = DEVLINK_PORT_FLAVOUR_PCI_PF },
+ { .str = "pcivf", .num = DEVLINK_PORT_FLAVOUR_PCI_VF },
+ { .str = "pcisf", .num = DEVLINK_PORT_FLAVOUR_PCI_SF },
+ { .str = "virtual", .num = DEVLINK_PORT_FLAVOUR_VIRTUAL},
+ { .str = NULL, },
+};
+
+static struct str_num_map port_fn_state_map[] = {
+ { .str = "inactive", .num = DEVLINK_PORT_FN_STATE_INACTIVE},
+ { .str = "active", .num = DEVLINK_PORT_FN_STATE_ACTIVE },
+ { .str = NULL, }
+};
+
+static struct str_num_map port_fn_opstate_map[] = {
+ { .str = "attached", .num = DEVLINK_PORT_FN_OPSTATE_ATTACHED},
+ { .str = "detached", .num = DEVLINK_PORT_FN_OPSTATE_DETACHED},
+ { .str = NULL, }
+};
+
+static int port_flavour_parse(const char *flavour, uint16_t *value)
+{
+ int num;
+
+ num = str_map_lookup_str(port_flavour_map, flavour);
+ if (num < 0) {
+ invarg("unknown flavour", flavour);
+ return num;
+ }
+ *value = num;
+ return 0;
+}
+
+static int port_fn_state_parse(const char *statestr, uint8_t *state)
+{
+ int num;
+
+ num = str_map_lookup_str(port_fn_state_map, statestr);
+ if (num < 0) {
+ invarg("unknown state", statestr);
+ return num;
+ }
+ *state = num;
+ return 0;
+}
+
+struct dl_args_metadata {
+ uint64_t o_flag;
+ char err_msg[DL_ARGS_REQUIRED_MAX_ERR_LEN];
+};
+
+static const struct dl_args_metadata dl_args_required[] = {
+ {DL_OPT_PORT_TYPE, "Port type not set."},
+ {DL_OPT_PORT_COUNT, "Port split count option expected."},
+ {DL_OPT_SB_POOL, "Pool index option expected."},
+ {DL_OPT_SB_SIZE, "Pool size option expected."},
+ {DL_OPT_SB_TYPE, "Pool type option expected."},
+ {DL_OPT_SB_THTYPE, "Pool threshold type option expected."},
+ {DL_OPT_SB_TH, "Threshold option expected."},
+ {DL_OPT_SB_TC, "TC index option expected."},
+ {DL_OPT_ESWITCH_MODE, "E-Switch mode option expected."},
+ {DL_OPT_ESWITCH_INLINE_MODE, "E-Switch inline-mode option expected."},
+ {DL_OPT_DPIPE_TABLE_NAME, "Dpipe table name expected."},
+ {DL_OPT_DPIPE_TABLE_COUNTERS, "Dpipe table counter state expected."},
+ {DL_OPT_ESWITCH_ENCAP_MODE, "E-Switch encapsulation option expected."},
+ {DL_OPT_RESOURCE_PATH, "Resource path expected."},
+ {DL_OPT_RESOURCE_SIZE, "Resource size expected."},
+ {DL_OPT_PARAM_NAME, "Parameter name expected."},
+ {DL_OPT_PARAM_VALUE, "Value to set expected."},
+ {DL_OPT_PARAM_CMODE, "Configuration mode expected."},
+ {DL_OPT_REGION_SNAPSHOT_ID, "Region snapshot id expected."},
+ {DL_OPT_REGION_ADDRESS, "Region address value expected."},
+ {DL_OPT_REGION_LENGTH, "Region length value expected."},
+ {DL_OPT_HEALTH_REPORTER_NAME, "Reporter's name is expected."},
+ {DL_OPT_TRAP_NAME, "Trap's name is expected."},
+ {DL_OPT_TRAP_GROUP_NAME, "Trap group's name is expected."},
+ {DL_OPT_PORT_FUNCTION_HW_ADDR, "Port function's hardware address is expected."},
+ {DL_OPT_PORT_FLAVOUR, "Port flavour is expected."},
+ {DL_OPT_PORT_PFNUMBER, "Port PCI PF number is expected."},
+};
+
+static int dl_args_finding_required_validate(uint64_t o_required,
+ uint64_t o_found)
+{
+ uint64_t o_flag;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(dl_args_required); i++) {
+ o_flag = dl_args_required[i].o_flag;
+ if ((o_required & o_flag) && !(o_found & o_flag)) {
+ pr_err("%s\n", dl_args_required[i].err_msg);
+ return -EINVAL;
+ }
+ }
+ if (o_required & ~o_found) {
+ pr_err("BUG: unknown argument required but not found\n");
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int dl_argv_parse(struct dl *dl, uint64_t o_required,
+ uint64_t o_optional)
+{
+ struct dl_opts *opts = &dl->opts;
+ uint64_t o_all = o_required | o_optional;
+ uint64_t o_found = 0;
+ int err;
+
+ if (o_required & DL_OPT_HANDLE && o_required & DL_OPT_HANDLEP) {
+ uint64_t handle_bit;
+
+ err = dl_argv_handle_both(dl, &opts->bus_name, &opts->dev_name,
+ &opts->port_index, &handle_bit);
+ if (err)
+ return err;
+ o_required &= ~(DL_OPT_HANDLE | DL_OPT_HANDLEP) | handle_bit;
+ o_found |= handle_bit;
+ } else if (o_required & DL_OPT_HANDLE) {
+ err = dl_argv_handle(dl, &opts->bus_name, &opts->dev_name);
+ if (err)
+ return err;
+ o_found |= DL_OPT_HANDLE;
+ } else if (o_required & DL_OPT_HANDLEP) {
+ err = dl_argv_handle_port(dl, &opts->bus_name, &opts->dev_name,
+ &opts->port_index);
+ if (err)
+ return err;
+ o_found |= DL_OPT_HANDLEP;
+ } else if (o_required & DL_OPT_HANDLE_REGION) {
+ err = dl_argv_handle_region(dl, &opts->bus_name,
+ &opts->dev_name,
+ &opts->region_name);
+ if (err)
+ return err;
+ o_found |= DL_OPT_HANDLE_REGION;
+ }
+
+ while (dl_argc(dl)) {
+ if (dl_argv_match(dl, "type") &&
+ (o_all & DL_OPT_PORT_TYPE)) {
+ const char *typestr;
+
+ dl_arg_inc(dl);
+ err = dl_argv_str(dl, &typestr);
+ if (err)
+ return err;
+ err = port_type_get(typestr, &opts->port_type);
+ if (err)
+ return err;
+ o_found |= DL_OPT_PORT_TYPE;
+ } else if (dl_argv_match(dl, "count") &&
+ (o_all & DL_OPT_PORT_COUNT)) {
+ dl_arg_inc(dl);
+ err = dl_argv_uint32_t(dl, &opts->port_count);
+ if (err)
+ return err;
+ o_found |= DL_OPT_PORT_COUNT;
+ } else if (dl_argv_match(dl, "sb") &&
+ (o_all & DL_OPT_SB)) {
+ dl_arg_inc(dl);
+ err = dl_argv_uint32_t(dl, &opts->sb_index);
+ if (err)
+ return err;
+ o_found |= DL_OPT_SB;
+ } else if (dl_argv_match(dl, "pool") &&
+ (o_all & DL_OPT_SB_POOL)) {
+ dl_arg_inc(dl);
+ err = dl_argv_uint16_t(dl, &opts->sb_pool_index);
+ if (err)
+ return err;
+ o_found |= DL_OPT_SB_POOL;
+ } else if (dl_argv_match(dl, "size") &&
+ (o_all & DL_OPT_SB_SIZE)) {
+ dl_arg_inc(dl);
+ err = dl_argv_uint32_t(dl, &opts->sb_pool_size);
+ if (err)
+ return err;
+ o_found |= DL_OPT_SB_SIZE;
+ } else if (dl_argv_match(dl, "type") &&
+ (o_all & DL_OPT_SB_TYPE)) {
+ const char *typestr;
+
+ dl_arg_inc(dl);
+ err = dl_argv_str(dl, &typestr);
+ if (err)
+ return err;
+ err = pool_type_get(typestr, &opts->sb_pool_type);
+ if (err)
+ return err;
+ o_found |= DL_OPT_SB_TYPE;
+ } else if (dl_argv_match(dl, "thtype") &&
+ (o_all & DL_OPT_SB_THTYPE)) {
+ const char *typestr;
+
+ dl_arg_inc(dl);
+ err = dl_argv_str(dl, &typestr);
+ if (err)
+ return err;
+ err = threshold_type_get(typestr,
+ &opts->sb_pool_thtype);
+ if (err)
+ return err;
+ o_found |= DL_OPT_SB_THTYPE;
+ } else if (dl_argv_match(dl, "th") &&
+ (o_all & DL_OPT_SB_TH)) {
+ dl_arg_inc(dl);
+ err = dl_argv_uint32_t(dl, &opts->sb_threshold);
+ if (err)
+ return err;
+ o_found |= DL_OPT_SB_TH;
+ } else if (dl_argv_match(dl, "tc") &&
+ (o_all & DL_OPT_SB_TC)) {
+ dl_arg_inc(dl);
+ err = dl_argv_uint16_t(dl, &opts->sb_tc_index);
+ if (err)
+ return err;
+ o_found |= DL_OPT_SB_TC;
+ } else if (dl_argv_match(dl, "mode") &&
+ (o_all & DL_OPT_ESWITCH_MODE)) {
+ const char *typestr;
+
+ dl_arg_inc(dl);
+ err = dl_argv_str(dl, &typestr);
+ if (err)
+ return err;
+ err = eswitch_mode_get(typestr, &opts->eswitch_mode);
+ if (err)
+ return err;
+ o_found |= DL_OPT_ESWITCH_MODE;
+ } else if (dl_argv_match(dl, "inline-mode") &&
+ (o_all & DL_OPT_ESWITCH_INLINE_MODE)) {
+ const char *typestr;
+
+ dl_arg_inc(dl);
+ err = dl_argv_str(dl, &typestr);
+ if (err)
+ return err;
+ err = eswitch_inline_mode_get(
+ typestr, &opts->eswitch_inline_mode);
+ if (err)
+ return err;
+ o_found |= DL_OPT_ESWITCH_INLINE_MODE;
+ } else if (dl_argv_match(dl, "name") &&
+ (o_all & DL_OPT_DPIPE_TABLE_NAME)) {
+ dl_arg_inc(dl);
+ err = dl_argv_str(dl, &opts->dpipe_table_name);
+ if (err)
+ return err;
+ o_found |= DL_OPT_DPIPE_TABLE_NAME;
+ } else if ((dl_argv_match(dl, "counters") ||
+ dl_argv_match(dl, "counters_enabled")) &&
+ (o_all & DL_OPT_DPIPE_TABLE_COUNTERS)) {
+ dl_arg_inc(dl);
+ err = dl_argv_bool(dl, &opts->dpipe_counters_enabled);
+ if (err)
+ return err;
+ o_found |= DL_OPT_DPIPE_TABLE_COUNTERS;
+ } else if ((dl_argv_match(dl, "encap") || /* Original incorrect implementation */
+ dl_argv_match(dl, "encap-mode")) &&
+ (o_all & DL_OPT_ESWITCH_ENCAP_MODE)) {
+ const char *typestr;
+
+ dl_arg_inc(dl);
+ err = dl_argv_str(dl, &typestr);
+ if (err)
+ return err;
+ err = eswitch_encap_mode_get(typestr,
+ &opts->eswitch_encap_mode);
+ if (err)
+ return err;
+ o_found |= DL_OPT_ESWITCH_ENCAP_MODE;
+ } else if (dl_argv_match(dl, "path") &&
+ (o_all & DL_OPT_RESOURCE_PATH)) {
+ dl_arg_inc(dl);
+ err = dl_argv_str(dl, &opts->resource_path);
+ if (err)
+ return err;
+ o_found |= DL_OPT_RESOURCE_PATH;
+ } else if (dl_argv_match(dl, "size") &&
+ (o_all & DL_OPT_RESOURCE_SIZE)) {
+ dl_arg_inc(dl);
+ err = dl_argv_uint64_t(dl, &opts->resource_size);
+ if (err)
+ return err;
+ o_found |= DL_OPT_RESOURCE_SIZE;
+ } else if (dl_argv_match(dl, "name") &&
+ (o_all & DL_OPT_PARAM_NAME)) {
+ dl_arg_inc(dl);
+ err = dl_argv_str(dl, &opts->param_name);
+ if (err)
+ return err;
+ o_found |= DL_OPT_PARAM_NAME;
+ } else if (dl_argv_match(dl, "value") &&
+ (o_all & DL_OPT_PARAM_VALUE)) {
+ dl_arg_inc(dl);
+ err = dl_argv_str(dl, &opts->param_value);
+ if (err)
+ return err;
+ o_found |= DL_OPT_PARAM_VALUE;
+ } else if (dl_argv_match(dl, "cmode") &&
+ (o_all & DL_OPT_PARAM_CMODE)) {
+ const char *cmodestr;
+
+ dl_arg_inc(dl);
+ err = dl_argv_str(dl, &cmodestr);
+ if (err)
+ return err;
+ err = param_cmode_get(cmodestr, &opts->cmode);
+ if (err)
+ return err;
+ o_found |= DL_OPT_PARAM_CMODE;
+ } else if (dl_argv_match(dl, "snapshot") &&
+ (o_all & DL_OPT_REGION_SNAPSHOT_ID)) {
+ dl_arg_inc(dl);
+ err = dl_argv_uint32_t(dl, &opts->region_snapshot_id);
+ if (err)
+ return err;
+ o_found |= DL_OPT_REGION_SNAPSHOT_ID;
+ } else if (dl_argv_match(dl, "address") &&
+ (o_all & DL_OPT_REGION_ADDRESS)) {
+ dl_arg_inc(dl);
+ err = dl_argv_uint64_t(dl, &opts->region_address);
+ if (err)
+ return err;
+ o_found |= DL_OPT_REGION_ADDRESS;
+ } else if (dl_argv_match(dl, "length") &&
+ (o_all & DL_OPT_REGION_LENGTH)) {
+ dl_arg_inc(dl);
+ err = dl_argv_uint64_t(dl, &opts->region_length);
+ if (err)
+ return err;
+ o_found |= DL_OPT_REGION_LENGTH;
+ } else if (dl_argv_match(dl, "file") &&
+ (o_all & DL_OPT_FLASH_FILE_NAME)) {
+ dl_arg_inc(dl);
+ err = dl_argv_str(dl, &opts->flash_file_name);
+ if (err)
+ return err;
+ o_found |= DL_OPT_FLASH_FILE_NAME;
+ } else if (dl_argv_match(dl, "component") &&
+ (o_all & DL_OPT_FLASH_COMPONENT)) {
+ dl_arg_inc(dl);
+ err = dl_argv_str(dl, &opts->flash_component);
+ if (err)
+ return err;
+ o_found |= DL_OPT_FLASH_COMPONENT;
+
+ } else if (dl_argv_match(dl, "overwrite") &&
+ (o_all & DL_OPT_FLASH_OVERWRITE)) {
+ const char *sectionstr;
+
+ dl_arg_inc(dl);
+ err = dl_argv_str(dl, &sectionstr);
+ if(err)
+ return err;
+ err = flash_overwrite_section_get(sectionstr,
+ &opts->overwrite_mask);
+ if (err)
+ return err;
+ o_found |= DL_OPT_FLASH_OVERWRITE;
+
+ } else if (dl_argv_match(dl, "reporter") &&
+ (o_all & DL_OPT_HEALTH_REPORTER_NAME)) {
+ dl_arg_inc(dl);
+ err = dl_argv_str(dl, &opts->reporter_name);
+ if (err)
+ return err;
+ o_found |= DL_OPT_HEALTH_REPORTER_NAME;
+ } else if (dl_argv_match(dl, "grace_period") &&
+ (o_all & DL_OPT_HEALTH_REPORTER_GRACEFUL_PERIOD)) {
+ dl_arg_inc(dl);
+ err = dl_argv_uint64_t(dl,
+ &opts->reporter_graceful_period);
+ if (err)
+ return err;
+ o_found |= DL_OPT_HEALTH_REPORTER_GRACEFUL_PERIOD;
+ } else if (dl_argv_match(dl, "auto_recover") &&
+ (o_all & DL_OPT_HEALTH_REPORTER_AUTO_RECOVER)) {
+ dl_arg_inc(dl);
+ err = dl_argv_bool(dl, &opts->reporter_auto_recover);
+ if (err)
+ return err;
+ o_found |= DL_OPT_HEALTH_REPORTER_AUTO_RECOVER;
+ } else if (dl_argv_match(dl, "auto_dump") &&
+ (o_all & DL_OPT_HEALTH_REPORTER_AUTO_DUMP)) {
+ dl_arg_inc(dl);
+ err = dl_argv_bool(dl, &opts->reporter_auto_dump);
+ if (err)
+ return err;
+ o_found |= DL_OPT_HEALTH_REPORTER_AUTO_DUMP;
+ } else if (dl_argv_match(dl, "trap") &&
+ (o_all & DL_OPT_TRAP_NAME)) {
+ dl_arg_inc(dl);
+ err = dl_argv_str(dl, &opts->trap_name);
+ if (err)
+ return err;
+ o_found |= DL_OPT_TRAP_NAME;
+ } else if (dl_argv_match(dl, "group") &&
+ (o_all & DL_OPT_TRAP_GROUP_NAME)) {
+ dl_arg_inc(dl);
+ err = dl_argv_str(dl, &opts->trap_group_name);
+ if (err)
+ return err;
+ o_found |= DL_OPT_TRAP_GROUP_NAME;
+ } else if (dl_argv_match(dl, "action") &&
+ (o_all & DL_OPT_TRAP_ACTION)) {
+ const char *actionstr;
+
+ dl_arg_inc(dl);
+ err = dl_argv_str(dl, &actionstr);
+ if (err)
+ return err;
+ err = trap_action_get(actionstr, &opts->trap_action);
+ if (err)
+ return err;
+ o_found |= DL_OPT_TRAP_ACTION;
+ } else if (dl_argv_match(dl, "netns") &&
+ (o_all & DL_OPT_NETNS)) {
+ const char *netns_str;
+
+ dl_arg_inc(dl);
+ err = dl_argv_str(dl, &netns_str);
+ if (err)
+ return err;
+ opts->netns = netns_get_fd(netns_str);
+ if ((int)opts->netns < 0) {
+ dl_arg_dec(dl);
+ err = dl_argv_uint32_t(dl, &opts->netns);
+ if (err)
+ return err;
+ opts->netns_is_pid = true;
+ }
+ o_found |= DL_OPT_NETNS;
+ } else if (dl_argv_match(dl, "action") &&
+ (o_all & DL_OPT_RELOAD_ACTION)) {
+ const char *actionstr;
+
+ dl_arg_inc(dl);
+ err = dl_argv_str(dl, &actionstr);
+ if (err)
+ return err;
+ err = reload_action_get(dl, actionstr, &opts->reload_action);
+ if (err)
+ return err;
+ o_found |= DL_OPT_RELOAD_ACTION;
+ } else if (dl_argv_match(dl, "limit") &&
+ (o_all & DL_OPT_RELOAD_LIMIT)) {
+ const char *limitstr;
+
+ dl_arg_inc(dl);
+ err = dl_argv_str(dl, &limitstr);
+ if (err)
+ return err;
+ err = reload_limit_get(dl, limitstr, &opts->reload_limit);
+ if (err)
+ return err;
+ o_found |= DL_OPT_RELOAD_LIMIT;
+ } else if (dl_argv_match(dl, "policer") &&
+ (o_all & DL_OPT_TRAP_POLICER_ID)) {
+ dl_arg_inc(dl);
+ err = dl_argv_uint32_t(dl, &opts->trap_policer_id);
+ if (err)
+ return err;
+ o_found |= DL_OPT_TRAP_POLICER_ID;
+ } else if (dl_argv_match(dl, "nopolicer") &&
+ (o_all & DL_OPT_TRAP_POLICER_ID)) {
+ dl_arg_inc(dl);
+ opts->trap_policer_id = 0;
+ o_found |= DL_OPT_TRAP_POLICER_ID;
+ } else if (dl_argv_match(dl, "rate") &&
+ (o_all & DL_OPT_TRAP_POLICER_RATE)) {
+ dl_arg_inc(dl);
+ err = dl_argv_uint64_t(dl, &opts->trap_policer_rate);
+ if (err)
+ return err;
+ o_found |= DL_OPT_TRAP_POLICER_RATE;
+ } else if (dl_argv_match(dl, "burst") &&
+ (o_all & DL_OPT_TRAP_POLICER_BURST)) {
+ dl_arg_inc(dl);
+ err = dl_argv_uint64_t(dl, &opts->trap_policer_burst);
+ if (err)
+ return err;
+ o_found |= DL_OPT_TRAP_POLICER_BURST;
+ } else if (dl_argv_match(dl, "hw_addr") &&
+ (o_all & DL_OPT_PORT_FUNCTION_HW_ADDR)) {
+ const char *addrstr;
+
+ dl_arg_inc(dl);
+ err = dl_argv_str(dl, &addrstr);
+ if (err)
+ return err;
+ err = hw_addr_parse(addrstr, opts->port_function_hw_addr,
+ &opts->port_function_hw_addr_len);
+ if (err)
+ return err;
+ o_found |= DL_OPT_PORT_FUNCTION_HW_ADDR;
+ } else if (dl_argv_match(dl, "state") &&
+ (o_all & DL_OPT_PORT_FUNCTION_STATE)) {
+ const char *statestr;
+
+ dl_arg_inc(dl);
+ err = dl_argv_str(dl, &statestr);
+ if (err)
+ return err;
+ err = port_fn_state_parse(statestr, &opts->port_fn_state);
+ if (err)
+ return err;
+
+ o_found |= DL_OPT_PORT_FUNCTION_STATE;
+ } else if (dl_argv_match(dl, "flavour") && (o_all & DL_OPT_PORT_FLAVOUR)) {
+ const char *flavourstr;
+
+ dl_arg_inc(dl);
+ err = dl_argv_str(dl, &flavourstr);
+ if (err)
+ return err;
+ err = port_flavour_parse(flavourstr, &opts->port_flavour);
+ if (err)
+ return err;
+ o_found |= DL_OPT_PORT_FLAVOUR;
+ } else if (dl_argv_match(dl, "pfnum") && (o_all & DL_OPT_PORT_PFNUMBER)) {
+ dl_arg_inc(dl);
+ err = dl_argv_uint16_t(dl, &opts->port_pfnumber);
+ if (err)
+ return err;
+ o_found |= DL_OPT_PORT_PFNUMBER;
+ } else if (dl_argv_match(dl, "sfnum") && (o_all & DL_OPT_PORT_SFNUMBER)) {
+ dl_arg_inc(dl);
+ err = dl_argv_uint32_t(dl, &opts->port_sfnumber);
+ if (err)
+ return err;
+ o_found |= DL_OPT_PORT_SFNUMBER;
+ } else {
+ pr_err("Unknown option \"%s\"\n", dl_argv(dl));
+ return -EINVAL;
+ }
+ }
+
+ opts->present = o_found;
+
+ if ((o_optional & DL_OPT_SB) && !(o_found & DL_OPT_SB)) {
+ opts->sb_index = 0;
+ opts->present |= DL_OPT_SB;
+ }
+
+ return dl_args_finding_required_validate(o_required, o_found);
+}
+
+static void
+dl_function_attr_put(struct nlmsghdr *nlh, const struct dl_opts *opts)
+{
+ struct nlattr *nest;
+
+ nest = mnl_attr_nest_start(nlh, DEVLINK_ATTR_PORT_FUNCTION);
+
+ if (opts->present & DL_OPT_PORT_FUNCTION_HW_ADDR)
+ mnl_attr_put(nlh, DEVLINK_PORT_FUNCTION_ATTR_HW_ADDR,
+ opts->port_function_hw_addr_len,
+ opts->port_function_hw_addr);
+ if (opts->present & DL_OPT_PORT_FUNCTION_STATE)
+ mnl_attr_put_u8(nlh, DEVLINK_PORT_FN_ATTR_STATE,
+ opts->port_fn_state);
+ mnl_attr_nest_end(nlh, nest);
+}
+
+static void
+dl_flash_update_overwrite_put(struct nlmsghdr *nlh, const struct dl_opts *opts)
+{
+ struct nla_bitfield32 overwrite_mask;
+
+ overwrite_mask.selector = DEVLINK_SUPPORTED_FLASH_OVERWRITE_SECTIONS;
+ overwrite_mask.value = opts->overwrite_mask;
+
+ mnl_attr_put(nlh, DEVLINK_ATTR_FLASH_UPDATE_OVERWRITE_MASK,
+ sizeof(overwrite_mask), &overwrite_mask);
+}
+
+static void
+dl_reload_limits_put(struct nlmsghdr *nlh, const struct dl_opts *opts)
+{
+ struct nla_bitfield32 limits;
+
+ limits.selector = DEVLINK_RELOAD_LIMITS_VALID_MASK;
+ limits.value = BIT(opts->reload_limit);
+ mnl_attr_put(nlh, DEVLINK_ATTR_RELOAD_LIMITS, sizeof(limits), &limits);
+}
+
+static void dl_opts_put(struct nlmsghdr *nlh, struct dl *dl)
+{
+ struct dl_opts *opts = &dl->opts;
+
+ if (opts->present & DL_OPT_HANDLE) {
+ mnl_attr_put_strz(nlh, DEVLINK_ATTR_BUS_NAME, opts->bus_name);
+ mnl_attr_put_strz(nlh, DEVLINK_ATTR_DEV_NAME, opts->dev_name);
+ } else if (opts->present & DL_OPT_HANDLEP) {
+ mnl_attr_put_strz(nlh, DEVLINK_ATTR_BUS_NAME, opts->bus_name);
+ mnl_attr_put_strz(nlh, DEVLINK_ATTR_DEV_NAME, opts->dev_name);
+ mnl_attr_put_u32(nlh, DEVLINK_ATTR_PORT_INDEX,
+ opts->port_index);
+ } else if (opts->present & DL_OPT_HANDLE_REGION) {
+ mnl_attr_put_strz(nlh, DEVLINK_ATTR_BUS_NAME, opts->bus_name);
+ mnl_attr_put_strz(nlh, DEVLINK_ATTR_DEV_NAME, opts->dev_name);
+ mnl_attr_put_strz(nlh, DEVLINK_ATTR_REGION_NAME,
+ opts->region_name);
+ }
+ if (opts->present & DL_OPT_PORT_TYPE)
+ mnl_attr_put_u16(nlh, DEVLINK_ATTR_PORT_TYPE,
+ opts->port_type);
+ if (opts->present & DL_OPT_PORT_COUNT)
+ mnl_attr_put_u32(nlh, DEVLINK_ATTR_PORT_SPLIT_COUNT,
+ opts->port_count);
+ if (opts->present & DL_OPT_SB)
+ mnl_attr_put_u32(nlh, DEVLINK_ATTR_SB_INDEX,
+ opts->sb_index);
+ if (opts->present & DL_OPT_SB_POOL)
+ mnl_attr_put_u16(nlh, DEVLINK_ATTR_SB_POOL_INDEX,
+ opts->sb_pool_index);
+ if (opts->present & DL_OPT_SB_SIZE)
+ mnl_attr_put_u32(nlh, DEVLINK_ATTR_SB_POOL_SIZE,
+ opts->sb_pool_size);
+ if (opts->present & DL_OPT_SB_TYPE)
+ mnl_attr_put_u8(nlh, DEVLINK_ATTR_SB_POOL_TYPE,
+ opts->sb_pool_type);
+ if (opts->present & DL_OPT_SB_THTYPE)
+ mnl_attr_put_u8(nlh, DEVLINK_ATTR_SB_POOL_THRESHOLD_TYPE,
+ opts->sb_pool_thtype);
+ if (opts->present & DL_OPT_SB_TH)
+ mnl_attr_put_u32(nlh, DEVLINK_ATTR_SB_THRESHOLD,
+ opts->sb_threshold);
+ if (opts->present & DL_OPT_SB_TC)
+ mnl_attr_put_u16(nlh, DEVLINK_ATTR_SB_TC_INDEX,
+ opts->sb_tc_index);
+ if (opts->present & DL_OPT_ESWITCH_MODE)
+ mnl_attr_put_u16(nlh, DEVLINK_ATTR_ESWITCH_MODE,
+ opts->eswitch_mode);
+ if (opts->present & DL_OPT_ESWITCH_INLINE_MODE)
+ mnl_attr_put_u8(nlh, DEVLINK_ATTR_ESWITCH_INLINE_MODE,
+ opts->eswitch_inline_mode);
+ if (opts->present & DL_OPT_DPIPE_TABLE_NAME)
+ mnl_attr_put_strz(nlh, DEVLINK_ATTR_DPIPE_TABLE_NAME,
+ opts->dpipe_table_name);
+ if (opts->present & DL_OPT_DPIPE_TABLE_COUNTERS)
+ mnl_attr_put_u8(nlh, DEVLINK_ATTR_DPIPE_TABLE_COUNTERS_ENABLED,
+ opts->dpipe_counters_enabled);
+ if (opts->present & DL_OPT_ESWITCH_ENCAP_MODE)
+ mnl_attr_put_u8(nlh, DEVLINK_ATTR_ESWITCH_ENCAP_MODE,
+ opts->eswitch_encap_mode);
+ if ((opts->present & DL_OPT_RESOURCE_PATH) && opts->resource_id_valid)
+ mnl_attr_put_u64(nlh, DEVLINK_ATTR_RESOURCE_ID,
+ opts->resource_id);
+ if (opts->present & DL_OPT_RESOURCE_SIZE)
+ mnl_attr_put_u64(nlh, DEVLINK_ATTR_RESOURCE_SIZE,
+ opts->resource_size);
+ if (opts->present & DL_OPT_PARAM_NAME)
+ mnl_attr_put_strz(nlh, DEVLINK_ATTR_PARAM_NAME,
+ opts->param_name);
+ if (opts->present & DL_OPT_PARAM_CMODE)
+ mnl_attr_put_u8(nlh, DEVLINK_ATTR_PARAM_VALUE_CMODE,
+ opts->cmode);
+ if (opts->present & DL_OPT_REGION_SNAPSHOT_ID)
+ mnl_attr_put_u32(nlh, DEVLINK_ATTR_REGION_SNAPSHOT_ID,
+ opts->region_snapshot_id);
+ if (opts->present & DL_OPT_REGION_ADDRESS)
+ mnl_attr_put_u64(nlh, DEVLINK_ATTR_REGION_CHUNK_ADDR,
+ opts->region_address);
+ if (opts->present & DL_OPT_REGION_LENGTH)
+ mnl_attr_put_u64(nlh, DEVLINK_ATTR_REGION_CHUNK_LEN,
+ opts->region_length);
+ if (opts->present & DL_OPT_FLASH_FILE_NAME)
+ mnl_attr_put_strz(nlh, DEVLINK_ATTR_FLASH_UPDATE_FILE_NAME,
+ opts->flash_file_name);
+ if (opts->present & DL_OPT_FLASH_COMPONENT)
+ mnl_attr_put_strz(nlh, DEVLINK_ATTR_FLASH_UPDATE_COMPONENT,
+ opts->flash_component);
+ if (opts->present & DL_OPT_FLASH_OVERWRITE)
+ dl_flash_update_overwrite_put(nlh, opts);
+ if (opts->present & DL_OPT_HEALTH_REPORTER_NAME)
+ mnl_attr_put_strz(nlh, DEVLINK_ATTR_HEALTH_REPORTER_NAME,
+ opts->reporter_name);
+ if (opts->present & DL_OPT_HEALTH_REPORTER_GRACEFUL_PERIOD)
+ mnl_attr_put_u64(nlh,
+ DEVLINK_ATTR_HEALTH_REPORTER_GRACEFUL_PERIOD,
+ opts->reporter_graceful_period);
+ if (opts->present & DL_OPT_HEALTH_REPORTER_AUTO_RECOVER)
+ mnl_attr_put_u8(nlh, DEVLINK_ATTR_HEALTH_REPORTER_AUTO_RECOVER,
+ opts->reporter_auto_recover);
+ if (opts->present & DL_OPT_HEALTH_REPORTER_AUTO_DUMP)
+ mnl_attr_put_u8(nlh, DEVLINK_ATTR_HEALTH_REPORTER_AUTO_DUMP,
+ opts->reporter_auto_dump);
+ if (opts->present & DL_OPT_TRAP_NAME)
+ mnl_attr_put_strz(nlh, DEVLINK_ATTR_TRAP_NAME,
+ opts->trap_name);
+ if (opts->present & DL_OPT_TRAP_GROUP_NAME)
+ mnl_attr_put_strz(nlh, DEVLINK_ATTR_TRAP_GROUP_NAME,
+ opts->trap_group_name);
+ if (opts->present & DL_OPT_TRAP_ACTION)
+ mnl_attr_put_u8(nlh, DEVLINK_ATTR_TRAP_ACTION,
+ opts->trap_action);
+ if (opts->present & DL_OPT_NETNS)
+ mnl_attr_put_u32(nlh,
+ opts->netns_is_pid ? DEVLINK_ATTR_NETNS_PID :
+ DEVLINK_ATTR_NETNS_FD,
+ opts->netns);
+ if (opts->present & DL_OPT_RELOAD_ACTION)
+ mnl_attr_put_u8(nlh, DEVLINK_ATTR_RELOAD_ACTION,
+ opts->reload_action);
+ if (opts->present & DL_OPT_RELOAD_LIMIT)
+ dl_reload_limits_put(nlh, opts);
+ if (opts->present & DL_OPT_TRAP_POLICER_ID)
+ mnl_attr_put_u32(nlh, DEVLINK_ATTR_TRAP_POLICER_ID,
+ opts->trap_policer_id);
+ if (opts->present & DL_OPT_TRAP_POLICER_RATE)
+ mnl_attr_put_u64(nlh, DEVLINK_ATTR_TRAP_POLICER_RATE,
+ opts->trap_policer_rate);
+ if (opts->present & DL_OPT_TRAP_POLICER_BURST)
+ mnl_attr_put_u64(nlh, DEVLINK_ATTR_TRAP_POLICER_BURST,
+ opts->trap_policer_burst);
+ if (opts->present & (DL_OPT_PORT_FUNCTION_HW_ADDR | DL_OPT_PORT_FUNCTION_STATE))
+ dl_function_attr_put(nlh, opts);
+ if (opts->present & DL_OPT_PORT_FLAVOUR)
+ mnl_attr_put_u16(nlh, DEVLINK_ATTR_PORT_FLAVOUR, opts->port_flavour);
+ if (opts->present & DL_OPT_PORT_PFNUMBER)
+ mnl_attr_put_u16(nlh, DEVLINK_ATTR_PORT_PCI_PF_NUMBER, opts->port_pfnumber);
+ if (opts->present & DL_OPT_PORT_SFNUMBER)
+ mnl_attr_put_u32(nlh, DEVLINK_ATTR_PORT_PCI_SF_NUMBER, opts->port_sfnumber);
+}
+
+static int dl_argv_parse_put(struct nlmsghdr *nlh, struct dl *dl,
+ uint64_t o_required, uint64_t o_optional)
+{
+ int err;
+
+ err = dl_argv_parse(dl, o_required, o_optional);
+ if (err)
+ return err;
+ dl_opts_put(nlh, dl);
+ return 0;
+}
+
+static bool dl_dump_filter(struct dl *dl, struct nlattr **tb)
+{
+ struct dl_opts *opts = &dl->opts;
+ struct nlattr *attr_bus_name = tb[DEVLINK_ATTR_BUS_NAME];
+ struct nlattr *attr_dev_name = tb[DEVLINK_ATTR_DEV_NAME];
+ struct nlattr *attr_port_index = tb[DEVLINK_ATTR_PORT_INDEX];
+ struct nlattr *attr_sb_index = tb[DEVLINK_ATTR_SB_INDEX];
+
+ if (opts->present & DL_OPT_HANDLE &&
+ attr_bus_name && attr_dev_name) {
+ const char *bus_name = mnl_attr_get_str(attr_bus_name);
+ const char *dev_name = mnl_attr_get_str(attr_dev_name);
+
+ if (strcmp(bus_name, opts->bus_name) != 0 ||
+ strcmp(dev_name, opts->dev_name) != 0)
+ return false;
+ }
+ if (opts->present & DL_OPT_HANDLEP &&
+ attr_bus_name && attr_dev_name && attr_port_index) {
+ const char *bus_name = mnl_attr_get_str(attr_bus_name);
+ const char *dev_name = mnl_attr_get_str(attr_dev_name);
+ uint32_t port_index = mnl_attr_get_u32(attr_port_index);
+
+ if (strcmp(bus_name, opts->bus_name) != 0 ||
+ strcmp(dev_name, opts->dev_name) != 0 ||
+ port_index != opts->port_index)
+ return false;
+ }
+ if (opts->present & DL_OPT_SB && attr_sb_index) {
+ uint32_t sb_index = mnl_attr_get_u32(attr_sb_index);
+
+ if (sb_index != opts->sb_index)
+ return false;
+ }
+ return true;
+}
+
+static void cmd_dev_help(void)
+{
+ pr_err("Usage: devlink dev show [ DEV ]\n");
+ pr_err(" devlink dev eswitch set DEV [ mode { legacy | switchdev } ]\n");
+ pr_err(" [ inline-mode { none | link | network | transport } ]\n");
+ pr_err(" [ encap-mode { none | basic } ]\n");
+ pr_err(" devlink dev eswitch show DEV\n");
+ pr_err(" devlink dev param set DEV name PARAMETER value VALUE cmode { permanent | driverinit | runtime }\n");
+ pr_err(" devlink dev param show [DEV name PARAMETER]\n");
+ pr_err(" devlink dev reload DEV [ netns { PID | NAME | ID } ]\n");
+ pr_err(" [ action { driver_reinit | fw_activate } ] [ limit no_reset ]\n");
+ pr_err(" devlink dev info [ DEV ]\n");
+ pr_err(" devlink dev flash DEV file PATH [ component NAME ] [ overwrite SECTION ]\n");
+}
+
+static bool cmp_arr_last_handle(struct dl *dl, const char *bus_name,
+ const char *dev_name)
+{
+ if (!dl->arr_last.present)
+ return false;
+ return strcmp(dl->arr_last.bus_name, bus_name) == 0 &&
+ strcmp(dl->arr_last.dev_name, dev_name) == 0;
+}
+
+static void arr_last_handle_set(struct dl *dl, const char *bus_name,
+ const char *dev_name)
+{
+ dl->arr_last.present = true;
+ free(dl->arr_last.dev_name);
+ free(dl->arr_last.bus_name);
+ dl->arr_last.bus_name = strdup(bus_name);
+ dl->arr_last.dev_name = strdup(dev_name);
+}
+
+static bool should_arr_last_handle_start(struct dl *dl, const char *bus_name,
+ const char *dev_name)
+{
+ return !cmp_arr_last_handle(dl, bus_name, dev_name);
+}
+
+static bool should_arr_last_handle_end(struct dl *dl, const char *bus_name,
+ const char *dev_name)
+{
+ return dl->arr_last.present &&
+ !cmp_arr_last_handle(dl, bus_name, dev_name);
+}
+
+static void __pr_out_handle_start(struct dl *dl, struct nlattr **tb,
+ bool content, bool array)
+{
+ const char *bus_name = mnl_attr_get_str(tb[DEVLINK_ATTR_BUS_NAME]);
+ const char *dev_name = mnl_attr_get_str(tb[DEVLINK_ATTR_DEV_NAME]);
+ char buf[64];
+
+ sprintf(buf, "%s/%s", bus_name, dev_name);
+
+ if (dl->json_output) {
+ if (array) {
+ if (should_arr_last_handle_end(dl, bus_name, dev_name))
+ close_json_array(PRINT_JSON, NULL);
+ if (should_arr_last_handle_start(dl, bus_name,
+ dev_name)) {
+ open_json_array(PRINT_JSON, buf);
+ open_json_object(NULL);
+ arr_last_handle_set(dl, bus_name, dev_name);
+ } else {
+ open_json_object(NULL);
+ }
+ } else {
+ open_json_object(buf);
+ }
+ } else {
+ if (array) {
+ if (should_arr_last_handle_end(dl, bus_name, dev_name))
+ __pr_out_indent_dec();
+ if (should_arr_last_handle_start(dl, bus_name,
+ dev_name)) {
+ pr_out("%s%s", buf, content ? ":" : "");
+ __pr_out_newline();
+ __pr_out_indent_inc();
+ arr_last_handle_set(dl, bus_name, dev_name);
+ }
+ } else {
+ pr_out("%s%s", buf, content ? ":" : "");
+ }
+ }
+}
+
+static void pr_out_handle_start_arr(struct dl *dl, struct nlattr **tb)
+{
+ __pr_out_handle_start(dl, tb, true, true);
+}
+
+static void pr_out_handle_end(struct dl *dl)
+{
+ if (dl->json_output)
+ close_json_object();
+ else
+ __pr_out_newline();
+}
+
+static void pr_out_handle(struct dl *dl, struct nlattr **tb)
+{
+ __pr_out_handle_start(dl, tb, false, false);
+ pr_out_handle_end(dl);
+}
+
+static bool cmp_arr_last_port_handle(struct dl *dl, const char *bus_name,
+ const char *dev_name, uint32_t port_index)
+{
+ return cmp_arr_last_handle(dl, bus_name, dev_name) &&
+ dl->arr_last.port_index == port_index;
+}
+
+static void arr_last_port_handle_set(struct dl *dl, const char *bus_name,
+ const char *dev_name, uint32_t port_index)
+{
+ arr_last_handle_set(dl, bus_name, dev_name);
+ dl->arr_last.port_index = port_index;
+}
+
+static bool should_arr_last_port_handle_start(struct dl *dl,
+ const char *bus_name,
+ const char *dev_name,
+ uint32_t port_index)
+{
+ return !cmp_arr_last_port_handle(dl, bus_name, dev_name, port_index);
+}
+
+static bool should_arr_last_port_handle_end(struct dl *dl,
+ const char *bus_name,
+ const char *dev_name,
+ uint32_t port_index)
+{
+ return dl->arr_last.present &&
+ !cmp_arr_last_port_handle(dl, bus_name, dev_name, port_index);
+}
+
+static void __pr_out_port_handle_start(struct dl *dl, const char *bus_name,
+ const char *dev_name,
+ uint32_t port_index, bool try_nice,
+ bool array)
+{
+ static char buf[64];
+ char *ifname = NULL;
+
+ if (dl->no_nice_names || !try_nice ||
+ ifname_map_rev_lookup(dl, bus_name, dev_name,
+ port_index, &ifname) != 0)
+ sprintf(buf, "%s/%s/%d", bus_name, dev_name, port_index);
+ else
+ sprintf(buf, "%s", ifname);
+
+ if (dl->json_output) {
+ if (array) {
+ if (should_arr_last_port_handle_end(dl, bus_name,
+ dev_name,
+ port_index))
+ close_json_array(PRINT_JSON, NULL);
+ if (should_arr_last_port_handle_start(dl, bus_name,
+ dev_name,
+ port_index)) {
+ open_json_array(PRINT_JSON, buf);
+ open_json_object(NULL);
+ arr_last_port_handle_set(dl, bus_name, dev_name,
+ port_index);
+ } else {
+ open_json_object(NULL);
+ }
+ } else {
+ open_json_object(buf);
+ }
+ } else {
+ if (array) {
+ if (should_arr_last_port_handle_end(dl, bus_name, dev_name, port_index))
+ __pr_out_indent_dec();
+ if (should_arr_last_port_handle_start(dl, bus_name,
+ dev_name, port_index)) {
+ pr_out("%s:", buf);
+ __pr_out_newline();
+ __pr_out_indent_inc();
+ arr_last_port_handle_set(dl, bus_name, dev_name, port_index);
+ }
+ } else {
+ pr_out("%s:", buf);
+ }
+ }
+}
+
+static void pr_out_port_handle_start(struct dl *dl, struct nlattr **tb, bool try_nice)
+{
+ const char *bus_name;
+ const char *dev_name;
+ uint32_t port_index;
+
+ bus_name = mnl_attr_get_str(tb[DEVLINK_ATTR_BUS_NAME]);
+ dev_name = mnl_attr_get_str(tb[DEVLINK_ATTR_DEV_NAME]);
+ port_index = mnl_attr_get_u32(tb[DEVLINK_ATTR_PORT_INDEX]);
+ __pr_out_port_handle_start(dl, bus_name, dev_name, port_index, try_nice, false);
+}
+
+static void pr_out_port_handle_start_arr(struct dl *dl, struct nlattr **tb, bool try_nice)
+{
+ const char *bus_name;
+ const char *dev_name;
+ uint32_t port_index;
+
+ bus_name = mnl_attr_get_str(tb[DEVLINK_ATTR_BUS_NAME]);
+ dev_name = mnl_attr_get_str(tb[DEVLINK_ATTR_DEV_NAME]);
+ port_index = mnl_attr_get_u32(tb[DEVLINK_ATTR_PORT_INDEX]);
+ __pr_out_port_handle_start(dl, bus_name, dev_name, port_index, try_nice, true);
+}
+
+static void pr_out_port_handle_end(struct dl *dl)
+{
+ if (dl->json_output)
+ close_json_object();
+ else
+ pr_out("\n");
+}
+
+static void pr_out_region_chunk_start(struct dl *dl, uint64_t addr)
+{
+ if (dl->json_output) {
+ print_uint(PRINT_JSON, "address", NULL, addr);
+ open_json_array(PRINT_JSON, "data");
+ }
+}
+
+static void pr_out_region_chunk_end(struct dl *dl)
+{
+ if (dl->json_output)
+ close_json_array(PRINT_JSON, NULL);
+}
+
+static void pr_out_region_chunk(struct dl *dl, uint8_t *data, uint32_t len,
+ uint64_t addr)
+{
+ static uint64_t align_val;
+ uint32_t i = 0;
+
+ pr_out_region_chunk_start(dl, addr);
+ while (i < len) {
+ if (!dl->json_output)
+ if (!(align_val % 16))
+ pr_out("%s%016"PRIx64" ",
+ align_val ? "\n" : "",
+ addr);
+
+ align_val++;
+
+ if (dl->json_output)
+ print_int(PRINT_JSON, NULL, NULL, data[i]);
+ else
+ pr_out("%02x ", data[i]);
+
+ addr++;
+ i++;
+ }
+ pr_out_region_chunk_end(dl);
+}
+
+static void pr_out_stats(struct dl *dl, struct nlattr *nla_stats)
+{
+ struct nlattr *tb[DEVLINK_ATTR_STATS_MAX + 1] = {};
+ int err;
+
+ if (!dl->stats)
+ return;
+
+ err = mnl_attr_parse_nested(nla_stats, attr_stats_cb, tb);
+ if (err != MNL_CB_OK)
+ return;
+
+ pr_out_object_start(dl, "stats");
+ pr_out_object_start(dl, "rx");
+ if (tb[DEVLINK_ATTR_STATS_RX_BYTES])
+ pr_out_u64(dl, "bytes",
+ mnl_attr_get_u64(tb[DEVLINK_ATTR_STATS_RX_BYTES]));
+ if (tb[DEVLINK_ATTR_STATS_RX_PACKETS])
+ pr_out_u64(dl, "packets",
+ mnl_attr_get_u64(tb[DEVLINK_ATTR_STATS_RX_PACKETS]));
+ if (tb[DEVLINK_ATTR_STATS_RX_DROPPED])
+ pr_out_u64(dl, "dropped",
+ mnl_attr_get_u64(tb[DEVLINK_ATTR_STATS_RX_DROPPED]));
+ pr_out_object_end(dl);
+ pr_out_object_end(dl);
+}
+
+static const char *param_cmode_name(uint8_t cmode)
+{
+ switch (cmode) {
+ case DEVLINK_PARAM_CMODE_RUNTIME:
+ return PARAM_CMODE_RUNTIME_STR;
+ case DEVLINK_PARAM_CMODE_DRIVERINIT:
+ return PARAM_CMODE_DRIVERINIT_STR;
+ case DEVLINK_PARAM_CMODE_PERMANENT:
+ return PARAM_CMODE_PERMANENT_STR;
+ default: return "<unknown type>";
+ }
+}
+
+static const char *reload_action_name(uint8_t reload_action)
+{
+ switch (reload_action) {
+ case DEVLINK_RELOAD_ACTION_DRIVER_REINIT:
+ return "driver_reinit";
+ case DEVLINK_RELOAD_ACTION_FW_ACTIVATE:
+ return "fw_activate";
+ default:
+ return "<unknown reload action>";
+ }
+}
+
+static const char *reload_limit_name(uint8_t reload_limit)
+{
+ switch (reload_limit) {
+ case DEVLINK_RELOAD_LIMIT_UNSPEC:
+ return "unspecified";
+ case DEVLINK_RELOAD_LIMIT_NO_RESET:
+ return "no_reset";
+ default:
+ return "<unknown reload action>";
+ }
+}
+
+static const char *eswitch_mode_name(uint32_t mode)
+{
+ switch (mode) {
+ case DEVLINK_ESWITCH_MODE_LEGACY: return ESWITCH_MODE_LEGACY;
+ case DEVLINK_ESWITCH_MODE_SWITCHDEV: return ESWITCH_MODE_SWITCHDEV;
+ default: return "<unknown mode>";
+ }
+}
+
+static const char *eswitch_inline_mode_name(uint32_t mode)
+{
+ switch (mode) {
+ case DEVLINK_ESWITCH_INLINE_MODE_NONE:
+ return ESWITCH_INLINE_MODE_NONE;
+ case DEVLINK_ESWITCH_INLINE_MODE_LINK:
+ return ESWITCH_INLINE_MODE_LINK;
+ case DEVLINK_ESWITCH_INLINE_MODE_NETWORK:
+ return ESWITCH_INLINE_MODE_NETWORK;
+ case DEVLINK_ESWITCH_INLINE_MODE_TRANSPORT:
+ return ESWITCH_INLINE_MODE_TRANSPORT;
+ default:
+ return "<unknown mode>";
+ }
+}
+
+static const char *eswitch_encap_mode_name(uint32_t mode)
+{
+ switch (mode) {
+ case DEVLINK_ESWITCH_ENCAP_MODE_NONE:
+ return ESWITCH_ENCAP_MODE_NONE;
+ case DEVLINK_ESWITCH_ENCAP_MODE_BASIC:
+ return ESWITCH_ENCAP_MODE_BASIC;
+ default:
+ return "<unknown mode>";
+ }
+}
+
+static void pr_out_eswitch(struct dl *dl, struct nlattr **tb)
+{
+ __pr_out_handle_start(dl, tb, true, false);
+
+ if (tb[DEVLINK_ATTR_ESWITCH_MODE]) {
+ check_indent_newline(dl);
+ print_string(PRINT_ANY, "mode", "mode %s",
+ eswitch_mode_name(mnl_attr_get_u16(
+ tb[DEVLINK_ATTR_ESWITCH_MODE])));
+ }
+ if (tb[DEVLINK_ATTR_ESWITCH_INLINE_MODE]) {
+ check_indent_newline(dl);
+ print_string(PRINT_ANY, "inline-mode", "inline-mode %s",
+ eswitch_inline_mode_name(mnl_attr_get_u8(
+ tb[DEVLINK_ATTR_ESWITCH_INLINE_MODE])));
+ }
+ if (tb[DEVLINK_ATTR_ESWITCH_ENCAP_MODE]) {
+ check_indent_newline(dl);
+ print_string(PRINT_ANY, "encap-mode", "encap-mode %s",
+ eswitch_encap_mode_name(mnl_attr_get_u8(
+ tb[DEVLINK_ATTR_ESWITCH_ENCAP_MODE])));
+ }
+
+ pr_out_handle_end(dl);
+}
+
+static int cmd_dev_eswitch_show_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct dl *dl = data;
+ struct nlattr *tb[DEVLINK_ATTR_MAX + 1] = {};
+ struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+
+ mnl_attr_parse(nlh, sizeof(*genl), attr_cb, tb);
+ if (!tb[DEVLINK_ATTR_BUS_NAME] || !tb[DEVLINK_ATTR_DEV_NAME])
+ return MNL_CB_ERROR;
+ pr_out_eswitch(dl, tb);
+ return MNL_CB_OK;
+}
+
+static int cmd_dev_eswitch_show(struct dl *dl)
+{
+ struct nlmsghdr *nlh;
+ int err;
+
+ nlh = mnlu_gen_socket_cmd_prepare(&dl->nlg, DEVLINK_CMD_ESWITCH_GET,
+ NLM_F_REQUEST | NLM_F_ACK);
+
+ err = dl_argv_parse_put(nlh, dl, DL_OPT_HANDLE, 0);
+ if (err)
+ return err;
+
+ pr_out_section_start(dl, "dev");
+ err = mnlu_gen_socket_sndrcv(&dl->nlg, nlh, cmd_dev_eswitch_show_cb, dl);
+ pr_out_section_end(dl);
+ return err;
+}
+
+static int cmd_dev_eswitch_set(struct dl *dl)
+{
+ struct nlmsghdr *nlh;
+ int err;
+
+ nlh = mnlu_gen_socket_cmd_prepare(&dl->nlg, DEVLINK_CMD_ESWITCH_SET,
+ NLM_F_REQUEST | NLM_F_ACK);
+
+ err = dl_argv_parse_put(nlh, dl, DL_OPT_HANDLE,
+ DL_OPT_ESWITCH_MODE |
+ DL_OPT_ESWITCH_INLINE_MODE |
+ DL_OPT_ESWITCH_ENCAP_MODE);
+
+ if (err)
+ return err;
+
+ if (dl->opts.present == 1) {
+ pr_err("Need to set at least one option\n");
+ return -ENOENT;
+ }
+
+ return mnlu_gen_socket_sndrcv(&dl->nlg, nlh, NULL, NULL);
+}
+
+static int cmd_dev_eswitch(struct dl *dl)
+{
+ if (dl_argv_match(dl, "help") || dl_no_arg(dl)) {
+ cmd_dev_help();
+ return 0;
+ } else if (dl_argv_match(dl, "set")) {
+ dl_arg_inc(dl);
+ return cmd_dev_eswitch_set(dl);
+ } else if (dl_argv_match(dl, "show")) {
+ dl_arg_inc(dl);
+ return cmd_dev_eswitch_show(dl);
+ }
+ pr_err("Command \"%s\" not found\n", dl_argv(dl));
+ return -ENOENT;
+}
+
+struct param_val_conv {
+ const char *name;
+ const char *vstr;
+ uint32_t vuint;
+};
+
+static bool param_val_conv_exists(const struct param_val_conv *param_val_conv,
+ uint32_t len, const char *name)
+{
+ uint32_t i;
+
+ for (i = 0; i < len; i++)
+ if (!strcmp(param_val_conv[i].name, name))
+ return true;
+
+ return false;
+}
+
+static int
+param_val_conv_uint_get(const struct param_val_conv *param_val_conv,
+ uint32_t len, const char *name, const char *vstr,
+ uint32_t *vuint)
+{
+ uint32_t i;
+
+ for (i = 0; i < len; i++)
+ if (!strcmp(param_val_conv[i].name, name) &&
+ !strcmp(param_val_conv[i].vstr, vstr)) {
+ *vuint = param_val_conv[i].vuint;
+ return 0;
+ }
+
+ return -ENOENT;
+}
+
+static int
+param_val_conv_str_get(const struct param_val_conv *param_val_conv,
+ uint32_t len, const char *name, uint32_t vuint,
+ const char **vstr)
+{
+ uint32_t i;
+
+ for (i = 0; i < len; i++)
+ if (!strcmp(param_val_conv[i].name, name) &&
+ param_val_conv[i].vuint == vuint) {
+ *vstr = param_val_conv[i].vstr;
+ return 0;
+ }
+
+ return -ENOENT;
+}
+
+static const struct param_val_conv param_val_conv[] = {
+ {
+ .name = "fw_load_policy",
+ .vstr = "driver",
+ .vuint = DEVLINK_PARAM_FW_LOAD_POLICY_VALUE_DRIVER,
+ },
+ {
+ .name = "fw_load_policy",
+ .vstr = "flash",
+ .vuint = DEVLINK_PARAM_FW_LOAD_POLICY_VALUE_FLASH,
+ },
+ {
+ .name = "fw_load_policy",
+ .vstr = "disk",
+ .vuint = DEVLINK_PARAM_FW_LOAD_POLICY_VALUE_DISK,
+ },
+ {
+ .name = "reset_dev_on_drv_probe",
+ .vstr = "unknown",
+ .vuint = DEVLINK_PARAM_RESET_DEV_ON_DRV_PROBE_VALUE_UNKNOWN,
+ },
+ {
+ .name = "fw_load_policy",
+ .vstr = "unknown",
+ .vuint = DEVLINK_PARAM_FW_LOAD_POLICY_VALUE_UNKNOWN,
+ },
+ {
+ .name = "reset_dev_on_drv_probe",
+ .vstr = "always",
+ .vuint = DEVLINK_PARAM_RESET_DEV_ON_DRV_PROBE_VALUE_ALWAYS,
+ },
+ {
+ .name = "reset_dev_on_drv_probe",
+ .vstr = "never",
+ .vuint = DEVLINK_PARAM_RESET_DEV_ON_DRV_PROBE_VALUE_NEVER,
+ },
+ {
+ .name = "reset_dev_on_drv_probe",
+ .vstr = "disk",
+ .vuint = DEVLINK_PARAM_RESET_DEV_ON_DRV_PROBE_VALUE_DISK,
+ },
+};
+
+#define PARAM_VAL_CONV_LEN ARRAY_SIZE(param_val_conv)
+
+static void pr_out_param_value(struct dl *dl, const char *nla_name,
+ int nla_type, struct nlattr *nl)
+{
+ struct nlattr *nla_value[DEVLINK_ATTR_MAX + 1] = {};
+ struct nlattr *val_attr;
+ const char *vstr;
+ bool conv_exists;
+ int err;
+
+ err = mnl_attr_parse_nested(nl, attr_cb, nla_value);
+ if (err != MNL_CB_OK)
+ return;
+
+ if (!nla_value[DEVLINK_ATTR_PARAM_VALUE_CMODE] ||
+ (nla_type != MNL_TYPE_FLAG &&
+ !nla_value[DEVLINK_ATTR_PARAM_VALUE_DATA]))
+ return;
+
+ check_indent_newline(dl);
+ print_string(PRINT_ANY, "cmode", "cmode %s",
+ param_cmode_name(mnl_attr_get_u8(nla_value[DEVLINK_ATTR_PARAM_VALUE_CMODE])));
+
+ val_attr = nla_value[DEVLINK_ATTR_PARAM_VALUE_DATA];
+
+ conv_exists = param_val_conv_exists(param_val_conv, PARAM_VAL_CONV_LEN,
+ nla_name);
+
+ switch (nla_type) {
+ case MNL_TYPE_U8:
+ if (conv_exists) {
+ err = param_val_conv_str_get(param_val_conv,
+ PARAM_VAL_CONV_LEN,
+ nla_name,
+ mnl_attr_get_u8(val_attr),
+ &vstr);
+ if (err)
+ return;
+ print_string(PRINT_ANY, "value", " value %s", vstr);
+ } else {
+ print_uint(PRINT_ANY, "value", " value %u",
+ mnl_attr_get_u8(val_attr));
+ }
+ break;
+ case MNL_TYPE_U16:
+ if (conv_exists) {
+ err = param_val_conv_str_get(param_val_conv,
+ PARAM_VAL_CONV_LEN,
+ nla_name,
+ mnl_attr_get_u16(val_attr),
+ &vstr);
+ if (err)
+ return;
+ print_string(PRINT_ANY, "value", " value %s", vstr);
+ } else {
+ print_uint(PRINT_ANY, "value", " value %u",
+ mnl_attr_get_u16(val_attr));
+ }
+ break;
+ case MNL_TYPE_U32:
+ if (conv_exists) {
+ err = param_val_conv_str_get(param_val_conv,
+ PARAM_VAL_CONV_LEN,
+ nla_name,
+ mnl_attr_get_u32(val_attr),
+ &vstr);
+ if (err)
+ return;
+ print_string(PRINT_ANY, "value", " value %s", vstr);
+ } else {
+ print_uint(PRINT_ANY, "value", " value %u",
+ mnl_attr_get_u32(val_attr));
+ }
+ break;
+ case MNL_TYPE_STRING:
+ print_string(PRINT_ANY, "value", " value %s",
+ mnl_attr_get_str(val_attr));
+ break;
+ case MNL_TYPE_FLAG:
+ print_bool(PRINT_ANY, "value", " value %s", val_attr);
+ break;
+ }
+}
+
+static void pr_out_param(struct dl *dl, struct nlattr **tb, bool array,
+ bool is_port_param)
+{
+ struct nlattr *nla_param[DEVLINK_ATTR_MAX + 1] = {};
+ struct nlattr *param_value_attr;
+ const char *nla_name;
+ int nla_type;
+ int err;
+
+ err = mnl_attr_parse_nested(tb[DEVLINK_ATTR_PARAM], attr_cb, nla_param);
+ if (err != MNL_CB_OK)
+ return;
+ if (!nla_param[DEVLINK_ATTR_PARAM_NAME] ||
+ !nla_param[DEVLINK_ATTR_PARAM_TYPE] ||
+ !nla_param[DEVLINK_ATTR_PARAM_VALUES_LIST])
+ return;
+
+ if (array)
+ if (is_port_param)
+ pr_out_port_handle_start_arr(dl, tb, false);
+ else
+ pr_out_handle_start_arr(dl, tb);
+ else
+ if (is_port_param)
+ pr_out_port_handle_start(dl, tb, false);
+ else
+ __pr_out_handle_start(dl, tb, true, false);
+
+ nla_type = mnl_attr_get_u8(nla_param[DEVLINK_ATTR_PARAM_TYPE]);
+
+ nla_name = mnl_attr_get_str(nla_param[DEVLINK_ATTR_PARAM_NAME]);
+ check_indent_newline(dl);
+ print_string(PRINT_ANY, "name", "name %s ", nla_name);
+ if (!nla_param[DEVLINK_ATTR_PARAM_GENERIC])
+ print_string(PRINT_ANY, "type", "type %s", "driver-specific");
+ else
+ print_string(PRINT_ANY, "type", "type %s", "generic");
+
+ pr_out_array_start(dl, "values");
+ mnl_attr_for_each_nested(param_value_attr,
+ nla_param[DEVLINK_ATTR_PARAM_VALUES_LIST]) {
+ pr_out_entry_start(dl);
+ pr_out_param_value(dl, nla_name, nla_type, param_value_attr);
+ pr_out_entry_end(dl);
+ }
+ pr_out_array_end(dl);
+ if (is_port_param)
+ pr_out_port_handle_end(dl);
+ else
+ pr_out_handle_end(dl);
+}
+
+static int cmd_dev_param_show_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+ struct nlattr *tb[DEVLINK_ATTR_MAX + 1] = {};
+ struct dl *dl = data;
+
+ mnl_attr_parse(nlh, sizeof(*genl), attr_cb, tb);
+ if (!tb[DEVLINK_ATTR_BUS_NAME] || !tb[DEVLINK_ATTR_DEV_NAME] ||
+ !tb[DEVLINK_ATTR_PARAM])
+ return MNL_CB_ERROR;
+ pr_out_param(dl, tb, true, false);
+ return MNL_CB_OK;
+}
+
+struct param_ctx {
+ struct dl *dl;
+ int nla_type;
+ union {
+ uint8_t vu8;
+ uint16_t vu16;
+ uint32_t vu32;
+ const char *vstr;
+ bool vbool;
+ } value;
+};
+
+static int cmd_dev_param_set_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+ struct nlattr *nla_param[DEVLINK_ATTR_MAX + 1] = {};
+ struct nlattr *tb[DEVLINK_ATTR_MAX + 1] = {};
+ struct nlattr *param_value_attr;
+ enum devlink_param_cmode cmode;
+ struct param_ctx *ctx = data;
+ struct dl *dl = ctx->dl;
+ int nla_type;
+ int err;
+
+ mnl_attr_parse(nlh, sizeof(*genl), attr_cb, tb);
+ if (!tb[DEVLINK_ATTR_BUS_NAME] || !tb[DEVLINK_ATTR_DEV_NAME] ||
+ !tb[DEVLINK_ATTR_PARAM])
+ return MNL_CB_ERROR;
+
+ err = mnl_attr_parse_nested(tb[DEVLINK_ATTR_PARAM], attr_cb, nla_param);
+ if (err != MNL_CB_OK)
+ return MNL_CB_ERROR;
+
+ if (!nla_param[DEVLINK_ATTR_PARAM_TYPE] ||
+ !nla_param[DEVLINK_ATTR_PARAM_VALUES_LIST])
+ return MNL_CB_ERROR;
+
+ nla_type = mnl_attr_get_u8(nla_param[DEVLINK_ATTR_PARAM_TYPE]);
+ mnl_attr_for_each_nested(param_value_attr,
+ nla_param[DEVLINK_ATTR_PARAM_VALUES_LIST]) {
+ struct nlattr *nla_value[DEVLINK_ATTR_MAX + 1] = {};
+ struct nlattr *val_attr;
+
+ err = mnl_attr_parse_nested(param_value_attr,
+ attr_cb, nla_value);
+ if (err != MNL_CB_OK)
+ return MNL_CB_ERROR;
+
+ if (!nla_value[DEVLINK_ATTR_PARAM_VALUE_CMODE] ||
+ (nla_type != MNL_TYPE_FLAG &&
+ !nla_value[DEVLINK_ATTR_PARAM_VALUE_DATA]))
+ return MNL_CB_ERROR;
+
+ cmode = mnl_attr_get_u8(nla_value[DEVLINK_ATTR_PARAM_VALUE_CMODE]);
+ if (cmode == dl->opts.cmode) {
+ val_attr = nla_value[DEVLINK_ATTR_PARAM_VALUE_DATA];
+ switch (nla_type) {
+ case MNL_TYPE_U8:
+ ctx->value.vu8 = mnl_attr_get_u8(val_attr);
+ break;
+ case MNL_TYPE_U16:
+ ctx->value.vu16 = mnl_attr_get_u16(val_attr);
+ break;
+ case MNL_TYPE_U32:
+ ctx->value.vu32 = mnl_attr_get_u32(val_attr);
+ break;
+ case MNL_TYPE_STRING:
+ ctx->value.vstr = mnl_attr_get_str(val_attr);
+ break;
+ case MNL_TYPE_FLAG:
+ ctx->value.vbool = val_attr ? true : false;
+ break;
+ }
+ break;
+ }
+ }
+ ctx->nla_type = nla_type;
+ return MNL_CB_OK;
+}
+
+static int cmd_dev_param_set(struct dl *dl)
+{
+ struct param_ctx ctx = {};
+ struct nlmsghdr *nlh;
+ bool conv_exists;
+ uint32_t val_u32 = 0;
+ uint16_t val_u16;
+ uint8_t val_u8;
+ bool val_bool;
+ int err;
+
+ err = dl_argv_parse(dl, DL_OPT_HANDLE |
+ DL_OPT_PARAM_NAME |
+ DL_OPT_PARAM_VALUE |
+ DL_OPT_PARAM_CMODE, 0);
+ if (err)
+ return err;
+
+ /* Get value type */
+ nlh = mnlu_gen_socket_cmd_prepare(&dl->nlg, DEVLINK_CMD_PARAM_GET,
+ NLM_F_REQUEST | NLM_F_ACK);
+ dl_opts_put(nlh, dl);
+
+ ctx.dl = dl;
+ err = mnlu_gen_socket_sndrcv(&dl->nlg, nlh, cmd_dev_param_set_cb, &ctx);
+ if (err)
+ return err;
+
+ nlh = mnlu_gen_socket_cmd_prepare(&dl->nlg, DEVLINK_CMD_PARAM_SET,
+ NLM_F_REQUEST | NLM_F_ACK);
+ dl_opts_put(nlh, dl);
+
+ conv_exists = param_val_conv_exists(param_val_conv, PARAM_VAL_CONV_LEN,
+ dl->opts.param_name);
+
+ mnl_attr_put_u8(nlh, DEVLINK_ATTR_PARAM_TYPE, ctx.nla_type);
+ switch (ctx.nla_type) {
+ case MNL_TYPE_U8:
+ if (conv_exists) {
+ err = param_val_conv_uint_get(param_val_conv,
+ PARAM_VAL_CONV_LEN,
+ dl->opts.param_name,
+ dl->opts.param_value,
+ &val_u32);
+ val_u8 = val_u32;
+ } else {
+ err = strtouint8_t(dl->opts.param_value, &val_u8);
+ }
+ if (err)
+ goto err_param_value_parse;
+ if (val_u8 == ctx.value.vu8)
+ return 0;
+ mnl_attr_put_u8(nlh, DEVLINK_ATTR_PARAM_VALUE_DATA, val_u8);
+ break;
+ case MNL_TYPE_U16:
+ if (conv_exists) {
+ err = param_val_conv_uint_get(param_val_conv,
+ PARAM_VAL_CONV_LEN,
+ dl->opts.param_name,
+ dl->opts.param_value,
+ &val_u32);
+ val_u16 = val_u32;
+ } else {
+ err = strtouint16_t(dl->opts.param_value, &val_u16);
+ }
+ if (err)
+ goto err_param_value_parse;
+ if (val_u16 == ctx.value.vu16)
+ return 0;
+ mnl_attr_put_u16(nlh, DEVLINK_ATTR_PARAM_VALUE_DATA, val_u16);
+ break;
+ case MNL_TYPE_U32:
+ if (conv_exists)
+ err = param_val_conv_uint_get(param_val_conv,
+ PARAM_VAL_CONV_LEN,
+ dl->opts.param_name,
+ dl->opts.param_value,
+ &val_u32);
+ else
+ err = strtouint32_t(dl->opts.param_value, &val_u32);
+ if (err)
+ goto err_param_value_parse;
+ if (val_u32 == ctx.value.vu32)
+ return 0;
+ mnl_attr_put_u32(nlh, DEVLINK_ATTR_PARAM_VALUE_DATA, val_u32);
+ break;
+ case MNL_TYPE_FLAG:
+ err = strtobool(dl->opts.param_value, &val_bool);
+ if (err)
+ goto err_param_value_parse;
+ if (val_bool == ctx.value.vbool)
+ return 0;
+ if (val_bool)
+ mnl_attr_put(nlh, DEVLINK_ATTR_PARAM_VALUE_DATA,
+ 0, NULL);
+ break;
+ case MNL_TYPE_STRING:
+ mnl_attr_put_strz(nlh, DEVLINK_ATTR_PARAM_VALUE_DATA,
+ dl->opts.param_value);
+ if (!strcmp(dl->opts.param_value, ctx.value.vstr))
+ return 0;
+ break;
+ default:
+ printf("Value type not supported\n");
+ return -ENOTSUP;
+ }
+ return mnlu_gen_socket_sndrcv(&dl->nlg, nlh, NULL, NULL);
+
+err_param_value_parse:
+ pr_err("Value \"%s\" is not a number or not within range\n",
+ dl->opts.param_value);
+ return err;
+}
+
+static int cmd_port_param_show_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+ struct nlattr *tb[DEVLINK_ATTR_MAX + 1] = {};
+ struct dl *dl = data;
+
+ mnl_attr_parse(nlh, sizeof(*genl), attr_cb, tb);
+ if (!tb[DEVLINK_ATTR_BUS_NAME] || !tb[DEVLINK_ATTR_DEV_NAME] ||
+ !tb[DEVLINK_ATTR_PORT_INDEX] || !tb[DEVLINK_ATTR_PARAM])
+ return MNL_CB_ERROR;
+
+ pr_out_param(dl, tb, true, true);
+ return MNL_CB_OK;
+}
+
+static int cmd_dev_param_show(struct dl *dl)
+{
+ uint16_t flags = NLM_F_REQUEST | NLM_F_ACK;
+ struct nlmsghdr *nlh;
+ int err;
+
+ if (dl_argc(dl) == 0)
+ flags |= NLM_F_DUMP;
+
+ nlh = mnlu_gen_socket_cmd_prepare(&dl->nlg, DEVLINK_CMD_PARAM_GET, flags);
+
+ if (dl_argc(dl) > 0) {
+ err = dl_argv_parse_put(nlh, dl, DL_OPT_HANDLE |
+ DL_OPT_PARAM_NAME, 0);
+ if (err)
+ return err;
+ }
+
+ pr_out_section_start(dl, "param");
+ err = mnlu_gen_socket_sndrcv(&dl->nlg, nlh, cmd_dev_param_show_cb, dl);
+ pr_out_section_end(dl);
+ return err;
+}
+
+static int cmd_dev_param(struct dl *dl)
+{
+ if (dl_argv_match(dl, "help")) {
+ cmd_dev_help();
+ return 0;
+ } else if (dl_argv_match(dl, "show") ||
+ dl_argv_match(dl, "list") || dl_no_arg(dl)) {
+ dl_arg_inc(dl);
+ return cmd_dev_param_show(dl);
+ } else if (dl_argv_match(dl, "set")) {
+ dl_arg_inc(dl);
+ return cmd_dev_param_set(dl);
+ }
+ pr_err("Command \"%s\" not found\n", dl_argv(dl));
+ return -ENOENT;
+}
+
+static void pr_out_action_stats(struct dl *dl, struct nlattr *action_stats)
+{
+ struct nlattr *tb_stats_entry[DEVLINK_ATTR_MAX + 1] = {};
+ struct nlattr *nla_reload_stats_entry, *nla_limit, *nla_value;
+ enum devlink_reload_limit limit;
+ uint32_t value;
+ int err;
+
+ mnl_attr_for_each_nested(nla_reload_stats_entry, action_stats) {
+ err = mnl_attr_parse_nested(nla_reload_stats_entry, attr_cb,
+ tb_stats_entry);
+ if (err != MNL_CB_OK)
+ return;
+
+ nla_limit = tb_stats_entry[DEVLINK_ATTR_RELOAD_STATS_LIMIT];
+ nla_value = tb_stats_entry[DEVLINK_ATTR_RELOAD_STATS_VALUE];
+ if (!nla_limit || !nla_value)
+ return;
+
+ check_indent_newline(dl);
+ limit = mnl_attr_get_u8(nla_limit);
+ value = mnl_attr_get_u32(nla_value);
+ print_uint_name_value(reload_limit_name(limit), value);
+ }
+}
+
+static void pr_out_reload_stats(struct dl *dl, struct nlattr *reload_stats)
+{
+ struct nlattr *nla_action_info, *nla_action, *nla_action_stats;
+ struct nlattr *tb[DEVLINK_ATTR_MAX + 1] = {};
+ enum devlink_reload_action action;
+ int err;
+
+ mnl_attr_for_each_nested(nla_action_info, reload_stats) {
+ err = mnl_attr_parse_nested(nla_action_info, attr_cb, tb);
+ if (err != MNL_CB_OK)
+ return;
+ nla_action = tb[DEVLINK_ATTR_RELOAD_ACTION];
+ nla_action_stats = tb[DEVLINK_ATTR_RELOAD_ACTION_STATS];
+ if (!nla_action || !nla_action_stats)
+ return;
+
+ action = mnl_attr_get_u8(nla_action);
+ pr_out_object_start(dl, reload_action_name(action));
+ pr_out_action_stats(dl, nla_action_stats);
+ pr_out_object_end(dl);
+ }
+}
+
+static void pr_out_reload_data(struct dl *dl, struct nlattr **tb)
+{
+ struct nlattr *nla_reload_stats, *nla_remote_reload_stats;
+ struct nlattr *tb_stats[DEVLINK_ATTR_MAX + 1] = {};
+ uint8_t reload_failed = 0;
+ int err;
+
+ if (tb[DEVLINK_ATTR_RELOAD_FAILED])
+ reload_failed = mnl_attr_get_u8(tb[DEVLINK_ATTR_RELOAD_FAILED]);
+
+ if (reload_failed) {
+ check_indent_newline(dl);
+ print_bool(PRINT_ANY, "reload_failed", "reload_failed %s", true);
+ }
+ if (!tb[DEVLINK_ATTR_DEV_STATS] || !dl->stats)
+ return;
+ err = mnl_attr_parse_nested(tb[DEVLINK_ATTR_DEV_STATS], attr_cb,
+ tb_stats);
+ if (err != MNL_CB_OK)
+ return;
+
+ pr_out_object_start(dl, "stats");
+
+ nla_reload_stats = tb_stats[DEVLINK_ATTR_RELOAD_STATS];
+ if (nla_reload_stats) {
+ pr_out_object_start(dl, "reload");
+ pr_out_reload_stats(dl, nla_reload_stats);
+ pr_out_object_end(dl);
+ }
+ nla_remote_reload_stats = tb_stats[DEVLINK_ATTR_REMOTE_RELOAD_STATS];
+ if (nla_remote_reload_stats) {
+ pr_out_object_start(dl, "remote_reload");
+ pr_out_reload_stats(dl, nla_remote_reload_stats);
+ pr_out_object_end(dl);
+ }
+
+ pr_out_object_end(dl);
+}
+
+
+static void pr_out_dev(struct dl *dl, struct nlattr **tb)
+{
+ if ((tb[DEVLINK_ATTR_RELOAD_FAILED] && mnl_attr_get_u8(tb[DEVLINK_ATTR_RELOAD_FAILED])) ||
+ (tb[DEVLINK_ATTR_DEV_STATS] && dl->stats)) {
+ __pr_out_handle_start(dl, tb, true, false);
+ pr_out_reload_data(dl, tb);
+ pr_out_handle_end(dl);
+ } else {
+ pr_out_handle(dl, tb);
+ }
+}
+
+static int cmd_dev_show_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct dl *dl = data;
+ struct nlattr *tb[DEVLINK_ATTR_MAX + 1] = {};
+ struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+
+ mnl_attr_parse(nlh, sizeof(*genl), attr_cb, tb);
+ if (!tb[DEVLINK_ATTR_BUS_NAME] || !tb[DEVLINK_ATTR_DEV_NAME])
+ return MNL_CB_ERROR;
+
+ pr_out_dev(dl, tb);
+ return MNL_CB_OK;
+}
+
+static int cmd_dev_show(struct dl *dl)
+{
+ struct nlmsghdr *nlh;
+ uint16_t flags = NLM_F_REQUEST | NLM_F_ACK;
+ int err;
+
+ if (dl_argc(dl) == 0)
+ flags |= NLM_F_DUMP;
+
+ nlh = mnlu_gen_socket_cmd_prepare(&dl->nlg, DEVLINK_CMD_GET, flags);
+
+ if (dl_argc(dl) > 0) {
+ err = dl_argv_parse_put(nlh, dl, DL_OPT_HANDLE, 0);
+ if (err)
+ return err;
+ }
+
+ pr_out_section_start(dl, "dev");
+ err = mnlu_gen_socket_sndrcv(&dl->nlg, nlh, cmd_dev_show_cb, dl);
+ pr_out_section_end(dl);
+ return err;
+}
+
+static void pr_out_reload_actions_performed(struct dl *dl, struct nlattr **tb)
+{
+ struct nlattr *nla_actions_performed;
+ struct nla_bitfield32 *actions;
+ uint32_t actions_performed;
+ uint16_t len;
+ int action;
+
+ if (!tb[DEVLINK_ATTR_RELOAD_ACTIONS_PERFORMED])
+ return;
+
+ nla_actions_performed = tb[DEVLINK_ATTR_RELOAD_ACTIONS_PERFORMED];
+ len = mnl_attr_get_payload_len(nla_actions_performed);
+ if (len != sizeof(*actions))
+ return;
+ actions = mnl_attr_get_payload(nla_actions_performed);
+ if (!actions)
+ return;
+ g_new_line_count = 1; /* Avoid extra new line in non-json print */
+ pr_out_array_start(dl, "reload_actions_performed");
+ actions_performed = actions->value & actions->selector;
+ for (action = 0; action <= DEVLINK_RELOAD_ACTION_MAX; action++) {
+ if (BIT(action) & actions_performed) {
+ check_indent_newline(dl);
+ print_string(PRINT_ANY, NULL, "%s",
+ reload_action_name(action));
+ }
+ }
+ pr_out_array_end(dl);
+ if (!dl->json_output)
+ __pr_out_newline();
+}
+
+static int cmd_dev_reload_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+ struct nlattr *tb[DEVLINK_ATTR_MAX + 1] = {};
+ struct dl *dl = data;
+
+ mnl_attr_parse(nlh, sizeof(*genl), attr_cb, tb);
+ if (!tb[DEVLINK_ATTR_BUS_NAME] || !tb[DEVLINK_ATTR_DEV_NAME] ||
+ !tb[DEVLINK_ATTR_RELOAD_ACTIONS_PERFORMED])
+ return MNL_CB_ERROR;
+
+ pr_out_section_start(dl, "reload");
+ pr_out_reload_actions_performed(dl, tb);
+ pr_out_section_end(dl);
+
+ return MNL_CB_OK;
+}
+
+static int cmd_dev_reload(struct dl *dl)
+{
+ struct nlmsghdr *nlh;
+ int err;
+
+ if (dl_argv_match(dl, "help") || dl_no_arg(dl)) {
+ cmd_dev_help();
+ return 0;
+ }
+
+ nlh = mnlu_gen_socket_cmd_prepare(&dl->nlg, DEVLINK_CMD_RELOAD,
+ NLM_F_REQUEST | NLM_F_ACK);
+
+ err = dl_argv_parse_put(nlh, dl, DL_OPT_HANDLE,
+ DL_OPT_NETNS | DL_OPT_RELOAD_ACTION |
+ DL_OPT_RELOAD_LIMIT);
+ if (err)
+ return err;
+
+ return mnlu_gen_socket_sndrcv(&dl->nlg, nlh, cmd_dev_reload_cb, dl);
+}
+
+static void pr_out_versions_single(struct dl *dl, const struct nlmsghdr *nlh,
+ const char *name, int type)
+{
+ struct nlattr *version;
+
+ mnl_attr_for_each(version, nlh, sizeof(struct genlmsghdr)) {
+ struct nlattr *tb[DEVLINK_ATTR_MAX + 1] = {};
+ const char *ver_value;
+ const char *ver_name;
+ int err;
+
+ if (mnl_attr_get_type(version) != type)
+ continue;
+
+ err = mnl_attr_parse_nested(version, attr_cb, tb);
+ if (err != MNL_CB_OK)
+ continue;
+
+ if (!tb[DEVLINK_ATTR_INFO_VERSION_NAME] ||
+ !tb[DEVLINK_ATTR_INFO_VERSION_VALUE])
+ continue;
+
+ if (name) {
+ pr_out_object_start(dl, name);
+ name = NULL;
+ }
+
+ ver_name = mnl_attr_get_str(tb[DEVLINK_ATTR_INFO_VERSION_NAME]);
+ ver_value = mnl_attr_get_str(tb[DEVLINK_ATTR_INFO_VERSION_VALUE]);
+
+ check_indent_newline(dl);
+ print_string_name_value(ver_name, ver_value);
+ if (!dl->json_output)
+ __pr_out_newline();
+ }
+
+ if (!name)
+ pr_out_object_end(dl);
+}
+
+static void pr_out_info(struct dl *dl, const struct nlmsghdr *nlh,
+ struct nlattr **tb, bool has_versions)
+{
+ __pr_out_handle_start(dl, tb, true, false);
+
+ __pr_out_indent_inc();
+ if (tb[DEVLINK_ATTR_INFO_DRIVER_NAME]) {
+ struct nlattr *nla_drv = tb[DEVLINK_ATTR_INFO_DRIVER_NAME];
+
+ if (!dl->json_output)
+ __pr_out_newline();
+ check_indent_newline(dl);
+ print_string(PRINT_ANY, "driver", "driver %s",
+ mnl_attr_get_str(nla_drv));
+ }
+
+ if (tb[DEVLINK_ATTR_INFO_SERIAL_NUMBER]) {
+ struct nlattr *nla_sn = tb[DEVLINK_ATTR_INFO_SERIAL_NUMBER];
+
+ if (!dl->json_output)
+ __pr_out_newline();
+ check_indent_newline(dl);
+ print_string(PRINT_ANY, "serial_number", "serial_number %s",
+ mnl_attr_get_str(nla_sn));
+ }
+
+ if (tb[DEVLINK_ATTR_INFO_BOARD_SERIAL_NUMBER]) {
+ struct nlattr *nla_bsn = tb[DEVLINK_ATTR_INFO_BOARD_SERIAL_NUMBER];
+
+ if (!dl->json_output)
+ __pr_out_newline();
+ check_indent_newline(dl);
+ print_string(PRINT_ANY, "board.serial_number", "board.serial_number %s",
+ mnl_attr_get_str(nla_bsn));
+ }
+ __pr_out_indent_dec();
+
+ if (has_versions) {
+ pr_out_object_start(dl, "versions");
+
+ pr_out_versions_single(dl, nlh, "fixed",
+ DEVLINK_ATTR_INFO_VERSION_FIXED);
+ pr_out_versions_single(dl, nlh, "running",
+ DEVLINK_ATTR_INFO_VERSION_RUNNING);
+ pr_out_versions_single(dl, nlh, "stored",
+ DEVLINK_ATTR_INFO_VERSION_STORED);
+
+ pr_out_object_end(dl);
+ }
+
+ pr_out_handle_end(dl);
+}
+
+static int cmd_versions_show_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+ struct nlattr *tb[DEVLINK_ATTR_MAX + 1] = {};
+ bool has_versions, has_info;
+ struct dl *dl = data;
+
+ mnl_attr_parse(nlh, sizeof(*genl), attr_cb, tb);
+
+ if (!tb[DEVLINK_ATTR_BUS_NAME] || !tb[DEVLINK_ATTR_DEV_NAME])
+ return MNL_CB_ERROR;
+
+ has_versions = tb[DEVLINK_ATTR_INFO_VERSION_FIXED] ||
+ tb[DEVLINK_ATTR_INFO_VERSION_RUNNING] ||
+ tb[DEVLINK_ATTR_INFO_VERSION_STORED];
+ has_info = tb[DEVLINK_ATTR_INFO_DRIVER_NAME] ||
+ tb[DEVLINK_ATTR_INFO_SERIAL_NUMBER] ||
+ tb[DEVLINK_ATTR_INFO_BOARD_SERIAL_NUMBER] ||
+ has_versions;
+
+ if (has_info)
+ pr_out_info(dl, nlh, tb, has_versions);
+
+ return MNL_CB_OK;
+}
+
+static int cmd_dev_info(struct dl *dl)
+{
+ struct nlmsghdr *nlh;
+ uint16_t flags = NLM_F_REQUEST | NLM_F_ACK;
+ int err;
+
+ if (dl_argv_match(dl, "help")) {
+ cmd_dev_help();
+ return 0;
+ }
+
+ if (dl_argc(dl) == 0)
+ flags |= NLM_F_DUMP;
+
+ nlh = mnlu_gen_socket_cmd_prepare(&dl->nlg, DEVLINK_CMD_INFO_GET, flags);
+
+ if (dl_argc(dl) > 0) {
+ err = dl_argv_parse_put(nlh, dl, DL_OPT_HANDLE, 0);
+ if (err)
+ return err;
+ }
+
+ pr_out_section_start(dl, "info");
+ err = mnlu_gen_socket_sndrcv(&dl->nlg, nlh, cmd_versions_show_cb, dl);
+ pr_out_section_end(dl);
+ return err;
+}
+
+struct cmd_dev_flash_status_ctx {
+ struct dl *dl;
+ struct timespec time_of_last_status;
+ uint64_t status_msg_timeout;
+ size_t elapsed_time_msg_len;
+ char *last_msg;
+ char *last_component;
+ uint8_t not_first:1,
+ last_pc:1,
+ received_end:1,
+ flash_done:1;
+};
+
+static int nullstrcmp(const char *str1, const char *str2)
+{
+ if (str1 && str2)
+ return strcmp(str1, str2);
+ if (!str1 && !str2)
+ return 0;
+ return str1 ? 1 : -1;
+}
+
+static void cmd_dev_flash_clear_elapsed_time(struct cmd_dev_flash_status_ctx *ctx)
+{
+ int i;
+
+ for (i = 0; i < ctx->elapsed_time_msg_len; i++)
+ pr_out_tty("\b \b");
+
+ ctx->elapsed_time_msg_len = 0;
+}
+
+static int cmd_dev_flash_status_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct cmd_dev_flash_status_ctx *ctx = data;
+ struct dl_opts *opts = &ctx->dl->opts;
+ struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+ struct nlattr *tb[DEVLINK_ATTR_MAX + 1] = {};
+ const char *component = NULL;
+ uint64_t done = 0, total = 0;
+ const char *msg = NULL;
+ const char *bus_name;
+ const char *dev_name;
+
+ cmd_dev_flash_clear_elapsed_time(ctx);
+
+ if (genl->cmd != DEVLINK_CMD_FLASH_UPDATE_STATUS &&
+ genl->cmd != DEVLINK_CMD_FLASH_UPDATE_END)
+ return MNL_CB_STOP;
+
+ mnl_attr_parse(nlh, sizeof(*genl), attr_cb, tb);
+ if (!tb[DEVLINK_ATTR_BUS_NAME] || !tb[DEVLINK_ATTR_DEV_NAME])
+ return MNL_CB_ERROR;
+ bus_name = mnl_attr_get_str(tb[DEVLINK_ATTR_BUS_NAME]);
+ dev_name = mnl_attr_get_str(tb[DEVLINK_ATTR_DEV_NAME]);
+ if (strcmp(bus_name, opts->bus_name) ||
+ strcmp(dev_name, opts->dev_name))
+ return MNL_CB_ERROR;
+
+ if (genl->cmd == DEVLINK_CMD_FLASH_UPDATE_END && ctx->not_first) {
+ pr_out("\n");
+ free(ctx->last_msg);
+ free(ctx->last_component);
+ ctx->received_end = 1;
+ return MNL_CB_STOP;
+ }
+
+ if (tb[DEVLINK_ATTR_FLASH_UPDATE_STATUS_MSG])
+ msg = mnl_attr_get_str(tb[DEVLINK_ATTR_FLASH_UPDATE_STATUS_MSG]);
+ if (tb[DEVLINK_ATTR_FLASH_UPDATE_COMPONENT])
+ component = mnl_attr_get_str(tb[DEVLINK_ATTR_FLASH_UPDATE_COMPONENT]);
+ if (tb[DEVLINK_ATTR_FLASH_UPDATE_STATUS_DONE])
+ done = mnl_attr_get_u64(tb[DEVLINK_ATTR_FLASH_UPDATE_STATUS_DONE]);
+ if (tb[DEVLINK_ATTR_FLASH_UPDATE_STATUS_TOTAL])
+ total = mnl_attr_get_u64(tb[DEVLINK_ATTR_FLASH_UPDATE_STATUS_TOTAL]);
+ if (tb[DEVLINK_ATTR_FLASH_UPDATE_STATUS_TIMEOUT])
+ ctx->status_msg_timeout = mnl_attr_get_u64(tb[DEVLINK_ATTR_FLASH_UPDATE_STATUS_TIMEOUT]);
+ else
+ ctx->status_msg_timeout = 0;
+
+ if (!nullstrcmp(msg, ctx->last_msg) &&
+ !nullstrcmp(component, ctx->last_component) &&
+ ctx->last_pc && ctx->not_first) {
+ pr_out_tty("\b\b\b\b\b"); /* clean percentage */
+ } else {
+ /* only update the last status timestamp if the message changed */
+ clock_gettime(CLOCK_MONOTONIC, &ctx->time_of_last_status);
+
+ if (ctx->not_first)
+ pr_out("\n");
+ if (component) {
+ pr_out("[%s] ", component);
+ free(ctx->last_component);
+ ctx->last_component = strdup(component);
+ }
+ if (msg) {
+ pr_out("%s", msg);
+ free(ctx->last_msg);
+ ctx->last_msg = strdup(msg);
+ }
+ }
+ if (total) {
+ pr_out_tty(" %3"PRIu64"%%", (done * 100) / total);
+ ctx->last_pc = 1;
+ } else {
+ ctx->last_pc = 0;
+ }
+ fflush(stdout);
+ ctx->not_first = 1;
+
+ return MNL_CB_STOP;
+}
+
+static void cmd_dev_flash_time_elapsed(struct cmd_dev_flash_status_ctx *ctx)
+{
+ struct timespec now, res;
+
+ clock_gettime(CLOCK_MONOTONIC, &now);
+
+ res.tv_sec = now.tv_sec - ctx->time_of_last_status.tv_sec;
+ res.tv_nsec = now.tv_nsec - ctx->time_of_last_status.tv_nsec;
+ if (res.tv_nsec < 0) {
+ res.tv_sec--;
+ res.tv_nsec += 1000000000L;
+ }
+
+ /* Only begin displaying an elapsed time message if we've waited a few
+ * seconds with no response, or the status message included a timeout
+ * value.
+ */
+ if (res.tv_sec > 2 || ctx->status_msg_timeout) {
+ uint64_t elapsed_m, elapsed_s;
+ char msg[128];
+ size_t len;
+
+ /* clear the last elapsed time message, if we have one */
+ cmd_dev_flash_clear_elapsed_time(ctx);
+
+ elapsed_m = res.tv_sec / 60;
+ elapsed_s = res.tv_sec % 60;
+
+ /**
+ * If we've elapsed a few seconds without receiving any status
+ * notification from the device, we display a time elapsed
+ * message. This has a few possible formats:
+ *
+ * 1) just time elapsed, when no timeout was provided
+ * " ( Xm Ys )"
+ * 2) time elapsed out of a timeout that came from the device
+ * driver via DEVLINK_CMD_FLASH_UPDATE_STATUS_TIMEOUT
+ * " ( Xm Ys : Am Ys)"
+ * 3) time elapsed if we still receive no status after
+ * reaching the provided timeout.
+ * " ( Xm Ys : timeout reached )"
+ */
+ if (!ctx->status_msg_timeout) {
+ len = snprintf(msg, sizeof(msg),
+ " ( %"PRIu64"m %"PRIu64"s )", elapsed_m, elapsed_s);
+ } else if (res.tv_sec <= ctx->status_msg_timeout) {
+ uint64_t timeout_m, timeout_s;
+
+ timeout_m = ctx->status_msg_timeout / 60;
+ timeout_s = ctx->status_msg_timeout % 60;
+
+ len = snprintf(msg, sizeof(msg),
+ " ( %"PRIu64"m %"PRIu64"s : %"PRIu64"m %"PRIu64"s )",
+ elapsed_m, elapsed_s, timeout_m, timeout_s);
+ } else {
+ len = snprintf(msg, sizeof(msg),
+ " ( %"PRIu64"m %"PRIu64"s : timeout reached )", elapsed_m, elapsed_s);
+ }
+
+ ctx->elapsed_time_msg_len = len;
+
+ pr_out_tty("%s", msg);
+ fflush(stdout);
+ }
+}
+
+static int cmd_dev_flash_fds_process(struct cmd_dev_flash_status_ctx *ctx,
+ struct mnlu_gen_socket *nlg_ntf,
+ int pipe_r)
+{
+ int nlfd = mnlg_socket_get_fd(nlg_ntf);
+ struct timeval timeout;
+ fd_set fds[3];
+ int fdmax;
+ int i;
+ int err;
+ int err2;
+
+ for (i = 0; i < 3; i++)
+ FD_ZERO(&fds[i]);
+ FD_SET(pipe_r, &fds[0]);
+ fdmax = pipe_r + 1;
+ FD_SET(nlfd, &fds[0]);
+ if (nlfd >= fdmax)
+ fdmax = nlfd + 1;
+
+ /* select only for a short while (1/10th of a second) in order to
+ * allow periodically updating the screen with an elapsed time
+ * indicator.
+ */
+ timeout.tv_sec = 0;
+ timeout.tv_usec = 100000;
+
+ while (select(fdmax, &fds[0], &fds[1], &fds[2], &timeout) < 0) {
+ if (errno == EINTR)
+ continue;
+ pr_err("select() failed\n");
+ return -errno;
+ }
+ if (FD_ISSET(nlfd, &fds[0])) {
+ err = mnlu_gen_socket_recv_run(nlg_ntf,
+ cmd_dev_flash_status_cb, ctx);
+ if (err)
+ return err;
+ }
+ if (FD_ISSET(pipe_r, &fds[0])) {
+ err = read(pipe_r, &err2, sizeof(err2));
+ if (err == -1) {
+ pr_err("Failed to read pipe\n");
+ return -errno;
+ }
+ if (err2)
+ return err2;
+ ctx->flash_done = 1;
+ }
+ cmd_dev_flash_time_elapsed(ctx);
+ return 0;
+}
+
+
+static int cmd_dev_flash(struct dl *dl)
+{
+ struct cmd_dev_flash_status_ctx ctx = {.dl = dl,};
+ struct mnlu_gen_socket nlg_ntf;
+ struct nlmsghdr *nlh;
+ int pipe_r, pipe_w;
+ int pipe_fds[2];
+ pid_t pid;
+ int err;
+
+ if (dl_argv_match(dl, "help") || dl_no_arg(dl)) {
+ cmd_dev_help();
+ return 0;
+ }
+
+ nlh = mnlu_gen_socket_cmd_prepare(&dl->nlg, DEVLINK_CMD_FLASH_UPDATE,
+ NLM_F_REQUEST | NLM_F_ACK);
+
+ err = dl_argv_parse_put(nlh, dl, DL_OPT_HANDLE | DL_OPT_FLASH_FILE_NAME,
+ DL_OPT_FLASH_COMPONENT | DL_OPT_FLASH_OVERWRITE);
+ if (err)
+ return err;
+
+ err = mnlu_gen_socket_open(&nlg_ntf, DEVLINK_GENL_NAME,
+ DEVLINK_GENL_VERSION);
+ if (err)
+ return err;
+
+ err = _mnlg_socket_group_add(&nlg_ntf, DEVLINK_GENL_MCGRP_CONFIG_NAME);
+ if (err)
+ goto err_socket;
+
+ err = pipe(pipe_fds);
+ if (err == -1) {
+ err = -errno;
+ goto err_socket;
+ }
+ pipe_r = pipe_fds[0];
+ pipe_w = pipe_fds[1];
+
+ pid = fork();
+ if (pid == -1) {
+ close(pipe_w);
+ err = -errno;
+ goto out;
+ } else if (!pid) {
+ /* In child, just execute the flash and pass returned
+ * value through pipe once it is done.
+ */
+ int cc;
+
+ close(pipe_r);
+ err = _mnlg_socket_send(&dl->nlg, nlh);
+ cc = write(pipe_w, &err, sizeof(err));
+ close(pipe_w);
+ exit(cc != sizeof(err));
+ }
+ close(pipe_w);
+
+ /* initialize starting time to allow comparison for when to begin
+ * displaying a time elapsed message.
+ */
+ clock_gettime(CLOCK_MONOTONIC, &ctx.time_of_last_status);
+
+ do {
+ err = cmd_dev_flash_fds_process(&ctx, &nlg_ntf, pipe_r);
+ if (err)
+ goto out;
+ } while (!ctx.flash_done || (ctx.not_first && !ctx.received_end));
+
+ err = mnlu_gen_socket_recv_run(&dl->nlg, NULL, NULL);
+
+out:
+ close(pipe_r);
+err_socket:
+ mnlu_gen_socket_close(&nlg_ntf);
+ return err;
+}
+
+static int cmd_dev(struct dl *dl)
+{
+ if (dl_argv_match(dl, "help")) {
+ cmd_dev_help();
+ return 0;
+ } else if (dl_argv_match(dl, "show") ||
+ dl_argv_match(dl, "list") || dl_no_arg(dl)) {
+ dl_arg_inc(dl);
+ return cmd_dev_show(dl);
+ } else if (dl_argv_match(dl, "eswitch")) {
+ dl_arg_inc(dl);
+ return cmd_dev_eswitch(dl);
+ } else if (dl_argv_match(dl, "reload")) {
+ dl_arg_inc(dl);
+ return cmd_dev_reload(dl);
+ } else if (dl_argv_match(dl, "param")) {
+ dl_arg_inc(dl);
+ return cmd_dev_param(dl);
+ } else if (dl_argv_match(dl, "info")) {
+ dl_arg_inc(dl);
+ return cmd_dev_info(dl);
+ } else if (dl_argv_match(dl, "flash")) {
+ dl_arg_inc(dl);
+ return cmd_dev_flash(dl);
+ }
+ pr_err("Command \"%s\" not found\n", dl_argv(dl));
+ return -ENOENT;
+}
+
+static void cmd_port_help(void)
+{
+ pr_err("Usage: devlink port show [ DEV/PORT_INDEX ]\n");
+ pr_err(" devlink port set DEV/PORT_INDEX [ type { eth | ib | auto} ]\n");
+ pr_err(" devlink port split DEV/PORT_INDEX count COUNT\n");
+ pr_err(" devlink port unsplit DEV/PORT_INDEX\n");
+ pr_err(" devlink port function set DEV/PORT_INDEX [ hw_addr ADDR ] [ state STATE ]\n");
+ pr_err(" devlink port param set DEV/PORT_INDEX name PARAMETER value VALUE cmode { permanent | driverinit | runtime }\n");
+ pr_err(" devlink port param show [DEV/PORT_INDEX name PARAMETER]\n");
+ pr_err(" devlink port health show [ DEV/PORT_INDEX reporter REPORTER_NAME ]\n");
+ pr_err(" devlink port add DEV/PORT_INDEX flavour FLAVOUR pfnum PFNUM [ sfnum SFNUM ]\n");
+ pr_err(" devlink port del DEV/PORT_INDEX\n");
+}
+
+static const char *port_type_name(uint32_t type)
+{
+ switch (type) {
+ case DEVLINK_PORT_TYPE_NOTSET: return "notset";
+ case DEVLINK_PORT_TYPE_AUTO: return "auto";
+ case DEVLINK_PORT_TYPE_ETH: return "eth";
+ case DEVLINK_PORT_TYPE_IB: return "ib";
+ default: return "<unknown type>";
+ }
+}
+
+static const char *port_flavour_name(uint16_t flavour)
+{
+ const char *str;
+
+ str = str_map_lookup_u16(port_flavour_map, flavour);
+ return str ? str : "<unknown flavour>";
+}
+
+static void pr_out_port_pfvfsf_num(struct dl *dl, struct nlattr **tb)
+{
+ uint16_t fn_num;
+
+ if (tb[DEVLINK_ATTR_PORT_CONTROLLER_NUMBER])
+ print_uint(PRINT_ANY, "controller", " controller %u",
+ mnl_attr_get_u32(tb[DEVLINK_ATTR_PORT_CONTROLLER_NUMBER]));
+ if (tb[DEVLINK_ATTR_PORT_PCI_PF_NUMBER]) {
+ fn_num = mnl_attr_get_u16(tb[DEVLINK_ATTR_PORT_PCI_PF_NUMBER]);
+ print_uint(PRINT_ANY, "pfnum", " pfnum %u", fn_num);
+ }
+ if (tb[DEVLINK_ATTR_PORT_PCI_VF_NUMBER]) {
+ fn_num = mnl_attr_get_u16(tb[DEVLINK_ATTR_PORT_PCI_VF_NUMBER]);
+ print_uint(PRINT_ANY, "vfnum", " vfnum %u", fn_num);
+ }
+ if (tb[DEVLINK_ATTR_PORT_PCI_SF_NUMBER]) {
+ fn_num = mnl_attr_get_u32(tb[DEVLINK_ATTR_PORT_PCI_SF_NUMBER]);
+ print_uint(PRINT_ANY, "sfnum", " sfnum %u", fn_num);
+ }
+ if (tb[DEVLINK_ATTR_PORT_EXTERNAL]) {
+ uint8_t external;
+
+ external = mnl_attr_get_u8(tb[DEVLINK_ATTR_PORT_EXTERNAL]);
+ print_bool(PRINT_ANY, "external", " external %s", external);
+ }
+}
+
+static const char *port_fn_state(uint8_t state)
+{
+ const char *str;
+
+ str = str_map_lookup_u8(port_fn_state_map, state);
+ return str ? str : "<unknown state>";
+}
+
+static const char *port_fn_opstate(uint8_t state)
+{
+ const char *str;
+
+ str = str_map_lookup_u8(port_fn_opstate_map, state);
+ return str ? str : "<unknown state>";
+}
+
+static void pr_out_port_function(struct dl *dl, struct nlattr **tb_port)
+{
+ struct nlattr *tb[DEVLINK_PORT_FUNCTION_ATTR_MAX + 1] = {};
+ unsigned char *data;
+ SPRINT_BUF(hw_addr);
+ uint32_t len;
+ int err;
+
+ if (!tb_port[DEVLINK_ATTR_PORT_FUNCTION])
+ return;
+
+ err = mnl_attr_parse_nested(tb_port[DEVLINK_ATTR_PORT_FUNCTION],
+ function_attr_cb, tb);
+ if (err != MNL_CB_OK)
+ return;
+
+ pr_out_object_start(dl, "function");
+ check_indent_newline(dl);
+
+ if (tb[DEVLINK_PORT_FUNCTION_ATTR_HW_ADDR]) {
+ len = mnl_attr_get_payload_len(tb[DEVLINK_PORT_FUNCTION_ATTR_HW_ADDR]);
+ data = mnl_attr_get_payload(tb[DEVLINK_PORT_FUNCTION_ATTR_HW_ADDR]);
+
+ print_string(PRINT_ANY, "hw_addr", "hw_addr %s",
+ ll_addr_n2a(data, len, 0, hw_addr, sizeof(hw_addr)));
+ }
+ if (tb[DEVLINK_PORT_FN_ATTR_STATE]) {
+ uint8_t state;
+
+ state = mnl_attr_get_u8(tb[DEVLINK_PORT_FN_ATTR_STATE]);
+
+ print_string(PRINT_ANY, "state", " state %s",
+ port_fn_state(state));
+ }
+ if (tb[DEVLINK_PORT_FN_ATTR_OPSTATE]) {
+ uint8_t state;
+
+ state = mnl_attr_get_u8(tb[DEVLINK_PORT_FN_ATTR_OPSTATE]);
+
+ print_string(PRINT_ANY, "opstate", " opstate %s",
+ port_fn_opstate(state));
+ }
+
+ if (!dl->json_output)
+ __pr_out_indent_dec();
+ pr_out_object_end(dl);
+}
+
+static void pr_out_port(struct dl *dl, struct nlattr **tb)
+{
+ struct nlattr *pt_attr = tb[DEVLINK_ATTR_PORT_TYPE];
+ struct nlattr *dpt_attr = tb[DEVLINK_ATTR_PORT_DESIRED_TYPE];
+
+ pr_out_port_handle_start(dl, tb, false);
+ check_indent_newline(dl);
+ if (pt_attr) {
+ uint16_t port_type = mnl_attr_get_u16(pt_attr);
+
+ print_string(PRINT_ANY, "type", "type %s",
+ port_type_name(port_type));
+ if (dpt_attr) {
+ uint16_t des_port_type = mnl_attr_get_u16(dpt_attr);
+
+ if (port_type != des_port_type)
+ print_string(PRINT_ANY, "des_type", " des_type %s",
+ port_type_name(des_port_type));
+ }
+ }
+ if (tb[DEVLINK_ATTR_PORT_NETDEV_NAME]) {
+ print_string(PRINT_ANY, "netdev", " netdev %s",
+ mnl_attr_get_str(tb[DEVLINK_ATTR_PORT_NETDEV_NAME]));
+ }
+ if (tb[DEVLINK_ATTR_PORT_IBDEV_NAME]) {
+ print_string(PRINT_ANY, "ibdev", " ibdev %s",
+ mnl_attr_get_str(tb[DEVLINK_ATTR_PORT_IBDEV_NAME]));
+ }
+ if (tb[DEVLINK_ATTR_PORT_FLAVOUR]) {
+ uint16_t port_flavour =
+ mnl_attr_get_u16(tb[DEVLINK_ATTR_PORT_FLAVOUR]);
+
+ print_string(PRINT_ANY, "flavour", " flavour %s",
+ port_flavour_name(port_flavour));
+
+ switch (port_flavour) {
+ case DEVLINK_PORT_FLAVOUR_PCI_PF:
+ case DEVLINK_PORT_FLAVOUR_PCI_VF:
+ case DEVLINK_PORT_FLAVOUR_PCI_SF:
+ pr_out_port_pfvfsf_num(dl, tb);
+ break;
+ default:
+ break;
+ }
+ }
+ if (tb[DEVLINK_ATTR_PORT_NUMBER]) {
+ uint32_t port_number;
+
+ port_number = mnl_attr_get_u32(tb[DEVLINK_ATTR_PORT_NUMBER]);
+ print_uint(PRINT_ANY, "port", " port %u", port_number);
+ }
+ if (tb[DEVLINK_ATTR_PORT_SPLIT_GROUP])
+ print_uint(PRINT_ANY, "split_group", " split_group %u",
+ mnl_attr_get_u32(tb[DEVLINK_ATTR_PORT_SPLIT_GROUP]));
+ if (tb[DEVLINK_ATTR_PORT_SPLITTABLE])
+ print_bool(PRINT_ANY, "splittable", " splittable %s",
+ mnl_attr_get_u8(tb[DEVLINK_ATTR_PORT_SPLITTABLE]));
+ if (tb[DEVLINK_ATTR_PORT_LANES])
+ print_uint(PRINT_ANY, "lanes", " lanes %u",
+ mnl_attr_get_u32(tb[DEVLINK_ATTR_PORT_LANES]));
+
+ pr_out_port_function(dl, tb);
+ pr_out_port_handle_end(dl);
+}
+
+static int cmd_port_show_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct dl *dl = data;
+ struct nlattr *tb[DEVLINK_ATTR_MAX + 1] = {};
+ struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+
+ mnl_attr_parse(nlh, sizeof(*genl), attr_cb, tb);
+ if (!tb[DEVLINK_ATTR_BUS_NAME] || !tb[DEVLINK_ATTR_DEV_NAME] ||
+ !tb[DEVLINK_ATTR_PORT_INDEX])
+ return MNL_CB_ERROR;
+ pr_out_port(dl, tb);
+ return MNL_CB_OK;
+}
+
+static int cmd_port_show(struct dl *dl)
+{
+ struct nlmsghdr *nlh;
+ uint16_t flags = NLM_F_REQUEST | NLM_F_ACK;
+ int err;
+
+ if (dl_argc(dl) == 0)
+ flags |= NLM_F_DUMP;
+
+ nlh = mnlu_gen_socket_cmd_prepare(&dl->nlg, DEVLINK_CMD_PORT_GET, flags);
+
+ if (dl_argc(dl) > 0) {
+ err = dl_argv_parse_put(nlh, dl, DL_OPT_HANDLEP, 0);
+ if (err)
+ return err;
+ }
+
+ pr_out_section_start(dl, "port");
+ err = mnlu_gen_socket_sndrcv(&dl->nlg, nlh, cmd_port_show_cb, dl);
+ pr_out_section_end(dl);
+ return err;
+}
+
+static int cmd_port_set(struct dl *dl)
+{
+ struct nlmsghdr *nlh;
+ int err;
+
+ nlh = mnlu_gen_socket_cmd_prepare(&dl->nlg, DEVLINK_CMD_PORT_SET,
+ NLM_F_REQUEST | NLM_F_ACK);
+
+ err = dl_argv_parse_put(nlh, dl, DL_OPT_HANDLEP | DL_OPT_PORT_TYPE, 0);
+ if (err)
+ return err;
+
+ return mnlu_gen_socket_sndrcv(&dl->nlg, nlh, NULL, NULL);
+}
+
+static int cmd_port_split(struct dl *dl)
+{
+ struct nlmsghdr *nlh;
+ int err;
+
+ nlh = mnlu_gen_socket_cmd_prepare(&dl->nlg, DEVLINK_CMD_PORT_SPLIT,
+ NLM_F_REQUEST | NLM_F_ACK);
+
+ err = dl_argv_parse_put(nlh, dl, DL_OPT_HANDLEP | DL_OPT_PORT_COUNT, 0);
+ if (err)
+ return err;
+
+ return mnlu_gen_socket_sndrcv(&dl->nlg, nlh, NULL, NULL);
+}
+
+static int cmd_port_unsplit(struct dl *dl)
+{
+ struct nlmsghdr *nlh;
+ int err;
+
+ nlh = mnlu_gen_socket_cmd_prepare(&dl->nlg, DEVLINK_CMD_PORT_UNSPLIT,
+ NLM_F_REQUEST | NLM_F_ACK);
+
+ err = dl_argv_parse_put(nlh, dl, DL_OPT_HANDLEP, 0);
+ if (err)
+ return err;
+
+ return mnlu_gen_socket_sndrcv(&dl->nlg, nlh, NULL, NULL);
+}
+
+static int cmd_port_param_show(struct dl *dl)
+{
+ uint16_t flags = NLM_F_REQUEST | NLM_F_ACK;
+ struct nlmsghdr *nlh;
+ int err;
+
+ if (dl_argc(dl) == 0)
+ flags |= NLM_F_DUMP;
+
+ nlh = mnlu_gen_socket_cmd_prepare(&dl->nlg, DEVLINK_CMD_PORT_PARAM_GET,
+ flags);
+
+ if (dl_argc(dl) > 0) {
+ err = dl_argv_parse_put(nlh, dl, DL_OPT_HANDLEP |
+ DL_OPT_PARAM_NAME, 0);
+ if (err)
+ return err;
+ }
+
+ pr_out_section_start(dl, "param");
+ err = mnlu_gen_socket_sndrcv(&dl->nlg, nlh, cmd_port_param_show_cb, dl);
+ pr_out_section_end(dl);
+
+ return err;
+}
+
+static void cmd_port_function_help(void)
+{
+ pr_err("Usage: devlink port function set DEV/PORT_INDEX [ hw_addr ADDR ] [ state STATE ]\n");
+}
+
+static int cmd_port_function_set(struct dl *dl)
+{
+ struct nlmsghdr *nlh;
+ int err;
+
+ if (dl_no_arg(dl)) {
+ cmd_port_function_help();
+ return 0;
+ }
+ nlh = mnlu_gen_socket_cmd_prepare(&dl->nlg, DEVLINK_CMD_PORT_SET,
+ NLM_F_REQUEST | NLM_F_ACK);
+
+ err = dl_argv_parse_put(nlh, dl, DL_OPT_HANDLEP,
+ DL_OPT_PORT_FUNCTION_HW_ADDR | DL_OPT_PORT_FUNCTION_STATE);
+ if (err)
+ return err;
+
+ return mnlu_gen_socket_sndrcv(&dl->nlg, nlh, NULL, NULL);
+}
+
+static int cmd_port_param_set_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+ struct nlattr *nla_param[DEVLINK_ATTR_MAX + 1] = {};
+ struct nlattr *tb[DEVLINK_ATTR_MAX + 1] = {};
+ struct nlattr *param_value_attr;
+ enum devlink_param_cmode cmode;
+ struct param_ctx *ctx = data;
+ struct dl *dl = ctx->dl;
+ int nla_type;
+ int err;
+
+ mnl_attr_parse(nlh, sizeof(*genl), attr_cb, tb);
+ if (!tb[DEVLINK_ATTR_BUS_NAME] || !tb[DEVLINK_ATTR_DEV_NAME] ||
+ !tb[DEVLINK_ATTR_PORT_INDEX] || !tb[DEVLINK_ATTR_PARAM])
+ return MNL_CB_ERROR;
+
+ err = mnl_attr_parse_nested(tb[DEVLINK_ATTR_PARAM], attr_cb, nla_param);
+ if (err != MNL_CB_OK)
+ return MNL_CB_ERROR;
+
+ if (!nla_param[DEVLINK_ATTR_PARAM_TYPE] ||
+ !nla_param[DEVLINK_ATTR_PARAM_VALUES_LIST])
+ return MNL_CB_ERROR;
+
+ nla_type = mnl_attr_get_u8(nla_param[DEVLINK_ATTR_PARAM_TYPE]);
+ mnl_attr_for_each_nested(param_value_attr,
+ nla_param[DEVLINK_ATTR_PARAM_VALUES_LIST]) {
+ struct nlattr *nla_value[DEVLINK_ATTR_MAX + 1] = {};
+ struct nlattr *val_attr;
+
+ err = mnl_attr_parse_nested(param_value_attr,
+ attr_cb, nla_value);
+ if (err != MNL_CB_OK)
+ return MNL_CB_ERROR;
+
+ if (!nla_value[DEVLINK_ATTR_PARAM_VALUE_CMODE] ||
+ (nla_type != MNL_TYPE_FLAG &&
+ !nla_value[DEVLINK_ATTR_PARAM_VALUE_DATA]))
+ return MNL_CB_ERROR;
+
+ cmode = mnl_attr_get_u8(nla_value[DEVLINK_ATTR_PARAM_VALUE_CMODE]);
+ if (cmode == dl->opts.cmode) {
+ val_attr = nla_value[DEVLINK_ATTR_PARAM_VALUE_DATA];
+ switch (nla_type) {
+ case MNL_TYPE_U8:
+ ctx->value.vu8 = mnl_attr_get_u8(val_attr);
+ break;
+ case MNL_TYPE_U16:
+ ctx->value.vu16 = mnl_attr_get_u16(val_attr);
+ break;
+ case MNL_TYPE_U32:
+ ctx->value.vu32 = mnl_attr_get_u32(val_attr);
+ break;
+ case MNL_TYPE_STRING:
+ ctx->value.vstr = mnl_attr_get_str(val_attr);
+ break;
+ case MNL_TYPE_FLAG:
+ ctx->value.vbool = val_attr ? true : false;
+ break;
+ }
+ break;
+ }
+ }
+ ctx->nla_type = nla_type;
+ return MNL_CB_OK;
+}
+
+static int cmd_port_param_set(struct dl *dl)
+{
+ struct param_ctx ctx = {};
+ struct nlmsghdr *nlh;
+ bool conv_exists;
+ uint32_t val_u32 = 0;
+ uint16_t val_u16;
+ uint8_t val_u8;
+ bool val_bool;
+ int err;
+
+ err = dl_argv_parse(dl, DL_OPT_HANDLEP |
+ DL_OPT_PARAM_NAME |
+ DL_OPT_PARAM_VALUE |
+ DL_OPT_PARAM_CMODE, 0);
+ if (err)
+ return err;
+
+ /* Get value type */
+ nlh = mnlu_gen_socket_cmd_prepare(&dl->nlg, DEVLINK_CMD_PORT_PARAM_GET,
+ NLM_F_REQUEST | NLM_F_ACK);
+ dl_opts_put(nlh, dl);
+
+ ctx.dl = dl;
+ err = mnlu_gen_socket_sndrcv(&dl->nlg, nlh, cmd_port_param_set_cb, &ctx);
+ if (err)
+ return err;
+
+ nlh = mnlu_gen_socket_cmd_prepare(&dl->nlg, DEVLINK_CMD_PORT_PARAM_SET,
+ NLM_F_REQUEST | NLM_F_ACK);
+ dl_opts_put(nlh, dl);
+
+ conv_exists = param_val_conv_exists(param_val_conv, PARAM_VAL_CONV_LEN,
+ dl->opts.param_name);
+
+ mnl_attr_put_u8(nlh, DEVLINK_ATTR_PARAM_TYPE, ctx.nla_type);
+ switch (ctx.nla_type) {
+ case MNL_TYPE_U8:
+ if (conv_exists) {
+ err = param_val_conv_uint_get(param_val_conv,
+ PARAM_VAL_CONV_LEN,
+ dl->opts.param_name,
+ dl->opts.param_value,
+ &val_u32);
+ val_u8 = val_u32;
+ } else {
+ err = strtouint8_t(dl->opts.param_value, &val_u8);
+ }
+ if (err)
+ goto err_param_value_parse;
+ if (val_u8 == ctx.value.vu8)
+ return 0;
+ mnl_attr_put_u8(nlh, DEVLINK_ATTR_PARAM_VALUE_DATA, val_u8);
+ break;
+ case MNL_TYPE_U16:
+ if (conv_exists) {
+ err = param_val_conv_uint_get(param_val_conv,
+ PARAM_VAL_CONV_LEN,
+ dl->opts.param_name,
+ dl->opts.param_value,
+ &val_u32);
+ val_u16 = val_u32;
+ } else {
+ err = strtouint16_t(dl->opts.param_value, &val_u16);
+ }
+ if (err)
+ goto err_param_value_parse;
+ if (val_u16 == ctx.value.vu16)
+ return 0;
+ mnl_attr_put_u16(nlh, DEVLINK_ATTR_PARAM_VALUE_DATA, val_u16);
+ break;
+ case MNL_TYPE_U32:
+ if (conv_exists)
+ err = param_val_conv_uint_get(param_val_conv,
+ PARAM_VAL_CONV_LEN,
+ dl->opts.param_name,
+ dl->opts.param_value,
+ &val_u32);
+ else
+ err = strtouint32_t(dl->opts.param_value, &val_u32);
+ if (err)
+ goto err_param_value_parse;
+ if (val_u32 == ctx.value.vu32)
+ return 0;
+ mnl_attr_put_u32(nlh, DEVLINK_ATTR_PARAM_VALUE_DATA, val_u32);
+ break;
+ case MNL_TYPE_FLAG:
+ err = strtobool(dl->opts.param_value, &val_bool);
+ if (err)
+ goto err_param_value_parse;
+ if (val_bool == ctx.value.vbool)
+ return 0;
+ if (val_bool)
+ mnl_attr_put(nlh, DEVLINK_ATTR_PARAM_VALUE_DATA,
+ 0, NULL);
+ break;
+ case MNL_TYPE_STRING:
+ mnl_attr_put_strz(nlh, DEVLINK_ATTR_PARAM_VALUE_DATA,
+ dl->opts.param_value);
+ if (!strcmp(dl->opts.param_value, ctx.value.vstr))
+ return 0;
+ break;
+ default:
+ printf("Value type not supported\n");
+ return -ENOTSUP;
+ }
+ return mnlu_gen_socket_sndrcv(&dl->nlg, nlh, NULL, NULL);
+
+err_param_value_parse:
+ pr_err("Value \"%s\" is not a number or not within range\n",
+ dl->opts.param_value);
+ return err;
+}
+
+static int cmd_port_param(struct dl *dl)
+{
+ if (dl_argv_match(dl, "help")) {
+ cmd_port_help();
+ return 0;
+ } else if (dl_argv_match(dl, "show") ||
+ dl_argv_match(dl, "list") || dl_no_arg(dl)) {
+ dl_arg_inc(dl);
+ return cmd_port_param_show(dl);
+ } else if (dl_argv_match(dl, "set")) {
+ dl_arg_inc(dl);
+ return cmd_port_param_set(dl);
+ }
+ pr_err("Command \"%s\" not found\n", dl_argv(dl));
+ return -ENOENT;
+}
+
+static int cmd_port_function(struct dl *dl)
+{
+ if (dl_argv_match(dl, "help") || dl_no_arg(dl)) {
+ cmd_port_function_help();
+ return 0;
+ } else if (dl_argv_match(dl, "set")) {
+ dl_arg_inc(dl);
+ return cmd_port_function_set(dl);
+ }
+ pr_err("Command \"%s\" not found\n", dl_argv(dl));
+ return -ENOENT;
+}
+
+static int cmd_health(struct dl *dl);
+static int __cmd_health_show(struct dl *dl, bool show_device, bool show_port);
+
+static void cmd_port_add_help(void)
+{
+ pr_err(" devlink port add { DEV | DEV/PORT_INDEX } flavour FLAVOUR pfnum PFNUM [ sfnum SFNUM ]\n");
+}
+
+static int cmd_port_add(struct dl *dl)
+{
+ struct nlmsghdr *nlh;
+ int err;
+
+ if (dl_argv_match(dl, "help") || dl_no_arg(dl)) {
+ cmd_port_add_help();
+ return 0;
+ }
+
+ nlh = mnlu_gen_socket_cmd_prepare(&dl->nlg, DEVLINK_CMD_PORT_NEW,
+ NLM_F_REQUEST | NLM_F_ACK);
+
+ err = dl_argv_parse_put(nlh, dl, DL_OPT_HANDLE | DL_OPT_HANDLEP |
+ DL_OPT_PORT_FLAVOUR | DL_OPT_PORT_PFNUMBER,
+ DL_OPT_PORT_SFNUMBER);
+ if (err)
+ return err;
+
+ return mnlu_gen_socket_sndrcv(&dl->nlg, nlh, cmd_port_show_cb, dl);
+}
+
+static void cmd_port_del_help(void)
+{
+ pr_err(" devlink port del DEV/PORT_INDEX\n");
+}
+
+static int cmd_port_del(struct dl *dl)
+{
+ struct nlmsghdr *nlh;
+ int err;
+
+ if (dl_argv_match(dl, "help") || dl_no_arg(dl)) {
+ cmd_port_del_help();
+ return 0;
+ }
+
+ nlh = mnlu_gen_socket_cmd_prepare(&dl->nlg, DEVLINK_CMD_PORT_DEL,
+ NLM_F_REQUEST | NLM_F_ACK);
+
+ err = dl_argv_parse_put(nlh, dl, DL_OPT_HANDLEP, 0);
+ if (err)
+ return err;
+
+ return mnlu_gen_socket_sndrcv(&dl->nlg, nlh, NULL, NULL);
+}
+
+static int cmd_port(struct dl *dl)
+{
+ if (dl_argv_match(dl, "help")) {
+ cmd_port_help();
+ return 0;
+ } else if (dl_argv_match(dl, "show") ||
+ dl_argv_match(dl, "list") || dl_no_arg(dl)) {
+ dl_arg_inc(dl);
+ return cmd_port_show(dl);
+ } else if (dl_argv_match(dl, "set")) {
+ dl_arg_inc(dl);
+ return cmd_port_set(dl);
+ } else if (dl_argv_match(dl, "split")) {
+ dl_arg_inc(dl);
+ return cmd_port_split(dl);
+ } else if (dl_argv_match(dl, "unsplit")) {
+ dl_arg_inc(dl);
+ return cmd_port_unsplit(dl);
+ } else if (dl_argv_match(dl, "param")) {
+ dl_arg_inc(dl);
+ return cmd_port_param(dl);
+ } else if (dl_argv_match(dl, "function")) {
+ dl_arg_inc(dl);
+ return cmd_port_function(dl);
+ } else if (dl_argv_match(dl, "health")) {
+ dl_arg_inc(dl);
+ if (dl_argv_match(dl, "list") || dl_no_arg(dl)
+ || (dl_argv_match(dl, "show") && dl_argc(dl) == 1)) {
+ dl_arg_inc(dl);
+ return __cmd_health_show(dl, false, true);
+ } else {
+ return cmd_health(dl);
+ }
+ } else if (dl_argv_match(dl, "add")) {
+ dl_arg_inc(dl);
+ return cmd_port_add(dl);
+ } else if (dl_argv_match(dl, "del")) {
+ dl_arg_inc(dl);
+ return cmd_port_del(dl);
+ }
+
+ pr_err("Command \"%s\" not found\n", dl_argv(dl));
+ return -ENOENT;
+}
+
+static void cmd_sb_help(void)
+{
+ pr_err("Usage: devlink sb show [ DEV [ sb SB_INDEX ] ]\n");
+ pr_err(" devlink sb pool show [ DEV [ sb SB_INDEX ] pool POOL_INDEX ]\n");
+ pr_err(" devlink sb pool set DEV [ sb SB_INDEX ] pool POOL_INDEX\n");
+ pr_err(" size POOL_SIZE thtype { static | dynamic }\n");
+ pr_err(" devlink sb port pool show [ DEV/PORT_INDEX [ sb SB_INDEX ]\n");
+ pr_err(" pool POOL_INDEX ]\n");
+ pr_err(" devlink sb port pool set DEV/PORT_INDEX [ sb SB_INDEX ]\n");
+ pr_err(" pool POOL_INDEX th THRESHOLD\n");
+ pr_err(" devlink sb tc bind show [ DEV/PORT_INDEX [ sb SB_INDEX ] tc TC_INDEX\n");
+ pr_err(" type { ingress | egress } ]\n");
+ pr_err(" devlink sb tc bind set DEV/PORT_INDEX [ sb SB_INDEX ] tc TC_INDEX\n");
+ pr_err(" type { ingress | egress } pool POOL_INDEX\n");
+ pr_err(" th THRESHOLD\n");
+ pr_err(" devlink sb occupancy show { DEV | DEV/PORT_INDEX } [ sb SB_INDEX ]\n");
+ pr_err(" devlink sb occupancy snapshot DEV [ sb SB_INDEX ]\n");
+ pr_err(" devlink sb occupancy clearmax DEV [ sb SB_INDEX ]\n");
+}
+
+static void pr_out_sb(struct dl *dl, struct nlattr **tb)
+{
+ pr_out_handle_start_arr(dl, tb);
+ check_indent_newline(dl);
+ print_uint(PRINT_ANY, "sb", "sb %u",
+ mnl_attr_get_u32(tb[DEVLINK_ATTR_SB_INDEX]));
+ print_uint(PRINT_ANY, "size", " size %u",
+ mnl_attr_get_u32(tb[DEVLINK_ATTR_SB_SIZE]));
+ print_uint(PRINT_ANY, "ing_pools", " ing_pools %u",
+ mnl_attr_get_u16(tb[DEVLINK_ATTR_SB_INGRESS_POOL_COUNT]));
+ print_uint(PRINT_ANY, "eg_pools", " eg_pools %u",
+ mnl_attr_get_u16(tb[DEVLINK_ATTR_SB_EGRESS_POOL_COUNT]));
+ print_uint(PRINT_ANY, "ing_tcs", " ing_tcs %u",
+ mnl_attr_get_u16(tb[DEVLINK_ATTR_SB_INGRESS_TC_COUNT]));
+ print_uint(PRINT_ANY, "eg_tcs", " eg_tcs %u",
+ mnl_attr_get_u16(tb[DEVLINK_ATTR_SB_EGRESS_TC_COUNT]));
+ pr_out_handle_end(dl);
+}
+
+static int cmd_sb_show_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct dl *dl = data;
+ struct nlattr *tb[DEVLINK_ATTR_MAX + 1] = {};
+ struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+
+ mnl_attr_parse(nlh, sizeof(*genl), attr_cb, tb);
+ if (!tb[DEVLINK_ATTR_BUS_NAME] || !tb[DEVLINK_ATTR_DEV_NAME] ||
+ !tb[DEVLINK_ATTR_SB_INDEX] || !tb[DEVLINK_ATTR_SB_SIZE] ||
+ !tb[DEVLINK_ATTR_SB_INGRESS_POOL_COUNT] ||
+ !tb[DEVLINK_ATTR_SB_EGRESS_POOL_COUNT] ||
+ !tb[DEVLINK_ATTR_SB_INGRESS_TC_COUNT] ||
+ !tb[DEVLINK_ATTR_SB_EGRESS_TC_COUNT])
+ return MNL_CB_ERROR;
+ pr_out_sb(dl, tb);
+ return MNL_CB_OK;
+}
+
+static int cmd_sb_show(struct dl *dl)
+{
+ struct nlmsghdr *nlh;
+ uint16_t flags = NLM_F_REQUEST | NLM_F_ACK;
+ int err;
+
+ if (dl_argc(dl) == 0)
+ flags |= NLM_F_DUMP;
+
+ nlh = mnlu_gen_socket_cmd_prepare(&dl->nlg, DEVLINK_CMD_SB_GET, flags);
+
+ if (dl_argc(dl) > 0) {
+ err = dl_argv_parse_put(nlh, dl, DL_OPT_HANDLE, DL_OPT_SB);
+ if (err)
+ return err;
+ }
+
+ pr_out_section_start(dl, "sb");
+ err = mnlu_gen_socket_sndrcv(&dl->nlg, nlh, cmd_sb_show_cb, dl);
+ pr_out_section_end(dl);
+ return err;
+}
+
+static const char *pool_type_name(uint8_t type)
+{
+ switch (type) {
+ case DEVLINK_SB_POOL_TYPE_INGRESS: return "ingress";
+ case DEVLINK_SB_POOL_TYPE_EGRESS: return "egress";
+ default: return "<unknown type>";
+ }
+}
+
+static const char *threshold_type_name(uint8_t type)
+{
+ switch (type) {
+ case DEVLINK_SB_THRESHOLD_TYPE_STATIC: return "static";
+ case DEVLINK_SB_THRESHOLD_TYPE_DYNAMIC: return "dynamic";
+ default: return "<unknown type>";
+ }
+}
+
+static void pr_out_sb_pool(struct dl *dl, struct nlattr **tb)
+{
+ pr_out_handle_start_arr(dl, tb);
+ check_indent_newline(dl);
+ print_uint(PRINT_ANY, "sb", "sb %u",
+ mnl_attr_get_u32(tb[DEVLINK_ATTR_SB_INDEX]));
+ print_uint(PRINT_ANY, "pool", " pool %u",
+ mnl_attr_get_u16(tb[DEVLINK_ATTR_SB_POOL_INDEX]));
+ print_string(PRINT_ANY, "type", " type %s",
+ pool_type_name(mnl_attr_get_u8(tb[DEVLINK_ATTR_SB_POOL_TYPE])));
+ print_uint(PRINT_ANY, "size", " size %u",
+ mnl_attr_get_u32(tb[DEVLINK_ATTR_SB_POOL_SIZE]));
+ print_string(PRINT_ANY, "thtype", " thtype %s",
+ threshold_type_name(mnl_attr_get_u8(tb[DEVLINK_ATTR_SB_POOL_THRESHOLD_TYPE])));
+ if (tb[DEVLINK_ATTR_SB_POOL_CELL_SIZE])
+ print_uint(PRINT_ANY, "cell_size", " cell size %u",
+ mnl_attr_get_u32(tb[DEVLINK_ATTR_SB_POOL_CELL_SIZE]));
+ pr_out_handle_end(dl);
+}
+
+static int cmd_sb_pool_show_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct dl *dl = data;
+ struct nlattr *tb[DEVLINK_ATTR_MAX + 1] = {};
+ struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+
+ mnl_attr_parse(nlh, sizeof(*genl), attr_cb, tb);
+ if (!tb[DEVLINK_ATTR_BUS_NAME] || !tb[DEVLINK_ATTR_DEV_NAME] ||
+ !tb[DEVLINK_ATTR_SB_INDEX] || !tb[DEVLINK_ATTR_SB_POOL_INDEX] ||
+ !tb[DEVLINK_ATTR_SB_POOL_TYPE] || !tb[DEVLINK_ATTR_SB_POOL_SIZE] ||
+ !tb[DEVLINK_ATTR_SB_POOL_THRESHOLD_TYPE])
+ return MNL_CB_ERROR;
+ pr_out_sb_pool(dl, tb);
+ return MNL_CB_OK;
+}
+
+static int cmd_sb_pool_show(struct dl *dl)
+{
+ struct nlmsghdr *nlh;
+ uint16_t flags = NLM_F_REQUEST | NLM_F_ACK;
+ int err;
+
+ if (dl_argc(dl) == 0)
+ flags |= NLM_F_DUMP;
+
+ nlh = mnlu_gen_socket_cmd_prepare(&dl->nlg, DEVLINK_CMD_SB_POOL_GET, flags);
+
+ if (dl_argc(dl) > 0) {
+ err = dl_argv_parse_put(nlh, dl, DL_OPT_HANDLE | DL_OPT_SB_POOL,
+ DL_OPT_SB);
+ if (err)
+ return err;
+ }
+
+ pr_out_section_start(dl, "pool");
+ err = mnlu_gen_socket_sndrcv(&dl->nlg, nlh, cmd_sb_pool_show_cb, dl);
+ pr_out_section_end(dl);
+ return err;
+}
+
+static int cmd_sb_pool_set(struct dl *dl)
+{
+ struct nlmsghdr *nlh;
+ int err;
+
+ nlh = mnlu_gen_socket_cmd_prepare(&dl->nlg, DEVLINK_CMD_SB_POOL_SET,
+ NLM_F_REQUEST | NLM_F_ACK);
+
+ err = dl_argv_parse_put(nlh, dl, DL_OPT_HANDLE | DL_OPT_SB_POOL |
+ DL_OPT_SB_SIZE | DL_OPT_SB_THTYPE, DL_OPT_SB);
+ if (err)
+ return err;
+
+ return mnlu_gen_socket_sndrcv(&dl->nlg, nlh, NULL, NULL);
+}
+
+static int cmd_sb_pool(struct dl *dl)
+{
+ if (dl_argv_match(dl, "help")) {
+ cmd_sb_help();
+ return 0;
+ } else if (dl_argv_match(dl, "show") ||
+ dl_argv_match(dl, "list") || dl_no_arg(dl)) {
+ dl_arg_inc(dl);
+ return cmd_sb_pool_show(dl);
+ } else if (dl_argv_match(dl, "set")) {
+ dl_arg_inc(dl);
+ return cmd_sb_pool_set(dl);
+ }
+ pr_err("Command \"%s\" not found\n", dl_argv(dl));
+ return -ENOENT;
+}
+
+static void pr_out_sb_port_pool(struct dl *dl, struct nlattr **tb)
+{
+ pr_out_port_handle_start_arr(dl, tb, true);
+ check_indent_newline(dl);
+ print_uint(PRINT_ANY, "sb", "sb %u",
+ mnl_attr_get_u32(tb[DEVLINK_ATTR_SB_INDEX]));
+ print_uint(PRINT_ANY, "pool", " pool %u",
+ mnl_attr_get_u16(tb[DEVLINK_ATTR_SB_POOL_INDEX]));
+ print_uint(PRINT_ANY, "threshold", " threshold %u",
+ mnl_attr_get_u32(tb[DEVLINK_ATTR_SB_THRESHOLD]));
+ pr_out_port_handle_end(dl);
+}
+
+static int cmd_sb_port_pool_show_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct dl *dl = data;
+ struct nlattr *tb[DEVLINK_ATTR_MAX + 1] = {};
+ struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+
+ mnl_attr_parse(nlh, sizeof(*genl), attr_cb, tb);
+ if (!tb[DEVLINK_ATTR_BUS_NAME] || !tb[DEVLINK_ATTR_DEV_NAME] ||
+ !tb[DEVLINK_ATTR_PORT_INDEX] || !tb[DEVLINK_ATTR_SB_INDEX] ||
+ !tb[DEVLINK_ATTR_SB_POOL_INDEX] || !tb[DEVLINK_ATTR_SB_THRESHOLD])
+ return MNL_CB_ERROR;
+ pr_out_sb_port_pool(dl, tb);
+ return MNL_CB_OK;
+}
+
+static int cmd_sb_port_pool_show(struct dl *dl)
+{
+ struct nlmsghdr *nlh;
+ uint16_t flags = NLM_F_REQUEST | NLM_F_ACK;
+ int err;
+
+ if (dl_argc(dl) == 0)
+ flags |= NLM_F_DUMP;
+
+ nlh = mnlu_gen_socket_cmd_prepare(&dl->nlg, DEVLINK_CMD_SB_PORT_POOL_GET, flags);
+
+ if (dl_argc(dl) > 0) {
+ err = dl_argv_parse_put(nlh, dl,
+ DL_OPT_HANDLEP | DL_OPT_SB_POOL,
+ DL_OPT_SB);
+ if (err)
+ return err;
+ }
+
+ pr_out_section_start(dl, "port_pool");
+ err = mnlu_gen_socket_sndrcv(&dl->nlg, nlh, cmd_sb_port_pool_show_cb, dl);
+ pr_out_section_end(dl);
+ return 0;
+}
+
+static int cmd_sb_port_pool_set(struct dl *dl)
+{
+ struct nlmsghdr *nlh;
+ int err;
+
+ nlh = mnlu_gen_socket_cmd_prepare(&dl->nlg, DEVLINK_CMD_SB_PORT_POOL_SET,
+ NLM_F_REQUEST | NLM_F_ACK);
+
+ err = dl_argv_parse_put(nlh, dl, DL_OPT_HANDLEP | DL_OPT_SB_POOL |
+ DL_OPT_SB_TH, DL_OPT_SB);
+ if (err)
+ return err;
+
+ return mnlu_gen_socket_sndrcv(&dl->nlg, nlh, NULL, NULL);
+}
+
+static int cmd_sb_port_pool(struct dl *dl)
+{
+ if (dl_argv_match(dl, "help")) {
+ cmd_sb_help();
+ return 0;
+ } else if (dl_argv_match(dl, "show") ||
+ dl_argv_match(dl, "list") || dl_no_arg(dl)) {
+ dl_arg_inc(dl);
+ return cmd_sb_port_pool_show(dl);
+ } else if (dl_argv_match(dl, "set")) {
+ dl_arg_inc(dl);
+ return cmd_sb_port_pool_set(dl);
+ }
+ pr_err("Command \"%s\" not found\n", dl_argv(dl));
+ return -ENOENT;
+}
+
+static int cmd_sb_port(struct dl *dl)
+{
+ if (dl_argv_match(dl, "help") || dl_no_arg(dl)) {
+ cmd_sb_help();
+ return 0;
+ } else if (dl_argv_match(dl, "pool")) {
+ dl_arg_inc(dl);
+ return cmd_sb_port_pool(dl);
+ }
+ pr_err("Command \"%s\" not found\n", dl_argv(dl));
+ return -ENOENT;
+}
+
+static void pr_out_sb_tc_bind(struct dl *dl, struct nlattr **tb)
+{
+ pr_out_port_handle_start_arr(dl, tb, true);
+ check_indent_newline(dl);
+ print_uint(PRINT_ANY, "sb", "sb %u",
+ mnl_attr_get_u32(tb[DEVLINK_ATTR_SB_INDEX]));
+ print_uint(PRINT_ANY, "tc", " tc %u",
+ mnl_attr_get_u16(tb[DEVLINK_ATTR_SB_TC_INDEX]));
+ print_string(PRINT_ANY, "type", " type %s",
+ pool_type_name(mnl_attr_get_u8(tb[DEVLINK_ATTR_SB_POOL_TYPE])));
+ print_uint(PRINT_ANY, "pool", " pool %u",
+ mnl_attr_get_u16(tb[DEVLINK_ATTR_SB_POOL_INDEX]));
+ print_uint(PRINT_ANY, "threshold", " threshold %u",
+ mnl_attr_get_u32(tb[DEVLINK_ATTR_SB_THRESHOLD]));
+ pr_out_port_handle_end(dl);
+}
+
+static int cmd_sb_tc_bind_show_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct dl *dl = data;
+ struct nlattr *tb[DEVLINK_ATTR_MAX + 1] = {};
+ struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+
+ mnl_attr_parse(nlh, sizeof(*genl), attr_cb, tb);
+ if (!tb[DEVLINK_ATTR_BUS_NAME] || !tb[DEVLINK_ATTR_DEV_NAME] ||
+ !tb[DEVLINK_ATTR_PORT_INDEX] || !tb[DEVLINK_ATTR_SB_INDEX] ||
+ !tb[DEVLINK_ATTR_SB_TC_INDEX] || !tb[DEVLINK_ATTR_SB_POOL_TYPE] ||
+ !tb[DEVLINK_ATTR_SB_POOL_INDEX] || !tb[DEVLINK_ATTR_SB_THRESHOLD])
+ return MNL_CB_ERROR;
+ pr_out_sb_tc_bind(dl, tb);
+ return MNL_CB_OK;
+}
+
+static int cmd_sb_tc_bind_show(struct dl *dl)
+{
+ struct nlmsghdr *nlh;
+ uint16_t flags = NLM_F_REQUEST | NLM_F_ACK;
+ int err;
+
+ if (dl_argc(dl) == 0)
+ flags |= NLM_F_DUMP;
+
+ nlh = mnlu_gen_socket_cmd_prepare(&dl->nlg, DEVLINK_CMD_SB_TC_POOL_BIND_GET, flags);
+
+ if (dl_argc(dl) > 0) {
+ err = dl_argv_parse_put(nlh, dl, DL_OPT_HANDLEP | DL_OPT_SB_TC |
+ DL_OPT_SB_TYPE, DL_OPT_SB);
+ if (err)
+ return err;
+ }
+
+ pr_out_section_start(dl, "tc_bind");
+ err = mnlu_gen_socket_sndrcv(&dl->nlg, nlh, cmd_sb_tc_bind_show_cb, dl);
+ pr_out_section_end(dl);
+ return err;
+}
+
+static int cmd_sb_tc_bind_set(struct dl *dl)
+{
+ struct nlmsghdr *nlh;
+ int err;
+
+ nlh = mnlu_gen_socket_cmd_prepare(&dl->nlg, DEVLINK_CMD_SB_TC_POOL_BIND_SET,
+ NLM_F_REQUEST | NLM_F_ACK);
+
+ err = dl_argv_parse_put(nlh, dl, DL_OPT_HANDLEP | DL_OPT_SB_TC |
+ DL_OPT_SB_TYPE | DL_OPT_SB_POOL | DL_OPT_SB_TH,
+ DL_OPT_SB);
+ if (err)
+ return err;
+
+ return mnlu_gen_socket_sndrcv(&dl->nlg, nlh, NULL, NULL);
+}
+
+static int cmd_sb_tc_bind(struct dl *dl)
+{
+ if (dl_argv_match(dl, "help")) {
+ cmd_sb_help();
+ return 0;
+ } else if (dl_argv_match(dl, "show") ||
+ dl_argv_match(dl, "list") || dl_no_arg(dl)) {
+ dl_arg_inc(dl);
+ return cmd_sb_tc_bind_show(dl);
+ } else if (dl_argv_match(dl, "set")) {
+ dl_arg_inc(dl);
+ return cmd_sb_tc_bind_set(dl);
+ }
+ pr_err("Command \"%s\" not found\n", dl_argv(dl));
+ return -ENOENT;
+}
+
+static int cmd_sb_tc(struct dl *dl)
+{
+ if (dl_argv_match(dl, "help") || dl_no_arg(dl)) {
+ cmd_sb_help();
+ return 0;
+ } else if (dl_argv_match(dl, "bind")) {
+ dl_arg_inc(dl);
+ return cmd_sb_tc_bind(dl);
+ }
+ pr_err("Command \"%s\" not found\n", dl_argv(dl));
+ return -ENOENT;
+}
+
+struct occ_item {
+ struct list_head list;
+ uint32_t index;
+ uint32_t cur;
+ uint32_t max;
+ uint32_t bound_pool_index;
+};
+
+struct occ_port {
+ struct list_head list;
+ char *bus_name;
+ char *dev_name;
+ uint32_t port_index;
+ uint32_t sb_index;
+ struct list_head pool_list;
+ struct list_head ing_tc_list;
+ struct list_head eg_tc_list;
+};
+
+struct occ_show {
+ struct dl *dl;
+ int err;
+ struct list_head port_list;
+};
+
+static struct occ_item *occ_item_alloc(void)
+{
+ return calloc(1, sizeof(struct occ_item));
+}
+
+static void occ_item_free(struct occ_item *occ_item)
+{
+ free(occ_item);
+}
+
+static struct occ_port *occ_port_alloc(uint32_t port_index)
+{
+ struct occ_port *occ_port;
+
+ occ_port = calloc(1, sizeof(*occ_port));
+ if (!occ_port)
+ return NULL;
+ occ_port->port_index = port_index;
+ INIT_LIST_HEAD(&occ_port->pool_list);
+ INIT_LIST_HEAD(&occ_port->ing_tc_list);
+ INIT_LIST_HEAD(&occ_port->eg_tc_list);
+ return occ_port;
+}
+
+static void occ_port_free(struct occ_port *occ_port)
+{
+ struct occ_item *occ_item, *tmp;
+
+ list_for_each_entry_safe(occ_item, tmp, &occ_port->pool_list, list)
+ occ_item_free(occ_item);
+ list_for_each_entry_safe(occ_item, tmp, &occ_port->ing_tc_list, list)
+ occ_item_free(occ_item);
+ list_for_each_entry_safe(occ_item, tmp, &occ_port->eg_tc_list, list)
+ occ_item_free(occ_item);
+}
+
+static struct occ_show *occ_show_alloc(struct dl *dl)
+{
+ struct occ_show *occ_show;
+
+ occ_show = calloc(1, sizeof(*occ_show));
+ if (!occ_show)
+ return NULL;
+ occ_show->dl = dl;
+ INIT_LIST_HEAD(&occ_show->port_list);
+ return occ_show;
+}
+
+static void occ_show_free(struct occ_show *occ_show)
+{
+ struct occ_port *occ_port, *tmp;
+
+ list_for_each_entry_safe(occ_port, tmp, &occ_show->port_list, list)
+ occ_port_free(occ_port);
+}
+
+static struct occ_port *occ_port_get(struct occ_show *occ_show,
+ struct nlattr **tb)
+{
+ struct occ_port *occ_port;
+ uint32_t port_index;
+
+ port_index = mnl_attr_get_u32(tb[DEVLINK_ATTR_PORT_INDEX]);
+
+ list_for_each_entry_reverse(occ_port, &occ_show->port_list, list) {
+ if (occ_port->port_index == port_index)
+ return occ_port;
+ }
+ occ_port = occ_port_alloc(port_index);
+ if (!occ_port)
+ return NULL;
+ list_add_tail(&occ_port->list, &occ_show->port_list);
+ return occ_port;
+}
+
+static void pr_out_occ_show_item_list(const char *label, struct list_head *list,
+ bool bound_pool)
+{
+ struct occ_item *occ_item;
+ int i = 1;
+
+ pr_out_sp(7, " %s:", label);
+ list_for_each_entry(occ_item, list, list) {
+ if ((i - 1) % 4 == 0 && i != 1)
+ pr_out_sp(7, " ");
+ if (bound_pool)
+ pr_out_sp(7, "%2u(%u):", occ_item->index,
+ occ_item->bound_pool_index);
+ else
+ pr_out_sp(7, "%2u:", occ_item->index);
+ pr_out_sp(21, "%10u/%u", occ_item->cur, occ_item->max);
+ if (i++ % 4 == 0)
+ pr_out("\n");
+ }
+ if ((i - 1) % 4 != 0)
+ pr_out("\n");
+}
+
+static void pr_out_json_occ_show_item_list(struct dl *dl, const char *label,
+ struct list_head *list,
+ bool bound_pool)
+{
+ struct occ_item *occ_item;
+ char buf[32];
+
+ open_json_object(label);
+ list_for_each_entry(occ_item, list, list) {
+ sprintf(buf, "%u", occ_item->index);
+ open_json_object(buf);
+ if (bound_pool)
+ print_uint(PRINT_JSON, "bound_pool", NULL,
+ occ_item->bound_pool_index);
+ print_uint(PRINT_JSON, "current", NULL, occ_item->cur);
+ print_uint(PRINT_JSON, "max", NULL, occ_item->max);
+ close_json_object();
+ }
+ close_json_object();
+}
+
+static void pr_out_occ_show_port(struct dl *dl, struct occ_port *occ_port)
+{
+ if (dl->json_output) {
+ pr_out_json_occ_show_item_list(dl, "pool",
+ &occ_port->pool_list, false);
+ pr_out_json_occ_show_item_list(dl, "itc",
+ &occ_port->ing_tc_list, true);
+ pr_out_json_occ_show_item_list(dl, "etc",
+ &occ_port->eg_tc_list, true);
+ } else {
+ pr_out("\n");
+ pr_out_occ_show_item_list("pool", &occ_port->pool_list, false);
+ pr_out_occ_show_item_list("itc", &occ_port->ing_tc_list, true);
+ pr_out_occ_show_item_list("etc", &occ_port->eg_tc_list, true);
+ }
+}
+
+static void pr_out_occ_show(struct occ_show *occ_show)
+{
+ struct dl *dl = occ_show->dl;
+ struct dl_opts *opts = &dl->opts;
+ struct occ_port *occ_port;
+
+ list_for_each_entry(occ_port, &occ_show->port_list, list) {
+ __pr_out_port_handle_start(dl, opts->bus_name, opts->dev_name,
+ occ_port->port_index, true, false);
+ pr_out_occ_show_port(dl, occ_port);
+ pr_out_port_handle_end(dl);
+ }
+}
+
+static void cmd_sb_occ_port_pool_process(struct occ_show *occ_show,
+ struct nlattr **tb)
+{
+ struct occ_port *occ_port;
+ struct occ_item *occ_item;
+
+ if (occ_show->err || !dl_dump_filter(occ_show->dl, tb))
+ return;
+
+ occ_port = occ_port_get(occ_show, tb);
+ if (!occ_port) {
+ occ_show->err = -ENOMEM;
+ return;
+ }
+
+ occ_item = occ_item_alloc();
+ if (!occ_item) {
+ occ_show->err = -ENOMEM;
+ return;
+ }
+ occ_item->index = mnl_attr_get_u16(tb[DEVLINK_ATTR_SB_POOL_INDEX]);
+ occ_item->cur = mnl_attr_get_u32(tb[DEVLINK_ATTR_SB_OCC_CUR]);
+ occ_item->max = mnl_attr_get_u32(tb[DEVLINK_ATTR_SB_OCC_MAX]);
+ list_add_tail(&occ_item->list, &occ_port->pool_list);
+}
+
+static int cmd_sb_occ_port_pool_process_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct occ_show *occ_show = data;
+ struct nlattr *tb[DEVLINK_ATTR_MAX + 1] = {};
+ struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+
+ mnl_attr_parse(nlh, sizeof(*genl), attr_cb, tb);
+ if (!tb[DEVLINK_ATTR_BUS_NAME] || !tb[DEVLINK_ATTR_DEV_NAME] ||
+ !tb[DEVLINK_ATTR_PORT_INDEX] || !tb[DEVLINK_ATTR_SB_INDEX] ||
+ !tb[DEVLINK_ATTR_SB_POOL_INDEX] ||
+ !tb[DEVLINK_ATTR_SB_OCC_CUR] || !tb[DEVLINK_ATTR_SB_OCC_MAX])
+ return MNL_CB_ERROR;
+ cmd_sb_occ_port_pool_process(occ_show, tb);
+ return MNL_CB_OK;
+}
+
+static void cmd_sb_occ_tc_pool_process(struct occ_show *occ_show,
+ struct nlattr **tb)
+{
+ struct occ_port *occ_port;
+ struct occ_item *occ_item;
+ uint8_t pool_type;
+
+ if (occ_show->err || !dl_dump_filter(occ_show->dl, tb))
+ return;
+
+ occ_port = occ_port_get(occ_show, tb);
+ if (!occ_port) {
+ occ_show->err = -ENOMEM;
+ return;
+ }
+
+ occ_item = occ_item_alloc();
+ if (!occ_item) {
+ occ_show->err = -ENOMEM;
+ return;
+ }
+ occ_item->index = mnl_attr_get_u16(tb[DEVLINK_ATTR_SB_TC_INDEX]);
+ occ_item->cur = mnl_attr_get_u32(tb[DEVLINK_ATTR_SB_OCC_CUR]);
+ occ_item->max = mnl_attr_get_u32(tb[DEVLINK_ATTR_SB_OCC_MAX]);
+ occ_item->bound_pool_index =
+ mnl_attr_get_u16(tb[DEVLINK_ATTR_SB_POOL_INDEX]);
+ pool_type = mnl_attr_get_u8(tb[DEVLINK_ATTR_SB_POOL_TYPE]);
+ if (pool_type == DEVLINK_SB_POOL_TYPE_INGRESS)
+ list_add_tail(&occ_item->list, &occ_port->ing_tc_list);
+ else if (pool_type == DEVLINK_SB_POOL_TYPE_EGRESS)
+ list_add_tail(&occ_item->list, &occ_port->eg_tc_list);
+ else
+ occ_item_free(occ_item);
+}
+
+static int cmd_sb_occ_tc_pool_process_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct occ_show *occ_show = data;
+ struct nlattr *tb[DEVLINK_ATTR_MAX + 1] = {};
+ struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+
+ mnl_attr_parse(nlh, sizeof(*genl), attr_cb, tb);
+ if (!tb[DEVLINK_ATTR_BUS_NAME] || !tb[DEVLINK_ATTR_DEV_NAME] ||
+ !tb[DEVLINK_ATTR_PORT_INDEX] || !tb[DEVLINK_ATTR_SB_INDEX] ||
+ !tb[DEVLINK_ATTR_SB_TC_INDEX] || !tb[DEVLINK_ATTR_SB_POOL_TYPE] ||
+ !tb[DEVLINK_ATTR_SB_POOL_INDEX] ||
+ !tb[DEVLINK_ATTR_SB_OCC_CUR] || !tb[DEVLINK_ATTR_SB_OCC_MAX])
+ return MNL_CB_ERROR;
+ cmd_sb_occ_tc_pool_process(occ_show, tb);
+ return MNL_CB_OK;
+}
+
+static int cmd_sb_occ_show(struct dl *dl)
+{
+ struct nlmsghdr *nlh;
+ struct occ_show *occ_show;
+ uint16_t flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP;
+ int err;
+
+ err = dl_argv_parse(dl, DL_OPT_HANDLE | DL_OPT_HANDLEP, DL_OPT_SB);
+ if (err)
+ return err;
+
+ occ_show = occ_show_alloc(dl);
+ if (!occ_show)
+ return -ENOMEM;
+
+ nlh = mnlu_gen_socket_cmd_prepare(&dl->nlg, DEVLINK_CMD_SB_PORT_POOL_GET, flags);
+
+ err = mnlu_gen_socket_sndrcv(&dl->nlg, nlh,
+ cmd_sb_occ_port_pool_process_cb, occ_show);
+ if (err)
+ goto out;
+
+ nlh = mnlu_gen_socket_cmd_prepare(&dl->nlg, DEVLINK_CMD_SB_TC_POOL_BIND_GET, flags);
+
+ err = mnlu_gen_socket_sndrcv(&dl->nlg, nlh,
+ cmd_sb_occ_tc_pool_process_cb, occ_show);
+ if (err)
+ goto out;
+
+ pr_out_section_start(dl, "occupancy");
+ pr_out_occ_show(occ_show);
+ pr_out_section_end(dl);
+
+out:
+ occ_show_free(occ_show);
+ return err;
+}
+
+static int cmd_sb_occ_snapshot(struct dl *dl)
+{
+ struct nlmsghdr *nlh;
+ int err;
+
+ nlh = mnlu_gen_socket_cmd_prepare(&dl->nlg, DEVLINK_CMD_SB_OCC_SNAPSHOT,
+ NLM_F_REQUEST | NLM_F_ACK);
+
+ err = dl_argv_parse_put(nlh, dl, DL_OPT_HANDLE, DL_OPT_SB);
+ if (err)
+ return err;
+
+ return mnlu_gen_socket_sndrcv(&dl->nlg, nlh, NULL, NULL);
+}
+
+static int cmd_sb_occ_clearmax(struct dl *dl)
+{
+ struct nlmsghdr *nlh;
+ int err;
+
+ nlh = mnlu_gen_socket_cmd_prepare(&dl->nlg, DEVLINK_CMD_SB_OCC_MAX_CLEAR,
+ NLM_F_REQUEST | NLM_F_ACK);
+
+ err = dl_argv_parse_put(nlh, dl, DL_OPT_HANDLE, DL_OPT_SB);
+ if (err)
+ return err;
+
+ return mnlu_gen_socket_sndrcv(&dl->nlg, nlh, NULL, NULL);
+}
+
+static int cmd_sb_occ(struct dl *dl)
+{
+ if (dl_argv_match(dl, "help") || dl_no_arg(dl)) {
+ cmd_sb_help();
+ return 0;
+ } else if (dl_argv_match(dl, "show") ||
+ dl_argv_match(dl, "list")) {
+ dl_arg_inc(dl);
+ return cmd_sb_occ_show(dl);
+ } else if (dl_argv_match(dl, "snapshot")) {
+ dl_arg_inc(dl);
+ return cmd_sb_occ_snapshot(dl);
+ } else if (dl_argv_match(dl, "clearmax")) {
+ dl_arg_inc(dl);
+ return cmd_sb_occ_clearmax(dl);
+ }
+ pr_err("Command \"%s\" not found\n", dl_argv(dl));
+ return -ENOENT;
+}
+
+static int cmd_sb(struct dl *dl)
+{
+ if (dl_argv_match(dl, "help")) {
+ cmd_sb_help();
+ return 0;
+ } else if (dl_argv_match(dl, "show") ||
+ dl_argv_match(dl, "list") || dl_no_arg(dl)) {
+ dl_arg_inc(dl);
+ return cmd_sb_show(dl);
+ } else if (dl_argv_match(dl, "pool")) {
+ dl_arg_inc(dl);
+ return cmd_sb_pool(dl);
+ } else if (dl_argv_match(dl, "port")) {
+ dl_arg_inc(dl);
+ return cmd_sb_port(dl);
+ } else if (dl_argv_match(dl, "tc")) {
+ dl_arg_inc(dl);
+ return cmd_sb_tc(dl);
+ } else if (dl_argv_match(dl, "occupancy")) {
+ dl_arg_inc(dl);
+ return cmd_sb_occ(dl);
+ }
+ pr_err("Command \"%s\" not found\n", dl_argv(dl));
+ return -ENOENT;
+}
+
+static const char *cmd_name(uint8_t cmd)
+{
+ switch (cmd) {
+ case DEVLINK_CMD_UNSPEC: return "unspec";
+ case DEVLINK_CMD_GET: return "get";
+ case DEVLINK_CMD_SET: return "set";
+ case DEVLINK_CMD_NEW: return "new";
+ case DEVLINK_CMD_DEL: return "del";
+ case DEVLINK_CMD_PORT_GET: return "get";
+ case DEVLINK_CMD_PORT_SET: return "set";
+ case DEVLINK_CMD_PORT_NEW: return "new";
+ case DEVLINK_CMD_PORT_DEL: return "del";
+ case DEVLINK_CMD_PARAM_GET: return "get";
+ case DEVLINK_CMD_PARAM_SET: return "set";
+ case DEVLINK_CMD_PARAM_NEW: return "new";
+ case DEVLINK_CMD_PARAM_DEL: return "del";
+ case DEVLINK_CMD_REGION_GET: return "get";
+ case DEVLINK_CMD_REGION_SET: return "set";
+ case DEVLINK_CMD_REGION_NEW: return "new";
+ case DEVLINK_CMD_REGION_DEL: return "del";
+ case DEVLINK_CMD_PORT_PARAM_GET: return "get";
+ case DEVLINK_CMD_PORT_PARAM_SET: return "set";
+ case DEVLINK_CMD_PORT_PARAM_NEW: return "new";
+ case DEVLINK_CMD_PORT_PARAM_DEL: return "del";
+ case DEVLINK_CMD_FLASH_UPDATE: return "begin";
+ case DEVLINK_CMD_FLASH_UPDATE_END: return "end";
+ case DEVLINK_CMD_FLASH_UPDATE_STATUS: return "status";
+ case DEVLINK_CMD_HEALTH_REPORTER_RECOVER: return "status";
+ case DEVLINK_CMD_TRAP_GET: return "get";
+ case DEVLINK_CMD_TRAP_SET: return "set";
+ case DEVLINK_CMD_TRAP_NEW: return "new";
+ case DEVLINK_CMD_TRAP_DEL: return "del";
+ case DEVLINK_CMD_TRAP_GROUP_GET: return "get";
+ case DEVLINK_CMD_TRAP_GROUP_SET: return "set";
+ case DEVLINK_CMD_TRAP_GROUP_NEW: return "new";
+ case DEVLINK_CMD_TRAP_GROUP_DEL: return "del";
+ case DEVLINK_CMD_TRAP_POLICER_GET: return "get";
+ case DEVLINK_CMD_TRAP_POLICER_SET: return "set";
+ case DEVLINK_CMD_TRAP_POLICER_NEW: return "new";
+ case DEVLINK_CMD_TRAP_POLICER_DEL: return "del";
+ default: return "<unknown cmd>";
+ }
+}
+
+static const char *cmd_obj(uint8_t cmd)
+{
+ switch (cmd) {
+ case DEVLINK_CMD_UNSPEC: return "unspec";
+ case DEVLINK_CMD_GET:
+ case DEVLINK_CMD_SET:
+ case DEVLINK_CMD_NEW:
+ case DEVLINK_CMD_DEL:
+ return "dev";
+ case DEVLINK_CMD_PORT_GET:
+ case DEVLINK_CMD_PORT_SET:
+ case DEVLINK_CMD_PORT_NEW:
+ case DEVLINK_CMD_PORT_DEL:
+ return "port";
+ case DEVLINK_CMD_PARAM_GET:
+ case DEVLINK_CMD_PARAM_SET:
+ case DEVLINK_CMD_PARAM_NEW:
+ case DEVLINK_CMD_PARAM_DEL:
+ case DEVLINK_CMD_PORT_PARAM_GET:
+ case DEVLINK_CMD_PORT_PARAM_SET:
+ case DEVLINK_CMD_PORT_PARAM_NEW:
+ case DEVLINK_CMD_PORT_PARAM_DEL:
+ return "param";
+ case DEVLINK_CMD_REGION_GET:
+ case DEVLINK_CMD_REGION_SET:
+ case DEVLINK_CMD_REGION_NEW:
+ case DEVLINK_CMD_REGION_DEL:
+ return "region";
+ case DEVLINK_CMD_FLASH_UPDATE:
+ case DEVLINK_CMD_FLASH_UPDATE_END:
+ case DEVLINK_CMD_FLASH_UPDATE_STATUS:
+ return "flash";
+ case DEVLINK_CMD_HEALTH_REPORTER_RECOVER:
+ return "health";
+ case DEVLINK_CMD_TRAP_GET:
+ case DEVLINK_CMD_TRAP_SET:
+ case DEVLINK_CMD_TRAP_NEW:
+ case DEVLINK_CMD_TRAP_DEL:
+ return "trap";
+ case DEVLINK_CMD_TRAP_GROUP_GET:
+ case DEVLINK_CMD_TRAP_GROUP_SET:
+ case DEVLINK_CMD_TRAP_GROUP_NEW:
+ case DEVLINK_CMD_TRAP_GROUP_DEL:
+ return "trap-group";
+ case DEVLINK_CMD_TRAP_POLICER_GET:
+ case DEVLINK_CMD_TRAP_POLICER_SET:
+ case DEVLINK_CMD_TRAP_POLICER_NEW:
+ case DEVLINK_CMD_TRAP_POLICER_DEL:
+ return "trap-policer";
+ default: return "<unknown obj>";
+ }
+}
+
+static void pr_out_mon_header(uint8_t cmd)
+{
+ if (!is_json_context()) {
+ pr_out("[%s,%s] ", cmd_obj(cmd), cmd_name(cmd));
+ } else {
+ open_json_object(NULL);
+ print_string(PRINT_JSON, "command", NULL, cmd_name(cmd));
+ open_json_object(cmd_obj(cmd));
+ }
+}
+
+static void pr_out_mon_footer(void)
+{
+ if (is_json_context()) {
+ close_json_object();
+ close_json_object();
+ }
+}
+
+static bool cmd_filter_check(struct dl *dl, uint8_t cmd)
+{
+ const char *obj = cmd_obj(cmd);
+ unsigned int index = 0;
+ const char *cur_obj;
+
+ if (dl_no_arg(dl))
+ return true;
+ while ((cur_obj = dl_argv_index(dl, index++))) {
+ if (strcmp(cur_obj, obj) == 0 || strcmp(cur_obj, "all") == 0)
+ return true;
+ }
+ return false;
+}
+
+static void pr_out_flash_update(struct dl *dl, struct nlattr **tb)
+{
+ __pr_out_handle_start(dl, tb, true, false);
+
+ if (tb[DEVLINK_ATTR_FLASH_UPDATE_STATUS_MSG]) {
+ check_indent_newline(dl);
+ print_string(PRINT_ANY, "msg", "msg %s",
+ mnl_attr_get_str(tb[DEVLINK_ATTR_FLASH_UPDATE_STATUS_MSG]));
+ }
+ if (tb[DEVLINK_ATTR_FLASH_UPDATE_COMPONENT]) {
+ check_indent_newline(dl);
+ print_string(PRINT_ANY, "component", "component %s",
+ mnl_attr_get_str(tb[DEVLINK_ATTR_FLASH_UPDATE_COMPONENT]));
+ }
+
+ if (tb[DEVLINK_ATTR_FLASH_UPDATE_STATUS_DONE])
+ pr_out_u64(dl, "done",
+ mnl_attr_get_u64(tb[DEVLINK_ATTR_FLASH_UPDATE_STATUS_DONE]));
+
+ if (tb[DEVLINK_ATTR_FLASH_UPDATE_STATUS_TOTAL])
+ pr_out_u64(dl, "total",
+ mnl_attr_get_u64(tb[DEVLINK_ATTR_FLASH_UPDATE_STATUS_TOTAL]));
+
+ pr_out_handle_end(dl);
+}
+
+static void pr_out_region(struct dl *dl, struct nlattr **tb);
+static void pr_out_health(struct dl *dl, struct nlattr **tb_health,
+ bool show_device, bool show_port);
+static void pr_out_trap(struct dl *dl, struct nlattr **tb, bool array);
+static void pr_out_trap_group(struct dl *dl, struct nlattr **tb, bool array);
+static void pr_out_trap_policer(struct dl *dl, struct nlattr **tb, bool array);
+
+static int cmd_mon_show_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct dl *dl = data;
+ struct nlattr *tb[DEVLINK_ATTR_MAX + 1] = {};
+ struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
+ uint8_t cmd = genl->cmd;
+
+ if (!cmd_filter_check(dl, cmd))
+ return MNL_CB_OK;
+
+ switch (cmd) {
+ case DEVLINK_CMD_GET: /* fall through */
+ case DEVLINK_CMD_SET: /* fall through */
+ case DEVLINK_CMD_NEW: /* fall through */
+ case DEVLINK_CMD_DEL:
+ mnl_attr_parse(nlh, sizeof(*genl), attr_cb, tb);
+ if (!tb[DEVLINK_ATTR_BUS_NAME] || !tb[DEVLINK_ATTR_DEV_NAME])
+ return MNL_CB_ERROR;
+ pr_out_mon_header(genl->cmd);
+ dl->stats = true;
+ pr_out_dev(dl, tb);
+ pr_out_mon_footer();
+ break;
+ case DEVLINK_CMD_PORT_GET: /* fall through */
+ case DEVLINK_CMD_PORT_SET: /* fall through */
+ case DEVLINK_CMD_PORT_NEW: /* fall through */
+ case DEVLINK_CMD_PORT_DEL:
+ mnl_attr_parse(nlh, sizeof(*genl), attr_cb, tb);
+ if (!tb[DEVLINK_ATTR_BUS_NAME] || !tb[DEVLINK_ATTR_DEV_NAME] ||
+ !tb[DEVLINK_ATTR_PORT_INDEX])
+ return MNL_CB_ERROR;
+ pr_out_mon_header(genl->cmd);
+ pr_out_port(dl, tb);
+ pr_out_mon_footer();
+ break;
+ case DEVLINK_CMD_PARAM_GET: /* fall through */
+ case DEVLINK_CMD_PARAM_SET: /* fall through */
+ case DEVLINK_CMD_PARAM_NEW: /* fall through */
+ case DEVLINK_CMD_PARAM_DEL:
+ mnl_attr_parse(nlh, sizeof(*genl), attr_cb, tb);
+ if (!tb[DEVLINK_ATTR_BUS_NAME] || !tb[DEVLINK_ATTR_DEV_NAME] ||
+ !tb[DEVLINK_ATTR_PARAM])
+ return MNL_CB_ERROR;
+ pr_out_mon_header(genl->cmd);
+ pr_out_param(dl, tb, false, false);
+ pr_out_mon_footer();
+ break;
+ case DEVLINK_CMD_REGION_GET: /* fall through */
+ case DEVLINK_CMD_REGION_SET: /* fall through */
+ case DEVLINK_CMD_REGION_NEW: /* fall through */
+ case DEVLINK_CMD_REGION_DEL:
+ mnl_attr_parse(nlh, sizeof(*genl), attr_cb, tb);
+ if (!tb[DEVLINK_ATTR_BUS_NAME] || !tb[DEVLINK_ATTR_DEV_NAME] ||
+ !tb[DEVLINK_ATTR_REGION_NAME])
+ return MNL_CB_ERROR;
+ pr_out_mon_header(genl->cmd);
+ pr_out_region(dl, tb);
+ pr_out_mon_footer();
+ break;
+ case DEVLINK_CMD_FLASH_UPDATE: /* fall through */
+ case DEVLINK_CMD_FLASH_UPDATE_END: /* fall through */
+ case DEVLINK_CMD_FLASH_UPDATE_STATUS:
+ mnl_attr_parse(nlh, sizeof(*genl), attr_cb, tb);
+ if (!tb[DEVLINK_ATTR_BUS_NAME] || !tb[DEVLINK_ATTR_DEV_NAME])
+ return MNL_CB_ERROR;
+ pr_out_mon_header(genl->cmd);
+ pr_out_flash_update(dl, tb);
+ pr_out_mon_footer();
+ break;
+ case DEVLINK_CMD_HEALTH_REPORTER_RECOVER:
+ mnl_attr_parse(nlh, sizeof(*genl), attr_cb, tb);
+ if (!tb[DEVLINK_ATTR_BUS_NAME] || !tb[DEVLINK_ATTR_DEV_NAME] ||
+ !tb[DEVLINK_ATTR_HEALTH_REPORTER])
+ return MNL_CB_ERROR;
+ pr_out_mon_header(genl->cmd);
+ pr_out_health(dl, tb, true, true);
+ pr_out_mon_footer();
+ break;
+ case DEVLINK_CMD_TRAP_GET: /* fall through */
+ case DEVLINK_CMD_TRAP_SET: /* fall through */
+ case DEVLINK_CMD_TRAP_NEW: /* fall through */
+ case DEVLINK_CMD_TRAP_DEL:
+ mnl_attr_parse(nlh, sizeof(*genl), attr_cb, tb);
+ if (!tb[DEVLINK_ATTR_BUS_NAME] || !tb[DEVLINK_ATTR_DEV_NAME] ||
+ !tb[DEVLINK_ATTR_TRAP_NAME] ||
+ !tb[DEVLINK_ATTR_TRAP_TYPE] ||
+ !tb[DEVLINK_ATTR_TRAP_ACTION] ||
+ !tb[DEVLINK_ATTR_TRAP_GROUP_NAME] ||
+ !tb[DEVLINK_ATTR_TRAP_METADATA] ||
+ !tb[DEVLINK_ATTR_STATS])
+ return MNL_CB_ERROR;
+ pr_out_mon_header(genl->cmd);
+ pr_out_trap(dl, tb, false);
+ pr_out_mon_footer();
+ break;
+ case DEVLINK_CMD_TRAP_GROUP_GET: /* fall through */
+ case DEVLINK_CMD_TRAP_GROUP_SET: /* fall through */
+ case DEVLINK_CMD_TRAP_GROUP_NEW: /* fall through */
+ case DEVLINK_CMD_TRAP_GROUP_DEL:
+ mnl_attr_parse(nlh, sizeof(*genl), attr_cb, tb);
+ if (!tb[DEVLINK_ATTR_BUS_NAME] || !tb[DEVLINK_ATTR_DEV_NAME] ||
+ !tb[DEVLINK_ATTR_TRAP_GROUP_NAME] ||
+ !tb[DEVLINK_ATTR_STATS])
+ return MNL_CB_ERROR;
+ pr_out_mon_header(genl->cmd);
+ pr_out_trap_group(dl, tb, false);
+ pr_out_mon_footer();
+ break;
+ case DEVLINK_CMD_TRAP_POLICER_GET: /* fall through */
+ case DEVLINK_CMD_TRAP_POLICER_SET: /* fall through */
+ case DEVLINK_CMD_TRAP_POLICER_NEW: /* fall through */
+ case DEVLINK_CMD_TRAP_POLICER_DEL: /* fall through */
+ mnl_attr_parse(nlh, sizeof(*genl), attr_cb, tb);
+ if (!tb[DEVLINK_ATTR_BUS_NAME] || !tb[DEVLINK_ATTR_DEV_NAME] ||
+ !tb[DEVLINK_ATTR_TRAP_POLICER_ID] ||
+ !tb[DEVLINK_ATTR_TRAP_POLICER_RATE] ||
+ !tb[DEVLINK_ATTR_TRAP_POLICER_BURST])
+ return MNL_CB_ERROR;
+ pr_out_mon_header(genl->cmd);
+ pr_out_trap_policer(dl, tb, false);
+ break;
+ }
+ fflush(stdout);
+ return MNL_CB_OK;
+}
+
+static int cmd_mon_show(struct dl *dl)
+{
+ int err;
+ unsigned int index = 0;
+ const char *cur_obj;
+
+ while ((cur_obj = dl_argv_index(dl, index++))) {
+ if (strcmp(cur_obj, "all") != 0 &&
+ strcmp(cur_obj, "dev") != 0 &&
+ strcmp(cur_obj, "port") != 0 &&
+ strcmp(cur_obj, "health") != 0 &&
+ strcmp(cur_obj, "trap") != 0 &&
+ strcmp(cur_obj, "trap-group") != 0 &&
+ strcmp(cur_obj, "trap-policer") != 0) {
+ pr_err("Unknown object \"%s\"\n", cur_obj);
+ return -EINVAL;
+ }
+ }
+ err = _mnlg_socket_group_add(&dl->nlg, DEVLINK_GENL_MCGRP_CONFIG_NAME);
+ if (err)
+ return err;
+ open_json_object(NULL);
+ open_json_array(PRINT_JSON, "mon");
+ err = _mnlg_socket_recv_run_intr(&dl->nlg, cmd_mon_show_cb, dl);
+ close_json_array(PRINT_JSON, NULL);
+ close_json_object();
+ if (err)
+ return err;
+ return 0;
+}
+
+static void cmd_mon_help(void)
+{
+ pr_err("Usage: devlink monitor [ all | OBJECT-LIST ]\n"
+ "where OBJECT-LIST := { dev | port | health | trap | trap-group | trap-policer }\n");
+}
+
+static int cmd_mon(struct dl *dl)
+{
+ if (dl_argv_match(dl, "help")) {
+ cmd_mon_help();
+ return 0;
+ }
+ return cmd_mon_show(dl);
+}
+
+struct dpipe_field {
+ char *name;
+ unsigned int id;
+ unsigned int bitwidth;
+ enum devlink_dpipe_field_mapping_type mapping_type;
+};
+
+struct dpipe_header {
+ struct list_head list;
+ char *name;
+ unsigned int id;
+ struct dpipe_field *fields;
+ unsigned int fields_count;
+};
+
+struct dpipe_table {
+ struct list_head list;
+ char *name;
+ unsigned int resource_id;
+ bool resource_valid;
+};
+
+struct dpipe_tables {
+ struct list_head table_list;
+};
+
+struct resource {
+ char *name;
+ uint64_t size;
+ uint64_t size_new;
+ uint64_t size_min;
+ uint64_t size_max;
+ uint64_t size_gran;
+ enum devlink_resource_unit unit;
+ bool size_valid;
+ uint64_t size_occ;
+ bool occ_valid;
+ uint64_t id;
+ struct list_head list;
+ struct list_head resource_list;
+ struct resource *parent;
+};
+
+struct resources {
+ struct list_head resource_list;
+};
+
+struct resource_ctx {
+ struct dl *dl;
+ int err;
+ struct resources *resources;
+ struct dpipe_tables *tables;
+ bool print_resources;
+ bool pending_change;
+};
+
+static struct resource *resource_alloc(void)
+{
+ struct resource *resource;
+
+ resource = calloc(1, sizeof(struct resource));
+ if (!resource)
+ return NULL;
+ INIT_LIST_HEAD(&resource->resource_list);
+ return resource;
+}
+
+static void resource_free(struct resource *resource)
+{
+ struct resource *child_resource, *tmp;
+
+ list_for_each_entry_safe(child_resource, tmp, &resource->resource_list,
+ list) {
+ free(child_resource->name);
+ resource_free(child_resource);
+ }
+ free(resource);
+}
+
+static struct resources