summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-09 13:14:35 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-09 13:14:35 +0000
commit9b8a97db9ec4b795e29e72289005fbc58484ebeb (patch)
treee24ca2d68215e57b4759fe5c032629821eabb250
parentInitial commit. (diff)
downloadiproute2-9b8a97db9ec4b795e29e72289005fbc58484ebeb.tar.xz
iproute2-9b8a97db9ec4b795e29e72289005fbc58484ebeb.zip
Adding upstream version 6.8.0.upstream/6.8.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
-rw-r--r--.clang-format130
-rw-r--r--.gitignore41
-rw-r--r--.mailmap22
-rw-r--r--COPYING340
-rw-r--r--CREDITS30
-rw-r--r--MAINTAINERS58
-rw-r--r--Makefile144
-rw-r--r--README42
-rw-r--r--README.devel18
-rw-r--r--SECURITY.md21
-rw-r--r--bash-completion/devlink1104
-rw-r--r--bash-completion/tc785
-rw-r--r--bridge/.gitignore1
-rw-r--r--bridge/Makefile15
-rw-r--r--bridge/br_common.h33
-rw-r--r--bridge/bridge.c195
-rw-r--r--bridge/fdb.c914
-rw-r--r--bridge/link.c696
-rw-r--r--bridge/mdb.c1107
-rw-r--r--bridge/monitor.c166
-rw-r--r--bridge/vlan.c1382
-rw-r--r--bridge/vni.c413
-rwxr-xr-xconfigure620
-rw-r--r--dcb/.gitignore1
-rw-r--r--dcb/Makefile27
-rw-r--r--dcb/dcb.c620
-rw-r--r--dcb/dcb.h142
-rw-r--r--dcb/dcb_app.c933
-rw-r--r--dcb/dcb_apptrust.c307
-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--dcb/dcb_rewr.c363
-rw-r--r--devlink/.gitignore1
-rw-r--r--devlink/Makefile19
-rw-r--r--devlink/devlink.c10206
-rw-r--r--devlink/mnlg.c143
-rw-r--r--devlink/mnlg.h19
-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/Makefile40
-rw-r--r--genl/ctrl.c374
-rw-r--r--genl/genl.c144
-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.h326
-rw-r--r--include/cg_map.h7
-rw-r--r--include/color.h29
-rw-r--r--include/dlfcn.h41
-rw-r--r--include/json_print.h114
-rw-r--r--include/json_writer.h76
-rw-r--r--include/libgenl.h29
-rw-r--r--include/libnetlink.h358
-rw-r--r--include/list.h134
-rw-r--r--include/ll_map.h17
-rw-r--r--include/mnl_utils.h35
-rw-r--r--include/names.h26
-rw-r--r--include/namespace.h65
-rw-r--r--include/netinet/tcp.h231
-rw-r--r--include/rt_names.h47
-rw-r--r--include/rtm_map.h10
-rw-r--r--include/selinux.h10
-rw-r--r--include/uapi/asm-generic/posix_types.h99
-rw-r--r--include/uapi/asm-generic/sockios.h14
-rw-r--r--include/uapi/linux/amt.h62
-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.h212
-rw-r--r--include/uapi/linux/atmioc.h42
-rw-r--r--include/uapi/linux/atmsap.h163
-rw-r--r--include/uapi/linux/ax25.h117
-rw-r--r--include/uapi/linux/batman_adv.h704
-rw-r--r--include/uapi/linux/bpf.h7368
-rw-r--r--include/uapi/linux/bpf_common.h57
-rw-r--r--include/uapi/linux/btf.h200
-rw-r--r--include/uapi/linux/can.h289
-rw-r--r--include/uapi/linux/can/netlink.h185
-rw-r--r--include/uapi/linux/can/vxcan.h13
-rw-r--r--include/uapi/linux/capability.h431
-rw-r--r--include/uapi/linux/const.h36
-rw-r--r--include/uapi/linux/dcbnl.h779
-rw-r--r--include/uapi/linux/devlink.h709
-rw-r--r--include/uapi/linux/elf-em.h71
-rw-r--r--include/uapi/linux/fib_rules.h90
-rw-r--r--include/uapi/linux/filter.h90
-rw-r--r--include/uapi/linux/fou.h46
-rw-r--r--include/uapi/linux/gen_stats.h78
-rw-r--r--include/uapi/linux/genetlink.h103
-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.h77
-rw-r--r--include/uapi/linux/if_addrlabel.h33
-rw-r--r--include/uapi/linux/if_alg.h61
-rw-r--r--include/uapi/linux/if_arp.h165
-rw-r--r--include/uapi/linux/if_bonding.h155
-rw-r--r--include/uapi/linux/if_bridge.h858
-rw-r--r--include/uapi/linux/if_ether.h181
-rw-r--r--include/uapi/linux/if_infiniband.h30
-rw-r--r--include/uapi/linux/if_link.h1956
-rw-r--r--include/uapi/linux/if_macsec.h194
-rw-r--r--include/uapi/linux/if_packet.h319
-rw-r--r--include/uapi/linux/if_tun.h118
-rw-r--r--include/uapi/linux/if_tunnel.h185
-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.h333
-rw-r--r--include/uapi/linux/in6.h302
-rw-r--r--include/uapi/linux/in_route.h33
-rw-r--r--include/uapi/linux/inet_diag.h239
-rw-r--r--include/uapi/linux/ioam6.h133
-rw-r--r--include/uapi/linux/ioam6_genl.h52
-rw-r--r--include/uapi/linux/ioam6_iptunnel.h58
-rw-r--r--include/uapi/linux/ip.h181
-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.h203
-rw-r--r--include/uapi/linux/libc-compat.h267
-rw-r--r--include/uapi/linux/limits.h21
-rw-r--r--include/uapi/linux/lwtunnel.h124
-rw-r--r--include/uapi/linux/magic.h105
-rw-r--r--include/uapi/linux/mpls.h77
-rw-r--r--include/uapi/linux/mpls_iptunnel.h31
-rw-r--r--include/uapi/linux/mptcp.h122
-rw-r--r--include/uapi/linux/mptcp_pm.h150
-rw-r--r--include/uapi/linux/neighbour.h224
-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.h76
-rw-r--r--include/uapi/linux/netfilter/ipset/ip_set.h312
-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.h379
-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.h386
-rw-r--r--include/uapi/linux/pkt_cls.h773
-rw-r--r--include/uapi/linux/pkt_sched.h1211
-rw-r--r--include/uapi/linux/posix_types.h38
-rw-r--r--include/uapi/linux/ppp_defs.h165
-rw-r--r--include/uapi/linux/rose.h91
-rw-r--r--include/uapi/linux/rpl.h48
-rw-r--r--include/uapi/linux/rpl_iptunnel.h21
-rw-r--r--include/uapi/linux/rtnetlink.h830
-rw-r--r--include/uapi/linux/sctp.h1220
-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.h42
-rw-r--r--include/uapi/linux/seg6_local.h137
-rw-r--r--include/uapi/linux/snmp.h362
-rw-r--r--include/uapi/linux/sock_diag.h65
-rw-r--r--include/uapi/linux/socket.h38
-rw-r--r--include/uapi/linux/sockios.h174
-rw-r--r--include/uapi/linux/stddef.h58
-rw-r--r--include/uapi/linux/sysinfo.h25
-rw-r--r--include/uapi/linux/tc_act/tc_bpf.h30
-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.h44
-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_mirred.h29
-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.h41
-rw-r--r--include/uapi/linux/tc_act/tc_skbmod.h34
-rw-r--r--include/uapi/linux/tc_act/tc_tunnel_key.h95
-rw-r--r--include/uapi/linux/tc_act/tc_vlan.h36
-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.h486
-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.h206
-rw-r--r--include/uapi/linux/types.h57
-rw-r--r--include/uapi/linux/udp.h47
-rw-r--r--include/uapi/linux/unix_diag.h61
-rw-r--r--include/uapi/linux/veth.h13
-rw-r--r--include/uapi/linux/virtio_config.h121
-rw-r--r--include/uapi/linux/virtio_ids.h84
-rw-r--r--include/uapi/linux/virtio_net.h409
-rw-r--r--include/uapi/linux/virtio_types.h46
-rw-r--r--include/uapi/linux/vm_sockets_diag.h34
-rw-r--r--include/uapi/linux/wwan.h16
-rw-r--r--include/uapi/linux/xdp_diag.h83
-rw-r--r--include/uapi/linux/xfrm.h569
-rw-r--r--include/utils.h396
-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/Makefile57
-rw-r--r--ip/ila_common.h106
-rw-r--r--ip/ip.c327
-rw-r--r--ip/ip6tunnel.c437
-rw-r--r--ip/ip_common.h234
-rw-r--r--ip/ipaddress.c2685
-rw-r--r--ip/ipaddrlabel.c260
-rw-r--r--ip/ipfou.c351
-rw-r--r--ip/ipila.c310
-rw-r--r--ip/ipioam6.c333
-rw-r--r--ip/ipl2tp.c846
-rw-r--r--ip/iplink.c1463
-rw-r--r--ip/iplink_amt.c196
-rw-r--r--ip/iplink_bareudp.c152
-rw-r--r--ip/iplink_batadv.c65
-rw-r--r--ip/iplink_bond.c964
-rw-r--r--ip/iplink_bond_slave.c195
-rw-r--r--ip/iplink_bridge.c1040
-rw-r--r--ip/iplink_bridge_slave.c488
-rw-r--r--ip/iplink_can.c670
-rw-r--r--ip/iplink_dsa.c68
-rw-r--r--ip/iplink_dummy.c17
-rw-r--r--ip/iplink_geneve.c391
-rw-r--r--ip/iplink_gtp.c140
-rw-r--r--ip/iplink_hsr.c166
-rw-r--r--ip/iplink_ifb.c17
-rw-r--r--ip/iplink_ipoib.c141
-rw-r--r--ip/iplink_ipvlan.c129
-rw-r--r--ip/iplink_macvlan.c329
-rw-r--r--ip/iplink_netdevsim.c17
-rw-r--r--ip/iplink_netkit.c183
-rw-r--r--ip/iplink_nlmon.c17
-rw-r--r--ip/iplink_rmnet.c81
-rw-r--r--ip/iplink_team.c26
-rw-r--r--ip/iplink_vcan.c17
-rw-r--r--ip/iplink_virt_wifi.c25
-rw-r--r--ip/iplink_vlan.c276
-rw-r--r--ip/iplink_vrf.c222
-rw-r--r--ip/iplink_vxcan.c84
-rw-r--r--ip/iplink_vxlan.c634
-rw-r--r--ip/iplink_wwan.c72
-rw-r--r--ip/iplink_xdp.c195
-rw-r--r--ip/iplink_xstats.c79
-rw-r--r--ip/ipmacsec.c1544
-rw-r--r--ip/ipmaddr.c387
-rw-r--r--ip/ipmonitor.c345
-rw-r--r--ip/ipmptcp.c615
-rw-r--r--ip/ipmroute.c325
-rw-r--r--ip/ipneigh.c767
-rw-r--r--ip/ipnetconf.c247
-rw-r--r--ip/ipnetns.c1052
-rw-r--r--ip/ipnexthop.c1321
-rw-r--r--ip/ipntable.c687
-rw-r--r--ip/ipprefix.c85
-rw-r--r--ip/iproute.c2409
-rw-r--r--ip/iproute_lwtunnel.c2285
-rw-r--r--ip/iprule.c1074
-rw-r--r--ip/ipseg6.c250
-rw-r--r--ip/ipstats.c1361
-rw-r--r--ip/iptoken.c204
-rw-r--r--ip/iptunnel.c602
-rw-r--r--ip/iptuntap.c562
-rw-r--r--ip/ipvrf.c654
-rw-r--r--ip/ipxfrm.c1553
-rw-r--r--ip/link_gre.c576
-rw-r--r--ip/link_gre6.c634
-rw-r--r--ip/link_ip6tnl.c461
-rw-r--r--ip/link_iptnl.c492
-rw-r--r--ip/link_veth.c81
-rw-r--r--ip/link_vti.c213
-rw-r--r--ip/link_vti6.c215
-rw-r--r--ip/link_xfrm.c101
-rw-r--r--ip/nh_common.h53
-rwxr-xr-xip/routel62
-rw-r--r--ip/rtm_map.c124
-rw-r--r--ip/rtmon.c179
-rw-r--r--ip/static-syms.c15
-rw-r--r--ip/tcp_metrics.c539
-rw-r--r--ip/tunnel.c434
-rw-r--r--ip/tunnel.h49
-rw-r--r--ip/xfrm.h130
-rw-r--r--ip/xfrm_monitor.c413
-rw-r--r--ip/xfrm_policy.c1348
-rw-r--r--ip/xfrm_state.c1486
-rw-r--r--lib/Makefile36
-rw-r--r--lib/ax25_ntop.c82
-rw-r--r--lib/bpf_glue.c91
-rw-r--r--lib/bpf_legacy.c3363
-rw-r--r--lib/bpf_libbpf.c389
-rw-r--r--lib/cg_map.c130
-rw-r--r--lib/color.c186
-rw-r--r--lib/coverity_model.c17
-rw-r--r--lib/exec.c46
-rw-r--r--lib/fs.c366
-rw-r--r--lib/inet_proto.c64
-rw-r--r--lib/json_print.c391
-rw-r--r--lib/json_print_math.c37
-rw-r--r--lib/json_writer.c386
-rw-r--r--lib/libgenl.c159
-rw-r--r--lib/libnetlink.c1650
-rw-r--r--lib/ll_addr.c91
-rw-r--r--lib/ll_map.c405
-rw-r--r--lib/ll_proto.c101
-rw-r--r--lib/ll_types.c117
-rw-r--r--lib/mnl_utils.c376
-rw-r--r--lib/mpls_ntop.c52
-rw-r--r--lib/mpls_pton.c63
-rw-r--r--lib/names.c147
-rw-r--r--lib/namespace.c224
-rw-r--r--lib/netrom_ntop.c23
-rw-r--r--lib/ppp_proto.c52
-rw-r--r--lib/rose_ntop.c56
-rw-r--r--lib/rt_names.c892
-rw-r--r--lib/selinux.c37
-rw-r--r--lib/utils.c2005
-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/Makefile29
-rw-r--r--man/man8/arpd.869
-rw-r--r--man/man8/bridge.81602
-rw-r--r--man/man8/ctstat.81
-rw-r--r--man/man8/dcb-app.8269
-rw-r--r--man/man8/dcb-apptrust.8110
-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-rewr.8206
-rw-r--r--man/man8/dcb.8158
-rw-r--r--man/man8/devlink-dev.8354
-rw-r--r--man/man8/devlink-dpipe.899
-rw-r--r--man/man8/devlink-health.8256
-rw-r--r--man/man8/devlink-lc.8101
-rw-r--r--man/man8/devlink-monitor.839
-rw-r--r--man/man8/devlink-port.8417
-rw-r--r--man/man8/devlink-rate.8292
-rw-r--r--man/man8/devlink-region.8156
-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.8147
-rw-r--r--man/man8/genl.877
-rw-r--r--man/man8/ifstat.877
-rw-r--r--man/man8/ip-address.8.in514
-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-ioam.872
-rw-r--r--man/man8/ip-l2tp.8412
-rw-r--r--man/man8/ip-link.8.in3030
-rw-r--r--man/man8/ip-macsec.8186
-rw-r--r--man/man8/ip-maddress.859
-rw-r--r--man/man8/ip-monitor.8133
-rw-r--r--man/man8/ip-mptcp.8235
-rw-r--r--man/man8/ip-mroute.858
-rw-r--r--man/man8/ip-neighbour.8303
-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.in1411
-rw-r--r--man/man8/ip-rule.8358
-rw-r--r--man/man8/ip-sr.858
-rw-r--r--man/man8/ip-stats.8208
-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.8139
-rw-r--r--man/man8/ip-xfrm.8775
-rw-r--r--man/man8/ip.8476
-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.8125
-rw-r--r--man/man8/rdma-statistic.8255
-rw-r--r--man/man8/rdma-system.8108
-rw-r--r--man/man8/rdma.8145
-rw-r--r--man/man8/routel.833
-rw-r--r--man/man8/rtacct.861
-rw-r--r--man/man8/rtmon.868
-rw-r--r--man/man8/rtstat.81
-rw-r--r--man/man8/ss.8607
-rw-r--r--man/man8/tc-actions.8312
-rw-r--r--man/man8/tc-basic.834
-rw-r--r--man/man8/tc-bfifo.872
-rw-r--r--man/man8/tc-bpf.8986
-rw-r--r--man/man8/tc-cake.8726
-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.8557
-rw-r--r--man/man8/tc-fq.8107
-rw-r--r--man/man8/tc-fq_codel.8143
-rw-r--r--man/man8/tc-fq_pie.8166
-rw-r--r--man/man8/tc-fw.8104
-rw-r--r--man/man8/tc-gact.885
-rw-r--r--man/man8/tc-gate.8123
-rw-r--r--man/man8/tc-hfsc.861
-rw-r--r--man/man8/tc-htb.8171
-rw-r--r--man/man8/tc-ife.8143
-rw-r--r--man/man8/tc-matchall.887
-rw-r--r--man/man8/tc-mirred.8107
-rw-r--r--man/man8/tc-mpls.8194
-rw-r--r--man/man8/tc-mqprio.8290
-rw-r--r--man/man8/tc-nat.878
-rw-r--r--man/man8/tc-netem.8405
-rw-r--r--man/man8/tc-pedit.8402
-rw-r--r--man/man8/tc-pfifo.81
-rw-r--r--man/man8/tc-pfifo_fast.853
-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.8160
-rw-r--r--man/man8/tc-skbprio.870
-rw-r--r--man/man8/tc-stab.8163
-rw-r--r--man/man8/tc-taprio.8256
-rw-r--r--man/man8/tc-tbf.8141
-rw-r--r--man/man8/tc-tunnel_key.8175
-rw-r--r--man/man8/tc-u32.8674
-rw-r--r--man/man8/tc-vlan.8164
-rw-r--r--man/man8/tc.8893
-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.8181
-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.c833
-rw-r--r--misc/ifstat.c1040
-rw-r--r--misc/lnstat.c379
-rw-r--r--misc/lnstat.h43
-rw-r--r--misc/lnstat_util.c325
-rw-r--r--misc/nstat.c776
-rw-r--r--misc/rtacct.c622
-rw-r--r--misc/ss.c5887
-rw-r--r--misc/ss_util.h23
-rw-r--r--misc/ssfilter.h34
-rw-r--r--misc/ssfilter.y369
-rw-r--r--misc/ssfilter_check.c104
-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.c235
-rw-r--r--netem/normal.c52
-rw-r--r--netem/pareto.c42
-rw-r--r--netem/paretonormal.c82
-rw-r--r--netem/stats.c78
-rw-r--r--rdma/.gitignore1
-rw-r--r--rdma/Makefile22
-rw-r--r--rdma/dev.c381
-rw-r--r--rdma/include/uapi/rdma/ib_user_sa.h77
-rw-r--r--rdma/include/uapi/rdma/ib_user_verbs.h1364
-rw-r--r--rdma/include/uapi/rdma/rdma_netlink.h599
-rw-r--r--rdma/include/uapi/rdma/rdma_user_cm.h341
-rw-r--r--rdma/link.c382
-rw-r--r--rdma/rdma.c177
-rw-r--r--rdma/rdma.h144
-rw-r--r--rdma/res-cmid.c250
-rw-r--r--rdma/res-cq.c181
-rw-r--r--rdma/res-ctx.c103
-rw-r--r--rdma/res-mr.c146
-rw-r--r--rdma/res-pd.c130
-rw-r--r--rdma/res-qp.c233
-rw-r--r--rdma/res-srq.c300
-rw-r--r--rdma/res.c258
-rw-r--r--rdma/res.h196
-rw-r--r--rdma/stat-mr.c86
-rw-r--r--rdma/stat.c1131
-rw-r--r--rdma/stat.h24
-rw-r--r--rdma/sys.c182
-rw-r--r--rdma/utils.c958
-rw-r--r--schema/bridge_fdb_schema.json62
-rw-r--r--tc/.gitignore5
-rw-r--r--tc/Makefile181
-rw-r--r--tc/e_bpf.c176
-rw-r--r--tc/em_canid.c186
-rw-r--r--tc/em_cmp.c180
-rw-r--r--tc/em_ipset.c260
-rw-r--r--tc/em_ipt.c207
-rw-r--r--tc/em_meta.c541
-rw-r--r--tc/em_nbyte.c136
-rw-r--r--tc/em_u32.c171
-rw-r--r--tc/emp_ematch.l146
-rw-r--r--tc/emp_ematch.y96
-rw-r--r--tc/f_basic.c147
-rw-r--r--tc/f_bpf.c266
-rw-r--r--tc/f_cgroup.c109
-rw-r--r--tc/f_flow.c362
-rw-r--r--tc/f_flower.c3190
-rw-r--r--tc/f_fw.c168
-rw-r--r--tc/f_matchall.c167
-rw-r--r--tc/f_route.c178
-rw-r--r--tc/f_u32.c1389
-rw-r--r--tc/m_action.c914
-rw-r--r--tc/m_bpf.c215
-rw-r--r--tc/m_connmark.c138
-rw-r--r--tc/m_csum.c228
-rw-r--r--tc/m_ct.c549
-rw-r--r--tc/m_ctinfo.c268
-rw-r--r--tc/m_ematch.c572
-rw-r--r--tc/m_ematch.h110
-rw-r--r--tc/m_estimator.c59
-rw-r--r--tc/m_gact.c217
-rw-r--r--tc/m_gate.c537
-rw-r--r--tc/m_ife.c331
-rw-r--r--tc/m_mirred.c325
-rw-r--r--tc/m_mpls.c299
-rw-r--r--tc/m_nat.c195
-rw-r--r--tc/m_pedit.c863
-rw-r--r--tc/m_pedit.h78
-rw-r--r--tc/m_police.c362
-rw-r--r--tc/m_sample.c185
-rw-r--r--tc/m_simple.c202
-rw-r--r--tc/m_skbedit.c266
-rw-r--r--tc/m_skbmod.c234
-rw-r--r--tc/m_tunnel_key.c758
-rw-r--r--tc/m_vlan.c309
-rw-r--r--tc/p_eth.c69
-rw-r--r--tc/p_icmp.c31
-rw-r--r--tc/p_ip.c156
-rw-r--r--tc/p_ip6.c99
-rw-r--r--tc/p_tcp.c67
-rw-r--r--tc/p_udp.c61
-rw-r--r--tc/q_cake.c829
-rw-r--r--tc/q_cbs.c136
-rw-r--r--tc/q_choke.c234
-rw-r--r--tc/q_clsact.c34
-rw-r--r--tc/q_codel.c200
-rw-r--r--tc/q_drr.c114
-rw-r--r--tc/q_etf.c145
-rw-r--r--tc/q_ets.c337
-rw-r--r--tc/q_fifo.c94
-rw-r--r--tc/q_fq.c598
-rw-r--r--tc/q_fq_codel.c325
-rw-r--r--tc/q_fq_pie.c315
-rw-r--r--tc/q_gred.c502
-rw-r--r--tc/q_hfsc.c411
-rw-r--r--tc/q_hhf.c210
-rw-r--r--tc/q_htb.c385
-rw-r--r--tc/q_ingress.c47
-rw-r--r--tc/q_mqprio.c419
-rw-r--r--tc/q_multiq.c71
-rw-r--r--tc/q_netem.c867
-rw-r--r--tc/q_pie.c242
-rw-r--r--tc/q_plug.c76
-rw-r--r--tc/q_prio.c126
-rw-r--r--tc/q_qfq.c111
-rw-r--r--tc/q_red.c278
-rw-r--r--tc/q_sfb.c212
-rw-r--r--tc/q_sfq.c279
-rw-r--r--tc/q_skbprio.c80
-rw-r--r--tc/q_taprio.c654
-rw-r--r--tc/q_tbf.c343
-rw-r--r--tc/static-syms.c15
-rw-r--r--tc/tc.c357
-rw-r--r--tc/tc_class.c488
-rw-r--r--tc/tc_common.h30
-rw-r--r--tc/tc_core.c253
-rw-r--r--tc/tc_core.h36
-rw-r--r--tc/tc_estimator.c40
-rw-r--r--tc/tc_exec.c108
-rw-r--r--tc/tc_filter.c797
-rw-r--r--tc/tc_monitor.c115
-rw-r--r--tc/tc_qdisc.c544
-rw-r--r--tc/tc_qevent.c218
-rw-r--r--tc/tc_qevent.h51
-rw-r--r--tc/tc_red.c119
-rw-r--r--tc/tc_red.h11
-rw-r--r--tc/tc_stab.c136
-rw-r--r--tc/tc_util.c916
-rw-r--r--tc/tc_util.h141
-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.t17
-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/neigh/basic.t13
-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/rule/dsfield.t29
-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/flower_mpls.t82
-rwxr-xr-xtestsuite/tests/tc/mpls.t69
-rwxr-xr-xtestsuite/tests/tc/pedit.t217
-rwxr-xr-xtestsuite/tests/tc/vlan.t85
-rw-r--r--testsuite/tools/Makefile13
-rw-r--r--testsuite/tools/generate_nlmsg.c112
-rw-r--r--tipc/.gitignore1
-rw-r--r--tipc/Makefile24
-rw-r--r--tipc/README63
-rw-r--r--tipc/bearer.c1134
-rw-r--r--tipc/bearer.h22
-rw-r--r--tipc/cmdl.c130
-rw-r--r--tipc/cmdl.h54
-rw-r--r--tipc/link.c1248
-rw-r--r--tipc/link.h17
-rw-r--r--tipc/media.c273
-rw-r--r--tipc/media.h17
-rw-r--r--tipc/misc.c167
-rw-r--r--tipc/misc.h19
-rw-r--r--tipc/msg.c48
-rw-r--r--tipc/msg.h16
-rw-r--r--tipc/nametable.c118
-rw-r--r--tipc/nametable.h17
-rw-r--r--tipc/node.c505
-rw-r--r--tipc/node.h17
-rw-r--r--tipc/peer.c142
-rw-r--r--tipc/peer.h17
-rw-r--r--tipc/socket.c146
-rw-r--r--tipc/socket.h17
-rw-r--r--tipc/tipc.c130
-rw-r--r--vdpa/.gitignore1
-rw-r--r--vdpa/Makefile19
-rw-r--r--vdpa/include/uapi/linux/vdpa.h63
-rw-r--r--vdpa/include/uapi/linux/virtio_ids.h84
-rw-r--r--vdpa/include/uapi/linux/virtio_ring.h248
-rw-r--r--vdpa/vdpa.c1167
694 files changed, 211439 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/CREDITS b/CREDITS
new file mode 100644
index 0000000..bcfe05b
--- /dev/null
+++ b/CREDITS
@@ -0,0 +1,30 @@
+ This is at least a partial credits file of people that have
+ contributed to iproute2 over its history.
+ It is sorted by name and formatted to allow easy grepping
+ and beautification by scripts. The fields are: name (N),
+ email (E), web-address (W)
+
+N: Werner Almesberger
+E: werner@almesberger.net
+W: https://www.almesberger.net/
+D: Wrote original classifiers, qdiscs, etc.
+
+N: Stephen Hemminger
+E: stephen@networkplumber.org
+D: Took over iproute2 starting with 2.6 kernel
+
+N: Alexey Kuznetsov
+E: kuznet@ms2.inr.ac.ru
+D: Original author of iproute2
+
+N: YOSHIFUJI Hideaki/ 吉藤英明
+E: yoshfuji@linux-ipv6.org
+D: USAGI project - Linux IPv6 Development
+
+N: David S. Miller
+E: davem@davemloft.net
+D: Developed the kernel networking and routing system
+
+N: Jamal Hadi Salam
+D: Original developer of TC classifier and action
+E: hadi@cyberus.ca
diff --git a/MAINTAINERS b/MAINTAINERS
new file mode 100644
index 0000000..1b49d69
--- /dev/null
+++ b/MAINTAINERS
@@ -0,0 +1,58 @@
+Iproute2 Maintainers
+====================
+
+The file provides a set of names that are are able to help
+review patches and answer questions. This is in addition to
+the netdev@vger.kernel.org mailing list used for all iproute2
+and kernel networking.
+
+Descriptions of section entries:
+
+ M: Maintainer's Full Name <address@domain>
+ T: Git tree location.
+ F: Files and directories with wildcard patterns.
+ A trailing slash includes all files and subdirectory files.
+ A wildcard includes all files but not subdirectories.
+ One pattern per line. Multiple F: lines acceptable.
+
+Main Branch
+M: Stephen Hemminger <stephen@networkplumber.org>
+T: git://git.kernel.org/pub/scm/network/iproute2/iproute2.git
+L: netdev@vger.kernel.org
+
+Next Tree
+M: David Ahern <dsahern@gmail.com>
+T: git://git.kernel.org/pub/scm/network/iproute2/iproute2-next.git
+L: netdev@vger.kernel.org
+
+Ethernet Bridging - bridge
+M: Roopa Prabhu <roopa@nvidia.com>
+M: Nikolay Aleksandrov <razor@blackwall.org>
+L: bridge@lists.linux-foundation.org (moderated for non-subscribers)
+F: bridge/*
+
+Data Center Bridging - dcb
+M: Petr Machata <me@pmachata.org>
+F: dcb/*
+
+devlink
+M: Jiri Pirko <jiri@resnulli.us>
+F: devlink/*
+
+netkit
+M: Daniel Borkmann <daniel@iogearbox.net>
+M: Nikolay Aleksandrov <razor@blackwall.org>
+L: bpf@vger.kernel.org
+F: ip/iplink_netkit.c
+
+Remote DMA - rdma
+M: Leon Romanovsky <leon@kernel.org>
+F: rdma/*
+
+Transparent Inter-Process Communication - tipc
+M: Jon Maloy <jmaloy@redhat.com>
+F: tipc/*
+
+virtual Datapath Acceleration - vdpa
+M: Parav Pandit <parav@nvidia.com>
+F: vdpa/*
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..8024d45
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,144 @@
+# SPDX-License-Identifier: GPL-2.0
+# Top level Makefile for iproute2
+
+-include config.mk
+
+ifeq ("$(origin V)", "command line")
+VERBOSE = $(V)
+endif
+ifndef VERBOSE
+VERBOSE = 0
+endif
+
+ifeq ($(VERBOSE),0)
+MAKEFLAGS += --no-print-directory
+endif
+
+PREFIX?=/usr
+SBINDIR?=/sbin
+NETNS_RUN_DIR?=/var/run/netns
+NETNS_ETC_DIR?=/etc/netns
+DATADIR?=$(PREFIX)/share
+HDRDIR?=$(PREFIX)/include/iproute2
+CONF_ETC_DIR?=/etc/iproute2
+CONF_USR_DIR?=$(DATADIR)/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+=-DCONF_USR_DIR=\"$(CONF_USR_DIR)\" \
+ -DCONF_ETC_DIR=\"$(CONF_ETC_DIR)\" \
+ -DNETNS_RUN_DIR=\"$(NETNS_RUN_DIR)\" \
+ -DNETNS_ETC_DIR=\"$(NETNS_ETC_DIR)\" \
+ -DCONF_COLOR=$(CONF_COLOR)
+
+#options for AX.25
+ADDLIB+=ax25_ntop.o
+
+#options for AX.25
+ADDLIB+=rose_ntop.o
+
+#options for mpls
+ADDLIB+=mpls_ntop.o mpls_pton.o
+
+#options for NETROM
+ADDLIB+=netrom_ntop.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 man
+ifeq ($(HAVE_MNL),y)
+SUBDIRS += tipc devlink rdma dcb vdpa
+endif
+
+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:
+ @if [ ! -f config.mk -o configure -nt config.mk ]; then \
+ sh configure $(KERNEL_INCLUDE); \
+ fi
+
+install: all
+ install -m 0755 -d $(DESTDIR)$(SBINDIR)
+ install -m 0755 -d $(DESTDIR)$(CONF_USR_DIR)
+ 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)$(CONF_USR_DIR)
+ 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..4eb9390
--- /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
+
+David Ahern
+dsahern@gmail.com
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/SECURITY.md b/SECURITY.md
new file mode 100644
index 0000000..d5a7775
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,21 @@
+# Security Policy
+
+## Reporting a vulnerability
+
+The iproute2 suite of utilities is tightly coupled with the Linux
+kernel networking. Therefore the bug reporting process mirrors
+the Linux kernel. Most security problems reported related to
+iproute2 are really Linux kernel issues (a.k.a Shoot the messenger)
+and are best handled via
+[Linux Security Bugs](https://docs.kernel.org/process/security-bugs.html).
+
+For other issues please report bugs to netdev@vger.kernel.org
+and include an example script.
+
+## Supported Versions
+
+There are no official "Long Term Support" versions for iproute2.
+The iproute2 version matches the Linux kernel versions.
+There will be occasional maintenance releases for serious
+issues if found. Users who need support are encouraged
+to use the version of iproute2 found in major distributions.
diff --git a/bash-completion/devlink b/bash-completion/devlink
new file mode 100644
index 0000000..52dc82b
--- /dev/null
+++ b/bash-completion/devlink
@@ -0,0 +1,1104 @@
+# 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)
+ ;;
+ selftests_id)
+ dev=${words[4]}
+ value=$(devlink -j dev selftests show 2>/dev/null \
+ | jq ".selftests[\"$dev\"][]")
+ ;;
+ 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)')
+ ;;
+ lc)
+ dev=${words[3]}
+ value=$(devlink -j lc show 2>/dev/null \
+ | jq ".lc[\"$dev\"]" \
+ | jq '. as $lcs | $lcs | keys[] as $key |($lcs[$key].lc)' \
+ 2>/dev/null)
+ ;;
+ lc_type)
+ dev=${words[3]}
+ lc=${words[5]}
+ value=$(devlink lc show $dev lc $lc -j 2>/dev/null \
+ | jq '.[][][]["supported_types"][]')
+ ;;
+ 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 selftests
+_devlink_dev_selftests()
+{
+ if [[ $cword -gt 5 ]]; then
+ _devlink_direct_complete "selftests_id"
+ return
+ fi
+ case "$cword" in
+ 3)
+ COMPREPLY=( $( compgen -W "show run" -- "$cur" ) )
+ return
+ ;;
+ 4)
+ _devlink_direct_complete "dev"
+ return
+ ;;
+ 5)
+ COMPREPLY=( $( compgen -W "id" -- "$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|selftests)
+ _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 lc set
+_devlink_lc_set()
+{
+ case "$cword" in
+ 3)
+ _devlink_direct_complete "dev"
+ return
+ ;;
+ 4)
+ COMPREPLY=( $( compgen -W "lc" -- "$cur" ) )
+ ;;
+ 5)
+ _devlink_direct_complete "lc"
+ ;;
+ 6)
+ COMPREPLY=( $( compgen -W "type notype" -- "$cur" ) )
+ return
+ ;;
+ 7)
+ if [[ "$prev" == "type" ]]; then
+ _devlink_direct_complete "lc_type"
+ fi
+ esac
+}
+
+# Completion for devlink lc show
+_devlink_lc_show()
+{
+ case $cword in
+ 3)
+ _devlink_direct_complete "dev"
+ ;;
+ 4)
+ COMPREPLY=( $( compgen -W "lc" -- "$cur" ) )
+ ;;
+ 5)
+ _devlink_direct_complete "lc"
+ ;;
+ esac
+}
+
+# Completion for devlink lc
+_devlink_lc()
+{
+ case $command in
+ set)
+ _devlink_lc_set
+ return
+ ;;
+ show)
+ _devlink_lc_show
+ 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]}
+ local prev=${words[cword - 1]}
+
+ 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..61f0039
--- /dev/null
+++ b/bash-completion/tc
@@ -0,0 +1,785 @@
+# 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 sfb sfq tbf \
+ drr hfsc htb prio qfq '
+FILTER_KIND=' basic bpf cgroup flow flower fw route 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 ethercat ieeepup \
+ ieeepupat ip ipv4 ipv6 ipx irda lat localtalk loop mobitex \
+ ppp_disc ppp_mp ppp_ses ppptalk profinet 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
+ ;;
+ 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
+ ;;
+ hfsc)
+ _tc_once_attr 'default'
+ return 0
+ ;;
+ htb)
+ _tc_once_attr 'default r2q direct_qlen debug'
+ return 0
+ ;;
+ multiq|pfifo_fast|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
+ ;;
+ 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..01f8a45
--- /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 vni.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..704e76b
--- /dev/null
+++ b/bridge/br_common.h
@@ -0,0 +1,33 @@
+/* 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))))
+
+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,
+ bool global_only);
+int print_vnifilter_rtm(struct nlmsghdr *n, void *arg);
+void br_print_router_port_stats(struct rtattr *pattr);
+void print_headers(FILE *fp, const char *label);
+
+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);
+int do_vni(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..f480509
--- /dev/null
+++ b/bridge/bridge.c
@@ -0,0 +1,195 @@
+/* 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;
+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 | vni | 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 },
+ { "vni", do_vni },
+ { "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)
+{
+ int color = CONF_COLOR;
+
+ 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_color(opt, &color)) {
+ } else if (matches(opt, "-compressvlans") == 0) {
+ ++compress_vlans;
+ } 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..7b44436
--- /dev/null
+++ b/bridge/fdb.c
@@ -0,0 +1,914 @@
+/* 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"
+ " bridge fdb flush dev DEV [ brport DEV ] [ vlan VID ] [ src_vni VNI ]\n"
+ " [ nhid NHID ] [ vni VNI ] [ port PORT ] [ dst IPADDR ] [ self ]\n"
+ " [ master ] [ [no]permanent | [no]static | [no]dynamic ]\n"
+ " [ [no]added_by_user ] [ [no]extern_learn ] [ [no]sticky ]\n"
+ " [ [no]offloaded ] [ [no]router ]\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, __u32 ext_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");
+
+ if (ext_flags & NTF_EXT_LOCKED)
+ print_string(PRINT_ANY, NULL, "%s ", "locked");
+
+ 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];
+ __u32 ext_flags = 0;
+ __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_FLAGS_EXT])
+ ext_flags = rta_getattr_u32(tb[NDA_FLAGS_EXT]);
+
+ 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;
+
+ print_headers(fp, "[NEIGH]");
+
+ 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_string(PRINT_FP, NULL, "dev ", NULL);
+
+ print_color_string(PRINT_ANY, COLOR_IFNAME,
+ "ifname", "%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_string(PRINT_FP, NULL, "dst ", NULL);
+
+ print_color_string(PRINT_ANY,
+ ifa_family_color(family),
+ "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, ext_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;
+ int ret;
+
+ 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);
+ ret = 0;
+ if (print_fdb(answer, stdout) < 0) {
+ fprintf(stderr, "An error :-)\n");
+ ret = -1;
+ }
+ delete_json_obj();
+ free(answer);
+
+ return ret;
+}
+
+static int fdb_flush(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 | NLM_F_BULK,
+ .n.nlmsg_type = RTM_DELNEIGH,
+ .ndm.ndm_family = PF_BRIDGE,
+ };
+ unsigned short ndm_state_mask = 0;
+ unsigned short ndm_flags_mask = 0;
+ short vid = -1, brport_ifidx = -1;
+ char *d = NULL, *brport = NULL;
+ unsigned short ndm_flags = 0;
+ unsigned short ndm_state = 0;
+ unsigned long src_vni = ~0;
+ unsigned long vni = ~0;
+ unsigned long port = 0;
+ inet_prefix dst;
+ int dst_ok = 0;
+ __u32 nhid = 0;
+ char *endptr;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "dev") == 0) {
+ NEXT_ARG();
+ d = *argv;
+ } else if (strcmp(*argv, "master") == 0) {
+ ndm_flags |= NTF_MASTER;
+ } else if (strcmp(*argv, "self") == 0) {
+ ndm_flags |= NTF_SELF;
+ } else if (strcmp(*argv, "permanent") == 0) {
+ ndm_state |= NUD_PERMANENT;
+ ndm_state_mask |= NUD_PERMANENT;
+ } else if (strcmp(*argv, "nopermanent") == 0) {
+ ndm_state &= ~NUD_PERMANENT;
+ ndm_state_mask |= NUD_PERMANENT;
+ } else if (strcmp(*argv, "static") == 0) {
+ ndm_state |= NUD_NOARP;
+ ndm_state_mask |= NUD_NOARP | NUD_PERMANENT;
+ } else if (strcmp(*argv, "nostatic") == 0) {
+ ndm_state &= ~NUD_NOARP;
+ ndm_state_mask |= NUD_NOARP;
+ } else if (strcmp(*argv, "dynamic") == 0) {
+ ndm_state &= ~NUD_NOARP | NUD_PERMANENT;
+ ndm_state_mask |= NUD_NOARP | NUD_PERMANENT;
+ } else if (strcmp(*argv, "nodynamic") == 0) {
+ ndm_state |= NUD_NOARP;
+ ndm_state_mask |= NUD_NOARP;
+ } else if (strcmp(*argv, "added_by_user") == 0) {
+ ndm_flags |= NTF_USE;
+ ndm_flags_mask |= NTF_USE;
+ } else if (strcmp(*argv, "noadded_by_user") == 0) {
+ ndm_flags &= ~NTF_USE;
+ ndm_flags_mask |= NTF_USE;
+ } else if (strcmp(*argv, "extern_learn") == 0) {
+ ndm_flags |= NTF_EXT_LEARNED;
+ ndm_flags_mask |= NTF_EXT_LEARNED;
+ } else if (strcmp(*argv, "noextern_learn") == 0) {
+ ndm_flags &= ~NTF_EXT_LEARNED;
+ ndm_flags_mask |= NTF_EXT_LEARNED;
+ } else if (strcmp(*argv, "sticky") == 0) {
+ ndm_flags |= NTF_STICKY;
+ ndm_flags_mask |= NTF_STICKY;
+ } else if (strcmp(*argv, "nosticky") == 0) {
+ ndm_flags &= ~NTF_STICKY;
+ ndm_flags_mask |= NTF_STICKY;
+ } else if (strcmp(*argv, "offloaded") == 0) {
+ ndm_flags |= NTF_OFFLOADED;
+ ndm_flags_mask |= NTF_OFFLOADED;
+ } else if (strcmp(*argv, "nooffloaded") == 0) {
+ ndm_flags &= ~NTF_OFFLOADED;
+ ndm_flags_mask |= NTF_OFFLOADED;
+ } else if (strcmp(*argv, "router") == 0) {
+ ndm_flags |= NTF_ROUTER;
+ ndm_flags_mask |= NTF_ROUTER;
+ } else if (strcmp(*argv, "norouter") == 0) {
+ ndm_flags &= ~NTF_ROUTER;
+ ndm_flags_mask |= NTF_ROUTER;
+ } else if (strcmp(*argv, "brport") == 0) {
+ if (brport)
+ duparg2("brport", *argv);
+ NEXT_ARG();
+ brport = *argv;
+ } else if (strcmp(*argv, "vlan") == 0) {
+ if (vid >= 0)
+ duparg2("vlan", *argv);
+ NEXT_ARG();
+ vid = atoi(*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, "nhid") == 0) {
+ NEXT_ARG();
+ if (get_u32(&nhid, *argv, 0))
+ invarg("\"nid\" value is invalid\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, "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, "dst") == 0) {
+ NEXT_ARG();
+ if (dst_ok)
+ duparg2("dst", *argv);
+ get_addr(&dst, *argv, preferred_family);
+ dst_ok = 1;
+ } else if (strcmp(*argv, "help") == 0) {
+ NEXT_ARG();
+ } else {
+ fprintf(stderr, "bridge fdb: unknown command \"%s\"?\n",
+ *argv);
+ usage();
+ return -1;
+ }
+ argc--; argv++;
+ }
+
+ if (d == NULL) {
+ fprintf(stderr, "Device is a required argument.\n");
+ return -1;
+ }
+
+ req.ndm.ndm_ifindex = ll_name_to_index(d);
+ if (req.ndm.ndm_ifindex == 0) {
+ fprintf(stderr, "Cannot find bridge device \"%s\"\n", d);
+ return -1;
+ }
+
+ if (brport) {
+ brport_ifidx = ll_name_to_index(brport);
+ if (brport_ifidx == 0) {
+ fprintf(stderr, "Cannot find bridge port device \"%s\"\n",
+ brport);
+ return -1;
+ }
+ }
+
+ if (vid >= 4096) {
+ fprintf(stderr, "Invalid VLAN ID \"%hu\"\n", vid);
+ return -1;
+ }
+
+ /* if self and master were not specified assume self */
+ if (!(ndm_flags & (NTF_SELF | NTF_MASTER)))
+ ndm_flags |= NTF_SELF;
+
+ req.ndm.ndm_flags = ndm_flags;
+ req.ndm.ndm_state = ndm_state;
+ if (brport_ifidx > -1)
+ addattr32(&req.n, sizeof(req), NDA_IFINDEX, brport_ifidx);
+ if (vid > -1)
+ addattr16(&req.n, sizeof(req), NDA_VLAN, vid);
+ if (src_vni != ~0)
+ addattr32(&req.n, sizeof(req), NDA_SRC_VNI, src_vni);
+ if (nhid > 0)
+ addattr32(&req.n, sizeof(req), NDA_NH_ID, nhid);
+ if (vni != ~0)
+ addattr32(&req.n, sizeof(req), NDA_VNI, vni);
+ if (port) {
+ unsigned short dport;
+
+ dport = htons((unsigned short)port);
+ addattr16(&req.n, sizeof(req), NDA_PORT, dport);
+ }
+ if (dst_ok)
+ addattr_l(&req.n, sizeof(req), NDA_DST, &dst.data, dst.bytelen);
+ if (ndm_flags_mask)
+ addattr8(&req.n, sizeof(req), NDA_NDM_FLAGS_MASK,
+ ndm_flags_mask);
+ if (ndm_state_mask)
+ addattr16(&req.n, sizeof(req), NDA_NDM_STATE_MASK,
+ ndm_state_mask);
+
+ if (rtnl_talk(&rth, &req.n, NULL) < 0)
+ return -1;
+
+ return 0;
+}
+
+int do_fdb(int argc, char **argv)
+{
+ ll_init_map(&rth);
+ timestamp = 0;
+
+ 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 (strcmp(*argv, "flush") == 0)
+ return fdb_flush(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..1c8faa8
--- /dev/null
+++ b/bridge/link.c
@@ -0,0 +1,696 @@
+/* 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_dev_index;
+static unsigned int filter_master_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_BCAST_FLOOD])
+ print_on_off(PRINT_ANY, "bcast_flood", "bcast_flood %s ",
+ rta_getattr_u8(prtb[IFLA_BRPORT_BCAST_FLOOD]));
+ if (prtb[IFLA_BRPORT_MULTICAST_ROUTER])
+ print_uint(PRINT_ANY, "mcast_router", "mcast_router %u ",
+ rta_getattr_u8(prtb[IFLA_BRPORT_MULTICAST_ROUTER]));
+ 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_NEIGH_VLAN_SUPPRESS]) {
+ struct rtattr *at;
+
+ at = prtb[IFLA_BRPORT_NEIGH_VLAN_SUPPRESS];
+ print_on_off(PRINT_ANY, "neigh_vlan_suppress",
+ "neigh_vlan_suppress %s ",
+ rta_getattr_u8(at));
+ }
+ 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_BACKUP_NHID])
+ print_uint(PRINT_ANY, "backup_nhid", "backup_nhid %u ",
+ rta_getattr_u32(prtb[IFLA_BRPORT_BACKUP_NHID]));
+
+ if (prtb[IFLA_BRPORT_ISOLATED])
+ print_on_off(PRINT_ANY, "isolated", "isolated %s ",
+ rta_getattr_u8(prtb[IFLA_BRPORT_ISOLATED]));
+ if (prtb[IFLA_BRPORT_LOCKED])
+ print_on_off(PRINT_ANY, "locked", "locked %s ",
+ rta_getattr_u8(prtb[IFLA_BRPORT_LOCKED]));
+ if (prtb[IFLA_BRPORT_MAB])
+ print_on_off(PRINT_ANY, "mab", "mab %s ",
+ rta_getattr_u8(prtb[IFLA_BRPORT_MAB]));
+ if (prtb[IFLA_BRPORT_MCAST_N_GROUPS]) {
+ struct rtattr *at = prtb[IFLA_BRPORT_MCAST_N_GROUPS];
+
+ print_uint(PRINT_ANY, "mcast_n_groups",
+ "mcast_n_groups %u ", rta_getattr_u32(at));
+ }
+ if (prtb[IFLA_BRPORT_MCAST_MAX_GROUPS]) {
+ struct rtattr *at = prtb[IFLA_BRPORT_MCAST_MAX_GROUPS];
+
+ print_uint(PRINT_ANY, "mcast_max_groups",
+ "mcast_max_groups %u ", rta_getattr_u32(at));
+ }
+ } 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]));
+}
+
+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_dev_index && filter_dev_index != ifi->ifi_index)
+ return 0;
+
+ parse_rtattr_flags(tb, IFLA_MAX, IFLA_RTA(ifi), len, NLA_F_NESTED);
+
+ if (filter_master_index && tb[IFLA_MASTER] &&
+ filter_master_index != rta_getattr_u32(tb[IFLA_MASTER]))
+ return 0;
+
+ name = get_ifname_rta(ifi->ifi_index, tb[IFLA_IFNAME]);
+ if (!name)
+ return -1;
+
+ print_headers(fp, "[LINK]");
+
+ 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_router MULTICAST_ROUTER ]\n"
+ " [ mcast_flood {on | off} ]\n"
+ " [ bcast_flood {on | off} ]\n"
+ " [ mcast_to_unicast {on | off} ]\n"
+ " [ mcast_max_groups MAX_GROUPS ]\n"
+ " [ neigh_suppress {on | off} ]\n"
+ " [ neigh_vlan_suppress {on | off} ]\n"
+ " [ vlan_tunnel {on | off} ]\n"
+ " [ isolated {on | off} ]\n"
+ " [ locked {on | off} ]\n"
+ " [ mab {on | off} ]\n"
+ " [ hwmode {vepa | veb} ]\n"
+ " [ backup_port DEVICE ] [ nobackup_port ]\n"
+ " [ backup_nhid NHID ]\n"
+ " [ self ] [ master ]\n"
+ " bridge link show [dev DEV] [master DEVICE]\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;
+ bool backup_nhid_set = false;
+ __u32 backup_nhid;
+ int backup_port_idx = -1;
+ __s8 neigh_suppress = -1;
+ __s8 neigh_vlan_suppress = -1;
+ __s8 learning = -1;
+ __s8 learning_sync = -1;
+ __s8 flood = -1;
+ __s8 vlan_tunnel = -1;
+ __s8 mcast_router = -1;
+ __s8 mcast_flood = -1;
+ __s8 bcast_flood = -1;
+ __s8 mcast_to_unicast = -1;
+ __s32 max_groups = -1;
+ __s8 locked = -1;
+ __s8 macauth = -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_router") == 0) {
+ NEXT_ARG();
+ mcast_router = atoi(*argv);
+ } 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, "bcast_flood") == 0) {
+ NEXT_ARG();
+ bcast_flood = parse_on_off("bcast_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, "mcast_max_groups") == 0) {
+ NEXT_ARG();
+ if (get_s32(&max_groups, *argv, 0))
+ invarg("invalid mcast_max_groups", *argv);
+ } 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, "neigh_vlan_suppress") == 0) {
+ NEXT_ARG();
+ neigh_vlan_suppress = parse_on_off("neigh_vlan_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, "locked") == 0) {
+ NEXT_ARG();
+ locked = parse_on_off("locked", *argv, &ret);
+ if (ret)
+ return ret;
+ } else if (strcmp(*argv, "mab") == 0) {
+ NEXT_ARG();
+ macauth = parse_on_off("mab", *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 if (strcmp(*argv, "backup_nhid") == 0) {
+ NEXT_ARG();
+ if (get_u32(&backup_nhid, *argv, 0))
+ invarg("invalid backup_nhid", *argv);
+ backup_nhid_set = true;
+ } 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_router >= 0)
+ addattr8(&req.n, sizeof(req), IFLA_BRPORT_MULTICAST_ROUTER,
+ mcast_router);
+ if (mcast_flood >= 0)
+ addattr8(&req.n, sizeof(req), IFLA_BRPORT_MCAST_FLOOD,
+ mcast_flood);
+ if (bcast_flood >= 0)
+ addattr8(&req.n, sizeof(req), IFLA_BRPORT_BCAST_FLOOD,
+ bcast_flood);
+ if (mcast_to_unicast >= 0)
+ addattr8(&req.n, sizeof(req), IFLA_BRPORT_MCAST_TO_UCAST,
+ mcast_to_unicast);
+ if (max_groups >= 0)
+ addattr32(&req.n, sizeof(req), IFLA_BRPORT_MCAST_MAX_GROUPS,
+ max_groups);
+ 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 (neigh_vlan_suppress != -1)
+ addattr8(&req.n, sizeof(req), IFLA_BRPORT_NEIGH_VLAN_SUPPRESS,
+ neigh_vlan_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 (locked >= 0)
+ addattr8(&req.n, sizeof(req), IFLA_BRPORT_LOCKED, locked);
+
+ if (macauth >= 0)
+ addattr8(&req.n, sizeof(req), IFLA_BRPORT_MAB, macauth);
+
+ if (backup_port_idx != -1)
+ addattr32(&req.n, sizeof(req), IFLA_BRPORT_BACKUP_PORT,
+ backup_port_idx);
+
+ if (backup_nhid_set)
+ addattr32(&req.n, sizeof(req), IFLA_BRPORT_BACKUP_NHID,
+ backup_nhid);
+
+ 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;
+ char *filter_master = NULL;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "dev") == 0) {
+ NEXT_ARG();
+ if (filter_dev)
+ duparg("dev", *argv);
+ filter_dev = *argv;
+ }
+ if (strcmp(*argv, "master") == 0) {
+ NEXT_ARG();
+ if (filter_master)
+ duparg("master", *argv);
+ filter_master = *argv;
+ }
+ argc--; argv++;
+ }
+
+ if (filter_dev) {
+ filter_dev_index = ll_name_to_index(filter_dev);
+ if (!filter_dev_index)
+ return nodev(filter_dev);
+ }
+ if (filter_master) {
+ filter_master_index = ll_name_to_index(filter_master);
+ if (!filter_master_index)
+ return nodev(filter_master);
+ }
+
+ if (rtnl_linkdump_req(&rth, PF_BRIDGE) < 0) {
+ perror("Cannot 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);
+ timestamp = 0;
+
+ 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..196363a
--- /dev/null
+++ b/bridge/mdb.c
@@ -0,0 +1,1107 @@
+/* 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 <netdb.h>
+#include <limits.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 | replace } dev DEV port PORT grp GROUP [src SOURCE] [permanent | temp] [vid VID]\n"
+ " [ filter_mode { include | exclude } ] [ source_list SOURCE_LIST ] [ proto PROTO ] [ dst IPADDR ]\n"
+ " [ dst_port DST_PORT ] [ vni VNI ] [ src_vni SRC_VNI ] [ via DEV ]\n"
+ " bridge mdb {show} [ dev DEV ] [ vid VID ]\n"
+ " bridge mdb get dev DEV grp GROUP [ src SOURCE ] [ vid VID ] [ src_vni SRC_VNI ]\n"
+ " bridge mdb flush dev DEV [ port PORT ] [ vid VID ] [ src_vni SRC_VNI ] [ proto PROTO ]\n"
+ " [ [no]permanent ] [ dst IPADDR ] [ dst_port DST_PORT ] [ vni VNI ]\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;
+}
+
+void br_print_router_port_stats(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)
+ br_print_router_port_stats(i);
+ close_json_object();
+ } else if (show_stats) {
+ fprintf(f, "router ports on %s: %s",
+ brifname, port_ifname);
+
+ br_print_router_port_stats(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_dst(const struct rtattr *dst_attr)
+{
+ SPRINT_BUF(abuf);
+ int af = AF_INET;
+ const void *dst;
+
+ if (RTA_PAYLOAD(dst_attr) == sizeof(struct in6_addr))
+ af = AF_INET6;
+
+ dst = (const void *)RTA_DATA(dst_attr);
+ print_color_string(PRINT_ANY, ifa_family_color(af),
+ "dst", " dst %s",
+ inet_ntop(af, dst, abuf, sizeof(abuf)));
+}
+
+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 (tb[MDBA_MDB_EATTR_DST])
+ print_dst(tb[MDBA_MDB_EATTR_DST]);
+
+ if (tb[MDBA_MDB_EATTR_DST_PORT])
+ print_uint(PRINT_ANY, "dst_port", " dst_port %u",
+ rta_getattr_u16(tb[MDBA_MDB_EATTR_DST_PORT]));
+
+ if (tb[MDBA_MDB_EATTR_VNI])
+ print_uint(PRINT_ANY, "vni", " vni %u",
+ rta_getattr_u32(tb[MDBA_MDB_EATTR_VNI]));
+
+ if (tb[MDBA_MDB_EATTR_SRC_VNI])
+ print_uint(PRINT_ANY, "src_vni", " src_vni %u",
+ rta_getattr_u32(tb[MDBA_MDB_EATTR_SRC_VNI]));
+
+ if (tb[MDBA_MDB_EATTR_IFINDEX]) {
+ unsigned int ifindex;
+
+ ifindex = rta_getattr_u32(tb[MDBA_MDB_EATTR_IFINDEX]);
+ print_string(PRINT_ANY, "via", " via %s",
+ ll_index_to_name(ifindex));
+ }
+
+ 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;
+
+ print_headers(fp, "[MDB]");
+
+ 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");
+ delete_json_obj();
+ return -1;
+ }
+
+ open_json_array(PRINT_JSON, "mdb");
+ if (rtnl_dump_filter(&rth, print_mdbs, stdout) < 0) {
+ fprintf(stderr, "Dump terminated\n");
+ delete_json_obj();
+ return -1;
+ }
+ close_json_array(PRINT_JSON, NULL);
+
+ /* get router ports */
+ if (rtnl_mdbdump_req(&rth, PF_BRIDGE) < 0) {
+ perror("Cannot send dump request");
+ delete_json_obj();
+ return -1;
+ }
+
+ open_json_object("router");
+ if (rtnl_dump_filter(&rth, print_rtrs, stdout) < 0) {
+ fprintf(stderr, "Dump terminated\n");
+ delete_json_obj();
+ 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_parse_src(struct nlmsghdr *n, int maxlen, const char *src)
+{
+ struct in6_addr src_ip6;
+ __be32 src_ip4;
+
+ if (inet_pton(AF_INET, src, &src_ip4)) {
+ addattr32(n, maxlen, MDBE_ATTR_SOURCE, src_ip4);
+ return 0;
+ }
+
+ if (inet_pton(AF_INET6, src, &src_ip6)) {
+ addattr_l(n, maxlen, MDBE_ATTR_SOURCE, &src_ip6,
+ sizeof(src_ip6));
+ return 0;
+ }
+
+ return -1;
+}
+
+static int mdb_parse_mode(struct nlmsghdr *n, int maxlen, const char *mode)
+{
+ if (strcmp(mode, "include") == 0) {
+ addattr8(n, maxlen, MDBE_ATTR_GROUP_MODE, MCAST_INCLUDE);
+ return 0;
+ }
+
+ if (strcmp(mode, "exclude") == 0) {
+ addattr8(n, maxlen, MDBE_ATTR_GROUP_MODE, MCAST_EXCLUDE);
+ return 0;
+ }
+
+ return -1;
+}
+
+static int mdb_parse_src_entry(struct nlmsghdr *n, int maxlen, char *src_entry)
+{
+ struct in6_addr src_ip6;
+ struct rtattr *nest;
+ __be32 src_ip4;
+
+ nest = addattr_nest(n, maxlen, MDBE_SRC_LIST_ENTRY | NLA_F_NESTED);
+
+ if (inet_pton(AF_INET, src_entry, &src_ip4))
+ addattr32(n, maxlen, MDBE_SRCATTR_ADDRESS, src_ip4);
+ else if (inet_pton(AF_INET6, src_entry, &src_ip6))
+ addattr_l(n, maxlen, MDBE_SRCATTR_ADDRESS, &src_ip6,
+ sizeof(src_ip6));
+ else
+ return -1;
+
+ addattr_nest_end(n, nest);
+
+ return 0;
+}
+
+static int mdb_parse_src_list(struct nlmsghdr *n, int maxlen, char *src_list)
+{
+ struct rtattr *nest;
+ char *sep;
+
+ nest = addattr_nest(n, maxlen, MDBE_ATTR_SRC_LIST | NLA_F_NESTED);
+
+ do {
+ sep = strchr(src_list, ',');
+ if (sep)
+ *sep = '\0';
+
+ if (mdb_parse_src_entry(n, maxlen, src_list)) {
+ fprintf(stderr, "Invalid source entry \"%s\" in source list\n",
+ src_list);
+ return -1;
+ }
+
+ src_list = sep + 1;
+ } while (sep);
+
+ addattr_nest_end(n, nest);
+
+ return 0;
+}
+
+static int mdb_parse_proto(struct nlmsghdr *n, int maxlen, const char *proto)
+{
+ __u32 proto_id;
+ int err;
+
+ err = rtnl_rtprot_a2n(&proto_id, proto);
+ if (err)
+ return err;
+
+ addattr8(n, maxlen, MDBE_ATTR_RTPROT, proto_id);
+
+ return 0;
+}
+
+static int mdb_parse_dst(struct nlmsghdr *n, int maxlen, const char *dst)
+{
+ struct in6_addr dst_ip6;
+ __be32 dst_ip4;
+
+ if (inet_pton(AF_INET, dst, &dst_ip4)) {
+ addattr32(n, maxlen, MDBE_ATTR_DST, dst_ip4);
+ return 0;
+ }
+
+ if (inet_pton(AF_INET6, dst, &dst_ip6)) {
+ addattr_l(n, maxlen, MDBE_ATTR_DST, &dst_ip6,
+ sizeof(dst_ip6));
+ return 0;
+ }
+
+ return -1;
+}
+
+static int mdb_parse_dst_port(struct nlmsghdr *n, int maxlen,
+ const char *dst_port)
+{
+ unsigned long port;
+ char *endptr;
+
+ port = strtoul(dst_port, &endptr, 0);
+ if (endptr && *endptr) {
+ struct servent *pse;
+
+ pse = getservbyname(dst_port, "udp");
+ if (!pse)
+ return -1;
+ port = ntohs(pse->s_port);
+ } else if (port > USHRT_MAX) {
+ return -1;
+ }
+
+ addattr16(n, maxlen, MDBE_ATTR_DST_PORT, port);
+
+ return 0;
+}
+
+static int mdb_parse_vni(struct nlmsghdr *n, int maxlen, const char *vni,
+ int attr_type)
+{
+ unsigned long vni_num;
+ char *endptr;
+
+ vni_num = strtoul(vni, &endptr, 0);
+ if ((endptr && *endptr) || vni_num == ULONG_MAX)
+ return -1;
+
+ addattr32(n, maxlen, attr_type, vni_num);
+
+ return 0;
+}
+
+static int mdb_parse_dev(struct nlmsghdr *n, int maxlen, const char *dev)
+{
+ unsigned int ifindex;
+
+ ifindex = ll_name_to_index(dev);
+ if (!ifindex)
+ return -1;
+
+ addattr32(n, maxlen, MDBE_ATTR_IFINDEX, ifindex);
+
+ return 0;
+}
+
+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, *mode = NULL;
+ char *dst_port = NULL, *vni = NULL, *src_vni = NULL, *via = NULL;
+ char *src_list = NULL, *proto = NULL, *dst = NULL;
+ struct br_mdb_entry entry = {};
+ bool set_attrs = false;
+ 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;
+ set_attrs = true;
+ } else if (strcmp(*argv, "filter_mode") == 0) {
+ NEXT_ARG();
+ mode = *argv;
+ set_attrs = true;
+ } else if (strcmp(*argv, "source_list") == 0) {
+ NEXT_ARG();
+ src_list = *argv;
+ set_attrs = true;
+ } else if (strcmp(*argv, "proto") == 0) {
+ NEXT_ARG();
+ proto = *argv;
+ set_attrs = true;
+ } else if (strcmp(*argv, "dst") == 0) {
+ NEXT_ARG();
+ dst = *argv;
+ set_attrs = true;
+ } else if (strcmp(*argv, "dst_port") == 0) {
+ NEXT_ARG();
+ dst_port = *argv;
+ set_attrs = true;
+ } else if (strcmp(*argv, "vni") == 0) {
+ NEXT_ARG();
+ vni = *argv;
+ set_attrs = true;
+ } else if (strcmp(*argv, "src_vni") == 0) {
+ NEXT_ARG();
+ src_vni = *argv;
+ set_attrs = true;
+ } else if (strcmp(*argv, "via") == 0) {
+ NEXT_ARG();
+ via = *argv;
+ set_attrs = true;
+ } 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 (set_attrs) {
+ struct rtattr *nest = addattr_nest(&req.n, sizeof(req),
+ MDBA_SET_ENTRY_ATTRS);
+
+ nest->rta_type |= NLA_F_NESTED;
+
+ if (src && mdb_parse_src(&req.n, sizeof(req), src)) {
+ fprintf(stderr, "Invalid source address \"%s\"\n", src);
+ return -1;
+ }
+
+ if (mode && mdb_parse_mode(&req.n, sizeof(req), mode)) {
+ fprintf(stderr, "Invalid filter mode \"%s\"\n", mode);
+ return -1;
+ }
+
+ if (src_list && mdb_parse_src_list(&req.n, sizeof(req),
+ src_list))
+ return -1;
+
+ if (proto && mdb_parse_proto(&req.n, sizeof(req), proto)) {
+ fprintf(stderr, "Invalid protocol value \"%s\"\n",
+ proto);
+ return -1;
+ }
+
+ if (dst && mdb_parse_dst(&req.n, sizeof(req), dst)) {
+ fprintf(stderr, "Invalid underlay destination address \"%s\"\n",
+ dst);
+ return -1;
+ }
+
+ if (dst_port && mdb_parse_dst_port(&req.n, sizeof(req),
+ dst_port)) {
+ fprintf(stderr, "Invalid destination port \"%s\"\n", dst_port);
+ return -1;
+ }
+
+ if (vni && mdb_parse_vni(&req.n, sizeof(req), vni,
+ MDBE_ATTR_VNI)) {
+ fprintf(stderr, "Invalid destination VNI \"%s\"\n",
+ vni);
+ return -1;
+ }
+
+ if (src_vni && mdb_parse_vni(&req.n, sizeof(req), src_vni,
+ MDBE_ATTR_SRC_VNI)) {
+ fprintf(stderr, "Invalid source VNI \"%s\"\n", src_vni);
+ return -1;
+ }
+
+ if (via && mdb_parse_dev(&req.n, sizeof(req), via))
+ return nodev(via);
+
+ addattr_nest_end(&req.n, nest);
+ }
+
+ if (rtnl_talk(&rth, &req.n, NULL) < 0)
+ return -1;
+
+ return 0;
+}
+
+static int mdb_get(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,
+ .n.nlmsg_type = RTM_GETMDB,
+ .bpm.family = PF_BRIDGE,
+ };
+ char *d = NULL, *grp = NULL, *src = NULL, *src_vni = NULL;
+ struct br_mdb_entry entry = {};
+ struct nlmsghdr *answer;
+ bool get_attrs = false;
+ short vid = 0;
+ int ret = 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, "vid") == 0) {
+ NEXT_ARG();
+ vid = atoi(*argv);
+ } else if (strcmp(*argv, "src") == 0) {
+ NEXT_ARG();
+ src = *argv;
+ get_attrs = true;
+ } else if (strcmp(*argv, "src_vni") == 0) {
+ NEXT_ARG();
+ src_vni = *argv;
+ get_attrs = true;
+ } else {
+ if (strcmp(*argv, "help") == 0)
+ usage();
+ }
+ argc--; argv++;
+ }
+
+ if (d == NULL || grp == NULL) {
+ fprintf(stderr, "Device and group address are required arguments.\n");
+ return -1;
+ }
+
+ req.bpm.ifindex = ll_name_to_index(d);
+ if (!req.bpm.ifindex)
+ return nodev(d);
+
+ 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_GET_ENTRY, &entry, sizeof(entry));
+ if (get_attrs) {
+ struct rtattr *nest = addattr_nest(&req.n, sizeof(req),
+ MDBA_GET_ENTRY_ATTRS);
+
+ nest->rta_type |= NLA_F_NESTED;
+
+ if (src && mdb_parse_src(&req.n, sizeof(req), src)) {
+ fprintf(stderr, "Invalid source address \"%s\"\n", src);
+ return -1;
+ }
+
+ if (src_vni && mdb_parse_vni(&req.n, sizeof(req), src_vni,
+ MDBE_ATTR_SRC_VNI)) {
+ fprintf(stderr, "Invalid source VNI \"%s\"\n", src_vni);
+ return -1;
+ }
+
+ addattr_nest_end(&req.n, nest);
+ }
+
+ if (rtnl_talk(&rth, &req.n, &answer) < 0)
+ return -2;
+
+ new_json_obj(json);
+
+ if (print_mdbs(answer, stdout) < 0)
+ ret = -1;
+
+ delete_json_obj();
+ free(answer);
+
+ return ret;
+}
+
+static int mdb_flush(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 | NLM_F_BULK,
+ .n.nlmsg_type = RTM_DELMDB,
+ .bpm.family = PF_BRIDGE,
+ };
+ char *d = NULL, *p = NULL, *src_vni = NULL, *proto = NULL, *dst = NULL;
+ char *dst_port = NULL, *vni = NULL;
+ struct br_mdb_entry entry = {};
+ unsigned short state_mask = 0;
+ bool set_attrs = false;
+ short vid = 0;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "dev") == 0) {
+ NEXT_ARG();
+ d = *argv;
+ } else if (strcmp(*argv, "port") == 0) {
+ NEXT_ARG();
+ p = *argv;
+ } else if (strcmp(*argv, "vid") == 0) {
+ NEXT_ARG();
+ vid = atoi(*argv);
+ } else if (strcmp(*argv, "src_vni") == 0) {
+ NEXT_ARG();
+ src_vni = *argv;
+ set_attrs = true;
+ } else if (strcmp(*argv, "proto") == 0) {
+ NEXT_ARG();
+ proto = *argv;
+ set_attrs = true;
+ } else if (strcmp(*argv, "permanent") == 0) {
+ entry.state |= MDB_PERMANENT;
+ state_mask |= MDB_PERMANENT;
+ set_attrs = true;
+ } else if (strcmp(*argv, "nopermanent") == 0) {
+ entry.state &= ~MDB_PERMANENT;
+ state_mask |= MDB_PERMANENT;
+ set_attrs = true;
+ } else if (strcmp(*argv, "dst") == 0) {
+ NEXT_ARG();
+ dst = *argv;
+ set_attrs = true;
+ } else if (strcmp(*argv, "dst_port") == 0) {
+ NEXT_ARG();
+ dst_port = *argv;
+ set_attrs = true;
+ } else if (strcmp(*argv, "vni") == 0) {
+ NEXT_ARG();
+ vni = *argv;
+ set_attrs = true;
+ } else {
+ if (strcmp(*argv, "help") == 0)
+ usage();
+ }
+ argc--; argv++;
+ }
+
+ if (d == NULL) {
+ fprintf(stderr, "Device is a required argument.\n");
+ return -1;
+ }
+
+ req.bpm.ifindex = ll_name_to_index(d);
+ if (!req.bpm.ifindex)
+ return nodev(d);
+
+ if (p) {
+ entry.ifindex = ll_name_to_index(p);
+ if (!entry.ifindex)
+ return nodev(p);
+ }
+
+ entry.vid = vid;
+ addattr_l(&req.n, sizeof(req), MDBA_SET_ENTRY, &entry, sizeof(entry));
+ if (set_attrs) {
+ struct rtattr *nest = addattr_nest(&req.n, sizeof(req),
+ MDBA_SET_ENTRY_ATTRS);
+
+ nest->rta_type |= NLA_F_NESTED;
+
+ if (proto && mdb_parse_proto(&req.n, sizeof(req), proto)) {
+ fprintf(stderr, "Invalid protocol value \"%s\"\n",
+ proto);
+ return -1;
+ }
+
+ if (dst && mdb_parse_dst(&req.n, sizeof(req), dst)) {
+ fprintf(stderr, "Invalid underlay destination address \"%s\"\n",
+ dst);
+ return -1;
+ }
+
+ if (dst_port && mdb_parse_dst_port(&req.n, sizeof(req),
+ dst_port)) {
+ fprintf(stderr, "Invalid destination port \"%s\"\n", dst_port);
+ return -1;
+ }
+
+ if (vni && mdb_parse_vni(&req.n, sizeof(req), vni,
+ MDBE_ATTR_VNI)) {
+ fprintf(stderr, "Invalid destination VNI \"%s\"\n",
+ vni);
+ return -1;
+ }
+
+ if (src_vni && mdb_parse_vni(&req.n, sizeof(req), src_vni,
+ MDBE_ATTR_SRC_VNI)) {
+ fprintf(stderr, "Invalid source VNI \"%s\"\n", src_vni);
+ return -1;
+ }
+
+ if (state_mask)
+ addattr8(&req.n, sizeof(req), MDBE_ATTR_STATE_MASK,
+ state_mask);
+
+ 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);
+ timestamp = 0;
+
+ if (argc > 0) {
+ if (matches(*argv, "add") == 0)
+ return mdb_modify(RTM_NEWMDB, NLM_F_CREATE|NLM_F_EXCL, argc-1, argv+1);
+ if (strcmp(*argv, "replace") == 0)
+ return mdb_modify(RTM_NEWMDB, NLM_F_CREATE|NLM_F_REPLACE, 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 (strcmp(*argv, "get") == 0)
+ return mdb_get(argc-1, argv+1);
+ if (strcmp(*argv, "flush") == 0)
+ return mdb_flush(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..552614d
--- /dev/null
+++ b/bridge/monitor.c
@@ -0,0 +1,166 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * brmonitor.c "bridge monitor"
+ *
+ * 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 | vni | all]\n");
+ exit(-1);
+}
+
+static int accept_msg(struct rtnl_ctrl_data *ctrl,
+ struct nlmsghdr *n, void *arg)
+{
+ FILE *fp = arg;
+
+ switch (n->nlmsg_type) {
+ case RTM_NEWLINK:
+ case RTM_DELLINK:
+ return print_linkinfo(n, arg);
+
+ case RTM_NEWNEIGH:
+ case RTM_DELNEIGH:
+ return print_fdb(n, arg);
+
+ case RTM_NEWMDB:
+ case RTM_DELMDB:
+ return print_mdb_mon(n, arg);
+
+ case NLMSG_TSTAMP:
+ print_nlmsg_timestamp(fp, n);
+ return 0;
+
+ case RTM_NEWVLAN:
+ case RTM_DELVLAN:
+ return print_vlan_rtm(n, arg, true, false);
+
+ case RTM_NEWTUNNEL:
+ case RTM_DELTUNNEL:
+ return print_vnifilter_rtm(n, arg);
+
+ default:
+ return 0;
+ }
+}
+
+void print_headers(FILE *fp, const char *label)
+{
+ if (timestamp)
+ print_timestamp(fp);
+
+ if (prefix_banner)
+ fprintf(fp, "%s", label);
+}
+
+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;
+ int lvni = 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, "vni") == 0) {
+ lvni = 1;
+ groups = 0;
+ } else if (strcmp(*argv, "all") == 0) {
+ groups = ~RTMGRP_TC;
+ lvlan = 1;
+ lvni = 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);
+ }
+
+ if (lvni && rtnl_add_nl_group(&rth, RTNLGRP_TUNNEL) < 0) {
+ fprintf(stderr, "Failed to add bridge vni 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..5352eb2
--- /dev/null
+++ b/bridge/vlan.c
@@ -0,0 +1,1382 @@
+/* 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 <errno.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;
+static void print_vlan_info(struct rtattr *tb, int ifindex);
+
+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"
+ " [ mcast_router MULTICAST_ROUTER ]\n"
+ " [ mcast_max_groups MAX_GROUPS ]\n"
+ " [ neigh_suppress {on | off} ]\n"
+ " bridge vlan { show } [ dev DEV ] [ vid VLAN_ID ]\n"
+ " bridge vlan { tunnelshow } [ dev DEV ] [ vid VLAN_ID ]\n"
+ " bridge vlan global { set } vid VLAN_ID dev DEV\n"
+ " [ mcast_snooping MULTICAST_SNOOPING ]\n"
+ " [ mcast_querier MULTICAST_QUERIER ]\n"
+ " [ mcast_igmp_version IGMP_VERSION ]\n"
+ " [ mcast_mld_version MLD_VERSION ]\n"
+ " [ mcast_last_member_count LAST_MEMBER_COUNT ]\n"
+ " [ mcast_last_member_interval LAST_MEMBER_INTERVAL ]\n"
+ " [ mcast_startup_query_count STARTUP_QUERY_COUNT ]\n"
+ " [ mcast_startup_query_interval STARTUP_QUERY_INTERVAL ]\n"
+ " [ mcast_membership_interval MEMBERSHIP_INTERVAL ]\n"
+ " [ mcast_querier_interval QUERIER_INTERVAL ]\n"
+ " [ mcast_query_interval QUERY_INTERVAL ]\n"
+ " [ mcast_query_response_interval QUERY_RESPONSE_INTERVAL ]\n"
+ " bridge vlan global { show } [ 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;
+ char *d = NULL;
+ short vid = -1;
+
+ afspec = addattr_nest(&req.n, sizeof(req), BRIDGE_VLANDB_ENTRY);
+ afspec->rta_type |= NLA_F_NESTED;
+ while (argc > 0) {
+ if (strcmp(*argv, "dev") == 0) {
+ NEXT_ARG();
+ d = *argv;
+ req.bvm.ifindex = ll_name_to_index(d);
+ if (req.bvm.ifindex == 0) {
+ fprintf(stderr,
+ "Cannot find network device \"%s\"\n",
+ d);
+ return -1;
+ }
+ } else if (strcmp(*argv, "vid") == 0) {
+ short vid_end = -1;
+ 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);
+ }
+ if (vid >= 4096) {
+ fprintf(stderr, "Invalid VLAN ID \"%hu\"\n",
+ vid);
+ return -1;
+ }
+
+ 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);
+ } else if (strcmp(*argv, "state") == 0) {
+ char *endptr;
+ int state;
+
+ 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;
+ }
+ addattr8(&req.n, sizeof(req), BRIDGE_VLANDB_ENTRY_STATE,
+ state);
+ } else if (strcmp(*argv, "mcast_router") == 0) {
+ __u8 mcast_router;
+
+ NEXT_ARG();
+ if (get_u8(&mcast_router, *argv, 0))
+ invarg("invalid mcast_router", *argv);
+ addattr8(&req.n, sizeof(req),
+ BRIDGE_VLANDB_ENTRY_MCAST_ROUTER,
+ mcast_router);
+ } else if (strcmp(*argv, "mcast_max_groups") == 0) {
+ __u32 max_groups;
+
+ NEXT_ARG();
+ if (get_u32(&max_groups, *argv, 0))
+ invarg("invalid mcast_max_groups", *argv);
+ addattr32(&req.n, sizeof(req),
+ BRIDGE_VLANDB_ENTRY_MCAST_MAX_GROUPS,
+ max_groups);
+ } else if (strcmp(*argv, "neigh_suppress") == 0) {
+ bool neigh_suppress;
+ int ret;
+
+ NEXT_ARG();
+ neigh_suppress = parse_on_off("neigh_suppress", *argv,
+ &ret);
+ if (ret)
+ return ret;
+ addattr8(&req.n, sizeof(req),
+ BRIDGE_VLANDB_ENTRY_NEIGH_SUPPRESS,
+ neigh_suppress);
+ } else {
+ if (matches(*argv, "help") == 0)
+ NEXT_ARG();
+ }
+ argc--; argv++;
+ }
+ addattr_nest_end(&req.n, afspec);
+
+ if (d == NULL || vid == -1) {
+ fprintf(stderr, "Device and VLAN ID are required arguments.\n");
+ return -1;
+ }
+
+ if (rtnl_talk(&rth, &req.n, NULL) < 0)
+ return -1;
+
+ return 0;
+}
+
+static int vlan_global_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 rtattr *afspec;
+ short vid_end = -1;
+ char *d = NULL;
+ short vid = -1;
+ __u64 val64;
+ __u32 val32;
+ __u8 val8;
+
+ afspec = addattr_nest(&req.n, sizeof(req),
+ BRIDGE_VLANDB_GLOBAL_OPTIONS);
+ afspec->rta_type |= NLA_F_NESTED;
+ while (argc > 0) {
+ if (strcmp(*argv, "dev") == 0) {
+ NEXT_ARG();
+ d = *argv;
+ req.bvm.ifindex = ll_name_to_index(d);
+ if (req.bvm.ifindex == 0) {
+ fprintf(stderr, "Cannot find network device \"%s\"\n",
+ d);
+ return -1;
+ }
+ } 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);
+ }
+ if (vid >= 4096) {
+ fprintf(stderr, "Invalid VLAN ID \"%hu\"\n",
+ vid);
+ return -1;
+ }
+ addattr16(&req.n, sizeof(req), BRIDGE_VLANDB_GOPTS_ID,
+ vid);
+ if (vid_end != -1)
+ addattr16(&req.n, sizeof(req),
+ BRIDGE_VLANDB_GOPTS_RANGE, vid_end);
+ } else if (strcmp(*argv, "mcast_snooping") == 0) {
+ NEXT_ARG();
+ if (get_u8(&val8, *argv, 0))
+ invarg("invalid mcast_snooping", *argv);
+ addattr8(&req.n, 1024,
+ BRIDGE_VLANDB_GOPTS_MCAST_SNOOPING, val8);
+ } else if (strcmp(*argv, "mcast_querier") == 0) {
+ NEXT_ARG();
+ if (get_u8(&val8, *argv, 0))
+ invarg("invalid mcast_querier", *argv);
+ addattr8(&req.n, 1024,
+ BRIDGE_VLANDB_GOPTS_MCAST_QUERIER, val8);
+ } else if (strcmp(*argv, "mcast_igmp_version") == 0) {
+ NEXT_ARG();
+ if (get_u8(&val8, *argv, 0))
+ invarg("invalid mcast_igmp_version", *argv);
+ addattr8(&req.n, 1024,
+ BRIDGE_VLANDB_GOPTS_MCAST_IGMP_VERSION, val8);
+ } else if (strcmp(*argv, "mcast_mld_version") == 0) {
+ NEXT_ARG();
+ if (get_u8(&val8, *argv, 0))
+ invarg("invalid mcast_mld_version", *argv);
+ addattr8(&req.n, 1024,
+ BRIDGE_VLANDB_GOPTS_MCAST_MLD_VERSION, val8);
+ } else if (strcmp(*argv, "mcast_last_member_count") == 0) {
+ NEXT_ARG();
+ if (get_u32(&val32, *argv, 0))
+ invarg("invalid mcast_last_member_count", *argv);
+ addattr32(&req.n, 1024,
+ BRIDGE_VLANDB_GOPTS_MCAST_LAST_MEMBER_CNT,
+ val32);
+ } else if (strcmp(*argv, "mcast_startup_query_count") == 0) {
+ NEXT_ARG();
+ if (get_u32(&val32, *argv, 0))
+ invarg("invalid mcast_startup_query_count",
+ *argv);
+ addattr32(&req.n, 1024,
+ BRIDGE_VLANDB_GOPTS_MCAST_STARTUP_QUERY_CNT,
+ val32);
+ } else if (strcmp(*argv, "mcast_last_member_interval") == 0) {
+ NEXT_ARG();
+ if (get_u64(&val64, *argv, 0))
+ invarg("invalid mcast_last_member_interval",
+ *argv);
+ addattr64(&req.n, 1024,
+ BRIDGE_VLANDB_GOPTS_MCAST_LAST_MEMBER_INTVL,
+ val64);
+ } else if (strcmp(*argv, "mcast_membership_interval") == 0) {
+ NEXT_ARG();
+ if (get_u64(&val64, *argv, 0))
+ invarg("invalid mcast_membership_interval",
+ *argv);
+ addattr64(&req.n, 1024,
+ BRIDGE_VLANDB_GOPTS_MCAST_MEMBERSHIP_INTVL,
+ val64);
+ } else if (strcmp(*argv, "mcast_querier_interval") == 0) {
+ NEXT_ARG();
+ if (get_u64(&val64, *argv, 0))
+ invarg("invalid mcast_querier_interval",
+ *argv);
+ addattr64(&req.n, 1024,
+ BRIDGE_VLANDB_GOPTS_MCAST_QUERIER_INTVL,
+ val64);
+ } else if (strcmp(*argv, "mcast_query_interval") == 0) {
+ NEXT_ARG();
+ if (get_u64(&val64, *argv, 0))
+ invarg("invalid mcast_query_interval",
+ *argv);
+ addattr64(&req.n, 1024,
+ BRIDGE_VLANDB_GOPTS_MCAST_QUERY_INTVL,
+ val64);
+ } else if (strcmp(*argv, "mcast_query_response_interval") == 0) {
+ NEXT_ARG();
+ if (get_u64(&val64, *argv, 0))
+ invarg("invalid mcast_query_response_interval",
+ *argv);
+ addattr64(&req.n, 1024,
+ BRIDGE_VLANDB_GOPTS_MCAST_QUERY_RESPONSE_INTVL,
+ val64);
+ } else if (strcmp(*argv, "mcast_startup_query_interval") == 0) {
+ NEXT_ARG();
+ if (get_u64(&val64, *argv, 0))
+ invarg("invalid mcast_startup_query_interval",
+ *argv);
+ addattr64(&req.n, 1024,
+ BRIDGE_VLANDB_GOPTS_MCAST_STARTUP_QUERY_INTVL,
+ val64);
+ } else {
+ if (strcmp(*argv, "help") == 0)
+ NEXT_ARG();
+ }
+ argc--; argv++;
+ }
+ addattr_nest_end(&req.n, afspec);
+
+ if (d == NULL || vid == -1) {
+ fprintf(stderr, "Device and VLAN ID are required arguments.\n");
+ return -1;
+ }
+
+ 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 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 (!is_json_context())
+ printf("%-*s ", VLAN_ID_LEN - 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;
+}
+
+static void print_vlan_router_ports(struct rtattr *rattr)
+{
+ int rem = RTA_PAYLOAD(rattr);
+ struct rtattr *i;
+
+ print_string(PRINT_FP, NULL, "%-" __stringify(IFNAMSIZ) "s ", "");
+ open_json_array(PRINT_ANY, is_json_context() ? "router_ports" :
+ "router ports: ");
+ for (i = RTA_DATA(rattr); 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);
+
+ open_json_object(NULL);
+ if (show_stats && i != RTA_DATA(rattr)) {
+ print_nl();
+ /* start: IFNAMSIZ + 4 + strlen("router ports: ") */
+ print_string(PRINT_FP, NULL,
+ "%-" __stringify(IFNAMSIZ) "s "
+ " ",
+ "");
+ }
+ print_string(PRINT_ANY, "port", "%s ", port_ifname);
+ if (show_stats)
+ br_print_router_port_stats(i);
+ close_json_object();
+ }
+ close_json_array(PRINT_JSON, NULL);
+ print_nl();
+}
+
+static void print_vlan_global_opts(struct rtattr *a, int ifindex)
+{
+ struct rtattr *vtb[BRIDGE_VLANDB_GOPTS_MAX + 1], *vattr;
+ __u16 vid, vrange = 0;
+
+ if (rta_type(a) != BRIDGE_VLANDB_GLOBAL_OPTIONS)
+ return;
+
+ parse_rtattr_flags(vtb, BRIDGE_VLANDB_GOPTS_MAX, RTA_DATA(a),
+ RTA_PAYLOAD(a), NLA_F_NESTED);
+ vid = rta_getattr_u16(vtb[BRIDGE_VLANDB_GOPTS_ID]);
+ if (vtb[BRIDGE_VLANDB_GOPTS_RANGE])
+ vrange = rta_getattr_u16(vtb[BRIDGE_VLANDB_GOPTS_RANGE]);
+ else
+ vrange = vid;
+
+ if (filter_vlan && (filter_vlan < vid || filter_vlan > vrange))
+ return;
+
+ if (vlan_rtm_cur_ifidx != ifindex) {
+ open_vlan_port(ifindex, VLAN_SHOW_VLAN);
+ open_json_object(NULL);
+ vlan_rtm_cur_ifidx = ifindex;
+ } else {
+ open_json_object(NULL);
+ print_string(PRINT_FP, NULL, "%-" __stringify(IFNAMSIZ) "s ", "");
+ }
+ print_range("vlan", vid, vrange);
+ print_nl();
+ print_string(PRINT_FP, NULL, "%-" __stringify(IFNAMSIZ) "s ", "");
+ if (vtb[BRIDGE_VLANDB_GOPTS_MCAST_SNOOPING]) {
+ vattr = vtb[BRIDGE_VLANDB_GOPTS_MCAST_SNOOPING];
+ print_uint(PRINT_ANY, "mcast_snooping", "mcast_snooping %u ",
+ rta_getattr_u8(vattr));
+ }
+ if (vtb[BRIDGE_VLANDB_GOPTS_MCAST_QUERIER]) {
+ vattr = vtb[BRIDGE_VLANDB_GOPTS_MCAST_QUERIER];
+ print_uint(PRINT_ANY, "mcast_querier", "mcast_querier %u ",
+ rta_getattr_u8(vattr));
+ }
+ if (vtb[BRIDGE_VLANDB_GOPTS_MCAST_IGMP_VERSION]) {
+ vattr = vtb[BRIDGE_VLANDB_GOPTS_MCAST_IGMP_VERSION];
+ print_uint(PRINT_ANY, "mcast_igmp_version",
+ "mcast_igmp_version %u ", rta_getattr_u8(vattr));
+ }
+ if (vtb[BRIDGE_VLANDB_GOPTS_MCAST_MLD_VERSION]) {
+ vattr = vtb[BRIDGE_VLANDB_GOPTS_MCAST_MLD_VERSION];
+ print_uint(PRINT_ANY, "mcast_mld_version",
+ "mcast_mld_version %u ", rta_getattr_u8(vattr));
+ }
+ if (vtb[BRIDGE_VLANDB_GOPTS_MCAST_MLD_VERSION]) {
+ vattr = vtb[BRIDGE_VLANDB_GOPTS_MCAST_LAST_MEMBER_CNT];
+ print_uint(PRINT_ANY, "mcast_last_member_count",
+ "mcast_last_member_count %u ",
+ rta_getattr_u32(vattr));
+ }
+ if (vtb[BRIDGE_VLANDB_GOPTS_MCAST_LAST_MEMBER_INTVL]) {
+ vattr = vtb[BRIDGE_VLANDB_GOPTS_MCAST_LAST_MEMBER_INTVL];
+ print_lluint(PRINT_ANY, "mcast_last_member_interval",
+ "mcast_last_member_interval %llu ",
+ rta_getattr_u64(vattr));
+ }
+ if (vtb[BRIDGE_VLANDB_GOPTS_MCAST_STARTUP_QUERY_CNT]) {
+ vattr = vtb[BRIDGE_VLANDB_GOPTS_MCAST_STARTUP_QUERY_CNT];
+ print_uint(PRINT_ANY, "mcast_startup_query_count",
+ "mcast_startup_query_count %u ",
+ rta_getattr_u32(vattr));
+ }
+ if (vtb[BRIDGE_VLANDB_GOPTS_MCAST_STARTUP_QUERY_INTVL]) {
+ vattr = vtb[BRIDGE_VLANDB_GOPTS_MCAST_STARTUP_QUERY_INTVL];
+ print_lluint(PRINT_ANY, "mcast_startup_query_interval",
+ "mcast_startup_query_interval %llu ",
+ rta_getattr_u64(vattr));
+ }
+ if (vtb[BRIDGE_VLANDB_GOPTS_MCAST_MEMBERSHIP_INTVL]) {
+ vattr = vtb[BRIDGE_VLANDB_GOPTS_MCAST_MEMBERSHIP_INTVL];
+ print_lluint(PRINT_ANY, "mcast_membership_interval",
+ "mcast_membership_interval %llu ",
+ rta_getattr_u64(vattr));
+ }
+ if (vtb[BRIDGE_VLANDB_GOPTS_MCAST_QUERIER_INTVL]) {
+ vattr = vtb[BRIDGE_VLANDB_GOPTS_MCAST_QUERIER_INTVL];
+ print_lluint(PRINT_ANY, "mcast_querier_interval",
+ "mcast_querier_interval %llu ",
+ rta_getattr_u64(vattr));
+ }
+ if (vtb[BRIDGE_VLANDB_GOPTS_MCAST_QUERY_INTVL]) {
+ vattr = vtb[BRIDGE_VLANDB_GOPTS_MCAST_QUERY_INTVL];
+ print_lluint(PRINT_ANY, "mcast_query_interval",
+ "mcast_query_interval %llu ",
+ rta_getattr_u64(vattr));
+ }
+ if (vtb[BRIDGE_VLANDB_GOPTS_MCAST_QUERY_RESPONSE_INTVL]) {
+ vattr = vtb[BRIDGE_VLANDB_GOPTS_MCAST_QUERY_RESPONSE_INTVL];
+ print_lluint(PRINT_ANY, "mcast_query_response_interval",
+ "mcast_query_response_interval %llu ",
+ rta_getattr_u64(vattr));
+ }
+ print_nl();
+ if (vtb[BRIDGE_VLANDB_GOPTS_MCAST_ROUTER_PORTS]) {
+ vattr = RTA_DATA(vtb[BRIDGE_VLANDB_GOPTS_MCAST_ROUTER_PORTS]);
+ print_vlan_router_ports(vattr);
+ }
+ close_json_object();
+}
+
+static void print_vlan_opts(struct rtattr *a, int ifindex)
+{
+ struct rtattr *vtb[BRIDGE_VLANDB_ENTRY_MAX + 1], *vattr;
+ struct bridge_vlan_xstats vstats;
+ struct bridge_vlan_info *vinfo;
+ __u16 vrange = 0;
+ __u8 state = 0;
+
+ if (rta_type(a) != BRIDGE_VLANDB_ENTRY)
+ return;
+
+ 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 (filter_vlan && (filter_vlan < vinfo->vid || filter_vlan > vrange))
+ return;
+
+ 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 != ifindex) {
+ open_vlan_port(ifindex, VLAN_SHOW_VLAN);
+ open_json_object(NULL);
+ vlan_rtm_cur_ifidx = 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);
+ if (vtb[BRIDGE_VLANDB_ENTRY_MCAST_ROUTER]) {
+ vattr = vtb[BRIDGE_VLANDB_ENTRY_MCAST_ROUTER];
+ print_uint(PRINT_ANY, "mcast_router", "mcast_router %u ",
+ rta_getattr_u8(vattr));
+ }
+ if (vtb[BRIDGE_VLANDB_ENTRY_MCAST_N_GROUPS]) {
+ vattr = vtb[BRIDGE_VLANDB_ENTRY_MCAST_N_GROUPS];
+ print_uint(PRINT_ANY, "mcast_n_groups", "mcast_n_groups %u ",
+ rta_getattr_u32(vattr));
+ }
+ if (vtb[BRIDGE_VLANDB_ENTRY_MCAST_MAX_GROUPS]) {
+ vattr = vtb[BRIDGE_VLANDB_ENTRY_MCAST_MAX_GROUPS];
+ print_uint(PRINT_ANY, "mcast_max_groups", "mcast_max_groups %u ",
+ rta_getattr_u32(vattr));
+ }
+ if (vtb[BRIDGE_VLANDB_ENTRY_NEIGH_SUPPRESS]) {
+ vattr = vtb[BRIDGE_VLANDB_ENTRY_NEIGH_SUPPRESS];
+ print_on_off(PRINT_ANY, "neigh_suppress", "neigh_suppress %s ",
+ rta_getattr_u8(vattr));
+ }
+ print_nl();
+ if (show_stats)
+ __print_one_vlan_stats(&vstats);
+ close_json_object();
+}
+
+int print_vlan_rtm(struct nlmsghdr *n, void *arg, bool monitor, bool global_only)
+{
+ struct br_vlan_msg *bvm = NLMSG_DATA(n);
+ int len = n->nlmsg_len;
+ struct rtattr *a;
+ FILE *fp = arg;
+ 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;
+
+ print_headers(fp, "[VLAN]");
+
+ 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)) {
+ unsigned short attr_type = rta_type(a);
+
+ /* skip unknown attributes */
+ if (attr_type > BRIDGE_VLANDB_MAX ||
+ (global_only && attr_type != BRIDGE_VLANDB_GLOBAL_OPTIONS))
+ continue;
+
+ switch (attr_type) {
+ case BRIDGE_VLANDB_ENTRY:
+ print_vlan_opts(a, bvm->ifindex);
+ break;
+ case BRIDGE_VLANDB_GLOBAL_OPTIONS:
+ print_vlan_global_opts(a, bvm->ifindex);
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static int print_vlan_rtm_filter(struct nlmsghdr *n, void *arg)
+{
+ return print_vlan_rtm(n, arg, false, false);
+}
+
+static int print_vlan_rtm_global_filter(struct nlmsghdr *n, void *arg)
+{
+ return print_vlan_rtm(n, arg, false, true);
+}
+
+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,
+ NULL, NULL) < 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,
+ NULL, NULL) < 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;
+}
+
+static int vlan_global_show(int argc, char **argv)
+{
+ __u32 dump_flags = BRIDGE_VLANDB_DUMPF_GLOBAL;
+ int ret = 0, subject = VLAN_SHOW_VLAN;
+ 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);
+
+ 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_global_filter, &subject);
+ if (ret < 0) {
+ fprintf(stderr, "Dump terminated\n");
+ exit(1);
+ }
+
+ if (vlan_rtm_cur_ifidx != -1)
+ close_vlan_port();
+
+ delete_json_obj();
+ fflush(stdout);
+ return 0;
+}
+
+static 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();
+}
+
+static int vlan_global(int argc, char **argv)
+{
+ if (argc > 0) {
+ if (strcmp(*argv, "show") == 0 ||
+ strcmp(*argv, "lst") == 0 ||
+ strcmp(*argv, "list") == 0)
+ return vlan_global_show(argc-1, argv+1);
+ else if (strcmp(*argv, "set") == 0)
+ return vlan_global_option_set(argc-1, argv+1);
+ else
+ usage();
+ } else {
+ return vlan_global_show(0, NULL);
+ }
+
+ return 0;
+}
+
+int do_vlan(int argc, char **argv)
+{
+ ll_init_map(&rth);
+ timestamp = 0;
+
+ 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 (strcmp(*argv, "global") == 0)
+ return vlan_global(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/bridge/vni.c b/bridge/vni.c
new file mode 100644
index 0000000..a7abe6d
--- /dev/null
+++ b/bridge/vni.c
@@ -0,0 +1,413 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Command to manage vnifiltering on a vxlan device
+ *
+ * Authors: Roopa Prabhu <roopa@nvidia.com>
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <linux/if_link.h>
+#include <linux/if_bridge.h>
+#include <linux/if_ether.h>
+
+#include "json_print.h"
+#include "libnetlink.h"
+#include "br_common.h"
+#include "utils.h"
+
+static unsigned int filter_index;
+
+/* max len of "<start>-<end>" */
+#define VXLAN_ID_LEN 17
+
+#define __stringify_1(x...) #x
+#define __stringify(x...) __stringify_1(x)
+
+static void usage(void)
+{
+ fprintf(stderr,
+ "Usage: bridge vni { add | del } vni VNI\n"
+ " [ { group | remote } IP_ADDRESS ]\n"
+ " dev DEV\n"
+ " bridge vni { show } [ dev DEV ]\n"
+ "\n"
+ "Where: VNI := 0-16777215\n"
+ );
+ exit(-1);
+}
+
+static int parse_vni_filter(const char *argv, struct nlmsghdr *n, int reqsize,
+ inet_prefix *group)
+{
+ char *vnilist = strdupa(argv);
+ char *vni = strtok(vnilist, ",");
+ int group_type = AF_UNSPEC;
+ struct rtattr *nlvlist_e;
+ char *v;
+
+ if (group && is_addrtype_inet(group))
+ group_type = (group->family == AF_INET) ? VXLAN_VNIFILTER_ENTRY_GROUP :
+ VXLAN_VNIFILTER_ENTRY_GROUP6;
+
+ while (vni != NULL) {
+ __u32 vni_start = 0, vni_end = 0;
+
+ v = strchr(vni, '-');
+ if (v) {
+ *v = '\0';
+ v++;
+ vni_start = atoi(vni);
+ vni_end = atoi(v);
+ } else {
+ vni_start = atoi(vni);
+ }
+ nlvlist_e = addattr_nest(n, reqsize, VXLAN_VNIFILTER_ENTRY |
+ NLA_F_NESTED);
+ addattr32(n, 1024, VXLAN_VNIFILTER_ENTRY_START, vni_start);
+ if (vni_end)
+ addattr32(n, 1024, VXLAN_VNIFILTER_ENTRY_END, vni_end);
+ if (group)
+ addattr_l(n, 1024, group_type, group->data, group->bytelen);
+ addattr_nest_end(n, nlvlist_e);
+ vni = strtok(NULL, ",");
+ }
+
+ return 0;
+}
+
+static int vni_modify(int cmd, int argc, char **argv)
+{
+ struct {
+ struct nlmsghdr n;
+ struct tunnel_msg tmsg;
+ char buf[1024];
+ } req = {
+ .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct tunnel_msg)),
+ .n.nlmsg_flags = NLM_F_REQUEST,
+ .n.nlmsg_type = cmd,
+ .tmsg.family = PF_BRIDGE,
+ };
+ bool daddr_present = false;
+ inet_prefix daddr;
+ char *vni = NULL;
+ char *d = NULL;
+
+ while (argc > 0) {
+ if (strcmp(*argv, "dev") == 0) {
+ NEXT_ARG();
+ d = *argv;
+ } else if (strcmp(*argv, "vni") == 0) {
+ NEXT_ARG();
+ if (vni)
+ duparg("vni", *argv);
+ vni = *argv;
+ } else if (strcmp(*argv, "group") == 0) {
+ NEXT_ARG();
+ if (daddr_present)
+ duparg("destination", *argv);
+ get_addr(&daddr, *argv, AF_UNSPEC);
+ if (!is_addrtype_inet_multi(&daddr))
+ invarg("invalid group address", *argv);
+ daddr_present = true;
+ } else if (strcmp(*argv, "remote") == 0) {
+ NEXT_ARG();
+ if (daddr_present)
+ duparg("destination", *argv);
+ get_addr(&daddr, *argv, AF_UNSPEC);
+ daddr_present = true;
+ } else {
+ if (strcmp(*argv, "help") == 0)
+ usage();
+ }
+ argc--; argv++;
+ }
+
+ if (d == NULL || vni == NULL) {
+ fprintf(stderr, "Device and VNI ID are required arguments.\n");
+ return -1;
+ }
+
+ parse_vni_filter(vni, &req.n, sizeof(req),
+ (daddr_present ? &daddr : NULL));
+
+ req.tmsg.ifindex = ll_name_to_index(d);
+ if (req.tmsg.ifindex == 0) {
+ fprintf(stderr, "Cannot find vxlan device \"%s\"\n", d);
+ return -1;
+ }
+
+ if (rtnl_talk(&rth, &req.n, NULL) < 0)
+ return -1;
+
+ return 0;
+}
+
+static void open_vni_port(int ifi_index)
+{
+ 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, "vnis");
+}
+
+static void close_vni_port(void)
+{
+ close_json_array(PRINT_JSON, NULL);
+ close_json_object();
+}
+
+static void print_vnifilter_entry_stats(struct rtattr *stats_attr)
+{
+ struct rtattr *stb[VNIFILTER_ENTRY_STATS_MAX+1];
+ __u64 stat;
+
+ open_json_object("stats");
+ parse_rtattr_flags(stb, VNIFILTER_ENTRY_STATS_MAX, RTA_DATA(stats_attr),
+ RTA_PAYLOAD(stats_attr), NLA_F_NESTED);
+
+ print_nl();
+ print_string(PRINT_FP, NULL, "%-" __stringify(IFNAMSIZ) "s RX: ",
+ "");
+
+ if (stb[VNIFILTER_ENTRY_STATS_RX_BYTES]) {
+ stat = rta_getattr_u64(stb[VNIFILTER_ENTRY_STATS_RX_BYTES]);
+ print_lluint(PRINT_ANY, "rx_bytes", "bytes %llu ", stat);
+ }
+ if (stb[VNIFILTER_ENTRY_STATS_RX_PKTS]) {
+ stat = rta_getattr_u64(stb[VNIFILTER_ENTRY_STATS_RX_PKTS]);
+ print_lluint(PRINT_ANY, "rx_pkts", "pkts %llu ", stat);
+ }
+ if (stb[VNIFILTER_ENTRY_STATS_RX_DROPS]) {
+ stat = rta_getattr_u64(stb[VNIFILTER_ENTRY_STATS_RX_DROPS]);
+ print_lluint(PRINT_ANY, "rx_drops", "drops %llu ", stat);
+ }
+ if (stb[VNIFILTER_ENTRY_STATS_RX_ERRORS]) {
+ stat = rta_getattr_u64(stb[VNIFILTER_ENTRY_STATS_RX_ERRORS]);
+ print_lluint(PRINT_ANY, "rx_errors", "errors %llu ", stat);
+ }
+
+ print_nl();
+ print_string(PRINT_FP, NULL, "%-" __stringify(IFNAMSIZ) "s TX: ",
+ "");
+
+ if (stb[VNIFILTER_ENTRY_STATS_TX_BYTES]) {
+ stat = rta_getattr_u64(stb[VNIFILTER_ENTRY_STATS_TX_BYTES]);
+ print_lluint(PRINT_ANY, "tx_bytes", "bytes %llu ", stat);
+ }
+ if (stb[VNIFILTER_ENTRY_STATS_TX_PKTS]) {
+ stat = rta_getattr_u64(stb[VNIFILTER_ENTRY_STATS_TX_PKTS]);
+ print_lluint(PRINT_ANY, "tx_pkts", "pkts %llu ", stat);
+ }
+ if (stb[VNIFILTER_ENTRY_STATS_TX_DROPS]) {
+ stat = rta_getattr_u64(stb[VNIFILTER_ENTRY_STATS_TX_DROPS]);
+ print_lluint(PRINT_ANY, "tx_drops", "drops %llu ", stat);
+ }
+ if (stb[VNIFILTER_ENTRY_STATS_TX_ERRORS]) {
+ stat = rta_getattr_u64(stb[VNIFILTER_ENTRY_STATS_TX_ERRORS]);
+ print_lluint(PRINT_ANY, "tx_errors", "errors %llu ", stat);
+ }
+ close_json_object();
+}
+
+static void print_vni(struct rtattr *t, int ifindex)
+{
+ struct rtattr *ttb[VXLAN_VNIFILTER_ENTRY_MAX+1];
+ __u32 vni_start = 0;
+ unsigned int width;
+ __u32 vni_end;
+
+ parse_rtattr_flags(ttb, VXLAN_VNIFILTER_ENTRY_MAX, RTA_DATA(t),
+ RTA_PAYLOAD(t), NLA_F_NESTED);
+
+ if (ttb[VXLAN_VNIFILTER_ENTRY_START])
+ vni_start = rta_getattr_u32(ttb[VXLAN_VNIFILTER_ENTRY_START]);
+
+ if (ttb[VXLAN_VNIFILTER_ENTRY_END])
+ vni_end = rta_getattr_u32(ttb[VXLAN_VNIFILTER_ENTRY_END]);
+ else
+ vni_end = vni_start;
+
+ open_json_object(NULL);
+ width = print_range("vni", vni_start, vni_end);
+ if (!is_json_context())
+ printf("%-*s ", VXLAN_ID_LEN - width, "");
+
+ if (ttb[VXLAN_VNIFILTER_ENTRY_GROUP]) {
+ __be32 addr = rta_getattr_u32(ttb[VXLAN_VNIFILTER_ENTRY_GROUP]);
+
+ if (addr) {
+ if (IN_MULTICAST(ntohl(addr)))
+ print_string(PRINT_ANY,
+ "group",
+ "%s",
+ format_host(AF_INET, 4, &addr));
+ else
+ print_string(PRINT_ANY,
+ "remote",
+ "%s",
+ format_host(AF_INET, 4, &addr));
+ }
+ } else if (ttb[VXLAN_VNIFILTER_ENTRY_GROUP6]) {
+ struct in6_addr addr;
+
+ memcpy(&addr, RTA_DATA(ttb[VXLAN_VNIFILTER_ENTRY_GROUP6]), sizeof(struct in6_addr));
+ if (!IN6_IS_ADDR_UNSPECIFIED(&addr)) {
+ if (IN6_IS_ADDR_MULTICAST(&addr))
+ print_string(PRINT_ANY,
+ "group",
+ "%s",
+ format_host(AF_INET6,
+ sizeof(struct in6_addr),
+ &addr));
+ else
+ print_string(PRINT_ANY,
+ "remote",
+ "%s",
+ format_host(AF_INET6,
+ sizeof(struct in6_addr),
+ &addr));
+ }
+ }
+
+ if (ttb[VXLAN_VNIFILTER_ENTRY_STATS])
+ print_vnifilter_entry_stats(ttb[VXLAN_VNIFILTER_ENTRY_STATS]);
+
+ close_json_object();
+ print_nl();
+}
+
+int print_vnifilter_rtm(struct nlmsghdr *n, void *arg)
+{
+ struct tunnel_msg *tmsg = NLMSG_DATA(n);
+ int len = n->nlmsg_len;
+ bool opened = false;
+ struct rtattr *t;
+ FILE *fp = arg;
+ int rem;
+
+ if (n->nlmsg_type != RTM_NEWTUNNEL &&
+ n->nlmsg_type != RTM_DELTUNNEL &&
+ n->nlmsg_type != RTM_GETTUNNEL) {
+ fprintf(stderr, "Unknown vni tunnel rtm msg: %08x %08x %08x\n",
+ n->nlmsg_len, n->nlmsg_type, n->nlmsg_flags);
+ return 0;
+ }
+
+ len -= NLMSG_LENGTH(sizeof(*tmsg));
+ if (len < 0) {
+ fprintf(stderr, "BUG: wrong nlmsg len %d\n", len);
+ return -1;
+ }
+
+ if (tmsg->family != AF_BRIDGE)
+ return 0;
+
+ if (filter_index && filter_index != tmsg->ifindex)
+ return 0;
+
+ print_headers(fp, "[TUNNEL]");
+
+ if (n->nlmsg_type == RTM_DELTUNNEL)
+ print_bool(PRINT_ANY, "deleted", "Deleted ", true);
+
+ rem = len;
+ for (t = TUNNEL_RTA(tmsg); RTA_OK(t, rem); t = RTA_NEXT(t, rem)) {
+ if (rta_type(t) != VXLAN_VNIFILTER_ENTRY)
+ continue;
+
+ if (!opened) {
+ open_vni_port(tmsg->ifindex);
+ opened = true;
+ } else {
+ print_string(PRINT_FP, NULL, "%-" __stringify(IFNAMSIZ) "s ", "");
+ }
+
+ print_vni(t, tmsg->ifindex);
+ }
+
+ if (opened)
+ close_vni_port();
+
+ fflush(stdout);
+ return 0;
+}
+
+static int vni_show(int argc, char **argv)
+{
+ char *filter_dev = NULL;
+ __u8 flags = 0;
+ int ret = 0;
+
+ 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);
+ }
+
+ new_json_obj(json);
+
+ if (show_stats)
+ flags = TUNNEL_MSG_FLAG_STATS;
+
+ if (rtnl_tunneldump_req(&rth, PF_BRIDGE, filter_index, flags) < 0) {
+ perror("Cannot send dump request");
+ exit(1);
+ }
+
+ if (!is_json_context())
+ printf("%-" __stringify(IFNAMSIZ) "s %-"
+ __stringify(VXLAN_ID_LEN) "s group/remote\n", "dev",
+ "vni");
+
+ ret = rtnl_dump_filter(&rth, print_vnifilter_rtm, NULL);
+ if (ret < 0) {
+ fprintf(stderr, "Dump ternminated\n");
+ exit(1);
+ }
+
+ delete_json_obj();
+ fflush(stdout);
+ return 0;
+}
+
+int do_vni(int argc, char **argv)
+{
+ ll_init_map(&rth);
+ timestamp = 0;
+
+ if (argc > 0) {
+ if (strcmp(*argv, "add") == 0)
+ return vni_modify(RTM_NEWTUNNEL, argc-1, argv+1);
+ if (strcmp(*argv, "delete") == 0 ||
+ strcmp(*argv, "del") == 0)
+ return vni_modify(RTM_DELTUNNEL, argc-1, argv+1);
+ if (strcmp(*argv, "show") == 0 ||
+ strcmp(*argv, "lst") == 0 ||
+ strcmp(*argv, "list") == 0)
+ return vni_show(argc-1, argv+1);
+ if (strcmp(*argv, "help") == 0)
+ usage();
+ } else {
+ return vni_show(0, NULL);
+ }
+
+ fprintf(stderr, "Command \"%s\" is unknown, try \"bridge vni help\".\n", *argv);
+ exit(-1);
+}
diff --git a/configure b/configure
new file mode 100755
index 0000000..928048b
--- /dev/null
+++ b/configure
@@ -0,0 +1,620 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+# This is not an autoconf generated configure
+
+INCLUDE="$PWD/include"
+PREFIX="/usr"
+LIBDIR="\${prefix}/lib"
+COLOR="never"
+
+# 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_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_lib_dir()
+{
+ LIBDIR=$(echo $LIBDIR | sed "s|\${prefix}|$PREFIX|")
+
+ echo -n "lib directory: "
+ echo "$LIBDIR"
+ echo "LIBDIR:=$LIBDIR" >> $CONFIG
+}
+
+check_setns()
+{
+ cat >$TMPDIR/setnstest.c <<EOF
+#define _GNU_SOURCE
+#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_name_to_handle_at()
+{
+ cat >$TMPDIR/name_to_handle_at_test.c <<EOF
+#define _GNU_SOURCE
+#include <sys/types.h>
+#include <fcntl.h>
+int main(int argc, char **argv)
+{
+ struct file_handle *fhp;
+ int mount_id, flags, dirfd;
+ char *pathname;
+ name_to_handle_at(dirfd, pathname, fhp, &mount_id, flags);
+ return 0;
+}
+EOF
+ if $CC -I$INCLUDE -o $TMPDIR/name_to_handle_at_test $TMPDIR/name_to_handle_at_test.c >/dev/null 2>&1; then
+ echo "yes"
+ echo "CFLAGS += -DHAVE_HANDLE_AT" >>$CONFIG
+ else
+ echo "no"
+ fi
+ rm -f $TMPDIR/name_to_handle_at_test.c $TMPDIR/name_to_handle_at_test
+}
+
+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_tirpc()
+{
+ if ${PKG_CONFIG} libtirpc --exists; then
+ echo "HAVE_RPC:=y" >>$CONFIG
+ echo "yes"
+
+ echo 'LDLIBS +=' "$(${PKG_CONFIG} --libs libtirpc)" >>$CONFIG
+ echo 'CFLAGS += -DHAVE_RPC' "$(${PKG_CONFIG} --cflags libtirpc)" >>$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
+#define _GNU_SOURCE
+#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
+}
+
+check_color()
+{
+ case "$COLOR" in
+ never)
+ echo 'CONF_COLOR:=COLOR_OPT_NEVER' >> $CONFIG
+ echo 'never'
+ ;;
+ auto)
+ echo 'CONF_COLOR:=COLOR_OPT_AUTO' >> $CONFIG
+ echo 'auto'
+ ;;
+ always)
+ echo 'CONF_COLOR:=COLOR_OPT_ALWAYS' >> $CONFIG
+ echo 'always'
+ ;;
+ esac
+}
+
+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
+}
+
+usage()
+{
+ cat <<EOF
+Usage: $0 [OPTIONS]
+ --color <never|auto|always> Default color output configuration. Available options:
+ never: color output is disabled (default)
+ auto: color output is enabled if stdout is a terminal
+ always: color output is enabled regardless of stdout state
+ --include_dir <dir> Path to iproute2 include dir
+ --libdir <dir> Path to iproute2 lib dir
+ --libbpf_dir <dir> Path to libbpf DESTDIR
+ --libbpf_force <on|off> Enable/disable libbpf by force. Available options:
+ on: require link against libbpf, quit config if no libbpf support
+ off: disable libbpf probing
+ --prefix <dir> Path prefix of the lib files to install
+ -h | --help Show this usage info
+EOF
+ exit $1
+}
+
+# Compat with the old INCLUDE path setting method.
+if [ $# -eq 1 ] && [ "$(echo $1 | cut -c 1)" != '-' ]; then
+ INCLUDE="$1"
+else
+ while [ "$#" -gt 0 ]; do
+ case "$1" in
+ --color)
+ shift
+ COLOR="$1" ;;
+ --color=*)
+ COLOR="${1#*=}" ;;
+ --include_dir)
+ shift
+ INCLUDE="$1" ;;
+ --include_dir=*)
+ INCLUDE="${1#*=}" ;;
+ --libdir)
+ shift
+ LIBDIR="$1" ;;
+ --libdir=*)
+ LIBDIR="${1#*=}" ;;
+ --libbpf_dir)
+ shift
+ LIBBPF_DIR="$1" ;;
+ --libbpf_dir=*)
+ LIBBPF_DIR="${1#*=}" ;;
+ --libbpf_force)
+ shift
+ LIBBPF_FORCE="$1" ;;
+ --libbpf_force=*)
+ LIBBPF_FORCE="${1#*=}" ;;
+ --prefix)
+ shift
+ PREFIX="$1" ;;
+ --prefix=*)
+ PREFIX="${1#*=}" ;;
+ -h | --help)
+ usage 0 ;;
+ --*)
+ ;;
+ *)
+ usage 1 ;;
+ esac
+ [ "$#" -gt 0 ] && shift
+ done
+fi
+
+case "$COLOR" in
+ never) ;;
+ auto) ;;
+ always) ;;
+ *) usage 1 ;;
+esac
+[ -d "$INCLUDE" ] || usage 1
+if [ "${LIBBPF_DIR-unused}" != "unused" ]; then
+ [ -d "$LIBBPF_DIR" ] || usage 1
+fi
+if [ "${LIBBPF_FORCE-unused}" != "unused" ]; then
+ if [ "$LIBBPF_FORCE" != 'on' ] && [ "$LIBBPF_FORCE" != 'off' ]; then
+ usage 1
+ fi
+fi
+[ -z "$PREFIX" ] && usage 1
+[ -z "$LIBDIR" ] && usage 1
+
+echo "# Generated config based on" $INCLUDE >$CONFIG
+quiet_config >> $CONFIG
+
+check_toolchain
+
+echo "TC schedulers"
+
+check_xtables
+if ! grep -q TC_CONFIG_NO_XT $CONFIG; then
+ echo -n " IPT "
+ check_xt
+ check_xt_old
+ check_xt_old_internal_h
+
+ echo -n " IPSET "
+ check_ipset
+fi
+
+echo
+check_lib_dir
+
+echo -n "libc has setns: "
+check_setns
+
+echo -n "libc has name_to_handle_at: "
+check_name_to_handle_at
+
+echo -n "SELinux support: "
+check_selinux
+
+echo -n "libtirpc support: "
+check_tirpc
+
+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 -n "color output: "
+check_color
+
+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..10794c9
--- /dev/null
+++ b/dcb/Makefile
@@ -0,0 +1,27 @@
+# SPDX-License-Identifier: GPL-2.0
+include ../config.mk
+
+DCBOBJ = dcb.o \
+ dcb_app.o \
+ dcb_buffer.o \
+ dcb_dcbx.o \
+ dcb_ets.o \
+ dcb_maxrate.o \
+ dcb_pfc.o \
+ dcb_apptrust.o \
+ dcb_rewr.o
+TARGETS += dcb
+LDLIBS += -lm
+
+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..fe0a0f0
--- /dev/null
+++ b/dcb/dcb.c
@@ -0,0 +1,620 @@
+// 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_OK;
+}
+
+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;
+ int8_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;
+ }
+
+ /* The attribute is formally u8, but actually an i8 containing a
+ * negative errno value.
+ */
+ err = mnl_attr_get_u8(attr);
+ if (err) {
+ errno = -err;
+ return MNL_CB_ERROR;
+ }
+
+ return MNL_CB_OK;
+}
+
+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 | NLM_F_ACK,
+ &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;
+
+ errno = 0;
+ ret = dcb_talk(dcb, nlh, dcb_set_attribute_cb, &resp);
+ if (ret) {
+ if (errno)
+ 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)
+{
+ size_t i;
+
+ for (i = 0; i < size; i++) {
+ print_uint(PRINT_JSON, NULL, NULL, array[i]);
+ print_uint(PRINT_FP, NULL, "%zd:", i);
+ print_uint(PRINT_FP, NULL, "%d ", array[i]);
+ }
+}
+
+void dcb_print_array_u64(const __u64 *array, size_t size)
+{
+ size_t i;
+
+ for (i = 0; i < size; i++) {
+ print_u64(PRINT_JSON, NULL, NULL, array[i]);
+ print_uint(PRINT_FP, NULL, "%zd:", i);
+ print_u64(PRINT_FP, NULL, "%" PRIu64 " ", array[i]);
+ }
+}
+
+void dcb_print_array_on_off(const __u8 *array, size_t size)
+{
+ size_t i;
+
+ for (i = 0; i < size; i++) {
+ print_on_off(PRINT_JSON, NULL, NULL, array[i]);
+ print_uint(PRINT_FP, NULL, "%zd:", i);
+ print_on_off(PRINT_FP, NULL, "%s ", array[i]);
+ }
+}
+
+void dcb_print_array_kw(const __u8 *array, size_t array_size,
+ const char *const kw[], size_t kw_size)
+{
+ size_t i;
+
+ for (i = 0; i < array_size; i++) {
+ const char *str = "???";
+ __u8 emt = array[i];
+
+ if (emt < kw_size && kw[emt])
+ str = kw[emt];
+ print_string(PRINT_JSON, NULL, NULL, str);
+ print_uint(PRINT_FP, NULL, "%zd:", i);
+ print_string(PRINT_FP, NULL, "%s ", str);
+ }
+}
+
+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 | apptrust | buffer | dcbx | ets | maxrate | pfc | rewr }\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 (strcmp(*argv, "apptrust") == 0) {
+ return dcb_cmd_apptrust(dcb, argc - 1, argv + 1);
+ } else if (strcmp(*argv, "rewr") == 0) {
+ return dcb_cmd_rewr(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();
+ ret = EXIT_SUCCESS;
+ goto dcb_free;
+ 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..b2e8e89
--- /dev/null
+++ b/dcb/dcb.h
@@ -0,0 +1,142 @@
+/* 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);
+
+/* dcp_rewr.c */
+
+int dcb_cmd_rewr(struct dcb *dcb, int argc, char **argv);
+
+/* dcb_app.c */
+
+struct dcb_app_table {
+ struct dcb_app *apps;
+ size_t n_apps;
+ int attr;
+};
+
+struct dcb_app_parse_mapping {
+ __u8 selector;
+ struct dcb_app_table *tab;
+ int err;
+};
+
+#define DCB_APP_PCP_MAX 15
+#define DCB_APP_DSCP_MAX 63
+
+int dcb_cmd_app(struct dcb *dcb, int argc, char **argv);
+
+int dcb_app_get(struct dcb *dcb, const char *dev, struct dcb_app_table *tab);
+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 *));
+
+bool dcb_app_is_dscp(const struct dcb_app *app);
+bool dcb_app_is_pcp(const struct dcb_app *app);
+
+int dcb_app_print_pid_dscp(__u16 protocol);
+int dcb_app_print_pid_pcp(__u16 protocol);
+int dcb_app_print_pid_dec(__u16 protocol);
+void dcb_app_print_filtered(const struct dcb_app_table *tab,
+ bool (*filter)(const struct dcb_app *),
+ void (*print_pid_prio)(int (*print_pid)(__u16),
+ const struct dcb_app *),
+ int (*print_pid)(__u16 protocol),
+ const char *json_name,
+ const char *fp_name);
+
+enum ieee_attrs_app dcb_app_attr_type_get(__u8 selector);
+bool dcb_app_attr_type_validate(enum ieee_attrs_app type);
+bool dcb_app_selector_validate(enum ieee_attrs_app type, __u8 selector);
+
+int dcb_app_table_push(struct dcb_app_table *tab, struct dcb_app *app);
+int dcb_app_table_copy(struct dcb_app_table *a,
+ const struct dcb_app_table *b);
+void dcb_app_table_sort(struct dcb_app_table *tab);
+void dcb_app_table_fini(struct dcb_app_table *tab);
+void dcb_app_table_remove_existing(struct dcb_app_table *a,
+ const struct dcb_app_table *b);
+void dcb_app_table_remove_replaced(struct dcb_app_table *a,
+ const struct dcb_app_table *b,
+ bool (*key_eq)(const struct dcb_app *aa,
+ const struct dcb_app *ab));
+
+int dcb_app_parse_pcp(__u32 *key, const char *arg);
+int dcb_app_parse_dscp(__u32 *key, const char *arg);
+
+/* dcb_apptrust.c */
+
+int dcb_cmd_apptrust(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..7040e62
--- /dev/null
+++ b/dcb/dcb_app.c
@@ -0,0 +1,933 @@
+// 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 const char *const pcp_names[DCB_APP_PCP_MAX + 1] = {
+ "0nd", "1nd", "2nd", "3nd", "4nd", "5nd", "6nd", "7nd",
+ "0de", "1de", "2de", "3de", "4de", "5de", "6de", "7de"
+};
+
+static const char *const ieee_attrs_app_names[__DCB_ATTR_IEEE_APP_MAX] = {
+ [DCB_ATTR_IEEE_APP] = "DCB_ATTR_IEEE_APP",
+ [DCB_ATTR_DCB_APP] = "DCB_ATTR_DCB_APP"
+};
+
+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"
+ " [ pcp-prio PCP:PRIO ]\n"
+ "\n"
+ " where PRIO := { 0 .. 7 }\n"
+ " ET := { 0x600 .. 0xffff }\n"
+ " PORT := { 1 .. 65535 }\n"
+ " DSCP := { 0 .. 63 }\n"
+ " PCP := { 0(nd/de) .. 7(nd/de) }\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"
+ " [ pcp-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();
+}
+
+enum ieee_attrs_app dcb_app_attr_type_get(__u8 selector)
+{
+ switch (selector) {
+ case IEEE_8021QAZ_APP_SEL_ETHERTYPE:
+ case IEEE_8021QAZ_APP_SEL_STREAM:
+ case IEEE_8021QAZ_APP_SEL_DGRAM:
+ case IEEE_8021QAZ_APP_SEL_ANY:
+ case IEEE_8021QAZ_APP_SEL_DSCP:
+ return DCB_ATTR_IEEE_APP;
+ case DCB_APP_SEL_PCP:
+ return DCB_ATTR_DCB_APP;
+ default:
+ return DCB_ATTR_IEEE_APP_UNSPEC;
+ }
+}
+
+bool dcb_app_attr_type_validate(enum ieee_attrs_app type)
+{
+ switch (type) {
+ case DCB_ATTR_IEEE_APP:
+ case DCB_ATTR_DCB_APP:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool dcb_app_selector_validate(enum ieee_attrs_app type, __u8 selector)
+{
+ return dcb_app_attr_type_get(selector) == type;
+}
+
+void dcb_app_table_fini(struct dcb_app_table *tab)
+{
+ free(tab->apps);
+}
+
+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;
+}
+
+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 bool dcb_app_pid_eq(const struct dcb_app *aa, const struct dcb_app *ab)
+{
+ return aa->selector == ab->selector &&
+ aa->protocol == ab->protocol;
+}
+
+void dcb_app_table_remove_replaced(struct dcb_app_table *a,
+ const struct dcb_app_table *b,
+ bool (*key_eq)(const struct dcb_app *aa,
+ const struct dcb_app *ab))
+{
+ 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 (key_eq(aa, ab))
+ present = true;
+ else
+ continue;
+
+ if (aa->protocol == ab->protocol &&
+ 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;
+}
+
+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);
+}
+
+void dcb_app_table_sort(struct dcb_app_table *tab)
+{
+ qsort(tab->apps, tab->n_apps, sizeof(*tab->apps), dcb_app_cmp_cb);
+}
+
+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);
+}
+
+int dcb_app_parse_pcp(__u32 *key, const char *arg)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(pcp_names); i++) {
+ if (pcp_names[i] && strcmp(arg, pcp_names[i]) == 0) {
+ *key = i;
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
+
+static int dcb_app_parse_mapping_pcp_prio(__u32 key, char *value, void *data)
+{
+ __u8 prio;
+
+ if (get_u8(&prio, value, 0))
+ return -EINVAL;
+
+ return dcb_parse_mapping("PCP", key, DCB_APP_PCP_MAX,
+ "PRIO", prio, IEEE_8021QAZ_MAX_TCS - 1,
+ dcb_app_parse_mapping_cb, data);
+}
+
+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, DCB_APP_DSCP_MAX,
+ "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;
+}
+
+bool dcb_app_is_dscp(const struct dcb_app *app)
+{
+ return app->selector == IEEE_8021QAZ_APP_SEL_DSCP;
+}
+
+bool dcb_app_is_pcp(const struct dcb_app *app)
+{
+ return app->selector == DCB_APP_SEL_PCP;
+}
+
+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;
+}
+
+int dcb_app_print_pid_dec(__u16 protocol)
+{
+ return print_uint(PRINT_ANY, NULL, "%u", protocol);
+}
+
+static int dcb_app_print_pid_hex(__u16 protocol)
+{
+ return print_uint(PRINT_ANY, NULL, "%x", protocol);
+}
+
+int dcb_app_print_pid_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, "%u", protocol);
+}
+
+int dcb_app_print_pid_pcp(__u16 protocol)
+{
+ /* Print in numerical form, if protocol value is out-of-range */
+ if (protocol > DCB_APP_PCP_MAX)
+ return print_uint(PRINT_ANY, NULL, "%u", protocol);
+
+ return print_string(PRINT_ANY, NULL, "%s", pcp_names[protocol]);
+}
+
+void dcb_app_print_filtered(const struct dcb_app_table *tab,
+ bool (*filter)(const struct dcb_app *),
+ void (*print_pid_prio)(int (*print_pid)(__u16),
+ const struct dcb_app *),
+ int (*print_pid)(__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_pid_prio(print_pid, app);
+ print_string(PRINT_ANY, NULL, "%s", " ");
+ close_json_array(PRINT_JSON, NULL);
+ }
+
+ if (!first) {
+ close_json_array(PRINT_JSON, json_name);
+ print_nl();
+ }
+}
+
+static void dcb_app_print_pid_prio(int (*print_pid)(__u16 protocol),
+ const struct dcb_app *app)
+{
+ print_pid(app->protocol);
+ print_uint(PRINT_ANY, NULL, ":%u", app->priority);
+}
+
+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_pid_prio, dcb_app_print_pid_hex,
+ "ethtype_prio", "ethtype-prio");
+}
+
+static void dcb_app_print_pcp_prio(const struct dcb *dcb,
+ const struct dcb_app_table *tab)
+{
+ dcb_app_print_filtered(tab, dcb_app_is_pcp,
+ dcb_app_print_pid_prio,
+ dcb->numeric ? dcb_app_print_pid_dec :
+ dcb_app_print_pid_pcp,
+ "pcp_prio", "pcp-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_app_print_pid_prio,
+ dcb->numeric ? dcb_app_print_pid_dec :
+ dcb_app_print_pid_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_pid_prio, dcb_app_print_pid_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_pid_prio, dcb_app_print_pid_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_pid_prio, dcb_app_print_pid_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, "%u ", 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);
+ dcb_app_print_pcp_prio(dcb, 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;
+ uint16_t type;
+ int ret;
+
+ type = mnl_attr_get_type(attr);
+
+ if (!dcb_app_attr_type_validate(type)) {
+ fprintf(stderr,
+ "Unknown attribute in DCB_ATTR_IEEE_APP_TABLE: %u\n",
+ type);
+ return MNL_CB_OK;
+ }
+ if (mnl_attr_get_payload_len(attr) < sizeof(struct dcb_app)) {
+ fprintf(stderr,
+ "%s payload expected to have size %zu, not %u\n",
+ ieee_attrs_app_names[type], sizeof(struct dcb_app),
+ mnl_attr_get_payload_len(attr));
+ return MNL_CB_OK;
+ }
+
+ app = mnl_attr_get_payload(attr);
+
+ /* Check that selector is encapsulated in the right attribute */
+ if (!dcb_app_selector_validate(type, app->selector)) {
+ fprintf(stderr, "Wrong selector for type: %s\n",
+ ieee_attrs_app_names[type]);
+ return MNL_CB_OK;
+ }
+
+ ret = dcb_app_table_push(tab, app);
+ if (ret != 0)
+ return MNL_CB_ERROR;
+
+ return MNL_CB_OK;
+}
+
+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, tab->attr, &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;
+ enum ieee_attrs_app type;
+ struct nlattr *nest;
+ size_t i;
+
+ nest = mnl_attr_nest_start(nlh, add_del->tab->attr);
+
+ for (i = 0; i < add_del->tab->n_apps; i++) {
+ const struct dcb_app *app = &add_del->tab->apps[i];
+ type = dcb_app_attr_type_get(app->selector);
+
+ if (add_del->filter == NULL || add_del->filter(app))
+ mnl_attr_put(nlh, type, sizeof(*app), app);
+ }
+
+ mnl_attr_nest_end(nlh, nest);
+ return 0;
+}
+
+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 if (strcmp(*argv, "pcp-prio") == 0) {
+ NEXT_ARG();
+ pm.selector = DCB_APP_SEL_PCP;
+ ret = parse_mapping_gen(&argc, &argv, &dcb_app_parse_pcp,
+ &dcb_app_parse_mapping_pcp_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 = { .attr = DCB_ATTR_IEEE_APP_TABLE };
+ 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 = { .attr = DCB_ATTR_IEEE_APP_TABLE };
+ 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 = { .attr = DCB_ATTR_IEEE_APP_TABLE };
+ 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 if (matches(*argv, "default-prio") == 0) {
+ dcb_app_print_default_prio(&tab);
+ } else if (strcmp(*argv, "pcp-prio") == 0) {
+ dcb_app_print_pcp_prio(dcb, &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 ret;
+}
+
+static int dcb_cmd_app_flush(struct dcb *dcb, const char *dev, int argc, char **argv)
+{
+ struct dcb_app_table tab = { .attr = DCB_ATTR_IEEE_APP_TABLE };
+ 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 if (strcmp(*argv, "pcp-prio") == 0) {
+ ret = dcb_app_add_del(dcb, dev, DCB_CMD_IEEE_DEL, &tab,
+ &dcb_app_is_pcp);
+ 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 = { .attr = DCB_ATTR_IEEE_APP_TABLE };
+ struct dcb_app_table tab = { .attr = DCB_ATTR_IEEE_APP_TABLE };
+ struct dcb_app_table new = { .attr = DCB_ATTR_IEEE_APP_TABLE };
+ 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, dcb_app_pid_eq);
+ 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_apptrust.c b/dcb/dcb_apptrust.c
new file mode 100644
index 0000000..ed1cba7
--- /dev/null
+++ b/dcb/dcb_apptrust.c
@@ -0,0 +1,307 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+#include <errno.h>
+#include <linux/dcbnl.h>
+
+#include "dcb.h"
+#include "utils.h"
+
+static void dcb_apptrust_help_set(void)
+{
+ fprintf(stderr,
+ "Usage: dcb apptrust set dev STRING\n"
+ " [ order [ ethtype | stream-port | dgram-port | port | dscp | pcp ] ]\n"
+ "\n");
+}
+
+static void dcb_apptrust_help_show(void)
+{
+ fprintf(stderr, "Usage: dcb apptrust show dev STRING\n"
+ " [ order ]\n"
+ "\n");
+}
+
+static void dcb_apptrust_help(void)
+{
+ fprintf(stderr, "Usage: dcb apptrust help\n"
+ "\n");
+ dcb_apptrust_help_show();
+ dcb_apptrust_help_set();
+}
+
+static const char *const selector_names[] = {
+ [IEEE_8021QAZ_APP_SEL_ETHERTYPE] = "ethtype",
+ [IEEE_8021QAZ_APP_SEL_STREAM] = "stream-port",
+ [IEEE_8021QAZ_APP_SEL_DGRAM] = "dgram-port",
+ [IEEE_8021QAZ_APP_SEL_ANY] = "port",
+ [IEEE_8021QAZ_APP_SEL_DSCP] = "dscp",
+ [DCB_APP_SEL_PCP] = "pcp",
+};
+
+struct dcb_apptrust_table {
+ __u8 selectors[IEEE_8021QAZ_APP_SEL_MAX + 1];
+ int nselectors;
+};
+
+static bool dcb_apptrust_contains(const struct dcb_apptrust_table *table,
+ __u8 selector)
+{
+ int i;
+
+ for (i = 0; i < table->nselectors; i++)
+ if (table->selectors[i] == selector)
+ return true;
+
+ return false;
+}
+
+static void dcb_apptrust_print_order(const struct dcb_apptrust_table *table)
+{
+ const char *str;
+ __u8 selector;
+ int i;
+
+ open_json_array(PRINT_JSON, "order");
+ print_string(PRINT_FP, NULL, "order: ", NULL);
+
+ for (i = 0; i < table->nselectors; i++) {
+ selector = table->selectors[i];
+ str = selector_names[selector];
+ print_string(PRINT_ANY, NULL, "%s ", str);
+ }
+ print_nl();
+
+ close_json_array(PRINT_JSON, "order");
+}
+
+static void dcb_apptrust_print(const struct dcb_apptrust_table *table)
+{
+ dcb_apptrust_print_order(table);
+ print_nl();
+}
+
+static int dcb_apptrust_get_cb(const struct nlattr *attr, void *data)
+{
+ struct dcb_apptrust_table *table = data;
+ uint16_t type;
+ __u8 selector;
+
+ type = mnl_attr_get_type(attr);
+
+ if (!dcb_app_attr_type_validate(type)) {
+ fprintf(stderr,
+ "Unknown attribute in DCB_ATTR_IEEE_APP_TRUST_TABLE: %d\n",
+ type);
+ return MNL_CB_OK;
+ }
+
+ if (mnl_attr_get_payload_len(attr) < 1) {
+ fprintf(stderr,
+ "DCB_ATTR_IEEE_APP_TRUST payload expected to have size %zd, not %d\n",
+ sizeof(struct dcb_app), mnl_attr_get_payload_len(attr));
+ return MNL_CB_OK;
+ }
+
+ selector = mnl_attr_get_u8(attr);
+
+ /* Check that selector is encapsulated in the right attribute */
+ if (!dcb_app_selector_validate(type, selector)) {
+ fprintf(stderr, "Wrong type for selector: %s\n",
+ selector_names[selector]);
+ return MNL_CB_OK;
+ }
+
+ table->selectors[table->nselectors++] = selector;
+
+ return MNL_CB_OK;
+}
+
+static int dcb_apptrust_get(struct dcb *dcb, const char *dev,
+ struct dcb_apptrust_table *table)
+{
+ uint16_t payload_len;
+ void *payload;
+ int ret;
+
+ ret = dcb_get_attribute_va(dcb, dev, DCB_ATTR_DCB_APP_TRUST_TABLE,
+ &payload, &payload_len);
+ if (ret != 0)
+ return ret;
+
+ ret = mnl_attr_parse_payload(payload, payload_len, dcb_apptrust_get_cb,
+ table);
+ if (ret != MNL_CB_OK)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int dcb_apptrust_set_cb(struct dcb *dcb, struct nlmsghdr *nlh,
+ void *data)
+{
+ const struct dcb_apptrust_table *table = data;
+ enum ieee_attrs_app type;
+ struct nlattr *nest;
+ int i;
+
+ nest = mnl_attr_nest_start(nlh, DCB_ATTR_DCB_APP_TRUST_TABLE);
+
+ for (i = 0; i < table->nselectors; i++) {
+ type = dcb_app_attr_type_get(table->selectors[i]);
+ mnl_attr_put_u8(nlh, type, table->selectors[i]);
+ }
+
+ mnl_attr_nest_end(nlh, nest);
+
+ return 0;
+}
+
+static int dcb_apptrust_set(struct dcb *dcb, const char *dev,
+ const struct dcb_apptrust_table *table)
+{
+ return dcb_set_attribute_va(dcb, DCB_CMD_IEEE_SET, dev,
+ &dcb_apptrust_set_cb, (void *)table);
+}
+
+static __u8 dcb_apptrust_parse_selector(const char *selector, int *err)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(selector_names); i++) {
+ if (selector_names[i] &&
+ strcmp(selector, selector_names[i]) == 0) {
+ *err = 0;
+ return i;
+ }
+ }
+
+ *err = -EINVAL;
+ return 0;
+}
+
+static int dcb_apptrust_parse_selector_list(int *argcp, char ***argvp,
+ struct dcb_apptrust_table *table)
+{
+ int argc = *argcp, err;
+ char **argv = *argvp;
+ __u8 selector;
+
+ /* No trusted selectors ? */
+ if (argc == 0)
+ goto out;
+
+ while (argc > 0) {
+ selector = dcb_apptrust_parse_selector(*argv, &err);
+ if (err < 0)
+ goto out;
+
+ if (table->nselectors > IEEE_8021QAZ_APP_SEL_MAX)
+ return -ERANGE;
+
+ if (dcb_apptrust_contains(table, selector)) {
+ fprintf(stderr, "Duplicate selector: %s\n",
+ selector_names[selector]);
+ return -EINVAL;
+ }
+
+ table->selectors[table->nselectors++] = selector;
+
+ NEXT_ARG_FWD();
+ }
+
+out:
+ *argcp = argc;
+ *argvp = argv;
+
+ return 0;
+}
+
+static int dcb_cmd_apptrust_set(struct dcb *dcb, const char *dev, int argc,
+ char **argv)
+{
+ struct dcb_apptrust_table table = { 0 };
+ int ret;
+
+ if (!argc) {
+ dcb_apptrust_help_set();
+ return 0;
+ }
+
+ do {
+ if (strcmp(*argv, "help") == 0) {
+ dcb_apptrust_help_set();
+ return 0;
+ } else if (strcmp(*argv, "order") == 0) {
+ NEXT_ARG_FWD();
+ ret = dcb_apptrust_parse_selector_list(&argc, &argv,
+ &table);
+ if (ret < 0) {
+ fprintf(stderr, "Invalid list of selectors\n");
+ return -EINVAL;
+ }
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ dcb_apptrust_help_set();
+ return -EINVAL;
+ }
+ } while (argc > 0);
+
+ return dcb_apptrust_set(dcb, dev, &table);
+}
+
+static int dcb_cmd_apptrust_show(struct dcb *dcb, const char *dev, int argc,
+ char **argv)
+{
+ struct dcb_apptrust_table table = { 0 };
+ int ret;
+
+ ret = dcb_apptrust_get(dcb, dev, &table);
+ if (ret)
+ return ret;
+
+ open_json_object(NULL);
+
+ if (!argc) {
+ dcb_apptrust_print(&table);
+ goto out;
+ }
+
+ do {
+ if (strcmp(*argv, "help") == 0) {
+ dcb_apptrust_help_show();
+ return 0;
+ } else if (strcmp(*argv, "order") == 0) {
+ dcb_apptrust_print_order(&table);
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ dcb_apptrust_help_show();
+ return -EINVAL;
+ }
+
+ NEXT_ARG_FWD();
+ } while (argc > 0);
+
+out:
+ close_json_object();
+ return 0;
+}
+
+int dcb_cmd_apptrust(struct dcb *dcb, int argc, char **argv)
+{
+ if (!argc || strcmp(*argv, "help") == 0) {
+ dcb_apptrust_help();
+ return 0;
+ } else if (strcmp(*argv, "show") == 0) {
+ NEXT_ARG_FWD();
+ return dcb_cmd_parse_dev(dcb, argc, argv, dcb_cmd_apptrust_show,
+ dcb_apptrust_help_show);
+ } else if (strcmp(*argv, "set") == 0) {
+ NEXT_ARG_FWD();
+ return dcb_cmd_parse_dev(dcb, argc, argv, dcb_cmd_apptrust_set,
+ dcb_apptrust_help_set);
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ dcb_apptrust_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..9e3dd2a
--- /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 const struct dcb_dcbx_flag dcb_dcbx_flags[] = {
+ {DCB_CAP_DCBX_HOST, "host", NULL },
+ {DCB_CAP_DCBX_LLD_MANAGED, "lld-managed", "lld_managed" },
+ {DCB_CAP_DCBX_VER_CEE, "cee", NULL },
+ {DCB_CAP_DCBX_VER_IEEE, "ieee", NULL },
+ {DCB_CAP_DCBX_STATIC, "static", NULL },
+};
+
+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++) {
+ const 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++) {
+ const 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/dcb/dcb_rewr.c b/dcb/dcb_rewr.c
new file mode 100644
index 0000000..5bb649a
--- /dev/null
+++ b/dcb/dcb_rewr.c
@@ -0,0 +1,363 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+#include <errno.h>
+#include <linux/dcbnl.h>
+#include <stdio.h>
+
+#include "dcb.h"
+#include "utils.h"
+
+static void dcb_rewr_help_add(void)
+{
+ fprintf(stderr,
+ "Usage: dcb rewr { add | del | replace } dev STRING\n"
+ " [ prio-pcp PRIO:PCP ]\n"
+ " [ prio-dscp PRIO:DSCP ]\n"
+ "\n"
+ " where PRIO := { 0 .. 7 }\n"
+ " PCP := { 0(nd/de) .. 7(nd/de) }\n"
+ " DSCP := { 0 .. 63 }\n"
+ "\n"
+ );
+}
+
+static void dcb_rewr_help_show_flush(void)
+{
+ fprintf(stderr,
+ "Usage: dcb rewr { show | flush } dev STRING\n"
+ " [ prio-pcp ]\n"
+ " [ prio-dscp ]\n"
+ "\n"
+ );
+}
+
+static void dcb_rewr_help(void)
+{
+ fprintf(stderr,
+ "Usage: dcb rewr help\n"
+ "\n"
+ );
+ dcb_rewr_help_show_flush();
+ dcb_rewr_help_add();
+}
+
+static void dcb_rewr_parse_mapping_cb(__u32 key, __u64 value, void *data)
+{
+ struct dcb_app_parse_mapping *pm = data;
+ struct dcb_app app = {
+ .selector = pm->selector,
+ .priority = key,
+ .protocol = value,
+ };
+
+ if (pm->err)
+ return;
+
+ pm->err = dcb_app_table_push(pm->tab, &app);
+}
+
+static int dcb_rewr_parse_mapping_prio_pcp(__u32 key, char *value, void *data)
+{
+ __u32 pcp;
+
+ if (dcb_app_parse_pcp(&pcp, value))
+ return -EINVAL;
+
+ return dcb_parse_mapping("PRIO", key, IEEE_8021QAZ_MAX_TCS - 1,
+ "PCP", pcp, DCB_APP_PCP_MAX,
+ dcb_rewr_parse_mapping_cb, data);
+}
+
+static int dcb_rewr_parse_mapping_prio_dscp(__u32 key, char *value, void *data)
+{
+ __u32 dscp;
+
+ if (dcb_app_parse_dscp(&dscp, value))
+ return -EINVAL;
+
+ return dcb_parse_mapping("PRIO", key, IEEE_8021QAZ_MAX_TCS - 1,
+ "DSCP", dscp, DCB_APP_DSCP_MAX,
+ dcb_rewr_parse_mapping_cb, data);
+}
+
+static void dcb_rewr_print_prio_pid(int (*print_pid)(__u16 protocol),
+ const struct dcb_app *app)
+{
+ print_uint(PRINT_ANY, NULL, "%u:", app->priority);
+ print_pid(app->protocol);
+}
+
+static void dcb_rewr_print_prio_pcp(const struct dcb *dcb,
+ const struct dcb_app_table *tab)
+{
+ dcb_app_print_filtered(tab, dcb_app_is_pcp,
+ dcb_rewr_print_prio_pid,
+ dcb->numeric ? dcb_app_print_pid_dec :
+ dcb_app_print_pid_pcp,
+ "prio_pcp", "prio-pcp");
+}
+
+static void dcb_rewr_print_prio_dscp(const struct dcb *dcb,
+ const struct dcb_app_table *tab)
+{
+ dcb_app_print_filtered(tab, dcb_app_is_dscp,
+ dcb_rewr_print_prio_pid,
+ dcb->numeric ? dcb_app_print_pid_dec :
+ dcb_app_print_pid_dscp,
+ "prio_dscp", "prio-dscp");
+}
+
+static void dcb_rewr_print(const struct dcb *dcb,
+ const struct dcb_app_table *tab)
+{
+ dcb_rewr_print_prio_pcp(dcb, tab);
+ dcb_rewr_print_prio_dscp(dcb, tab);
+}
+
+static bool dcb_rewr_prio_eq(const struct dcb_app *aa, const struct dcb_app *ab)
+{
+ return aa->selector == ab->selector &&
+ aa->priority == ab->priority;
+}
+
+static int dcb_cmd_rewr_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_rewr_help_add();
+ return 0;
+ }
+
+ do {
+ if (strcmp(*argv, "help") == 0) {
+ dcb_rewr_help_add();
+ return 0;
+ } else if (strcmp(*argv, "prio-pcp") == 0) {
+ NEXT_ARG();
+ pm.selector = DCB_APP_SEL_PCP;
+ ret = parse_mapping(&argc, &argv, false,
+ &dcb_rewr_parse_mapping_prio_pcp,
+ &pm);
+ } else if (strcmp(*argv, "prio-dscp") == 0) {
+ NEXT_ARG();
+ pm.selector = IEEE_8021QAZ_APP_SEL_DSCP;
+ ret = parse_mapping(&argc, &argv, false,
+ &dcb_rewr_parse_mapping_prio_dscp,
+ &pm);
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ dcb_rewr_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_rewr_add(struct dcb *dcb, const char *dev, int argc,
+ char **argv)
+{
+ struct dcb_app_table tab = { .attr = DCB_ATTR_DCB_REWR_TABLE };
+ int ret;
+
+ ret = dcb_cmd_rewr_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_rewr_del(struct dcb *dcb, const char *dev, int argc,
+ char **argv)
+{
+ struct dcb_app_table tab = { .attr = DCB_ATTR_DCB_REWR_TABLE };
+ int ret;
+
+ ret = dcb_cmd_rewr_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_rewr_replace(struct dcb *dcb, const char *dev, int argc,
+ char **argv)
+{
+ struct dcb_app_table orig = { .attr = DCB_ATTR_DCB_REWR_TABLE };
+ struct dcb_app_table tab = { .attr = DCB_ATTR_DCB_REWR_TABLE };
+ struct dcb_app_table new = { .attr = DCB_ATTR_DCB_REWR_TABLE };
+ int ret;
+
+ ret = dcb_app_get(dcb, dev, &orig);
+ if (ret != 0)
+ return ret;
+
+ ret = dcb_cmd_rewr_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 rewrite entries\n");
+ goto out;
+ }
+
+ /* Remove the obsolete entries. */
+ dcb_app_table_remove_replaced(&orig, &tab, dcb_rewr_prio_eq);
+ ret = dcb_app_add_del(dcb, dev, DCB_CMD_IEEE_DEL, &orig, NULL);
+ if (ret != 0) {
+ fprintf(stderr, "Could not remove replaced rewrite entries\n");
+ goto out;
+ }
+
+out:
+ dcb_app_table_fini(&new);
+ dcb_app_table_fini(&tab);
+ dcb_app_table_fini(&orig);
+ return 0;
+}
+
+static int dcb_cmd_rewr_show(struct dcb *dcb, const char *dev, int argc,
+ char **argv)
+{
+ struct dcb_app_table tab = { .attr = DCB_ATTR_DCB_REWR_TABLE };
+ 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_rewr_print(dcb, &tab);
+ goto out;
+ }
+
+ do {
+ if (strcmp(*argv, "help") == 0) {
+ dcb_rewr_help_show_flush();
+ goto out;
+ } else if (strcmp(*argv, "prio-pcp") == 0) {
+ dcb_rewr_print_prio_pcp(dcb, &tab);
+ } else if (strcmp(*argv, "prio-dscp") == 0) {
+ dcb_rewr_print_prio_dscp(dcb, &tab);
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ dcb_rewr_help_show_flush();
+ ret = -EINVAL;
+ goto out;
+ }
+
+ NEXT_ARG_FWD();
+ } while (argc > 0);
+
+out:
+ close_json_object();
+ dcb_app_table_fini(&tab);
+ return ret;
+}
+
+static int dcb_cmd_rewr_flush(struct dcb *dcb, const char *dev, int argc,
+ char **argv)
+{
+ struct dcb_app_table tab = { .attr = DCB_ATTR_DCB_REWR_TABLE };
+ 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 (strcmp(*argv, "help") == 0) {
+ dcb_rewr_help_show_flush();
+ goto out;
+ } else if (strcmp(*argv, "prio-pcp") == 0) {
+ ret = dcb_app_add_del(dcb, dev, DCB_CMD_IEEE_DEL, &tab,
+ &dcb_app_is_pcp);
+ if (ret != 0)
+ goto out;
+ } else if (strcmp(*argv, "prio-dscp") == 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_rewr_help_show_flush();
+ ret = -EINVAL;
+ goto out;
+ }
+
+ NEXT_ARG_FWD();
+ } while (argc > 0);
+
+out:
+ dcb_app_table_fini(&tab);
+ return ret;
+}
+
+int dcb_cmd_rewr(struct dcb *dcb, int argc, char **argv)
+{
+ if (!argc || strcmp(*argv, "help") == 0) {
+ dcb_rewr_help();
+ return 0;
+ } else if (strcmp(*argv, "show") == 0) {
+ NEXT_ARG_FWD();
+ return dcb_cmd_parse_dev(dcb, argc, argv, dcb_cmd_rewr_show,
+ dcb_rewr_help_show_flush);
+ } else if (strcmp(*argv, "flush") == 0) {
+ NEXT_ARG_FWD();
+ return dcb_cmd_parse_dev(dcb, argc, argv, dcb_cmd_rewr_flush,
+ dcb_rewr_help_show_flush);
+ } else if (strcmp(*argv, "add") == 0) {
+ NEXT_ARG_FWD();
+ return dcb_cmd_parse_dev(dcb, argc, argv, dcb_cmd_rewr_add,
+ dcb_rewr_help_add);
+ } else if (strcmp(*argv, "del") == 0) {
+ NEXT_ARG_FWD();
+ return dcb_cmd_parse_dev(dcb, argc, argv, dcb_cmd_rewr_del,
+ dcb_rewr_help_add);
+ } else if (strcmp(*argv, "replace") == 0) {
+ NEXT_ARG_FWD();
+ return dcb_cmd_parse_dev(dcb, argc, argv, dcb_cmd_rewr_replace,
+ dcb_rewr_help_add);
+ } else {
+ fprintf(stderr, "What is \"%s\"?\n", *argv);
+ dcb_rewr_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..1a1eed7
--- /dev/null
+++ b/devlink/Makefile
@@ -0,0 +1,19 @@
+# SPDX-License-Identifier: GPL-2.0
+include ../config.mk
+
+DEVLINKOBJ = devlink.o mnlg.o
+TARGETS += devlink
+LDLIBS += -lm
+
+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..dbeb6e3
--- /dev/null
+++ b/devlink/devlink.c
@@ -0,0 +1,10206 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * devlink.c Devlink tool
+ *
+ * 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 <linux/net_namespace.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"
+#include "libnetlink.h"
+#include "../ip/ip_common.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;
+static bool g_err_suspended;
+
+#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;
+
+ if (g_err_suspended)
+ return;
+ 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 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);
+}
+
+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) {
+ ifname_map_free(ifname_map);
+ return NULL;
+ }
+ return ifname_map;
+}
+
+static int ifname_map_update(struct ifname_map *ifname_map, const char *ifname)
+{
+ char *new_ifname;
+
+ new_ifname = strdup(ifname);
+ if (!new_ifname)
+ return -ENOMEM;
+ free(ifname_map->ifname);
+ ifname_map->ifname = new_ifname;
+ return 0;
+}
+
+#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)
+#define DL_OPT_PORT_CONTROLLER BIT(46)
+#define DL_OPT_PORT_FN_RATE_TYPE BIT(47)
+#define DL_OPT_PORT_FN_RATE_TX_SHARE BIT(48)
+#define DL_OPT_PORT_FN_RATE_TX_MAX BIT(49)
+#define DL_OPT_PORT_FN_RATE_NODE_NAME BIT(50)
+#define DL_OPT_PORT_FN_RATE_PARENT BIT(51)
+#define DL_OPT_LINECARD BIT(52)
+#define DL_OPT_LINECARD_TYPE BIT(53)
+#define DL_OPT_SELFTESTS BIT(54)
+#define DL_OPT_PORT_FN_RATE_TX_PRIORITY BIT(55)
+#define DL_OPT_PORT_FN_RATE_TX_WEIGHT BIT(56)
+#define DL_OPT_PORT_FN_CAPS BIT(57)
+
+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;
+ __u64 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;
+ __u64 region_address;
+ __u64 region_length;
+ const char *flash_file_name;
+ const char *flash_component;
+ const char *reporter_name;
+ __u64 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;
+ __u64 trap_policer_rate;
+ __u64 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_controller;
+ uint32_t port_sfnumber;
+ uint16_t port_flavour;
+ uint16_t port_pfnumber;
+ uint8_t port_fn_state;
+ uint16_t rate_type;
+ uint64_t rate_tx_share;
+ uint64_t rate_tx_max;
+ uint32_t rate_tx_priority;
+ uint32_t rate_tx_weight;
+ char *rate_node_name;
+ const char *rate_parent_node;
+ uint32_t linecard_index;
+ const char *linecard_type;
+ bool selftests_opt[DEVLINK_ATTR_SELFTEST_ID_MAX + 1];
+ struct nla_bitfield32 port_fn_caps;
+};
+
+struct dl {
+ struct mnlu_gen_socket nlg;
+ struct list_head ifname_map_list;
+ int argc;
+ char **argv;
+ char *handle_argv;
+ bool no_nice_names;
+ struct dl_opts opts;
+ bool json_output;
+ bool pretty_output;
+ bool verbose;
+ bool stats;
+ bool hex;
+ bool use_iec;
+ bool map_loaded;
+ 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,
+ [DEVLINK_ATTR_LINECARD_INDEX] = MNL_TYPE_U32,
+ [DEVLINK_ATTR_LINECARD_STATE] = MNL_TYPE_U8,
+ [DEVLINK_ATTR_LINECARD_TYPE] = MNL_TYPE_STRING,
+ [DEVLINK_ATTR_LINECARD_SUPPORTED_TYPES] = MNL_TYPE_NESTED,
+ [DEVLINK_ATTR_NESTED_DEVLINK] = MNL_TYPE_NESTED,
+ [DEVLINK_ATTR_SELFTESTS] = MNL_TYPE_NESTED,
+ [DEVLINK_ATTR_NETNS_ID] = MNL_TYPE_U32,
+};
+
+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,
+ [DEVLINK_PORT_FN_ATTR_DEVLINK] = MNL_TYPE_NESTED,
+};
+
+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_add(struct dl *dl, const char *ifname,
+ const char *bus_name, const char *dev_name,
+ uint32_t port_index)
+{
+ struct ifname_map *ifname_map;
+
+ ifname_map = ifname_map_alloc(bus_name, dev_name, port_index, ifname);
+ if (!ifname_map)
+ return -ENOMEM;
+ list_add(&ifname_map->list, &dl->ifname_map_list);
+ return 0;
+}
+
+static void ifname_map_del(struct ifname_map *ifname_map)
+{
+ list_del(&ifname_map->list);
+ ifname_map_free(ifname_map);
+}
+
+static int ifname_map_rtnl_port_parse(struct dl *dl, const char *ifname,
+ struct rtattr *nest)
+{
+ struct rtattr *tb[DEVLINK_ATTR_MAX + 1];
+ const char *bus_name;
+ const char *dev_name;
+ uint32_t port_index;
+
+ parse_rtattr_nested(tb, DEVLINK_ATTR_MAX, nest);
+ if (!tb[DEVLINK_ATTR_BUS_NAME] || !tb[DEVLINK_ATTR_DEV_NAME] ||
+ !tb[DEVLINK_ATTR_PORT_INDEX])
+ return -ENOENT;
+
+ bus_name = rta_getattr_str(tb[DEVLINK_ATTR_BUS_NAME]);
+ dev_name = rta_getattr_str(tb[DEVLINK_ATTR_DEV_NAME]);
+ port_index = rta_getattr_u32(tb[DEVLINK_ATTR_PORT_INDEX]);
+ return ifname_map_add(dl, ifname, bus_name, dev_name, port_index);
+}
+
+static int ifname_map_rtnl_init(struct dl *dl, const char *ifname)
+{
+ struct iplink_req req = {
+ .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)),
+ .n.nlmsg_flags = NLM_F_REQUEST,
+ .n.nlmsg_type = RTM_GETLINK,
+ .i.ifi_family = AF_UNSPEC,
+ };
+ struct rtattr *tb[IFLA_MAX + 1];
+ struct rtnl_handle rth;
+ struct ifinfomsg *ifi;
+ struct nlmsghdr *n;
+ int len;
+ int err;
+
+ if (rtnl_open(&rth, 0) < 0) {
+ pr_err("Cannot open rtnetlink\n");
+ return -EINVAL;
+ }
+
+ addattr_l(&req.n, sizeof(req),
+ !check_ifname(ifname) ? IFLA_IFNAME : IFLA_ALT_IFNAME,
+ ifname, strlen(ifname) + 1);
+
+ if (rtnl_talk(&rth, &req.n, &n) < 0) {
+ err = -EINVAL;
+ goto out;
+ }
+
+ if (n->nlmsg_type != RTM_NEWLINK) {
+ err = -EINVAL;
+ goto out;
+ }
+
+ ifi = NLMSG_DATA(n);
+ len = n->nlmsg_len;
+
+ len -= NLMSG_LENGTH(sizeof(*ifi));
+ if (len < 0) {
+ err = -EINVAL;
+ goto out;
+ }
+
+ parse_rtattr_flags(tb, IFLA_MAX, IFLA_RTA(ifi), len, NLA_F_NESTED);
+ if (!tb[IFLA_DEVLINK_PORT]) {
+ err = -EOPNOTSUPP;
+ goto out;
+ }
+
+ err = ifname_map_rtnl_port_parse(dl, ifname, tb[IFLA_DEVLINK_PORT]);
+
+out:
+ rtnl_close(&rth);
+ return err;
+}
+
+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;
+ const char *bus_name;
+ const char *dev_name;
+ uint32_t port_index;
+ const char *port_ifname;
+ 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])
+ 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_index = mnl_attr_get_u32(tb[DEVLINK_ATTR_PORT_INDEX]);
+ port_ifname = mnl_attr_get_str(tb[DEVLINK_ATTR_PORT_NETDEV_NAME]);
+ err = ifname_map_add(dl, port_ifname, bus_name, dev_name, port_index);
+ if (err)
+ return MNL_CB_ERROR;
+
+ 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) {
+ ifname_map_del(ifname_map);
+ }
+}
+
+static void ifname_map_init(struct dl *dl)
+{
+ INIT_LIST_HEAD(&dl->ifname_map_list);
+}
+
+static int ifname_map_load(struct dl *dl, const char *ifname)
+{
+ struct mnlu_gen_socket nlg_map;
+ struct nlmsghdr *nlh;
+ int err;
+
+ if (ifname) {
+ err = ifname_map_rtnl_init(dl, ifname);
+ if (!err)
+ return 0;
+ /* In case kernel does not support devlink port info passed over
+ * RT netlink, fall-back to ports dump.
+ */
+ }
+
+ err = mnlu_gen_socket_open(&nlg_map, DEVLINK_GENL_NAME,
+ DEVLINK_GENL_VERSION);
+ if (err)
+ return err;
+
+ nlh = mnlu_gen_socket_cmd_prepare(&nlg_map, DEVLINK_CMD_PORT_GET,
+ NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP);
+
+ err = mnlu_gen_socket_sndrcv(&nlg_map, nlh, ifname_map_cb, dl);
+ if (err)
+ ifname_map_fini(dl);
+
+ mnlu_gen_socket_close(&nlg_map);
+ return err;
+}
+
+static int ifname_map_check_load(struct dl *dl, const char *ifname)
+{
+ int err;
+
+ if (dl->map_loaded)
+ return 0;
+
+ err = ifname_map_load(dl, ifname);
+ if (err) {
+ pr_err("Failed to create index map\n");
+ return err;
+ }
+ dl->map_loaded = true;
+ 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;
+ int err;
+
+ err = ifname_map_check_load(dl, ifname);
+ if (err)
+ return err;
+
+ 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,
+ const char **p_ifname)
+{
+ struct ifname_map *ifname_map;
+
+ int err;
+
+ err = ifname_map_check_load(dl, NULL);
+ if (err)
+ return err;
+
+ 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) {
+ /* In case non-NULL ifname is passed, update the
+ * looked-up entry.
+ */
+ if (*p_ifname)
+ return ifname_map_update(ifname_map, *p_ifname);
+
+ *p_ifname = ifname_map->ifname;
+ return 0;
+ }
+ }
+ return -ENOENT;
+}
+
+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 ident_str_validate(char *str, unsigned int expected)
+{
+ if (!str)
+ return -ENOENT;
+
+ if (get_str_char_count(str, '/') != expected) {
+ pr_err("Wrong identification string format.\n");
+ return -EINVAL;
+ }
+
+ 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(char *str, char **p_bus_name, char **p_dev_name)
+{
+ int err;
+
+ err = ident_str_validate(str, 1);
+ if (err) {
+ pr_err("Devlink identification (\"bus_name/dev_name\") expected\n");
+ return err;
+ }
+ 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 = get_u32(p_port_index, portstr, 10);
+ 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 *str, char **p_bus_name,
+ char **p_dev_name, uint32_t *p_port_index)
+{
+ unsigned int slash_count;
+
+ if (!str) {
+ pr_err("Port identification (\"bus_name/dev_name/port_index\" or \"netdev ifname\") expected.\n");
+ return -ENOENT;
+ }
+ 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 *str, char **p_bus_name,
+ char **p_dev_name, uint32_t *p_port_index,
+ uint64_t *p_handle_bit)
+{
+ 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 -ENOENT;
+ }
+ 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_name(char *str, char **p_bus_name,
+ char **p_dev_name, char **p_name)
+{
+ char *handlestr;
+ int err;
+
+ err = str_split_by_char(str, &handlestr, p_name, '/');
+ if (err)
+ return err;
+
+ return str_split_by_char(handlestr, p_bus_name, p_dev_name, '/');
+}
+
+static int dl_argv_handle_region(char *str, char **p_bus_name,
+ char **p_dev_name, char **p_region)
+{
+ int err;
+
+ err = ident_str_validate(str, 2);
+ if (err) {
+ pr_err("Expected \"bus_name/dev_name/region\" identification.\n");
+ return err;
+ }
+
+ err = __dl_argv_handle_name(str, p_bus_name, p_dev_name, p_region);
+ if (err)
+ pr_err("Region identification \"%s\" is invalid\n", str);
+ return err;
+}
+
+
+static int dl_argv_handle_rate_node(char *str, char **p_bus_name,
+ char **p_dev_name, char **p_node)
+{
+ int err;
+
+ err = ident_str_validate(str, 2);
+ if (err) {
+ pr_err("Expected \"bus_name/dev_name/node\" identification.\n");
+ return err;
+ }
+
+ err = __dl_argv_handle_name(str, p_bus_name, p_dev_name, p_node);
+ if (err) {
+ pr_err("Node identification \"%s\" is invalid\n", str);
+ return err;
+ }
+
+ if (!**p_node || strspn(*p_node, "0123456789") == strlen(*p_node)) {
+ err = -EINVAL;
+ pr_err("Node name cannot be a devlink port index or empty.\n");
+ }
+
+ return err;
+}
+
+static int dl_argv_handle_rate(char *str, char **p_bus_name,
+ char **p_dev_name, uint32_t *p_port_index,
+ char **p_node_name, uint64_t *p_handle_bit)
+{
+ char *identifier;
+ int err;
+
+ err = ident_str_validate(str, 2);
+ if (err) {
+ pr_err("Expected \"bus_name/dev_name/node\" or "
+ "\"bus_name/dev_name/port_index\" identification.\n");
+ return err;
+ }
+
+ err = __dl_argv_handle_name(str, p_bus_name, p_dev_name, &identifier);
+ if (err) {
+ pr_err("Identification \"%s\" is invalid\n", str);
+ return err;
+ }
+
+ if (!*identifier) {
+ pr_err("Identifier cannot be empty");
+ return -EINVAL;
+ }
+
+ if (strspn(identifier, "0123456789") == strlen(identifier)) {
+ err = get_u32(p_port_index, identifier, 10);
+ if (err) {
+ pr_err("Port index \"%s\" is not a number"
+ " or not within range\n", identifier);
+ return err;
+ }
+ *p_handle_bit = DL_OPT_HANDLEP;
+ } else {
+ *p_handle_bit = DL_OPT_PORT_FN_RATE_NODE_NAME;
+ *p_node_name = identifier;
+ }
+ return 0;
+}
+
+static int dl_argv_uint64_t(struct dl *dl, __u64 *p_val)
+{
+ char *str = dl_argv_next(dl);
+ int err;
+
+ if (!str) {
+ pr_err("Unsigned number argument expected\n");
+ return -EINVAL;
+ }
+
+ err = get_u64(p_val, str, 10);
+ 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 = get_u32(p_val, str, 10);
+ 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 = get_u16(p_val, str, 10);
+ 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 selftests_get(const char *selftest_name, bool *selftests_opt)
+{
+ if (strcmp(selftest_name, "flash") == 0) {
+ selftests_opt[0] = true;
+ } else {
+ pr_err("Unknown selftest \"%s\"\n", selftest_name);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+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;
+}
+
+static int port_fn_rate_type_get(const char *typestr, uint16_t *type)
+{
+ if (!strcmp(typestr, "leaf"))
+ *type = DEVLINK_RATE_TYPE_LEAF;
+ else if (!strcmp(typestr, "node"))
+ *type = DEVLINK_RATE_TYPE_NODE;
+ else
+ return -EINVAL;
+ return 0;
+}
+
+static int port_fn_rate_value_get(struct dl *dl, uint64_t *rate)
+{
+ const char *ratestr;
+ __u64 rate64;
+ int err;
+
+ err = dl_argv_str(dl, &ratestr);
+ if (err)
+ return err;
+ err = get_rate64(&rate64, ratestr);
+ if (err) {
+ pr_err("Invalid rate value: \"%s\"\n", ratestr);
+ return -EINVAL;
+ }
+
+ *rate = rate64;
+ 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."},
+ {DL_OPT_LINECARD, "Linecard index expected."},
+ {DL_OPT_LINECARD_TYPE, "Linecard type expected."},
+ {DL_OPT_SELFTESTS, "Test name 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 -ENOENT;
+ }
+ }
+ 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;
+ char *str = dl_argv_next(dl);
+ uint64_t o_found = 0;
+ int err;
+
+ if (str) {
+ str = strdup(str);
+ if (!str)
+ return -ENOMEM;
+ free(dl->handle_argv);
+ dl->handle_argv = str;
+ }
+
+ if (o_required & DL_OPT_HANDLE && o_required & DL_OPT_HANDLEP) {
+ uint64_t handle_bit;
+
+ err = dl_argv_handle_both(dl, str, &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_HANDLEP &&
+ o_required & DL_OPT_PORT_FN_RATE_NODE_NAME) {
+ uint64_t handle_bit;
+
+ err = dl_argv_handle_rate(str, &opts->bus_name, &opts->dev_name,
+ &opts->port_index,
+ &opts->rate_node_name,
+ &handle_bit);
+ if (err)
+ return err;
+ o_required &= ~(DL_OPT_HANDLEP | DL_OPT_PORT_FN_RATE_NODE_NAME) |
+ handle_bit;
+ o_found |= handle_bit;
+ } else if (o_required & DL_OPT_HANDLE) {
+ err = dl_argv_handle(str, &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, str, &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(str, &opts->bus_name,
+ &opts->dev_name,
+ &opts->region_name);
+ if (err)
+ return err;
+ o_found |= DL_OPT_HANDLE_REGION;
+ } else if (o_required & DL_OPT_PORT_FN_RATE_NODE_NAME) {
+ err = dl_argv_handle_rate_node(str, &opts->bus_name,
+ &opts->dev_name,
+ &opts->rate_node_name);
+ if (err)
+ return err;
+ o_found |= DL_OPT_PORT_FN_RATE_NODE_NAME;
+ }
+
+ 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, "id") &&
+ (o_all & DL_OPT_SELFTESTS)) {
+ dl_arg_inc(dl);
+ while (dl_argc(dl)) {
+ const char *selftest_name;
+ err = dl_argv_str(dl, &selftest_name);
+ if (err)
+ return err;
+ err = selftests_get(selftest_name,
+ opts->selftests_opt);
+ if (err)
+ return err;
+ }
+ o_found |= DL_OPT_SELFTESTS;
+
+ } 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") ||
+ dl_argv_match(dl, "name")) &&
+ (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") ||
+ dl_argv_match(dl, "name")) &&
+ (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 if (dl_argv_match(dl, "controller") && (o_all & DL_OPT_PORT_CONTROLLER)) {
+ dl_arg_inc(dl);
+ err = dl_argv_uint32_t(dl, &opts->port_controller);
+ if (err)
+ return err;
+ o_found |= DL_OPT_PORT_CONTROLLER;
+ } else if (dl_argv_match(dl, "type") &&
+ (o_all & DL_OPT_PORT_FN_RATE_TYPE)) {
+ const char *typestr;
+
+ dl_arg_inc(dl);
+ err = dl_argv_str(dl, &typestr);
+ if (err)
+ return err;
+ err = port_fn_rate_type_get(typestr, &opts->rate_type);
+ if (err)
+ return err;
+ o_found |= DL_OPT_PORT_FN_RATE_TYPE;
+ } else if (dl_argv_match(dl, "tx_share") &&
+ (o_all & DL_OPT_PORT_FN_RATE_TX_SHARE)) {
+ dl_arg_inc(dl);
+ err = port_fn_rate_value_get(dl, &opts->rate_tx_share);
+ if (err)
+ return err;
+ o_found |= DL_OPT_PORT_FN_RATE_TX_SHARE;
+ } else if (dl_argv_match(dl, "tx_max") &&
+ (o_all & DL_OPT_PORT_FN_RATE_TX_MAX)) {
+ dl_arg_inc(dl);
+ err = port_fn_rate_value_get(dl, &opts->rate_tx_max);
+ if (err)
+ return err;
+ o_found |= DL_OPT_PORT_FN_RATE_TX_MAX;
+ } else if (dl_argv_match(dl, "tx_priority") &&
+ (o_all & DL_OPT_PORT_FN_RATE_TX_PRIORITY)) {
+ dl_arg_inc(dl);
+ err = dl_argv_uint32_t(dl, &opts->rate_tx_priority);
+ if (err)
+ return err;
+ o_found |= DL_OPT_PORT_FN_RATE_TX_PRIORITY;
+ } else if (dl_argv_match(dl, "tx_weight") &&
+ (o_all & DL_OPT_PORT_FN_RATE_TX_WEIGHT)) {
+ dl_arg_inc(dl);
+ err = dl_argv_uint32_t(dl, &opts->rate_tx_weight);
+ if (err)
+ return err;
+ o_found |= DL_OPT_PORT_FN_RATE_TX_WEIGHT;
+ } else if (dl_argv_match(dl, "parent") &&
+ (o_all & DL_OPT_PORT_FN_RATE_PARENT)) {
+ dl_arg_inc(dl);
+ err = dl_argv_str(dl, &opts->rate_parent_node);
+ if (err)
+ return err;
+ o_found |= DL_OPT_PORT_FN_RATE_PARENT;
+ } else if (dl_argv_match(dl, "noparent") &&
+ (o_all & DL_OPT_PORT_FN_RATE_PARENT)) {
+ dl_arg_inc(dl);
+ opts->rate_parent_node = "";
+ o_found |= DL_OPT_PORT_FN_RATE_PARENT;
+ } else if (dl_argv_match(dl, "lc") &&
+ (o_all & DL_OPT_LINECARD)) {
+ dl_arg_inc(dl);
+ err = dl_argv_uint32_t(dl, &opts->linecard_index);
+ if (err)
+ return err;
+ o_found |= DL_OPT_LINECARD;
+ } else if (dl_argv_match(dl, "type") &&
+ (o_all & DL_OPT_LINECARD_TYPE)) {
+ dl_arg_inc(dl);
+ err = dl_argv_str(dl, &opts->linecard_type);
+ if (err)
+ return err;
+ o_found |= DL_OPT_LINECARD_TYPE;
+ } else if (dl_argv_match(dl, "notype") &&
+ (o_all & DL_OPT_LINECARD_TYPE)) {
+ dl_arg_inc(dl);
+ opts->linecard_type = "";
+ o_found |= DL_OPT_LINECARD_TYPE;
+ } else if (dl_argv_match(dl, "roce") &&
+ (o_all & DL_OPT_PORT_FN_CAPS)) {
+ bool roce;
+
+ dl_arg_inc(dl);
+ err = dl_argv_bool(dl, &roce);
+ if (err)
+ return err;
+ opts->port_fn_caps.selector |= DEVLINK_PORT_FN_CAP_ROCE;
+ if (roce)
+ opts->port_fn_caps.value |= DEVLINK_PORT_FN_CAP_ROCE;
+ o_found |= DL_OPT_PORT_FN_CAPS;
+ } else if (dl_argv_match(dl, "migratable") &&
+ (o_all & DL_OPT_PORT_FN_CAPS)) {
+ bool mig;
+
+ dl_arg_inc(dl);
+ err = dl_argv_bool(dl, &mig);
+ if (err)
+ return err;
+ opts->port_fn_caps.selector |= DEVLINK_PORT_FN_CAP_MIGRATABLE;
+ if (mig)
+ opts->port_fn_caps.value |= DEVLINK_PORT_FN_CAP_MIGRATABLE;
+ o_found |= DL_OPT_PORT_FN_CAPS;
+ } else if (dl_argv_match(dl, "ipsec_crypto") &&
+ (o_all & DL_OPT_PORT_FN_CAPS)) {
+ bool ipsec_crypto;
+
+ dl_arg_inc(dl);
+ err = dl_argv_bool(dl, &ipsec_crypto);
+ if (err)
+ return err;
+ opts->port_fn_caps.selector |= DEVLINK_PORT_FN_CAP_IPSEC_CRYPTO;
+ if (ipsec_crypto)
+ opts->port_fn_caps.value |= DEVLINK_PORT_FN_CAP_IPSEC_CRYPTO;
+ o_found |= DL_OPT_PORT_FN_CAPS;
+ } else if (dl_argv_match(dl, "ipsec_packet") &&
+ (o_all & DL_OPT_PORT_FN_CAPS)) {
+ bool ipsec_packet;
+
+ dl_arg_inc(dl);
+ err = dl_argv_bool(dl, &ipsec_packet);
+ if (err)
+ return err;
+ opts->port_fn_caps.selector |= DEVLINK_PORT_FN_CAP_IPSEC_PACKET;
+ if (ipsec_packet)
+ opts->port_fn_caps.value |= DEVLINK_PORT_FN_CAP_IPSEC_PACKET;
+ o_found |= DL_OPT_PORT_FN_CAPS;
+ } else {
+ pr_err("Unknown option \"%s\"\n", dl_argv(dl));
+ return -EINVAL;
+ }
+ }
+
+ if ((o_required & DL_OPT_SB) && !(o_found & DL_OPT_SB)) {
+ opts->sb_index = 0;
+ o_found |= DL_OPT_SB;
+ }
+
+ opts->present = o_found;
+
+ return dl_args_finding_required_validate(o_required, o_found);
+}
+
+static int dl_argv_dry_parse(struct dl *dl, uint64_t o_required,
+ uint64_t o_optional)
+{
+ char **argv = dl->argv;
+ int argc = dl->argc;
+ int err;
+
+ g_err_suspended = true;
+ err = dl_argv_parse(dl, o_required, o_optional);
+ g_err_suspended = false;
+ dl->argv = argv;
+ dl->argc = argc;
+ return err;
+}
+
+/* List of extended handles with two slashes. */
+static const uint64_t dl_opt_extended_handle[] = {
+ DL_OPT_HANDLEP,
+ DL_OPT_HANDLE_REGION,
+ DL_OPT_PORT_FN_RATE_NODE_NAME,
+};
+
+static int dl_argv_parse_with_selector(struct dl *dl, uint16_t *flags,
+ uint8_t cmd,
+ uint64_t o_required,
+ uint64_t o_optional,
+ uint64_t o_dump_required,
+ uint64_t o_dump_optional)
+{
+ int err;
+ int i;
+
+ if (dl_no_arg(dl))
+ goto flag_set;
+
+ /* In case the handle suggests it, do dry parsing first
+ * to see if all required options are there. Proceed with
+ * dump selector in case there are missing options on the
+ * command line. That means user provided partial
+ * object identification.
+ */
+
+ if ((o_required & (DL_OPT_HANDLE | DL_OPT_HANDLEP)) ==
+ (DL_OPT_HANDLE | DL_OPT_HANDLEP)) {
+ /* Handle case when both devlink handle and port handle
+ * are allowed. Try both alone, if parsing of either
+ * is successful, we have a do parse case.
+ */
+ err = dl_argv_dry_parse(dl, o_required & ~DL_OPT_HANDLEP,
+ o_optional);
+ if (err == -ENOENT)
+ goto dump_parse;
+ else if (!err)
+ goto do_parse;
+ err = dl_argv_dry_parse(dl, o_required & ~DL_OPT_HANDLE,
+ o_optional);
+ if (err == -ENOENT)
+ goto dump_parse;
+ goto do_parse;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(dl_opt_extended_handle); i++) {
+ uint64_t handle = dl_opt_extended_handle[i];
+
+ if ((o_required & handle) == handle) {
+ err = dl_argv_dry_parse(dl, (o_required & ~handle) |
+ DL_OPT_HANDLE,
+ o_optional);
+ if (err == -ENOENT || !err)
+ goto dump_parse;
+ goto do_parse;
+ }
+ }
+
+ err = dl_argv_dry_parse(dl, o_required, o_optional);
+ if (err == -ENOENT)
+ goto dump_parse;
+
+do_parse:
+ return dl_argv_parse(dl, o_required, o_optional);
+
+dump_parse:
+ err = mnlu_gen_cmd_dump_policy(&dl->nlg, cmd);
+ if (err) {
+ pr_err("Dump selectors are not supported by kernel for this command\n");
+ return -ENOTSUP;
+ }
+
+ err = dl_argv_parse(dl, o_dump_required, o_dump_optional);
+ if (err)
+ return err;
+
+flag_set:
+ *flags |= NLM_F_DUMP;
+ return 0;
+}
+
+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);
+ if (opts->present & DL_OPT_PORT_FN_CAPS)
+ mnl_attr_put(nlh, DEVLINK_PORT_FN_ATTR_CAPS,
+ sizeof(opts->port_fn_caps), &opts->port_fn_caps);
+
+ 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_selftests_put(struct nlmsghdr *nlh, const struct dl_opts *opts)
+{
+ bool test_sel = false;
+ struct nlattr *nest;
+ int id;
+
+ nest = mnl_attr_nest_start(nlh, DEVLINK_ATTR_SELFTESTS);
+
+ for (id = DEVLINK_ATTR_SELFTEST_ID_UNSPEC + 1;
+ id <= DEVLINK_ATTR_SELFTEST_ID_MAX &&
+ opts->selftests_opt[id]; id++) {
+ if (opts->selftests_opt[id]) {
+ test_sel = true;
+ mnl_attr_put(nlh, id, 0, NULL);
+ }
+ }
+
+ /* No test selected from user, select all */
+ if (!test_sel) {
+ for (id = DEVLINK_ATTR_SELFTEST_ID_UNSPEC + 1;
+ id <= DEVLINK_ATTR_SELFTEST_ID_MAX; id++)
+ mnl_attr_put(nlh, id, 0, NULL);
+ }
+
+ mnl_attr_nest_end(nlh, nest);
+}
+
+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);
+ } else if (opts->present & DL_OPT_PORT_FN_RATE_NODE_NAME) {
+ 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_RATE_NODE_NAME,
+ opts->rate_node_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_SELFTESTS)
+ dl_selftests_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)