From 6c38360edf8285915202c8e4192e77a5efd71307 Mon Sep 17 00:00:00 2001 From: Giluerre <72444537+Giluerre@users.noreply.github.com> Date: Wed, 21 Jun 2023 14:31:10 +0200 Subject: [PATCH] Support for VPP 23.06 (#1932) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Daniel Béreš Co-authored-by: Daniel Beres --- .github/workflows/test.yml | 4 +- plugins/govppmux/plugin_impl_govppmux.go | 1 + .../govppmux/vppcalls/vpp2306/vpe_vppcalls.go | 170 + .../vppcalls/vpp2306/vppcalls_handler.go | 47 + plugins/telemetry/telemetry.go | 1 + .../vppcalls/vpp2306/telemetry_vppcalls.go | 314 ++ .../vpp2306/telemetry_vppcalls_test.go | 527 ++ .../vppcalls/vpp2306/vppcalls_handler.go | 43 + plugins/vpp/abfplugin/abfplugin.go | 1 + .../vppcalls/vpp2306/abf_vppcalls.go | 185 + .../vppcalls/vpp2306/abf_vppcalls_test.go | 277 + .../vppcalls/vpp2306/dump_abf_vppcalls.go | 163 + .../vppcalls/vpp2306/vppcalls_handlers.go | 56 + plugins/vpp/aclplugin/aclplugin.go | 1 + .../vppcalls/vpp2306/acl_vppcalls.go | 402 ++ .../vppcalls/vpp2306/acl_vppcalls_test.go | 493 ++ .../vppcalls/vpp2306/dump_vppcalls.go | 647 +++ .../vppcalls/vpp2306/dump_vppcalls_test.go | 453 ++ .../vppcalls/vpp2306/interfaces_vppcalls.go | 335 ++ .../vpp2306/interfaces_vppcalls_test.go | 167 + .../vppcalls/vpp2306/vppcalls_handlers.go | 73 + plugins/vpp/binapi/vpp2306/abf/abf.ba.go | 584 ++ plugins/vpp/binapi/vpp2306/abf/abf_rpc.ba.go | 142 + plugins/vpp/binapi/vpp2306/acl/acl.ba.go | 1923 +++++++ plugins/vpp/binapi/vpp2306/acl/acl_rpc.ba.go | 404 ++ .../binapi/vpp2306/acl_types/acl_types.ba.go | 80 + .../binapi/vpp2306/af_packet/af_packet.ba.go | 631 +++ .../vpp2306/af_packet/af_packet_rpc.ba.go | 118 + plugins/vpp/binapi/vpp2306/arp/arp.ba.go | 335 ++ plugins/vpp/binapi/vpp2306/arp/arp_rpc.ba.go | 132 + plugins/vpp/binapi/vpp2306/bond/bond.ba.go | 1132 ++++ .../vpp/binapi/vpp2306/bond/bond_rpc.ba.go | 280 + plugins/vpp/binapi/vpp2306/dhcp/dhcp.ba.go | 1757 ++++++ .../vpp/binapi/vpp2306/dhcp/dhcp_rpc.ba.go | 222 + plugins/vpp/binapi/vpp2306/dns/dns.ba.go | 352 ++ plugins/vpp/binapi/vpp2306/dns/dns_rpc.ba.go | 61 + .../ethernet_types/ethernet_types.ba.go | 59 + .../binapi/vpp2306/fib_types/fib_types.ba.go | 200 + .../binapi/vpp2306/flowprobe/flowprobe.ba.go | 678 +++ .../vpp2306/flowprobe/flowprobe_rpc.ba.go | 118 + plugins/vpp/binapi/vpp2306/gre/gre.ba.go | 292 + plugins/vpp/binapi/vpp2306/gre/gre_rpc.ba.go | 78 + plugins/vpp/binapi/vpp2306/gtpu/gtpu.ba.go | 491 ++ .../vpp/binapi/vpp2306/gtpu/gtpu_rpc.ba.go | 108 + .../binapi/vpp2306/interface/interface.ba.go | 2925 ++++++++++ .../vpp2306/interface/interface_rpc.ba.go | 466 ++ .../interface_types/interface_types.ba.go | 304 + plugins/vpp/binapi/vpp2306/ip/ip.ba.go | 4919 +++++++++++++++++ plugins/vpp/binapi/vpp2306/ip/ip_rpc.ba.go | 926 ++++ .../vpp/binapi/vpp2306/ip6_nd/ip6_nd.ba.go | 1000 ++++ .../binapi/vpp2306/ip6_nd/ip6_nd_rpc.ba.go | 172 + .../vpp2306/ip_neighbor/ip_neighbor.ba.go | 889 +++ .../vpp2306/ip_neighbor/ip_neighbor_rpc.ba.go | 138 + .../binapi/vpp2306/ip_types/ip_types.ba.go | 672 +++ .../vpp2306/ipfix_export/ipfix_export.ba.go | 840 +++ .../ipfix_export/ipfix_export_rpc.ba.go | 250 + plugins/vpp/binapi/vpp2306/ipip/ipip.ba.go | 492 ++ .../vpp/binapi/vpp2306/ipip/ipip_rpc.ba.go | 108 + plugins/vpp/binapi/vpp2306/ipsec/ipsec.ba.go | 2678 +++++++++ .../vpp/binapi/vpp2306/ipsec/ipsec_rpc.ba.go | 580 ++ .../vpp2306/ipsec_types/ipsec_types.ba.go | 351 ++ plugins/vpp/binapi/vpp2306/l2/l2.ba.go | 2840 ++++++++++ plugins/vpp/binapi/vpp2306/l2/l2_rpc.ba.go | 470 ++ plugins/vpp/binapi/vpp2306/l3xc/l3xc.ba.go | 490 ++ .../vpp/binapi/vpp2306/l3xc/l3xc_rpc.ba.go | 98 + .../vpp/binapi/vpp2306/memclnt/memclnt.ba.go | 1145 ++++ .../binapi/vpp2306/memclnt/memclnt_rpc.ba.go | 199 + plugins/vpp/binapi/vpp2306/memif/memif.ba.go | 736 +++ .../vpp/binapi/vpp2306/memif/memif_rpc.ba.go | 162 + .../vpp2306/mfib_types/mfib_types.ba.go | 147 + .../binapi/vpp2306/nat44_ed/nat44_ed.ba.go | 3423 ++++++++++++ .../vpp2306/nat44_ed/nat44_ed_rpc.ba.go | 870 +++ .../binapi/vpp2306/nat44_ei/nat44_ei.ba.go | 3478 ++++++++++++ .../vpp2306/nat44_ei/nat44_ei_rpc.ba.go | 788 +++ .../binapi/vpp2306/nat_types/nat_types.ba.go | 140 + plugins/vpp/binapi/vpp2306/punt/punt.ba.go | 545 ++ .../vpp/binapi/vpp2306/punt/punt_rpc.ba.go | 142 + plugins/vpp/binapi/vpp2306/rd_cp/rd_cp.ba.go | 113 + .../vpp/binapi/vpp2306/rd_cp/rd_cp_rpc.ba.go | 31 + plugins/vpp/binapi/vpp2306/rdma/rdma.ba.go | 514 ++ .../vpp/binapi/vpp2306/rdma/rdma_rpc.ba.go | 61 + plugins/vpp/binapi/vpp2306/span/span.ba.go | 239 + .../vpp/binapi/vpp2306/span/span_rpc.ba.go | 78 + plugins/vpp/binapi/vpp2306/sr/sr.ba.go | 1567 ++++++ plugins/vpp/binapi/vpp2306/sr/sr_rpc.ba.go | 378 ++ .../binapi/vpp2306/sr_types/sr_types.ba.go | 139 + plugins/vpp/binapi/vpp2306/stn/stn.ba.go | 188 + plugins/vpp/binapi/vpp2306/stn/stn_rpc.ba.go | 78 + plugins/vpp/binapi/vpp2306/tapv2/tapv2.ba.go | 662 +++ .../vpp/binapi/vpp2306/tapv2/tapv2_rpc.ba.go | 98 + plugins/vpp/binapi/vpp2306/teib/teib.ba.go | 213 + .../vpp/binapi/vpp2306/teib/teib_rpc.ba.go | 78 + .../vpp2306/tunnel_types/tunnel_types.ba.go | 179 + plugins/vpp/binapi/vpp2306/vlib/vlib.ba.go | 746 +++ .../vpp/binapi/vpp2306/vlib/vlib_rpc.ba.go | 111 + .../vpp/binapi/vpp2306/vmxnet3/vmxnet3.ba.go | 553 ++ .../binapi/vpp2306/vmxnet3/vmxnet3_rpc.ba.go | 132 + plugins/vpp/binapi/vpp2306/vpe/vpe.ba.go | 265 + plugins/vpp/binapi/vpp2306/vpe/vpe_rpc.ba.go | 88 + .../binapi/vpp2306/vpe_types/vpe_types.ba.go | 122 + plugins/vpp/binapi/vpp2306/vpp2306.go | 148 + plugins/vpp/binapi/vpp2306/vrrp/vrrp.ba.go | 1256 +++++ .../vpp/binapi/vpp2306/vrrp/vrrp_rpc.ba.go | 226 + plugins/vpp/binapi/vpp2306/vxlan/vxlan.ba.go | 754 +++ .../vpp/binapi/vpp2306/vxlan/vxlan_rpc.ba.go | 162 + .../binapi/vpp2306/vxlan_gpe/vxlan_gpe.ba.go | 568 ++ .../vpp2306/vxlan_gpe/vxlan_gpe_rpc.ba.go | 142 + .../binapi/vpp2306/wireguard/wireguard.ba.go | 910 +++ .../vpp2306/wireguard/wireguard_rpc.ba.go | 172 + plugins/vpp/dnsplugin/dnsplugin.go | 1 + plugins/vpp/dnsplugin/vppcalls/vpp2306/dns.go | 110 + .../dnsplugin/vppcalls/vpp2306/dns_test.go | 212 + .../vppcalls/vpp2306/vppcalls_handler.go | 55 + plugins/vpp/ifplugin/descriptor/interface.go | 20 +- .../vpp/ifplugin/descriptor/interface_crud.go | 11 + plugins/vpp/ifplugin/ifplugin.go | 3 +- .../vppcalls/vpp2306/admin_vppcalls.go | 78 + .../vppcalls/vpp2306/admin_vppcalls_test.go | 201 + .../vppcalls/vpp2306/afpacket_vppcalls.go | 56 + .../vpp2306/afpacket_vppcalls_test.go | 152 + .../vppcalls/vpp2306/bond_vppcalls.go | 118 + .../vppcalls/vpp2306/dhcp_vppcalls.go | 46 + .../vppcalls/vpp2306/dhcp_vppcalls_test.go | 105 + plugins/vpp/ifplugin/vppcalls/vpp2306/doc.go | 17 + .../vpp2306/dump_interface_status_test.go | 106 + .../vpp2306/dump_interface_vppcalls.go | 1120 ++++ .../vpp2306/dump_interface_vppcalls_test.go | 666 +++ .../ifplugin/vppcalls/vpp2306/gre_vppcalls.go | 140 + .../vppcalls/vpp2306/gtpu_vppcalls.go | 202 + .../vppcalls/vpp2306/gtpu_vppcalls_test.go | 267 + .../vpp/ifplugin/vppcalls/vpp2306/helpers.go | 73 + .../vppcalls/vpp2306/ip6nd_vppcalls.go | 34 + .../vppcalls/vpp2306/ip_container_vppcalls.go | 49 + .../vpp2306/ip_container_vppcalls_test.go | 169 + .../ifplugin/vppcalls/vpp2306/ip_vppcalls.go | 79 + .../vppcalls/vpp2306/ip_vppcalls_test.go | 283 + .../vppcalls/vpp2306/ipip_vppcalls.go | 175 + .../vppcalls/vpp2306/ipip_vppcalls_test.go | 217 + .../vppcalls/vpp2306/ipsec_vppcalls.go | 51 + .../vppcalls/vpp2306/ipsec_vppcalls_test.go | 90 + .../ifplugin/vppcalls/vpp2306/l2_vppcalls.go | 65 + .../vppcalls/vpp2306/loopback_vppcalls.go | 45 + .../vpp2306/loopback_vppcalls_test.go | 100 + .../ifplugin/vppcalls/vpp2306/mac_vppcalls.go | 53 + .../vppcalls/vpp2306/mac_vppcalls_test.go | 76 + .../vppcalls/vpp2306/memif_vppcalls.go | 202 + .../vppcalls/vpp2306/memif_vppcalls_test.go | 202 + .../ifplugin/vppcalls/vpp2306/mtu_vppcalls.go | 34 + .../vppcalls/vpp2306/mtu_vppcalls_test.go | 62 + .../vppcalls/vpp2306/rdma_vppcalls.go | 80 + .../vppcalls/vpp2306/rdma_vppcalls_test.go | 84 + .../vppcalls/vpp2306/rx_mode_vppcalls.go | 53 + .../vppcalls/vpp2306/rx_mode_vppcalls_test.go | 98 + .../vppcalls/vpp2306/rx_placement_vppcalls.go | 37 + .../vpp2306/rx_placement_vppcalls_test.go | 98 + .../vppcalls/vpp2306/span_vppcalls.go | 95 + .../vppcalls/vpp2306/subif_vppcalls.go | 49 + .../vppcalls/vpp2306/subif_vppcalls_test.go | 76 + .../ifplugin/vppcalls/vpp2306/tap_vppcalls.go | 83 + .../vppcalls/vpp2306/tap_vppcalls_test.go | 74 + .../vppcalls/vpp2306/vmxnet3_vppcalls.go | 129 + .../vppcalls/vpp2306/vmxnet3_vppcalls_test.go | 115 + .../vppcalls/vpp2306/vppcalls_handler.go | 131 + .../ifplugin/vppcalls/vpp2306/vrf_vppcalls.go | 69 + .../vppcalls/vpp2306/vrf_vppcalls_test.go | 122 + .../vppcalls/vpp2306/vxlan_gpe_vppcalls.go | 79 + .../vppcalls/vpp2306/vxlan_vppcalls.go | 75 + .../vppcalls/vpp2306/vxlan_vppcalls_test.go | 235 + .../vppcalls/vpp2306/watch_vppcalls.go | 199 + .../vppcalls/vpp2306/watch_vppcalls_test.go | 143 + .../vppcalls/vpp2306/wireguard_vppcalls.go | 132 + .../vpp2306/wireguard_vppcalls_test.go | 102 + plugins/vpp/ipfixplugin/ipfixplugin.go | 1 + .../vppcalls/vpp2306/dump_vppcalls.go | 64 + .../vppcalls/vpp2306/flowprobe_vppcalls.go | 102 + .../vppcalls/vpp2306/ipfix_vppcalls.go | 107 + .../vppcalls/vpp2306/vppcalls_handlers.go | 52 + plugins/vpp/ipsecplugin/ipsecplugin.go | 1 + .../vppcalls/vpp2306/dump_vppcalls.go | 315 ++ .../vppcalls/vpp2306/ipsec_vppcalls.go | 340 ++ .../vppcalls/vpp2306/ipsec_vppcalls_test.go | 400 ++ .../vppcalls/vpp2306/vppcalls_handlers.go | 75 + plugins/vpp/l2plugin/l2plugin.go | 1 + .../vppcalls/vpp2306/arp_term_vppcalls.go | 67 + .../vpp2306/arp_term_vppcalls_test.go | 141 + .../vpp2306/bridge_domain_vppcalls.go | 57 + .../vpp2306/bridge_domain_vppcalls_test.go | 122 + plugins/vpp/l2plugin/vppcalls/vpp2306/doc.go | 17 + .../vppcalls/vpp2306/dump_vppcalls.go | 245 + .../vppcalls/vpp2306/dump_vppcalls_test.go | 307 + .../vppcalls/vpp2306/interface_vppcalls.go | 70 + .../vpp2306/interface_vppcalls_test.go | 144 + .../vppcalls/vpp2306/l2fib_vppcalls.go | 83 + .../vppcalls/vpp2306/l2fib_vppcalls_test.go | 113 + .../vppcalls/vpp2306/vppcalls_handler.go | 119 + .../vppcalls/vpp2306/xconnect_vppcalls.go | 60 + .../vpp2306/xconnect_vppcalls_test.go | 129 + plugins/vpp/l3plugin/l3plugin.go | 1 + .../vpp/l3plugin/vppcalls/vpp2306/arp_dump.go | 93 + .../l3plugin/vppcalls/vpp2306/arp_vppcalls.go | 76 + .../vppcalls/vpp2306/arp_vppcalls_test.go | 92 + .../vppcalls/vpp2306/dhcpproxy_dump.go | 80 + .../vppcalls/vpp2306/dhcpproxy_vppcalls.go | 75 + plugins/vpp/l3plugin/vppcalls/vpp2306/doc.go | 16 + .../vppcalls/vpp2306/ipneigh_vppcalls.go | 151 + .../vppcalls/vpp2306/ipneigh_vppcalls_test.go | 70 + .../vppcalls/vpp2306/l3xc_vppcalls.go | 144 + .../vppcalls/vpp2306/proxyarp_dump.go | 86 + .../vppcalls/vpp2306/proxyarp_vppcalls.go | 104 + .../vpp2306/proxyarp_vppcalls_test.go | 129 + .../l3plugin/vppcalls/vpp2306/route_dump.go | 205 + .../vppcalls/vpp2306/route_dump_test.go | 88 + .../vppcalls/vpp2306/route_vppcalls.go | 150 + .../vppcalls/vpp2306/route_vppcalls_test.go | 99 + .../vppcalls/vpp2306/teib_vppcalls.go | 104 + .../vppcalls/vpp2306/vppcalls_handlers.go | 310 ++ .../vpp/l3plugin/vppcalls/vpp2306/vrf_dump.go | 52 + .../vppcalls/vpp2306/vrf_dump_test.go | 79 + .../l3plugin/vppcalls/vpp2306/vrf_vppcalls.go | 92 + .../vppcalls/vpp2306/vrf_vppcalls_test.go | 163 + .../l3plugin/vppcalls/vpp2306/vrrp_dump.go | 79 + .../vppcalls/vpp2306/vrrp_vppcalls.go | 136 + .../vppcalls/vpp2306/vrrp_vppcalls_test.go | 136 + plugins/vpp/natplugin/natplugin.go | 1 + .../vppcalls/vpp2306/dump_nat_vppcalls.go | 1236 +++++ .../vpp2306/dump_nat_vppcalls_test.go | 626 +++ .../vppcalls/vpp2306/nat_vppcalls.go | 837 +++ .../vppcalls/vpp2306/nat_vppcalls_test.go | 2011 +++++++ .../vppcalls/vpp2306/vppcalls_handler.go | 63 + plugins/vpp/puntplugin/puntplugin.go | 1 + .../vppcalls/vpp2306/dump_vppcalls.go | 247 + .../vppcalls/vpp2306/punt_vppcalls.go | 287 + .../vppcalls/vpp2306/punt_vppcalls_test.go | 224 + .../vppcalls/vpp2306/vppcalls_handler.go | 75 + plugins/vpp/srplugin/srplugin.go | 1 + plugins/vpp/srplugin/vppcalls/vpp2306/srv6.go | 651 +++ .../srplugin/vppcalls/vpp2306/srv6_dump.go | 165 + .../srplugin/vppcalls/vpp2306/srv6_test.go | 1525 +++++ .../vppcalls/vpp2306/vppcalls_handlers.go | 106 + plugins/vpp/stnplugin/stnplugin.go | 1 + .../vppcalls/vpp2306/dump_stn_vppcalls.go | 73 + .../vppcalls/vpp2306/stn_vppcalls.go | 95 + .../vppcalls/vpp2306/stn_vppcalls_test.go | 151 + .../vppcalls/vpp2306/vppcalls_handler.go | 50 + .../vppcalls/vpp2306/dump_vppcalls.go | 73 + .../vppcalls/vpp2306/vppcalls_handlers.go | 42 + .../vppcalls/vpp2306/wireguard_vppcalls.go | 86 + .../vpp2306/wireguard_vppcalls_test.go | 93 + .../vpp/wireguardplugin/wireguardplugin.go | 1 + tests/e2e/010_interfaces_test.go | 7 +- tests/e2e/120_dns_test.go | 5 +- tests/e2e/e2etest/e2e.go | 18 + tests/integration/vpp/150_nat_test.go | 7 +- vpp.env | 7 +- 254 files changed, 81679 insertions(+), 22 deletions(-) create mode 100644 plugins/govppmux/vppcalls/vpp2306/vpe_vppcalls.go create mode 100644 plugins/govppmux/vppcalls/vpp2306/vppcalls_handler.go create mode 100644 plugins/telemetry/vppcalls/vpp2306/telemetry_vppcalls.go create mode 100644 plugins/telemetry/vppcalls/vpp2306/telemetry_vppcalls_test.go create mode 100644 plugins/telemetry/vppcalls/vpp2306/vppcalls_handler.go create mode 100644 plugins/vpp/abfplugin/vppcalls/vpp2306/abf_vppcalls.go create mode 100644 plugins/vpp/abfplugin/vppcalls/vpp2306/abf_vppcalls_test.go create mode 100644 plugins/vpp/abfplugin/vppcalls/vpp2306/dump_abf_vppcalls.go create mode 100644 plugins/vpp/abfplugin/vppcalls/vpp2306/vppcalls_handlers.go create mode 100644 plugins/vpp/aclplugin/vppcalls/vpp2306/acl_vppcalls.go create mode 100644 plugins/vpp/aclplugin/vppcalls/vpp2306/acl_vppcalls_test.go create mode 100644 plugins/vpp/aclplugin/vppcalls/vpp2306/dump_vppcalls.go create mode 100644 plugins/vpp/aclplugin/vppcalls/vpp2306/dump_vppcalls_test.go create mode 100644 plugins/vpp/aclplugin/vppcalls/vpp2306/interfaces_vppcalls.go create mode 100644 plugins/vpp/aclplugin/vppcalls/vpp2306/interfaces_vppcalls_test.go create mode 100644 plugins/vpp/aclplugin/vppcalls/vpp2306/vppcalls_handlers.go create mode 100644 plugins/vpp/binapi/vpp2306/abf/abf.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/abf/abf_rpc.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/acl/acl.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/acl/acl_rpc.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/acl_types/acl_types.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/af_packet/af_packet.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/af_packet/af_packet_rpc.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/arp/arp.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/arp/arp_rpc.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/bond/bond.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/bond/bond_rpc.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/dhcp/dhcp.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/dhcp/dhcp_rpc.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/dns/dns.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/dns/dns_rpc.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/ethernet_types/ethernet_types.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/fib_types/fib_types.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/flowprobe/flowprobe.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/flowprobe/flowprobe_rpc.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/gre/gre.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/gre/gre_rpc.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/gtpu/gtpu.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/gtpu/gtpu_rpc.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/interface/interface.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/interface/interface_rpc.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/interface_types/interface_types.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/ip/ip.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/ip/ip_rpc.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/ip6_nd/ip6_nd.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/ip6_nd/ip6_nd_rpc.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/ip_neighbor/ip_neighbor.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/ip_neighbor/ip_neighbor_rpc.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/ip_types/ip_types.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/ipfix_export/ipfix_export.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/ipfix_export/ipfix_export_rpc.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/ipip/ipip.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/ipip/ipip_rpc.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/ipsec/ipsec.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/ipsec/ipsec_rpc.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/ipsec_types/ipsec_types.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/l2/l2.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/l2/l2_rpc.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/l3xc/l3xc.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/l3xc/l3xc_rpc.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/memclnt/memclnt.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/memclnt/memclnt_rpc.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/memif/memif.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/memif/memif_rpc.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/mfib_types/mfib_types.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/nat44_ed/nat44_ed.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/nat44_ed/nat44_ed_rpc.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/nat44_ei/nat44_ei.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/nat44_ei/nat44_ei_rpc.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/nat_types/nat_types.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/punt/punt.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/punt/punt_rpc.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/rd_cp/rd_cp.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/rd_cp/rd_cp_rpc.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/rdma/rdma.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/rdma/rdma_rpc.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/span/span.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/span/span_rpc.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/sr/sr.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/sr/sr_rpc.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/sr_types/sr_types.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/stn/stn.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/stn/stn_rpc.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/tapv2/tapv2.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/tapv2/tapv2_rpc.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/teib/teib.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/teib/teib_rpc.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/tunnel_types/tunnel_types.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/vlib/vlib.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/vlib/vlib_rpc.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/vmxnet3/vmxnet3.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/vmxnet3/vmxnet3_rpc.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/vpe/vpe.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/vpe/vpe_rpc.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/vpe_types/vpe_types.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/vpp2306.go create mode 100644 plugins/vpp/binapi/vpp2306/vrrp/vrrp.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/vrrp/vrrp_rpc.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/vxlan/vxlan.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/vxlan/vxlan_rpc.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/vxlan_gpe/vxlan_gpe.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/vxlan_gpe/vxlan_gpe_rpc.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/wireguard/wireguard.ba.go create mode 100644 plugins/vpp/binapi/vpp2306/wireguard/wireguard_rpc.ba.go create mode 100644 plugins/vpp/dnsplugin/vppcalls/vpp2306/dns.go create mode 100644 plugins/vpp/dnsplugin/vppcalls/vpp2306/dns_test.go create mode 100644 plugins/vpp/dnsplugin/vppcalls/vpp2306/vppcalls_handler.go create mode 100644 plugins/vpp/ifplugin/vppcalls/vpp2306/admin_vppcalls.go create mode 100644 plugins/vpp/ifplugin/vppcalls/vpp2306/admin_vppcalls_test.go create mode 100644 plugins/vpp/ifplugin/vppcalls/vpp2306/afpacket_vppcalls.go create mode 100644 plugins/vpp/ifplugin/vppcalls/vpp2306/afpacket_vppcalls_test.go create mode 100644 plugins/vpp/ifplugin/vppcalls/vpp2306/bond_vppcalls.go create mode 100644 plugins/vpp/ifplugin/vppcalls/vpp2306/dhcp_vppcalls.go create mode 100644 plugins/vpp/ifplugin/vppcalls/vpp2306/dhcp_vppcalls_test.go create mode 100644 plugins/vpp/ifplugin/vppcalls/vpp2306/doc.go create mode 100644 plugins/vpp/ifplugin/vppcalls/vpp2306/dump_interface_status_test.go create mode 100644 plugins/vpp/ifplugin/vppcalls/vpp2306/dump_interface_vppcalls.go create mode 100644 plugins/vpp/ifplugin/vppcalls/vpp2306/dump_interface_vppcalls_test.go create mode 100644 plugins/vpp/ifplugin/vppcalls/vpp2306/gre_vppcalls.go create mode 100644 plugins/vpp/ifplugin/vppcalls/vpp2306/gtpu_vppcalls.go create mode 100644 plugins/vpp/ifplugin/vppcalls/vpp2306/gtpu_vppcalls_test.go create mode 100644 plugins/vpp/ifplugin/vppcalls/vpp2306/helpers.go create mode 100644 plugins/vpp/ifplugin/vppcalls/vpp2306/ip6nd_vppcalls.go create mode 100644 plugins/vpp/ifplugin/vppcalls/vpp2306/ip_container_vppcalls.go create mode 100644 plugins/vpp/ifplugin/vppcalls/vpp2306/ip_container_vppcalls_test.go create mode 100644 plugins/vpp/ifplugin/vppcalls/vpp2306/ip_vppcalls.go create mode 100644 plugins/vpp/ifplugin/vppcalls/vpp2306/ip_vppcalls_test.go create mode 100644 plugins/vpp/ifplugin/vppcalls/vpp2306/ipip_vppcalls.go create mode 100644 plugins/vpp/ifplugin/vppcalls/vpp2306/ipip_vppcalls_test.go create mode 100644 plugins/vpp/ifplugin/vppcalls/vpp2306/ipsec_vppcalls.go create mode 100644 plugins/vpp/ifplugin/vppcalls/vpp2306/ipsec_vppcalls_test.go create mode 100644 plugins/vpp/ifplugin/vppcalls/vpp2306/l2_vppcalls.go create mode 100644 plugins/vpp/ifplugin/vppcalls/vpp2306/loopback_vppcalls.go create mode 100644 plugins/vpp/ifplugin/vppcalls/vpp2306/loopback_vppcalls_test.go create mode 100644 plugins/vpp/ifplugin/vppcalls/vpp2306/mac_vppcalls.go create mode 100644 plugins/vpp/ifplugin/vppcalls/vpp2306/mac_vppcalls_test.go create mode 100644 plugins/vpp/ifplugin/vppcalls/vpp2306/memif_vppcalls.go create mode 100644 plugins/vpp/ifplugin/vppcalls/vpp2306/memif_vppcalls_test.go create mode 100644 plugins/vpp/ifplugin/vppcalls/vpp2306/mtu_vppcalls.go create mode 100644 plugins/vpp/ifplugin/vppcalls/vpp2306/mtu_vppcalls_test.go create mode 100644 plugins/vpp/ifplugin/vppcalls/vpp2306/rdma_vppcalls.go create mode 100644 plugins/vpp/ifplugin/vppcalls/vpp2306/rdma_vppcalls_test.go create mode 100644 plugins/vpp/ifplugin/vppcalls/vpp2306/rx_mode_vppcalls.go create mode 100644 plugins/vpp/ifplugin/vppcalls/vpp2306/rx_mode_vppcalls_test.go create mode 100644 plugins/vpp/ifplugin/vppcalls/vpp2306/rx_placement_vppcalls.go create mode 100644 plugins/vpp/ifplugin/vppcalls/vpp2306/rx_placement_vppcalls_test.go create mode 100644 plugins/vpp/ifplugin/vppcalls/vpp2306/span_vppcalls.go create mode 100644 plugins/vpp/ifplugin/vppcalls/vpp2306/subif_vppcalls.go create mode 100644 plugins/vpp/ifplugin/vppcalls/vpp2306/subif_vppcalls_test.go create mode 100644 plugins/vpp/ifplugin/vppcalls/vpp2306/tap_vppcalls.go create mode 100644 plugins/vpp/ifplugin/vppcalls/vpp2306/tap_vppcalls_test.go create mode 100644 plugins/vpp/ifplugin/vppcalls/vpp2306/vmxnet3_vppcalls.go create mode 100644 plugins/vpp/ifplugin/vppcalls/vpp2306/vmxnet3_vppcalls_test.go create mode 100644 plugins/vpp/ifplugin/vppcalls/vpp2306/vppcalls_handler.go create mode 100644 plugins/vpp/ifplugin/vppcalls/vpp2306/vrf_vppcalls.go create mode 100644 plugins/vpp/ifplugin/vppcalls/vpp2306/vrf_vppcalls_test.go create mode 100644 plugins/vpp/ifplugin/vppcalls/vpp2306/vxlan_gpe_vppcalls.go create mode 100644 plugins/vpp/ifplugin/vppcalls/vpp2306/vxlan_vppcalls.go create mode 100644 plugins/vpp/ifplugin/vppcalls/vpp2306/vxlan_vppcalls_test.go create mode 100644 plugins/vpp/ifplugin/vppcalls/vpp2306/watch_vppcalls.go create mode 100644 plugins/vpp/ifplugin/vppcalls/vpp2306/watch_vppcalls_test.go create mode 100644 plugins/vpp/ifplugin/vppcalls/vpp2306/wireguard_vppcalls.go create mode 100644 plugins/vpp/ifplugin/vppcalls/vpp2306/wireguard_vppcalls_test.go create mode 100644 plugins/vpp/ipfixplugin/vppcalls/vpp2306/dump_vppcalls.go create mode 100644 plugins/vpp/ipfixplugin/vppcalls/vpp2306/flowprobe_vppcalls.go create mode 100644 plugins/vpp/ipfixplugin/vppcalls/vpp2306/ipfix_vppcalls.go create mode 100644 plugins/vpp/ipfixplugin/vppcalls/vpp2306/vppcalls_handlers.go create mode 100644 plugins/vpp/ipsecplugin/vppcalls/vpp2306/dump_vppcalls.go create mode 100644 plugins/vpp/ipsecplugin/vppcalls/vpp2306/ipsec_vppcalls.go create mode 100644 plugins/vpp/ipsecplugin/vppcalls/vpp2306/ipsec_vppcalls_test.go create mode 100644 plugins/vpp/ipsecplugin/vppcalls/vpp2306/vppcalls_handlers.go create mode 100644 plugins/vpp/l2plugin/vppcalls/vpp2306/arp_term_vppcalls.go create mode 100644 plugins/vpp/l2plugin/vppcalls/vpp2306/arp_term_vppcalls_test.go create mode 100644 plugins/vpp/l2plugin/vppcalls/vpp2306/bridge_domain_vppcalls.go create mode 100644 plugins/vpp/l2plugin/vppcalls/vpp2306/bridge_domain_vppcalls_test.go create mode 100644 plugins/vpp/l2plugin/vppcalls/vpp2306/doc.go create mode 100644 plugins/vpp/l2plugin/vppcalls/vpp2306/dump_vppcalls.go create mode 100644 plugins/vpp/l2plugin/vppcalls/vpp2306/dump_vppcalls_test.go create mode 100644 plugins/vpp/l2plugin/vppcalls/vpp2306/interface_vppcalls.go create mode 100644 plugins/vpp/l2plugin/vppcalls/vpp2306/interface_vppcalls_test.go create mode 100644 plugins/vpp/l2plugin/vppcalls/vpp2306/l2fib_vppcalls.go create mode 100644 plugins/vpp/l2plugin/vppcalls/vpp2306/l2fib_vppcalls_test.go create mode 100644 plugins/vpp/l2plugin/vppcalls/vpp2306/vppcalls_handler.go create mode 100644 plugins/vpp/l2plugin/vppcalls/vpp2306/xconnect_vppcalls.go create mode 100644 plugins/vpp/l2plugin/vppcalls/vpp2306/xconnect_vppcalls_test.go create mode 100644 plugins/vpp/l3plugin/vppcalls/vpp2306/arp_dump.go create mode 100644 plugins/vpp/l3plugin/vppcalls/vpp2306/arp_vppcalls.go create mode 100644 plugins/vpp/l3plugin/vppcalls/vpp2306/arp_vppcalls_test.go create mode 100644 plugins/vpp/l3plugin/vppcalls/vpp2306/dhcpproxy_dump.go create mode 100644 plugins/vpp/l3plugin/vppcalls/vpp2306/dhcpproxy_vppcalls.go create mode 100644 plugins/vpp/l3plugin/vppcalls/vpp2306/doc.go create mode 100644 plugins/vpp/l3plugin/vppcalls/vpp2306/ipneigh_vppcalls.go create mode 100644 plugins/vpp/l3plugin/vppcalls/vpp2306/ipneigh_vppcalls_test.go create mode 100644 plugins/vpp/l3plugin/vppcalls/vpp2306/l3xc_vppcalls.go create mode 100644 plugins/vpp/l3plugin/vppcalls/vpp2306/proxyarp_dump.go create mode 100644 plugins/vpp/l3plugin/vppcalls/vpp2306/proxyarp_vppcalls.go create mode 100644 plugins/vpp/l3plugin/vppcalls/vpp2306/proxyarp_vppcalls_test.go create mode 100644 plugins/vpp/l3plugin/vppcalls/vpp2306/route_dump.go create mode 100644 plugins/vpp/l3plugin/vppcalls/vpp2306/route_dump_test.go create mode 100644 plugins/vpp/l3plugin/vppcalls/vpp2306/route_vppcalls.go create mode 100644 plugins/vpp/l3plugin/vppcalls/vpp2306/route_vppcalls_test.go create mode 100644 plugins/vpp/l3plugin/vppcalls/vpp2306/teib_vppcalls.go create mode 100644 plugins/vpp/l3plugin/vppcalls/vpp2306/vppcalls_handlers.go create mode 100644 plugins/vpp/l3plugin/vppcalls/vpp2306/vrf_dump.go create mode 100644 plugins/vpp/l3plugin/vppcalls/vpp2306/vrf_dump_test.go create mode 100644 plugins/vpp/l3plugin/vppcalls/vpp2306/vrf_vppcalls.go create mode 100644 plugins/vpp/l3plugin/vppcalls/vpp2306/vrf_vppcalls_test.go create mode 100644 plugins/vpp/l3plugin/vppcalls/vpp2306/vrrp_dump.go create mode 100644 plugins/vpp/l3plugin/vppcalls/vpp2306/vrrp_vppcalls.go create mode 100644 plugins/vpp/l3plugin/vppcalls/vpp2306/vrrp_vppcalls_test.go create mode 100644 plugins/vpp/natplugin/vppcalls/vpp2306/dump_nat_vppcalls.go create mode 100644 plugins/vpp/natplugin/vppcalls/vpp2306/dump_nat_vppcalls_test.go create mode 100644 plugins/vpp/natplugin/vppcalls/vpp2306/nat_vppcalls.go create mode 100644 plugins/vpp/natplugin/vppcalls/vpp2306/nat_vppcalls_test.go create mode 100644 plugins/vpp/natplugin/vppcalls/vpp2306/vppcalls_handler.go create mode 100644 plugins/vpp/puntplugin/vppcalls/vpp2306/dump_vppcalls.go create mode 100644 plugins/vpp/puntplugin/vppcalls/vpp2306/punt_vppcalls.go create mode 100644 plugins/vpp/puntplugin/vppcalls/vpp2306/punt_vppcalls_test.go create mode 100644 plugins/vpp/puntplugin/vppcalls/vpp2306/vppcalls_handler.go create mode 100644 plugins/vpp/srplugin/vppcalls/vpp2306/srv6.go create mode 100644 plugins/vpp/srplugin/vppcalls/vpp2306/srv6_dump.go create mode 100644 plugins/vpp/srplugin/vppcalls/vpp2306/srv6_test.go create mode 100644 plugins/vpp/srplugin/vppcalls/vpp2306/vppcalls_handlers.go create mode 100644 plugins/vpp/stnplugin/vppcalls/vpp2306/dump_stn_vppcalls.go create mode 100644 plugins/vpp/stnplugin/vppcalls/vpp2306/stn_vppcalls.go create mode 100644 plugins/vpp/stnplugin/vppcalls/vpp2306/stn_vppcalls_test.go create mode 100644 plugins/vpp/stnplugin/vppcalls/vpp2306/vppcalls_handler.go create mode 100644 plugins/vpp/wireguardplugin/vppcalls/vpp2306/dump_vppcalls.go create mode 100644 plugins/vpp/wireguardplugin/vppcalls/vpp2306/vppcalls_handlers.go create mode 100644 plugins/vpp/wireguardplugin/vppcalls/vpp2306/wireguard_vppcalls.go create mode 100644 plugins/vpp/wireguardplugin/vppcalls/vpp2306/wireguard_vppcalls_test.go diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b71ca118a7..0cddeef1e8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,7 +15,7 @@ jobs: strategy: fail-fast: false matrix: - version: ['2210', '2202'] + version: ['2306', '2210', '2202'] steps: - name: "Checkout" @@ -34,7 +34,7 @@ jobs: strategy: fail-fast: false matrix: - version: ['2210', '2202'] + version: ['2306', '2210', '2202'] steps: - name: "Checkout" diff --git a/plugins/govppmux/plugin_impl_govppmux.go b/plugins/govppmux/plugin_impl_govppmux.go index 7c24d9caed..235afc08e2 100644 --- a/plugins/govppmux/plugin_impl_govppmux.go +++ b/plugins/govppmux/plugin_impl_govppmux.go @@ -43,6 +43,7 @@ import ( _ "go.ligato.io/vpp-agent/v3/plugins/govppmux/vppcalls/vpp2202" _ "go.ligato.io/vpp-agent/v3/plugins/govppmux/vppcalls/vpp2210" + _ "go.ligato.io/vpp-agent/v3/plugins/govppmux/vppcalls/vpp2306" ) var ( diff --git a/plugins/govppmux/vppcalls/vpp2306/vpe_vppcalls.go b/plugins/govppmux/vppcalls/vpp2306/vpe_vppcalls.go new file mode 100644 index 0000000000..7fae750c5f --- /dev/null +++ b/plugins/govppmux/vppcalls/vpp2306/vpe_vppcalls.go @@ -0,0 +1,170 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "context" + "fmt" + "strings" + + "github.com/pkg/errors" + "go.fd.io/govpp/api" + + "go.ligato.io/vpp-agent/v3/plugins/govppmux/vppcalls" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/memclnt" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/vlib" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/vpe" +) + +// Ping sends VPP control ping. +func (h *VpeHandler) Ping(ctx context.Context) error { + _, err := h.memclnt.ControlPing(ctx, new(memclnt.ControlPing)) + return err +} + +// GetVersion retrieves version info from VPP. +func (h *VpeHandler) GetVersion(ctx context.Context) (*vppcalls.VersionInfo, error) { + version, err := h.vpe.ShowVersion(ctx, new(vpe.ShowVersion)) + if err != nil { + return nil, err + } + info := &vppcalls.VersionInfo{ + Program: strings.TrimRight(version.Program, "\x00"), + Version: strings.TrimRight(version.Version, "\x00"), + BuildDate: strings.TrimRight(version.BuildDate, "\x00"), + BuildDirectory: strings.TrimRight(version.BuildDirectory, "\x00"), + } + return info, nil +} + +// GetSession retrieves session info from VPP. +func (h *VpeHandler) GetSession(ctx context.Context) (*vppcalls.SessionInfo, error) { + pong, err := h.memclnt.ControlPing(ctx, new(memclnt.ControlPing)) + if err != nil { + return nil, err + } + info := &vppcalls.SessionInfo{ + PID: pong.VpePID, + ClientIdx: pong.ClientIndex, + } + + systime, err := h.vpe.ShowVpeSystemTime(ctx, new(vpe.ShowVpeSystemTime)) + if err != nil { + // TODO: log returned error as warning? + } else { + info.Uptime = float64(systime.VpeSystemTime) + } + return info, nil +} + +// GetModules retrieves module info from VPP. +func (h *VpeHandler) GetModules(ctx context.Context) ([]vppcalls.APIModule, error) { + versions, err := h.memclnt.APIVersions(ctx, new(memclnt.APIVersions)) + if err != nil { + return nil, err + } + var modules []vppcalls.APIModule + for _, v := range versions.APIVersions { + modules = append(modules, vppcalls.APIModule{ + Name: strings.TrimSuffix(strings.TrimRight(v.Name, "\x00"), ".api"), + Major: v.Major, + Minor: v.Minor, + Patch: v.Patch, + }) + } + return modules, nil +} + +func (h *VpeHandler) GetPlugins(ctx context.Context) ([]vppcalls.PluginInfo, error) { + const ( + pluginPathPrefix = "Plugin path is:" + pluginNameSuffix = "_plugin.so" + ) + + out, err := h.RunCli(ctx, "show plugins") + if err != nil { + return nil, err + } + + lines := strings.Split(out, "\n") + if len(lines) == 0 { + return nil, fmt.Errorf("empty output for 'show plugins'") + } + pluginPathLine := strings.TrimSpace(lines[0]) + if !strings.HasPrefix(pluginPathLine, pluginPathPrefix) { + return nil, fmt.Errorf("unexpected output for 'show plugins'") + } + pluginPath := strings.TrimSpace(strings.TrimPrefix(pluginPathLine, pluginPathPrefix)) + if len(pluginPath) == 0 { + return nil, fmt.Errorf("plugin path not found in output for 'show plugins'") + } + + var plugins []vppcalls.PluginInfo + for _, line := range lines { + fields := strings.Fields(line) + if len(fields) < 3 { + continue + } + var i int + if _, err := fmt.Sscanf(fields[0], "%d.", &i); err != nil { + continue + } + if i <= 0 { + continue + } + plugin := vppcalls.PluginInfo{ + Name: strings.TrimSuffix(fields[1], pluginNameSuffix), + Path: fields[1], + Version: fields[2], + Description: strings.Join(fields[3:], " "), + } + plugins = append(plugins, plugin) + } + + return plugins, nil +} + +func (h *VpeHandler) GetThreads(ctx context.Context) ([]vppcalls.ThreadInfo, error) { + resp, err := h.vlib.ShowThreads(ctx, &vlib.ShowThreads{}) + if err != nil { + return nil, err + } + threads := make([]vppcalls.ThreadInfo, len(resp.ThreadData)) + for i, thread := range resp.ThreadData { + threads[i] = vppcalls.ThreadInfo{ + Name: thread.Name, + ID: thread.ID, + Type: thread.Type, + PID: thread.PID, + Core: thread.Core, + CPUID: thread.CPUID, + CPUSocket: thread.CPUSocket, + } + } + return threads, nil +} + +// RunCli sends CLI command to VPP and returns response. +func (h *VpeHandler) RunCli(ctx context.Context, cmd string) (string, error) { + reply, err := h.vlib.CliInband(ctx, &vlib.CliInband{ + Cmd: cmd, + }) + if err != nil { + return "", errors.Wrapf(err, "VPP CLI command '%s' failed", cmd) + } else if err = api.RetvalToVPPApiError(reply.Retval); err != nil { + return "", err + } + return reply.Reply, nil +} diff --git a/plugins/govppmux/vppcalls/vpp2306/vppcalls_handler.go b/plugins/govppmux/vppcalls/vpp2306/vppcalls_handler.go new file mode 100644 index 0000000000..c91b3a289f --- /dev/null +++ b/plugins/govppmux/vppcalls/vpp2306/vppcalls_handler.go @@ -0,0 +1,47 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "go.ligato.io/vpp-agent/v3/plugins/govppmux/vppcalls" + "go.ligato.io/vpp-agent/v3/plugins/vpp" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/memclnt" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/vlib" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/vpe" +) + +func init() { + msgs := vpp.Messages( + vpe.AllMessages, + memclnt.AllMessages, + vlib.AllMessages, + ) + vppcalls.AddVersion(vpp2306.Version, msgs.AllMessages(), NewVpeHandler) +} + +type VpeHandler struct { + memclnt memclnt.RPCService + vlib vlib.RPCService + vpe vpe.RPCService +} + +func NewVpeHandler(c vpp.Client) vppcalls.VppCoreAPI { + return &VpeHandler{ + memclnt: memclnt.NewServiceClient(c), + vlib: vlib.NewServiceClient(c), + vpe: vpe.NewServiceClient(c), + } +} diff --git a/plugins/telemetry/telemetry.go b/plugins/telemetry/telemetry.go index 30153ac41c..1ce8835a27 100644 --- a/plugins/telemetry/telemetry.go +++ b/plugins/telemetry/telemetry.go @@ -41,6 +41,7 @@ import ( _ "go.ligato.io/vpp-agent/v3/plugins/telemetry/vppcalls/vpp2202" _ "go.ligato.io/vpp-agent/v3/plugins/telemetry/vppcalls/vpp2210" + _ "go.ligato.io/vpp-agent/v3/plugins/telemetry/vppcalls/vpp2306" ) var debug = os.Getenv("DEBUG_TELEMETRY") != "" diff --git a/plugins/telemetry/vppcalls/vpp2306/telemetry_vppcalls.go b/plugins/telemetry/vppcalls/vpp2306/telemetry_vppcalls.go new file mode 100644 index 0000000000..cacaa5cde4 --- /dev/null +++ b/plugins/telemetry/vppcalls/vpp2306/telemetry_vppcalls.go @@ -0,0 +1,314 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "context" + "fmt" + "regexp" + "strconv" + "strings" + + govppapi "go.fd.io/govpp/api" + + "go.ligato.io/vpp-agent/v3/plugins/telemetry/vppcalls" +) + +func (h *TelemetryHandler) GetSystemStats(context.Context) (*govppapi.SystemStats, error) { + return nil, nil +} + +var ( + // Regular expression to parse output from `show memory` + memoryRe = regexp.MustCompile( + `Thread\s+(\d+)\s+(\w+).?\s+` + + `base 0x[0-9a-f]+, size ([\dkmg\.]+), (?:(?:locked|unmap-on-destroy)[,\s]+)*name '[-\w\s]*'\s+` + + `page stats: page-size ([\dkmgKMG\.]+), total ([\dkmg\.]+), mapped [\dkmg\.]+, not-mapped [\dkmg\.]+(?:, unknown [\dkmg\.]+)?\s+` + + `(?:(?:\s+numa [\d]+: [\dkmg\.]+ pages, [\dkmg\.]+ bytes\s+)*\s+)*` + + `\s+total: ([\dkmgKMG\.]+), used: ([\dkmgKMG\.]+), free: ([\dkmgKMG\.]+), trimmable: ([\dkmgKMG\.]+)\s+` + + `free chunks (\d+)\s+free fastbin blks (\d+)\s+max total allocated\s+([\dkmgKMG\.]+)`, + ) +) + +// GetMemory retrieves `show memory` info. +func (h *TelemetryHandler) GetMemory(ctx context.Context) (*vppcalls.MemoryInfo, error) { + input, err := h.vpe.RunCli(context.TODO(), "show memory main-heap verbose") + if err != nil { + return nil, err + } + + threadMatches := memoryRe.FindAllStringSubmatch(input, -1) + + if len(threadMatches) == 0 && input != "" { + return nil, fmt.Errorf("invalid memory input: %q", input) + } + + var threads []vppcalls.MemoryThread + for _, matches := range threadMatches { + fields := matches[1:] + if len(fields) != 12 { + return nil, fmt.Errorf("invalid memory data %v for thread: %q", fields, matches[0]) + } + id, err := strconv.ParseUint(fields[0], 10, 64) + if err != nil { + return nil, err + } + thread := &vppcalls.MemoryThread{ + ID: uint(id), + Name: fields[1], + Size: strToUint64(fields[2]), + PageSize: strToUint64(fields[3]), + Pages: strToUint64(fields[4]), + Total: strToUint64(fields[5]), + Used: strToUint64(fields[6]), + Free: strToUint64(fields[7]), + Trimmable: strToUint64(fields[8]), + FreeChunks: strToUint64(fields[9]), + FreeFastbinBlks: strToUint64(fields[10]), + MaxTotalAlloc: strToUint64(fields[11]), + } + threads = append(threads, *thread) + } + + info := &vppcalls.MemoryInfo{ + Threads: threads, + } + + return info, nil +} + +func (h *TelemetryHandler) GetInterfaceStats(context.Context) (*govppapi.InterfaceStats, error) { + return nil, nil +} + +var ( + // Regular expression to parse output from `show node counters` + nodeCountersRe = regexp.MustCompile(`^\s+(\d+)\s+([\w-\/]+)\s+(.+)$`) +) + +// GetNodeCounters retrieves node counters info. +func (h *TelemetryHandler) GetNodeCounters(ctx context.Context) (*vppcalls.NodeCounterInfo, error) { + data, err := h.vpe.RunCli(context.TODO(), "show node counters") + if err != nil { + return nil, err + } + + var counters []vppcalls.NodeCounter + + for i, line := range strings.Split(string(data), "\n") { + // Skip empty lines + if strings.TrimSpace(line) == "" { + continue + } + // Check first line + if i == 0 { + fields := strings.Fields(line) + // Verify header + if len(fields) != 3 || fields[0] != "Count" { + return nil, fmt.Errorf("invalid header for `show node counters` received: %q", line) + } + continue + } + + // Parse lines using regexp + matches := nodeCountersRe.FindStringSubmatch(line) + if len(matches)-1 != 3 { + return nil, fmt.Errorf("parsing failed for `show node counters` line: %q", line) + } + fields := matches[1:] + + counters = append(counters, vppcalls.NodeCounter{ + Value: strToUint64(fields[0]), + Node: fields[1], + Name: fields[2], + }) + } + + info := &vppcalls.NodeCounterInfo{ + Counters: counters, + } + + return info, nil +} + +var ( + // Regular expression to parse output from `show runtime` + runtimeRe = regexp.MustCompile(`(?:-+\n)?(?:Thread (\d+) (\w+)(?: \(lcore \d+\))?\n)?` + + `Time ([0-9\.e-]+), average vectors/node ([0-9\.e-]+), last (\d+) main loops ([0-9\.e-]+) per node ([0-9\.e-]+)\s+` + + `vector rates in ([0-9\.e-]+), out ([0-9\.e-]+), drop ([0-9\.e-]+), punt ([0-9\.e-]+)\n` + + `\s+Name\s+State\s+Calls\s+Vectors\s+Suspends\s+Clocks\s+Vectors/Call\s+(?:Perf Ticks\s+)?` + + `((?:[\w-:\.]+\s+\w+(?:[ -]\w+)*\s+\d+\s+\d+\s+\d+\s+[0-9\.e-]+\s+[0-9\.e-]+\s+)+)`) + runtimeItemsRe = regexp.MustCompile(`([\w-:\.]+)\s+(\w+(?:[ -]\w+)*)\s+(\d+)\s+(\d+)\s+(\d+)\s+([0-9\.e-]+)\s+([0-9\.e-]+)\s+`) +) + +// GetRuntimeInfo retrieves how runtime info. +func (h *TelemetryHandler) GetRuntimeInfo(ctx context.Context) (*vppcalls.RuntimeInfo, error) { + input, err := h.vpe.RunCli(context.TODO(), "show runtime") + if err != nil { + return nil, err + } + + threadMatches := runtimeRe.FindAllStringSubmatch(input, -1) + + if len(threadMatches) == 0 && input != "" { + return nil, fmt.Errorf("invalid runtime input: %q", input) + } + + var threads []vppcalls.RuntimeThread + for _, matches := range threadMatches { + fields := matches[1:] + if len(fields) != 12 { + return nil, fmt.Errorf("invalid runtime data for thread (len=%v): %q", len(fields), matches[0]) + } + thread := vppcalls.RuntimeThread{ + ID: uint(strToUint64(fields[0])), + Name: fields[1], + Time: strToFloat64(fields[2]), + AvgVectorsPerNode: strToFloat64(fields[3]), + LastMainLoops: strToUint64(fields[4]), + VectorsPerMainLoop: strToFloat64(fields[5]), + VectorLengthPerNode: strToFloat64(fields[6]), + VectorRatesIn: strToFloat64(fields[7]), + VectorRatesOut: strToFloat64(fields[8]), + VectorRatesDrop: strToFloat64(fields[9]), + VectorRatesPunt: strToFloat64(fields[10]), + } + + itemMatches := runtimeItemsRe.FindAllStringSubmatch(fields[11], -1) + for _, matches := range itemMatches { + fields := matches[1:] + if len(fields) != 7 { + return nil, fmt.Errorf("invalid runtime data for thread item: %q", matches[0]) + } + thread.Items = append(thread.Items, vppcalls.RuntimeItem{ + Name: fields[0], + State: fields[1], + Calls: strToUint64(fields[2]), + Vectors: strToUint64(fields[3]), + Suspends: strToUint64(fields[4]), + Clocks: strToFloat64(fields[5]), + VectorsPerCall: strToFloat64(fields[6]), + }) + } + + threads = append(threads, thread) + } + + info := &vppcalls.RuntimeInfo{ + Threads: threads, + } + + return info, nil +} + +var ( + // Regular expression to parse output from `show buffers` + buffersRe = regexp.MustCompile( + `^(\w+(?:[ \-]\w+)*)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+([\dkmg\.]+)\s+([\dkmg\.]+)\s+([\dkmg\.]+)\s+([\dkmg\.]+)(?:\s+)?$`, + ) +) + +// GetBuffersInfo retrieves buffers info from VPP. +func (h *TelemetryHandler) GetBuffersInfo(ctx context.Context) (*vppcalls.BuffersInfo, error) { + data, err := h.vpe.RunCli(context.TODO(), "show buffers") + if err != nil { + return nil, err + } + + var items []vppcalls.BuffersItem + + for i, line := range strings.Split(string(data), "\n") { + // Skip empty lines + if strings.TrimSpace(line) == "" { + continue + } + // Check first line + if i == 0 { + fields := strings.Fields(line) + // Verify header + if len(fields) != 11 || fields[0] != "Pool" { + return nil, fmt.Errorf("invalid header for `show buffers` received: %q", line) + } + continue + } + + // Parse lines using regexp + matches := buffersRe.FindStringSubmatch(line) + if len(matches)-1 != 9 { + return nil, fmt.Errorf("parsing failed (%d matches) for `show buffers` line: %q", len(matches), line) + } + fields := matches[1:] + + items = append(items, vppcalls.BuffersItem{ + // ThreadID: uint(strToUint64(fields[0])), + Name: fields[0], + Index: uint(strToUint64(fields[1])), + Size: strToUint64(fields[3]), + Alloc: strToUint64(fields[7]), + Free: strToUint64(fields[5]), + // NumAlloc: strToUint64(fields[6]), + // NumFree: strToUint64(fields[7]), + }) + } + + info := &vppcalls.BuffersInfo{ + Items: items, + } + + return info, nil +} + +// GetThreads retrieves info about the VPP threads +func (h *TelemetryHandler) GetThreads(ctx context.Context) (*vppcalls.ThreadsInfo, error) { + threads, err := h.vpe.GetThreads(ctx) + if err != nil { + return nil, err + } + var items []vppcalls.ThreadsItem + for _, thread := range threads { + items = append(items, vppcalls.ThreadsItem{ + Name: thread.Name, + ID: thread.ID, + Type: thread.Type, + PID: thread.PID, + CPUID: thread.CPUID, + Core: thread.Core, + CPUSocket: thread.CPUSocket, + }) + } + return &vppcalls.ThreadsInfo{ + Items: items, + }, err +} + +func strToFloat64(s string) float64 { + // Replace 'k' (thousands) with 'e3' to make it parsable with strconv + s = strings.Replace(s, "k", "e3", 1) + s = strings.Replace(s, "K", "e3", 1) + s = strings.Replace(s, "m", "e6", 1) + s = strings.Replace(s, "M", "e6", 1) + s = strings.Replace(s, "g", "e9", 1) + s = strings.Replace(s, "G", "e9", 1) + + num, err := strconv.ParseFloat(s, 64) + if err != nil { + return 0 + } + return num +} + +func strToUint64(s string) uint64 { + return uint64(strToFloat64(s)) +} diff --git a/plugins/telemetry/vppcalls/vpp2306/telemetry_vppcalls_test.go b/plugins/telemetry/vppcalls/vpp2306/telemetry_vppcalls_test.go new file mode 100644 index 0000000000..cc55ed821c --- /dev/null +++ b/plugins/telemetry/vppcalls/vpp2306/telemetry_vppcalls_test.go @@ -0,0 +1,527 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306_test + +import ( + "context" + "testing" + + . "github.com/onsi/gomega" + + "go.ligato.io/vpp-agent/v3/plugins/telemetry/vppcalls" + "go.ligato.io/vpp-agent/v3/plugins/telemetry/vppcalls/vpp2306" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/vlib" + "go.ligato.io/vpp-agent/v3/plugins/vpp/vppmock" +) + +func TestGetBuffers(t *testing.T) { + ctx, handler := testSetup(t) + defer ctx.TeardownTestCtx() + + const reply = `Pool Name Index NUMA Size Data Size Total Avail Cached Used +default-numa-0 0 0 2304 2048 17290 17290 0 0 ` + ctx.MockVpp.MockReply(&vlib.CliInbandReply{ + Reply: reply, + }) + + info, err := handler.GetBuffersInfo(context.TODO()) + + Expect(err).ShouldNot(HaveOccurred()) + Expect(info.Items).To(HaveLen(1)) + Expect(info.Items[0]).To(Equal(vppcalls.BuffersItem{ + //ThreadID: 0, + Name: "default-numa-0", + Index: 0, + Size: 2304, + Alloc: 0, + Free: 17290, + //NumAlloc: 256, + //NumFree: 19, + })) + /*Expect(info.Items[1]).To(Equal(vppcalls.BuffersItem{ + ThreadID: 0, + Name: "lacp-ethernet", + Index: 1, + Size: 256, + Alloc: 1130000, + Free: 27000, + NumAlloc: 512, + NumFree: 12, + })) + Expect(info.Items[2]).To(Equal(vppcalls.BuffersItem{ + ThreadID: 0, + Name: "marker-ethernet", + Index: 2, + Size: 256, + Alloc: 1110000000, + Free: 0, + NumAlloc: 0, + NumFree: 0, + }))*/ +} + +func TestGetRuntime(t *testing.T) { + tests := []struct { + name string + reply string + threadCount int + itemCount int + itemIdx int + item vppcalls.RuntimeItem + }{ + { + name: "19.08", + reply: `Time 84714.7, average vectors/node 0.00, last 128 main loops 0.00 per node 0.00 + vector rates in 0.0000e0, out 0.0000e0, drop 0.0000e0, punt 0.0000e0 + Name State Calls Vectors Suspends Clocks Vectors/Call +acl-plugin-fa-cleaner-process event wait 6 5 1 1.10e4 0.00 +api-rx-from-ring active 0 0 7870 8.63e5 0.00 +avf-process event wait 0 0 1 4.53e3 0.00 +bfd-process event wait 0 0 1 7.01e3 0.00 +bond-process event wait 0 0 1 2.95e3 0.00 +cdp-process any wait 0 0 1 5.46e3 0.00 +dhcp-client-process any wait 0 0 847 6.63e3 0.00 +dhcp6-client-cp-process any wait 0 0 1 1.52e3 0.00 +dhcp6-pd-client-cp-process any wait 0 0 1 1.71e3 0.00 +dhcp6-pd-reply-publisher-proce event wait 0 0 1 9.73e2 0.00 +dhcp6-reply-publisher-process event wait 0 0 1 9.12e2 0.00 +dns-resolver-process any wait 0 0 85 8.98e3 0.00 +fib-walk any wait 0 0 42247 1.08e4 0.00 +flow-report-process any wait 0 0 1 1.33e3 0.00 +flowprobe-timer-process any wait 0 0 1 5.18e3 0.00 +gbp-scanner event wait 0 0 1 5.17e3 0.00 +igmp-timer-process event wait 0 0 1 6.53e3 0.00 +ikev2-manager-process any wait 0 0 84353 7.84e3 0.00 +ioam-export-process any wait 0 0 1 1.64e3 0.00 +ip-neighbor-scan-process any wait 0 0 1412 9.65e3 0.00 +ip-route-resolver-process any wait 0 0 847 6.12e3 0.00 +ip4-reassembly-expire-walk any wait 0 0 8464 6.92e3 0.00 +ip6-icmp-neighbor-discovery-ev any wait 0 0 84353 8.58e3 0.00 +ip6-reassembly-expire-walk any wait 0 0 8464 6.67e3 0.00 +l2fib-mac-age-scanner-process event wait 0 0 1 1.98e3 0.00 +lacp-process event wait 0 0 1 1.58e5 0.00 +lisp-retry-service any wait 0 0 42247 1.08e4 0.00 +lldp-process event wait 0 0 1 8.76e4 0.00 +memif-process event wait 0 0 1 9.34e3 0.00 +nat-det-expire-walk done 1 0 0 2.92e3 0.00 +nat-ha-process event wait 0 0 1 4.12e3 0.00 +nat64-expire-walk event wait 0 0 1 2.41e3 0.00 +nsh-md2-ioam-export-process any wait 0 0 1 1.10e4 0.00 +perfmon-periodic-process event wait 0 0 1 3.61e7 0.00 +rd-cp-process any wait 0 0 1 1.55e3 0.00 +send-dhcp6-client-message-proc any wait 0 0 1 2.22e3 0.00 +send-dhcp6-pd-client-message-p any wait 0 0 1 1.43e3 0.00 +send-rs-process any wait 0 0 1 1.49e3 0.00 +startup-config-process done 1 0 1 5.68e3 0.00 +statseg-collector-process time wait 0 0 8464 2.79e5 0.00 +udp-ping-process any wait 0 0 1 6.96e3 0.00 +unix-cli-127.0.0.1:mdns done 2 0 4 2.14e9 0.00 +unix-epoll-input polling 20325059 0 0 1.13e7 0.00 +vhost-user-process any wait 0 0 1 3.73e3 0.00 +vhost-user-send-interrupt-proc any wait 0 0 1 1.28e3 0.00 +vpe-link-state-process event wait 0 0 1 9.63e2 0.00 +vpe-oam-process any wait 0 0 41419 9.59e3 0.00 +vxlan-gpe-ioam-export-process any wait 0 0 1 1.60e3 0.00 +wildcard-ip4-arp-publisher-pro event wait 0 0 1 1.44e3 0.00 +`, + threadCount: 1, + itemCount: 49, + item: vppcalls.RuntimeItem{ + Name: "acl-plugin-fa-cleaner-process", + State: "event wait", + Calls: 6, + Vectors: 5, + Suspends: 1, + Clocks: 1.10e4, + VectorsPerCall: 0, + }, + }, + { + name: "one thread", + reply: `Time 3151.2, average vectors/node 1.00, last 128 main loops 0.00 per node 0.00 + vector rates in 2.8561e-3, out 0.0000e0, drop 4.4428e-3, punt 0.0000e0 + Name State Calls Vectors Suspends Clocks Vectors/Call Perf Ticks +acl-plugin-fa-cleaner-process event wait 0 0 1 5.14e3 0.00 +af-packet-input interrupt wa 9 9 0 1.55e5 1.00 +api-rx-from-ring any wait 0 0 4735 4.72e6 0.00 +avf-process event wait 0 0 1 4.52e3 0.00 +bfd-process event wait 0 0 1 6.59e3 0.00 +bond-process event wait 0 0 1 2.07e3 0.00 +cdp-process any wait 0 0 1 4.43e3 0.00 +dhcp-client-process any wait 0 0 32 8.73e3 0.00 +dhcp6-client-cp-process any wait 0 0 1 1.94e3 0.00 +dhcp6-pd-client-cp-process any wait 0 0 1 1.73e3 0.00 +dhcp6-pd-reply-publisher-proce event wait 0 0 1 1.01e3 0.00 +dhcp6-reply-publisher-process event wait 0 0 1 8.75e2 0.00 +dns-resolver-process any wait 0 0 4 2.11e4 0.00 +error-drop active 14 14 0 1.29e5 1.00 +ethernet-input active 9 9 0 6.41e5 1.00 +fib-walk any wait 0 0 1571 2.12e4 0.00 +flow-report-process any wait 0 0 1 1.13e3 0.00 +flowprobe-timer-process any wait 0 0 1 5.27e3 0.00 +gbp-scanner event wait 0 0 1 5.36e3 0.00 +igmp-timer-process event wait 0 0 1 5.24e4 0.00 +ikev2-manager-process any wait 0 0 3132 1.32e4 0.00 +ioam-export-process any wait 0 0 1 1.18e3 0.00 +ip-neighbor-scan-process any wait 0 0 53 1.49e4 0.00 +ip-route-resolver-process any wait 0 0 32 5.80e3 0.00 +ip4-drop active 5 5 0 3.13e3 1.00 +ip4-local active 5 5 0 1.00e4 1.00 +ip4-lookup active 5 5 0 1.08e6 1.00 +ip4-reassembly-expire-walk any wait 0 0 315 1.27e4 0.00 +ip6-icmp-neighbor-discovery-ev any wait 0 0 3132 1.12e4 0.00 +ip6-input active 9 9 0 3.41e3 1.00 +ip6-not-enabled active 9 9 0 1.47e3 1.00 +ip6-reassembly-expire-walk any wait 0 0 315 8.52e3 0.00 +l2fib-mac-age-scanner-process event wait 0 0 1 1.18e3 0.00 +lacp-process event wait 0 0 1 1.84e5 0.00 +lisp-retry-service any wait 0 0 1571 1.49e4 0.00 +lldp-process event wait 0 0 1 5.81e5 0.00 +memif-process any wait 0 0 1168 1.11e5 0.00 +nat-det-expire-walk done 1 0 0 2.50e3 0.00 +nat64-expire-walk event wait 0 0 1 1.34e4 0.00 +nsh-md2-ioam-export-process any wait 0 0 1 7.89e3 0.00 +perfmon-periodic-process event wait 0 0 1 1.18e8 0.00 +rd-cp-process any wait 0 0 1 1.52e3 0.00 +send-dhcp6-client-message-proc any wait 0 0 1 1.56e3 0.00 +send-dhcp6-pd-client-message-p any wait 0 0 1 1.53e3 0.00 +send-rs-process any wait 0 0 1 1.69e3 0.00 +startup-config-process done 1 0 1 6.13e3 0.00 +statseg-collector-process time wait 0 0 315 3.77e5 0.00 +udp-ping-process any wait 0 0 1 1.62e4 0.00 +unix-cli-127.0.0.1:39670 event wait 0 0 103 2.26e7 0.00 +unix-cli-127.0.0.1:40652 active 1 0 3 4.64e9 0.00 +unix-epoll-input polling 1698354 0 0 5.00e6 0.00 +vhost-user-process any wait 0 0 1 5.29e3 0.00 +vhost-user-send-interrupt-proc any wait 0 0 1 1.88e3 0.00 +vpe-link-state-process event wait 0 0 15 2.33e4 0.00 +vpe-oam-process any wait 0 0 1540 1.21e4 0.00 +vxlan-gpe-ioam-export-process any wait 0 0 1 1.38e3 0.00 +wildcard-ip4-arp-publisher-pro event wait 0 0 1 2.24e3 0.00 +`, + threadCount: 1, + itemCount: 57, + itemIdx: 1, + item: vppcalls.RuntimeItem{ + Name: "af-packet-input", + State: "interrupt wa", + Calls: 9, + Vectors: 9, + Suspends: 0, + Clocks: 1.55e5, + VectorsPerCall: 1, + }, + }, + { + name: "three threads", + reply: `Thread 0 vpp_main (lcore 0) +Time 21.5, average vectors/node 0.00, last 128 main loops 0.00 per node 0.00 + vector rates in 0.0000e0, out 5.0000e-2, drop 0.0000e0, punt 0.0000e0 + Name State Calls Vectors Suspends Clocks Vectors/Call +acl-plugin-fa-cleaner-process event wait 6 5 1 3.12e4 0.00 +api-rx-from-ring any wait 0 0 31 8.61e6 0.00 +avf-process event wait 0 0 1 7.79e3 0.00 +bfd-process event wait 0 0 1 6.80e3 0.00 +cdp-process any wait 0 0 1 1.78e8 0.00 +dhcp-client-process any wait 0 0 1 2.59e3 0.00 +dns-resolver-process any wait 0 0 1 3.35e3 0.00 +fib-walk any wait 0 0 11 1.08e4 0.00 +flow-report-process any wait 0 0 1 1.64e3 0.00 +flowprobe-timer-process any wait 0 0 1 1.16e4 0.00 +igmp-timer-process event wait 0 0 1 1.81e4 0.00 +ikev2-manager-process any wait 0 0 22 5.47e3 0.00 +ioam-export-process any wait 0 0 1 3.26e3 0.00 +ip-route-resolver-process any wait 0 0 1 1.69e3 0.00 +ip4-reassembly-expire-walk any wait 0 0 3 4.27e3 0.00 +ip6-icmp-neighbor-discovery-ev any wait 0 0 22 4.48e3 0.00 +ip6-reassembly-expire-walk any wait 0 0 3 6.88e3 0.00 +l2fib-mac-age-scanner-process event wait 0 0 1 3.94e3 0.00 +lacp-process event wait 0 0 1 1.35e8 0.00 +lisp-retry-service any wait 0 0 11 9.68e3 0.00 +lldp-process event wait 0 0 1 1.49e8 0.00 +memif-process event wait 0 0 1 2.67e4 0.00 +nat-det-expire-walk done 1 0 0 5.42e3 0.00 +nat64-expire-walk event wait 0 0 1 5.87e4 0.00 +rd-cp-process any wait 0 0 614363 3.93e2 0.00 +send-rs-process any wait 0 0 1 3.22e3 0.00 +startup-config-process done 1 0 1 1.33e4 0.00 +udp-ping-process any wait 0 0 1 3.69e4 0.00 +unix-cli-127.0.0.1:38448 active 0 0 23 6.72e7 0.00 +unix-epoll-input polling 8550283 0 0 3.77e3 0.00 +vhost-user-process any wait 0 0 1 2.48e3 0.00 +vhost-user-send-interrupt-proc any wait 0 0 1 1.43e3 0.00 +vpe-link-state-process event wait 0 0 1 1.58e3 0.00 +vpe-oam-process any wait 0 0 11 9.20e3 0.00 +vxlan-gpe-ioam-export-process any wait 0 0 1 1.59e4 0.00 +wildcard-ip4-arp-publisher-pro event wait 0 0 1 1.03e4 0.00 +--------------- +Thread 1 vpp_wk_0 (lcore 1) +Time 21.5, average vectors/node 0.00, last 128 main loops 0.00 per node 0.00 + vector rates in 0.0000e0, out 0.0000e0, drop 0.0000e0, punt 0.0000e0 + Name State Calls Vectors Suspends Clocks Vectors/Call +unix-epoll-input polling 15251181 0 0 3.67e3 0.00 +--------------- +Thread 2 vpp_wk_1 (lcore 2) +Time 21.5, average vectors/node 0.00, last 128 main loops 0.00 per node 0.00 + vector rates in 0.0000e0, out 0.0000e0, drop 0.0000e0, punt 0.0000e0 + Name State Calls Vectors Suspends Clocks Vectors/Call +unix-epoll-input polling 20563870 0 0 3.56e3 0.00 +`, + threadCount: 3, + itemCount: 36, + item: vppcalls.RuntimeItem{ + Name: "acl-plugin-fa-cleaner-process", + State: "event wait", + Calls: 6, + Vectors: 5, + Suspends: 1, + Clocks: 3.12e4, + VectorsPerCall: 0, + }, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + ctx, handler := testSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vlib.CliInbandReply{Reply: test.reply}) + + info, err := handler.GetRuntimeInfo(context.TODO()) + + Expect(err).ShouldNot(HaveOccurred()) + Expect(len(info.Threads)).To(Equal(test.threadCount)) + Expect(info.Threads[0].Items).To(HaveLen(test.itemCount)) + Expect(info.Threads[0].Items[test.itemIdx]).To(Equal(test.item)) + }) + } +} + +func TestGetMemory(t *testing.T) { + tests := []struct { + name string + reply string + threadCount int + threadIdx int + thread vppcalls.MemoryThread + }{ + { + name: "single", + reply: `Thread 0 vpp_main + base 0x7f74752f2000, size 1g, locked, unmap-on-destroy, name 'main heap' + page stats: page-size 4K, total 262144, mapped 14970, not-mapped 247174 + numa 0: 14970 pages, 58.48m bytes + total: 1023.99M, used: 55.46M, free: 968.54M, trimmable: 968.53M + free chunks 310 free fastbin blks 0 + max total allocated 1023.99M +`, + threadCount: 1, + threadIdx: 0, + thread: vppcalls.MemoryThread{ + ID: 0, + Name: "vpp_main", + Size: 1e9, + Pages: 262144, + PageSize: 4000, + Used: 55.46e6, + Total: 1023.99e6, + Free: 968.54e6, + Trimmable: 968.53e6, + FreeChunks: 310, + FreeFastbinBlks: 0, + MaxTotalAlloc: 1023.99e6, + }, + }, + { + name: "unknown", + reply: `Thread 0 vpp_main + base 0x7ff4bf55f000, size 1g, locked, unmap-on-destroy, name 'main heap' + page stats: page-size 4K, total 262144, mapped 14945, not-mapped 247174, unknown 25 + numa 0: 14945 pages, 58.38m bytes + total: 1023.99M, used: 55.46M, free: 968.54M, trimmable: 968.53M + free chunks 303 free fastbin blks 0 + max total allocated 1023.99M +`, + threadCount: 1, + threadIdx: 0, + thread: vppcalls.MemoryThread{ + ID: 0, + Name: "vpp_main", + Size: 1e9, + Pages: 262144, + PageSize: 4000, + Used: 55.46e6, + Total: 1023.99e6, + Free: 968.54e6, + Trimmable: 968.53e6, + FreeChunks: 303, + FreeFastbinBlks: 0, + MaxTotalAlloc: 1023.99e6, + }, + }, + { + name: "3 workers", + reply: `Thread 0 vpp_main + base 0x7f0f14823000, size 1g, locked, unmap-on-destroy, name 'main heap' + page stats: page-size 4K, total 262144, mapped 19483, not-mapped 242661 + numa 0: 19483 pages, 76.11m bytes + total: 1023.99M, used: 72.26M, free: 951.74M, trimmable: 950.90M + free chunks 298 free fastbin blks 0 + max total allocated 1023.99M + +Thread 1 vpp_wk_0 + base 0x7f0f14823000, size 1g, locked, unmap-on-destroy, name 'main heap' + page stats: page-size 4K, total 262144, mapped 19483, not-mapped 242661 + numa 0: 19483 pages, 76.11m bytes + total: 1023.99M, used: 72.26M, free: 951.74M, trimmable: 950.90M + free chunks 299 free fastbin blks 0 + max total allocated 1023.99M + +Thread 2 vpp_wk_1 + base 0x7f0f14823000, size 1g, locked, unmap-on-destroy, name 'main heap' + page stats: page-size 4K, total 262144, mapped 19483, not-mapped 242661 + numa 0: 19483 pages, 76.11m bytes + total: 1023.99M, used: 72.26M, free: 951.74M, trimmable: 950.90M + free chunks 299 free fastbin blks 0 + max total allocated 1023.99M + +Thread 3 vpp_wk_2 + base 0x7f0f14823000, size 1g, locked, unmap-on-destroy, name 'main heap' + page stats: page-size 4K, total 262144, mapped 19483, not-mapped 242661 + numa 0: 19483 pages, 76.11m bytes + total: 1023.99M, used: 72.26M, free: 951.74M, trimmable: 950.90M + free chunks 299 free fastbin blks 0 + max total allocated 1023.99M +`, + threadCount: 4, + threadIdx: 1, + thread: vppcalls.MemoryThread{ + ID: 1, + Name: "vpp_wk_0", + Size: 1.e9, + Pages: 262144, + PageSize: 4000, + Used: 72.26e6, + Total: 1023.99e6, + Free: 951.74e6, + Trimmable: 950.90e6, + FreeChunks: 299, + FreeFastbinBlks: 0, + MaxTotalAlloc: 1023.99e6, + }, + }, + // "19.08 update" test case tests for "page information not available" error. + // It contains reply from VPP version 20.09. The format of replies changed + // since VPP version 21.01, so the test case should be updated accordingly. + // + // { + // name: "19.08 update", + // // reply: `Thread 0 vpp_main + // // virtual memory start 0x7fc363c20000, size 1048640k, 262160 pages, page size 4k + // // page information not available (errno 1) + // // total: 1.00G, used: 56.78M, free: 967.29M, trimmable: 966.64M + // // free chunks 337 free fastbin blks 0 + // // max total allocated 1.00G + // //`, + // threadCount: 1, + // threadIdx: 0, + // thread: vppcalls.MemoryThread{ + // ID: 0, + // Name: "vpp_main", + // Size: 1048.64e6, + // Pages: 262160, + // PageSize: 4000, + // Used: 56.78e6, + // Total: 1e9, + // Free: 967.29e6, + // Trimmable: 966.64e6, + // FreeChunks: 337, + // FreeFastbinBlks: 0, + // MaxTotalAlloc: 1e9, + // }, + // }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + ctx, handler := testSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vlib.CliInbandReply{Reply: test.reply}) + + info, err := handler.GetMemory(context.TODO()) + + Expect(err).ShouldNot(HaveOccurred()) + Expect(info.Threads).To(HaveLen(test.threadCount)) + Expect(info.Threads[test.threadIdx]).To(Equal(test.thread)) + }) + } +} + +func TestGetNodeCounters(t *testing.T) { + ctx, handler := testSetup(t) + defer ctx.TeardownTestCtx() + + const reply = ` Count Node Reason + 32 ipsec-output-ip4 IPSec policy protect + 32 esp-encrypt ESP pkts received + 64 ipsec-input-ip4 IPSEC pkts received + 32 ip4-icmp-input unknown type + 32 ip4-icmp-input echo replies sent + 14 ethernet-input l3 mac mismatch + 1 arp-input ARP replies sent + 4 ip4-input ip4 spoofed local-address packet drops + 2 memif1/1-output interface is down + 1 cdp-input good cdp packets (processed) +` + ctx.MockVpp.MockReply(&vlib.CliInbandReply{ + Reply: reply, + }) + + info, err := handler.GetNodeCounters(context.TODO()) + + Expect(err).ShouldNot(HaveOccurred()) + Expect(info.Counters).To(HaveLen(10)) + Expect(info.Counters[0]).To(Equal(vppcalls.NodeCounter{ + Value: 32, + Node: "ipsec-output-ip4", + Name: "IPSec policy protect", + })) + Expect(info.Counters[6]).To(Equal(vppcalls.NodeCounter{ + Value: 1, + Node: "arp-input", + Name: "ARP replies sent", + })) + Expect(info.Counters[7]).To(Equal(vppcalls.NodeCounter{ + Value: 4, + Node: "ip4-input", + Name: "ip4 spoofed local-address packet drops", + })) + Expect(info.Counters[8]).To(Equal(vppcalls.NodeCounter{ + Value: 2, + Node: "memif1/1-output", + Name: "interface is down", + })) + Expect(info.Counters[9]).To(Equal(vppcalls.NodeCounter{ + Value: 1, + Node: "cdp-input", + Name: "good cdp packets (processed)", + })) +} + +func testSetup(t *testing.T) (*vppmock.TestCtx, vppcalls.TelemetryVppAPI) { + ctx := vppmock.SetupTestCtx(t) + handler := vpp2306.NewTelemetryVppHandler(ctx.MockVPPClient) + return ctx, handler +} diff --git a/plugins/telemetry/vppcalls/vpp2306/vppcalls_handler.go b/plugins/telemetry/vppcalls/vpp2306/vppcalls_handler.go new file mode 100644 index 0000000000..f6b832d7b6 --- /dev/null +++ b/plugins/telemetry/vppcalls/vpp2306/vppcalls_handler.go @@ -0,0 +1,43 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + vpe_vppcalls "go.ligato.io/vpp-agent/v3/plugins/govppmux/vppcalls" + vpe_vpp2306 "go.ligato.io/vpp-agent/v3/plugins/govppmux/vppcalls/vpp2306" + "go.ligato.io/vpp-agent/v3/plugins/telemetry/vppcalls" + "go.ligato.io/vpp-agent/v3/plugins/vpp" + vpp2306 "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/memclnt" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/vpe" +) + +func init() { + msgs := vpp.Messages( + vpe.AllMessages, + memclnt.AllMessages, + ) + vppcalls.AddHandlerVersion(vpp2306.Version, msgs.AllMessages(), NewTelemetryVppHandler) +} + +type TelemetryHandler struct { + vpe vpe_vppcalls.VppCoreAPI +} + +func NewTelemetryVppHandler(c vpp.Client) vppcalls.TelemetryVppAPI { + return &TelemetryHandler{ + vpe: vpe_vpp2306.NewVpeHandler(c), + } +} diff --git a/plugins/vpp/abfplugin/abfplugin.go b/plugins/vpp/abfplugin/abfplugin.go index a5fe5d758e..a924ec4922 100644 --- a/plugins/vpp/abfplugin/abfplugin.go +++ b/plugins/vpp/abfplugin/abfplugin.go @@ -31,6 +31,7 @@ import ( _ "go.ligato.io/vpp-agent/v3/plugins/vpp/abfplugin/vppcalls/vpp2202" _ "go.ligato.io/vpp-agent/v3/plugins/vpp/abfplugin/vppcalls/vpp2210" + _ "go.ligato.io/vpp-agent/v3/plugins/vpp/abfplugin/vppcalls/vpp2306" ) // ABFPlugin is a plugin that manages ACL-based forwarding. diff --git a/plugins/vpp/abfplugin/vppcalls/vpp2306/abf_vppcalls.go b/plugins/vpp/abfplugin/vppcalls/vpp2306/abf_vppcalls.go new file mode 100644 index 0000000000..8448bb5002 --- /dev/null +++ b/plugins/vpp/abfplugin/vppcalls/vpp2306/abf_vppcalls.go @@ -0,0 +1,185 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "fmt" + "net" + + "github.com/go-errors/errors" + + vpp_abf "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/abf" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/fib_types" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface_types" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip_types" + abf "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/abf" +) + +const ( + // NextHopViaLabelUnset constant has to be assigned into the field next hop via label + // in abf_policy_add_del binary message if next hop via label is not defined. + NextHopViaLabelUnset uint32 = 0xfffff + 1 + + // ClassifyTableIndexUnset is a default value for field classify_table_index + // in abf_policy_add_del binary message. + ClassifyTableIndexUnset = ^uint32(0) +) + +// GetAbfVersion retrieves version of the VPP ABF plugin +func (h *ABFVppHandler) GetAbfVersion() (ver string, err error) { + req := &vpp_abf.AbfPluginGetVersion{} + reply := &vpp_abf.AbfPluginGetVersionReply{} + + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return "", err + } + + return fmt.Sprintf("%d.%d", reply.Major, reply.Minor), nil +} + +// AddAbfPolicy creates new ABF entry together with a list of forwarding paths +func (h *ABFVppHandler) AddAbfPolicy(policyID, aclID uint32, abfPaths []*abf.ABF_ForwardingPath) error { + if err := h.abfAddDelPolicy(policyID, aclID, abfPaths, true); err != nil { + return errors.Errorf("failed to add ABF policy %d (ACL: %v): %v", policyID, aclID, err) + } + return nil +} + +// DeleteAbfPolicy removes existing ABF entry +func (h *ABFVppHandler) DeleteAbfPolicy(policyID uint32, abfPaths []*abf.ABF_ForwardingPath) error { + if err := h.abfAddDelPolicy(policyID, 0, abfPaths, false); err != nil { + return errors.Errorf("failed to delete ABF policy %d: %v", policyID, err) + } + return nil +} + +// AbfAttachInterfaceIPv4 attaches IPv4 interface to the ABF +func (h *ABFVppHandler) AbfAttachInterfaceIPv4(policyID, ifIdx, priority uint32) error { + if err := h.abfAttachDetachInterface(policyID, ifIdx, priority, true, false); err != nil { + return errors.Errorf("failed to attach IPv4 interface %d to ABF policy %d: %v", ifIdx, policyID, err) + } + return nil +} + +// AbfDetachInterfaceIPv4 detaches IPV4 interface from the ABF +func (h *ABFVppHandler) AbfDetachInterfaceIPv4(policyID, ifIdx, priority uint32) error { + if err := h.abfAttachDetachInterface(policyID, ifIdx, priority, false, false); err != nil { + return errors.Errorf("failed to detach IPv4 interface %d from ABF policy %d: %v", ifIdx, policyID, err) + } + return nil +} + +// AbfAttachInterfaceIPv6 attaches IPv6 interface to the ABF +func (h *ABFVppHandler) AbfAttachInterfaceIPv6(policyID, ifIdx, priority uint32) error { + if err := h.abfAttachDetachInterface(policyID, ifIdx, priority, true, true); err != nil { + return errors.Errorf("failed to attach IPv6 interface %d to ABF policy %d: %v", ifIdx, policyID, err) + } + return nil +} + +// AbfDetachInterfaceIPv6 detaches IPv6 interface from the ABF +func (h *ABFVppHandler) AbfDetachInterfaceIPv6(policyID, ifIdx, priority uint32) error { + if err := h.abfAttachDetachInterface(policyID, ifIdx, priority, false, true); err != nil { + return errors.Errorf("failed to detach IPv6 interface %d from ABF policy %d: %v", ifIdx, policyID, err) + } + return nil +} + +func (h *ABFVppHandler) abfAttachDetachInterface(policyID, ifIdx, priority uint32, isAdd, isIPv6 bool) error { + req := &vpp_abf.AbfItfAttachAddDel{ + IsAdd: isAdd, + Attach: vpp_abf.AbfItfAttach{ + PolicyID: policyID, + SwIfIndex: interface_types.InterfaceIndex(ifIdx), + Priority: priority, + IsIPv6: isIPv6, + }, + } + reply := &vpp_abf.AbfItfAttachAddDelReply{} + + return h.callsChannel.SendRequest(req).ReceiveReply(reply) +} + +func (h *ABFVppHandler) abfAddDelPolicy(policyID, aclID uint32, abfPaths []*abf.ABF_ForwardingPath, isAdd bool) error { + req := &vpp_abf.AbfPolicyAddDel{ + IsAdd: isAdd, + Policy: vpp_abf.AbfPolicy{ + PolicyID: policyID, + ACLIndex: aclID, + Paths: h.toFibPaths(abfPaths), + NPaths: uint8(len(abfPaths)), + }, + } + reply := &vpp_abf.AbfPolicyAddDelReply{} + + return h.callsChannel.SendRequest(req).ReceiveReply(reply) +} + +func (h *ABFVppHandler) toFibPaths(abfPaths []*abf.ABF_ForwardingPath) (fibPaths []fib_types.FibPath) { + var err error + for _, abfPath := range abfPaths { + // fib path interface + ifData, exists := h.ifIndexes.LookupByName(abfPath.InterfaceName) + if !exists { + continue + } + + fibPath := fib_types.FibPath{ + SwIfIndex: ifData.SwIfIndex, + Weight: uint8(abfPath.Weight), + Preference: uint8(abfPath.Preference), + Type: setFibPathType(abfPath.Dvr), + } + if fibPath.Nh, fibPath.Proto, err = setFibPathNhAndProto(abfPath.NextHopIp); err != nil { + h.log.Errorf("ABF path next hop error: %v", err) + } + fibPaths = append(fibPaths, fibPath) + } + + return fibPaths +} + +// supported cases are DVR and normal +func setFibPathType(isDvr bool) fib_types.FibPathType { + if isDvr { + return fib_types.FIB_API_PATH_TYPE_DVR + } + return fib_types.FIB_API_PATH_TYPE_NORMAL +} + +// resolve IP address and return FIB path next hop (IP address) and IPv4/IPv6 version +func setFibPathNhAndProto(ipStr string) (nh fib_types.FibPathNh, proto fib_types.FibPathNhProto, err error) { + netIP := net.ParseIP(ipStr) + if netIP == nil { + return nh, proto, errors.Errorf("failed to parse next hop IP address %s", ipStr) + } + var au ip_types.AddressUnion + if ipv4 := netIP.To4(); ipv4 == nil { + var address ip_types.IP6Address + proto = fib_types.FIB_API_PATH_NH_PROTO_IP6 + copy(address[:], netIP[:]) + au.SetIP6(address) + } else { + var address ip_types.IP4Address + proto = fib_types.FIB_API_PATH_NH_PROTO_IP4 + copy(address[:], netIP[12:]) + au.SetIP4(address) + } + return fib_types.FibPathNh{ + Address: au, + ViaLabel: NextHopViaLabelUnset, + ClassifyTableIndex: ClassifyTableIndexUnset, + }, proto, nil +} diff --git a/plugins/vpp/abfplugin/vppcalls/vpp2306/abf_vppcalls_test.go b/plugins/vpp/abfplugin/vppcalls/vpp2306/abf_vppcalls_test.go new file mode 100644 index 0000000000..b3a5662921 --- /dev/null +++ b/plugins/vpp/abfplugin/vppcalls/vpp2306/abf_vppcalls_test.go @@ -0,0 +1,277 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "net" + "testing" + + . "github.com/onsi/gomega" + "go.ligato.io/cn-infra/v2/logging/logrus" + + "go.ligato.io/vpp-agent/v3/plugins/vpp/abfplugin/vppcalls" + "go.ligato.io/vpp-agent/v3/plugins/vpp/aclplugin/aclidx" + vpp_abf "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/abf" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip_types" + "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/ifaceidx" + "go.ligato.io/vpp-agent/v3/plugins/vpp/vppmock" + abf "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/abf" +) + +func TestGetABFVersion(t *testing.T) { + ctx, abfHandler, _ := abfTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_abf.AbfPluginGetVersionReply{ + Major: 1, + Minor: 0, + }) + version, err := abfHandler.GetAbfVersion() + + Expect(err).To(BeNil()) + Expect(version).To(Equal("1.0")) +} + +func TestAddABFPolicy(t *testing.T) { + ctx, abfHandler, ifIndexes := abfTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_abf.AbfPolicyAddDelReply{}) + + ifIndexes.Put("if1", &ifaceidx.IfaceMetadata{ + SwIfIndex: 5, + }) + ifIndexes.Put("if2", &ifaceidx.IfaceMetadata{ + SwIfIndex: 10, + }) + + err := abfHandler.AddAbfPolicy(1, 2, []*abf.ABF_ForwardingPath{ + { + InterfaceName: "if1", + NextHopIp: "10.0.0.1", + }, + { + InterfaceName: "if2", + NextHopIp: "ffff::", + }, + }) + + Expect(err).To(BeNil()) + req, ok := ctx.MockChannel.Msg.(*vpp_abf.AbfPolicyAddDel) + Expect(ok).To(BeTrue()) + Expect(req.IsAdd).To(BeTrue()) + Expect(req.Policy.PolicyID).To(Equal(uint32(1))) + Expect(req.Policy.ACLIndex).To(Equal(uint32(2))) + Expect(req.Policy.NPaths).To(Equal(uint8(2))) + Expect(req.Policy.Paths[0].SwIfIndex).To(Equal(uint32(5))) + Expect(req.Policy.Paths[0].Nh.Address.GetIP4()).To(BeEquivalentTo(ip_types.IP4Address([4]uint8{10, 0, 0, 1}))) + Expect(req.Policy.Paths[1].SwIfIndex).To(Equal(uint32(10))) + Expect(req.Policy.Paths[1].Nh.Address.GetIP6()).To(BeEquivalentTo(ip_types.IP6Address([16]uint8{255, 255}))) +} + +func TestAddABFPolicyError(t *testing.T) { + ctx, abfHandler, _ := abfTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_abf.AbfPolicyAddDelReply{ + Retval: 1, + }) + + err := abfHandler.AddAbfPolicy(1, 2, nil) + + Expect(err).ToNot(BeNil()) +} + +func TestDeleteABFPolicy(t *testing.T) { + ctx, abfHandler, ifIndexes := abfTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_abf.AbfPolicyAddDelReply{}) + + ifIndexes.Put("if1", &ifaceidx.IfaceMetadata{ + SwIfIndex: 5, + }) + ifIndexes.Put("if2", &ifaceidx.IfaceMetadata{ + SwIfIndex: 10, + }) + + err := abfHandler.DeleteAbfPolicy(1, []*abf.ABF_ForwardingPath{ + { + InterfaceName: "if1", + NextHopIp: "10.0.0.1", + }, + { + InterfaceName: "if2", + NextHopIp: "ffff::", + }, + }) + + Expect(err).To(BeNil()) + req, ok := ctx.MockChannel.Msg.(*vpp_abf.AbfPolicyAddDel) + Expect(ok).To(BeTrue()) + Expect(req.IsAdd).To(BeFalse()) + Expect(req.Policy.PolicyID).To(Equal(uint32(1))) + Expect(req.Policy.NPaths).To(Equal(uint8(2))) + Expect(req.Policy.Paths[0].SwIfIndex).To(Equal(uint32(5))) + Expect(req.Policy.Paths[0].Nh.Address.XXX_UnionData[:4]).To(BeEquivalentTo(net.ParseIP("10.0.0.1").To4())) + Expect(req.Policy.Paths[1].SwIfIndex).To(Equal(uint32(10))) + Expect(req.Policy.Paths[1].Nh.Address.XXX_UnionData[:]).To(BeEquivalentTo(net.ParseIP("ffff::").To16())) +} + +func TestDeleteABFPolicyError(t *testing.T) { + ctx, abfHandler, _ := abfTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_abf.AbfPolicyAddDelReply{ + Retval: 1, + }) + + err := abfHandler.DeleteAbfPolicy(1, nil) + + Expect(err).ToNot(BeNil()) +} + +func TestAttachABFInterfaceIPv4(t *testing.T) { + ctx, abfHandler, _ := abfTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_abf.AbfItfAttachAddDelReply{}) + + err := abfHandler.AbfAttachInterfaceIPv4(1, 2, 3) + + Expect(err).To(BeNil()) + req, ok := ctx.MockChannel.Msg.(*vpp_abf.AbfItfAttachAddDel) + Expect(ok).To(BeTrue()) + Expect(req.IsAdd).To(BeTrue()) + Expect(req.Attach.PolicyID).To(Equal(uint32(1))) + Expect(req.Attach.SwIfIndex).To(BeEquivalentTo(uint32(2))) + Expect(req.Attach.Priority).To(Equal(uint32(3))) + Expect(req.Attach.IsIPv6).To(BeFalse()) +} + +func TestAttachABFInterfaceIPv4Error(t *testing.T) { + ctx, abfHandler, _ := abfTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_abf.AbfItfAttachAddDelReply{ + Retval: -1, + }) + + err := abfHandler.AbfAttachInterfaceIPv4(1, 2, 3) + + Expect(err).ToNot(BeNil()) +} + +func TestAttachABFInterfaceIPv6(t *testing.T) { + ctx, abfHandler, _ := abfTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_abf.AbfItfAttachAddDelReply{}) + + err := abfHandler.AbfAttachInterfaceIPv6(1, 2, 3) + + Expect(err).To(BeNil()) + req, ok := ctx.MockChannel.Msg.(*vpp_abf.AbfItfAttachAddDel) + Expect(ok).To(BeTrue()) + Expect(req.IsAdd).To(BeTrue()) + Expect(req.Attach.PolicyID).To(Equal(uint32(1))) + Expect(req.Attach.SwIfIndex).To(BeEquivalentTo(uint32(2))) + Expect(req.Attach.Priority).To(Equal(uint32(3))) + Expect(req.Attach.IsIPv6).To(BeTrue()) +} + +func TestAttachABFInterfaceIPv6Error(t *testing.T) { + ctx, abfHandler, _ := abfTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_abf.AbfItfAttachAddDelReply{ + Retval: -1, + }) + + err := abfHandler.AbfAttachInterfaceIPv6(1, 2, 3) + + Expect(err).ToNot(BeNil()) +} + +func TestDetachABFInterfaceIPv4(t *testing.T) { + ctx, abfHandler, _ := abfTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_abf.AbfItfAttachAddDelReply{}) + + err := abfHandler.AbfDetachInterfaceIPv4(1, 2, 3) + + Expect(err).To(BeNil()) + req, ok := ctx.MockChannel.Msg.(*vpp_abf.AbfItfAttachAddDel) + Expect(ok).To(BeTrue()) + Expect(req.IsAdd).To(BeFalse()) + Expect(req.Attach.PolicyID).To(Equal(uint32(1))) + Expect(req.Attach.SwIfIndex).To(BeEquivalentTo(uint32(2))) + Expect(req.Attach.Priority).To(Equal(uint32(3))) + Expect(req.Attach.IsIPv6).To(BeFalse()) +} + +func TestDetachABFInterfaceIPv4Error(t *testing.T) { + ctx, abfHandler, _ := abfTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_abf.AbfItfAttachAddDelReply{ + Retval: -1, + }) + + err := abfHandler.AbfDetachInterfaceIPv4(1, 2, 3) + + Expect(err).ToNot(BeNil()) +} + +func TestDetachABFInterfaceIPv6(t *testing.T) { + ctx, abfHandler, _ := abfTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_abf.AbfItfAttachAddDelReply{}) + + err := abfHandler.AbfDetachInterfaceIPv6(1, 2, 3) + + Expect(err).To(BeNil()) + req, ok := ctx.MockChannel.Msg.(*vpp_abf.AbfItfAttachAddDel) + Expect(ok).To(BeTrue()) + Expect(req.IsAdd).To(BeFalse()) + Expect(req.Attach.PolicyID).To(Equal(uint32(1))) + Expect(req.Attach.SwIfIndex).To(BeEquivalentTo(uint32(2))) + Expect(req.Attach.Priority).To(Equal(uint32(3))) + Expect(req.Attach.IsIPv6).To(BeTrue()) +} + +func TestDetachABFInterfaceIPv6Error(t *testing.T) { + ctx, abfHandler, _ := abfTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_abf.AbfItfAttachAddDelReply{ + Retval: -1, + }) + + err := abfHandler.AbfDetachInterfaceIPv6(1, 2, 3) + + Expect(err).ToNot(BeNil()) +} + +func abfTestSetup(t *testing.T) (*vppmock.TestCtx, vppcalls.ABFVppAPI, ifaceidx.IfaceMetadataIndexRW) { + ctx := vppmock.SetupTestCtx(t) + log := logrus.NewLogger("test-log") + aclIdx := aclidx.NewACLIndex(log, "acl-index") + ifIdx := ifaceidx.NewIfaceIndex(log, "if-index") + abfHandler := NewABFVppHandler(ctx.MockChannel, aclIdx, ifIdx, log) + return ctx, abfHandler, ifIdx +} diff --git a/plugins/vpp/abfplugin/vppcalls/vpp2306/dump_abf_vppcalls.go b/plugins/vpp/abfplugin/vppcalls/vpp2306/dump_abf_vppcalls.go new file mode 100644 index 0000000000..8812f5399a --- /dev/null +++ b/plugins/vpp/abfplugin/vppcalls/vpp2306/dump_abf_vppcalls.go @@ -0,0 +1,163 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "net" + + "go.ligato.io/vpp-agent/v3/plugins/vpp/abfplugin/vppcalls" + vpp_abf "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/abf" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/fib_types" + abf "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/abf" +) + +// placeholder for unknown names +const unknownName = "" + +// DumpABFPolicy retrieves VPP ABF configuration. +func (h *ABFVppHandler) DumpABFPolicy() ([]*vppcalls.ABFDetails, error) { + // retrieve ABF interfaces + attachedIfs, err := h.dumpABFInterfaces() + if err != nil { + return nil, err + } + + // retrieve ABF policy + abfPolicy, err := h.dumpABFPolicy() + if err != nil { + return nil, err + } + + // merge attached interfaces data to policy + for _, policy := range abfPolicy { + ifData, ok := attachedIfs[policy.Meta.PolicyID] + if ok { + policy.ABF.AttachedInterfaces = ifData + } + } + + return abfPolicy, nil +} + +func (h *ABFVppHandler) dumpABFInterfaces() (map[uint32][]*abf.ABF_AttachedInterface, error) { + // ABF index <-> attached interfaces + abfIfs := make(map[uint32][]*abf.ABF_AttachedInterface) + + req := &vpp_abf.AbfItfAttachDump{} + reqCtx := h.callsChannel.SendMultiRequest(req) + + for { + reply := &vpp_abf.AbfItfAttachDetails{} + last, err := reqCtx.ReceiveReply(reply) + if err != nil { + return nil, err + } + if last { + break + } + + // interface name + ifName, _, exists := h.ifIndexes.LookupBySwIfIndex(uint32(reply.Attach.SwIfIndex)) + if !exists { + ifName = unknownName + } + + // attached interface entry + attached := &abf.ABF_AttachedInterface{ + InputInterface: ifName, + Priority: reply.Attach.Priority, + IsIpv6: reply.Attach.IsIPv6, + } + + _, ok := abfIfs[reply.Attach.PolicyID] + if !ok { + abfIfs[reply.Attach.PolicyID] = []*abf.ABF_AttachedInterface{} + } + abfIfs[reply.Attach.PolicyID] = append(abfIfs[reply.Attach.PolicyID], attached) + } + + return abfIfs, nil +} + +func (h *ABFVppHandler) dumpABFPolicy() ([]*vppcalls.ABFDetails, error) { + var abfs []*vppcalls.ABFDetails + req := &vpp_abf.AbfPolicyDump{} + reqCtx := h.callsChannel.SendMultiRequest(req) + + for { + reply := &vpp_abf.AbfPolicyDetails{} + last, err := reqCtx.ReceiveReply(reply) + if err != nil { + return nil, err + } + if last { + break + } + + // ACL name + aclName, _, exists := h.aclIndexes.LookupByIndex(reply.Policy.ACLIndex) + if !exists { + aclName = unknownName + } + + // paths + var fwdPaths []*abf.ABF_ForwardingPath + for _, path := range reply.Policy.Paths { + // interface name + ifName, _, exists := h.ifIndexes.LookupBySwIfIndex(path.SwIfIndex) + if !exists { + ifName = unknownName + } + + // base fields + fwdPath := &abf.ABF_ForwardingPath{ + NextHopIp: parseNextHopToString(path.Nh, path.Proto), + InterfaceName: ifName, + Weight: uint32(path.Weight), + Preference: uint32(path.Preference), + Dvr: path.Type == fib_types.FIB_API_PATH_TYPE_DVR, + } + fwdPaths = append(fwdPaths, fwdPath) + } + + abfData := &vppcalls.ABFDetails{ + ABF: &abf.ABF{ + Index: reply.Policy.PolicyID, + AclName: aclName, + ForwardingPaths: fwdPaths, + }, + Meta: &vppcalls.ABFMeta{ + PolicyID: reply.Policy.PolicyID, + }, + } + + abfs = append(abfs, abfData) + } + + return abfs, nil +} + +// returns next hop IP address +func parseNextHopToString(nh fib_types.FibPathNh, proto fib_types.FibPathNhProto) string { + if proto == fib_types.FIB_API_PATH_NH_PROTO_IP4 { + addr := nh.Address.GetIP4() + return net.IP(addr[:]).To4().String() + } + if proto == fib_types.FIB_API_PATH_NH_PROTO_IP6 { + addr := nh.Address.GetIP6() + return net.IP(addr[:]).To16().String() + } + return "" +} diff --git a/plugins/vpp/abfplugin/vppcalls/vpp2306/vppcalls_handlers.go b/plugins/vpp/abfplugin/vppcalls/vpp2306/vppcalls_handlers.go new file mode 100644 index 0000000000..bfef4d749b --- /dev/null +++ b/plugins/vpp/abfplugin/vppcalls/vpp2306/vppcalls_handlers.go @@ -0,0 +1,56 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + govppapi "go.fd.io/govpp/api" + "go.ligato.io/cn-infra/v2/logging" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306" + + "go.ligato.io/vpp-agent/v3/plugins/vpp/abfplugin/vppcalls" + "go.ligato.io/vpp-agent/v3/plugins/vpp/aclplugin/aclidx" + vpp_abf "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/abf" + "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/ifaceidx" +) + +func init() { + var msgs []govppapi.Message + msgs = append(msgs, vpp_abf.AllMessages()...) + + vppcalls.AddABFHandlerVersion(vpp2306.Version, msgs, NewABFVppHandler) +} + +// ABFVppHandler is accessor for abf-related vppcalls methods +type ABFVppHandler struct { + callsChannel govppapi.Channel + aclIndexes aclidx.ACLMetadataIndex + ifIndexes ifaceidx.IfaceMetadataIndex + log logging.Logger +} + +// NewABFVppHandler returns new ABFVppHandler. +func NewABFVppHandler( + calls govppapi.Channel, + aclIdx aclidx.ACLMetadataIndex, + ifIdx ifaceidx.IfaceMetadataIndex, + log logging.Logger, +) vppcalls.ABFVppAPI { + return &ABFVppHandler{ + callsChannel: calls, + aclIndexes: aclIdx, + ifIndexes: ifIdx, + log: log, + } +} diff --git a/plugins/vpp/aclplugin/aclplugin.go b/plugins/vpp/aclplugin/aclplugin.go index 26c1d7986e..65209d2ba9 100644 --- a/plugins/vpp/aclplugin/aclplugin.go +++ b/plugins/vpp/aclplugin/aclplugin.go @@ -31,6 +31,7 @@ import ( _ "go.ligato.io/vpp-agent/v3/plugins/vpp/aclplugin/vppcalls/vpp2202" _ "go.ligato.io/vpp-agent/v3/plugins/vpp/aclplugin/vppcalls/vpp2210" + _ "go.ligato.io/vpp-agent/v3/plugins/vpp/aclplugin/vppcalls/vpp2306" ) // ACLPlugin is a plugin that manages ACLs. diff --git a/plugins/vpp/aclplugin/vppcalls/vpp2306/acl_vppcalls.go b/plugins/vpp/aclplugin/vppcalls/vpp2306/acl_vppcalls.go new file mode 100644 index 0000000000..a3c3a9bf92 --- /dev/null +++ b/plugins/vpp/aclplugin/vppcalls/vpp2306/acl_vppcalls.go @@ -0,0 +1,402 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "fmt" + "net" + "strings" + + "go.ligato.io/cn-infra/v2/utils/addrs" + + "go.ligato.io/vpp-agent/v3/plugins/vpp/aclplugin/vppcalls" + vpp_acl "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/acl" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/acl_types" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip_types" + acl "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/acl" +) + +// AddACL implements ACL handler. +func (h *ACLVppHandler) AddACL(rules []*acl.ACL_Rule, aclName string) (uint32, error) { + // Prepare Ip rules + aclIPRules, err := transformACLIpRules(rules) + if err != nil { + return 0, err + } + if len(aclIPRules) == 0 { + return 0, fmt.Errorf("no rules found for ACL %v", aclName) + } + + req := &vpp_acl.ACLAddReplace{ + ACLIndex: 0xffffffff, // to make new Entry + Count: uint32(len(aclIPRules)), + Tag: aclName, + R: aclIPRules, + } + reply := &vpp_acl.ACLAddReplaceReply{} + + if err = h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return 0, fmt.Errorf("failed to write ACL %v: %v", aclName, err) + } + + return reply.ACLIndex, nil +} + +// AddMACIPACL implements ACL handler. +func (h *ACLVppHandler) AddMACIPACL(rules []*acl.ACL_Rule, aclName string) (uint32, error) { + // Prepare MAc Ip rules + aclMacIPRules, err := h.transformACLMacIPRules(rules) + if err != nil { + return 0, err + } + if len(aclMacIPRules) == 0 { + return 0, fmt.Errorf("no rules found for ACL %v", aclName) + } + + req := &vpp_acl.MacipACLAdd{ + Count: uint32(len(aclMacIPRules)), + Tag: aclName, + R: aclMacIPRules, + } + reply := &vpp_acl.MacipACLAddReply{} + + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return 0, fmt.Errorf("failed to write ACL %v: %v", aclName, err) + } + + return reply.ACLIndex, nil +} + +// ModifyACL implements ACL handler. +func (h *ACLVppHandler) ModifyACL(aclIndex uint32, rules []*acl.ACL_Rule, aclName string) error { + // Prepare Ip rules + aclIPRules, err := transformACLIpRules(rules) + if err != nil { + return err + } + if len(aclIPRules) == 0 { + return nil + } + + req := &vpp_acl.ACLAddReplace{ + ACLIndex: aclIndex, + Count: uint32(len(aclIPRules)), + Tag: aclName, + R: aclIPRules, + } + reply := &vpp_acl.ACLAddReplaceReply{} + + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return fmt.Errorf("failed to write ACL %v: %v", aclName, err) + } + + return nil +} + +// ModifyMACIPACL implements ACL handler. +func (h *ACLVppHandler) ModifyMACIPACL(aclIndex uint32, rules []*acl.ACL_Rule, aclName string) error { + // Prepare MAc Ip rules + aclMacIPRules, err := h.transformACLMacIPRules(rules) + if err != nil { + return err + } + if len(aclMacIPRules) == 0 { + return fmt.Errorf("no rules found for ACL %v", aclName) + } + + req := &vpp_acl.MacipACLAddReplace{ + ACLIndex: aclIndex, + Count: uint32(len(aclMacIPRules)), + Tag: aclName, + R: aclMacIPRules, + } + reply := &vpp_acl.MacipACLAddReplaceReply{} + + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return fmt.Errorf("failed to write ACL %v: %v", aclName, err) + } + + return nil +} + +// DeleteACL implements ACL handler. +func (h *ACLVppHandler) DeleteACL(aclIndex uint32) error { + req := &vpp_acl.ACLDel{ + ACLIndex: aclIndex, + } + reply := &vpp_acl.ACLDelReply{} + + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return fmt.Errorf("failed to remove L3/L4 ACL %v: %v", aclIndex, err) + } + + return nil +} + +// DeleteMACIPACL implements ACL handler. +func (h *ACLVppHandler) DeleteMACIPACL(aclIndex uint32) error { + req := &vpp_acl.MacipACLDel{ + ACLIndex: aclIndex, + } + reply := &vpp_acl.MacipACLDelReply{} + + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return fmt.Errorf("failed to remove L2 ACL %v: %v", aclIndex, err) + } + + return nil +} + +// Method transforms provided set of IP proto ACL rules to binapi ACL rules. +func transformACLIpRules(rules []*acl.ACL_Rule) (aclIPRules []acl_types.ACLRule, err error) { + for _, rule := range rules { + aclRule := &acl_types.ACLRule{ + IsPermit: ruleAction(rule.Action), + } + // Match + if ipRule := rule.GetIpRule(); ipRule != nil { + // Concerned to IP rules only + // L3 + if ipRule.Ip != nil { + aclRule, err = ipACL(ipRule.Ip, aclRule) + if err != nil { + return nil, err + } + } + // ICMP/L4 + switch ipRule.Ip.GetProtocol() { + case 0: // determine protocol based on rule definition + if ipRule.Icmp != nil { + aclRule = icmpACL(ipRule.Icmp, aclRule) + } else if ipRule.Tcp != nil { + aclRule = tcpACL(ipRule.Tcp, aclRule) + } else if ipRule.Udp != nil { + aclRule = udpACL(ipRule.Udp, aclRule) + } + case vppcalls.ICMPv4Proto: + fallthrough + case vppcalls.ICMPv6Proto: + if ipRule.Icmp != nil { + aclRule = icmpACL(ipRule.Icmp, aclRule) + } + case vppcalls.TCPProto: + if ipRule.Tcp != nil { + aclRule = tcpACL(ipRule.Tcp, aclRule) + } + case vppcalls.UDPProto: + if ipRule.Udp != nil { + aclRule = udpACL(ipRule.Udp, aclRule) + } + } + aclIPRules = append(aclIPRules, *aclRule) + } + } + return aclIPRules, nil +} + +func (h *ACLVppHandler) transformACLMacIPRules(rules []*acl.ACL_Rule) (aclMacIPRules []acl_types.MacipACLRule, err error) { + for _, rule := range rules { + aclMacIPRule := &acl_types.MacipACLRule{ + IsPermit: ruleAction(rule.Action), + } + // Matche + if macIPRule := rule.GetMacipRule(); macIPRule != nil { + // Concerned to MAC IP rules only + // Source IP Address + Prefix + aclMacIPRule.SrcPrefix, err = IPtoPrefix(macIPRule.SourceAddress) + if err != nil { + return nil, fmt.Errorf("invalid IP address %v", macIPRule.SourceAddress) + } + aclMacIPRule.SrcPrefix.Len = uint8(macIPRule.SourceAddressPrefix) + // MAC + mask + srcMac, err := net.ParseMAC(macIPRule.SourceMacAddress) + if err != nil { + return aclMacIPRules, err + } + srcMacMask, err := net.ParseMAC(macIPRule.SourceMacAddressMask) + if err != nil { + return aclMacIPRules, err + } + copy(aclMacIPRule.SrcMac[:], srcMac) + copy(aclMacIPRule.SrcMacMask[:], srcMacMask) + aclMacIPRules = append(aclMacIPRules, *aclMacIPRule) + } + } + return aclMacIPRules, nil +} + +// The function sets an IP ACL rule fields into provided ACL Rule object. Source +// and destination addresses have to be the same IP version and contain a network mask. +func ipACL(ipRule *acl.ACL_Rule_IpRule_Ip, aclRule *acl_types.ACLRule) (*acl_types.ACLRule, error) { + var ( + err error + srcNetwork *net.IPNet + dstNetwork *net.IPNet + ) + + if strings.TrimSpace(ipRule.SourceNetwork) != "" { + // Resolve source address + _, srcNetwork, err = net.ParseCIDR(ipRule.SourceNetwork) + if err != nil { + return nil, err + } + if srcNetwork == nil { + srcNetwork = &net.IPNet{} + } + if srcNetwork.IP.To4() == nil && srcNetwork.IP.To16() == nil { + return aclRule, fmt.Errorf("source address %v is invalid", ipRule.SourceNetwork) + } + } else { + return aclRule, fmt.Errorf("source address is empty") + } + + if strings.TrimSpace(ipRule.DestinationNetwork) != "" { + // Resolve destination address + _, dstNetwork, err = net.ParseCIDR(ipRule.DestinationNetwork) + if err != nil { + return nil, err + } + if dstNetwork == nil { + dstNetwork = &net.IPNet{} + } + if dstNetwork.IP.To4() == nil && dstNetwork.IP.To16() == nil { + return aclRule, fmt.Errorf("destination address %v is invalid", ipRule.DestinationNetwork) + } + } else { + return aclRule, fmt.Errorf("destination address is empty") + } + + // Check IP version (they should be the same), beware: IPv4 address can be converted to IPv6. + if (srcNetwork.IP.To4() != nil && dstNetwork.IP.To4() == nil && dstNetwork.IP.To16() != nil) || + (srcNetwork.IP.To4() == nil && srcNetwork.IP.To16() != nil && dstNetwork.IP.To4() != nil) { + return aclRule, fmt.Errorf("source address %v and destionation address %v have different IP versions", + ipRule.SourceNetwork, ipRule.DestinationNetwork) + } + + if srcNetwork.IP.To4() != nil || dstNetwork.IP.To4() != nil { + // Ipv4 case + aclRule.SrcPrefix = IPNetToPrefix(srcNetwork) + aclRule.DstPrefix = IPNetToPrefix(dstNetwork) + } else if srcNetwork.IP.To16() != nil || dstNetwork.IP.To16() != nil { + // Ipv6 case + aclRule.SrcPrefix = IPNetToPrefix(srcNetwork) + aclRule.DstPrefix = IPNetToPrefix(dstNetwork) + } + aclRule.Proto = ip_types.IPProto(ipRule.GetProtocol()) + return aclRule, nil +} + +// The function sets an ICMP ACL rule fields into provided ACL Rule object. +// The ranges are exclusive, use first = 0 and last = 255/65535 (icmpv4/icmpv6) to match "any". +func icmpACL(icmpRule *acl.ACL_Rule_IpRule_Icmp, aclRule *acl_types.ACLRule) *acl_types.ACLRule { + if icmpRule == nil { + return aclRule + } + if icmpRule.Icmpv6 { + aclRule.Proto = vppcalls.ICMPv6Proto // IANA ICMPv6 + // ICMPv6 type range + aclRule.SrcportOrIcmptypeFirst = uint16(icmpRule.IcmpTypeRange.First) + aclRule.SrcportOrIcmptypeLast = uint16(icmpRule.IcmpTypeRange.Last) + // ICMPv6 code range + aclRule.DstportOrIcmpcodeFirst = uint16(icmpRule.IcmpCodeRange.First) + aclRule.DstportOrIcmpcodeLast = uint16(icmpRule.IcmpCodeRange.Last) + } else { + aclRule.Proto = vppcalls.ICMPv4Proto // IANA ICMPv4 + // ICMPv4 type range + aclRule.SrcportOrIcmptypeFirst = uint16(icmpRule.IcmpTypeRange.First) + aclRule.SrcportOrIcmptypeLast = uint16(icmpRule.IcmpTypeRange.Last) + // ICMPv4 code range + aclRule.DstportOrIcmpcodeFirst = uint16(icmpRule.IcmpCodeRange.First) + aclRule.DstportOrIcmpcodeLast = uint16(icmpRule.IcmpCodeRange.Last) + } + return aclRule +} + +// Sets an TCP ACL rule fields into provided ACL Rule object. +func tcpACL(tcpRule *acl.ACL_Rule_IpRule_Tcp, aclRule *acl_types.ACLRule) *acl_types.ACLRule { + aclRule.Proto = vppcalls.TCPProto // IANA TCP + aclRule.SrcportOrIcmptypeFirst = uint16(tcpRule.SourcePortRange.LowerPort) + aclRule.SrcportOrIcmptypeLast = uint16(tcpRule.SourcePortRange.UpperPort) + aclRule.DstportOrIcmpcodeFirst = uint16(tcpRule.DestinationPortRange.LowerPort) + aclRule.DstportOrIcmpcodeLast = uint16(tcpRule.DestinationPortRange.UpperPort) + aclRule.TCPFlagsValue = uint8(tcpRule.TcpFlagsValue) + aclRule.TCPFlagsMask = uint8(tcpRule.TcpFlagsMask) + return aclRule +} + +// Sets an UDP ACL rule fields into provided ACL Rule object. +func udpACL(udpRule *acl.ACL_Rule_IpRule_Udp, aclRule *acl_types.ACLRule) *acl_types.ACLRule { + aclRule.Proto = vppcalls.UDPProto // IANA UDP + aclRule.SrcportOrIcmptypeFirst = uint16(udpRule.SourcePortRange.LowerPort) + aclRule.SrcportOrIcmptypeLast = uint16(udpRule.SourcePortRange.UpperPort) + aclRule.DstportOrIcmpcodeFirst = uint16(udpRule.DestinationPortRange.LowerPort) + aclRule.DstportOrIcmpcodeLast = uint16(udpRule.DestinationPortRange.UpperPort) + return aclRule +} + +func ruleAction(action acl.ACL_Rule_Action) acl_types.ACLAction { + switch action { + case acl.ACL_Rule_DENY: + return acl_types.ACL_ACTION_API_DENY + case acl.ACL_Rule_PERMIT: + return acl_types.ACL_ACTION_API_PERMIT + case acl.ACL_Rule_REFLECT: + return acl_types.ACL_ACTION_API_PERMIT_REFLECT + default: + return 0 + } +} + +func IPNetToPrefix(dstNetwork *net.IPNet) ip_types.Prefix { + var addr ip_types.Address + if dstNetwork.IP.To4() == nil { + addr.Af = ip_types.ADDRESS_IP6 + var ip6addr ip_types.IP6Address + copy(ip6addr[:], dstNetwork.IP.To16()) + addr.Un.SetIP6(ip6addr) + } else { + addr.Af = ip_types.ADDRESS_IP4 + var ip4addr ip_types.IP4Address + copy(ip4addr[:], dstNetwork.IP.To4()) + addr.Un.SetIP4(ip4addr) + } + mask, _ := dstNetwork.Mask.Size() + return ip_types.Prefix{ + Address: addr, + Len: uint8(mask), + } +} + +func IPtoPrefix(addr string) (ip_types.Prefix, error) { + ipAddr, isIPv6, err := addrs.ParseIPWithPrefix(addr) + if err != nil { + return ip_types.Prefix{}, err + } + var prefix ip_types.Prefix + maskSize, _ := ipAddr.Mask.Size() + prefix.Len = byte(maskSize) + if isIPv6 { + prefix.Address.Af = ip_types.ADDRESS_IP6 + var ip6addr ip_types.IP6Address + copy(ip6addr[:], ipAddr.IP.To16()) + prefix.Address.Un.SetIP6(ip6addr) + } else { + prefix.Address.Af = ip_types.ADDRESS_IP4 + var ip4addr ip_types.IP4Address + copy(ip4addr[:], ipAddr.IP.To4()) + prefix.Address.Un.SetIP4(ip4addr) + } + return prefix, nil +} diff --git a/plugins/vpp/aclplugin/vppcalls/vpp2306/acl_vppcalls_test.go b/plugins/vpp/aclplugin/vppcalls/vpp2306/acl_vppcalls_test.go new file mode 100644 index 0000000000..a7000e8f53 --- /dev/null +++ b/plugins/vpp/aclplugin/vppcalls/vpp2306/acl_vppcalls_test.go @@ -0,0 +1,493 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "testing" + + . "github.com/onsi/gomega" + "go.ligato.io/cn-infra/v2/logging/logrus" + + vpp_acl "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/acl" + "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/ifaceidx" + "go.ligato.io/vpp-agent/v3/plugins/vpp/vppmock" + acl "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/acl" +) + +var aclNoRules []*acl.ACL_Rule + +var aclErr1Rules = []*acl.ACL_Rule{ + { + Action: acl.ACL_Rule_PERMIT, + IpRule: &acl.ACL_Rule_IpRule{ + Ip: &acl.ACL_Rule_IpRule_Ip{ + SourceNetwork: ".0.", + DestinationNetwork: "10.20.0.0/24", + }, + }, + }, +} + +var aclErr2Rules = []*acl.ACL_Rule{ + { + Action: acl.ACL_Rule_PERMIT, + IpRule: &acl.ACL_Rule_IpRule{ + Ip: &acl.ACL_Rule_IpRule_Ip{ + SourceNetwork: "192.168.1.1/32", + DestinationNetwork: ".0.", + }, + }, + }, +} + +var aclErr3Rules = []*acl.ACL_Rule{ + { + Action: acl.ACL_Rule_PERMIT, + IpRule: &acl.ACL_Rule_IpRule{ + Ip: &acl.ACL_Rule_IpRule_Ip{ + SourceNetwork: "192.168.1.1/32", + DestinationNetwork: "dead::1/64", + }, + }, + }, +} + +var aclErr4Rules = []*acl.ACL_Rule{ + { + Action: acl.ACL_Rule_PERMIT, + IpRule: &acl.ACL_Rule_IpRule{ + Ip: &acl.ACL_Rule_IpRule_Ip{ + SourceNetwork: "", + DestinationNetwork: "", + }, + }, + }, +} + +var aclErr5Rules = []*acl.ACL_Rule{ + { + Action: acl.ACL_Rule_PERMIT, + MacipRule: &acl.ACL_Rule_MacIpRule{ + SourceAddress: "192.168.0.1", + SourceAddressPrefix: uint32(16), + SourceMacAddress: "", + SourceMacAddressMask: "ff:ff:ff:ff:00:00", + }, + }, +} + +var aclErr6Rules = []*acl.ACL_Rule{ + { + Action: acl.ACL_Rule_PERMIT, + MacipRule: &acl.ACL_Rule_MacIpRule{ + SourceAddress: "192.168.0.1", + SourceAddressPrefix: uint32(16), + SourceMacAddress: "11:44:0A:B8:4A:36", + SourceMacAddressMask: "", + }, + }, +} + +var aclErr7Rules = []*acl.ACL_Rule{ + { + Action: acl.ACL_Rule_PERMIT, + MacipRule: &acl.ACL_Rule_MacIpRule{ + SourceAddress: "", + SourceAddressPrefix: uint32(16), + SourceMacAddress: "11:44:0A:B8:4A:36", + SourceMacAddressMask: "ff:ff:ff:ff:00:00", + }, + }, +} + +var aclIPrules = []*acl.ACL_Rule{ + { + //RuleName: "permitIPv4", + Action: acl.ACL_Rule_PERMIT, + IpRule: &acl.ACL_Rule_IpRule{ + Ip: &acl.ACL_Rule_IpRule_Ip{ + SourceNetwork: "192.168.1.1/32", + DestinationNetwork: "10.20.0.0/24", + }, + }, + }, + { + //RuleName: "permitIPv6", + Action: acl.ACL_Rule_PERMIT, + IpRule: &acl.ACL_Rule_IpRule{ + Ip: &acl.ACL_Rule_IpRule_Ip{ + SourceNetwork: "dead::1/64", + DestinationNetwork: "dead::2/64", + }, + }, + }, + { + //RuleName: "denyICMP", + Action: acl.ACL_Rule_DENY, + IpRule: &acl.ACL_Rule_IpRule{ + Icmp: &acl.ACL_Rule_IpRule_Icmp{ + Icmpv6: false, + IcmpCodeRange: &acl.ACL_Rule_IpRule_Icmp_Range{ + First: 1, + Last: 2, + }, + IcmpTypeRange: &acl.ACL_Rule_IpRule_Icmp_Range{ + First: 3, + Last: 4, + }, + }, + }, + }, + { + //RuleName: "denyICMPv6", + Action: acl.ACL_Rule_DENY, + IpRule: &acl.ACL_Rule_IpRule{ + Icmp: &acl.ACL_Rule_IpRule_Icmp{ + Icmpv6: true, + IcmpCodeRange: &acl.ACL_Rule_IpRule_Icmp_Range{ + First: 10, + Last: 20, + }, + IcmpTypeRange: &acl.ACL_Rule_IpRule_Icmp_Range{ + First: 30, + Last: 40, + }, + }, + }, + }, + { + //RuleName: "permitTCP", + Action: acl.ACL_Rule_PERMIT, + IpRule: &acl.ACL_Rule_IpRule{ + Tcp: &acl.ACL_Rule_IpRule_Tcp{ + TcpFlagsMask: 20, + TcpFlagsValue: 10, + SourcePortRange: &acl.ACL_Rule_IpRule_PortRange{ + LowerPort: 150, + UpperPort: 250, + }, + DestinationPortRange: &acl.ACL_Rule_IpRule_PortRange{ + LowerPort: 1150, + UpperPort: 1250, + }, + }, + }, + }, + { + //RuleName: "denyUDP", + Action: acl.ACL_Rule_DENY, + IpRule: &acl.ACL_Rule_IpRule{ + Udp: &acl.ACL_Rule_IpRule_Udp{ + SourcePortRange: &acl.ACL_Rule_IpRule_PortRange{ + LowerPort: 150, + UpperPort: 250, + }, + DestinationPortRange: &acl.ACL_Rule_IpRule_PortRange{ + LowerPort: 1150, + UpperPort: 1250, + }, + }, + }, + }, +} + +var aclMACIPrules = []*acl.ACL_Rule{ + { + //RuleName: "denyIPv4", + Action: acl.ACL_Rule_DENY, + MacipRule: &acl.ACL_Rule_MacIpRule{ + SourceAddress: "192.168.0.1", + SourceAddressPrefix: uint32(16), + SourceMacAddress: "11:44:0A:B8:4A:35", + SourceMacAddressMask: "ff:ff:ff:ff:00:00", + }, + }, + { + //RuleName: "denyIPv6", + Action: acl.ACL_Rule_DENY, + MacipRule: &acl.ACL_Rule_MacIpRule{ + SourceAddress: "dead::1", + SourceAddressPrefix: uint32(64), + SourceMacAddress: "11:44:0A:B8:4A:35", + SourceMacAddressMask: "ff:ff:ff:ff:00:00", + }, + }, +} + +type testCtx struct { + *vppmock.TestCtx + aclHandler *ACLVppHandler + ifIndexes ifaceidx.IfaceMetadataIndexRW +} + +func setupACLTest(t *testing.T) *testCtx { + ctx := vppmock.SetupTestCtx(t) + + ifaceIdx := ifaceidx.NewIfaceIndex(logrus.NewLogger("test"), "test") + aclHandler := NewACLVppHandler(ctx.MockVPPClient, ifaceIdx).(*ACLVppHandler) + + return &testCtx{ + TestCtx: ctx, + aclHandler: aclHandler, + ifIndexes: ifaceIdx, + } +} + +func (ctx *testCtx) teardownACLTest() { + ctx.TeardownTestCtx() +} + +// Test add IP acl rules +func TestAddIPAcl(t *testing.T) { + ctx := setupACLTest(t) + defer ctx.teardownACLTest() + ctx.MockVpp.MockReply(&vpp_acl.ACLAddReplaceReply{}) + + aclIndex, err := ctx.aclHandler.AddACL(aclIPrules, "test0") + Expect(err).To(BeNil()) + Expect(aclIndex).To(BeEquivalentTo(0)) + + _, err = ctx.aclHandler.AddACL(aclNoRules, "test1") + Expect(err).To(Not(BeNil())) + + _, err = ctx.aclHandler.AddACL(aclErr1Rules, "test2") + Expect(err).To(Not(BeNil())) + + _, err = ctx.aclHandler.AddACL(aclErr2Rules, "test3") + Expect(err).To(Not(BeNil())) + + _, err = ctx.aclHandler.AddACL(aclErr3Rules, "test4") + Expect(err).To(Not(BeNil())) + + _, err = ctx.aclHandler.AddACL(aclErr4Rules, "test5") + Expect(err).To(Not(BeNil())) + + ctx.MockVpp.MockReply(&vpp_acl.MacipACLAddReply{}) + _, err = ctx.aclHandler.AddACL(aclIPrules, "test5") + Expect(err).To(Not(BeNil())) + + ctx.MockVpp.MockReply(&vpp_acl.ACLAddReplaceReply{Retval: -1}) + _, err = ctx.aclHandler.AddACL(aclIPrules, "test6") + Expect(err).To(Not(BeNil())) +} + +// Test add MACIP acl rules +func TestAddMacIPAcl(t *testing.T) { + ctx := setupACLTest(t) + defer ctx.teardownACLTest() + ctx.MockVpp.MockReply(&vpp_acl.MacipACLAddReply{}) + + aclIndex, err := ctx.aclHandler.AddMACIPACL(aclMACIPrules, "test6") + Expect(err).To(BeNil()) + Expect(aclIndex).To(BeEquivalentTo(0)) + + _, err = ctx.aclHandler.AddMACIPACL(aclNoRules, "test7") + Expect(err).To(Not(BeNil())) + + _, err = ctx.aclHandler.AddMACIPACL(aclErr5Rules, "test8") + Expect(err).To(Not(BeNil())) + + _, err = ctx.aclHandler.AddMACIPACL(aclErr6Rules, "test9") + Expect(err).To(Not(BeNil())) + + _, err = ctx.aclHandler.AddMACIPACL(aclErr7Rules, "test10") + Expect(err).To(Not(BeNil())) + Expect(err.Error()).To(HavePrefix("invalid IP address ")) + + ctx.MockVpp.MockReply(&vpp_acl.ACLAddReplaceReply{}) + _, err = ctx.aclHandler.AddMACIPACL(aclMACIPrules, "test11") + Expect(err).To(Not(BeNil())) + + ctx.MockVpp.MockReply(&vpp_acl.MacipACLAddReply{Retval: -1}) + _, err = ctx.aclHandler.AddMACIPACL(aclMACIPrules, "test12") + Expect(err).To(Not(BeNil())) +} + +// Test deletion of IP acl rules +func TestDeleteIPAcl(t *testing.T) { + ctx := setupACLTest(t) + defer ctx.teardownACLTest() + ctx.MockVpp.MockReply(&vpp_acl.ACLAddReplaceReply{}) + + aclIndex, err := ctx.aclHandler.AddACL(aclIPrules, "test_del0") + Expect(err).To(BeNil()) + Expect(aclIndex).To(BeEquivalentTo(0)) + + rule2del := []*acl.ACL_Rule{ + { + Action: acl.ACL_Rule_PERMIT, + IpRule: &acl.ACL_Rule_IpRule{ + Ip: &acl.ACL_Rule_IpRule_Ip{ + SourceNetwork: "10.20.30.1/32", + DestinationNetwork: "10.20.0.0/24", + }, + }, + }, + } + + ctx.MockVpp.MockReply(&vpp_acl.ACLAddReplaceReply{ACLIndex: 1}) + aclIndex, err = ctx.aclHandler.AddACL(rule2del, "test_del1") + Expect(err).To(BeNil()) + Expect(aclIndex).To(BeEquivalentTo(1)) + + ctx.MockVpp.MockReply(&vpp_acl.ACLAddReplaceReply{}) + err = ctx.aclHandler.DeleteACL(5) + Expect(err).To(Not(BeNil())) + + ctx.MockVpp.MockReply(&vpp_acl.ACLDelReply{Retval: -1}) + err = ctx.aclHandler.DeleteACL(5) + Expect(err).To(Not(BeNil())) + + ctx.MockVpp.MockReply(&vpp_acl.ACLDelReply{}) + err = ctx.aclHandler.DeleteACL(1) + Expect(err).To(BeNil()) +} + +// Test deletion of MACIP acl rules +func TestDeleteMACIPAcl(t *testing.T) { + ctx := setupACLTest(t) + defer ctx.teardownACLTest() + ctx.MockVpp.MockReply(&vpp_acl.MacipACLAddReply{}) + + aclIndex, err := ctx.aclHandler.AddMACIPACL(aclMACIPrules, "test_del2") + Expect(err).To(BeNil()) + Expect(aclIndex).To(BeEquivalentTo(0)) + + rule2del := []*acl.ACL_Rule{ + { + Action: acl.ACL_Rule_PERMIT, + MacipRule: &acl.ACL_Rule_MacIpRule{ + SourceAddress: "192.168.0.1", + SourceAddressPrefix: uint32(16), + SourceMacAddress: "11:44:0A:B8:4A:35", + SourceMacAddressMask: "ff:ff:ff:ff:00:00", + }, + }, + } + + ctx.MockVpp.MockReply(&vpp_acl.MacipACLAddReply{ACLIndex: 1}) + aclIndex, err = ctx.aclHandler.AddMACIPACL(rule2del, "test_del3") + Expect(err).To(BeNil()) + Expect(aclIndex).To(BeEquivalentTo(1)) + + ctx.MockVpp.MockReply(&vpp_acl.MacipACLAddReply{}) + err = ctx.aclHandler.DeleteMACIPACL(5) + Expect(err).To(Not(BeNil())) + + ctx.MockVpp.MockReply(&vpp_acl.MacipACLDelReply{Retval: -1}) + err = ctx.aclHandler.DeleteMACIPACL(5) + Expect(err).To(Not(BeNil())) + + ctx.MockVpp.MockReply(&vpp_acl.MacipACLDelReply{}) + err = ctx.aclHandler.DeleteMACIPACL(1) + Expect(err).To(BeNil()) +} + +// Test modification of IP acl rule +func TestModifyIPAcl(t *testing.T) { + ctx := setupACLTest(t) + defer ctx.teardownACLTest() + ctx.MockVpp.MockReply(&vpp_acl.ACLAddReplaceReply{}) + + aclIndex, err := ctx.aclHandler.AddACL(aclIPrules, "test_modify") + Expect(err).To(BeNil()) + Expect(aclIndex).To(BeEquivalentTo(0)) + + rule2modify := []*acl.ACL_Rule{ + { + Action: acl.ACL_Rule_PERMIT, + IpRule: &acl.ACL_Rule_IpRule{ + Ip: &acl.ACL_Rule_IpRule_Ip{ + SourceNetwork: "10.20.30.1/32", + DestinationNetwork: "10.20.0.0/24", + }, + }, + }, + { + Action: acl.ACL_Rule_PERMIT, + IpRule: &acl.ACL_Rule_IpRule{ + Ip: &acl.ACL_Rule_IpRule_Ip{ + SourceNetwork: "dead:dead::3/64", + DestinationNetwork: "dead:dead::4/64", + }, + }, + }, + } + + ctx.MockVpp.MockReply(&vpp_acl.ACLAddReplaceReply{}) + err = ctx.aclHandler.ModifyACL(0, rule2modify, "test_modify0") + Expect(err).To(BeNil()) + + err = ctx.aclHandler.ModifyACL(0, aclErr1Rules, "test_modify1") + Expect(err).To(Not(BeNil())) + + err = ctx.aclHandler.ModifyACL(0, aclNoRules, "test_modify2") + Expect(err).To(BeNil()) + + ctx.MockVpp.MockReply(&vpp_acl.MacipACLAddReplaceReply{}) + err = ctx.aclHandler.ModifyACL(0, aclIPrules, "test_modify3") + Expect(err).To(Not(BeNil())) + + ctx.MockVpp.MockReply(&vpp_acl.ACLAddReplaceReply{Retval: -1}) + err = ctx.aclHandler.ModifyACL(0, aclIPrules, "test_modify4") + Expect(err).To(Not(BeNil())) +} + +// Test modification of MACIP acl rule +func TestModifyMACIPAcl(t *testing.T) { + ctx := setupACLTest(t) + defer ctx.teardownACLTest() + ctx.MockVpp.MockReply(&vpp_acl.MacipACLAddReply{}) + + aclIndex, err := ctx.aclHandler.AddMACIPACL(aclMACIPrules, "test_modify") + Expect(err).To(BeNil()) + Expect(aclIndex).To(BeEquivalentTo(0)) + + rule2modify := []*acl.ACL_Rule{ + { + Action: acl.ACL_Rule_DENY, + MacipRule: &acl.ACL_Rule_MacIpRule{ + SourceAddress: "192.168.10.1", + SourceAddressPrefix: uint32(24), + SourceMacAddress: "11:44:0A:B8:4A:37", + SourceMacAddressMask: "ff:ff:ff:ff:00:00", + }, + }, + { + Action: acl.ACL_Rule_DENY, + MacipRule: &acl.ACL_Rule_MacIpRule{ + SourceAddress: "dead::2", + SourceAddressPrefix: uint32(64), + SourceMacAddress: "11:44:0A:B8:4A:38", + SourceMacAddressMask: "ff:ff:ff:ff:00:00", + }, + }, + } + + ctx.MockVpp.MockReply(&vpp_acl.MacipACLAddReplaceReply{}) + err = ctx.aclHandler.ModifyMACIPACL(0, rule2modify, "test_modify0") + Expect(err).To(BeNil()) + + err = ctx.aclHandler.ModifyMACIPACL(0, aclErr1Rules, "test_modify1") + Expect(err).To(Not(BeNil())) + + ctx.MockVpp.MockReply(&vpp_acl.MacipACLAddReplaceReply{}) + err = ctx.aclHandler.ModifyMACIPACL(0, aclIPrules, "test_modify3") + Expect(err).To(Not(BeNil())) + + ctx.MockVpp.MockReply(&vpp_acl.MacipACLAddReplaceReply{Retval: -1}) + err = ctx.aclHandler.ModifyMACIPACL(0, aclIPrules, "test_modify4") + Expect(err).To(Not(BeNil())) +} diff --git a/plugins/vpp/aclplugin/vppcalls/vpp2306/dump_vppcalls.go b/plugins/vpp/aclplugin/vppcalls/vpp2306/dump_vppcalls.go new file mode 100644 index 0000000000..910f223bd5 --- /dev/null +++ b/plugins/vpp/aclplugin/vppcalls/vpp2306/dump_vppcalls.go @@ -0,0 +1,647 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "fmt" + "net" + "strings" + + "go.ligato.io/cn-infra/v2/logging/logrus" + + "go.ligato.io/vpp-agent/v3/plugins/vpp/aclplugin/vppcalls" + vpp_acl "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/acl" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/acl_types" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface_types" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip_types" + acl "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/acl" +) + +// DumpACL implements ACL handler. +func (h *ACLVppHandler) DumpACL() ([]*vppcalls.ACLDetails, error) { + ruleIPData := make(map[vppcalls.ACLMeta][]*acl.ACL_Rule) + + // get all ACLs with IP ruleData + IPRuleACLs, err := h.DumpIPAcls() + if len(IPRuleACLs) < 1 || err != nil { + return nil, err + } + + // resolve IP rules for every ACL + // Note: currently ACL may have only IP ruleData or only MAC IP ruleData + var wasErr error + for identifier, IPRules := range IPRuleACLs { + var rulesDetails []*acl.ACL_Rule + + if len(IPRules) > 0 { + for _, IPRule := range IPRules { + ruleDetails, err := h.getIPRuleDetails(IPRule) + if err != nil { + return nil, fmt.Errorf("failed to get IP Rule %v details: %v", IPRule, err) + } + rulesDetails = append(rulesDetails, ruleDetails) + } + } + ruleIPData[identifier] = rulesDetails + } + + // Prepare separate list of all active ACL indices on the VPP + var indices []uint32 + for identifier := range ruleIPData { + indices = append(indices, identifier.Index) + } + + // Get all ACL indices with ingress and egress interfaces + interfaceData, err := h.DumpACLInterfaces(indices) + if err != nil { + return nil, err + } + + var ACLs []*vppcalls.ACLDetails + // Build a list of ACL ruleData with ruleData, interfaces, index and tag (name) + for identifier, rules := range ruleIPData { + ACLs = append(ACLs, &vppcalls.ACLDetails{ + ACL: &acl.ACL{ + Name: identifier.Tag, + Rules: rules, + Interfaces: interfaceData[identifier.Index], + }, + Meta: &vppcalls.ACLMeta{ + Index: identifier.Index, + Tag: identifier.Tag, + }, + }) + } + + return ACLs, wasErr +} + +// DumpMACIPACL implements ACL handler. +func (h *ACLVppHandler) DumpMACIPACL() ([]*vppcalls.ACLDetails, error) { + ruleMACIPData := make(map[vppcalls.ACLMeta][]*acl.ACL_Rule) + + // get all ACLs with MACIP ruleData + MACIPRuleACLs, err := h.DumpMacIPAcls() + if err != nil || len(MACIPRuleACLs) == 0 { + return nil, err + } + + // resolve MACIP rules for every ACL + for metadata, MACIPRules := range MACIPRuleACLs { + var rulesDetails []*acl.ACL_Rule + + for _, MACIPRule := range MACIPRules { + ruleDetails, err := h.getMACIPRuleDetails(MACIPRule) + if err != nil { + return nil, fmt.Errorf("failed to get MACIP Rule %v details: %v", MACIPRule, err) + } + rulesDetails = append(rulesDetails, ruleDetails) + } + ruleMACIPData[metadata] = rulesDetails + } + + // Prepare separate list of all active ACL indices on the VPP + var indices []uint32 + for identifier := range ruleMACIPData { + indices = append(indices, identifier.Index) + } + + // Get all ACL indices with ingress and egress interfaces + interfaceData, err := h.DumpMACIPACLInterfaces(indices) + if err != nil { + return nil, err + } + + var ACLs []*vppcalls.ACLDetails + // Build a list of ACL ruleData with ruleData, interfaces, index and tag (name) + for metadata, rules := range ruleMACIPData { + ACLs = append(ACLs, &vppcalls.ACLDetails{ + ACL: &acl.ACL{ + Name: metadata.Tag, + Rules: rules, + Interfaces: interfaceData[metadata.Index], + }, + Meta: &vppcalls.ACLMeta{ + Index: metadata.Index, + Tag: metadata.Tag, + }, + }) + } + return ACLs, nil +} + +// DumpACLInterfaces implements ACL handler. +func (h *ACLVppHandler) DumpACLInterfaces(indices []uint32) (map[uint32]*acl.ACL_Interfaces, error) { + // list of ACL-to-interfaces + aclsWithInterfaces := make(map[uint32]*acl.ACL_Interfaces) + + var interfaceData []*vppcalls.ACLToInterface + var wasErr error + + msgIP := &vpp_acl.ACLInterfaceListDump{ + SwIfIndex: 0xffffffff, // dump all + } + reqIP := h.callsChannel.SendMultiRequest(msgIP) + for { + replyIP := &vpp_acl.ACLInterfaceListDetails{} + stop, err := reqIP.ReceiveReply(replyIP) + if stop { + break + } + if err != nil { + return aclsWithInterfaces, fmt.Errorf("ACL interface list dump reply error: %v", err) + } + + if replyIP.Count > 0 { + data := &vppcalls.ACLToInterface{ + SwIfIdx: uint32(replyIP.SwIfIndex), + } + for i, aclIdx := range replyIP.Acls { + if i < int(replyIP.NInput) { + data.IngressACL = append(data.IngressACL, aclIdx) + } else { + data.EgressACL = append(data.EgressACL, aclIdx) + } + } + interfaceData = append(interfaceData, data) + } + } + + // sort interfaces for every ACL + for _, aclIdx := range indices { + var ingress []string + var egress []string + for _, data := range interfaceData { + // look for ingress + for _, ingressACLIdx := range data.IngressACL { + if ingressACLIdx == aclIdx { + name, _, found := h.ifIndexes.LookupBySwIfIndex(data.SwIfIdx) + if !found { + continue + } + ingress = append(ingress, name) + } + } + // look for egress + for _, egressACLIdx := range data.EgressACL { + if egressACLIdx == aclIdx { + name, _, found := h.ifIndexes.LookupBySwIfIndex(data.SwIfIdx) + if !found { + continue + } + egress = append(egress, name) + } + } + } + + aclsWithInterfaces[aclIdx] = &acl.ACL_Interfaces{ + Egress: egress, + Ingress: ingress, + } + } + + return aclsWithInterfaces, wasErr +} + +// DumpMACIPACLInterfaces implements ACL handler. +func (h *ACLVppHandler) DumpMACIPACLInterfaces(indices []uint32) (map[uint32]*acl.ACL_Interfaces, error) { + // list of ACL-to-interfaces + aclsWithInterfaces := make(map[uint32]*acl.ACL_Interfaces) + + var interfaceData []*vppcalls.ACLToInterface + + msgMACIP := &vpp_acl.MacipACLInterfaceListDump{ + SwIfIndex: 0xffffffff, // dump all + } + reqMACIP := h.callsChannel.SendMultiRequest(msgMACIP) + for { + replyMACIP := &vpp_acl.MacipACLInterfaceListDetails{} + stop, err := reqMACIP.ReceiveReply(replyMACIP) + if stop { + break + } + if err != nil { + return nil, fmt.Errorf("MACIP ACL interface list dump reply error: %v", err) + } + if replyMACIP.Count > 0 { + data := &vppcalls.ACLToInterface{ + SwIfIdx: uint32(replyMACIP.SwIfIndex), + } + data.IngressACL = append(data.IngressACL, replyMACIP.Acls...) + interfaceData = append(interfaceData, data) + } + } + + for _, aclIdx := range indices { + var ingress []string + for _, data := range interfaceData { + // look for ingress + for _, ingressACLIdx := range data.IngressACL { + if ingressACLIdx == aclIdx { + name, _, found := h.ifIndexes.LookupBySwIfIndex(data.SwIfIdx) + if !found { + continue + } + ingress = append(ingress, name) + } + } + } + var ifaces *acl.ACL_Interfaces + if len(ingress) > 0 { + ifaces = &acl.ACL_Interfaces{ + Egress: nil, + Ingress: ingress, + } + } + aclsWithInterfaces[aclIdx] = ifaces + } + + return aclsWithInterfaces, nil +} + +// DumpIPAcls implements ACL handler. +func (h *ACLVppHandler) DumpIPAcls() (map[vppcalls.ACLMeta][]acl_types.ACLRule, error) { + aclIPRules := make(map[vppcalls.ACLMeta][]acl_types.ACLRule) + var wasErr error + + req := &vpp_acl.ACLDump{ + ACLIndex: 0xffffffff, + } + reqContext := h.callsChannel.SendMultiRequest(req) + for { + msg := &vpp_acl.ACLDetails{} + stop, err := reqContext.ReceiveReply(msg) + if err != nil { + return aclIPRules, fmt.Errorf("ACL dump reply error: %v", err) + } + if stop { + break + } + + metadata := vppcalls.ACLMeta{ + Index: msg.ACLIndex, + Tag: strings.Trim(msg.Tag, "\x00"), + } + + aclIPRules[metadata] = msg.R + } + + return aclIPRules, wasErr +} + +// DumpMacIPAcls implements ACL handler. +func (h *ACLVppHandler) DumpMacIPAcls() (map[vppcalls.ACLMeta][]acl_types.MacipACLRule, error) { + aclMACIPRules := make(map[vppcalls.ACLMeta][]acl_types.MacipACLRule) + + req := &vpp_acl.MacipACLDump{ + ACLIndex: 0xffffffff, + } + reqContext := h.callsChannel.SendMultiRequest(req) + for { + msg := &vpp_acl.MacipACLDetails{} + stop, err := reqContext.ReceiveReply(msg) + if err != nil { + return nil, fmt.Errorf("ACL MACIP dump reply error: %v", err) + } + if stop { + break + } + + metadata := vppcalls.ACLMeta{ + Index: msg.ACLIndex, + Tag: strings.Trim(msg.Tag, "\x00"), + } + + aclMACIPRules[metadata] = msg.R + } + return aclMACIPRules, nil +} + +// DumpInterfaceACLs implements ACL handler. +func (h *ACLVppHandler) DumpInterfaceACLs(swIndex uint32) (acls []*acl.ACL, err error) { + res, err := h.DumpInterfaceACLList(swIndex) + if err != nil { + return nil, err + } + + if uint32(res.SwIfIndex) != swIndex { + return nil, fmt.Errorf("returned interface index %d does not match request", res.SwIfIndex) + } + + for aidx := range res.Acls { + ipACL, err := h.getIPACLDetails(uint32(aidx)) + if err != nil { + return nil, err + } + acls = append(acls, ipACL) + } + return acls, nil +} + +// DumpInterfaceMACIPACLs implements ACL handler. +func (h *ACLVppHandler) DumpInterfaceMACIPACLs(swIndex uint32) (acls []*acl.ACL, err error) { + resMacIP, err := h.DumpInterfaceMACIPACLList(swIndex) + if err != nil { + return nil, err + } + + if uint32(resMacIP.SwIfIndex) != swIndex { + return nil, fmt.Errorf("returned interface index %d does not match request", resMacIP.SwIfIndex) + } + + for aidx := range resMacIP.Acls { + macipACL, err := h.getMACIPACLDetails(uint32(aidx)) + if err != nil { + return nil, err + } + acls = append(acls, macipACL) + } + return acls, nil +} + +// DumpInterfaceACLList implements ACL handler. +func (h *ACLVppHandler) DumpInterfaceACLList(swIndex uint32) (*vpp_acl.ACLInterfaceListDetails, error) { + req := &vpp_acl.ACLInterfaceListDump{ + SwIfIndex: interface_types.InterfaceIndex(swIndex), + } + reply := &vpp_acl.ACLInterfaceListDetails{} + + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return nil, err + } + + return reply, nil +} + +// DumpInterfaceMACIPACLList implements ACL handler. +func (h *ACLVppHandler) DumpInterfaceMACIPACLList(swIndex uint32) (*vpp_acl.MacipACLInterfaceListDetails, error) { + req := &vpp_acl.MacipACLInterfaceListDump{ + SwIfIndex: interface_types.InterfaceIndex(swIndex), + } + reply := &vpp_acl.MacipACLInterfaceListDetails{} + + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return nil, err + } + + return reply, nil +} + +// DumpInterfacesLists implements ACL handler. +func (h *ACLVppHandler) DumpInterfacesLists() ([]*vpp_acl.ACLInterfaceListDetails, []*vpp_acl.MacipACLInterfaceListDetails, error) { + msgIPACL := &vpp_acl.ACLInterfaceListDump{ + SwIfIndex: 0xffffffff, // dump all + } + + reqIPACL := h.callsChannel.SendMultiRequest(msgIPACL) + + var IPaclInterfaces []*vpp_acl.ACLInterfaceListDetails + for { + reply := &vpp_acl.ACLInterfaceListDetails{} + stop, err := reqIPACL.ReceiveReply(reply) + if stop { + break + } + if err != nil { + logrus.DefaultLogger().Error(err) + return nil, nil, err + } + IPaclInterfaces = append(IPaclInterfaces, reply) + } + + msgMACIPACL := &vpp_acl.ACLInterfaceListDump{ + SwIfIndex: 0xffffffff, // dump all + } + + reqMACIPACL := h.callsChannel.SendMultiRequest(msgMACIPACL) + + var MACIPaclInterfaces []*vpp_acl.MacipACLInterfaceListDetails + for { + reply := &vpp_acl.MacipACLInterfaceListDetails{} + stop, err := reqMACIPACL.ReceiveReply(reply) + if stop { + break + } + if err != nil { + logrus.DefaultLogger().Error(err) + return nil, nil, err + } + MACIPaclInterfaces = append(MACIPaclInterfaces, reply) + } + + return IPaclInterfaces, MACIPaclInterfaces, nil +} + +func (h *ACLVppHandler) getIPRuleDetails(rule acl_types.ACLRule) (*acl.ACL_Rule, error) { + // Resolve rule actions + aclAction, err := h.resolveRuleAction(rule.IsPermit) + if err != nil { + return nil, err + } + + return &acl.ACL_Rule{ + Action: aclAction, + IpRule: h.getIPRuleMatches(rule), + }, nil +} + +// getIPACLDetails gets details for a given IP ACL from VPP and translates +// them from the binary VPP API format into the ACL Plugin's NB format. +func (h *ACLVppHandler) getIPACLDetails(idx uint32) (aclRule *acl.ACL, err error) { + req := &vpp_acl.ACLDump{ + ACLIndex: uint32(idx), + } + + reply := &vpp_acl.ACLDetails{} + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return nil, err + } + + var ruleData []*acl.ACL_Rule + for _, r := range reply.R { + rule := &acl.ACL_Rule{} + + ipRule, err := h.getIPRuleDetails(r) + if err != nil { + return nil, err + } + + aclAction, err := h.resolveRuleAction(r.IsPermit) + if err != nil { + return nil, err + } + + rule.IpRule = ipRule.GetIpRule() + rule.Action = aclAction + ruleData = append(ruleData, rule) + } + + return &acl.ACL{Rules: ruleData, Name: strings.Trim(reply.Tag, "\x00")}, nil +} + +func (h *ACLVppHandler) getMACIPRuleDetails(rule acl_types.MacipACLRule) (*acl.ACL_Rule, error) { + // Resolve rule actions + aclAction, err := h.resolveRuleAction(rule.IsPermit) + if err != nil { + return nil, err + } + + return &acl.ACL_Rule{ + Action: aclAction, + MacipRule: h.getMACIPRuleMatches(rule), + }, nil +} + +// getMACIPACLDetails gets details for a given MACIP ACL from VPP and translates +// them from the binary VPP API format into the ACL Plugin's NB format. +func (h *ACLVppHandler) getMACIPACLDetails(idx uint32) (aclRule *acl.ACL, err error) { + req := &vpp_acl.MacipACLDump{ + ACLIndex: uint32(idx), + } + + reply := &vpp_acl.MacipACLDetails{} + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return nil, err + } + + var ruleData []*acl.ACL_Rule + for _, r := range reply.R { + rule := &acl.ACL_Rule{} + + ipRule, err := h.getMACIPRuleDetails(r) + if err != nil { + return nil, err + } + + aclAction, err := h.resolveRuleAction(r.IsPermit) + if err != nil { + return nil, err + } + + rule.IpRule = ipRule.GetIpRule() + rule.Action = aclAction + ruleData = append(ruleData, rule) + } + + return &acl.ACL{Rules: ruleData, Name: strings.Trim(reply.Tag, "\x00")}, nil +} + +// getIPRuleMatches translates an IP rule from the binary VPP API format into the +// ACL Plugin's NB format +func (h *ACLVppHandler) getIPRuleMatches(r acl_types.ACLRule) *acl.ACL_Rule_IpRule { + srcNet := prefixToString(r.SrcPrefix) + dstNet := prefixToString(r.DstPrefix) + + ipRule := &acl.ACL_Rule_IpRule{ + Ip: &acl.ACL_Rule_IpRule_Ip{ + SourceNetwork: srcNet, + DestinationNetwork: dstNet, + Protocol: uint32(r.Proto), + }, + } + + switch r.Proto { + case vppcalls.TCPProto: + ipRule.Tcp = h.getTCPMatchRule(r) + case vppcalls.UDPProto: + ipRule.Udp = h.getUDPMatchRule(r) + case vppcalls.ICMPv4Proto, vppcalls.ICMPv6Proto: + ipRule.Icmp = h.getIcmpMatchRule(r) + } + return ipRule +} + +// getMACIPRuleMatches translates an MACIP rule from the binary VPP API format into the +// ACL Plugin's NB format +func (h *ACLVppHandler) getMACIPRuleMatches(rule acl_types.MacipACLRule) *acl.ACL_Rule_MacIpRule { + srcAddr := addressToIP(rule.SrcPrefix.Address) + srcMacAddr := net.HardwareAddr(rule.SrcMac[:]) + srcMacAddrMask := net.HardwareAddr(rule.SrcMacMask[:]) + return &acl.ACL_Rule_MacIpRule{ + SourceAddress: srcAddr.String(), + SourceAddressPrefix: uint32(rule.SrcPrefix.Len), + SourceMacAddress: srcMacAddr.String(), + SourceMacAddressMask: srcMacAddrMask.String(), + } +} + +// getTCPMatchRule translates a TCP match rule from the binary VPP API format +// into the ACL Plugin's NB format +func (h *ACLVppHandler) getTCPMatchRule(r acl_types.ACLRule) *acl.ACL_Rule_IpRule_Tcp { + dstPortRange := &acl.ACL_Rule_IpRule_PortRange{ + LowerPort: uint32(r.DstportOrIcmpcodeFirst), + UpperPort: uint32(r.DstportOrIcmpcodeLast), + } + srcPortRange := &acl.ACL_Rule_IpRule_PortRange{ + LowerPort: uint32(r.SrcportOrIcmptypeFirst), + UpperPort: uint32(r.SrcportOrIcmptypeLast), + } + tcp := acl.ACL_Rule_IpRule_Tcp{ + DestinationPortRange: dstPortRange, + SourcePortRange: srcPortRange, + TcpFlagsMask: uint32(r.TCPFlagsMask), + TcpFlagsValue: uint32(r.TCPFlagsValue), + } + return &tcp +} + +// getUDPMatchRule translates a UDP match rule from the binary VPP API format +// into the ACL Plugin's NB format +func (h *ACLVppHandler) getUDPMatchRule(r acl_types.ACLRule) *acl.ACL_Rule_IpRule_Udp { + dstPortRange := &acl.ACL_Rule_IpRule_PortRange{ + LowerPort: uint32(r.DstportOrIcmpcodeFirst), + UpperPort: uint32(r.DstportOrIcmpcodeLast), + } + srcPortRange := &acl.ACL_Rule_IpRule_PortRange{ + LowerPort: uint32(r.SrcportOrIcmptypeFirst), + UpperPort: uint32(r.SrcportOrIcmptypeLast), + } + udp := acl.ACL_Rule_IpRule_Udp{ + DestinationPortRange: dstPortRange, + SourcePortRange: srcPortRange, + } + return &udp +} + +// getIcmpMatchRule translates an ICMP match rule from the binary VPP API +// format into the ACL Plugin's NB format +func (h *ACLVppHandler) getIcmpMatchRule(r acl_types.ACLRule) *acl.ACL_Rule_IpRule_Icmp { + icmp := &acl.ACL_Rule_IpRule_Icmp{ + Icmpv6: r.Proto == ip_types.IP_API_PROTO_ICMP6, + IcmpCodeRange: &acl.ACL_Rule_IpRule_Icmp_Range{ + First: uint32(r.DstportOrIcmpcodeFirst), + Last: uint32(r.DstportOrIcmpcodeLast), + }, + IcmpTypeRange: &acl.ACL_Rule_IpRule_Icmp_Range{ + First: uint32(r.SrcportOrIcmptypeFirst), + Last: uint32(r.SrcportOrIcmptypeLast), + }, + } + return icmp +} + +// Returns rule action representation in model according to the vpp input +func (h *ACLVppHandler) resolveRuleAction(isPermit acl_types.ACLAction) (acl.ACL_Rule_Action, error) { + switch isPermit { + case acl_types.ACL_ACTION_API_DENY: + return acl.ACL_Rule_DENY, nil + case acl_types.ACL_ACTION_API_PERMIT: + return acl.ACL_Rule_PERMIT, nil + case acl_types.ACL_ACTION_API_PERMIT_REFLECT: + return acl.ACL_Rule_REFLECT, nil + default: + return acl.ACL_Rule_DENY, fmt.Errorf("invalid match rule %v", isPermit) + } +} diff --git a/plugins/vpp/aclplugin/vppcalls/vpp2306/dump_vppcalls_test.go b/plugins/vpp/aclplugin/vppcalls/vpp2306/dump_vppcalls_test.go new file mode 100644 index 0000000000..0fd3ff32e4 --- /dev/null +++ b/plugins/vpp/aclplugin/vppcalls/vpp2306/dump_vppcalls_test.go @@ -0,0 +1,453 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "testing" + + . "github.com/onsi/gomega" + "go.ligato.io/cn-infra/v2/logging/logrus" + + "go.ligato.io/vpp-agent/v3/plugins/vpp/aclplugin/vppcalls" + vpp_acl "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/acl" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/acl_types" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ethernet_types" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip_types" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/memclnt" + "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/ifaceidx" +) + +// Test translation of IP rule into ACL Plugin's format +func TestGetIPRuleMatch(t *testing.T) { + ctx := setupACLTest(t) + defer ctx.teardownACLTest() + + icmpV4Rule := ctx.aclHandler.getIPRuleMatches(acl_types.ACLRule{ + DstPrefix: ip_types.Prefix{ + Address: ip_types.Address{ + Af: ip_types.ADDRESS_IP4, + Un: ip_types.AddressUnionIP4(ip_types.IP4Address{20, 0, 0, 1}), + }, + Len: 24, + }, + SrcPrefix: ip_types.Prefix{ + Address: ip_types.Address{ + Af: ip_types.ADDRESS_IP4, + Un: ip_types.AddressUnionIP4(ip_types.IP4Address{10, 0, 0, 1}), + }, + Len: 24, + }, + Proto: vppcalls.ICMPv4Proto, + }) + if icmpV4Rule.GetIcmp() == nil { + t.Fatal("should have icmp match") + } + + icmpV6Rule := ctx.aclHandler.getIPRuleMatches(acl_types.ACLRule{ + SrcPrefix: ip_types.Prefix{ + Address: ip_types.Address{ + Af: ip_types.ADDRESS_IP6, + Un: ip_types.AddressUnionIP6(ip_types.IP6Address{'d', 'e', 'd', 'd', 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}), + }, + Len: 64, + }, + DstPrefix: ip_types.Prefix{ + Address: ip_types.Address{ + Af: ip_types.ADDRESS_IP6, + Un: ip_types.AddressUnionIP6(ip_types.IP6Address{'d', 'e', 'd', 'd', 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}), + }, + Len: 32, + }, + Proto: vppcalls.ICMPv6Proto, + }) + if icmpV6Rule.GetIcmp() == nil { + t.Fatal("should have icmpv6 match") + } + + tcpRule := ctx.aclHandler.getIPRuleMatches(acl_types.ACLRule{ + DstPrefix: ip_types.Prefix{ + Address: ip_types.Address{ + Af: ip_types.ADDRESS_IP4, + Un: ip_types.AddressUnionIP4(ip_types.IP4Address{20, 0, 0, 1}), + }, + Len: 24, + }, + SrcPrefix: ip_types.Prefix{ + Address: ip_types.Address{ + Af: ip_types.ADDRESS_IP4, + Un: ip_types.AddressUnionIP4(ip_types.IP4Address{10, 0, 0, 1}), + }, + Len: 24, + }, + Proto: vppcalls.TCPProto, + }) + if tcpRule.GetTcp() == nil { + t.Fatal("should have tcp match") + } + + udpRule := ctx.aclHandler.getIPRuleMatches(acl_types.ACLRule{ + DstPrefix: ip_types.Prefix{ + Address: ip_types.Address{ + Af: ip_types.ADDRESS_IP4, + Un: ip_types.AddressUnionIP4(ip_types.IP4Address{20, 0, 0, 1}), + }, + Len: 24, + }, + SrcPrefix: ip_types.Prefix{ + Address: ip_types.Address{ + Af: ip_types.ADDRESS_IP4, + Un: ip_types.AddressUnionIP4(ip_types.IP4Address{10, 0, 0, 1}), + }, + Len: 24, + }, + Proto: vppcalls.UDPProto, + }) + if udpRule.GetUdp() == nil { + t.Fatal("should have udp match") + } +} + +// Test translation of MACIP rule into ACL Plugin's format +func TestGetMACIPRuleMatches(t *testing.T) { + ctx := setupACLTest(t) + defer ctx.teardownACLTest() + + macipV4Rule := ctx.aclHandler.getMACIPRuleMatches(acl_types.MacipACLRule{ + IsPermit: 1, + SrcMac: ethernet_types.MacAddress{2, 'd', 'e', 'a', 'd', 2}, + SrcMacMask: ethernet_types.MacAddress{0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, + SrcPrefix: ip_types.Prefix{ + Address: ip_types.Address{ + Af: ip_types.ADDRESS_IP4, + Un: ip_types.AddressUnionIP4(ip_types.IP4Address{10, 0, 0, 1}), + }, + Len: 32, + }, + }) + if macipV4Rule.GetSourceMacAddress() == "" { + t.Fatal("should have mac match") + } + macipV6Rule := ctx.aclHandler.getMACIPRuleMatches(acl_types.MacipACLRule{ + IsPermit: 0, + SrcMac: ethernet_types.MacAddress{2, 'd', 'e', 'a', 'd', 2}, + SrcMacMask: ethernet_types.MacAddress{0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, + SrcPrefix: ip_types.Prefix{ + Address: ip_types.Address{ + Af: ip_types.ADDRESS_IP6, + Un: ip_types.AddressUnionIP6(ip_types.IP6Address{'d', 'e', 'd', 'd', 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}), + }, + Len: 64, + }, + }) + if macipV6Rule.GetSourceMacAddress() == "" { + t.Fatal("should have mac match") + } +} + +// Test dumping of IP rules +func TestDumpIPACL(t *testing.T) { + ctx := setupACLTest(t) + defer ctx.teardownACLTest() + + ctx.MockVpp.MockReply( + &vpp_acl.ACLDetails{ + ACLIndex: 0, + Tag: "acl1", + Count: 1, + R: []acl_types.ACLRule{{IsPermit: 1}}, + }, + &vpp_acl.ACLDetails{ + ACLIndex: 1, + Tag: "acl2", + Count: 2, + R: []acl_types.ACLRule{{IsPermit: 0}, {IsPermit: 2}}, + }, + &vpp_acl.ACLDetails{ + ACLIndex: 2, + Tag: "acl3", + Count: 3, + R: []acl_types.ACLRule{{IsPermit: 0}, {IsPermit: 1}, {IsPermit: 2}}, + }) + ctx.MockVpp.MockReply(&memclnt.ControlPingReply{}) + ctx.MockVpp.MockReply(&vpp_acl.ACLInterfaceListDetails{ + SwIfIndex: 1, + Count: 2, + NInput: 1, + Acls: []uint32{0, 2}, + }) + ctx.MockVpp.MockReply(&memclnt.ControlPingReply{}) + + ctx.ifIndexes.Put("if0", &ifaceidx.IfaceMetadata{SwIfIndex: 1}) + + ifaces, err := ctx.aclHandler.DumpACL() + Expect(err).To(Succeed()) + Expect(ifaces).To(HaveLen(3)) + //Expect(ifaces[0].Identifier.ACLIndex).To(Equal(uint32(0))) + //Expect(ifaces[0].vppcalls.ACLDetails.Rules[0].AclAction).To(Equal(uint32(1))) + //Expect(ifaces[1].Identifier.ACLIndex).To(Equal(uint32(1))) + //Expect(ifaces[2].Identifier.ACLIndex).To(Equal(uint32(2))) +} + +// Test dumping of MACIP rules +func TestDumpMACIPACL(t *testing.T) { + ctx := setupACLTest(t) + defer ctx.teardownACLTest() + + ctx.MockVpp.MockReply( + &vpp_acl.MacipACLDetails{ + ACLIndex: 0, + Tag: "acl1", + Count: 1, + R: []acl_types.MacipACLRule{{IsPermit: 1}}, + }, + &vpp_acl.MacipACLDetails{ + ACLIndex: 1, + Tag: "acl2", + Count: 2, + R: []acl_types.MacipACLRule{{IsPermit: 0}, {IsPermit: 2}}, + }, + &vpp_acl.MacipACLDetails{ + ACLIndex: 2, + Tag: "acl3", + Count: 3, + R: []acl_types.MacipACLRule{{IsPermit: 0}, {IsPermit: 1}, {IsPermit: 2}}, + }) + ctx.MockVpp.MockReply(&memclnt.ControlPingReply{}) + ctx.MockVpp.MockReply(&vpp_acl.MacipACLInterfaceListDetails{ + SwIfIndex: 1, + Count: 2, + Acls: []uint32{0, 2}, + }) + ctx.MockVpp.MockReply(&memclnt.ControlPingReply{}) + + swIfIndexes := ifaceidx.NewIfaceIndex(logrus.DefaultLogger(), "test") + swIfIndexes.Put("if0", &ifaceidx.IfaceMetadata{SwIfIndex: 1}) + + ifaces, err := ctx.aclHandler.DumpMACIPACL() + Expect(err).To(Succeed()) + Expect(ifaces).To(HaveLen(3)) + //Expect(ifaces[0].Identifier.ACLIndex).To(Equal(uint32(0))) + //Expect(ifaces[0].vppcalls.ACLDetails.Rules[0].AclAction).To(Equal(uint32(1))) + //Expect(ifaces[1].Identifier.ACLIndex).To(Equal(uint32(1))) + //Expect(ifaces[2].Identifier.ACLIndex).To(Equal(uint32(2))) +} + +// Test dumping of interfaces with assigned IP rules +func TestDumpACLInterfaces(t *testing.T) { + ctx := setupACLTest(t) + defer ctx.teardownACLTest() + + ctx.MockVpp.MockReply(&vpp_acl.ACLInterfaceListDetails{ + SwIfIndex: 1, + Count: 2, + NInput: 1, + Acls: []uint32{0, 2}, + }) + ctx.MockVpp.MockReply(&memclnt.ControlPingReply{}) + + ctx.ifIndexes.Put("if0", &ifaceidx.IfaceMetadata{SwIfIndex: 1}) + + indexes := []uint32{0, 2} + ifaces, err := ctx.aclHandler.DumpACLInterfaces(indexes) + Expect(err).To(Succeed()) + Expect(ifaces).To(HaveLen(2)) + Expect(ifaces[0].Ingress).To(Equal([]string{"if0"})) + Expect(ifaces[2].Egress).To(Equal([]string{"if0"})) +} + +// Test dumping of interfaces with assigned MACIP rules +func TestDumpMACIPACLInterfaces(t *testing.T) { + ctx := setupACLTest(t) + defer ctx.teardownACLTest() + + ctx.MockVpp.MockReply(&vpp_acl.MacipACLInterfaceListDetails{ + SwIfIndex: 1, + Count: 2, + Acls: []uint32{0, 1}, + }) + ctx.MockVpp.MockReply(&memclnt.ControlPingReply{}) + + ctx.ifIndexes.Put("if0", &ifaceidx.IfaceMetadata{SwIfIndex: 1}) + + indexes := []uint32{0, 1} + ifaces, err := ctx.aclHandler.DumpMACIPACLInterfaces(indexes) + Expect(err).To(Succeed()) + Expect(ifaces).To(HaveLen(2)) + Expect(ifaces[0].Ingress).To(Equal([]string{"if0"})) + Expect(ifaces[0].Egress).To(BeNil()) + Expect(ifaces[1].Ingress).To(Equal([]string{"if0"})) + Expect(ifaces[1].Egress).To(BeNil()) +} + +// Test dumping of all configured ACLs with IP-type ruleData +func TestDumpIPAcls(t *testing.T) { + ctx := setupACLTest(t) + defer ctx.teardownACLTest() + + ctx.MockVpp.MockReply(&vpp_acl.ACLDetails{ + ACLIndex: 0, + Count: 1, + R: []acl_types.ACLRule{{IsPermit: 1}}, + }) + ctx.MockVpp.MockReply(&memclnt.ControlPingReply{}) + + IPRuleACLs, err := ctx.aclHandler.DumpIPAcls() + Expect(err).To(Succeed()) + Expect(IPRuleACLs).To(HaveLen(1)) +} + +// Test dumping of all configured ACLs with MACIP-type ruleData +func TestDumpMacIPAcls(t *testing.T) { + ctx := setupACLTest(t) + defer ctx.teardownACLTest() + + ctx.MockVpp.MockReply(&vpp_acl.MacipACLDetails{ + ACLIndex: 0, + Count: 1, + R: []acl_types.MacipACLRule{{IsPermit: 1}}, + }) + ctx.MockVpp.MockReply(&memclnt.ControlPingReply{}) + + MacIPRuleACLs, err := ctx.aclHandler.DumpMacIPAcls() + Expect(err).To(Succeed()) + Expect(MacIPRuleACLs).To(HaveLen(1)) +} + +func TestDumpInterfaceIPAcls(t *testing.T) { + ctx := setupACLTest(t) + defer ctx.teardownACLTest() + + ctx.MockVpp.MockReply(&vpp_acl.ACLInterfaceListDetails{ + SwIfIndex: 0, + Count: 2, + NInput: 1, + Acls: []uint32{0, 1}, + }) + ctx.MockVpp.MockReply(&vpp_acl.ACLDetails{ + ACLIndex: 0, + Count: 1, + R: []acl_types.ACLRule{{IsPermit: 1}, {IsPermit: 0}}, + }) + ctx.MockVpp.MockReply(&vpp_acl.ACLDetails{ + ACLIndex: 1, + Count: 1, + R: []acl_types.ACLRule{{IsPermit: 2}, {IsPermit: 0}}, + }) + + ACLs, err := ctx.aclHandler.DumpInterfaceACLs(0) + Expect(err).To(Succeed()) + Expect(ACLs).To(HaveLen(2)) +} + +func TestDumpInterfaceMACIPAcls(t *testing.T) { + ctx := setupACLTest(t) + defer ctx.teardownACLTest() + + ctx.MockVpp.MockReply(&vpp_acl.MacipACLInterfaceListDetails{ + SwIfIndex: 0, + Count: 2, + Acls: []uint32{0, 1}, + }) + ctx.MockVpp.MockReply(&vpp_acl.MacipACLDetails{ + ACLIndex: 0, + Count: 1, + R: []acl_types.MacipACLRule{{IsPermit: 1}, {IsPermit: 0}}, + }) + ctx.MockVpp.MockReply(&vpp_acl.MacipACLDetails{ + ACLIndex: 1, + Count: 1, + R: []acl_types.MacipACLRule{{IsPermit: 2}, {IsPermit: 1}}, + }) + + ACLs, err := ctx.aclHandler.DumpInterfaceMACIPACLs(0) + Expect(err).To(Succeed()) + Expect(ACLs).To(HaveLen(2)) +} + +func TestDumpInterface(t *testing.T) { + ctx := setupACLTest(t) + defer ctx.teardownACLTest() + + ctx.MockVpp.MockReply(&vpp_acl.ACLInterfaceListDetails{ + SwIfIndex: 0, + Count: 2, + NInput: 1, + Acls: []uint32{0, 1}, + }) + IPacls, err := ctx.aclHandler.DumpInterfaceACLList(0) + Expect(err).To(BeNil()) + Expect(IPacls.Acls).To(HaveLen(2)) + + ctx.MockVpp.MockReply(&vpp_acl.ACLInterfaceListDetails{}) + IPacls, err = ctx.aclHandler.DumpInterfaceACLList(0) + Expect(err).To(BeNil()) + Expect(IPacls.Acls).To(HaveLen(0)) + + ctx.MockVpp.MockReply(&vpp_acl.MacipACLInterfaceListDetails{ + SwIfIndex: 0, + Count: 2, + Acls: []uint32{0, 1}, + }) + MACIPacls, err := ctx.aclHandler.DumpInterfaceMACIPACLList(0) + Expect(err).To(BeNil()) + Expect(MACIPacls.Acls).To(HaveLen(2)) + + ctx.MockVpp.MockReply(&vpp_acl.MacipACLInterfaceListDetails{}) + MACIPacls, err = ctx.aclHandler.DumpInterfaceMACIPACLList(0) + Expect(err).To(BeNil()) + Expect(MACIPacls.Acls).To(HaveLen(0)) +} + +func TestDumpInterfaces(t *testing.T) { + ctx := setupACLTest(t) + defer ctx.teardownACLTest() + + ctx.MockVpp.MockReply( + &vpp_acl.ACLInterfaceListDetails{ + SwIfIndex: 0, + Count: 2, + NInput: 1, + Acls: []uint32{0, 1}, + }, + &vpp_acl.ACLInterfaceListDetails{ + SwIfIndex: 1, + Count: 1, + NInput: 1, + Acls: []uint32{2}, + }, + &vpp_acl.ACLInterfaceListDetails{ + SwIfIndex: 2, + Count: 2, + NInput: 1, + Acls: []uint32{3, 4}, + }) + ctx.MockVpp.MockReply(&memclnt.ControlPingReply{}) + ctx.MockVpp.MockReply(&vpp_acl.MacipACLInterfaceListDetails{ + SwIfIndex: 3, + Count: 2, + Acls: []uint32{6, 7}, + }, + &vpp_acl.MacipACLInterfaceListDetails{ + SwIfIndex: 4, + Count: 1, + Acls: []uint32{5}, + }) + ctx.MockVpp.MockReply(&memclnt.ControlPingReply{}) + + IPacls, MACIPacls, err := ctx.aclHandler.DumpInterfacesLists() + Expect(err).To(BeNil()) + Expect(IPacls).To(HaveLen(3)) + Expect(MACIPacls).To(HaveLen(2)) +} diff --git a/plugins/vpp/aclplugin/vppcalls/vpp2306/interfaces_vppcalls.go b/plugins/vpp/aclplugin/vppcalls/vpp2306/interfaces_vppcalls.go new file mode 100644 index 0000000000..23e3890c5a --- /dev/null +++ b/plugins/vpp/aclplugin/vppcalls/vpp2306/interfaces_vppcalls.go @@ -0,0 +1,335 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "fmt" + + vpp_acl "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/acl" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface_types" +) + +// SetACLToInterfacesAsIngress implements ACL handler. +func (h *ACLVppHandler) SetACLToInterfacesAsIngress(ACLIndex uint32, ifIndices []uint32) error { + return h.requestSetACLToInterfaces(&aclInterfaceLogicalReq{ + aclIndex: ACLIndex, + ifIndices: ifIndices, + ingress: true, + }) +} + +// RemoveACLFromInterfacesAsIngress implements ACL handler. +func (h *ACLVppHandler) RemoveACLFromInterfacesAsIngress(ACLIndex uint32, ifIndices []uint32) error { + return h.requestRemoveInterfacesFromACL(&aclInterfaceLogicalReq{ + aclIndex: ACLIndex, + ifIndices: ifIndices, + ingress: true, + }) +} + +// SetACLToInterfacesAsEgress implements ACL handler. +func (h *ACLVppHandler) SetACLToInterfacesAsEgress(ACLIndex uint32, ifIndices []uint32) error { + return h.requestSetACLToInterfaces(&aclInterfaceLogicalReq{ + aclIndex: ACLIndex, + ifIndices: ifIndices, + ingress: false, + }) +} + +// RemoveACLFromInterfacesAsEgress implements ACL handler. +func (h *ACLVppHandler) RemoveACLFromInterfacesAsEgress(ACLIndex uint32, ifIndices []uint32) error { + return h.requestRemoveInterfacesFromACL(&aclInterfaceLogicalReq{ + aclIndex: ACLIndex, + ifIndices: ifIndices, + ingress: false, + }) +} + +// AddACLToInterfaceAsIngress implements ACL handler. +func (h *ACLVppHandler) AddACLToInterfaceAsIngress(aclIndex uint32, ifName string) error { + meta, ok := h.ifIndexes.LookupByName(ifName) + if !ok { + return fmt.Errorf("metadata for interface %s not found", ifName) + } + ifIdx := meta.SwIfIndex + + req := &vpp_acl.ACLInterfaceAddDel{ + ACLIndex: aclIndex, + IsAdd: true, + SwIfIndex: interface_types.InterfaceIndex(ifIdx), + IsInput: true, + } + reply := &vpp_acl.ACLInterfaceAddDelReply{} + + err := h.callsChannel.SendRequest(req).ReceiveReply(reply) + if err != nil { + return fmt.Errorf("failed to add interface %d to ACL (L3/L4) %d as ingress: %v", ifIdx, aclIndex, err) + } + + return nil +} + +// AddACLToInterfaceAsEgress implements ACL handler. +func (h *ACLVppHandler) AddACLToInterfaceAsEgress(aclIndex uint32, ifName string) error { + meta, ok := h.ifIndexes.LookupByName(ifName) + if !ok { + return fmt.Errorf("metadata for interface %s not found", ifName) + } + ifIdx := meta.SwIfIndex + + req := &vpp_acl.ACLInterfaceAddDel{ + ACLIndex: aclIndex, + IsAdd: true, + SwIfIndex: interface_types.InterfaceIndex(ifIdx), + IsInput: false, + } + reply := &vpp_acl.ACLInterfaceAddDelReply{} + + err := h.callsChannel.SendRequest(req).ReceiveReply(reply) + if err != nil { + return fmt.Errorf("failed to add interface %d to ACL (L3/L4) %d as egress: %v", ifIdx, aclIndex, err) + } + + return nil +} + +// DeleteACLFromInterfaceAsIngress implements ACL handler. +func (h *ACLVppHandler) DeleteACLFromInterfaceAsIngress(aclIndex uint32, ifName string) error { + meta, ok := h.ifIndexes.LookupByName(ifName) + if !ok { + return fmt.Errorf("metadata for interface %s not found", ifName) + } + ifIdx := meta.SwIfIndex + + req := &vpp_acl.ACLInterfaceAddDel{ + ACLIndex: aclIndex, + IsAdd: false, + SwIfIndex: interface_types.InterfaceIndex(ifIdx), + IsInput: true, + } + reply := &vpp_acl.ACLInterfaceAddDelReply{} + + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return fmt.Errorf("failed to delete interface %d from ACL (L3/L4) %d as ingress: %v", ifIdx, aclIndex, err) + } + + return nil +} + +// DeleteACLFromInterfaceAsEgress implements ACL handler. +func (h *ACLVppHandler) DeleteACLFromInterfaceAsEgress(aclIndex uint32, ifName string) error { + meta, ok := h.ifIndexes.LookupByName(ifName) + if !ok { + return fmt.Errorf("metadata for interface %s not found", ifName) + } + ifIdx := meta.SwIfIndex + + req := &vpp_acl.ACLInterfaceAddDel{ + ACLIndex: aclIndex, + IsAdd: false, + SwIfIndex: interface_types.InterfaceIndex(ifIdx), + IsInput: false, + } + reply := &vpp_acl.ACLInterfaceAddDelReply{} + + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return fmt.Errorf("failed to delete interface %d from ACL (L3/L4) %d as egress: %v", ifIdx, aclIndex, err) + } + + return nil +} + +// AddMACIPACLToInterface implements ACL handler. +func (h *ACLVppHandler) AddMACIPACLToInterface(aclIndex uint32, ifName string) error { + meta, ok := h.ifIndexes.LookupByName(ifName) + if !ok { + return fmt.Errorf("metadata for interface %s not found", ifName) + } + ifIdx := meta.SwIfIndex + + req := &vpp_acl.MacipACLInterfaceAddDel{ + ACLIndex: aclIndex, + IsAdd: true, + SwIfIndex: interface_types.InterfaceIndex(ifIdx), + } + reply := &vpp_acl.MacipACLInterfaceAddDelReply{} + + err := h.callsChannel.SendRequest(req).ReceiveReply(reply) + if err != nil { + return fmt.Errorf("failed to add interface %d to MACIP ACL (L2) %d: %v", ifIdx, aclIndex, err) + } + + return nil +} + +// DeleteMACIPACLFromInterface implements ACL handler. +func (h *ACLVppHandler) DeleteMACIPACLFromInterface(aclIndex uint32, ifName string) error { + meta, ok := h.ifIndexes.LookupByName(ifName) + if !ok { + return fmt.Errorf("metadata for interface %s not found", ifName) + } + ifIdx := meta.SwIfIndex + + req := &vpp_acl.MacipACLInterfaceAddDel{ + ACLIndex: aclIndex, + IsAdd: false, + SwIfIndex: interface_types.InterfaceIndex(ifIdx), + } + reply := &vpp_acl.MacipACLInterfaceAddDelReply{} + + err := h.callsChannel.SendRequest(req).ReceiveReply(reply) + if err != nil { + return fmt.Errorf("failed to delete interface %d from MACIP ACL (L2) %d: %v", ifIdx, aclIndex, err) + } + + return nil +} + +// SetMACIPACLToInterfaces implements ACL handler. +func (h *ACLVppHandler) SetMACIPACLToInterfaces(aclIndex uint32, ifIndices []uint32) error { + for _, ifIdx := range ifIndices { + req := &vpp_acl.MacipACLInterfaceAddDel{ + ACLIndex: aclIndex, + IsAdd: true, + SwIfIndex: interface_types.InterfaceIndex(ifIdx), + } + reply := &vpp_acl.MacipACLInterfaceAddDelReply{} + + err := h.callsChannel.SendRequest(req).ReceiveReply(reply) + if err != nil { + return fmt.Errorf("failed to set interface %d to L2 ACL %d: %v", ifIdx, aclIndex, err) + } + } + + return nil +} + +// RemoveMACIPACLFromInterfaces implements ACL handler. +func (h *ACLVppHandler) RemoveMACIPACLFromInterfaces(removedACLIndex uint32, ifIndices []uint32) error { + for _, ifIdx := range ifIndices { + req := &vpp_acl.MacipACLInterfaceAddDel{ + ACLIndex: removedACLIndex, + SwIfIndex: interface_types.InterfaceIndex(ifIdx), + IsAdd: false, + } + reply := &vpp_acl.MacipACLInterfaceAddDelReply{} + + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return fmt.Errorf("failed to remove L2 ACL %d from interface %d: %v", removedACLIndex, ifIdx, err) + } + } + return nil +} + +// aclInterfaceLogicalReq groups multiple fields to not enumerate all of them in one function call +type aclInterfaceLogicalReq struct { + aclIndex uint32 + ifIndices []uint32 + ingress bool +} + +func (h *ACLVppHandler) requestSetACLToInterfaces(logicalReq *aclInterfaceLogicalReq) error { + for _, aclIfIdx := range logicalReq.ifIndices { + // Create acl list with new entry + var ACLs []uint32 + + // All previously assigned ACLs have to be dumped and added to acl list + aclInterfaceDetails, err := h.DumpInterfaceACLList(aclIfIdx) + if err != nil { + return err + } + + var nInput uint8 + if aclInterfaceDetails != nil { + nInput = aclInterfaceDetails.NInput + if logicalReq.ingress { + // Construct ACL list. ACLs within NInput are defined as ingress, so provided new aclIndex has to be + // added to the beginning of the list + // TODO it would be nicer to add new acl index to newNInput index + ACLs = append(ACLs, logicalReq.aclIndex) + ACLs = append(ACLs, aclInterfaceDetails.Acls...) + nInput++ // Rise NInput + } else { + // Construct ACL list. ACLs outside of NInput are defined as egress, so provided new aclIndex has to be + // added to the end of the list + ACLs = append(ACLs, aclInterfaceDetails.Acls...) + ACLs = append(ACLs, logicalReq.aclIndex) + // NInput remains the same + } + } + + msg := &vpp_acl.ACLInterfaceSetACLList{ + Acls: ACLs, + Count: uint8(len(ACLs)), + SwIfIndex: interface_types.InterfaceIndex(aclIfIdx), + NInput: nInput, + } + reply := &vpp_acl.ACLInterfaceSetACLListReply{} + + err = h.callsChannel.SendRequest(msg).ReceiveReply(reply) + if err != nil { + return err + } + } + + return nil +} + +func (h *ACLVppHandler) requestRemoveInterfacesFromACL(logicalReq *aclInterfaceLogicalReq) error { + var wasErr error + for _, aclIfIdx := range logicalReq.ifIndices { + // Create empty ACL list + var ACLs []uint32 + + // All assigned ACLs have to be dumped + aclInterfaceDetails, err := h.DumpInterfaceACLList(aclIfIdx) + if err != nil { + return err + } + + // Reconstruct ACL list without removed ACL + var nInput uint8 + if aclInterfaceDetails != nil { + nInput = aclInterfaceDetails.NInput + for idx, aclIndex := range aclInterfaceDetails.Acls { + if (aclIndex != logicalReq.aclIndex) || + (logicalReq.ingress && idx >= int(aclInterfaceDetails.NInput)) || + (!logicalReq.ingress && idx < int(aclInterfaceDetails.NInput)) { + ACLs = append(ACLs, aclIndex) + } else { + // Decrease NInput if ingress, otherwise keep it the same + if logicalReq.ingress { + nInput-- + } + } + } + } + + msg := &vpp_acl.ACLInterfaceSetACLList{ + Acls: ACLs, + Count: uint8(len(ACLs)), + SwIfIndex: interface_types.InterfaceIndex(aclIfIdx), + NInput: nInput, + } + + reply := &vpp_acl.ACLInterfaceSetACLListReply{} + err = h.callsChannel.SendRequest(msg).ReceiveReply(reply) + if err != nil { + wasErr = err + } + } + + return wasErr +} diff --git a/plugins/vpp/aclplugin/vppcalls/vpp2306/interfaces_vppcalls_test.go b/plugins/vpp/aclplugin/vppcalls/vpp2306/interfaces_vppcalls_test.go new file mode 100644 index 0000000000..51259b2e2c --- /dev/null +++ b/plugins/vpp/aclplugin/vppcalls/vpp2306/interfaces_vppcalls_test.go @@ -0,0 +1,167 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "testing" + + . "github.com/onsi/gomega" + + vpp_acl "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2210/acl" +) + +// Test assignment of IP acl rule to given interface +func TestRequestSetACLToInterfaces(t *testing.T) { + ctx := setupACLTest(t) + defer ctx.teardownACLTest() + + ctx.MockVpp.MockReply(&vpp_acl.ACLInterfaceListDetails{ + SwIfIndex: 0, + Count: 1, + NInput: 1, + Acls: []uint32{0, 1}, + }) + ctx.MockVpp.MockReply(&vpp_acl.ACLInterfaceSetACLListReply{}) + err := ctx.aclHandler.SetACLToInterfacesAsIngress(0, []uint32{0}) + Expect(err).To(BeNil()) + + ctx.MockVpp.MockReply(&vpp_acl.ACLInterfaceListDetails{ + SwIfIndex: 0, + Count: 1, + NInput: 1, + Acls: []uint32{0, 1}, + }) + ctx.MockVpp.MockReply(&vpp_acl.ACLInterfaceSetACLListReply{}) + err = ctx.aclHandler.SetACLToInterfacesAsEgress(0, []uint32{0}) + Expect(err).To(BeNil()) + + // error cases + + ctx.MockVpp.MockReply(&vpp_acl.ACLInterfaceSetACLListReply{}) + err = ctx.aclHandler.SetACLToInterfacesAsIngress(0, []uint32{0}) + Expect(err).To(Not(BeNil())) + + ctx.MockVpp.MockReply(&vpp_acl.ACLInterfaceListDetails{ + SwIfIndex: 0, + Count: 1, + NInput: 1, + Acls: []uint32{0, 1}, + }) + ctx.MockVpp.MockReply(&vpp_acl.MacipACLAddReplaceReply{}) + err = ctx.aclHandler.SetACLToInterfacesAsIngress(0, []uint32{0}) + Expect(err).To(Not(BeNil())) + + ctx.MockVpp.MockReply(&vpp_acl.ACLInterfaceListDetails{ + SwIfIndex: 0, + Count: 1, + NInput: 1, + Acls: []uint32{0, 1}, + }) + ctx.MockVpp.MockReply(&vpp_acl.ACLInterfaceSetACLListReply{Retval: -1}) + err = ctx.aclHandler.SetACLToInterfacesAsIngress(0, []uint32{0}) + Expect(err).To(Not(BeNil())) +} + +// Test deletion of IP acl rule from given interface +func TestRequestRemoveInterfacesFromACL(t *testing.T) { + ctx := setupACLTest(t) + defer ctx.teardownACLTest() + + ctx.MockVpp.MockReply(&vpp_acl.ACLInterfaceListDetails{ + SwIfIndex: 0, + Count: 1, + NInput: 1, + Acls: []uint32{0, 1}, + }) + ctx.MockVpp.MockReply(&vpp_acl.ACLInterfaceSetACLListReply{}) + err := ctx.aclHandler.RemoveACLFromInterfacesAsIngress(0, []uint32{0}) + Expect(err).To(BeNil()) + + ctx.MockVpp.MockReply(&vpp_acl.ACLInterfaceListDetails{ + SwIfIndex: 0, + Count: 1, + NInput: 1, + Acls: []uint32{0, 1}, + }) + ctx.MockVpp.MockReply(&vpp_acl.ACLInterfaceSetACLListReply{}) + err = ctx.aclHandler.RemoveACLFromInterfacesAsEgress(0, []uint32{0}) + Expect(err).To(BeNil()) + + // error cases + + ctx.MockVpp.MockReply(&vpp_acl.ACLInterfaceSetACLListReply{}) + err = ctx.aclHandler.RemoveACLFromInterfacesAsEgress(0, []uint32{0}) + Expect(err).To(Not(BeNil())) + + ctx.MockVpp.MockReply(&vpp_acl.ACLInterfaceListDetails{ + SwIfIndex: 0, + Count: 1, + NInput: 1, + Acls: []uint32{0, 1}, + }) + ctx.MockVpp.MockReply(&vpp_acl.MacipACLAddReplaceReply{}) + err = ctx.aclHandler.RemoveACLFromInterfacesAsEgress(0, []uint32{0}) + Expect(err).To(Not(BeNil())) + + ctx.MockVpp.MockReply(&vpp_acl.ACLInterfaceListDetails{ + SwIfIndex: 0, + Count: 1, + NInput: 1, + Acls: []uint32{0, 1}, + }) + ctx.MockVpp.MockReply(&vpp_acl.ACLInterfaceSetACLListReply{Retval: -1}) + err = ctx.aclHandler.RemoveACLFromInterfacesAsEgress(0, []uint32{0}) + Expect(err).To(Not(BeNil())) +} + +// Test assignment of MACIP acl rule to given interface +func TestSetMacIPAclToInterface(t *testing.T) { + ctx := setupACLTest(t) + defer ctx.teardownACLTest() + + ctx.MockVpp.MockReply(&vpp_acl.MacipACLInterfaceAddDelReply{}) + err := ctx.aclHandler.SetMACIPACLToInterfaces(0, []uint32{0}) + Expect(err).To(BeNil()) + + // error cases + + ctx.MockVpp.MockReply(&vpp_acl.MacipACLAddReplaceReply{}) + err = ctx.aclHandler.SetMACIPACLToInterfaces(0, []uint32{0}) + Expect(err).To(Not(BeNil())) + + ctx.MockVpp.MockReply(&vpp_acl.MacipACLInterfaceAddDelReply{Retval: -1}) + err = ctx.aclHandler.SetMACIPACLToInterfaces(0, []uint32{0}) + Expect(err).To(Not(BeNil())) +} + +// Test deletion of MACIP acl rule from given interface +func TestRemoveMacIPIngressACLFromInterfaces(t *testing.T) { + ctx := setupACLTest(t) + defer ctx.teardownACLTest() + + ctx.MockVpp.MockReply(&vpp_acl.MacipACLInterfaceAddDelReply{}) + err := ctx.aclHandler.RemoveMACIPACLFromInterfaces(1, []uint32{0}) + Expect(err).To(BeNil()) + + // error cases + + ctx.MockVpp.MockReply(&vpp_acl.MacipACLAddReplaceReply{}) + err = ctx.aclHandler.RemoveMACIPACLFromInterfaces(0, []uint32{0}) + Expect(err).To(Not(BeNil())) + + ctx.MockVpp.MockReply(&vpp_acl.MacipACLInterfaceAddDelReply{Retval: -1}) + err = ctx.aclHandler.RemoveMACIPACLFromInterfaces(0, []uint32{0}) + Expect(err).To(Not(BeNil())) +} diff --git a/plugins/vpp/aclplugin/vppcalls/vpp2306/vppcalls_handlers.go b/plugins/vpp/aclplugin/vppcalls/vpp2306/vppcalls_handlers.go new file mode 100644 index 0000000000..557d8b13a2 --- /dev/null +++ b/plugins/vpp/aclplugin/vppcalls/vpp2306/vppcalls_handlers.go @@ -0,0 +1,73 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "fmt" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306" + "net" + + govppapi "go.fd.io/govpp/api" + + "go.ligato.io/vpp-agent/v3/plugins/vpp" + "go.ligato.io/vpp-agent/v3/plugins/vpp/aclplugin/vppcalls" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/acl" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip_types" + "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/ifaceidx" +) + +func init() { + msgs := acl.AllMessages() + vppcalls.AddHandlerVersion(vpp2306.Version, msgs, NewACLVppHandler) +} + +// ACLVppHandler is accessor for acl-related vppcalls methods +type ACLVppHandler struct { + callsChannel govppapi.Channel + // TODO: use only RPC service + acl acl.RPCService + ifIndexes ifaceidx.IfaceMetadataIndex +} + +func NewACLVppHandler(c vpp.Client, ifIdx ifaceidx.IfaceMetadataIndex) vppcalls.ACLVppAPI { + ch, err := c.NewAPIChannel() + if err != nil { + return nil + } + return &ACLVppHandler{ + callsChannel: ch, + acl: acl.NewServiceClient(c), + ifIndexes: ifIdx, + } +} + +func prefixToString(address ip_types.Prefix) string { + if address.Address.Af == ip_types.ADDRESS_IP6 { + ip6 := address.Address.Un.GetIP6() + return fmt.Sprintf("%s/%d", net.IP(ip6[:]).To16(), address.Len) + } else { + ip4 := address.Address.Un.GetIP4() + return fmt.Sprintf("%s/%d", net.IP(ip4[:]).To4(), address.Len) + } +} + +func addressToIP(address ip_types.Address) net.IP { + if address.Af == ip_types.ADDRESS_IP6 { + ipAddr := address.Un.GetIP6() + return net.IP(ipAddr[:]).To16() + } + ipAddr := address.Un.GetIP4() + return net.IP(ipAddr[:]).To4() +} diff --git a/plugins/vpp/binapi/vpp2306/abf/abf.ba.go b/plugins/vpp/binapi/vpp2306/abf/abf.ba.go new file mode 100644 index 0000000000..4391c89986 --- /dev/null +++ b/plugins/vpp/binapi/vpp2306/abf/abf.ba.go @@ -0,0 +1,584 @@ +// Code generated by GoVPP's binapi-generator. DO NOT EDIT. + +// Package abf contains generated bindings for API file abf.api. +// +// Contents: +// - 2 structs +// - 10 messages +package abf + +import ( + api "go.fd.io/govpp/api" + codec "go.fd.io/govpp/codec" + fib_types "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/fib_types" + interface_types "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface_types" + _ "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip_types" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the GoVPP api package it is being compiled against. +// A compilation error at this line likely means your copy of the +// GoVPP api package needs to be updated. +const _ = api.GoVppAPIPackageIsVersion2 + +const ( + APIFile = "abf" + APIVersion = "1.0.0" + VersionCrc = 0xf2367b47 +) + +// AbfItfAttach defines type 'abf_itf_attach'. +type AbfItfAttach struct { + PolicyID uint32 `binapi:"u32,name=policy_id" json:"policy_id,omitempty"` + SwIfIndex interface_types.InterfaceIndex `binapi:"interface_index,name=sw_if_index" json:"sw_if_index,omitempty"` + Priority uint32 `binapi:"u32,name=priority" json:"priority,omitempty"` + IsIPv6 bool `binapi:"bool,name=is_ipv6" json:"is_ipv6,omitempty"` +} + +// AbfPolicy defines type 'abf_policy'. +type AbfPolicy struct { + PolicyID uint32 `binapi:"u32,name=policy_id" json:"policy_id,omitempty"` + ACLIndex uint32 `binapi:"u32,name=acl_index" json:"acl_index,omitempty"` + NPaths uint8 `binapi:"u8,name=n_paths" json:"-"` + Paths []fib_types.FibPath `binapi:"fib_path[n_paths],name=paths" json:"paths,omitempty"` +} + +// AbfItfAttachAddDel defines message 'abf_itf_attach_add_del'. +// InProgress: the message form may change in the future versions +type AbfItfAttachAddDel struct { + IsAdd bool `binapi:"bool,name=is_add" json:"is_add,omitempty"` + Attach AbfItfAttach `binapi:"abf_itf_attach,name=attach" json:"attach,omitempty"` +} + +func (m *AbfItfAttachAddDel) Reset() { *m = AbfItfAttachAddDel{} } +func (*AbfItfAttachAddDel) GetMessageName() string { return "abf_itf_attach_add_del" } +func (*AbfItfAttachAddDel) GetCrcString() string { return "25c8621b" } +func (*AbfItfAttachAddDel) GetMessageType() api.MessageType { + return api.RequestMessage +} + +func (m *AbfItfAttachAddDel) Size() (size int) { + if m == nil { + return 0 + } + size += 1 // m.IsAdd + size += 4 // m.Attach.PolicyID + size += 4 // m.Attach.SwIfIndex + size += 4 // m.Attach.Priority + size += 1 // m.Attach.IsIPv6 + return size +} +func (m *AbfItfAttachAddDel) Marshal(b []byte) ([]byte, error) { + if b == nil { + b = make([]byte, m.Size()) + } + buf := codec.NewBuffer(b) + buf.EncodeBool(m.IsAdd) + buf.EncodeUint32(m.Attach.PolicyID) + buf.EncodeUint32(uint32(m.Attach.SwIfIndex)) + buf.EncodeUint32(m.Attach.Priority) + buf.EncodeBool(m.Attach.IsIPv6) + return buf.Bytes(), nil +} +func (m *AbfItfAttachAddDel) Unmarshal(b []byte) error { + buf := codec.NewBuffer(b) + m.IsAdd = buf.DecodeBool() + m.Attach.PolicyID = buf.DecodeUint32() + m.Attach.SwIfIndex = interface_types.InterfaceIndex(buf.DecodeUint32()) + m.Attach.Priority = buf.DecodeUint32() + m.Attach.IsIPv6 = buf.DecodeBool() + return nil +} + +// AbfItfAttachAddDelReply defines message 'abf_itf_attach_add_del_reply'. +// InProgress: the message form may change in the future versions +type AbfItfAttachAddDelReply struct { + Retval int32 `binapi:"i32,name=retval" json:"retval,omitempty"` +} + +func (m *AbfItfAttachAddDelReply) Reset() { *m = AbfItfAttachAddDelReply{} } +func (*AbfItfAttachAddDelReply) GetMessageName() string { return "abf_itf_attach_add_del_reply" } +func (*AbfItfAttachAddDelReply) GetCrcString() string { return "e8d4e804" } +func (*AbfItfAttachAddDelReply) GetMessageType() api.MessageType { + return api.ReplyMessage +} + +func (m *AbfItfAttachAddDelReply) Size() (size int) { + if m == nil { + return 0 + } + size += 4 // m.Retval + return size +} +func (m *AbfItfAttachAddDelReply) Marshal(b []byte) ([]byte, error) { + if b == nil { + b = make([]byte, m.Size()) + } + buf := codec.NewBuffer(b) + buf.EncodeInt32(m.Retval) + return buf.Bytes(), nil +} +func (m *AbfItfAttachAddDelReply) Unmarshal(b []byte) error { + buf := codec.NewBuffer(b) + m.Retval = buf.DecodeInt32() + return nil +} + +// AbfItfAttachDetails defines message 'abf_itf_attach_details'. +// InProgress: the message form may change in the future versions +type AbfItfAttachDetails struct { + Attach AbfItfAttach `binapi:"abf_itf_attach,name=attach" json:"attach,omitempty"` +} + +func (m *AbfItfAttachDetails) Reset() { *m = AbfItfAttachDetails{} } +func (*AbfItfAttachDetails) GetMessageName() string { return "abf_itf_attach_details" } +func (*AbfItfAttachDetails) GetCrcString() string { return "7819523e" } +func (*AbfItfAttachDetails) GetMessageType() api.MessageType { + return api.ReplyMessage +} + +func (m *AbfItfAttachDetails) Size() (size int) { + if m == nil { + return 0 + } + size += 4 // m.Attach.PolicyID + size += 4 // m.Attach.SwIfIndex + size += 4 // m.Attach.Priority + size += 1 // m.Attach.IsIPv6 + return size +} +func (m *AbfItfAttachDetails) Marshal(b []byte) ([]byte, error) { + if b == nil { + b = make([]byte, m.Size()) + } + buf := codec.NewBuffer(b) + buf.EncodeUint32(m.Attach.PolicyID) + buf.EncodeUint32(uint32(m.Attach.SwIfIndex)) + buf.EncodeUint32(m.Attach.Priority) + buf.EncodeBool(m.Attach.IsIPv6) + return buf.Bytes(), nil +} +func (m *AbfItfAttachDetails) Unmarshal(b []byte) error { + buf := codec.NewBuffer(b) + m.Attach.PolicyID = buf.DecodeUint32() + m.Attach.SwIfIndex = interface_types.InterfaceIndex(buf.DecodeUint32()) + m.Attach.Priority = buf.DecodeUint32() + m.Attach.IsIPv6 = buf.DecodeBool() + return nil +} + +// AbfItfAttachDump defines message 'abf_itf_attach_dump'. +// InProgress: the message form may change in the future versions +type AbfItfAttachDump struct{} + +func (m *AbfItfAttachDump) Reset() { *m = AbfItfAttachDump{} } +func (*AbfItfAttachDump) GetMessageName() string { return "abf_itf_attach_dump" } +func (*AbfItfAttachDump) GetCrcString() string { return "51077d14" } +func (*AbfItfAttachDump) GetMessageType() api.MessageType { + return api.RequestMessage +} + +func (m *AbfItfAttachDump) Size() (size int) { + if m == nil { + return 0 + } + return size +} +func (m *AbfItfAttachDump) Marshal(b []byte) ([]byte, error) { + if b == nil { + b = make([]byte, m.Size()) + } + buf := codec.NewBuffer(b) + return buf.Bytes(), nil +} +func (m *AbfItfAttachDump) Unmarshal(b []byte) error { + return nil +} + +// AbfPluginGetVersion defines message 'abf_plugin_get_version'. +// InProgress: the message form may change in the future versions +type AbfPluginGetVersion struct{} + +func (m *AbfPluginGetVersion) Reset() { *m = AbfPluginGetVersion{} } +func (*AbfPluginGetVersion) GetMessageName() string { return "abf_plugin_get_version" } +func (*AbfPluginGetVersion) GetCrcString() string { return "51077d14" } +func (*AbfPluginGetVersion) GetMessageType() api.MessageType { + return api.RequestMessage +} + +func (m *AbfPluginGetVersion) Size() (size int) { + if m == nil { + return 0 + } + return size +} +func (m *AbfPluginGetVersion) Marshal(b []byte) ([]byte, error) { + if b == nil { + b = make([]byte, m.Size()) + } + buf := codec.NewBuffer(b) + return buf.Bytes(), nil +} +func (m *AbfPluginGetVersion) Unmarshal(b []byte) error { + return nil +} + +// AbfPluginGetVersionReply defines message 'abf_plugin_get_version_reply'. +// InProgress: the message form may change in the future versions +type AbfPluginGetVersionReply struct { + Major uint32 `binapi:"u32,name=major" json:"major,omitempty"` + Minor uint32 `binapi:"u32,name=minor" json:"minor,omitempty"` +} + +func (m *AbfPluginGetVersionReply) Reset() { *m = AbfPluginGetVersionReply{} } +func (*AbfPluginGetVersionReply) GetMessageName() string { return "abf_plugin_get_version_reply" } +func (*AbfPluginGetVersionReply) GetCrcString() string { return "9b32cf86" } +func (*AbfPluginGetVersionReply) GetMessageType() api.MessageType { + return api.ReplyMessage +} + +func (m *AbfPluginGetVersionReply) Size() (size int) { + if m == nil { + return 0 + } + size += 4 // m.Major + size += 4 // m.Minor + return size +} +func (m *AbfPluginGetVersionReply) Marshal(b []byte) ([]byte, error) { + if b == nil { + b = make([]byte, m.Size()) + } + buf := codec.NewBuffer(b) + buf.EncodeUint32(m.Major) + buf.EncodeUint32(m.Minor) + return buf.Bytes(), nil +} +func (m *AbfPluginGetVersionReply) Unmarshal(b []byte) error { + buf := codec.NewBuffer(b) + m.Major = buf.DecodeUint32() + m.Minor = buf.DecodeUint32() + return nil +} + +// AbfPolicyAddDel defines message 'abf_policy_add_del'. +// InProgress: the message form may change in the future versions +type AbfPolicyAddDel struct { + IsAdd bool `binapi:"bool,name=is_add" json:"is_add,omitempty"` + Policy AbfPolicy `binapi:"abf_policy,name=policy" json:"policy,omitempty"` +} + +func (m *AbfPolicyAddDel) Reset() { *m = AbfPolicyAddDel{} } +func (*AbfPolicyAddDel) GetMessageName() string { return "abf_policy_add_del" } +func (*AbfPolicyAddDel) GetCrcString() string { return "c6131197" } +func (*AbfPolicyAddDel) GetMessageType() api.MessageType { + return api.RequestMessage +} + +func (m *AbfPolicyAddDel) Size() (size int) { + if m == nil { + return 0 + } + size += 1 // m.IsAdd + size += 4 // m.Policy.PolicyID + size += 4 // m.Policy.ACLIndex + size += 1 // m.Policy.NPaths + for j2 := 0; j2 < len(m.Policy.Paths); j2++ { + var s2 fib_types.FibPath + _ = s2 + if j2 < len(m.Policy.Paths) { + s2 = m.Policy.Paths[j2] + } + size += 4 // s2.SwIfIndex + size += 4 // s2.TableID + size += 4 // s2.RpfID + size += 1 // s2.Weight + size += 1 // s2.Preference + size += 4 // s2.Type + size += 4 // s2.Flags + size += 4 // s2.Proto + size += 1 * 16 // s2.Nh.Address + size += 4 // s2.Nh.ViaLabel + size += 4 // s2.Nh.ObjID + size += 4 // s2.Nh.ClassifyTableIndex + size += 1 // s2.NLabels + for j3 := 0; j3 < 16; j3++ { + size += 1 // s2.LabelStack[j3].IsUniform + size += 4 // s2.LabelStack[j3].Label + size += 1 // s2.LabelStack[j3].TTL + size += 1 // s2.LabelStack[j3].Exp + } + } + return size +} +func (m *AbfPolicyAddDel) Marshal(b []byte) ([]byte, error) { + if b == nil { + b = make([]byte, m.Size()) + } + buf := codec.NewBuffer(b) + buf.EncodeBool(m.IsAdd) + buf.EncodeUint32(m.Policy.PolicyID) + buf.EncodeUint32(m.Policy.ACLIndex) + buf.EncodeUint8(uint8(len(m.Policy.Paths))) + for j1 := 0; j1 < len(m.Policy.Paths); j1++ { + var v1 fib_types.FibPath // Paths + if j1 < len(m.Policy.Paths) { + v1 = m.Policy.Paths[j1] + } + buf.EncodeUint32(v1.SwIfIndex) + buf.EncodeUint32(v1.TableID) + buf.EncodeUint32(v1.RpfID) + buf.EncodeUint8(v1.Weight) + buf.EncodeUint8(v1.Preference) + buf.EncodeUint32(uint32(v1.Type)) + buf.EncodeUint32(uint32(v1.Flags)) + buf.EncodeUint32(uint32(v1.Proto)) + buf.EncodeBytes(v1.Nh.Address.XXX_UnionData[:], 16) + buf.EncodeUint32(v1.Nh.ViaLabel) + buf.EncodeUint32(v1.Nh.ObjID) + buf.EncodeUint32(v1.Nh.ClassifyTableIndex) + buf.EncodeUint8(v1.NLabels) + for j2 := 0; j2 < 16; j2++ { + buf.EncodeUint8(v1.LabelStack[j2].IsUniform) + buf.EncodeUint32(v1.LabelStack[j2].Label) + buf.EncodeUint8(v1.LabelStack[j2].TTL) + buf.EncodeUint8(v1.LabelStack[j2].Exp) + } + } + return buf.Bytes(), nil +} +func (m *AbfPolicyAddDel) Unmarshal(b []byte) error { + buf := codec.NewBuffer(b) + m.IsAdd = buf.DecodeBool() + m.Policy.PolicyID = buf.DecodeUint32() + m.Policy.ACLIndex = buf.DecodeUint32() + m.Policy.NPaths = buf.DecodeUint8() + m.Policy.Paths = make([]fib_types.FibPath, m.Policy.NPaths) + for j1 := 0; j1 < len(m.Policy.Paths); j1++ { + m.Policy.Paths[j1].SwIfIndex = buf.DecodeUint32() + m.Policy.Paths[j1].TableID = buf.DecodeUint32() + m.Policy.Paths[j1].RpfID = buf.DecodeUint32() + m.Policy.Paths[j1].Weight = buf.DecodeUint8() + m.Policy.Paths[j1].Preference = buf.DecodeUint8() + m.Policy.Paths[j1].Type = fib_types.FibPathType(buf.DecodeUint32()) + m.Policy.Paths[j1].Flags = fib_types.FibPathFlags(buf.DecodeUint32()) + m.Policy.Paths[j1].Proto = fib_types.FibPathNhProto(buf.DecodeUint32()) + copy(m.Policy.Paths[j1].Nh.Address.XXX_UnionData[:], buf.DecodeBytes(16)) + m.Policy.Paths[j1].Nh.ViaLabel = buf.DecodeUint32() + m.Policy.Paths[j1].Nh.ObjID = buf.DecodeUint32() + m.Policy.Paths[j1].Nh.ClassifyTableIndex = buf.DecodeUint32() + m.Policy.Paths[j1].NLabels = buf.DecodeUint8() + for j2 := 0; j2 < 16; j2++ { + m.Policy.Paths[j1].LabelStack[j2].IsUniform = buf.DecodeUint8() + m.Policy.Paths[j1].LabelStack[j2].Label = buf.DecodeUint32() + m.Policy.Paths[j1].LabelStack[j2].TTL = buf.DecodeUint8() + m.Policy.Paths[j1].LabelStack[j2].Exp = buf.DecodeUint8() + } + } + return nil +} + +// AbfPolicyAddDelReply defines message 'abf_policy_add_del_reply'. +// InProgress: the message form may change in the future versions +type AbfPolicyAddDelReply struct { + Retval int32 `binapi:"i32,name=retval" json:"retval,omitempty"` +} + +func (m *AbfPolicyAddDelReply) Reset() { *m = AbfPolicyAddDelReply{} } +func (*AbfPolicyAddDelReply) GetMessageName() string { return "abf_policy_add_del_reply" } +func (*AbfPolicyAddDelReply) GetCrcString() string { return "e8d4e804" } +func (*AbfPolicyAddDelReply) GetMessageType() api.MessageType { + return api.ReplyMessage +} + +func (m *AbfPolicyAddDelReply) Size() (size int) { + if m == nil { + return 0 + } + size += 4 // m.Retval + return size +} +func (m *AbfPolicyAddDelReply) Marshal(b []byte) ([]byte, error) { + if b == nil { + b = make([]byte, m.Size()) + } + buf := codec.NewBuffer(b) + buf.EncodeInt32(m.Retval) + return buf.Bytes(), nil +} +func (m *AbfPolicyAddDelReply) Unmarshal(b []byte) error { + buf := codec.NewBuffer(b) + m.Retval = buf.DecodeInt32() + return nil +} + +// AbfPolicyDetails defines message 'abf_policy_details'. +// InProgress: the message form may change in the future versions +type AbfPolicyDetails struct { + Policy AbfPolicy `binapi:"abf_policy,name=policy" json:"policy,omitempty"` +} + +func (m *AbfPolicyDetails) Reset() { *m = AbfPolicyDetails{} } +func (*AbfPolicyDetails) GetMessageName() string { return "abf_policy_details" } +func (*AbfPolicyDetails) GetCrcString() string { return "b7487fa4" } +func (*AbfPolicyDetails) GetMessageType() api.MessageType { + return api.ReplyMessage +} + +func (m *AbfPolicyDetails) Size() (size int) { + if m == nil { + return 0 + } + size += 4 // m.Policy.PolicyID + size += 4 // m.Policy.ACLIndex + size += 1 // m.Policy.NPaths + for j2 := 0; j2 < len(m.Policy.Paths); j2++ { + var s2 fib_types.FibPath + _ = s2 + if j2 < len(m.Policy.Paths) { + s2 = m.Policy.Paths[j2] + } + size += 4 // s2.SwIfIndex + size += 4 // s2.TableID + size += 4 // s2.RpfID + size += 1 // s2.Weight + size += 1 // s2.Preference + size += 4 // s2.Type + size += 4 // s2.Flags + size += 4 // s2.Proto + size += 1 * 16 // s2.Nh.Address + size += 4 // s2.Nh.ViaLabel + size += 4 // s2.Nh.ObjID + size += 4 // s2.Nh.ClassifyTableIndex + size += 1 // s2.NLabels + for j3 := 0; j3 < 16; j3++ { + size += 1 // s2.LabelStack[j3].IsUniform + size += 4 // s2.LabelStack[j3].Label + size += 1 // s2.LabelStack[j3].TTL + size += 1 // s2.LabelStack[j3].Exp + } + } + return size +} +func (m *AbfPolicyDetails) Marshal(b []byte) ([]byte, error) { + if b == nil { + b = make([]byte, m.Size()) + } + buf := codec.NewBuffer(b) + buf.EncodeUint32(m.Policy.PolicyID) + buf.EncodeUint32(m.Policy.ACLIndex) + buf.EncodeUint8(uint8(len(m.Policy.Paths))) + for j1 := 0; j1 < len(m.Policy.Paths); j1++ { + var v1 fib_types.FibPath // Paths + if j1 < len(m.Policy.Paths) { + v1 = m.Policy.Paths[j1] + } + buf.EncodeUint32(v1.SwIfIndex) + buf.EncodeUint32(v1.TableID) + buf.EncodeUint32(v1.RpfID) + buf.EncodeUint8(v1.Weight) + buf.EncodeUint8(v1.Preference) + buf.EncodeUint32(uint32(v1.Type)) + buf.EncodeUint32(uint32(v1.Flags)) + buf.EncodeUint32(uint32(v1.Proto)) + buf.EncodeBytes(v1.Nh.Address.XXX_UnionData[:], 16) + buf.EncodeUint32(v1.Nh.ViaLabel) + buf.EncodeUint32(v1.Nh.ObjID) + buf.EncodeUint32(v1.Nh.ClassifyTableIndex) + buf.EncodeUint8(v1.NLabels) + for j2 := 0; j2 < 16; j2++ { + buf.EncodeUint8(v1.LabelStack[j2].IsUniform) + buf.EncodeUint32(v1.LabelStack[j2].Label) + buf.EncodeUint8(v1.LabelStack[j2].TTL) + buf.EncodeUint8(v1.LabelStack[j2].Exp) + } + } + return buf.Bytes(), nil +} +func (m *AbfPolicyDetails) Unmarshal(b []byte) error { + buf := codec.NewBuffer(b) + m.Policy.PolicyID = buf.DecodeUint32() + m.Policy.ACLIndex = buf.DecodeUint32() + m.Policy.NPaths = buf.DecodeUint8() + m.Policy.Paths = make([]fib_types.FibPath, m.Policy.NPaths) + for j1 := 0; j1 < len(m.Policy.Paths); j1++ { + m.Policy.Paths[j1].SwIfIndex = buf.DecodeUint32() + m.Policy.Paths[j1].TableID = buf.DecodeUint32() + m.Policy.Paths[j1].RpfID = buf.DecodeUint32() + m.Policy.Paths[j1].Weight = buf.DecodeUint8() + m.Policy.Paths[j1].Preference = buf.DecodeUint8() + m.Policy.Paths[j1].Type = fib_types.FibPathType(buf.DecodeUint32()) + m.Policy.Paths[j1].Flags = fib_types.FibPathFlags(buf.DecodeUint32()) + m.Policy.Paths[j1].Proto = fib_types.FibPathNhProto(buf.DecodeUint32()) + copy(m.Policy.Paths[j1].Nh.Address.XXX_UnionData[:], buf.DecodeBytes(16)) + m.Policy.Paths[j1].Nh.ViaLabel = buf.DecodeUint32() + m.Policy.Paths[j1].Nh.ObjID = buf.DecodeUint32() + m.Policy.Paths[j1].Nh.ClassifyTableIndex = buf.DecodeUint32() + m.Policy.Paths[j1].NLabels = buf.DecodeUint8() + for j2 := 0; j2 < 16; j2++ { + m.Policy.Paths[j1].LabelStack[j2].IsUniform = buf.DecodeUint8() + m.Policy.Paths[j1].LabelStack[j2].Label = buf.DecodeUint32() + m.Policy.Paths[j1].LabelStack[j2].TTL = buf.DecodeUint8() + m.Policy.Paths[j1].LabelStack[j2].Exp = buf.DecodeUint8() + } + } + return nil +} + +// AbfPolicyDump defines message 'abf_policy_dump'. +// InProgress: the message form may change in the future versions +type AbfPolicyDump struct{} + +func (m *AbfPolicyDump) Reset() { *m = AbfPolicyDump{} } +func (*AbfPolicyDump) GetMessageName() string { return "abf_policy_dump" } +func (*AbfPolicyDump) GetCrcString() string { return "51077d14" } +func (*AbfPolicyDump) GetMessageType() api.MessageType { + return api.RequestMessage +} + +func (m *AbfPolicyDump) Size() (size int) { + if m == nil { + return 0 + } + return size +} +func (m *AbfPolicyDump) Marshal(b []byte) ([]byte, error) { + if b == nil { + b = make([]byte, m.Size()) + } + buf := codec.NewBuffer(b) + return buf.Bytes(), nil +} +func (m *AbfPolicyDump) Unmarshal(b []byte) error { + return nil +} + +func init() { file_abf_binapi_init() } +func file_abf_binapi_init() { + api.RegisterMessage((*AbfItfAttachAddDel)(nil), "abf_itf_attach_add_del_25c8621b") + api.RegisterMessage((*AbfItfAttachAddDelReply)(nil), "abf_itf_attach_add_del_reply_e8d4e804") + api.RegisterMessage((*AbfItfAttachDetails)(nil), "abf_itf_attach_details_7819523e") + api.RegisterMessage((*AbfItfAttachDump)(nil), "abf_itf_attach_dump_51077d14") + api.RegisterMessage((*AbfPluginGetVersion)(nil), "abf_plugin_get_version_51077d14") + api.RegisterMessage((*AbfPluginGetVersionReply)(nil), "abf_plugin_get_version_reply_9b32cf86") + api.RegisterMessage((*AbfPolicyAddDel)(nil), "abf_policy_add_del_c6131197") + api.RegisterMessage((*AbfPolicyAddDelReply)(nil), "abf_policy_add_del_reply_e8d4e804") + api.RegisterMessage((*AbfPolicyDetails)(nil), "abf_policy_details_b7487fa4") + api.RegisterMessage((*AbfPolicyDump)(nil), "abf_policy_dump_51077d14") +} + +// Messages returns list of all messages in this module. +func AllMessages() []api.Message { + return []api.Message{ + (*AbfItfAttachAddDel)(nil), + (*AbfItfAttachAddDelReply)(nil), + (*AbfItfAttachDetails)(nil), + (*AbfItfAttachDump)(nil), + (*AbfPluginGetVersion)(nil), + (*AbfPluginGetVersionReply)(nil), + (*AbfPolicyAddDel)(nil), + (*AbfPolicyAddDelReply)(nil), + (*AbfPolicyDetails)(nil), + (*AbfPolicyDump)(nil), + } +} diff --git a/plugins/vpp/binapi/vpp2306/abf/abf_rpc.ba.go b/plugins/vpp/binapi/vpp2306/abf/abf_rpc.ba.go new file mode 100644 index 0000000000..2003738cbc --- /dev/null +++ b/plugins/vpp/binapi/vpp2306/abf/abf_rpc.ba.go @@ -0,0 +1,142 @@ +// Code generated by GoVPP's binapi-generator. DO NOT EDIT. + +package abf + +import ( + "context" + "fmt" + "io" + + api "go.fd.io/govpp/api" + memclnt "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/memclnt" +) + +// RPCService defines RPC service abf. +type RPCService interface { + AbfItfAttachAddDel(ctx context.Context, in *AbfItfAttachAddDel) (*AbfItfAttachAddDelReply, error) + AbfItfAttachDump(ctx context.Context, in *AbfItfAttachDump) (RPCService_AbfItfAttachDumpClient, error) + AbfPluginGetVersion(ctx context.Context, in *AbfPluginGetVersion) (*AbfPluginGetVersionReply, error) + AbfPolicyAddDel(ctx context.Context, in *AbfPolicyAddDel) (*AbfPolicyAddDelReply, error) + AbfPolicyDump(ctx context.Context, in *AbfPolicyDump) (RPCService_AbfPolicyDumpClient, error) +} + +type serviceClient struct { + conn api.Connection +} + +func NewServiceClient(conn api.Connection) RPCService { + return &serviceClient{conn} +} + +func (c *serviceClient) AbfItfAttachAddDel(ctx context.Context, in *AbfItfAttachAddDel) (*AbfItfAttachAddDelReply, error) { + out := new(AbfItfAttachAddDelReply) + err := c.conn.Invoke(ctx, in, out) + if err != nil { + return nil, err + } + return out, api.RetvalToVPPApiError(out.Retval) +} + +func (c *serviceClient) AbfItfAttachDump(ctx context.Context, in *AbfItfAttachDump) (RPCService_AbfItfAttachDumpClient, error) { + stream, err := c.conn.NewStream(ctx) + if err != nil { + return nil, err + } + x := &serviceClient_AbfItfAttachDumpClient{stream} + if err := x.Stream.SendMsg(in); err != nil { + return nil, err + } + if err = x.Stream.SendMsg(&memclnt.ControlPing{}); err != nil { + return nil, err + } + return x, nil +} + +type RPCService_AbfItfAttachDumpClient interface { + Recv() (*AbfItfAttachDetails, error) + api.Stream +} + +type serviceClient_AbfItfAttachDumpClient struct { + api.Stream +} + +func (c *serviceClient_AbfItfAttachDumpClient) Recv() (*AbfItfAttachDetails, error) { + msg, err := c.Stream.RecvMsg() + if err != nil { + return nil, err + } + switch m := msg.(type) { + case *AbfItfAttachDetails: + return m, nil + case *memclnt.ControlPingReply: + err = c.Stream.Close() + if err != nil { + return nil, err + } + return nil, io.EOF + default: + return nil, fmt.Errorf("unexpected message: %T %v", m, m) + } +} + +func (c *serviceClient) AbfPluginGetVersion(ctx context.Context, in *AbfPluginGetVersion) (*AbfPluginGetVersionReply, error) { + out := new(AbfPluginGetVersionReply) + err := c.conn.Invoke(ctx, in, out) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *serviceClient) AbfPolicyAddDel(ctx context.Context, in *AbfPolicyAddDel) (*AbfPolicyAddDelReply, error) { + out := new(AbfPolicyAddDelReply) + err := c.conn.Invoke(ctx, in, out) + if err != nil { + return nil, err + } + return out, api.RetvalToVPPApiError(out.Retval) +} + +func (c *serviceClient) AbfPolicyDump(ctx context.Context, in *AbfPolicyDump) (RPCService_AbfPolicyDumpClient, error) { + stream, err := c.conn.NewStream(ctx) + if err != nil { + return nil, err + } + x := &serviceClient_AbfPolicyDumpClient{stream} + if err := x.Stream.SendMsg(in); err != nil { + return nil, err + } + if err = x.Stream.SendMsg(&memclnt.ControlPing{}); err != nil { + return nil, err + } + return x, nil +} + +type RPCService_AbfPolicyDumpClient interface { + Recv() (*AbfPolicyDetails, error) + api.Stream +} + +type serviceClient_AbfPolicyDumpClient struct { + api.Stream +} + +func (c *serviceClient_AbfPolicyDumpClient) Recv() (*AbfPolicyDetails, error) { + msg, err := c.Stream.RecvMsg() + if err != nil { + return nil, err + } + switch m := msg.(type) { + case *AbfPolicyDetails: + return m, nil + case *memclnt.ControlPingReply: + err = c.Stream.Close() + if err != nil { + return nil, err + } + return nil, io.EOF + default: + return nil, fmt.Errorf("unexpected message: %T %v", m, m) + } +} diff --git a/plugins/vpp/binapi/vpp2306/acl/acl.ba.go b/plugins/vpp/binapi/vpp2306/acl/acl.ba.go new file mode 100644 index 0000000000..7134192d5b --- /dev/null +++ b/plugins/vpp/binapi/vpp2306/acl/acl.ba.go @@ -0,0 +1,1923 @@ +// Code generated by GoVPP's binapi-generator. DO NOT EDIT. + +// Package acl contains generated bindings for API file acl.api. +// +// Contents: +// - 42 messages +package acl + +import ( + api "go.fd.io/govpp/api" + codec "go.fd.io/govpp/codec" + acl_types "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/acl_types" + _ "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ethernet_types" + interface_types "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface_types" + ip_types "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip_types" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the GoVPP api package it is being compiled against. +// A compilation error at this line likely means your copy of the +// GoVPP api package needs to be updated. +const _ = api.GoVppAPIPackageIsVersion2 + +const ( + APIFile = "acl" + APIVersion = "2.0.1" + VersionCrc = 0x5133bba0 +) + +// ACLAddReplace defines message 'acl_add_replace'. +type ACLAddReplace struct { + ACLIndex uint32 `binapi:"u32,name=acl_index" json:"acl_index,omitempty"` + Tag string `binapi:"string[64],name=tag" json:"tag,omitempty"` + Count uint32 `binapi:"u32,name=count" json:"-"` + R []acl_types.ACLRule `binapi:"acl_rule[count],name=r" json:"r,omitempty"` +} + +func (m *ACLAddReplace) Reset() { *m = ACLAddReplace{} } +func (*ACLAddReplace) GetMessageName() string { return "acl_add_replace" } +func (*ACLAddReplace) GetCrcString() string { return "ee5c2f18" } +func (*ACLAddReplace) GetMessageType() api.MessageType { + return api.RequestMessage +} + +func (m *ACLAddReplace) Size() (size int) { + if m == nil { + return 0 + } + size += 4 // m.ACLIndex + size += 64 // m.Tag + size += 4 // m.Count + for j1 := 0; j1 < len(m.R); j1++ { + var s1 acl_types.ACLRule + _ = s1 + if j1 < len(m.R) { + s1 = m.R[j1] + } + size += 1 // s1.IsPermit + size += 1 // s1.SrcPrefix.Address.Af + size += 1 * 16 // s1.SrcPrefix.Address.Un + size += 1 // s1.SrcPrefix.Len + size += 1 // s1.DstPrefix.Address.Af + size += 1 * 16 // s1.DstPrefix.Address.Un + size += 1 // s1.DstPrefix.Len + size += 1 // s1.Proto + size += 2 // s1.SrcportOrIcmptypeFirst + size += 2 // s1.SrcportOrIcmptypeLast + size += 2 // s1.DstportOrIcmpcodeFirst + size += 2 // s1.DstportOrIcmpcodeLast + size += 1 // s1.TCPFlagsMask + size += 1 // s1.TCPFlagsValue + } + return size +} +func (m *ACLAddReplace) Marshal(b []byte) ([]byte, error) { + if b == nil { + b = make([]byte, m.Size()) + } + buf := codec.NewBuffer(b) + buf.EncodeUint32(m.ACLIndex) + buf.EncodeString(m.Tag, 64) + buf.EncodeUint32(uint32(len(m.R))) + for j0 := 0; j0 < len(m.R); j0++ { + var v0 acl_types.ACLRule // R + if j0 < len(m.R) { + v0 = m.R[j0] + } + buf.EncodeUint8(uint8(v0.IsPermit)) + buf.EncodeUint8(uint8(v0.SrcPrefix.Address.Af)) + buf.EncodeBytes(v0.SrcPrefix.Address.Un.XXX_UnionData[:], 16) + buf.EncodeUint8(v0.SrcPrefix.Len) + buf.EncodeUint8(uint8(v0.DstPrefix.Address.Af)) + buf.EncodeBytes(v0.DstPrefix.Address.Un.XXX_UnionData[:], 16) + buf.EncodeUint8(v0.DstPrefix.Len) + buf.EncodeUint8(uint8(v0.Proto)) + buf.EncodeUint16(v0.SrcportOrIcmptypeFirst) + buf.EncodeUint16(v0.SrcportOrIcmptypeLast) + buf.EncodeUint16(v0.DstportOrIcmpcodeFirst) + buf.EncodeUint16(v0.DstportOrIcmpcodeLast) + buf.EncodeUint8(v0.TCPFlagsMask) + buf.EncodeUint8(v0.TCPFlagsValue) + } + return buf.Bytes(), nil +} +func (m *ACLAddReplace) Unmarshal(b []byte) error { + buf := codec.NewBuffer(b) + m.ACLIndex = buf.DecodeUint32() + m.Tag = buf.DecodeString(64) + m.Count = buf.DecodeUint32() + m.R = make([]acl_types.ACLRule, m.Count) + for j0 := 0; j0 < len(m.R); j0++ { + m.R[j0].IsPermit = acl_types.ACLAction(buf.DecodeUint8()) + m.R[j0].SrcPrefix.Address.Af = ip_types.AddressFamily(buf.DecodeUint8()) + copy(m.R[j0].SrcPrefix.Address.Un.XXX_UnionData[:], buf.DecodeBytes(16)) + m.R[j0].SrcPrefix.Len = buf.DecodeUint8() + m.R[j0].DstPrefix.Address.Af = ip_types.AddressFamily(buf.DecodeUint8()) + copy(m.R[j0].DstPrefix.Address.Un.XXX_UnionData[:], buf.DecodeBytes(16)) + m.R[j0].DstPrefix.Len = buf.DecodeUint8() + m.R[j0].Proto = ip_types.IPProto(buf.DecodeUint8()) + m.R[j0].SrcportOrIcmptypeFirst = buf.DecodeUint16() + m.R[j0].SrcportOrIcmptypeLast = buf.DecodeUint16() + m.R[j0].DstportOrIcmpcodeFirst = buf.DecodeUint16() + m.R[j0].DstportOrIcmpcodeLast = buf.DecodeUint16() + m.R[j0].TCPFlagsMask = buf.DecodeUint8() + m.R[j0].TCPFlagsValue = buf.DecodeUint8() + } + return nil +} + +// ACLAddReplaceReply defines message 'acl_add_replace_reply'. +type ACLAddReplaceReply struct { + ACLIndex uint32 `binapi:"u32,name=acl_index" json:"acl_index,omitempty"` + Retval int32 `binapi:"i32,name=retval" json:"retval,omitempty"` +} + +func (m *ACLAddReplaceReply) Reset() { *m = ACLAddReplaceReply{} } +func (*ACLAddReplaceReply) GetMessageName() string { return "acl_add_replace_reply" } +func (*ACLAddReplaceReply) GetCrcString() string { return "ac407b0c" } +func (*ACLAddReplaceReply) GetMessageType() api.MessageType { + return api.ReplyMessage +} + +func (m *ACLAddReplaceReply) Size() (size int) { + if m == nil { + return 0 + } + size += 4 // m.ACLIndex + size += 4 // m.Retval + return size +} +func (m *ACLAddReplaceReply) Marshal(b []byte) ([]byte, error) { + if b == nil { + b = make([]byte, m.Size()) + } + buf := codec.NewBuffer(b) + buf.EncodeUint32(m.ACLIndex) + buf.EncodeInt32(m.Retval) + return buf.Bytes(), nil +} +func (m *ACLAddReplaceReply) Unmarshal(b []byte) error { + buf := codec.NewBuffer(b) + m.ACLIndex = buf.DecodeUint32() + m.Retval = buf.DecodeInt32() + return nil +} + +// ACLDel defines message 'acl_del'. +type ACLDel struct { + ACLIndex uint32 `binapi:"u32,name=acl_index" json:"acl_index,omitempty"` +} + +func (m *ACLDel) Reset() { *m = ACLDel{} } +func (*ACLDel) GetMessageName() string { return "acl_del" } +func (*ACLDel) GetCrcString() string { return "ef34fea4" } +func (*ACLDel) GetMessageType() api.MessageType { + return api.RequestMessage +} + +func (m *ACLDel) Size() (size int) { + if m == nil { + return 0 + } + size += 4 // m.ACLIndex + return size +} +func (m *ACLDel) Marshal(b []byte) ([]byte, error) { + if b == nil { + b = make([]byte, m.Size()) + } + buf := codec.NewBuffer(b) + buf.EncodeUint32(m.ACLIndex) + return buf.Bytes(), nil +} +func (m *ACLDel) Unmarshal(b []byte) error { + buf := codec.NewBuffer(b) + m.ACLIndex = buf.DecodeUint32() + return nil +} + +// ACLDelReply defines message 'acl_del_reply'. +type ACLDelReply struct { + Retval int32 `binapi:"i32,name=retval" json:"retval,omitempty"` +} + +func (m *ACLDelReply) Reset() { *m = ACLDelReply{} } +func (*ACLDelReply) GetMessageName() string { return "acl_del_reply" } +func (*ACLDelReply) GetCrcString() string { return "e8d4e804" } +func (*ACLDelReply) GetMessageType() api.MessageType { + return api.ReplyMessage +} + +func (m *ACLDelReply) Size() (size int) { + if m == nil { + return 0 + } + size += 4 // m.Retval + return size +} +func (m *ACLDelReply) Marshal(b []byte) ([]byte, error) { + if b == nil { + b = make([]byte, m.Size()) + } + buf := codec.NewBuffer(b) + buf.EncodeInt32(m.Retval) + return buf.Bytes(), nil +} +func (m *ACLDelReply) Unmarshal(b []byte) error { + buf := codec.NewBuffer(b) + m.Retval = buf.DecodeInt32() + return nil +} + +// ACLDetails defines message 'acl_details'. +type ACLDetails struct { + ACLIndex uint32 `binapi:"u32,name=acl_index" json:"acl_index,omitempty"` + Tag string `binapi:"string[64],name=tag" json:"tag,omitempty"` + Count uint32 `binapi:"u32,name=count" json:"-"` + R []acl_types.ACLRule `binapi:"acl_rule[count],name=r" json:"r,omitempty"` +} + +func (m *ACLDetails) Reset() { *m = ACLDetails{} } +func (*ACLDetails) GetMessageName() string { return "acl_details" } +func (*ACLDetails) GetCrcString() string { return "95babae0" } +func (*ACLDetails) GetMessageType() api.MessageType { + return api.ReplyMessage +} + +func (m *ACLDetails) Size() (size int) { + if m == nil { + return 0 + } + size += 4 // m.ACLIndex + size += 64 // m.Tag + size += 4 // m.Count + for j1 := 0; j1 < len(m.R); j1++ { + var s1 acl_types.ACLRule + _ = s1 + if j1 < len(m.R) { + s1 = m.R[j1] + } + size += 1 // s1.IsPermit + size += 1 // s1.SrcPrefix.Address.Af + size += 1 * 16 // s1.SrcPrefix.Address.Un + size += 1 // s1.SrcPrefix.Len + size += 1 // s1.DstPrefix.Address.Af + size += 1 * 16 // s1.DstPrefix.Address.Un + size += 1 // s1.DstPrefix.Len + size += 1 // s1.Proto + size += 2 // s1.SrcportOrIcmptypeFirst + size += 2 // s1.SrcportOrIcmptypeLast + size += 2 // s1.DstportOrIcmpcodeFirst + size += 2 // s1.DstportOrIcmpcodeLast + size += 1 // s1.TCPFlagsMask + size += 1 // s1.TCPFlagsValue + } + return size +} +func (m *ACLDetails) Marshal(b []byte) ([]byte, error) { + if b == nil { + b = make([]byte, m.Size()) + } + buf := codec.NewBuffer(b) + buf.EncodeUint32(m.ACLIndex) + buf.EncodeString(m.Tag, 64) + buf.EncodeUint32(uint32(len(m.R))) + for j0 := 0; j0 < len(m.R); j0++ { + var v0 acl_types.ACLRule // R + if j0 < len(m.R) { + v0 = m.R[j0] + } + buf.EncodeUint8(uint8(v0.IsPermit)) + buf.EncodeUint8(uint8(v0.SrcPrefix.Address.Af)) + buf.EncodeBytes(v0.SrcPrefix.Address.Un.XXX_UnionData[:], 16) + buf.EncodeUint8(v0.SrcPrefix.Len) + buf.EncodeUint8(uint8(v0.DstPrefix.Address.Af)) + buf.EncodeBytes(v0.DstPrefix.Address.Un.XXX_UnionData[:], 16) + buf.EncodeUint8(v0.DstPrefix.Len) + buf.EncodeUint8(uint8(v0.Proto)) + buf.EncodeUint16(v0.SrcportOrIcmptypeFirst) + buf.EncodeUint16(v0.SrcportOrIcmptypeLast) + buf.EncodeUint16(v0.DstportOrIcmpcodeFirst) + buf.EncodeUint16(v0.DstportOrIcmpcodeLast) + buf.EncodeUint8(v0.TCPFlagsMask) + buf.EncodeUint8(v0.TCPFlagsValue) + } + return buf.Bytes(), nil +} +func (m *ACLDetails) Unmarshal(b []byte) error { + buf := codec.NewBuffer(b) + m.ACLIndex = buf.DecodeUint32() + m.Tag = buf.DecodeString(64) + m.Count = buf.DecodeUint32() + m.R = make([]acl_types.ACLRule, m.Count) + for j0 := 0; j0 < len(m.R); j0++ { + m.R[j0].IsPermit = acl_types.ACLAction(buf.DecodeUint8()) + m.R[j0].SrcPrefix.Address.Af = ip_types.AddressFamily(buf.DecodeUint8()) + copy(m.R[j0].SrcPrefix.Address.Un.XXX_UnionData[:], buf.DecodeBytes(16)) + m.R[j0].SrcPrefix.Len = buf.DecodeUint8() + m.R[j0].DstPrefix.Address.Af = ip_types.AddressFamily(buf.DecodeUint8()) + copy(m.R[j0].DstPrefix.Address.Un.XXX_UnionData[:], buf.DecodeBytes(16)) + m.R[j0].DstPrefix.Len = buf.DecodeUint8() + m.R[j0].Proto = ip_types.IPProto(buf.DecodeUint8()) + m.R[j0].SrcportOrIcmptypeFirst = buf.DecodeUint16() + m.R[j0].SrcportOrIcmptypeLast = buf.DecodeUint16() + m.R[j0].DstportOrIcmpcodeFirst = buf.DecodeUint16() + m.R[j0].DstportOrIcmpcodeLast = buf.DecodeUint16() + m.R[j0].TCPFlagsMask = buf.DecodeUint8() + m.R[j0].TCPFlagsValue = buf.DecodeUint8() + } + return nil +} + +// ACLDump defines message 'acl_dump'. +type ACLDump struct { + ACLIndex uint32 `binapi:"u32,name=acl_index" json:"acl_index,omitempty"` +} + +func (m *ACLDump) Reset() { *m = ACLDump{} } +func (*ACLDump) GetMessageName() string { return "acl_dump" } +func (*ACLDump) GetCrcString() string { return "ef34fea4" } +func (*ACLDump) GetMessageType() api.MessageType { + return api.RequestMessage +} + +func (m *ACLDump) Size() (size int) { + if m == nil { + return 0 + } + size += 4 // m.ACLIndex + return size +} +func (m *ACLDump) Marshal(b []byte) ([]byte, error) { + if b == nil { + b = make([]byte, m.Size()) + } + buf := codec.NewBuffer(b) + buf.EncodeUint32(m.ACLIndex) + return buf.Bytes(), nil +} +func (m *ACLDump) Unmarshal(b []byte) error { + buf := codec.NewBuffer(b) + m.ACLIndex = buf.DecodeUint32() + return nil +} + +// ACLInterfaceAddDel defines message 'acl_interface_add_del'. +type ACLInterfaceAddDel struct { + IsAdd bool `binapi:"bool,name=is_add,default=true" json:"is_add,omitempty"` + IsInput bool `binapi:"bool,name=is_input" json:"is_input,omitempty"` + SwIfIndex interface_types.InterfaceIndex `binapi:"interface_index,name=sw_if_index" json:"sw_if_index,omitempty"` + ACLIndex uint32 `binapi:"u32,name=acl_index" json:"acl_index,omitempty"` +} + +func (m *ACLInterfaceAddDel) Reset() { *m = ACLInterfaceAddDel{} } +func (*ACLInterfaceAddDel) GetMessageName() string { return "acl_interface_add_del" } +func (*ACLInterfaceAddDel) GetCrcString() string { return "4b54bebd" } +func (*ACLInterfaceAddDel) GetMessageType() api.MessageType { + return api.RequestMessage +} + +func (m *ACLInterfaceAddDel) Size() (size int) { + if m == nil { + return 0 + } + size += 1 // m.IsAdd + size += 1 // m.IsInput + size += 4 // m.SwIfIndex + size += 4 // m.ACLIndex + return size +} +func (m *ACLInterfaceAddDel) Marshal(b []byte) ([]byte, error) { + if b == nil { + b = make([]byte, m.Size()) + } + buf := codec.NewBuffer(b) + buf.EncodeBool(m.IsAdd) + buf.EncodeBool(m.IsInput) + buf.EncodeUint32(uint32(m.SwIfIndex)) + buf.EncodeUint32(m.ACLIndex) + return buf.Bytes(), nil +} +func (m *ACLInterfaceAddDel) Unmarshal(b []byte) error { + buf := codec.NewBuffer(b) + m.IsAdd = buf.DecodeBool() + m.IsInput = buf.DecodeBool() + m.SwIfIndex = interface_types.InterfaceIndex(buf.DecodeUint32()) + m.ACLIndex = buf.DecodeUint32() + return nil +} + +// ACLInterfaceAddDelReply defines message 'acl_interface_add_del_reply'. +type ACLInterfaceAddDelReply struct { + Retval int32 `binapi:"i32,name=retval" json:"retval,omitempty"` +} + +func (m *ACLInterfaceAddDelReply) Reset() { *m = ACLInterfaceAddDelReply{} } +func (*ACLInterfaceAddDelReply) GetMessageName() string { return "acl_interface_add_del_reply" } +func (*ACLInterfaceAddDelReply) GetCrcString() string { return "e8d4e804" } +func (*ACLInterfaceAddDelReply) GetMessageType() api.MessageType { + return api.ReplyMessage +} + +func (m *ACLInterfaceAddDelReply) Size() (size int) { + if m == nil { + return 0 + } + size += 4 // m.Retval + return size +} +func (m *ACLInterfaceAddDelReply) Marshal(b []byte) ([]byte, error) { + if b == nil { + b = make([]byte, m.Size()) + } + buf := codec.NewBuffer(b) + buf.EncodeInt32(m.Retval) + return buf.Bytes(), nil +} +func (m *ACLInterfaceAddDelReply) Unmarshal(b []byte) error { + buf := codec.NewBuffer(b) + m.Retval = buf.DecodeInt32() + return nil +} + +// ACLInterfaceEtypeWhitelistDetails defines message 'acl_interface_etype_whitelist_details'. +type ACLInterfaceEtypeWhitelistDetails struct { + SwIfIndex interface_types.InterfaceIndex `binapi:"interface_index,name=sw_if_index" json:"sw_if_index,omitempty"` + Count uint8 `binapi:"u8,name=count" json:"-"` + NInput uint8 `binapi:"u8,name=n_input" json:"n_input,omitempty"` + Whitelist []uint16 `binapi:"u16[count],name=whitelist" json:"whitelist,omitempty"` +} + +func (m *ACLInterfaceEtypeWhitelistDetails) Reset() { *m = ACLInterfaceEtypeWhitelistDetails{} } +func (*ACLInterfaceEtypeWhitelistDetails) GetMessageName() string { + return "acl_interface_etype_whitelist_details" +} +func (*ACLInterfaceEtypeWhitelistDetails) GetCrcString() string { return "cc2bfded" } +func (*ACLInterfaceEtypeWhitelistDetails) GetMessageType() api.MessageType { + return api.ReplyMessage +} + +func (m *ACLInterfaceEtypeWhitelistDetails) Size() (size int) { + if m == nil { + return 0 + } + size += 4 // m.SwIfIndex + size += 1 // m.Count + size += 1 // m.NInput + size += 2 * len(m.Whitelist) // m.Whitelist + return size +} +func (m *ACLInterfaceEtypeWhitelistDetails) Marshal(b []byte) ([]byte, error) { + if b == nil { + b = make([]byte, m.Size()) + } + buf := codec.NewBuffer(b) + buf.EncodeUint32(uint32(m.SwIfIndex)) + buf.EncodeUint8(uint8(len(m.Whitelist))) + buf.EncodeUint8(m.NInput) + for i := 0; i < len(m.Whitelist); i++ { + var x uint16 + if i < len(m.Whitelist) { + x = uint16(m.Whitelist[i]) + } + buf.EncodeUint16(x) + } + return buf.Bytes(), nil +} +func (m *ACLInterfaceEtypeWhitelistDetails) Unmarshal(b []byte) error { + buf := codec.NewBuffer(b) + m.SwIfIndex = interface_types.InterfaceIndex(buf.DecodeUint32()) + m.Count = buf.DecodeUint8() + m.NInput = buf.DecodeUint8() + m.Whitelist = make([]uint16, m.Count) + for i := 0; i < len(m.Whitelist); i++ { + m.Whitelist[i] = buf.DecodeUint16() + } + return nil +} + +// ACLInterfaceEtypeWhitelistDump defines message 'acl_interface_etype_whitelist_dump'. +type ACLInterfaceEtypeWhitelistDump struct { + SwIfIndex interface_types.InterfaceIndex `binapi:"interface_index,name=sw_if_index" json:"sw_if_index,omitempty"` +} + +func (m *ACLInterfaceEtypeWhitelistDump) Reset() { *m = ACLInterfaceEtypeWhitelistDump{} } +func (*ACLInterfaceEtypeWhitelistDump) GetMessageName() string { + return "acl_interface_etype_whitelist_dump" +} +func (*ACLInterfaceEtypeWhitelistDump) GetCrcString() string { return "f9e6675e" } +func (*ACLInterfaceEtypeWhitelistDump) GetMessageType() api.MessageType { + return api.RequestMessage +} + +func (m *ACLInterfaceEtypeWhitelistDump) Size() (size int) { + if m == nil { + return 0 + } + size += 4 // m.SwIfIndex + return size +} +func (m *ACLInterfaceEtypeWhitelistDump) Marshal(b []byte) ([]byte, error) { + if b == nil { + b = make([]byte, m.Size()) + } + buf := codec.NewBuffer(b) + buf.EncodeUint32(uint32(m.SwIfIndex)) + return buf.Bytes(), nil +} +func (m *ACLInterfaceEtypeWhitelistDump) Unmarshal(b []byte) error { + buf := codec.NewBuffer(b) + m.SwIfIndex = interface_types.InterfaceIndex(buf.DecodeUint32()) + return nil +} + +// ACLInterfaceListDetails defines message 'acl_interface_list_details'. +type ACLInterfaceListDetails struct { + SwIfIndex interface_types.InterfaceIndex `binapi:"interface_index,name=sw_if_index" json:"sw_if_index,omitempty"` + Count uint8 `binapi:"u8,name=count" json:"-"` + NInput uint8 `binapi:"u8,name=n_input" json:"n_input,omitempty"` + Acls []uint32 `binapi:"u32[count],name=acls" json:"acls,omitempty"` +} + +func (m *ACLInterfaceListDetails) Reset() { *m = ACLInterfaceListDetails{} } +func (*ACLInterfaceListDetails) GetMessageName() string { return "acl_interface_list_details" } +func (*ACLInterfaceListDetails) GetCrcString() string { return "e695d256" } +func (*ACLInterfaceListDetails) GetMessageType() api.MessageType { + return api.ReplyMessage +} + +func (m *ACLInterfaceListDetails) Size() (size int) { + if m == nil { + return 0 + } + size += 4 // m.SwIfIndex + size += 1 // m.Count + size += 1 // m.NInput + size += 4 * len(m.Acls) // m.Acls + return size +} +func (m *ACLInterfaceListDetails) Marshal(b []byte) ([]byte, error) { + if b == nil { + b = make([]byte, m.Size()) + } + buf := codec.NewBuffer(b) + buf.EncodeUint32(uint32(m.SwIfIndex)) + buf.EncodeUint8(uint8(len(m.Acls))) + buf.EncodeUint8(m.NInput) + for i := 0; i < len(m.Acls); i++ { + var x uint32 + if i < len(m.Acls) { + x = uint32(m.Acls[i]) + } + buf.EncodeUint32(x) + } + return buf.Bytes(), nil +} +func (m *ACLInterfaceListDetails) Unmarshal(b []byte) error { + buf := codec.NewBuffer(b) + m.SwIfIndex = interface_types.InterfaceIndex(buf.DecodeUint32()) + m.Count = buf.DecodeUint8() + m.NInput = buf.DecodeUint8() + m.Acls = make([]uint32, m.Count) + for i := 0; i < len(m.Acls); i++ { + m.Acls[i] = buf.DecodeUint32() + } + return nil +} + +// ACLInterfaceListDump defines message 'acl_interface_list_dump'. +type ACLInterfaceListDump struct { + SwIfIndex interface_types.InterfaceIndex `binapi:"interface_index,name=sw_if_index,default=4294967295" json:"sw_if_index,omitempty"` +} + +func (m *ACLInterfaceListDump) Reset() { *m = ACLInterfaceListDump{} } +func (*ACLInterfaceListDump) GetMessageName() string { return "acl_interface_list_dump" } +func (*ACLInterfaceListDump) GetCrcString() string { return "f9e6675e" } +func (*ACLInterfaceListDump) GetMessageType() api.MessageType { + return api.RequestMessage +} + +func (m *ACLInterfaceListDump) Size() (size int) { + if m == nil { + return 0 + } + size += 4 // m.SwIfIndex + return size +} +func (m *ACLInterfaceListDump) Marshal(b []byte) ([]byte, error) { + if b == nil { + b = make([]byte, m.Size()) + } + buf := codec.NewBuffer(b) + buf.EncodeUint32(uint32(m.SwIfIndex)) + return buf.Bytes(), nil +} +func (m *ACLInterfaceListDump) Unmarshal(b []byte) error { + buf := codec.NewBuffer(b) + m.SwIfIndex = interface_types.InterfaceIndex(buf.DecodeUint32()) + return nil +} + +// ACLInterfaceSetACLList defines message 'acl_interface_set_acl_list'. +type ACLInterfaceSetACLList struct { + SwIfIndex interface_types.InterfaceIndex `binapi:"interface_index,name=sw_if_index" json:"sw_if_index,omitempty"` + Count uint8 `binapi:"u8,name=count" json:"-"` + NInput uint8 `binapi:"u8,name=n_input" json:"n_input,omitempty"` + Acls []uint32 `binapi:"u32[count],name=acls" json:"acls,omitempty"` +} + +func (m *ACLInterfaceSetACLList) Reset() { *m = ACLInterfaceSetACLList{} } +func (*ACLInterfaceSetACLList) GetMessageName() string { return "acl_interface_set_acl_list" } +func (*ACLInterfaceSetACLList) GetCrcString() string { return "473982bd" } +func (*ACLInterfaceSetACLList) GetMessageType() api.MessageType { + return api.RequestMessage +} + +func (m *ACLInterfaceSetACLList) Size() (size int) { + if m == nil { + return 0 + } + size += 4 // m.SwIfIndex + size += 1 // m.Count + size += 1 // m.NInput + size += 4 * len(m.Acls) // m.Acls + return size +} +func (m *ACLInterfaceSetACLList) Marshal(b []byte) ([]byte, error) { + if b == nil { + b = make([]byte, m.Size()) + } + buf := codec.NewBuffer(b) + buf.EncodeUint32(uint32(m.SwIfIndex)) + buf.EncodeUint8(uint8(len(m.Acls))) + buf.EncodeUint8(m.NInput) + for i := 0; i < len(m.Acls); i++ { + var x uint32 + if i < len(m.Acls) { + x = uint32(m.Acls[i]) + } + buf.EncodeUint32(x) + } + return buf.Bytes(), nil +} +func (m *ACLInterfaceSetACLList) Unmarshal(b []byte) error { + buf := codec.NewBuffer(b) + m.SwIfIndex = interface_types.InterfaceIndex(buf.DecodeUint32()) + m.Count = buf.DecodeUint8() + m.NInput = buf.DecodeUint8() + m.Acls = make([]uint32, m.Count) + for i := 0; i < len(m.Acls); i++ { + m.Acls[i] = buf.DecodeUint32() + } + return nil +} + +// ACLInterfaceSetACLListReply defines message 'acl_interface_set_acl_list_reply'. +type ACLInterfaceSetACLListReply struct { + Retval int32 `binapi:"i32,name=retval" json:"retval,omitempty"` +} + +func (m *ACLInterfaceSetACLListReply) Reset() { *m = ACLInterfaceSetACLListReply{} } +func (*ACLInterfaceSetACLListReply) GetMessageName() string { + return "acl_interface_set_acl_list_reply" +} +func (*ACLInterfaceSetACLListReply) GetCrcString() string { return "e8d4e804" } +func (*ACLInterfaceSetACLListReply) GetMessageType() api.MessageType { + return api.ReplyMessage +} + +func (m *ACLInterfaceSetACLListReply) Size() (size int) { + if m == nil { + return 0 + } + size += 4 // m.Retval + return size +} +func (m *ACLInterfaceSetACLListReply) Marshal(b []byte) ([]byte, error) { + if b == nil { + b = make([]byte, m.Size()) + } + buf := codec.NewBuffer(b) + buf.EncodeInt32(m.Retval) + return buf.Bytes(), nil +} +func (m *ACLInterfaceSetACLListReply) Unmarshal(b []byte) error { + buf := codec.NewBuffer(b) + m.Retval = buf.DecodeInt32() + return nil +} + +// ACLInterfaceSetEtypeWhitelist defines message 'acl_interface_set_etype_whitelist'. +type ACLInterfaceSetEtypeWhitelist struct { + SwIfIndex interface_types.InterfaceIndex `binapi:"interface_index,name=sw_if_index" json:"sw_if_index,omitempty"` + Count uint8 `binapi:"u8,name=count" json:"-"` + NInput uint8 `binapi:"u8,name=n_input" json:"n_input,omitempty"` + Whitelist []uint16 `binapi:"u16[count],name=whitelist" json:"whitelist,omitempty"` +} + +func (m *ACLInterfaceSetEtypeWhitelist) Reset() { *m = ACLInterfaceSetEtypeWhitelist{} } +func (*ACLInterfaceSetEtypeWhitelist) GetMessageName() string { + return "acl_interface_set_etype_whitelist" +} +func (*ACLInterfaceSetEtypeWhitelist) GetCrcString() string { return "3f5c2d2d" } +func (*ACLInterfaceSetEtypeWhitelist) GetMessageType() api.MessageType { + return api.RequestMessage +} + +func (m *ACLInterfaceSetEtypeWhitelist) Size() (size int) { + if m == nil { + return 0 + } + size += 4 // m.SwIfIndex + size += 1 // m.Count + size += 1 // m.NInput + size += 2 * len(m.Whitelist) // m.Whitelist + return size +} +func (m *ACLInterfaceSetEtypeWhitelist) Marshal(b []byte) ([]byte, error) { + if b == nil { + b = make([]byte, m.Size()) + } + buf := codec.NewBuffer(b) + buf.EncodeUint32(uint32(m.SwIfIndex)) + buf.EncodeUint8(uint8(len(m.Whitelist))) + buf.EncodeUint8(m.NInput) + for i := 0; i < len(m.Whitelist); i++ { + var x uint16 + if i < len(m.Whitelist) { + x = uint16(m.Whitelist[i]) + } + buf.EncodeUint16(x) + } + return buf.Bytes(), nil +} +func (m *ACLInterfaceSetEtypeWhitelist) Unmarshal(b []byte) error { + buf := codec.NewBuffer(b) + m.SwIfIndex = interface_types.InterfaceIndex(buf.DecodeUint32()) + m.Count = buf.DecodeUint8() + m.NInput = buf.DecodeUint8() + m.Whitelist = make([]uint16, m.Count) + for i := 0; i < len(m.Whitelist); i++ { + m.Whitelist[i] = buf.DecodeUint16() + } + return nil +} + +// ACLInterfaceSetEtypeWhitelistReply defines message 'acl_interface_set_etype_whitelist_reply'. +type ACLInterfaceSetEtypeWhitelistReply struct { + Retval int32 `binapi:"i32,name=retval" json:"retval,omitempty"` +} + +func (m *ACLInterfaceSetEtypeWhitelistReply) Reset() { *m = ACLInterfaceSetEtypeWhitelistReply{} } +func (*ACLInterfaceSetEtypeWhitelistReply) GetMessageName() string { + return "acl_interface_set_etype_whitelist_reply" +} +func (*ACLInterfaceSetEtypeWhitelistReply) GetCrcString() string { return "e8d4e804" } +func (*ACLInterfaceSetEtypeWhitelistReply) GetMessageType() api.MessageType { + return api.ReplyMessage +} + +func (m *ACLInterfaceSetEtypeWhitelistReply) Size() (size int) { + if m == nil { + return 0 + } + size += 4 // m.Retval + return size +} +func (m *ACLInterfaceSetEtypeWhitelistReply) Marshal(b []byte) ([]byte, error) { + if b == nil { + b = make([]byte, m.Size()) + } + buf := codec.NewBuffer(b) + buf.EncodeInt32(m.Retval) + return buf.Bytes(), nil +} +func (m *ACLInterfaceSetEtypeWhitelistReply) Unmarshal(b []byte) error { + buf := codec.NewBuffer(b) + m.Retval = buf.DecodeInt32() + return nil +} + +// ACLPluginControlPing defines message 'acl_plugin_control_ping'. +type ACLPluginControlPing struct{} + +func (m *ACLPluginControlPing) Reset() { *m = ACLPluginControlPing{} } +func (*ACLPluginControlPing) GetMessageName() string { return "acl_plugin_control_ping" } +func (*ACLPluginControlPing) GetCrcString() string { return "51077d14" } +func (*ACLPluginControlPing) GetMessageType() api.MessageType { + return api.RequestMessage +} + +func (m *ACLPluginControlPing) Size() (size int) { + if m == nil { + return 0 + } + return size +} +func (m *ACLPluginControlPing) Marshal(b []byte) ([]byte, error) { + if b == nil { + b = make([]byte, m.Size()) + } + buf := codec.NewBuffer(b) + return buf.Bytes(), nil +} +func (m *ACLPluginControlPing) Unmarshal(b []byte) error { + return nil +} + +// ACLPluginControlPingReply defines message 'acl_plugin_control_ping_reply'. +type ACLPluginControlPingReply struct { + Retval int32 `binapi:"i32,name=retval" json:"retval,omitempty"` + ClientIndex uint32 `binapi:"u32,name=client_index" json:"client_index,omitempty"` + VpePID uint32 `binapi:"u32,name=vpe_pid" json:"vpe_pid,omitempty"` +} + +func (m *ACLPluginControlPingReply) Reset() { *m = ACLPluginControlPingReply{} } +func (*ACLPluginControlPingReply) GetMessageName() string { return "acl_plugin_control_ping_reply" } +func (*ACLPluginControlPingReply) GetCrcString() string { return "f6b0b8ca" } +func (*ACLPluginControlPingReply) GetMessageType() api.MessageType { + return api.ReplyMessage +} + +func (m *ACLPluginControlPingReply) Size() (size int) { + if m == nil { + return 0 + } + size += 4 // m.Retval + size += 4 // m.ClientIndex + size += 4 // m.VpePID + return size +} +func (m *ACLPluginControlPingReply) Marshal(b []byte) ([]byte, error) { + if b == nil { + b = make([]byte, m.Size()) + } + buf := codec.NewBuffer(b) + buf.EncodeInt32(m.Retval) + buf.EncodeUint32(m.ClientIndex) + buf.EncodeUint32(m.VpePID) + return buf.Bytes(), nil +} +func (m *ACLPluginControlPingReply) Unmarshal(b []byte) error { + buf := codec.NewBuffer(b) + m.Retval = buf.DecodeInt32() + m.ClientIndex = buf.DecodeUint32() + m.VpePID = buf.DecodeUint32() + return nil +} + +// ACLPluginGetConnTableMaxEntries defines message 'acl_plugin_get_conn_table_max_entries'. +type ACLPluginGetConnTableMaxEntries struct{} + +func (m *ACLPluginGetConnTableMaxEntries) Reset() { *m = ACLPluginGetConnTableMaxEntries{} } +func (*ACLPluginGetConnTableMaxEntries) GetMessageName() string { + return "acl_plugin_get_conn_table_max_entries" +} +func (*ACLPluginGetConnTableMaxEntries) GetCrcString() string { return "51077d14" } +func (*ACLPluginGetConnTableMaxEntries) GetMessageType() api.MessageType { + return api.RequestMessage +} + +func (m *ACLPluginGetConnTableMaxEntries) Size() (size int) { + if m == nil { + return 0 + } + return size +} +func (m *ACLPluginGetConnTableMaxEntries) Marshal(b []byte) ([]byte, error) { + if b == nil { + b = make([]byte, m.Size()) + } + buf := codec.NewBuffer(b) + return buf.Bytes(), nil +} +func (m *ACLPluginGetConnTableMaxEntries) Unmarshal(b []byte) error { + return nil +} + +// ACLPluginGetConnTableMaxEntriesReply defines message 'acl_plugin_get_conn_table_max_entries_reply'. +type ACLPluginGetConnTableMaxEntriesReply struct { + ConnTableMaxEntries uint64 `binapi:"u64,name=conn_table_max_entries" json:"conn_table_max_entries,omitempty"` +} + +func (m *ACLPluginGetConnTableMaxEntriesReply) Reset() { *m = ACLPluginGetConnTableMaxEntriesReply{} } +func (*ACLPluginGetConnTableMaxEntriesReply) GetMessageName() string { + return "acl_plugin_get_conn_table_max_entries_reply" +} +func (*ACLPluginGetConnTableMaxEntriesReply) GetCrcString() string { return "7a096d3d" } +func (*ACLPluginGetConnTableMaxEntriesReply) GetMessageType() api.MessageType { + return api.ReplyMessage +} + +func (m *ACLPluginGetConnTableMaxEntriesReply) Size() (size int) { + if m == nil { + return 0 + } + size += 8 // m.ConnTableMaxEntries + return size +} +func (m *ACLPluginGetConnTableMaxEntriesReply) Marshal(b []byte) ([]byte, error) { + if b == nil { + b = make([]byte, m.Size()) + } + buf := codec.NewBuffer(b) + buf.EncodeUint64(m.ConnTableMaxEntries) + return buf.Bytes(), nil +} +func (m *ACLPluginGetConnTableMaxEntriesReply) Unmarshal(b []byte) error { + buf := codec.NewBuffer(b) + m.ConnTableMaxEntries = buf.DecodeUint64() + return nil +} + +// ACLPluginGetVersion defines message 'acl_plugin_get_version'. +type ACLPluginGetVersion struct{} + +func (m *ACLPluginGetVersion) Reset() { *m = ACLPluginGetVersion{} } +func (*ACLPluginGetVersion) GetMessageName() string { return "acl_plugin_get_version" } +func (*ACLPluginGetVersion) GetCrcString() string { return "51077d14" } +func (*ACLPluginGetVersion) GetMessageType() api.MessageType { + return api.RequestMessage +} + +func (m *ACLPluginGetVersion) Size() (size int) { + if m == nil { + return 0 + } + return size +} +func (m *ACLPluginGetVersion) Marshal(b []byte) ([]byte, error) { + if b == nil { + b = make([]byte, m.Size()) + } + buf := codec.NewBuffer(b) + return buf.Bytes(), nil +} +func (m *ACLPluginGetVersion) Unmarshal(b []byte) error { + return nil +} + +// ACLPluginGetVersionReply defines message 'acl_plugin_get_version_reply'. +type ACLPluginGetVersionReply struct { + Major uint32 `binapi:"u32,name=major" json:"major,omitempty"` + Minor uint32 `binapi:"u32,name=minor" json:"minor,omitempty"` +} + +func (m *ACLPluginGetVersionReply) Reset() { *m = ACLPluginGetVersionReply{} } +func (*ACLPluginGetVersionReply) GetMessageName() string { return "acl_plugin_get_version_reply" } +func (*ACLPluginGetVersionReply) GetCrcString() string { return "9b32cf86" } +func (*ACLPluginGetVersionReply) GetMessageType() api.MessageType { + return api.ReplyMessage +} + +func (m *ACLPluginGetVersionReply) Size() (size int) { + if m == nil { + return 0 + } + size += 4 // m.Major + size += 4 // m.Minor + return size +} +func (m *ACLPluginGetVersionReply) Marshal(b []byte) ([]byte, error) { + if b == nil { + b = make([]byte, m.Size()) + } + buf := codec.NewBuffer(b) + buf.EncodeUint32(m.Major) + buf.EncodeUint32(m.Minor) + return buf.Bytes(), nil +} +func (m *ACLPluginGetVersionReply) Unmarshal(b []byte) error { + buf := codec.NewBuffer(b) + m.Major = buf.DecodeUint32() + m.Minor = buf.DecodeUint32() + return nil +} + +// ACLPluginUseHashLookupGet defines message 'acl_plugin_use_hash_lookup_get'. +// InProgress: the message form may change in the future versions +type ACLPluginUseHashLookupGet struct{} + +func (m *ACLPluginUseHashLookupGet) Reset() { *m = ACLPluginUseHashLookupGet{} } +func (*ACLPluginUseHashLookupGet) GetMessageName() string { return "acl_plugin_use_hash_lookup_get" } +func (*ACLPluginUseHashLookupGet) GetCrcString() string { return "51077d14" } +func (*ACLPluginUseHashLookupGet) GetMessageType() api.MessageType { + return api.RequestMessage +} + +func (m *ACLPluginUseHashLookupGet) Size() (size int) { + if m == nil { + return 0 + } + return size +} +func (m *ACLPluginUseHashLookupGet) Marshal(b []byte) ([]byte, error) { + if b == nil { + b = make([]byte, m.Size()) + } + buf := codec.NewBuffer(b) + return buf.Bytes(), nil +} +func (m *ACLPluginUseHashLookupGet) Unmarshal(b []byte) error { + return nil +} + +// ACLPluginUseHashLookupGetReply defines message 'acl_plugin_use_hash_lookup_get_reply'. +// InProgress: the message form may change in the future versions +type ACLPluginUseHashLookupGetReply struct { + Enable bool `binapi:"bool,name=enable" json:"enable,omitempty"` +} + +func (m *ACLPluginUseHashLookupGetReply) Reset() { *m = ACLPluginUseHashLookupGetReply{} } +func (*ACLPluginUseHashLookupGetReply) GetMessageName() string { + return "acl_plugin_use_hash_lookup_get_reply" +} +func (*ACLPluginUseHashLookupGetReply) GetCrcString() string { return "5392ad31" } +func (*ACLPluginUseHashLookupGetReply) GetMessageType() api.MessageType { + return api.ReplyMessage +} + +func (m *ACLPluginUseHashLookupGetReply) Size() (size int) { + if m == nil { + return 0 + } + size += 1 // m.Enable + return size +} +func (m *ACLPluginUseHashLookupGetReply) Marshal(b []byte) ([]byte, error) { + if b == nil { + b = make([]byte, m.Size()) + } + buf := codec.NewBuffer(b) + buf.EncodeBool(m.Enable) + return buf.Bytes(), nil +} +func (m *ACLPluginUseHashLookupGetReply) Unmarshal(b []byte) error { + buf := codec.NewBuffer(b) + m.Enable = buf.DecodeBool() + return nil +} + +// ACLPluginUseHashLookupSet defines message 'acl_plugin_use_hash_lookup_set'. +// InProgress: the message form may change in the future versions +type ACLPluginUseHashLookupSet struct { + Enable bool `binapi:"bool,name=enable" json:"enable,omitempty"` +} + +func (m *ACLPluginUseHashLookupSet) Reset() { *m = ACLPluginUseHashLookupSet{} } +func (*ACLPluginUseHashLookupSet) GetMessageName() string { return "acl_plugin_use_hash_lookup_set" } +func (*ACLPluginUseHashLookupSet) GetCrcString() string { return "b3e225d2" } +func (*ACLPluginUseHashLookupSet) GetMessageType() api.MessageType { + return api.RequestMessage +} + +func (m *ACLPluginUseHashLookupSet) Size() (size int) { + if m == nil { + return 0 + } + size += 1 // m.Enable + return size +} +func (m *ACLPluginUseHashLookupSet) Marshal(b []byte) ([]byte, error) { + if b == nil { + b = make([]byte, m.Size()) + } + buf := codec.NewBuffer(b) + buf.EncodeBool(m.Enable) + return buf.Bytes(), nil +} +func (m *ACLPluginUseHashLookupSet) Unmarshal(b []byte) error { + buf := codec.NewBuffer(b) + m.Enable = buf.DecodeBool() + return nil +} + +// ACLPluginUseHashLookupSetReply defines message 'acl_plugin_use_hash_lookup_set_reply'. +// InProgress: the message form may change in the future versions +type ACLPluginUseHashLookupSetReply struct { + Retval int32 `binapi:"i32,name=retval" json:"retval,omitempty"` +} + +func (m *ACLPluginUseHashLookupSetReply) Reset() { *m = ACLPluginUseHashLookupSetReply{} } +func (*ACLPluginUseHashLookupSetReply) GetMessageName() string { + return "acl_plugin_use_hash_lookup_set_reply" +} +func (*ACLPluginUseHashLookupSetReply) GetCrcString() string { return "e8d4e804" } +func (*ACLPluginUseHashLookupSetReply) GetMessageType() api.MessageType { + return api.ReplyMessage +} + +func (m *ACLPluginUseHashLookupSetReply) Size() (size int) { + if m == nil { + return 0 + } + size += 4 // m.Retval + return size +} +func (m *ACLPluginUseHashLookupSetReply) Marshal(b []byte) ([]byte, error) { + if b == nil { + b = make([]byte, m.Size()) + } + buf := codec.NewBuffer(b) + buf.EncodeInt32(m.Retval) + return buf.Bytes(), nil +} +func (m *ACLPluginUseHashLookupSetReply) Unmarshal(b []byte) error { + buf := codec.NewBuffer(b) + m.Retval = buf.DecodeInt32() + return nil +} + +// ACLStatsIntfCountersEnable defines message 'acl_stats_intf_counters_enable'. +type ACLStatsIntfCountersEnable struct { + Enable bool `binapi:"bool,name=enable" json:"enable,omitempty"` +} + +func (m *ACLStatsIntfCountersEnable) Reset() { *m = ACLStatsIntfCountersEnable{} } +func (*ACLStatsIntfCountersEnable) GetMessageName() string { return "acl_stats_intf_counters_enable" } +func (*ACLStatsIntfCountersEnable) GetCrcString() string { return "b3e225d2" } +func (*ACLStatsIntfCountersEnable) GetMessageType() api.MessageType { + return api.RequestMessage +} + +func (m *ACLStatsIntfCountersEnable) Size() (size int) { + if m == nil { + return 0 + } + size += 1 // m.Enable + return size +} +func (m *ACLStatsIntfCountersEnable) Marshal(b []byte) ([]byte, error) { + if b == nil { + b = make([]byte, m.Size()) + } + buf := codec.NewBuffer(b) + buf.EncodeBool(m.Enable) + return buf.Bytes(), nil +} +func (m *ACLStatsIntfCountersEnable) Unmarshal(b []byte) error { + buf := codec.NewBuffer(b) + m.Enable = buf.DecodeBool() + return nil +} + +// ACLStatsIntfCountersEnableReply defines message 'acl_stats_intf_counters_enable_reply'. +type ACLStatsIntfCountersEnableReply struct { + Retval int32 `binapi:"i32,name=retval" json:"retval,omitempty"` +} + +func (m *ACLStatsIntfCountersEnableReply) Reset() { *m = ACLStatsIntfCountersEnableReply{} } +func (*ACLStatsIntfCountersEnableReply) GetMessageName() string { + return "acl_stats_intf_counters_enable_reply" +} +func (*ACLStatsIntfCountersEnableReply) GetCrcString() string { return "e8d4e804" } +func (*ACLStatsIntfCountersEnableReply) GetMessageType() api.MessageType { + return api.ReplyMessage +} + +func (m *ACLStatsIntfCountersEnableReply) Size() (size int) { + if m == nil { + return 0 + } + size += 4 // m.Retval + return size +} +func (m *ACLStatsIntfCountersEnableReply) Marshal(b []byte) ([]byte, error) { + if b == nil { + b = make([]byte, m.Size()) + } + buf := codec.NewBuffer(b) + buf.EncodeInt32(m.Retval) + return buf.Bytes(), nil +} +func (m *ACLStatsIntfCountersEnableReply) Unmarshal(b []byte) error { + buf := codec.NewBuffer(b) + m.Retval = buf.DecodeInt32() + return nil +} + +// MacipACLAdd defines message 'macip_acl_add'. +type MacipACLAdd struct { + Tag string `binapi:"string[64],name=tag" json:"tag,omitempty"` + Count uint32 `binapi:"u32,name=count" json:"-"` + R []acl_types.MacipACLRule `binapi:"macip_acl_rule[count],name=r" json:"r,omitempty"` +} + +func (m *MacipACLAdd) Reset() { *m = MacipACLAdd{} } +func (*MacipACLAdd) GetMessageName() string { return "macip_acl_add" } +func (*MacipACLAdd) GetCrcString() string { return "ce6fbad0" } +func (*MacipACLAdd) GetMessageType() api.MessageType { + return api.RequestMessage +} + +func (m *MacipACLAdd) Size() (size int) { + if m == nil { + return 0 + } + size += 64 // m.Tag + size += 4 // m.Count + for j1 := 0; j1 < len(m.R); j1++ { + var s1 acl_types.MacipACLRule + _ = s1 + if j1 < len(m.R) { + s1 = m.R[j1] + } + size += 1 // s1.IsPermit + size += 1 * 6 // s1.SrcMac + size += 1 * 6 // s1.SrcMacMask + size += 1 // s1.SrcPrefix.Address.Af + size += 1 * 16 // s1.SrcPrefix.Address.Un + size += 1 // s1.SrcPrefix.Len + } + return size +} +func (m *MacipACLAdd) Marshal(b []byte) ([]byte, error) { + if b == nil { + b = make([]byte, m.Size()) + } + buf := codec.NewBuffer(b) + buf.EncodeString(m.Tag, 64) + buf.EncodeUint32(uint32(len(m.R))) + for j0 := 0; j0 < len(m.R); j0++ { + var v0 acl_types.MacipACLRule // R + if j0 < len(m.R) { + v0 = m.R[j0] + } + buf.EncodeUint8(uint8(v0.IsPermit)) + buf.EncodeBytes(v0.SrcMac[:], 6) + buf.EncodeBytes(v0.SrcMacMask[:], 6) + buf.EncodeUint8(uint8(v0.SrcPrefix.Address.Af)) + buf.EncodeBytes(v0.SrcPrefix.Address.Un.XXX_UnionData[:], 16) + buf.EncodeUint8(v0.SrcPrefix.Len) + } + return buf.Bytes(), nil +} +func (m *MacipACLAdd) Unmarshal(b []byte) error { + buf := codec.NewBuffer(b) + m.Tag = buf.DecodeString(64) + m.Count = buf.DecodeUint32() + m.R = make([]acl_types.MacipACLRule, m.Count) + for j0 := 0; j0 < len(m.R); j0++ { + m.R[j0].IsPermit = acl_types.ACLAction(buf.DecodeUint8()) + copy(m.R[j0].SrcMac[:], buf.DecodeBytes(6)) + copy(m.R[j0].SrcMacMask[:], buf.DecodeBytes(6)) + m.R[j0].SrcPrefix.Address.Af = ip_types.AddressFamily(buf.DecodeUint8()) + copy(m.R[j0].SrcPrefix.Address.Un.XXX_UnionData[:], buf.DecodeBytes(16)) + m.R[j0].SrcPrefix.Len = buf.DecodeUint8() + } + return nil +} + +// MacipACLAddReplace defines message 'macip_acl_add_replace'. +type MacipACLAddReplace struct { + ACLIndex uint32 `binapi:"u32,name=acl_index,default=4294967295" json:"acl_index,omitempty"` + Tag string `binapi:"string[64],name=tag" json:"tag,omitempty"` + Count uint32 `binapi:"u32,name=count" json:"-"` + R []acl_types.MacipACLRule `binapi:"macip_acl_rule[count],name=r" json:"r,omitempty"` +} + +func (m *MacipACLAddReplace) Reset() { *m = MacipACLAddReplace{} } +func (*MacipACLAddReplace) GetMessageName() string { return "macip_acl_add_replace" } +func (*MacipACLAddReplace) GetCrcString() string { return "2a461dd4" } +func (*MacipACLAddReplace) GetMessageType() api.MessageType { + return api.RequestMessage +} + +func (m *MacipACLAddReplace) Size() (size int) { + if m == nil { + return 0 + } + size += 4 // m.ACLIndex + size += 64 // m.Tag + size += 4 // m.Count + for j1 := 0; j1 < len(m.R); j1++ { + var s1 acl_types.MacipACLRule + _ = s1 + if j1 < len(m.R) { + s1 = m.R[j1] + } + size += 1 // s1.IsPermit + size += 1 * 6 // s1.SrcMac + size += 1 * 6 // s1.SrcMacMask + size += 1 // s1.SrcPrefix.Address.Af + size += 1 * 16 // s1.SrcPrefix.Address.Un + size += 1 // s1.SrcPrefix.Len + } + return size +} +func (m *MacipACLAddReplace) Marshal(b []byte) ([]byte, error) { + if b == nil { + b = make([]byte, m.Size()) + } + buf := codec.NewBuffer(b) + buf.EncodeUint32(m.ACLIndex) + buf.EncodeString(m.Tag, 64) + buf.EncodeUint32(uint32(len(m.R))) + for j0 := 0; j0 < len(m.R); j0++ { + var v0 acl_types.MacipACLRule // R + if j0 < len(m.R) { + v0 = m.R[j0] + } + buf.EncodeUint8(uint8(v0.IsPermit)) + buf.EncodeBytes(v0.SrcMac[:], 6) + buf.EncodeBytes(v0.SrcMacMask[:], 6) + buf.EncodeUint8(uint8(v0.SrcPrefix.Address.Af)) + buf.EncodeBytes(v0.SrcPrefix.Address.Un.XXX_UnionData[:], 16) + buf.EncodeUint8(v0.SrcPrefix.Len) + } + return buf.Bytes(), nil +} +func (m *MacipACLAddReplace) Unmarshal(b []byte) error { + buf := codec.NewBuffer(b) + m.ACLIndex = buf.DecodeUint32() + m.Tag = buf.DecodeString(64) + m.Count = buf.DecodeUint32() + m.R = make([]acl_types.MacipACLRule, m.Count) + for j0 := 0; j0 < len(m.R); j0++ { + m.R[j0].IsPermit = acl_types.ACLAction(buf.DecodeUint8()) + copy(m.R[j0].SrcMac[:], buf.DecodeBytes(6)) + copy(m.R[j0].SrcMacMask[:], buf.DecodeBytes(6)) + m.R[j0].SrcPrefix.Address.Af = ip_types.AddressFamily(buf.DecodeUint8()) + copy(m.R[j0].SrcPrefix.Address.Un.XXX_UnionData[:], buf.DecodeBytes(16)) + m.R[j0].SrcPrefix.Len = buf.DecodeUint8() + } + return nil +} + +// MacipACLAddReplaceReply defines message 'macip_acl_add_replace_reply'. +type MacipACLAddReplaceReply struct { + ACLIndex uint32 `binapi:"u32,name=acl_index" json:"acl_index,omitempty"` + Retval int32 `binapi:"i32,name=retval" json:"retval,omitempty"` +} + +func (m *MacipACLAddReplaceReply) Reset() { *m = MacipACLAddReplaceReply{} } +func (*MacipACLAddReplaceReply) GetMessageName() string { return "macip_acl_add_replace_reply" } +func (*MacipACLAddReplaceReply) GetCrcString() string { return "ac407b0c" } +func (*MacipACLAddReplaceReply) GetMessageType() api.MessageType { + return api.ReplyMessage +} + +func (m *MacipACLAddReplaceReply) Size() (size int) { + if m == nil { + return 0 + } + size += 4 // m.ACLIndex + size += 4 // m.Retval + return size +} +func (m *MacipACLAddReplaceReply) Marshal(b []byte) ([]byte, error) { + if b == nil { + b = make([]byte, m.Size()) + } + buf := codec.NewBuffer(b) + buf.EncodeUint32(m.ACLIndex) + buf.EncodeInt32(m.Retval) + return buf.Bytes(), nil +} +func (m *MacipACLAddReplaceReply) Unmarshal(b []byte) error { + buf := codec.NewBuffer(b) + m.ACLIndex = buf.DecodeUint32() + m.Retval = buf.DecodeInt32() + return nil +} + +// MacipACLAddReply defines message 'macip_acl_add_reply'. +type MacipACLAddReply struct { + ACLIndex uint32 `binapi:"u32,name=acl_index" json:"acl_index,omitempty"` + Retval int32 `binapi:"i32,name=retval" json:"retval,omitempty"` +} + +func (m *MacipACLAddReply) Reset() { *m = MacipACLAddReply{} } +func (*MacipACLAddReply) GetMessageName() string { return "macip_acl_add_reply" } +func (*MacipACLAddReply) GetCrcString() string { return "ac407b0c" } +func (*MacipACLAddReply) GetMessageType() api.MessageType { + return api.ReplyMessage +} + +func (m *MacipACLAddReply) Size() (size int) { + if m == nil { + return 0 + } + size += 4 // m.ACLIndex + size += 4 // m.Retval + return size +} +func (m *MacipACLAddReply) Marshal(b []byte) ([]byte, error) { + if b == nil { + b = make([]byte, m.Size()) + } + buf := codec.NewBuffer(b) + buf.EncodeUint32(m.ACLIndex) + buf.EncodeInt32(m.Retval) + return buf.Bytes(), nil +} +func (m *MacipACLAddReply) Unmarshal(b []byte) error { + buf := codec.NewBuffer(b) + m.ACLIndex = buf.DecodeUint32() + m.Retval = buf.DecodeInt32() + return nil +} + +// MacipACLDel defines message 'macip_acl_del'. +type MacipACLDel struct { + ACLIndex uint32 `binapi:"u32,name=acl_index" json:"acl_index,omitempty"` +} + +func (m *MacipACLDel) Reset() { *m = MacipACLDel{} } +func (*MacipACLDel) GetMessageName() string { return "macip_acl_del" } +func (*MacipACLDel) GetCrcString() string { return "ef34fea4" } +func (*MacipACLDel) GetMessageType() api.MessageType { + return api.RequestMessage +} + +func (m *MacipACLDel) Size() (size int) { + if m == nil { + return 0 + } + size += 4 // m.ACLIndex + return size +} +func (m *MacipACLDel) Marshal(b []byte) ([]byte, error) { + if b == nil { + b = make([]byte, m.Size()) + } + buf := codec.NewBuffer(b) + buf.EncodeUint32(m.ACLIndex) + return buf.Bytes(), nil +} +func (m *MacipACLDel) Unmarshal(b []byte) error { + buf := codec.NewBuffer(b) + m.ACLIndex = buf.DecodeUint32() + return nil +} + +// MacipACLDelReply defines message 'macip_acl_del_reply'. +type MacipACLDelReply struct { + Retval int32 `binapi:"i32,name=retval" json:"retval,omitempty"` +} + +func (m *MacipACLDelReply) Reset() { *m = MacipACLDelReply{} } +func (*MacipACLDelReply) GetMessageName() string { return "macip_acl_del_reply" } +func (*MacipACLDelReply) GetCrcString() string { return "e8d4e804" } +func (*MacipACLDelReply) GetMessageType() api.MessageType { + return api.ReplyMessage +} + +func (m *MacipACLDelReply) Size() (size int) { + if m == nil { + return 0 + } + size += 4 // m.Retval + return size +} +func (m *MacipACLDelReply) Marshal(b []byte) ([]byte, error) { + if b == nil { + b = make([]byte, m.Size()) + } + buf := codec.NewBuffer(b) + buf.EncodeInt32(m.Retval) + return buf.Bytes(), nil +} +func (m *MacipACLDelReply) Unmarshal(b []byte) error { + buf := codec.NewBuffer(b) + m.Retval = buf.DecodeInt32() + return nil +} + +// MacipACLDetails defines message 'macip_acl_details'. +type MacipACLDetails struct { + ACLIndex uint32 `binapi:"u32,name=acl_index" json:"acl_index,omitempty"` + Tag string `binapi:"string[64],name=tag" json:"tag,omitempty"` + Count uint32 `binapi:"u32,name=count" json:"-"` + R []acl_types.MacipACLRule `binapi:"macip_acl_rule[count],name=r" json:"r,omitempty"` +} + +func (m *MacipACLDetails) Reset() { *m = MacipACLDetails{} } +func (*MacipACLDetails) GetMessageName() string { return "macip_acl_details" } +func (*MacipACLDetails) GetCrcString() string { return "27135b59" } +func (*MacipACLDetails) GetMessageType() api.MessageType { + return api.ReplyMessage +} + +func (m *MacipACLDetails) Size() (size int) { + if m == nil { + return 0 + } + size += 4 // m.ACLIndex + size += 64 // m.Tag + size += 4 // m.Count + for j1 := 0; j1 < len(m.R); j1++ { + var s1 acl_types.MacipACLRule + _ = s1 + if j1 < len(m.R) { + s1 = m.R[j1] + } + size += 1 // s1.IsPermit + size += 1 * 6 // s1.SrcMac + size += 1 * 6 // s1.SrcMacMask + size += 1 // s1.SrcPrefix.Address.Af + size += 1 * 16 // s1.SrcPrefix.Address.Un + size += 1 // s1.SrcPrefix.Len + } + return size +} +func (m *MacipACLDetails) Marshal(b []byte) ([]byte, error) { + if b == nil { + b = make([]byte, m.Size()) + } + buf := codec.NewBuffer(b) + buf.EncodeUint32(m.ACLIndex) + buf.EncodeString(m.Tag, 64) + buf.EncodeUint32(uint32(len(m.R))) + for j0 := 0; j0 < len(m.R); j0++ { + var v0 acl_types.MacipACLRule // R + if j0 < len(m.R) { + v0 = m.R[j0] + } + buf.EncodeUint8(uint8(v0.IsPermit)) + buf.EncodeBytes(v0.SrcMac[:], 6) + buf.EncodeBytes(v0.SrcMacMask[:], 6) + buf.EncodeUint8(uint8(v0.SrcPrefix.Address.Af)) + buf.EncodeBytes(v0.SrcPrefix.Address.Un.XXX_UnionData[:], 16) + buf.EncodeUint8(v0.SrcPrefix.Len) + } + return buf.Bytes(), nil +} +func (m *MacipACLDetails) Unmarshal(b []byte) error { + buf := codec.NewBuffer(b) + m.ACLIndex = buf.DecodeUint32() + m.Tag = buf.DecodeString(64) + m.Count = buf.DecodeUint32() + m.R = make([]acl_types.MacipACLRule, m.Count) + for j0 := 0; j0 < len(m.R); j0++ { + m.R[j0].IsPermit = acl_types.ACLAction(buf.DecodeUint8()) + copy(m.R[j0].SrcMac[:], buf.DecodeBytes(6)) + copy(m.R[j0].SrcMacMask[:], buf.DecodeBytes(6)) + m.R[j0].SrcPrefix.Address.Af = ip_types.AddressFamily(buf.DecodeUint8()) + copy(m.R[j0].SrcPrefix.Address.Un.XXX_UnionData[:], buf.DecodeBytes(16)) + m.R[j0].SrcPrefix.Len = buf.DecodeUint8() + } + return nil +} + +// MacipACLDump defines message 'macip_acl_dump'. +type MacipACLDump struct { + ACLIndex uint32 `binapi:"u32,name=acl_index,default=4294967295" json:"acl_index,omitempty"` +} + +func (m *MacipACLDump) Reset() { *m = MacipACLDump{} } +func (*MacipACLDump) GetMessageName() string { return "macip_acl_dump" } +func (*MacipACLDump) GetCrcString() string { return "ef34fea4" } +func (*MacipACLDump) GetMessageType() api.MessageType { + return api.RequestMessage +} + +func (m *MacipACLDump) Size() (size int) { + if m == nil { + return 0 + } + size += 4 // m.ACLIndex + return size +} +func (m *MacipACLDump) Marshal(b []byte) ([]byte, error) { + if b == nil { + b = make([]byte, m.Size()) + } + buf := codec.NewBuffer(b) + buf.EncodeUint32(m.ACLIndex) + return buf.Bytes(), nil +} +func (m *MacipACLDump) Unmarshal(b []byte) error { + buf := codec.NewBuffer(b) + m.ACLIndex = buf.DecodeUint32() + return nil +} + +// MacipACLInterfaceAddDel defines message 'macip_acl_interface_add_del'. +type MacipACLInterfaceAddDel struct { + IsAdd bool `binapi:"bool,name=is_add,default=true" json:"is_add,omitempty"` + SwIfIndex interface_types.InterfaceIndex `binapi:"interface_index,name=sw_if_index" json:"sw_if_index,omitempty"` + ACLIndex uint32 `binapi:"u32,name=acl_index" json:"acl_index,omitempty"` +} + +func (m *MacipACLInterfaceAddDel) Reset() { *m = MacipACLInterfaceAddDel{} } +func (*MacipACLInterfaceAddDel) GetMessageName() string { return "macip_acl_interface_add_del" } +func (*MacipACLInterfaceAddDel) GetCrcString() string { return "4b8690b1" } +func (*MacipACLInterfaceAddDel) GetMessageType() api.MessageType { + return api.RequestMessage +} + +func (m *MacipACLInterfaceAddDel) Size() (size int) { + if m == nil { + return 0 + } + size += 1 // m.IsAdd + size += 4 // m.SwIfIndex + size += 4 // m.ACLIndex + return size +} +func (m *MacipACLInterfaceAddDel) Marshal(b []byte) ([]byte, error) { + if b == nil { + b = make([]byte, m.Size()) + } + buf := codec.NewBuffer(b) + buf.EncodeBool(m.IsAdd) + buf.EncodeUint32(uint32(m.SwIfIndex)) + buf.EncodeUint32(m.ACLIndex) + return buf.Bytes(), nil +} +func (m *MacipACLInterfaceAddDel) Unmarshal(b []byte) error { + buf := codec.NewBuffer(b) + m.IsAdd = buf.DecodeBool() + m.SwIfIndex = interface_types.InterfaceIndex(buf.DecodeUint32()) + m.ACLIndex = buf.DecodeUint32() + return nil +} + +// MacipACLInterfaceAddDelReply defines message 'macip_acl_interface_add_del_reply'. +type MacipACLInterfaceAddDelReply struct { + Retval int32 `binapi:"i32,name=retval" json:"retval,omitempty"` +} + +func (m *MacipACLInterfaceAddDelReply) Reset() { *m = MacipACLInterfaceAddDelReply{} } +func (*MacipACLInterfaceAddDelReply) GetMessageName() string { + return "macip_acl_interface_add_del_reply" +} +func (*MacipACLInterfaceAddDelReply) GetCrcString() string { return "e8d4e804" } +func (*MacipACLInterfaceAddDelReply) GetMessageType() api.MessageType { + return api.ReplyMessage +} + +func (m *MacipACLInterfaceAddDelReply) Size() (size int) { + if m == nil { + return 0 + } + size += 4 // m.Retval + return size +} +func (m *MacipACLInterfaceAddDelReply) Marshal(b []byte) ([]byte, error) { + if b == nil { + b = make([]byte, m.Size()) + } + buf := codec.NewBuffer(b) + buf.EncodeInt32(m.Retval) + return buf.Bytes(), nil +} +func (m *MacipACLInterfaceAddDelReply) Unmarshal(b []byte) error { + buf := codec.NewBuffer(b) + m.Retval = buf.DecodeInt32() + return nil +} + +// MacipACLInterfaceGet defines message 'macip_acl_interface_get'. +type MacipACLInterfaceGet struct{} + +func (m *MacipACLInterfaceGet) Reset() { *m = MacipACLInterfaceGet{} } +func (*MacipACLInterfaceGet) GetMessageName() string { return "macip_acl_interface_get" } +func (*MacipACLInterfaceGet) GetCrcString() string { return "51077d14" } +func (*MacipACLInterfaceGet) GetMessageType() api.MessageType { + return api.RequestMessage +} + +func (m *MacipACLInterfaceGet) Size() (size int) { + if m == nil { + return 0 + } + return size +} +func (m *MacipACLInterfaceGet) Marshal(b []byte) ([]byte, error) { + if b == nil { + b = make([]byte, m.Size()) + } + buf := codec.NewBuffer(b) + return buf.Bytes(), nil +} +func (m *MacipACLInterfaceGet) Unmarshal(b []byte) error { + return nil +} + +// MacipACLInterfaceGetReply defines message 'macip_acl_interface_get_reply'. +type MacipACLInterfaceGetReply struct { + Count uint32 `binapi:"u32,name=count" json:"-"` + Acls []uint32 `binapi:"u32[count],name=acls" json:"acls,omitempty"` +} + +func (m *MacipACLInterfaceGetReply) Reset() { *m = MacipACLInterfaceGetReply{} } +func (*MacipACLInterfaceGetReply) GetMessageName() string { return "macip_acl_interface_get_reply" } +func (*MacipACLInterfaceGetReply) GetCrcString() string { return "accf9b05" } +func (*MacipACLInterfaceGetReply) GetMessageType() api.MessageType { + return api.ReplyMessage +} + +func (m *MacipACLInterfaceGetReply) Size() (size int) { + if m == nil { + return 0 + } + size += 4 // m.Count + size += 4 * len(m.Acls) // m.Acls + return size +} +func (m *MacipACLInterfaceGetReply) Marshal(b []byte) ([]byte, error) { + if b == nil { + b = make([]byte, m.Size()) + } + buf := codec.NewBuffer(b) + buf.EncodeUint32(uint32(len(m.Acls))) + for i := 0; i < len(m.Acls); i++ { + var x uint32 + if i < len(m.Acls) { + x = uint32(m.Acls[i]) + } + buf.EncodeUint32(x) + } + return buf.Bytes(), nil +} +func (m *MacipACLInterfaceGetReply) Unmarshal(b []byte) error { + buf := codec.NewBuffer(b) + m.Count = buf.DecodeUint32() + m.Acls = make([]uint32, m.Count) + for i := 0; i < len(m.Acls); i++ { + m.Acls[i] = buf.DecodeUint32() + } + return nil +} + +// MacipACLInterfaceListDetails defines message 'macip_acl_interface_list_details'. +type MacipACLInterfaceListDetails struct { + SwIfIndex interface_types.InterfaceIndex `binapi:"interface_index,name=sw_if_index" json:"sw_if_index,omitempty"` + Count uint8 `binapi:"u8,name=count" json:"-"` + Acls []uint32 `binapi:"u32[count],name=acls" json:"acls,omitempty"` +} + +func (m *MacipACLInterfaceListDetails) Reset() { *m = MacipACLInterfaceListDetails{} } +func (*MacipACLInterfaceListDetails) GetMessageName() string { + return "macip_acl_interface_list_details" +} +func (*MacipACLInterfaceListDetails) GetCrcString() string { return "a0c5d56d" } +func (*MacipACLInterfaceListDetails) GetMessageType() api.MessageType { + return api.ReplyMessage +} + +func (m *MacipACLInterfaceListDetails) Size() (size int) { + if m == nil { + return 0 + } + size += 4 // m.SwIfIndex + size += 1 // m.Count + size += 4 * len(m.Acls) // m.Acls + return size +} +func (m *MacipACLInterfaceListDetails) Marshal(b []byte) ([]byte, error) { + if b == nil { + b = make([]byte, m.Size()) + } + buf := codec.NewBuffer(b) + buf.EncodeUint32(uint32(m.SwIfIndex)) + buf.EncodeUint8(uint8(len(m.Acls))) + for i := 0; i < len(m.Acls); i++ { + var x uint32 + if i < len(m.Acls) { + x = uint32(m.Acls[i]) + } + buf.EncodeUint32(x) + } + return buf.Bytes(), nil +} +func (m *MacipACLInterfaceListDetails) Unmarshal(b []byte) error { + buf := codec.NewBuffer(b) + m.SwIfIndex = interface_types.InterfaceIndex(buf.DecodeUint32()) + m.Count = buf.DecodeUint8() + m.Acls = make([]uint32, m.Count) + for i := 0; i < len(m.Acls); i++ { + m.Acls[i] = buf.DecodeUint32() + } + return nil +} + +// MacipACLInterfaceListDump defines message 'macip_acl_interface_list_dump'. +type MacipACLInterfaceListDump struct { + SwIfIndex interface_types.InterfaceIndex `binapi:"interface_index,name=sw_if_index" json:"sw_if_index,omitempty"` +} + +func (m *MacipACLInterfaceListDump) Reset() { *m = MacipACLInterfaceListDump{} } +func (*MacipACLInterfaceListDump) GetMessageName() string { return "macip_acl_interface_list_dump" } +func (*MacipACLInterfaceListDump) GetCrcString() string { return "f9e6675e" } +func (*MacipACLInterfaceListDump) GetMessageType() api.MessageType { + return api.RequestMessage +} + +func (m *MacipACLInterfaceListDump) Size() (size int) { + if m == nil { + return 0 + } + size += 4 // m.SwIfIndex + return size +} +func (m *MacipACLInterfaceListDump) Marshal(b []byte) ([]byte, error) { + if b == nil { + b = make([]byte, m.Size()) + } + buf := codec.NewBuffer(b) + buf.EncodeUint32(uint32(m.SwIfIndex)) + return buf.Bytes(), nil +} +func (m *MacipACLInterfaceListDump) Unmarshal(b []byte) error { + buf := codec.NewBuffer(b) + m.SwIfIndex = interface_types.InterfaceIndex(buf.DecodeUint32()) + return nil +} + +func init() { file_acl_binapi_init() } +func file_acl_binapi_init() { + api.RegisterMessage((*ACLAddReplace)(nil), "acl_add_replace_ee5c2f18") + api.RegisterMessage((*ACLAddReplaceReply)(nil), "acl_add_replace_reply_ac407b0c") + api.RegisterMessage((*ACLDel)(nil), "acl_del_ef34fea4") + api.RegisterMessage((*ACLDelReply)(nil), "acl_del_reply_e8d4e804") + api.RegisterMessage((*ACLDetails)(nil), "acl_details_95babae0") + api.RegisterMessage((*ACLDump)(nil), "acl_dump_ef34fea4") + api.RegisterMessage((*ACLInterfaceAddDel)(nil), "acl_interface_add_del_4b54bebd") + api.RegisterMessage((*ACLInterfaceAddDelReply)(nil), "acl_interface_add_del_reply_e8d4e804") + api.RegisterMessage((*ACLInterfaceEtypeWhitelistDetails)(nil), "acl_interface_etype_whitelist_details_cc2bfded") + api.RegisterMessage((*ACLInterfaceEtypeWhitelistDump)(nil), "acl_interface_etype_whitelist_dump_f9e6675e") + api.RegisterMessage((*ACLInterfaceListDetails)(nil), "acl_interface_list_details_e695d256") + api.RegisterMessage((*ACLInterfaceListDump)(nil), "acl_interface_list_dump_f9e6675e") + api.RegisterMessage((*ACLInterfaceSetACLList)(nil), "acl_interface_set_acl_list_473982bd") + api.RegisterMessage((*ACLInterfaceSetACLListReply)(nil), "acl_interface_set_acl_list_reply_e8d4e804") + api.RegisterMessage((*ACLInterfaceSetEtypeWhitelist)(nil), "acl_interface_set_etype_whitelist_3f5c2d2d") + api.RegisterMessage((*ACLInterfaceSetEtypeWhitelistReply)(nil), "acl_interface_set_etype_whitelist_reply_e8d4e804") + api.RegisterMessage((*ACLPluginControlPing)(nil), "acl_plugin_control_ping_51077d14") + api.RegisterMessage((*ACLPluginControlPingReply)(nil), "acl_plugin_control_ping_reply_f6b0b8ca") + api.RegisterMessage((*ACLPluginGetConnTableMaxEntries)(nil), "acl_plugin_get_conn_table_max_entries_51077d14") + api.RegisterMessage((*ACLPluginGetConnTableMaxEntriesReply)(nil), "acl_plugin_get_conn_table_max_entries_reply_7a096d3d") + api.RegisterMessage((*ACLPluginGetVersion)(nil), "acl_plugin_get_version_51077d14") + api.RegisterMessage((*ACLPluginGetVersionReply)(nil), "acl_plugin_get_version_reply_9b32cf86") + api.RegisterMessage((*ACLPluginUseHashLookupGet)(nil), "acl_plugin_use_hash_lookup_get_51077d14") + api.RegisterMessage((*ACLPluginUseHashLookupGetReply)(nil), "acl_plugin_use_hash_lookup_get_reply_5392ad31") + api.RegisterMessage((*ACLPluginUseHashLookupSet)(nil), "acl_plugin_use_hash_lookup_set_b3e225d2") + api.RegisterMessage((*ACLPluginUseHashLookupSetReply)(nil), "acl_plugin_use_hash_lookup_set_reply_e8d4e804") + api.RegisterMessage((*ACLStatsIntfCountersEnable)(nil), "acl_stats_intf_counters_enable_b3e225d2") + api.RegisterMessage((*ACLStatsIntfCountersEnableReply)(nil), "acl_stats_intf_counters_enable_reply_e8d4e804") + api.RegisterMessage((*MacipACLAdd)(nil), "macip_acl_add_ce6fbad0") + api.RegisterMessage((*MacipACLAddReplace)(nil), "macip_acl_add_replace_2a461dd4") + api.RegisterMessage((*MacipACLAddReplaceReply)(nil), "macip_acl_add_replace_reply_ac407b0c") + api.RegisterMessage((*MacipACLAddReply)(nil), "macip_acl_add_reply_ac407b0c") + api.RegisterMessage((*MacipACLDel)(nil), "macip_acl_del_ef34fea4") + api.RegisterMessage((*MacipACLDelReply)(nil), "macip_acl_del_reply_e8d4e804") + api.RegisterMessage((*MacipACLDetails)(nil), "macip_acl_details_27135b59") + api.RegisterMessage((*MacipACLDump)(nil), "macip_acl_dump_ef34fea4") + api.RegisterMessage((*MacipACLInterfaceAddDel)(nil), "macip_acl_interface_add_del_4b8690b1") + api.RegisterMessage((*MacipACLInterfaceAddDelReply)(nil), "macip_acl_interface_add_del_reply_e8d4e804") + api.RegisterMessage((*MacipACLInterfaceGet)(nil), "macip_acl_interface_get_51077d14") + api.RegisterMessage((*MacipACLInterfaceGetReply)(nil), "macip_acl_interface_get_reply_accf9b05") + api.RegisterMessage((*MacipACLInterfaceListDetails)(nil), "macip_acl_interface_list_details_a0c5d56d") + api.RegisterMessage((*MacipACLInterfaceListDump)(nil), "macip_acl_interface_list_dump_f9e6675e") +} + +// Messages returns list of all messages in this module. +func AllMessages() []api.Message { + return []api.Message{ + (*ACLAddReplace)(nil), + (*ACLAddReplaceReply)(nil), + (*ACLDel)(nil), + (*ACLDelReply)(nil), + (*ACLDetails)(nil), + (*ACLDump)(nil), + (*ACLInterfaceAddDel)(nil), + (*ACLInterfaceAddDelReply)(nil), + (*ACLInterfaceEtypeWhitelistDetails)(nil), + (*ACLInterfaceEtypeWhitelistDump)(nil), + (*ACLInterfaceListDetails)(nil), + (*ACLInterfaceListDump)(nil), + (*ACLInterfaceSetACLList)(nil), + (*ACLInterfaceSetACLListReply)(nil), + (*ACLInterfaceSetEtypeWhitelist)(nil), + (*ACLInterfaceSetEtypeWhitelistReply)(nil), + (*ACLPluginControlPing)(nil), + (*ACLPluginControlPingReply)(nil), + (*ACLPluginGetConnTableMaxEntries)(nil), + (*ACLPluginGetConnTableMaxEntriesReply)(nil), + (*ACLPluginGetVersion)(nil), + (*ACLPluginGetVersionReply)(nil), + (*ACLPluginUseHashLookupGet)(nil), + (*ACLPluginUseHashLookupGetReply)(nil), + (*ACLPluginUseHashLookupSet)(nil), + (*ACLPluginUseHashLookupSetReply)(nil), + (*ACLStatsIntfCountersEnable)(nil), + (*ACLStatsIntfCountersEnableReply)(nil), + (*MacipACLAdd)(nil), + (*MacipACLAddReplace)(nil), + (*MacipACLAddReplaceReply)(nil), + (*MacipACLAddReply)(nil), + (*MacipACLDel)(nil), + (*MacipACLDelReply)(nil), + (*MacipACLDetails)(nil), + (*MacipACLDump)(nil), + (*MacipACLInterfaceAddDel)(nil), + (*MacipACLInterfaceAddDelReply)(nil), + (*MacipACLInterfaceGet)(nil), + (*MacipACLInterfaceGetReply)(nil), + (*MacipACLInterfaceListDetails)(nil), + (*MacipACLInterfaceListDump)(nil), + } +} diff --git a/plugins/vpp/binapi/vpp2306/acl/acl_rpc.ba.go b/plugins/vpp/binapi/vpp2306/acl/acl_rpc.ba.go new file mode 100644 index 0000000000..01baf2c1f4 --- /dev/null +++ b/plugins/vpp/binapi/vpp2306/acl/acl_rpc.ba.go @@ -0,0 +1,404 @@ +// Code generated by GoVPP's binapi-generator. DO NOT EDIT. + +package acl + +import ( + "context" + "fmt" + "io" + + api "go.fd.io/govpp/api" + memclnt "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/memclnt" +) + +// RPCService defines RPC service acl. +type RPCService interface { + ACLAddReplace(ctx context.Context, in *ACLAddReplace) (*ACLAddReplaceReply, error) + ACLDel(ctx context.Context, in *ACLDel) (*ACLDelReply, error) + ACLDump(ctx context.Context, in *ACLDump) (RPCService_ACLDumpClient, error) + ACLInterfaceAddDel(ctx context.Context, in *ACLInterfaceAddDel) (*ACLInterfaceAddDelReply, error) + ACLInterfaceEtypeWhitelistDump(ctx context.Context, in *ACLInterfaceEtypeWhitelistDump) (RPCService_ACLInterfaceEtypeWhitelistDumpClient, error) + ACLInterfaceListDump(ctx context.Context, in *ACLInterfaceListDump) (RPCService_ACLInterfaceListDumpClient, error) + ACLInterfaceSetACLList(ctx context.Context, in *ACLInterfaceSetACLList) (*ACLInterfaceSetACLListReply, error) + ACLInterfaceSetEtypeWhitelist(ctx context.Context, in *ACLInterfaceSetEtypeWhitelist) (*ACLInterfaceSetEtypeWhitelistReply, error) + ACLPluginControlPing(ctx context.Context, in *ACLPluginControlPing) (*ACLPluginControlPingReply, error) + ACLPluginGetConnTableMaxEntries(ctx context.Context, in *ACLPluginGetConnTableMaxEntries) (*ACLPluginGetConnTableMaxEntriesReply, error) + ACLPluginGetVersion(ctx context.Context, in *ACLPluginGetVersion) (*ACLPluginGetVersionReply, error) + ACLPluginUseHashLookupGet(ctx context.Context, in *ACLPluginUseHashLookupGet) (*ACLPluginUseHashLookupGetReply, error) + ACLPluginUseHashLookupSet(ctx context.Context, in *ACLPluginUseHashLookupSet) (*ACLPluginUseHashLookupSetReply, error) + ACLStatsIntfCountersEnable(ctx context.Context, in *ACLStatsIntfCountersEnable) (*ACLStatsIntfCountersEnableReply, error) + MacipACLAdd(ctx context.Context, in *MacipACLAdd) (*MacipACLAddReply, error) + MacipACLAddReplace(ctx context.Context, in *MacipACLAddReplace) (*MacipACLAddReplaceReply, error) + MacipACLDel(ctx context.Context, in *MacipACLDel) (*MacipACLDelReply, error) + MacipACLDump(ctx context.Context, in *MacipACLDump) (RPCService_MacipACLDumpClient, error) + MacipACLInterfaceAddDel(ctx context.Context, in *MacipACLInterfaceAddDel) (*MacipACLInterfaceAddDelReply, error) + MacipACLInterfaceGet(ctx context.Context, in *MacipACLInterfaceGet) (*MacipACLInterfaceGetReply, error) + MacipACLInterfaceListDump(ctx context.Context, in *MacipACLInterfaceListDump) (RPCService_MacipACLInterfaceListDumpClient, error) +} + +type serviceClient struct { + conn api.Connection +} + +func NewServiceClient(conn api.Connection) RPCService { + return &serviceClient{conn} +} + +func (c *serviceClient) ACLAddReplace(ctx context.Context, in *ACLAddReplace) (*ACLAddReplaceReply, error) { + out := new(ACLAddReplaceReply) + err := c.conn.Invoke(ctx, in, out) + if err != nil { + return nil, err + } + return out, api.RetvalToVPPApiError(out.Retval) +} + +func (c *serviceClient) ACLDel(ctx context.Context, in *ACLDel) (*ACLDelReply, error) { + out := new(ACLDelReply) + err := c.conn.Invoke(ctx, in, out) + if err != nil { + return nil, err + } + return out, api.RetvalToVPPApiError(out.Retval) +} + +func (c *serviceClient) ACLDump(ctx context.Context, in *ACLDump) (RPCService_ACLDumpClient, error) { + stream, err := c.conn.NewStream(ctx) + if err != nil { + return nil, err + } + x := &serviceClient_ACLDumpClient{stream} + if err := x.Stream.SendMsg(in); err != nil { + return nil, err + } + if err = x.Stream.SendMsg(&memclnt.ControlPing{}); err != nil { + return nil, err + } + return x, nil +} + +type RPCService_ACLDumpClient interface { + Recv() (*ACLDetails, error) + api.Stream +} + +type serviceClient_ACLDumpClient struct { + api.Stream +} + +func (c *serviceClient_ACLDumpClient) Recv() (*ACLDetails, error) { + msg, err := c.Stream.RecvMsg() + if err != nil { + return nil, err + } + switch m := msg.(type) { + case *ACLDetails: + return m, nil + case *memclnt.ControlPingReply: + err = c.Stream.Close() + if err != nil { + return nil, err + } + return nil, io.EOF + default: + return nil, fmt.Errorf("unexpected message: %T %v", m, m) + } +} + +func (c *serviceClient) ACLInterfaceAddDel(ctx context.Context, in *ACLInterfaceAddDel) (*ACLInterfaceAddDelReply, error) { + out := new(ACLInterfaceAddDelReply) + err := c.conn.Invoke(ctx, in, out) + if err != nil { + return nil, err + } + return out, api.RetvalToVPPApiError(out.Retval) +} + +func (c *serviceClient) ACLInterfaceEtypeWhitelistDump(ctx context.Context, in *ACLInterfaceEtypeWhitelistDump) (RPCService_ACLInterfaceEtypeWhitelistDumpClient, error) { + stream, err := c.conn.NewStream(ctx) + if err != nil { + return nil, err + } + x := &serviceClient_ACLInterfaceEtypeWhitelistDumpClient{stream} + if err := x.Stream.SendMsg(in); err != nil { + return nil, err + } + if err = x.Stream.SendMsg(&memclnt.ControlPing{}); err != nil { + return nil, err + } + return x, nil +} + +type RPCService_ACLInterfaceEtypeWhitelistDumpClient interface { + Recv() (*ACLInterfaceEtypeWhitelistDetails, error) + api.Stream +} + +type serviceClient_ACLInterfaceEtypeWhitelistDumpClient struct { + api.Stream +} + +func (c *serviceClient_ACLInterfaceEtypeWhitelistDumpClient) Recv() (*ACLInterfaceEtypeWhitelistDetails, error) { + msg, err := c.Stream.RecvMsg() + if err != nil { + return nil, err + } + switch m := msg.(type) { + case *ACLInterfaceEtypeWhitelistDetails: + return m, nil + case *memclnt.ControlPingReply: + err = c.Stream.Close() + if err != nil { + return nil, err + } + return nil, io.EOF + default: + return nil, fmt.Errorf("unexpected message: %T %v", m, m) + } +} + +func (c *serviceClient) ACLInterfaceListDump(ctx context.Context, in *ACLInterfaceListDump) (RPCService_ACLInterfaceListDumpClient, error) { + stream, err := c.conn.NewStream(ctx) + if err != nil { + return nil, err + } + x := &serviceClient_ACLInterfaceListDumpClient{stream} + if err := x.Stream.SendMsg(in); err != nil { + return nil, err + } + if err = x.Stream.SendMsg(&memclnt.ControlPing{}); err != nil { + return nil, err + } + return x, nil +} + +type RPCService_ACLInterfaceListDumpClient interface { + Recv() (*ACLInterfaceListDetails, error) + api.Stream +} + +type serviceClient_ACLInterfaceListDumpClient struct { + api.Stream +} + +func (c *serviceClient_ACLInterfaceListDumpClient) Recv() (*ACLInterfaceListDetails, error) { + msg, err := c.Stream.RecvMsg() + if err != nil { + return nil, err + } + switch m := msg.(type) { + case *ACLInterfaceListDetails: + return m, nil + case *memclnt.ControlPingReply: + err = c.Stream.Close() + if err != nil { + return nil, err + } + return nil, io.EOF + default: + return nil, fmt.Errorf("unexpected message: %T %v", m, m) + } +} + +func (c *serviceClient) ACLInterfaceSetACLList(ctx context.Context, in *ACLInterfaceSetACLList) (*ACLInterfaceSetACLListReply, error) { + out := new(ACLInterfaceSetACLListReply) + err := c.conn.Invoke(ctx, in, out) + if err != nil { + return nil, err + } + return out, api.RetvalToVPPApiError(out.Retval) +} + +func (c *serviceClient) ACLInterfaceSetEtypeWhitelist(ctx context.Context, in *ACLInterfaceSetEtypeWhitelist) (*ACLInterfaceSetEtypeWhitelistReply, error) { + out := new(ACLInterfaceSetEtypeWhitelistReply) + err := c.conn.Invoke(ctx, in, out) + if err != nil { + return nil, err + } + return out, api.RetvalToVPPApiError(out.Retval) +} + +func (c *serviceClient) ACLPluginControlPing(ctx context.Context, in *ACLPluginControlPing) (*ACLPluginControlPingReply, error) { + out := new(ACLPluginControlPingReply) + err := c.conn.Invoke(ctx, in, out) + if err != nil { + return nil, err + } + return out, api.RetvalToVPPApiError(out.Retval) +} + +func (c *serviceClient) ACLPluginGetConnTableMaxEntries(ctx context.Context, in *ACLPluginGetConnTableMaxEntries) (*ACLPluginGetConnTableMaxEntriesReply, error) { + out := new(ACLPluginGetConnTableMaxEntriesReply) + err := c.conn.Invoke(ctx, in, out) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *serviceClient) ACLPluginGetVersion(ctx context.Context, in *ACLPluginGetVersion) (*ACLPluginGetVersionReply, error) { + out := new(ACLPluginGetVersionReply) + err := c.conn.Invoke(ctx, in, out) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *serviceClient) ACLPluginUseHashLookupGet(ctx context.Context, in *ACLPluginUseHashLookupGet) (*ACLPluginUseHashLookupGetReply, error) { + out := new(ACLPluginUseHashLookupGetReply) + err := c.conn.Invoke(ctx, in, out) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *serviceClient) ACLPluginUseHashLookupSet(ctx context.Context, in *ACLPluginUseHashLookupSet) (*ACLPluginUseHashLookupSetReply, error) { + out := new(ACLPluginUseHashLookupSetReply) + err := c.conn.Invoke(ctx, in, out) + if err != nil { + return nil, err + } + return out, api.RetvalToVPPApiError(out.Retval) +} + +func (c *serviceClient) ACLStatsIntfCountersEnable(ctx context.Context, in *ACLStatsIntfCountersEnable) (*ACLStatsIntfCountersEnableReply, error) { + out := new(ACLStatsIntfCountersEnableReply) + err := c.conn.Invoke(ctx, in, out) + if err != nil { + return nil, err + } + return out, api.RetvalToVPPApiError(out.Retval) +} + +func (c *serviceClient) MacipACLAdd(ctx context.Context, in *MacipACLAdd) (*MacipACLAddReply, error) { + out := new(MacipACLAddReply) + err := c.conn.Invoke(ctx, in, out) + if err != nil { + return nil, err + } + return out, api.RetvalToVPPApiError(out.Retval) +} + +func (c *serviceClient) MacipACLAddReplace(ctx context.Context, in *MacipACLAddReplace) (*MacipACLAddReplaceReply, error) { + out := new(MacipACLAddReplaceReply) + err := c.conn.Invoke(ctx, in, out) + if err != nil { + return nil, err + } + return out, api.RetvalToVPPApiError(out.Retval) +} + +func (c *serviceClient) MacipACLDel(ctx context.Context, in *MacipACLDel) (*MacipACLDelReply, error) { + out := new(MacipACLDelReply) + err := c.conn.Invoke(ctx, in, out) + if err != nil { + return nil, err + } + return out, api.RetvalToVPPApiError(out.Retval) +} + +func (c *serviceClient) MacipACLDump(ctx context.Context, in *MacipACLDump) (RPCService_MacipACLDumpClient, error) { + stream, err := c.conn.NewStream(ctx) + if err != nil { + return nil, err + } + x := &serviceClient_MacipACLDumpClient{stream} + if err := x.Stream.SendMsg(in); err != nil { + return nil, err + } + if err = x.Stream.SendMsg(&memclnt.ControlPing{}); err != nil { + return nil, err + } + return x, nil +} + +type RPCService_MacipACLDumpClient interface { + Recv() (*MacipACLDetails, error) + api.Stream +} + +type serviceClient_MacipACLDumpClient struct { + api.Stream +} + +func (c *serviceClient_MacipACLDumpClient) Recv() (*MacipACLDetails, error) { + msg, err := c.Stream.RecvMsg() + if err != nil { + return nil, err + } + switch m := msg.(type) { + case *MacipACLDetails: + return m, nil + case *memclnt.ControlPingReply: + err = c.Stream.Close() + if err != nil { + return nil, err + } + return nil, io.EOF + default: + return nil, fmt.Errorf("unexpected message: %T %v", m, m) + } +} + +func (c *serviceClient) MacipACLInterfaceAddDel(ctx context.Context, in *MacipACLInterfaceAddDel) (*MacipACLInterfaceAddDelReply, error) { + out := new(MacipACLInterfaceAddDelReply) + err := c.conn.Invoke(ctx, in, out) + if err != nil { + return nil, err + } + return out, api.RetvalToVPPApiError(out.Retval) +} + +func (c *serviceClient) MacipACLInterfaceGet(ctx context.Context, in *MacipACLInterfaceGet) (*MacipACLInterfaceGetReply, error) { + out := new(MacipACLInterfaceGetReply) + err := c.conn.Invoke(ctx, in, out) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *serviceClient) MacipACLInterfaceListDump(ctx context.Context, in *MacipACLInterfaceListDump) (RPCService_MacipACLInterfaceListDumpClient, error) { + stream, err := c.conn.NewStream(ctx) + if err != nil { + return nil, err + } + x := &serviceClient_MacipACLInterfaceListDumpClient{stream} + if err := x.Stream.SendMsg(in); err != nil { + return nil, err + } + if err = x.Stream.SendMsg(&memclnt.ControlPing{}); err != nil { + return nil, err + } + return x, nil +} + +type RPCService_MacipACLInterfaceListDumpClient interface { + Recv() (*MacipACLInterfaceListDetails, error) + api.Stream +} + +type serviceClient_MacipACLInterfaceListDumpClient struct { + api.Stream +} + +func (c *serviceClient_MacipACLInterfaceListDumpClient) Recv() (*MacipACLInterfaceListDetails, error) { + msg, err := c.Stream.RecvMsg() + if err != nil { + return nil, err + } + switch m := msg.(type) { + case *MacipACLInterfaceListDetails: + return m, nil + case *memclnt.ControlPingReply: + err = c.Stream.Close() + if err != nil { + return nil, err + } + return nil, io.EOF + default: + return nil, fmt.Errorf("unexpected message: %T %v", m, m) + } +} diff --git a/plugins/vpp/binapi/vpp2306/acl_types/acl_types.ba.go b/plugins/vpp/binapi/vpp2306/acl_types/acl_types.ba.go new file mode 100644 index 0000000000..42c107c259 --- /dev/null +++ b/plugins/vpp/binapi/vpp2306/acl_types/acl_types.ba.go @@ -0,0 +1,80 @@ +// Code generated by GoVPP's binapi-generator. DO NOT EDIT. + +// Package acl_types contains generated bindings for API file acl_types.api. +// +// Contents: +// - 1 enum +// - 2 structs +package acl_types + +import ( + "strconv" + + api "go.fd.io/govpp/api" + ethernet_types "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ethernet_types" + ip_types "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip_types" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the GoVPP api package it is being compiled against. +// A compilation error at this line likely means your copy of the +// GoVPP api package needs to be updated. +const _ = api.GoVppAPIPackageIsVersion2 + +const ( + APIFile = "acl_types" + APIVersion = "1.0.0" + VersionCrc = 0x878da4fa +) + +// ACLAction defines enum 'acl_action'. +type ACLAction uint8 + +const ( + ACL_ACTION_API_DENY ACLAction = 0 + ACL_ACTION_API_PERMIT ACLAction = 1 + ACL_ACTION_API_PERMIT_REFLECT ACLAction = 2 +) + +var ( + ACLAction_name = map[uint8]string{ + 0: "ACL_ACTION_API_DENY", + 1: "ACL_ACTION_API_PERMIT", + 2: "ACL_ACTION_API_PERMIT_REFLECT", + } + ACLAction_value = map[string]uint8{ + "ACL_ACTION_API_DENY": 0, + "ACL_ACTION_API_PERMIT": 1, + "ACL_ACTION_API_PERMIT_REFLECT": 2, + } +) + +func (x ACLAction) String() string { + s, ok := ACLAction_name[uint8(x)] + if ok { + return s + } + return "ACLAction(" + strconv.Itoa(int(x)) + ")" +} + +// ACLRule defines type 'acl_rule'. +type ACLRule struct { + IsPermit ACLAction `binapi:"acl_action,name=is_permit" json:"is_permit,omitempty"` + SrcPrefix ip_types.Prefix `binapi:"prefix,name=src_prefix" json:"src_prefix,omitempty"` + DstPrefix ip_types.Prefix `binapi:"prefix,name=dst_prefix" json:"dst_prefix,omitempty"` + Proto ip_types.IPProto `binapi:"ip_proto,name=proto" json:"proto,omitempty"` + SrcportOrIcmptypeFirst uint16 `binapi:"u16,name=srcport_or_icmptype_first" json:"srcport_or_icmptype_first,omitempty"` + SrcportOrIcmptypeLast uint16 `binapi:"u16,name=srcport_or_icmptype_last" json:"srcport_or_icmptype_last,omitempty"` + DstportOrIcmpcodeFirst uint16 `binapi:"u16,name=dstport_or_icmpcode_first" json:"dstport_or_icmpcode_first,omitempty"` + DstportOrIcmpcodeLast uint16 `binapi:"u16,name=dstport_or_icmpcode_last" json:"dstport_or_icmpcode_last,omitempty"` + TCPFlagsMask uint8 `binapi:"u8,name=tcp_flags_mask" json:"tcp_flags_mask,omitempty"` + TCPFlagsValue uint8 `binapi:"u8,name=tcp_flags_value" json:"tcp_flags_value,omitempty"` +} + +// MacipACLRule defines type 'macip_acl_rule'. +type MacipACLRule struct { + IsPermit ACLAction `binapi:"acl_action,name=is_permit" json:"is_permit,omitempty"` + SrcMac ethernet_types.MacAddress `binapi:"mac_address,name=src_mac" json:"src_mac,omitempty"` + SrcMacMask ethernet_types.MacAddress `binapi:"mac_address,name=src_mac_mask" json:"src_mac_mask,omitempty"` + SrcPrefix ip_types.Prefix `binapi:"prefix,name=src_prefix" json:"src_prefix,omitempty"` +} diff --git a/plugins/vpp/binapi/vpp2306/af_packet/af_packet.ba.go b/plugins/vpp/binapi/vpp2306/af_packet/af_packet.ba.go new file mode 100644 index 0000000000..ebc3a2c28e --- /dev/null +++ b/plugins/vpp/binapi/vpp2306/af_packet/af_packet.ba.go @@ -0,0 +1,631 @@ +// Code generated by GoVPP's binapi-generator. DO NOT EDIT. + +// Package af_packet contains generated bindings for API file af_packet.api. +// +// Contents: +// - 2 enums +// - 12 messages +package af_packet + +import ( + "strconv" + + api "go.fd.io/govpp/api" + codec "go.fd.io/govpp/codec" + ethernet_types "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ethernet_types" + interface_types "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface_types" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the GoVPP api package it is being compiled against. +// A compilation error at this line likely means your copy of the +// GoVPP api package needs to be updated. +const _ = api.GoVppAPIPackageIsVersion2 + +const ( + APIFile = "af_packet" + APIVersion = "2.0.0" + VersionCrc = 0x5b12de21 +) + +// AfPacketFlags defines enum 'af_packet_flags'. +type AfPacketFlags uint32 + +const ( + AF_PACKET_API_FLAG_QDISC_BYPASS AfPacketFlags = 1 + AF_PACKET_API_FLAG_CKSUM_GSO AfPacketFlags = 2 + AF_PACKET_API_FLAG_VERSION_2 AfPacketFlags = 8 +) + +var ( + AfPacketFlags_name = map[uint32]string{ + 1: "AF_PACKET_API_FLAG_QDISC_BYPASS", + 2: "AF_PACKET_API_FLAG_CKSUM_GSO", + 8: "AF_PACKET_API_FLAG_VERSION_2", + } + AfPacketFlags_value = map[string]uint32{ + "AF_PACKET_API_FLAG_QDISC_BYPASS": 1, + "AF_PACKET_API_FLAG_CKSUM_GSO": 2, + "AF_PACKET_API_FLAG_VERSION_2": 8, + } +) + +func (x AfPacketFlags) String() string { + s, ok := AfPacketFlags_name[uint32(x)] + if ok { + return s + } + str := func(n uint32) string { + s, ok := AfPacketFlags_name[uint32(n)] + if ok { + return s + } + return "AfPacketFlags(" + strconv.Itoa(int(n)) + ")" + } + for i := uint32(0); i <= 32; i++ { + val := uint32(x) + if val&(1< 0 { + ifIdx = ifIdxs[0] + } + // First, dump all interfaces to create initial data. + reqCtx := h.callsChannel.SendMultiRequest(&vpp_ifs.SwInterfaceDump{ + SwIfIndex: interface_types.InterfaceIndex(ifIdx), + }) + for { + ifDetails := &vpp_ifs.SwInterfaceDetails{} + stop, err := reqCtx.ReceiveReply(ifDetails) + if stop { + break + } + if err != nil { + return nil, fmt.Errorf("failed to dump interface: %v", err) + } + + internalName := strings.TrimRight(ifDetails.InterfaceName, "\x00") + ifaceDevType := strings.TrimRight(ifDetails.InterfaceDevType, "\x00") + physAddr := make(net.HardwareAddr, macLength) + copy(physAddr, ifDetails.L2Address[:]) + + details := &vppcalls.InterfaceDetails{ + Interface: &ifs.Interface{ + Name: strings.TrimRight(ifDetails.Tag, "\x00"), + // the type may be amended later by further dumps + Type: guessInterfaceType(ifaceDevType, internalName), + Enabled: isAdminStateUp(ifDetails.Flags), + PhysAddress: net.HardwareAddr(ifDetails.L2Address[:]).String(), + Mtu: getMtu(ifDetails.LinkMtu), + }, + Meta: &vppcalls.InterfaceMeta{ + SwIfIndex: uint32(ifDetails.SwIfIndex), + SupSwIfIndex: ifDetails.SupSwIfIndex, + L2Address: physAddr, + InternalName: internalName, + DevType: ifaceDevType, + IsAdminStateUp: isAdminStateUp(ifDetails.Flags), + IsLinkStateUp: isLinkStateUp(ifDetails.Flags), + LinkDuplex: uint32(ifDetails.LinkDuplex), + LinkMTU: ifDetails.LinkMtu, + MTU: ifDetails.Mtu, + LinkSpeed: ifDetails.LinkSpeed, + SubID: ifDetails.SubID, + Tag: strings.TrimRight(ifDetails.Tag, "\x00"), + }, + } + + // sub interface + if ifDetails.SupSwIfIndex != uint32(ifDetails.SwIfIndex) { + details.Interface.Type = ifs.Interface_SUB_INTERFACE + details.Interface.Link = &ifs.Interface_Sub{ + Sub: &ifs.SubInterface{ + ParentName: interfaces[ifDetails.SupSwIfIndex].Interface.Name, + SubId: ifDetails.SubID, + TagRwOption: getTagRwOption(ifDetails.VtrOp), + PushDot1Q: uintToBool(uint8(ifDetails.VtrPushDot1q)), + Tag1: ifDetails.VtrTag1, + Tag2: ifDetails.VtrTag2, + }, + } + } + // Fill name for physical interfaces (they are mostly without tag) + switch details.Interface.Type { + case ifs.Interface_DPDK: + details.Interface.Name = internalName + case ifs.Interface_AF_PACKET: + details.Interface.Link = &ifs.Interface_Afpacket{ + Afpacket: &ifs.AfpacketLink{ + HostIfName: strings.TrimPrefix(internalName, "host-"), + }, + } + } + if details.Interface.Name == "" { + // untagged interface - generate a logical name for it + // (apart from local0 it will get removed by resync) + details.Interface.Name = untaggedIfPreffix + internalName + } + interfaces[uint32(ifDetails.SwIfIndex)] = details + } + + return interfaces, nil +} + +func (h *InterfaceVppHandler) DumpInterfaces(ctx context.Context) (map[uint32]*vppcalls.InterfaceDetails, error) { + // Dump all interfaces + interfaces, err := h.dumpInterfaces() + if err != nil { + return nil, err + } + + // Get DHCP clients + dhcpClients, err := h.DumpDhcpClients() + if err != nil { + return nil, fmt.Errorf("failed to dump interface DHCP clients: %v", err) + } + + // Get IP addresses before VRF + err = h.dumpIPAddressDetails(interfaces, false, dhcpClients) + if err != nil { + return nil, err + } + err = h.dumpIPAddressDetails(interfaces, true, dhcpClients) + if err != nil { + return nil, err + } + + // Get unnumbered interfaces + unnumbered, err := h.dumpUnnumberedDetails() + if err != nil { + return nil, fmt.Errorf("failed to dump unnumbered interfaces: %v", err) + } + + // dump VXLAN details before VRFs (used by isIpv6Interface) + err = h.dumpVxlanDetails(interfaces) + if err != nil { + return nil, err + } + + err = h.dumpVxLanGpeDetails(interfaces) + if err != nil { + return nil, err + } + + // Get interface VRF for every IP family, fill DHCP if set and resolve unnumbered interface setup + for _, ifData := range interfaces { + // VRF is stored in metadata for both, IPv4 and IPv6. If the interface is an IPv6 interface (it contains at least + // one IPv6 address), appropriate VRF is stored also in modelled data + ipv4Vrf, err := h.GetInterfaceVrf(ifData.Meta.SwIfIndex) + if err != nil { + return nil, fmt.Errorf("interface dump: failed to get IPv4 VRF from interface %d: %v", + ifData.Meta.SwIfIndex, err) + } + ifData.Meta.VrfIPv4 = ipv4Vrf + ipv6Vrf, err := h.GetInterfaceVrfIPv6(ifData.Meta.SwIfIndex) + if err != nil { + return nil, fmt.Errorf("interface dump: failed to get IPv6 VRF from interface %d: %v", + ifData.Meta.SwIfIndex, err) + } + ifData.Meta.VrfIPv6 = ipv6Vrf + if isIPv6If, err := isIpv6Interface(ifData.Interface); err != nil { + return interfaces, err + } else if isIPv6If { + ifData.Interface.Vrf = ipv6Vrf + } else { + ifData.Interface.Vrf = ipv4Vrf + } + + // DHCP + dhcpData, ok := dhcpClients[ifData.Meta.SwIfIndex] + if ok { + ifData.Interface.SetDhcpClient = true + ifData.Meta.Dhcp = dhcpData + } + // Unnumbered + ifWithIPIdx, ok := unnumbered[ifData.Meta.SwIfIndex] + if ok { + // Find unnumbered interface + var ifWithIPName string + ifWithIP, ok := interfaces[ifWithIPIdx] + if ok { + ifWithIPName = ifWithIP.Interface.Name + } else { + h.log.Debugf("cannot find name of the ip-interface for unnumbered %s", ifData.Interface.Name) + ifWithIPName = "" + } + ifData.Interface.Unnumbered = &ifs.Interface_Unnumbered{ + InterfaceWithIp: ifWithIPName, + } + } + } + + err = h.dumpMemifDetails(ctx, interfaces) + if err != nil { + return nil, err + } + + err = h.dumpTapDetails(interfaces) + if err != nil { + return nil, err + } + + err = h.dumpIPSecTunnelDetails(interfaces) + if err != nil { + return nil, err + } + + err = h.dumpVmxNet3Details(interfaces) + if err != nil { + return nil, err + } + + err = h.dumpBondDetails(interfaces) + if err != nil { + return nil, err + } + + err = h.dumpGreDetails(interfaces) + if err != nil { + return nil, err + } + + err = h.dumpGtpuDetails(interfaces) + if err != nil { + return nil, err + } + + err = h.dumpIpipDetails(interfaces) + if err != nil { + return nil, err + } + + err = h.dumpWireguardDetails(interfaces) + if err != nil { + return nil, err + } + + // Rx-placement dump is last since it uses interface type-specific data + err = h.dumpRxPlacement(interfaces) + if err != nil { + return nil, err + } + + return interfaces, nil +} + +// DumpDhcpClients returns a slice of DhcpMeta with all interfaces and other DHCP-related information available +func (h *InterfaceVppHandler) DumpDhcpClients() (map[uint32]*vppcalls.Dhcp, error) { + dhcpData := make(map[uint32]*vppcalls.Dhcp) + reqCtx := h.callsChannel.SendMultiRequest(&vpp_dhcp.DHCPClientDump{}) + + for { + dhcpDetails := &vpp_dhcp.DHCPClientDetails{} + last, err := reqCtx.ReceiveReply(dhcpDetails) + if last { + break + } + if err != nil { + return nil, err + } + client := dhcpDetails.Client + lease := dhcpDetails.Lease + + // DHCP client data + dhcpClient := &vppcalls.Client{ + SwIfIndex: uint32(client.SwIfIndex), + Hostname: strings.TrimRight(client.Hostname, "\x00"), + ID: string(bytes.SplitN(client.ID, []byte{0x00}, 2)[0]), + WantDhcpEvent: client.WantDHCPEvent, + SetBroadcastFlag: client.SetBroadcastFlag, + PID: client.PID, + } + + // DHCP lease data + dhcpLease := &vppcalls.Lease{ + SwIfIndex: uint32(lease.SwIfIndex), + State: uint8(lease.State), + Hostname: strings.TrimRight(lease.Hostname, "\x00"), + IsIPv6: lease.IsIPv6, + HostAddress: dhcpAddressToString(lease.HostAddress, uint32(lease.MaskWidth), lease.IsIPv6), + RouterAddress: dhcpAddressToString(lease.RouterAddress, uint32(lease.MaskWidth), lease.IsIPv6), + HostMac: net.HardwareAddr(lease.HostMac[:]).String(), + } + + // DHCP metadata + dhcpData[uint32(client.SwIfIndex)] = &vppcalls.Dhcp{ + Client: dhcpClient, + Lease: dhcpLease, + } + } + + return dhcpData, nil +} + +// DumpInterfaceStates dumps link and administrative state of every interface. +func (h *InterfaceVppHandler) DumpInterfaceStates(ifIdxs ...uint32) (map[uint32]*vppcalls.InterfaceState, error) { + // Dump all interface states if not specified. + if len(ifIdxs) == 0 { + ifIdxs = []uint32{allInterfaces} + } + + ifStates := make(map[uint32]*vppcalls.InterfaceState) + for _, ifIdx := range ifIdxs { + reqCtx := h.callsChannel.SendMultiRequest(&vpp_ifs.SwInterfaceDump{ + SwIfIndex: interface_types.InterfaceIndex(ifIdx), + }) + for { + ifDetails := &vpp_ifs.SwInterfaceDetails{} + stop, err := reqCtx.ReceiveReply(ifDetails) + if stop { + break // Break from the loop. + } + if err != nil { + return nil, fmt.Errorf("failed to dump interface states: %v", err) + } + + physAddr := make(net.HardwareAddr, macLength) + copy(physAddr, ifDetails.L2Address[:]) + + ifaceState := vppcalls.InterfaceState{ + SwIfIndex: uint32(ifDetails.SwIfIndex), + InternalName: strings.TrimRight(ifDetails.InterfaceName, "\x00"), + PhysAddress: physAddr, + AdminState: adminStateToInterfaceStatus(ifDetails.Flags), + LinkState: linkStateToInterfaceStatus(ifDetails.Flags), + LinkDuplex: toLinkDuplex(ifDetails.LinkDuplex), + LinkSpeed: toLinkSpeed(ifDetails.LinkSpeed), + LinkMTU: ifDetails.LinkMtu, + } + ifStates[uint32(ifDetails.SwIfIndex)] = &ifaceState + } + } + + return ifStates, nil +} + +func toLinkDuplex(duplex interface_types.LinkDuplex) ifs.InterfaceState_Duplex { + switch duplex { + case 1: + return ifs.InterfaceState_HALF + case 2: + return ifs.InterfaceState_FULL + default: + return ifs.InterfaceState_UNKNOWN_DUPLEX + } +} + +const megabit = 1000000 // one megabit in bytes + +func toLinkSpeed(speed uint32) uint64 { + switch speed { + case 1: + return 10 * megabit // 10M + case 2: + return 100 * megabit // 100M + case 4: + return 1000 * megabit // 1G + case 8: + return 10000 * megabit // 10G + case 16: + return 40000 * megabit // 40G + case 32: + return 100000 * megabit // 100G + default: + return 0 + } +} + +// Returns true if given interface contains at least one IPv6 address. For VxLAN, source and destination +// addresses are also checked +func isIpv6Interface(iface *ifs.Interface) (bool, error) { + if iface.Type == ifs.Interface_VXLAN_TUNNEL && iface.GetVxlan() != nil { + if ipAddress := net.ParseIP(iface.GetVxlan().SrcAddress); ipAddress.To4() == nil { + return true, nil + } + if ipAddress := net.ParseIP(iface.GetVxlan().DstAddress); ipAddress.To4() == nil { + return true, nil + } + } + for _, ifAddress := range iface.IpAddresses { + if ipAddress, _, err := net.ParseCIDR(ifAddress); err != nil { + return false, err + } else if ipAddress.To4() == nil { + return true, nil + } + } + return false, nil +} + +// dumpIPAddressDetails dumps IP address details of interfaces from VPP and fills them into the provided interface map. +func (h *InterfaceVppHandler) dumpIPAddressDetails(ifs map[uint32]*vppcalls.InterfaceDetails, isIPv6 bool, dhcpClients map[uint32]*vppcalls.Dhcp) error { + // Dump IP addresses of each interface. + for idx := range ifs { + reqCtx := h.callsChannel.SendMultiRequest(&vpp_ip.IPAddressDump{ + SwIfIndex: interface_types.InterfaceIndex(idx), + IsIPv6: isIPv6, + }) + for { + ipDetails := &vpp_ip.IPAddressDetails{} + stop, err := reqCtx.ReceiveReply(ipDetails) + if stop { + break // Break from the loop. + } + if err != nil { + return fmt.Errorf("failed to dump interface %d IP address details: %v", idx, err) + } + h.processIPDetails(ifs, ipDetails, dhcpClients) + } + } + + return nil +} + +// processIPDetails processes ip.IPAddressDetails binary API message and fills the details into the provided interface map. +func (h *InterfaceVppHandler) processIPDetails(ifs map[uint32]*vppcalls.InterfaceDetails, ipDetails *vpp_ip.IPAddressDetails, dhcpClients map[uint32]*vppcalls.Dhcp) { + ifDetails, ifIdxExists := ifs[uint32(ipDetails.SwIfIndex)] + if !ifIdxExists { + return + } + + var ipAddr string + ipByte := make([]byte, 16) + copy(ipByte[:], ipDetails.Prefix.Address.Un.XXX_UnionData[:]) + if ipDetails.Prefix.Address.Af == ip_types.ADDRESS_IP6 { + ipAddr = fmt.Sprintf("%s/%d", net.IP(ipByte).To16().String(), uint32(ipDetails.Prefix.Len)) + } else { + ipAddr = fmt.Sprintf("%s/%d", net.IP(ipByte[:4]).To4().String(), uint32(ipDetails.Prefix.Len)) + } + + // skip IP addresses given by DHCP + if dhcpClient, hasDhcpClient := dhcpClients[uint32(ipDetails.SwIfIndex)]; hasDhcpClient { + if dhcpClient.Lease != nil && dhcpClient.Lease.HostAddress == ipAddr { + return + } + } + + ifDetails.Interface.IpAddresses = append(ifDetails.Interface.IpAddresses, ipAddr) +} + +// dumpTapDetails dumps tap interface details from VPP and fills them into the provided interface map. +func (h *InterfaceVppHandler) dumpTapDetails(interfaces map[uint32]*vppcalls.InterfaceDetails) error { + // Original TAP v1 was DEPRECATED + + // TAP v2 + reqCtx := h.callsChannel.SendMultiRequest(&vpp_tapv2.SwInterfaceTapV2Dump{ + SwIfIndex: ^interface_types.InterfaceIndex(0), + }) + for { + tapDetails := &vpp_tapv2.SwInterfaceTapV2Details{} + stop, err := reqCtx.ReceiveReply(tapDetails) + if stop { + break // Break from the loop. + } + if err != nil { + return fmt.Errorf("failed to dump TAPv2 interface details: %v", err) + } + _, ifIdxExists := interfaces[tapDetails.SwIfIndex] + if !ifIdxExists { + continue + } + interfaces[tapDetails.SwIfIndex].Interface.Link = &ifs.Interface_Tap{ + Tap: &ifs.TapLink{ + Version: 2, + HostIfName: cleanString(tapDetails.HostIfName), + RxRingSize: uint32(tapDetails.RxRingSz), + TxRingSize: uint32(tapDetails.TxRingSz), + EnableGso: tapDetails.TapFlags&vpp_tapv2.TAP_API_FLAG_GSO == vpp_tapv2.TAP_API_FLAG_GSO, + EnableTunnel: tapDetails.TapFlags&vpp_tapv2.TAP_API_FLAG_TUN == vpp_tapv2.TAP_API_FLAG_TUN, + }, + } + interfaces[tapDetails.SwIfIndex].Interface.Type = ifs.Interface_TAP + } + + return nil +} + +// dumpVxlanDetails dumps VXLAN interface details from VPP and fills them into the provided interface map. +func (h *InterfaceVppHandler) dumpVxlanDetails(interfaces map[uint32]*vppcalls.InterfaceDetails) error { + reqCtx := h.callsChannel.SendMultiRequest(&vpp_vxlan.VxlanTunnelDump{ + SwIfIndex: ^interface_types.InterfaceIndex(0), + }) + for { + vxlanDetails := &vpp_vxlan.VxlanTunnelDetails{} + stop, err := reqCtx.ReceiveReply(vxlanDetails) + if stop { + break // Break from the loop. + } + if err != nil { + return fmt.Errorf("failed to dump VxLAN tunnel interface details: %v", err) + } + _, ifIdxExists := interfaces[uint32(vxlanDetails.SwIfIndex)] + if !ifIdxExists { + continue + } + // Multicast interface + var multicastIfName string + _, exists := interfaces[uint32(vxlanDetails.McastSwIfIndex)] + if exists { + multicastIfName = interfaces[uint32(vxlanDetails.McastSwIfIndex)].Interface.Name + } + + if vxlanDetails.SrcAddress.Af == ip_types.ADDRESS_IP6 { + srcIP := vxlanDetails.SrcAddress.Un.GetIP6() + dstIP := vxlanDetails.DstAddress.Un.GetIP6() + interfaces[uint32(vxlanDetails.SwIfIndex)].Interface.Link = &ifs.Interface_Vxlan{ + Vxlan: &ifs.VxlanLink{ + Multicast: multicastIfName, + SrcAddress: net.IP(srcIP[:]).To16().String(), + DstAddress: net.IP(dstIP[:]).To16().String(), + Vni: vxlanDetails.Vni, + }, + } + } else { + srcIP := vxlanDetails.SrcAddress.Un.GetIP4() + dstIP := vxlanDetails.DstAddress.Un.GetIP4() + interfaces[uint32(vxlanDetails.SwIfIndex)].Interface.Link = &ifs.Interface_Vxlan{ + Vxlan: &ifs.VxlanLink{ + Multicast: multicastIfName, + SrcAddress: net.IP(srcIP[:4]).To4().String(), + DstAddress: net.IP(dstIP[:4]).To4().String(), + Vni: vxlanDetails.Vni, + }, + } + } + interfaces[uint32(vxlanDetails.SwIfIndex)].Interface.Type = ifs.Interface_VXLAN_TUNNEL + } + + return nil +} + +// dumpVxlanDetails dumps VXLAN-GPE interface details from VPP and fills them into the provided interface map. +func (h *InterfaceVppHandler) dumpVxLanGpeDetails(interfaces map[uint32]*vppcalls.InterfaceDetails) error { + reqCtx := h.callsChannel.SendMultiRequest(&vpp_vxlangpe.VxlanGpeTunnelDump{SwIfIndex: ^interface_types.InterfaceIndex(0)}) + for { + vxlanGpeDetails := &vpp_vxlangpe.VxlanGpeTunnelDetails{} + stop, err := reqCtx.ReceiveReply(vxlanGpeDetails) + if stop { + break // Break from the loop. + } + if err != nil { + return fmt.Errorf("failed to dump VxLAN-GPE tunnel interface details: %v", err) + } + _, ifIdxExists := interfaces[uint32(vxlanGpeDetails.SwIfIndex)] + if !ifIdxExists { + continue + } + // Multicast interface + var multicastIfName string + _, exists := interfaces[uint32(vxlanGpeDetails.McastSwIfIndex)] + if exists { + multicastIfName = interfaces[uint32(vxlanGpeDetails.McastSwIfIndex)].Interface.Name + } + + vxLan := &ifs.VxlanLink{ + Multicast: multicastIfName, + Vni: vxlanGpeDetails.Vni, + Gpe: &ifs.VxlanLink_Gpe{ + DecapVrfId: vxlanGpeDetails.DecapVrfID, + Protocol: getVxLanGpeProtocol(vxlanGpeDetails.Protocol), + }, + } + + if vxlanGpeDetails.IsIPv6 { + localIP := vxlanGpeDetails.Local.Un.GetIP6() + remoteIP := vxlanGpeDetails.Remote.Un.GetIP6() + vxLan.SrcAddress = net.IP(localIP[:]).To16().String() + vxLan.DstAddress = net.IP(remoteIP[:]).To16().String() + } else { + localIP := vxlanGpeDetails.Local.Un.GetIP4() + remoteIP := vxlanGpeDetails.Remote.Un.GetIP4() + vxLan.SrcAddress = net.IP(localIP[:4]).To4().String() + vxLan.DstAddress = net.IP(remoteIP[:4]).To4().String() + } + + interfaces[uint32(vxlanGpeDetails.SwIfIndex)].Interface.Link = &ifs.Interface_Vxlan{Vxlan: vxLan} + interfaces[uint32(vxlanGpeDetails.SwIfIndex)].Interface.Type = ifs.Interface_VXLAN_TUNNEL + } + + return nil +} + +// dumpIPSecTunnelDetails dumps IPSec tunnel interfaces from the VPP and fills them into the provided interface map. +func (h *InterfaceVppHandler) dumpIPSecTunnelDetails(interfaces map[uint32]*vppcalls.InterfaceDetails) error { + // tunnel interfaces are a part of security association dump + var tunnels []*vpp_ipsec.IpsecSaV3Details + req := &vpp_ipsec.IpsecSaV3Dump{ + SaID: ^uint32(0), + } + requestCtx := h.callsChannel.SendMultiRequest(req) + + for { + saDetails := &vpp_ipsec.IpsecSaV3Details{} + stop, err := requestCtx.ReceiveReply(saDetails) + if stop { + break + } + if err != nil { + return err + } + // skip non-tunnel security associations + if saDetails.SwIfIndex != ^interface_types.InterfaceIndex(0) { + tunnels = append(tunnels, saDetails) + } + } + + // every tunnel interface is returned in two API calls. To reconstruct the correct proto-modelled data, + // first appearance is cached, and when the second part arrives, data are completed and stored. + tunnelParts := make(map[uint32]*vpp_ipsec.IpsecSaV3Details) + + for _, tunnel := range tunnels { + // first appearance is stored in the map, the second one is used in configuration. + firstSaData, ok := tunnelParts[uint32(tunnel.SwIfIndex)] + if !ok { + tunnelParts[uint32(tunnel.SwIfIndex)] = tunnel + continue + } + + local := firstSaData + remote := tunnel + + // verify data for local & remote + if err := verifyIPSecTunnelDetails(local, remote); err != nil { + h.log.Warnf("IPSec SA dump for tunnel interface data does not match: %v", err) + continue + } + + var localIP, remoteIP net.IP + if tunnel.Entry.Tunnel.Dst.Af == ip_types.ADDRESS_IP6 { + localSrc := local.Entry.Tunnel.Src.Un.GetIP6() + remoteSrc := remote.Entry.Tunnel.Src.Un.GetIP6() + localIP, remoteIP = net.IP(localSrc[:]), net.IP(remoteSrc[:]) + } else { + localSrc := local.Entry.Tunnel.Src.Un.GetIP4() + remoteSrc := remote.Entry.Tunnel.Src.Un.GetIP4() + localIP, remoteIP = net.IP(localSrc[:]), net.IP(remoteSrc[:]) + } + + ifDetails, ok := interfaces[uint32(tunnel.SwIfIndex)] + if !ok { + h.log.Warnf("ipsec SA dump returned unrecognized swIfIndex: %v", tunnel.SwIfIndex) + continue + } + ifDetails.Interface.Type = ifs.Interface_IPSEC_TUNNEL + ifDetails.Interface.Link = &ifs.Interface_Ipsec{ + Ipsec: &ifs.IPSecLink{ + Esn: (tunnel.Entry.Flags & ipsec_types.IPSEC_API_SAD_FLAG_USE_ESN) != 0, + AntiReplay: (tunnel.Entry.Flags & ipsec_types.IPSEC_API_SAD_FLAG_USE_ANTI_REPLAY) != 0, + LocalIp: localIP.String(), + RemoteIp: remoteIP.String(), + LocalSpi: local.Entry.Spi, + RemoteSpi: remote.Entry.Spi, + CryptoAlg: ipsec.CryptoAlg(tunnel.Entry.CryptoAlgorithm), + LocalCryptoKey: hex.EncodeToString(local.Entry.CryptoKey.Data[:local.Entry.CryptoKey.Length]), + RemoteCryptoKey: hex.EncodeToString(remote.Entry.CryptoKey.Data[:remote.Entry.CryptoKey.Length]), + IntegAlg: ipsec.IntegAlg(tunnel.Entry.IntegrityAlgorithm), + LocalIntegKey: hex.EncodeToString(local.Entry.IntegrityKey.Data[:local.Entry.IntegrityKey.Length]), + RemoteIntegKey: hex.EncodeToString(remote.Entry.IntegrityKey.Data[:remote.Entry.IntegrityKey.Length]), + EnableUdpEncap: (tunnel.Entry.Flags & ipsec_types.IPSEC_API_SAD_FLAG_UDP_ENCAP) != 0, + }, + } + } + + return nil +} + +func verifyIPSecTunnelDetails(local, remote *vpp_ipsec.IpsecSaV3Details) error { + if local.SwIfIndex != remote.SwIfIndex { + return fmt.Errorf("swIfIndex data mismatch (local: %v, remote: %v)", + local.SwIfIndex, remote.SwIfIndex) + } + localIsTunnel := local.Entry.Flags & ipsec_types.IPSEC_API_SAD_FLAG_IS_TUNNEL + remoteIsTunnel := remote.Entry.Flags & ipsec_types.IPSEC_API_SAD_FLAG_IS_TUNNEL + if localIsTunnel != remoteIsTunnel { + return fmt.Errorf("tunnel data mismatch (local: %v, remote: %v)", + localIsTunnel, remoteIsTunnel) + } + + localSrc, localDst := local.Entry.Tunnel.Src.Un.XXX_UnionData, local.Entry.Tunnel.Dst.Un.XXX_UnionData + remoteSrc, remoteDst := remote.Entry.Tunnel.Src.Un.XXX_UnionData, remote.Entry.Tunnel.Dst.Un.XXX_UnionData + if (local.Entry.Flags&ipsec_types.IPSEC_API_SAD_FLAG_IS_TUNNEL_V6) != (remote.Entry.Flags&ipsec_types.IPSEC_API_SAD_FLAG_IS_TUNNEL_V6) || + !bytes.Equal(localSrc[:], remoteDst[:]) || + !bytes.Equal(localDst[:], remoteSrc[:]) { + return fmt.Errorf("src/dst IP mismatch (local: %+v, remote: %+v)", + local.Entry, remote.Entry) + } + + return nil +} + +// dumpBondDetails dumps bond interface details from VPP and fills them into the provided interface map. +func (h *InterfaceVppHandler) dumpBondDetails(interfaces map[uint32]*vppcalls.InterfaceDetails) error { + bondIndexes := make([]uint32, 0) + reqCtx := h.callsChannel.SendMultiRequest(&vpp_bond.SwBondInterfaceDump{}) + for { + bondDetails := &vpp_bond.SwBondInterfaceDetails{} + stop, err := reqCtx.ReceiveReply(bondDetails) + if err != nil { + return fmt.Errorf("failed to dump bond interface details: %v", err) + } + if stop { + break + } + _, ifIdxExists := interfaces[uint32(bondDetails.SwIfIndex)] + if !ifIdxExists { + continue + } + interfaces[uint32(bondDetails.SwIfIndex)].Interface.Link = &ifs.Interface_Bond{ + Bond: &ifs.BondLink{ + Id: bondDetails.ID, + Mode: getBondIfMode(bondDetails.Mode), + Lb: getBondLoadBalance(bondDetails.Lb), + }, + } + interfaces[uint32(bondDetails.SwIfIndex)].Interface.Type = ifs.Interface_BOND_INTERFACE + bondIndexes = append(bondIndexes, uint32(bondDetails.SwIfIndex)) + } + + // get member interfaces for bonds + for _, bondIdx := range bondIndexes { + var bondMembers []*ifs.BondLink_BondedInterface + reqSlCtx := h.callsChannel.SendMultiRequest(&vpp_bond.SwMemberInterfaceDump{ + SwIfIndex: interface_types.InterfaceIndex(bondIdx), + }) + for { + memberDetails := &vpp_bond.SwMemberInterfaceDetails{} + stop, err := reqSlCtx.ReceiveReply(memberDetails) + if err != nil { + return fmt.Errorf("failed to dump bond member details: %v", err) + } + if stop { + break + } + memberIf, ifIdxExists := interfaces[uint32(memberDetails.SwIfIndex)] + if !ifIdxExists { + continue + } + bondMembers = append(bondMembers, &ifs.BondLink_BondedInterface{ + Name: memberIf.Interface.Name, + IsPassive: memberDetails.IsPassive, + IsLongTimeout: memberDetails.IsLongTimeout, + }) + interfaces[bondIdx].Interface.GetBond().BondedInterfaces = bondMembers + } + } + + return nil +} + +func (h *InterfaceVppHandler) dumpGreDetails(interfaces map[uint32]*vppcalls.InterfaceDetails) error { + msg := &vpp_gre.GreTunnelDump{SwIfIndex: interface_types.InterfaceIndex(^uint32(0))} + reqCtx := h.callsChannel.SendMultiRequest(msg) + for { + greDetails := &vpp_gre.GreTunnelDetails{} + stop, err := reqCtx.ReceiveReply(greDetails) + if stop { + break + } + if err != nil { + return fmt.Errorf("failed to dump span: %v", err) + } + + tunnel := greDetails.Tunnel + swIfIndex := uint32(tunnel.SwIfIndex) + + var srcAddr, dstAddr net.IP + if tunnel.Src.Af == ip_types.ADDRESS_IP4 { + srcAddrArr := tunnel.Src.Un.GetIP4() + srcAddr = net.IP(srcAddrArr[:]) + } else { + srcAddrArr := tunnel.Src.Un.GetIP6() + srcAddr = net.IP(srcAddrArr[:]) + } + if tunnel.Dst.Af == ip_types.ADDRESS_IP4 { + dstAddrArr := tunnel.Dst.Un.GetIP4() + dstAddr = net.IP(dstAddrArr[:]) + } else { + dstAddrArr := tunnel.Dst.Un.GetIP6() + dstAddr = net.IP(dstAddrArr[:]) + } + + interfaces[swIfIndex].Interface.Link = &ifs.Interface_Gre{ + Gre: &ifs.GreLink{ + TunnelType: getGreTunnelType(tunnel.Type), + SrcAddr: srcAddr.String(), + DstAddr: dstAddr.String(), + OuterFibId: tunnel.OuterTableID, + SessionId: uint32(tunnel.SessionID), + }, + } + interfaces[swIfIndex].Interface.Type = ifs.Interface_GRE_TUNNEL + } + return nil +} + +// dumpUnnumberedDetails returns a map of unnumbered interface indexes, every with interface index of element with IP +func (h *InterfaceVppHandler) dumpUnnumberedDetails() (map[uint32]uint32, error) { + unIfMap := make(map[uint32]uint32) // unnumbered/ip-interface + reqCtx := h.callsChannel.SendMultiRequest(&vpp_ip.IPUnnumberedDump{ + SwIfIndex: ^interface_types.InterfaceIndex(0), + }) + + for { + unDetails := &vpp_ip.IPUnnumberedDetails{} + last, err := reqCtx.ReceiveReply(unDetails) + if last { + break + } + if err != nil { + return nil, err + } + + unIfMap[uint32(unDetails.SwIfIndex)] = uint32(unDetails.IPSwIfIndex) + } + + return unIfMap, nil +} + +func (h *InterfaceVppHandler) dumpRxPlacement(interfaces map[uint32]*vppcalls.InterfaceDetails) error { + reqCtx := h.callsChannel.SendMultiRequest(&vpp_ifs.SwInterfaceRxPlacementDump{ + SwIfIndex: interface_types.InterfaceIndex(^uint32(0)), + }) + for { + rxDetails := &vpp_ifs.SwInterfaceRxPlacementDetails{} + stop, err := reqCtx.ReceiveReply(rxDetails) + if err != nil { + return fmt.Errorf("failed to dump rx-placement details: %v", err) + } + if stop { + break + } + + ifData, ok := interfaces[uint32(rxDetails.SwIfIndex)] + if !ok { + h.log.Warnf("Received rx-placement data for unknown interface with index %d", rxDetails.SwIfIndex) + continue + } + + ifData.Interface.RxModes = append(ifData.Interface.RxModes, + &ifs.Interface_RxMode{ + Queue: rxDetails.QueueID, + Mode: getRxModeType(rxDetails.Mode), + }) + + var worker uint32 + if rxDetails.WorkerID > 0 { + worker = rxDetails.WorkerID - 1 + } + ifData.Interface.RxPlacements = append(ifData.Interface.RxPlacements, + &ifs.Interface_RxPlacement{ + Queue: rxDetails.QueueID, + Worker: worker, + MainThread: rxDetails.WorkerID == 0, + }) + } + return nil +} + +func dhcpAddressToString(address ip_types.Address, maskWidth uint32, isIPv6 bool) string { + dhcpIPByte := make([]byte, 16) + copy(dhcpIPByte[:], address.Un.XXX_UnionData[:]) + if isIPv6 { + return fmt.Sprintf("%s/%d", net.IP(dhcpIPByte).To16().String(), maskWidth) + } + return fmt.Sprintf("%s/%d", net.IP(dhcpIPByte[:4]).To4().String(), maskWidth) +} + +// guessInterfaceType attempts to guess the correct interface type from its internal name (as given by VPP). +// This is required mainly for those interface types, that do not provide dump binary API, +// such as loopback of af_packet. +func guessInterfaceType(ifDevType, ifName string) ifs.Interface_Type { + switch { + case ifDevType == "RDMA interface": + return ifs.Interface_RDMA + + case strings.HasPrefix(ifName, "loop"), + strings.HasPrefix(ifName, "local"): + return ifs.Interface_SOFTWARE_LOOPBACK + + case strings.HasPrefix(ifName, "memif"): + return ifs.Interface_MEMIF + + case strings.HasPrefix(ifName, "tap"), + strings.HasPrefix(ifName, "tun"): + return ifs.Interface_TAP + + case strings.HasPrefix(ifName, "host"): + return ifs.Interface_AF_PACKET + + case strings.HasPrefix(ifName, "vxlan"): + return ifs.Interface_VXLAN_TUNNEL + + case strings.HasPrefix(ifName, "ipsec"): + return ifs.Interface_IPSEC_TUNNEL + + case strings.HasPrefix(ifName, "vmxnet3"): + return ifs.Interface_VMXNET3_INTERFACE + + case strings.HasPrefix(ifName, "Bond"): + return ifs.Interface_BOND_INTERFACE + + case strings.HasPrefix(ifName, "gtpu"): + return ifs.Interface_GTPU_TUNNEL + + case strings.HasPrefix(ifName, "ipip"): + return ifs.Interface_IPIP_TUNNEL + + case strings.HasPrefix(ifName, "wg"): + return ifs.Interface_WIREGUARD_TUNNEL + + default: + return ifs.Interface_DPDK + } +} + +// memifModetoNB converts binary API type of memif mode to the northbound API type memif mode. +func memifModetoNB(mode vpp_memif.MemifMode) ifs.MemifLink_MemifMode { + switch mode { + case vpp_memif.MEMIF_MODE_API_IP: + return ifs.MemifLink_IP + case vpp_memif.MEMIF_MODE_API_PUNT_INJECT: + return ifs.MemifLink_PUNT_INJECT + default: + return ifs.MemifLink_ETHERNET + } +} + +// Convert binary API rx-mode to northbound representation +func getRxModeType(mode interface_types.RxMode) ifs.Interface_RxMode_Type { + switch mode { + case 1: + return ifs.Interface_RxMode_POLLING + case 2: + return ifs.Interface_RxMode_INTERRUPT + case 3: + return ifs.Interface_RxMode_ADAPTIVE + case 4: + return ifs.Interface_RxMode_DEFAULT + default: + return ifs.Interface_RxMode_UNKNOWN + } +} + +func getBondIfMode(mode vpp_bond.BondMode) ifs.BondLink_Mode { + switch mode { + case vpp_bond.BOND_API_MODE_ROUND_ROBIN: + return ifs.BondLink_ROUND_ROBIN + case vpp_bond.BOND_API_MODE_ACTIVE_BACKUP: + return ifs.BondLink_ACTIVE_BACKUP + case vpp_bond.BOND_API_MODE_XOR: + return ifs.BondLink_XOR + case vpp_bond.BOND_API_MODE_BROADCAST: + return ifs.BondLink_BROADCAST + case vpp_bond.BOND_API_MODE_LACP: + return ifs.BondLink_LACP + default: + // UNKNOWN + return 0 + } +} + +func getBondLoadBalance(lb vpp_bond.BondLbAlgo) ifs.BondLink_LoadBalance { + switch lb { + case vpp_bond.BOND_API_LB_ALGO_L34: + return ifs.BondLink_L34 + case vpp_bond.BOND_API_LB_ALGO_L23: + return ifs.BondLink_L23 + case vpp_bond.BOND_API_LB_ALGO_RR: + return ifs.BondLink_RR + case vpp_bond.BOND_API_LB_ALGO_BC: + return ifs.BondLink_BC + case vpp_bond.BOND_API_LB_ALGO_AB: + return ifs.BondLink_AB + default: + return ifs.BondLink_L2 + } +} + +func getTagRwOption(op uint32) ifs.SubInterface_TagRewriteOptions { + switch op { + case 1: + return ifs.SubInterface_PUSH1 + case 2: + return ifs.SubInterface_PUSH2 + case 3: + return ifs.SubInterface_POP1 + case 4: + return ifs.SubInterface_POP2 + case 5: + return ifs.SubInterface_TRANSLATE11 + case 6: + return ifs.SubInterface_TRANSLATE12 + case 7: + return ifs.SubInterface_TRANSLATE21 + case 8: + return ifs.SubInterface_TRANSLATE22 + default: // disabled + return ifs.SubInterface_DISABLED + } +} + +func getGreTunnelType(tt vpp_gre.GreTunnelType) ifs.GreLink_Type { + switch tt { + case vpp_gre.GRE_API_TUNNEL_TYPE_L3: + return ifs.GreLink_L3 + case vpp_gre.GRE_API_TUNNEL_TYPE_TEB: + return ifs.GreLink_TEB + case vpp_gre.GRE_API_TUNNEL_TYPE_ERSPAN: + return ifs.GreLink_ERSPAN + default: + return ifs.GreLink_UNKNOWN + } +} + +func getVxLanGpeProtocol(p ip_types.IPProto) ifs.VxlanLink_Gpe_Protocol { + + // TODO: Fix conversion from IPProto, it seems those enums are incompatible now + + switch p { + case 1: + return ifs.VxlanLink_Gpe_IP4 + case 2: + return ifs.VxlanLink_Gpe_IP6 + case 3: + return ifs.VxlanLink_Gpe_ETHERNET + case 4: + return ifs.VxlanLink_Gpe_NSH + default: + return ifs.VxlanLink_Gpe_UNKNOWN + } +} + +func isAdminStateUp(flags interface_types.IfStatusFlags) bool { + return flags&interface_types.IF_STATUS_API_FLAG_ADMIN_UP != 0 +} + +func isLinkStateUp(flags interface_types.IfStatusFlags) bool { + return flags&interface_types.IF_STATUS_API_FLAG_LINK_UP != 0 +} + +func adminStateToInterfaceStatus(flags interface_types.IfStatusFlags) ifs.InterfaceState_Status { + if isAdminStateUp(flags) { + return ifs.InterfaceState_UP + } + return ifs.InterfaceState_DOWN +} + +func linkStateToInterfaceStatus(flags interface_types.IfStatusFlags) ifs.InterfaceState_Status { + if isLinkStateUp(flags) { + return ifs.InterfaceState_UP + } + return ifs.InterfaceState_DOWN +} diff --git a/plugins/vpp/ifplugin/vppcalls/vpp2306/dump_interface_vppcalls_test.go b/plugins/vpp/ifplugin/vppcalls/vpp2306/dump_interface_vppcalls_test.go new file mode 100644 index 0000000000..70eaa3d6b0 --- /dev/null +++ b/plugins/vpp/ifplugin/vppcalls/vpp2306/dump_interface_vppcalls_test.go @@ -0,0 +1,666 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306_test + +import ( + "net" + "testing" + + . "github.com/onsi/gomega" + govppapi "go.fd.io/govpp/api" + + vpp_dhcp "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/dhcp" + vpp_gtpu "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/gtpu" + vpp_interfaces "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface_types" + vpp_ip "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip_types" + vpp_ipip "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ipip" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/memclnt" + vpp_memif "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/memif" + vpp_tapv2 "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/tapv2" + vpp_vxlan "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/vxlan" + vpp_vxlangpe "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/vxlan_gpe" + vpp2306 "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/vppcalls/vpp2306" + "go.ligato.io/vpp-agent/v3/plugins/vpp/vppmock" + ifs "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/interfaces" +) + +// Test dump of interfaces with vxlan type +func TestDumpInterfacesVxLan(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ipv61Parse := net.ParseIP("dead:beef:feed:face:cafe:babe:baad:c0de").To16() + ipv62Parse := net.ParseIP("d3ad:beef:feed:face:cafe:babe:baad:c0de").To16() + + ctx.MockReplies([]*vppmock.HandleReplies{ + { + Name: (&vpp_interfaces.SwInterfaceDump{}).GetMessageName(), + Ping: true, + Message: &vpp_interfaces.SwInterfaceDetails{ + InterfaceName: "vxlan1", + }, + }, + { + Name: (&vpp_interfaces.SwInterfaceGetTable{}).GetMessageName(), + Ping: false, + Message: &vpp_interfaces.SwInterfaceGetTableReply{}, + }, + { + Name: (&vpp_ip.IPAddressDump{}).GetMessageName(), + Ping: true, + Message: &vpp_ip.IPAddressDetails{}, + }, + { + Name: (&vpp_memif.MemifSocketFilenameDump{}).GetMessageName(), + Ping: true, + }, + { + Name: (&vpp_memif.MemifDump{}).GetMessageName(), + Ping: true, + }, + { + Name: (&vpp_tapv2.SwInterfaceTapV2Dump{}).GetMessageName(), + Ping: true, + }, + { + Name: (&vpp_vxlan.VxlanTunnelDump{}).GetMessageName(), + Ping: true, + Message: &vpp_vxlan.VxlanTunnelDetails{ + SwIfIndex: 0, + SrcAddress: ipToAddr(ipv61Parse.String()), + DstAddress: ipToAddr(ipv62Parse.String()), + }, + }, + { + Name: (&vpp_vxlangpe.VxlanGpeTunnelDump{}).GetMessageName(), + Ping: true, + }, + { + Name: (&vpp_gtpu.GtpuTunnelDump{}).GetMessageName(), + Ping: true, + }, + { + Name: (&vpp_ipip.IpipTunnelDump{}).GetMessageName(), + Ping: true, + }, + { + Name: (&vpp_ip.IPUnnumberedDump{}).GetMessageName(), + Ping: true, + }, + { + Name: (&vpp_dhcp.DHCPClientDump{}).GetMessageName(), + Ping: true, + }, + }) + + intfs, err := ifHandler.DumpInterfaces(ctx.Context) + Expect(err).To(BeNil()) + Expect(intfs).To(HaveLen(1)) + intface := intfs[0].Interface + + // Check vxlan + Expect(intface.GetVxlan().SrcAddress).To(Equal("dead:beef:feed:face:cafe:babe:baad:c0de")) + Expect(intface.GetVxlan().DstAddress).To(Equal("d3ad:beef:feed:face:cafe:babe:baad:c0de")) +} + +// Test dump of interfaces with host type +func TestDumpInterfacesHost(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockReplies([]*vppmock.HandleReplies{ + { + Name: (&vpp_interfaces.SwInterfaceDump{}).GetMessageName(), + Ping: true, + Message: &vpp_interfaces.SwInterfaceDetails{ + InterfaceName: "host-localhost", + }, + }, + { + Name: (&vpp_interfaces.SwInterfaceGetTable{}).GetMessageName(), + Ping: false, + Message: &vpp_interfaces.SwInterfaceGetTableReply{}, + }, + { + Name: (&vpp_ip.IPAddressDump{}).GetMessageName(), + Ping: true, + Message: &vpp_ip.IPAddressDetails{}, + }, + { + Name: (&vpp_memif.MemifSocketFilenameDump{}).GetMessageName(), + Ping: true, + }, + { + Name: (&vpp_memif.MemifDump{}).GetMessageName(), + Ping: true, + }, + { + Name: (&vpp_tapv2.SwInterfaceTapV2Dump{}).GetMessageName(), + Ping: true, + }, + { + Name: (&vpp_vxlan.VxlanTunnelDump{}).GetMessageName(), + Ping: true, + }, + { + Name: (&vpp_gtpu.GtpuTunnelDump{}).GetMessageName(), + Ping: true, + }, + { + Name: (&vpp_ipip.IpipTunnelDump{}).GetMessageName(), + Ping: true, + }, + { + Name: (&vpp_dhcp.DHCPClientDump{}).GetMessageName(), + Ping: true, + }, + { + Name: (&vpp_ip.IPUnnumberedDump{}).GetMessageName(), + Ping: true, + }, + { + Name: (&vpp_vxlangpe.VxlanGpeTunnelDump{}).GetMessageName(), + Ping: true, + }, + }) + + intfs, err := ifHandler.DumpInterfaces(ctx.Context) + Expect(err).To(BeNil()) + Expect(intfs).To(HaveLen(1)) + intface := intfs[0].Interface + + // Check interface data + Expect(intface.GetAfpacket().HostIfName).To(Equal("localhost")) +} + +// Test dump of interfaces with memif type +func TestDumpInterfacesMemif(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockReplies([]*vppmock.HandleReplies{ + { + Name: (&vpp_interfaces.SwInterfaceDump{}).GetMessageName(), + Ping: true, + Message: &vpp_interfaces.SwInterfaceDetails{ + InterfaceName: "memif1", + }, + }, + { + Name: (&vpp_interfaces.SwInterfaceGetTable{}).GetMessageName(), + Ping: false, + Message: &vpp_interfaces.SwInterfaceGetTableReply{}, + }, + { + Name: (&vpp_ip.IPAddressDump{}).GetMessageName(), + Ping: true, + Message: &vpp_ip.IPAddressDetails{}, + }, + { + Name: (&vpp_memif.MemifSocketFilenameDump{}).GetMessageName(), + Ping: true, + Message: &vpp_memif.MemifSocketFilenameDetails{ + SocketID: 1, + SocketFilename: "test", + }, + }, + { + Name: (&vpp_memif.MemifDump{}).GetMessageName(), + Ping: true, + Message: &vpp_memif.MemifDetails{ + ID: 2, + SwIfIndex: 0, + Role: 1, // Slave + Mode: 1, // IP + SocketID: 1, + RingSize: 0, + BufferSize: 0, + }, + }, + { + Name: (&vpp_tapv2.SwInterfaceTapV2Dump{}).GetMessageName(), + Ping: true, + }, + { + Name: (&vpp_vxlan.VxlanTunnelDump{}).GetMessageName(), + Ping: true, + }, + { + Name: (&vpp_gtpu.GtpuTunnelDump{}).GetMessageName(), + Ping: true, + }, + { + Name: (&vpp_ipip.IpipTunnelDump{}).GetMessageName(), + Ping: true, + }, + }) + + intfs, err := ifHandler.DumpInterfaces(ctx.Context) + Expect(err).To(BeNil()) + Expect(intfs).To(HaveLen(1)) + intface := intfs[0].Interface + + // Check memif + Expect(intface.GetMemif().SocketFilename).To(Equal("test")) + Expect(intface.GetMemif().Id).To(Equal(uint32(2))) + Expect(intface.GetMemif().Mode).To(Equal(ifs.MemifLink_IP)) + Expect(intface.GetMemif().Master).To(BeFalse()) +} + +func TestDumpInterfacesTap2(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + hwAddr1Parse, err := vpp2306.ParseMAC("01:23:45:67:89:ab") + Expect(err).To(BeNil()) + + ctx.MockReplies([]*vppmock.HandleReplies{ + { + Name: (&vpp_interfaces.SwInterfaceDump{}).GetMessageName(), + Ping: true, + Message: &vpp_interfaces.SwInterfaceDetails{ + SwIfIndex: 0, + InterfaceName: "tap2", + Tag: "mytap2", + Flags: interface_types.IF_STATUS_API_FLAG_ADMIN_UP, + LinkMtu: 9216, + L2Address: hwAddr1Parse, + }, + }, + { + Name: (&vpp_interfaces.SwInterfaceGetTable{}).GetMessageName(), + Ping: false, + Message: &vpp_interfaces.SwInterfaceGetTableReply{ + Retval: 0, + VrfID: 42, + }, + }, + { + Name: (&vpp_ip.IPAddressDump{}).GetMessageName(), + Ping: true, + Message: &vpp_ip.IPAddressDetails{}, + }, + { + Name: (&vpp_dhcp.DHCPClientDump{}).GetMessageName(), + Ping: true, + Message: &vpp_dhcp.DHCPClientDetails{ + Client: vpp_dhcp.DHCPClient{ + SwIfIndex: 0, + }, + }, + }, + { + Name: (&vpp_tapv2.SwInterfaceTapV2Dump{}).GetMessageName(), + Ping: true, + Message: &vpp_tapv2.SwInterfaceTapV2Details{ + SwIfIndex: 0, + HostIfName: "taptap2", + }, + }, + { + Name: (&vpp_vxlan.VxlanTunnelDump{}).GetMessageName(), + Ping: true, + }, + { + Name: (&vpp_vxlangpe.VxlanGpeTunnelDump{}).GetMessageName(), + Ping: true, + }, + { + Name: (&vpp_gtpu.GtpuTunnelDump{}).GetMessageName(), + Ping: true, + }, + { + Name: (&vpp_ipip.IpipTunnelDump{}).GetMessageName(), + Ping: true, + }, + { + Name: (&vpp_ip.IPUnnumberedDump{}).GetMessageName(), + Ping: true, + }, + { + Name: (&vpp_memif.MemifSocketFilenameDump{}).GetMessageName(), + Ping: true, + }, + }) + + intfs, err := ifHandler.DumpInterfaces(ctx.Context) + Expect(err).To(BeNil()) + Expect(intfs).To(HaveLen(1)) + + intface := intfs[0].Interface + intMeta := intfs[0].Meta + + // This is last checked type, so it will be equal to that + Expect(intface.Type).To(Equal(ifs.Interface_TAP)) + Expect(intface.PhysAddress).To(Equal("01:23:45:67:89:ab")) + Expect(intface.Name).To(Equal("mytap2")) + Expect(intface.Mtu).To(Equal(uint32(0))) // default mtu + Expect(intface.Enabled).To(BeTrue()) + Expect(intface.Vrf).To(Equal(uint32(42))) + Expect(intface.SetDhcpClient).To(BeTrue()) + Expect(intface.GetTap().HostIfName).To(Equal("taptap2")) + Expect(intface.GetTap().Version).To(Equal(uint32(2))) + Expect(intMeta.VrfIPv4).To(Equal(uint32(42))) + Expect(intMeta.VrfIPv6).To(Equal(uint32(42))) +} + +// Test dump of memif socket details using standard reply mocking +func TestDumpMemifSocketDetails(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_memif.MemifSocketFilenameDetails{ + SocketID: 1, + SocketFilename: "test", + }) + + ctx.MockVpp.MockReply(&memclnt.ControlPingReply{}) + + result, err := ifHandler.DumpMemifSocketDetails(ctx.Context) + Expect(err).To(BeNil()) + Expect(result).To(Not(BeEmpty())) + + socketID, ok := result["test"] + Expect(ok).To(BeTrue()) + Expect(socketID).To(Equal(uint32(1))) +} + +func TestDumpInterfacesRxPlacement(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockReplies([]*vppmock.HandleReplies{ + { + Name: (&vpp_interfaces.SwInterfaceDump{}).GetMessageName(), + Ping: true, + Message: &vpp_interfaces.SwInterfaceDetails{ + InterfaceName: "memif1", + }, + }, + { + Name: (&vpp_interfaces.SwInterfaceGetTable{}).GetMessageName(), + Ping: false, + Message: &vpp_interfaces.SwInterfaceGetTableReply{}, + }, + { + Name: (&vpp_ip.IPAddressDump{}).GetMessageName(), + Ping: true, + Message: &vpp_ip.IPAddressDetails{}, + }, + { + Name: (&vpp_memif.MemifSocketFilenameDump{}).GetMessageName(), + Ping: true, + Message: &vpp_memif.MemifSocketFilenameDetails{ + SocketID: 1, + SocketFilename: "test", + }, + }, + { + Name: (&vpp_memif.MemifDump{}).GetMessageName(), + Ping: true, + Message: &vpp_memif.MemifDetails{ + ID: 2, + SwIfIndex: 0, + Role: 1, // Slave + Mode: 1, // IP + SocketID: 1, + RingSize: 0, + BufferSize: 0, + }, + }, + { + Name: (&vpp_tapv2.SwInterfaceTapV2Dump{}).GetMessageName(), + Ping: true, + }, + { + Name: (&vpp_vxlan.VxlanTunnelDump{}).GetMessageName(), + Ping: true, + }, + { + Name: (&vpp_gtpu.GtpuTunnelDump{}).GetMessageName(), + Ping: true, + }, + { + Name: (&vpp_ipip.IpipTunnelDump{}).GetMessageName(), + Ping: true, + }, + { + Name: (&vpp_interfaces.SwInterfaceRxPlacementDump{}).GetMessageName(), + Ping: true, + Messages: []govppapi.Message{ + &vpp_interfaces.SwInterfaceRxPlacementDetails{ + SwIfIndex: 0, + QueueID: 0, + WorkerID: 0, // main thread + Mode: 3, // adaptive + }, + &vpp_interfaces.SwInterfaceRxPlacementDetails{ + SwIfIndex: 0, + QueueID: 1, + WorkerID: 1, // worker 0 + Mode: 2, // interrupt + }, + &vpp_interfaces.SwInterfaceRxPlacementDetails{ + SwIfIndex: 0, + QueueID: 2, + WorkerID: 2, // worker 1 + Mode: 1, // polling + }, + }, + }, + { + Name: (&vpp_ip.IPUnnumberedDump{}).GetMessageName(), + Ping: true, + }, + { + Name: (&vpp_dhcp.DHCPClientDump{}).GetMessageName(), + Ping: true, + }, + { + Name: (&vpp_vxlan.VxlanTunnelDump{}).GetMessageName(), + Ping: true, + }, + }) + + intfs, err := ifHandler.DumpInterfaces(ctx.Context) + Expect(err).To(BeNil()) + Expect(intfs).To(HaveLen(1)) + intface := intfs[0].Interface + + // Check memif + Expect(intface.GetMemif().SocketFilename).To(Equal("test")) + Expect(intface.GetMemif().Id).To(Equal(uint32(2))) + Expect(intface.GetMemif().Mode).To(Equal(ifs.MemifLink_IP)) + Expect(intface.GetMemif().Master).To(BeFalse()) + + rxMode := intface.GetRxModes() + Expect(rxMode).To(HaveLen(3)) + Expect(rxMode[0].Queue).To(BeEquivalentTo(0)) + Expect(rxMode[0].Mode).To(BeEquivalentTo(ifs.Interface_RxMode_ADAPTIVE)) + Expect(rxMode[1].Queue).To(BeEquivalentTo(1)) + Expect(rxMode[1].Mode).To(BeEquivalentTo(ifs.Interface_RxMode_INTERRUPT)) + Expect(rxMode[2].Queue).To(BeEquivalentTo(2)) + Expect(rxMode[2].Mode).To(BeEquivalentTo(ifs.Interface_RxMode_POLLING)) + + rxPlacement := intface.GetRxPlacements() + Expect(rxPlacement).To(HaveLen(3)) + Expect(rxPlacement[0].Queue).To(BeEquivalentTo(0)) + Expect(rxPlacement[0].MainThread).To(BeTrue()) + Expect(rxPlacement[0].Worker).To(BeEquivalentTo(0)) + Expect(rxPlacement[1].Queue).To(BeEquivalentTo(1)) + Expect(rxPlacement[1].MainThread).To(BeFalse()) + Expect(rxPlacement[1].Worker).To(BeEquivalentTo(0)) + Expect(rxPlacement[2].Queue).To(BeEquivalentTo(2)) + Expect(rxPlacement[2].MainThread).To(BeFalse()) + Expect(rxPlacement[2].Worker).To(BeEquivalentTo(1)) +} + +// Test dump of interfaces with gtpu type +func TestDumpInterfacesGtpu(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ipv61Parse := ip_types.Address{ + Af: ip_types.ADDRESS_IP6, + Un: ip_types.AddressUnionIP6(ip_types.IP6Address{ + 0xde, 0xad, 0xbe, 0xef, 0xfe, 0xed, 0xfa, 0xce, 0xca, 0xfe, 0xba, 0xbe, 0xba, 0xad, 0xc0, 0xde, + }), + } + ipv62Parse := ip_types.Address{ + Af: ip_types.ADDRESS_IP6, + Un: ip_types.AddressUnionIP6(ip_types.IP6Address{ + 0xd3, 0xad, 0xbe, 0xef, 0xfe, 0xed, 0xfa, 0xce, 0xca, 0xfe, 0xba, 0xbe, 0xba, 0xad, 0xc0, 0xde, + }), + } + + ctx.MockReplies([]*vppmock.HandleReplies{ + { + Name: (&vpp_interfaces.SwInterfaceDump{}).GetMessageName(), + Ping: true, + Message: &vpp_interfaces.SwInterfaceDetails{ + InterfaceName: "gtpu1", + }, + }, + { + Name: (&vpp_interfaces.SwInterfaceGetTable{}).GetMessageName(), + Ping: false, + Message: &vpp_interfaces.SwInterfaceGetTableReply{}, + }, + { + Name: (&vpp_ip.IPAddressDump{}).GetMessageName(), + Ping: true, + Message: &vpp_ip.IPAddressDetails{}, + }, + { + Name: (&vpp_memif.MemifSocketFilenameDump{}).GetMessageName(), + Ping: true, + }, + { + Name: (&vpp_memif.MemifDump{}).GetMessageName(), + Ping: true, + }, + { + Name: (&vpp_tapv2.SwInterfaceTapV2Dump{}).GetMessageName(), + Ping: true, + }, + { + Name: (&vpp_vxlan.VxlanTunnelDump{}).GetMessageName(), + Ping: true, + }, + { + Name: (&vpp_gtpu.GtpuTunnelDump{}).GetMessageName(), + Ping: true, + Message: &vpp_gtpu.GtpuTunnelDetails{ + SwIfIndex: 0, + SrcAddress: ipv61Parse, + DstAddress: ipv62Parse, + EncapVrfID: 16, + Teid: 100, + }, + }, + { + Name: (&vpp_ipip.IpipTunnelDump{}).GetMessageName(), + Ping: true, + }, + }) + + intfs, err := ifHandler.DumpInterfaces(ctx.Context) + Expect(err).To(BeNil()) + Expect(intfs).To(HaveLen(1)) + intface := intfs[0].Interface + + // Check gtpu + Expect(intface.Type).To(Equal(ifs.Interface_GTPU_TUNNEL)) + Expect(intface.GetGtpu().SrcAddr).To(Equal("dead:beef:feed:face:cafe:babe:baad:c0de")) + Expect(intface.GetGtpu().DstAddr).To(Equal("d3ad:beef:feed:face:cafe:babe:baad:c0de")) + Expect(intface.GetGtpu().EncapVrfId).To(Equal(uint32(16))) + Expect(intface.GetGtpu().Teid).To(Equal(uint32(100))) +} + +// Test dump of interfaces with IPIP type +func TestDumpInterfacesIPIP(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockReplies([]*vppmock.HandleReplies{ + { + Name: (&vpp_interfaces.SwInterfaceDump{}).GetMessageName(), + Ping: true, + Message: &vpp_interfaces.SwInterfaceDetails{ + InterfaceName: "vxlan1", + }, + }, + { + Name: (&vpp_interfaces.SwInterfaceGetTable{}).GetMessageName(), + Ping: false, + Message: &vpp_interfaces.SwInterfaceGetTableReply{}, + }, + { + Name: (&vpp_ip.IPAddressDump{}).GetMessageName(), + Ping: true, + Message: &vpp_ip.IPAddressDetails{}, + }, + { + Name: (&vpp_memif.MemifSocketFilenameDump{}).GetMessageName(), + Ping: true, + }, + { + Name: (&vpp_memif.MemifDump{}).GetMessageName(), + Ping: true, + }, + { + Name: (&vpp_tapv2.SwInterfaceTapV2Dump{}).GetMessageName(), + Ping: true, + }, + { + Name: (&vpp_vxlan.VxlanTunnelDump{}).GetMessageName(), + Ping: true, + }, + { + Name: (&vpp_gtpu.GtpuTunnelDump{}).GetMessageName(), + Ping: true, + }, + { + Name: (&vpp_ipip.IpipTunnelDump{}).GetMessageName(), + Ping: true, + Message: &vpp_ipip.IpipTunnelDetails{ + Tunnel: vpp_ipip.IpipTunnel{ + Dst: ip_types.Address{ + Af: ip_types.ADDRESS_IP6, + Un: ip_types.AddressUnionIP6(ip_types.IP6Address{ + 0xde, 0xad, 0xbe, 0xef, 0xfe, 0xed, 0xfa, 0xce, 0xca, 0xfe, 0xba, 0xbe, 0xba, 0xad, 0xc0, 0xde, + })}, + Src: ip_types.Address{ + Af: ip_types.ADDRESS_IP6, + Un: ip_types.AddressUnionIP6(ip_types.IP6Address{ + 0xd3, 0xad, 0xbe, 0xef, 0xfe, 0xed, 0xfa, 0xce, 0xca, 0xfe, 0xba, 0xbe, 0xba, 0xad, 0xc0, 0xde, + })}, + }, + }, + }, + }) + + intfs, err := ifHandler.DumpInterfaces(ctx.Context) + Expect(err).To(BeNil()) + Expect(intfs).To(HaveLen(1)) + intface := intfs[0].Interface + + // Check IPIP + Expect(intface.GetIpip()).ToNot(BeNil()) + Expect(intface.GetIpip().DstAddr).To(Equal("dead:beef:feed:face:cafe:babe:baad:c0de")) + Expect(intface.GetIpip().SrcAddr).To(Equal("d3ad:beef:feed:face:cafe:babe:baad:c0de")) +} diff --git a/plugins/vpp/ifplugin/vppcalls/vpp2306/gre_vppcalls.go b/plugins/vpp/ifplugin/vppcalls/vpp2306/gre_vppcalls.go new file mode 100644 index 0000000000..79325f493c --- /dev/null +++ b/plugins/vpp/ifplugin/vppcalls/vpp2306/gre_vppcalls.go @@ -0,0 +1,140 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "errors" + "net" + + vpp_gre "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/gre" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip_types" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/tunnel_types" + ifs "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/interfaces" +) + +func (h *InterfaceVppHandler) greAddDelTunnel(isAdd bool, greLink *ifs.GreLink) (uint32, error) { + if greLink.TunnelType == ifs.GreLink_UNKNOWN { + err := errors.New("bad GRE tunnel type") + return 0, err + } + + greSource := net.ParseIP(greLink.SrcAddr) + if greSource == nil { + err := errors.New("bad source address for GRE tunnel") + return 0, err + } + greDestination := net.ParseIP(greLink.DstAddr) + if greDestination == nil { + err := errors.New("bad destination address for GRE tunnel") + return 0, err + } + + if greLink.SrcAddr == greLink.DstAddr { + err := errors.New("source and destination are the same") + return 0, err + } + + if greLink.TunnelType == ifs.GreLink_ERSPAN && greLink.SessionId > 1023 { + err := errors.New("set session id for ERSPAN tunnel type") + return 0, err + } + + var tt vpp_gre.GreTunnelType + switch greLink.TunnelType { + case ifs.GreLink_L3: + tt = vpp_gre.GRE_API_TUNNEL_TYPE_L3 + case ifs.GreLink_TEB: + tt = vpp_gre.GRE_API_TUNNEL_TYPE_TEB + case ifs.GreLink_ERSPAN: + tt = vpp_gre.GRE_API_TUNNEL_TYPE_ERSPAN + default: + return 0, errors.New("bad GRE tunnel type") + } + + tunnel := vpp_gre.GreTunnel{ + Type: tt, + Mode: tunnel_types.TUNNEL_API_MODE_P2P, // TODO: add mode to proto model + Instance: ^uint32(0), + OuterTableID: greLink.OuterFibId, + SessionID: uint16(greLink.SessionId), + } + + var isSrcIPv6, isDstIPv6 bool + + if greSource.To4() == nil { + isSrcIPv6 = true + } + if greDestination.To4() == nil { + isDstIPv6 = true + } + if isSrcIPv6 != isDstIPv6 { + return 0, errors.New("source and destination addresses must be both either in IPv4 or IPv6") + } + + if isSrcIPv6 { + var src, dst [16]uint8 + copy(src[:], greSource.To16()) + copy(dst[:], greDestination.To16()) + tunnel.Src = ip_types.Address{ + Af: ip_types.ADDRESS_IP6, + Un: ip_types.AddressUnionIP6(src), + } + tunnel.Dst = ip_types.Address{ + Af: ip_types.ADDRESS_IP6, + Un: ip_types.AddressUnionIP6(dst), + } + } else { + var src, dst [4]uint8 + copy(src[:], greSource.To4()) + copy(dst[:], greDestination.To4()) + tunnel.Src = ip_types.Address{ + Af: ip_types.ADDRESS_IP4, + Un: ip_types.AddressUnionIP4(src), + } + tunnel.Dst = ip_types.Address{ + Af: ip_types.ADDRESS_IP4, + Un: ip_types.AddressUnionIP4(dst), + } + } + + req := &vpp_gre.GreTunnelAddDel{ + IsAdd: isAdd, + Tunnel: tunnel, + } + reply := &vpp_gre.GreTunnelAddDelReply{} + + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return 0, err + } + return uint32(reply.SwIfIndex), nil +} + +// AddGreTunnel adds new GRE interface. +func (h *InterfaceVppHandler) AddGreTunnel(ifName string, greLink *ifs.GreLink) (uint32, error) { + swIfIndex, err := h.greAddDelTunnel(true, greLink) + if err != nil { + return 0, err + } + return swIfIndex, h.SetInterfaceTag(ifName, swIfIndex) +} + +// DelGreTunnel removes GRE interface. +func (h *InterfaceVppHandler) DelGreTunnel(ifName string, greLink *ifs.GreLink) (uint32, error) { + swIfIndex, err := h.greAddDelTunnel(false, greLink) + if err != nil { + return 0, err + } + return swIfIndex, h.RemoveInterfaceTag(ifName, swIfIndex) +} diff --git a/plugins/vpp/ifplugin/vppcalls/vpp2306/gtpu_vppcalls.go b/plugins/vpp/ifplugin/vppcalls/vpp2306/gtpu_vppcalls.go new file mode 100644 index 0000000000..0b5b63daba --- /dev/null +++ b/plugins/vpp/ifplugin/vppcalls/vpp2306/gtpu_vppcalls.go @@ -0,0 +1,202 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "fmt" + "net" + + "github.com/pkg/errors" + + "go.ligato.io/vpp-agent/v3/plugins/vpp" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/gtpu" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface_types" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip_types" + "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/vppcalls" + interfaces "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/interfaces" +) + +const defaultDecapNextIndex = 0xFFFFFFFF + +func (h *InterfaceVppHandler) gtpuAddDelTunnel(isAdd bool, gtpuLink *interfaces.GtpuLink, multicastIf uint32) (uint32, error) { + var decapNextNode uint32 = defaultDecapNextIndex + if gtpuLink.DecapNextNode != 0 { + decapNextNode = gtpuLink.DecapNextNode + } else { + // backwards compatible fallback + if gtpuLink.DecapNext != interfaces.GtpuLink_DEFAULT { + decapNextNode = uint32(gtpuLink.DecapNext) + } + } + + req := >pu.GtpuAddDelTunnel{ + IsAdd: isAdd, + McastSwIfIndex: interface_types.InterfaceIndex(multicastIf), + EncapVrfID: gtpuLink.EncapVrfId, + Teid: gtpuLink.Teid, + Tteid: gtpuLink.RemoteTeid, + DecapNextIndex: decapNextNode, + } + + srcAddr := net.ParseIP(gtpuLink.SrcAddr) + if srcAddr == nil { + err := errors.New("bad source address for GTPU tunnel") + return 0, err + } + + dstAddr := net.ParseIP(gtpuLink.DstAddr) + if dstAddr == nil { + err := errors.New("bad destination address for GTPU tunnel") + return 0, err + } + + if gtpuLink.SrcAddr == gtpuLink.DstAddr { + err := errors.New("source and destination are the same") + return 0, err + } + + var isSrcIPv6, isDstIPv6 bool + + if srcAddr.To4() == nil { + isSrcIPv6 = true + } + if dstAddr.To4() == nil { + isDstIPv6 = true + } + + if !isSrcIPv6 && !isDstIPv6 { + var src, dst [4]uint8 + copy(src[:], srcAddr.To4()) + copy(dst[:], dstAddr.To4()) + req.SrcAddress = ip_types.Address{ + Af: ip_types.ADDRESS_IP4, + Un: ip_types.AddressUnionIP4(src), + } + req.DstAddress = ip_types.Address{ + Af: ip_types.ADDRESS_IP4, + Un: ip_types.AddressUnionIP4(dst), + } + } else if isSrcIPv6 && isDstIPv6 { + var src, dst [16]uint8 + copy(src[:], srcAddr.To16()) + copy(dst[:], dstAddr.To16()) + req.SrcAddress = ip_types.Address{ + Af: ip_types.ADDRESS_IP6, + Un: ip_types.AddressUnionIP6(src), + } + req.DstAddress = ip_types.Address{ + Af: ip_types.ADDRESS_IP6, + Un: ip_types.AddressUnionIP6(dst), + } + } else { + return 0, errors.New("source and destination addresses must be both either IPv4 or IPv6") + } + + reply := >pu.GtpuAddDelTunnelReply{} + + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return 0, err + } + return uint32(reply.SwIfIndex), nil +} + +// AddGtpuTunnel adds new GTPU interface. +func (h *InterfaceVppHandler) AddGtpuTunnel(ifName string, gtpuLink *interfaces.GtpuLink, multicastIf uint32) (uint32, error) { + if h.gtpu == nil { + return 0, errors.WithMessage(vpp.ErrPluginDisabled, "gtpu") + } + if gtpuLink == nil { + return 0, errors.New("missing GTPU tunnel information") + } + + swIfIndex, err := h.gtpuAddDelTunnel(true, gtpuLink, multicastIf) + if err != nil { + return 0, err + } + return swIfIndex, h.SetInterfaceTag(ifName, swIfIndex) +} + +// DelGtpuTunnel removes GTPU interface. +func (h *InterfaceVppHandler) DelGtpuTunnel(ifName string, gtpuLink *interfaces.GtpuLink) error { + if h.gtpu == nil { + return errors.WithMessage(vpp.ErrPluginDisabled, "gtpu") + } + if gtpuLink == nil { + return errors.New("missing GTPU tunnel information") + } + + swIfIndex, err := h.gtpuAddDelTunnel(false, gtpuLink, 0) + if err != nil { + return err + } + return h.RemoveInterfaceTag(ifName, swIfIndex) +} + +// dumpGtpuDetails dumps GTP-U interface details from VPP and fills them into the provided interface map. +func (h *InterfaceVppHandler) dumpGtpuDetails(ifc map[uint32]*vppcalls.InterfaceDetails) error { + if h.gtpu == nil { + // no-op when disabled + return nil + } + + reqCtx := h.callsChannel.SendMultiRequest(>pu.GtpuTunnelDump{ + SwIfIndex: ^interface_types.InterfaceIndex(0), + }) + for { + gtpuDetails := >pu.GtpuTunnelDetails{} + stop, err := reqCtx.ReceiveReply(gtpuDetails) + if stop { + break // Break from the loop. + } + if err != nil { + return fmt.Errorf("failed to dump GTP-U tunnel interface details: %v", err) + } + _, ifIdxExists := ifc[uint32(gtpuDetails.SwIfIndex)] + if !ifIdxExists { + continue + } + // Multicast interface + var multicastIfName string + _, exists := ifc[uint32(gtpuDetails.McastSwIfIndex)] + if exists { + multicastIfName = ifc[uint32(gtpuDetails.McastSwIfIndex)].Interface.Name + } + + gtpuLink := &interfaces.GtpuLink{ + Multicast: multicastIfName, + EncapVrfId: gtpuDetails.EncapVrfID, + Teid: gtpuDetails.Teid, + RemoteTeid: gtpuDetails.Tteid, + DecapNextNode: gtpuDetails.DecapNextIndex, + } + + if gtpuDetails.SrcAddress.Af == ip_types.ADDRESS_IP6 { + srcAddrArr := gtpuDetails.SrcAddress.Un.GetIP6() + gtpuLink.SrcAddr = net.IP(srcAddrArr[:]).To16().String() + dstAddrArr := gtpuDetails.DstAddress.Un.GetIP6() + gtpuLink.DstAddr = net.IP(dstAddrArr[:]).To16().String() + } else { + srcAddrArr := gtpuDetails.SrcAddress.Un.GetIP4() + gtpuLink.SrcAddr = net.IP(srcAddrArr[:4]).To4().String() + dstAddrArr := gtpuDetails.DstAddress.Un.GetIP4() + gtpuLink.DstAddr = net.IP(dstAddrArr[:4]).To4().String() + } + + ifc[uint32(gtpuDetails.SwIfIndex)].Interface.Link = &interfaces.Interface_Gtpu{Gtpu: gtpuLink} + ifc[uint32(gtpuDetails.SwIfIndex)].Interface.Type = interfaces.Interface_GTPU_TUNNEL + } + + return nil +} diff --git a/plugins/vpp/ifplugin/vppcalls/vpp2306/gtpu_vppcalls_test.go b/plugins/vpp/ifplugin/vppcalls/vpp2306/gtpu_vppcalls_test.go new file mode 100644 index 0000000000..c4df8af3c4 --- /dev/null +++ b/plugins/vpp/ifplugin/vppcalls/vpp2306/gtpu_vppcalls_test.go @@ -0,0 +1,267 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306_test + +import ( + "testing" + + . "github.com/onsi/gomega" + + vpp_gtpu "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/gtpu" + vpp_ifs "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip_types" + ifs "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/interfaces" +) + +func TestAddGtpuTunnel(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_gtpu.GtpuAddDelTunnelReply{ + SwIfIndex: 1, + }) + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceTagAddDelReply{}) + + swIfIdx, err := ifHandler.AddGtpuTunnel("ifName", &ifs.GtpuLink{ + SrcAddr: "10.0.0.1", + DstAddr: "20.0.0.1", + EncapVrfId: 10, + Teid: 100, + }, 2) + Expect(err).To(BeNil()) + Expect(swIfIdx).To(BeEquivalentTo(1)) + var msgCheck bool + for _, msg := range ctx.MockChannel.Msgs { + vppMsg, ok := msg.(*vpp_gtpu.GtpuAddDelTunnel) + if ok { + Expect(vppMsg.SrcAddress).To(Equal(ip_types.Address{ + Af: ip_types.ADDRESS_IP4, + Un: ip_types.AddressUnionIP4(ip_types.IP4Address{10, 0, 0, 1}), + })) + Expect(vppMsg.DstAddress).To(Equal(ip_types.Address{ + Af: ip_types.ADDRESS_IP4, + Un: ip_types.AddressUnionIP4(ip_types.IP4Address{20, 0, 0, 1}), + })) + Expect(vppMsg.IsAdd).To(BeTrue()) + Expect(vppMsg.EncapVrfID).To(BeEquivalentTo(10)) + Expect(vppMsg.McastSwIfIndex).To(BeEquivalentTo(2)) + Expect(vppMsg.Teid).To(BeEquivalentTo(100)) + Expect(vppMsg.SrcAddress.Af).To(Equal(ip_types.ADDRESS_IP4)) + msgCheck = true + } + } + Expect(msgCheck).To(BeTrue()) +} + +func TestAddGtpuTunnelIPv6(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_gtpu.GtpuAddDelTunnelReply{ + SwIfIndex: 1, + }) + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceTagAddDelReply{}) + + swIfIdx, err := ifHandler.AddGtpuTunnel("ifName", &ifs.GtpuLink{ + SrcAddr: "2001:db8:0:1:1:1:1:1", + DstAddr: "2002:db8:0:1:1:1:1:1", + EncapVrfId: 10, + Teid: 200, + }, 0xFFFFFFFF) + Expect(err).To(BeNil()) + Expect(swIfIdx).To(BeEquivalentTo(1)) + var msgCheck bool + for _, msg := range ctx.MockChannel.Msgs { + vppMsg, ok := msg.(*vpp_gtpu.GtpuAddDelTunnel) + if ok { + Expect(vppMsg.SrcAddress).To(Equal(ip_types.Address{ + Af: ip_types.ADDRESS_IP6, + Un: ip_types.AddressUnionIP6(ip_types.IP6Address{ + 0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0x01, 0, 0x01, 0, 0x01, 0, 0x01, 0, 0x01, + }), + })) + Expect(vppMsg.DstAddress).To(Equal(ip_types.Address{ + Af: ip_types.ADDRESS_IP6, + Un: ip_types.AddressUnionIP6(ip_types.IP6Address{ + 0x20, 0x02, 0x0d, 0xb8, 0, 0, 0, 0x01, 0, 0x01, 0, 0x01, 0, 0x01, 0, 0x01, + }), + })) + msgCheck = true + } + } + Expect(msgCheck).To(BeTrue()) +} + +func TestAddGtpuTunnelIPMismatch(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_gtpu.GtpuAddDelTunnelReply{ + SwIfIndex: 1, + }) + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceTagAddDelReply{}) + + _, err := ifHandler.AddGtpuTunnel("ifName", &ifs.GtpuLink{ + SrcAddr: "10.0.0.1", + DstAddr: "2001:db8:0:1:1:1:1:1", + EncapVrfId: 0, + Teid: 100, + }, 0xFFFFFFFF) + Expect(err).ToNot(BeNil()) +} + +func TestAddGtpuTunnelInvalidIPv4(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_gtpu.GtpuAddDelTunnelReply{ + SwIfIndex: 1, + }) + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceTagAddDelReply{}) + + _, err := ifHandler.AddGtpuTunnel("ifName", &ifs.GtpuLink{ + SrcAddr: "10.0.0", + DstAddr: "20.0.0.1", + EncapVrfId: 0, + Teid: 100, + }, 0xFFFFFFFF) + Expect(err).ToNot(BeNil()) +} + +func TestAddGtpuTunnelInvalidIPv6(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_gtpu.GtpuAddDelTunnelReply{ + SwIfIndex: 1, + }) + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceTagAddDelReply{}) + + _, err := ifHandler.AddGtpuTunnel("ifName", &ifs.GtpuLink{ + SrcAddr: "2001:db8:0:1:1:1:1:1", + DstAddr: "2002:db8:0:1:1:1:1:1:1", + EncapVrfId: 0, + Teid: 100, + }, 0xFFFFFFFF) + Expect(err).ToNot(BeNil()) +} + +func TestAddGtpuTunnelError(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_gtpu.GtpuAddDelTunnel{}) + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceTagAddDelReply{}) + + _, err := ifHandler.AddGtpuTunnel("ifName", &ifs.GtpuLink{ + SrcAddr: "10.0.0.1", + DstAddr: "20.0.0.2", + EncapVrfId: 0, + Teid: 100, + }, 0xFFFFFFFF) + Expect(err).ToNot(BeNil()) +} + +func TestAddGtpuTunnelRetval(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_gtpu.GtpuAddDelTunnelReply{ + Retval: 1, + }) + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceTagAddDelReply{}) + + _, err := ifHandler.AddGtpuTunnel("ifName", &ifs.GtpuLink{ + SrcAddr: "10.0.0.1", + DstAddr: "20.0.0.2", + EncapVrfId: 0, + Teid: 100, + }, 0xFFFFFFFF) + Expect(err).ToNot(BeNil()) +} + +func TestDelGtpuTunnel(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_gtpu.GtpuAddDelTunnelReply{ + SwIfIndex: 1, + }) + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceTagAddDelReply{}) + + err := ifHandler.DelGtpuTunnel("ifName", &ifs.GtpuLink{ + SrcAddr: "10.0.0.1", + DstAddr: "20.0.0.2", + EncapVrfId: 0, + Teid: 100, + }) + Expect(err).To(BeNil()) +} + +func TestDelGtpuTunnelError(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_gtpu.GtpuAddDelTunnel{}) + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceTagAddDelReply{}) + + err := ifHandler.DelGtpuTunnel("ifName", &ifs.GtpuLink{ + SrcAddr: "10.0.0.1", + DstAddr: "20.0.0.2", + EncapVrfId: 0, + Teid: 100, + }) + Expect(err).ToNot(BeNil()) +} + +func TestDelGtpuTunnelRetval(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_gtpu.GtpuAddDelTunnelReply{ + Retval: 1, + }) + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceTagAddDelReply{}) + + err := ifHandler.DelGtpuTunnel("ifName", &ifs.GtpuLink{ + SrcAddr: "10.0.0.1", + DstAddr: "20.0.0.1", + EncapVrfId: 0, + Teid: 100, + }) + Expect(err).ToNot(BeNil()) +} + +func TestAddNilGtpuTunnel(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_gtpu.GtpuAddDelTunnel{}) + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceTagAddDelReply{}) + + _, err := ifHandler.AddGtpuTunnel("ifName", nil, 0xFFFFFFFF) + Expect(err).ToNot(BeNil()) +} + +func TestDelNilGtpuTunnel(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_gtpu.GtpuAddDelTunnel{}) + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceTagAddDelReply{}) + + err := ifHandler.DelGtpuTunnel("ifName", nil) + Expect(err).ToNot(BeNil()) +} diff --git a/plugins/vpp/ifplugin/vppcalls/vpp2306/helpers.go b/plugins/vpp/ifplugin/vppcalls/vpp2306/helpers.go new file mode 100644 index 0000000000..97be95095d --- /dev/null +++ b/plugins/vpp/ifplugin/vppcalls/vpp2306/helpers.go @@ -0,0 +1,73 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "fmt" + "net" + "strings" + + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip_types" +) + +// IPToAddress converts string type IP address to VPP ip.api address representation +func IPToAddress(ipStr string) (addr ip_types.Address, err error) { + netIP := net.ParseIP(ipStr) + if netIP == nil { + return ip_types.Address{}, fmt.Errorf("invalid IP: %q", ipStr) + } + if ip4 := netIP.To4(); ip4 == nil { + addr.Af = ip_types.ADDRESS_IP6 + var ip6addr ip_types.IP6Address + copy(ip6addr[:], netIP.To16()) + addr.Un.SetIP6(ip6addr) + } else { + addr.Af = ip_types.ADDRESS_IP4 + var ip4addr ip_types.IP4Address + copy(ip4addr[:], ip4) + addr.Un.SetIP4(ip4addr) + } + return +} + +func ipToAddress(address *net.IPNet, isIPv6 bool) (ipAddr ip_types.Address) { + if isIPv6 { + ipAddr.Af = ip_types.ADDRESS_IP6 + var ip6addr ip_types.IP6Address + copy(ip6addr[:], address.IP.To16()) + ipAddr.Un.SetIP6(ip6addr) + } else { + ipAddr.Af = ip_types.ADDRESS_IP4 + var ip4addr ip_types.IP4Address + copy(ip4addr[:], address.IP.To4()) + ipAddr.Un.SetIP4(ip4addr) + } + return +} + +func boolToUint(input bool) uint8 { + if input { + return 1 + } + return 0 +} + +func uintToBool(value uint8) bool { + return value != 0 +} + +func cleanString(s string) string { + return strings.SplitN(s, "\x00", 2)[0] +} diff --git a/plugins/vpp/ifplugin/vppcalls/vpp2306/ip6nd_vppcalls.go b/plugins/vpp/ifplugin/vppcalls/vpp2306/ip6nd_vppcalls.go new file mode 100644 index 0000000000..dfa88a6c68 --- /dev/null +++ b/plugins/vpp/ifplugin/vppcalls/vpp2306/ip6nd_vppcalls.go @@ -0,0 +1,34 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "context" + + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface_types" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/rd_cp" +) + +func (h *InterfaceVppHandler) SetIP6ndAutoconfig(ctx context.Context, ifIdx uint32, enable, installDefaultRoutes bool) error { + _, err := h.rpcRdCp.IP6NdAddressAutoconfig(ctx, &rd_cp.IP6NdAddressAutoconfig{ + SwIfIndex: interface_types.InterfaceIndex(ifIdx), + Enable: enable, + InstallDefaultRoutes: installDefaultRoutes, + }) + if err != nil { + return err + } + return nil +} diff --git a/plugins/vpp/ifplugin/vppcalls/vpp2306/ip_container_vppcalls.go b/plugins/vpp/ifplugin/vppcalls/vpp2306/ip_container_vppcalls.go new file mode 100644 index 0000000000..27179186d4 --- /dev/null +++ b/plugins/vpp/ifplugin/vppcalls/vpp2306/ip_container_vppcalls.go @@ -0,0 +1,49 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface_types" + vpp_ip "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip_types" +) + +func (h *InterfaceVppHandler) sendAndLogMessageForVpp(ifIdx uint32, addr string, isAdd bool) error { + prefix, err := ip_types.ParsePrefix(addr) + if err != nil { + return err + } + + req := &vpp_ip.IPContainerProxyAddDel{ + SwIfIndex: interface_types.InterfaceIndex(ifIdx), + IsAdd: isAdd, + Pfx: prefix, + } + reply := &vpp_ip.IPContainerProxyAddDelReply{} + + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + + return nil +} + +func (h *InterfaceVppHandler) AddContainerIP(ifIdx uint32, addr string) error { + return h.sendAndLogMessageForVpp(ifIdx, addr, true) +} + +func (h *InterfaceVppHandler) DelContainerIP(ifIdx uint32, addr string) error { + return h.sendAndLogMessageForVpp(ifIdx, addr, false) +} diff --git a/plugins/vpp/ifplugin/vppcalls/vpp2306/ip_container_vppcalls_test.go b/plugins/vpp/ifplugin/vppcalls/vpp2306/ip_container_vppcalls_test.go new file mode 100644 index 0000000000..c425ce94da --- /dev/null +++ b/plugins/vpp/ifplugin/vppcalls/vpp2306/ip_container_vppcalls_test.go @@ -0,0 +1,169 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306_test + +import ( + "fmt" + "testing" + + . "github.com/onsi/gomega" + + vpp_ip "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip_types" + "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/vppcalls/vpp2306" +) + +func ipToAddr(ip string) ip_types.Address { + addr, err := vpp2306.IPToAddress(ip) + if err != nil { + panic(fmt.Sprintf("invalid IP: %s", ip)) + } + return addr +} + +func TestAddContainerIP(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ip.IPContainerProxyAddDelReply{}) + + err := ifHandler.AddContainerIP(1, "10.0.0.1/24") + + Expect(err).To(BeNil()) + vppMsg, ok := ctx.MockChannel.Msg.(*vpp_ip.IPContainerProxyAddDel) + Expect(ok).To(BeTrue()) + Expect(vppMsg.SwIfIndex).To(BeEquivalentTo(1)) + Expect(vppMsg.Pfx).To(BeEquivalentTo(ip_types.Prefix{ + Address: ipToAddr("10.0.0.1"), + Len: 24, + })) + Expect(vppMsg.IsAdd).To(BeTrue()) +} + +func TestAddContainerIPv6(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ip.IPContainerProxyAddDelReply{}) + + err := ifHandler.AddContainerIP(1, "2001:db8:0:1:1:1:1:1/128") + + Expect(err).To(BeNil()) + vppMsg, ok := ctx.MockChannel.Msg.(*vpp_ip.IPContainerProxyAddDel) + Expect(ok).To(BeTrue()) + Expect(vppMsg.SwIfIndex).To(BeEquivalentTo(1)) + Expect(vppMsg.Pfx).To(BeEquivalentTo(ip_types.Prefix{ + Address: ipToAddr("2001:db8:0:1:1:1:1:1"), + Len: 128, + })) + Expect(vppMsg.IsAdd).To(BeTrue()) +} + +func TestAddContainerIPInvalidIP(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ip.IPAddressDetails{}) + + err := ifHandler.AddContainerIP(1, "invalid-ip") + + Expect(err).ToNot(BeNil()) +} + +func TestAddContainerIPError(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ip.IPAddressDetails{}) + + err := ifHandler.AddContainerIP(1, "10.0.0.1/24") + + Expect(err).ToNot(BeNil()) +} + +func TestAddContainerIPRetval(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ip.IPContainerProxyAddDelReply{ + Retval: 1, + }) + + err := ifHandler.AddContainerIP(1, "10.0.0.1/24") + + Expect(err).ToNot(BeNil()) +} + +func TestDelContainerIP(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ip.IPContainerProxyAddDelReply{}) + + err := ifHandler.DelContainerIP(1, "10.0.0.1/24") + + Expect(err).To(BeNil()) + vppMsg, ok := ctx.MockChannel.Msg.(*vpp_ip.IPContainerProxyAddDel) + Expect(ok).To(BeTrue()) + Expect(vppMsg.SwIfIndex).To(BeEquivalentTo(1)) + Expect(vppMsg.Pfx).To(BeEquivalentTo(ip_types.Prefix{ + Address: ipToAddr("10.0.0.1"), + Len: 24, + })) + Expect(vppMsg.IsAdd).To(BeFalse()) +} + +func TestDelContainerIPv6(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ip.IPContainerProxyAddDelReply{}) + + err := ifHandler.DelContainerIP(1, "2001:db8:0:1:1:1:1:1/128") + + Expect(err).To(BeNil()) + vppMsg, ok := ctx.MockChannel.Msg.(*vpp_ip.IPContainerProxyAddDel) + Expect(ok).To(BeTrue()) + Expect(vppMsg.SwIfIndex).To(BeEquivalentTo(1)) + Expect(vppMsg.Pfx).To(BeEquivalentTo(ip_types.Prefix{ + Address: ipToAddr("2001:db8:0:1:1:1:1:1"), + Len: 128, + })) + Expect(vppMsg.IsAdd).To(BeFalse()) +} + +func TestDelContainerIPError(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ip.IPAddressDetails{}) + + err := ifHandler.DelContainerIP(1, "10.0.0.1/24") + + Expect(err).ToNot(BeNil()) +} + +func TestDelContainerIPRetval(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ip.IPContainerProxyAddDelReply{ + Retval: 1, + }) + + err := ifHandler.DelContainerIP(1, "10.0.0.1/24") + + Expect(err).ToNot(BeNil()) +} diff --git a/plugins/vpp/ifplugin/vppcalls/vpp2306/ip_vppcalls.go b/plugins/vpp/ifplugin/vppcalls/vpp2306/ip_vppcalls.go new file mode 100644 index 0000000000..1711f4d8ab --- /dev/null +++ b/plugins/vpp/ifplugin/vppcalls/vpp2306/ip_vppcalls.go @@ -0,0 +1,79 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "context" + "net" + + "go.ligato.io/cn-infra/v2/utils/addrs" + + vpp_ifs "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface_types" +) + +func (h *InterfaceVppHandler) addDelInterfaceIP(ifIdx uint32, addr *net.IPNet, isAdd bool) error { + req := &vpp_ifs.SwInterfaceAddDelAddress{ + SwIfIndex: interface_types.InterfaceIndex(ifIdx), + IsAdd: isAdd, + } + + isIPv6, err := addrs.IsIPv6(addr.IP.String()) + if err != nil { + return err + } + req.Prefix.Address = ipToAddress(addr, isIPv6) + prefix, _ := addr.Mask.Size() + req.Prefix.Len = byte(prefix) + + reply := &vpp_ifs.SwInterfaceAddDelAddressReply{} + + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + + return nil +} + +func (h *InterfaceVppHandler) AddInterfaceIP(ifIdx uint32, addr *net.IPNet) error { + return h.addDelInterfaceIP(ifIdx, addr, true) +} + +func (h *InterfaceVppHandler) DelInterfaceIP(ifIdx uint32, addr *net.IPNet) error { + return h.addDelInterfaceIP(ifIdx, addr, false) +} + +func (h *InterfaceVppHandler) setUnsetUnnumberedIP(uIfIdx uint32, ifIdxWithIP uint32, isAdd bool) error { + req := &vpp_ifs.SwInterfaceSetUnnumbered{ + SwIfIndex: interface_types.InterfaceIndex(ifIdxWithIP), + UnnumberedSwIfIndex: interface_types.InterfaceIndex(uIfIdx), + IsAdd: isAdd, + } + reply := &vpp_ifs.SwInterfaceSetUnnumberedReply{} + + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + + return nil +} + +func (h *InterfaceVppHandler) SetUnnumberedIP(ctx context.Context, uIfIdx uint32, ifIdxWithIP uint32) error { + return h.setUnsetUnnumberedIP(uIfIdx, ifIdxWithIP, true) +} + +func (h *InterfaceVppHandler) UnsetUnnumberedIP(ctx context.Context, uIfIdx uint32) error { + return h.setUnsetUnnumberedIP(uIfIdx, 0, false) +} diff --git a/plugins/vpp/ifplugin/vppcalls/vpp2306/ip_vppcalls_test.go b/plugins/vpp/ifplugin/vppcalls/vpp2306/ip_vppcalls_test.go new file mode 100644 index 0000000000..da3cc423eb --- /dev/null +++ b/plugins/vpp/ifplugin/vppcalls/vpp2306/ip_vppcalls_test.go @@ -0,0 +1,283 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306_test + +import ( + "net" + "testing" + + . "github.com/onsi/gomega" + + vpp_ifs "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip_types" +) + +func TestAddInterfaceIP(t *testing.T) { + var ipv4Addr [4]uint8 + + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceAddDelAddressReply{}) + + _, ipNet, err := net.ParseCIDR("10.0.0.1/24") + Expect(err).To(BeNil()) + err = ifHandler.AddInterfaceIP(1, ipNet) + + Expect(err).To(BeNil()) + vppMsg, ok := ctx.MockChannel.Msg.(*vpp_ifs.SwInterfaceAddDelAddress) + Expect(ok).To(BeTrue()) + Expect(vppMsg.SwIfIndex).To(BeEquivalentTo(1)) + Expect(vppMsg.Prefix.Address.Af).To(BeEquivalentTo(ip_types.ADDRESS_IP4)) + copy(ipv4Addr[:], ipNet.IP.To4()) + Expect(vppMsg.Prefix.Address.Un.GetIP4()).To(BeEquivalentTo(ipv4Addr)) + Expect(vppMsg.Prefix.Len).To(BeEquivalentTo(24)) + Expect(vppMsg.DelAll).To(BeFalse()) + Expect(vppMsg.IsAdd).To(BeTrue()) +} + +func TestAddInterfaceIPv6(t *testing.T) { + var ipv6Addr [16]uint8 + + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceAddDelAddressReply{}) + + _, ipNet, err := net.ParseCIDR("2001:db8:0:1:1:1:1:1/128") + Expect(err).To(BeNil()) + err = ifHandler.AddInterfaceIP(1, ipNet) + + Expect(err).To(BeNil()) + vppMsg, ok := ctx.MockChannel.Msg.(*vpp_ifs.SwInterfaceAddDelAddress) + Expect(ok).To(BeTrue()) + Expect(vppMsg.SwIfIndex).To(BeEquivalentTo(1)) + Expect(vppMsg.Prefix.Address.Af).To(BeEquivalentTo(ip_types.ADDRESS_IP6)) + copy(ipv6Addr[:], ipNet.IP.To16()) + Expect(vppMsg.Prefix.Address.Un.GetIP6()).To(BeEquivalentTo(ipv6Addr)) + Expect(vppMsg.Prefix.Len).To(BeEquivalentTo(128)) + Expect(vppMsg.DelAll).To(BeFalse()) + Expect(vppMsg.IsAdd).To(BeTrue()) +} + +func TestAddInterfaceInvalidIP(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceAddDelAddressReply{}) + + err := ifHandler.AddInterfaceIP(1, &net.IPNet{ + IP: []byte("invalid-ip"), + }) + + Expect(err).ToNot(BeNil()) +} + +func TestAddInterfaceIPError(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + _, ipNet, err := net.ParseCIDR("10.0.0.1/24") + Expect(err).To(BeNil()) + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceAddDelAddress{}) + + err = ifHandler.AddInterfaceIP(1, ipNet) + + Expect(err).ToNot(BeNil()) +} + +func TestAddInterfaceIPRetval(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + _, ipNet, err := net.ParseCIDR("10.0.0.1/24") + Expect(err).To(BeNil()) + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceAddDelAddressReply{ + Retval: 1, + }) + + err = ifHandler.AddInterfaceIP(1, ipNet) + + Expect(err).ToNot(BeNil()) +} + +func TestDelInterfaceIP(t *testing.T) { + var ipv4Addr [4]uint8 + + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceAddDelAddressReply{}) + + _, ipNet, err := net.ParseCIDR("10.0.0.1/24") + Expect(err).To(BeNil()) + err = ifHandler.DelInterfaceIP(1, ipNet) + + Expect(err).To(BeNil()) + vppMsg, ok := ctx.MockChannel.Msg.(*vpp_ifs.SwInterfaceAddDelAddress) + Expect(ok).To(BeTrue()) + Expect(vppMsg.SwIfIndex).To(BeEquivalentTo(1)) + Expect(vppMsg.Prefix.Address.Af).To(BeEquivalentTo(ip_types.ADDRESS_IP4)) + copy(ipv4Addr[:], ipNet.IP.To4()) + Expect(vppMsg.Prefix.Address.Un.GetIP4()).To(BeEquivalentTo(ipv4Addr)) + Expect(vppMsg.Prefix.Len).To(BeEquivalentTo(24)) + Expect(vppMsg.DelAll).To(BeFalse()) + Expect(vppMsg.IsAdd).To(BeFalse()) +} + +func TestDelInterfaceIPv6(t *testing.T) { + var ipv6Addr [16]uint8 + + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceAddDelAddressReply{}) + + _, ipNet, err := net.ParseCIDR("2001:db8:0:1:1:1:1:1/128") + Expect(err).To(BeNil()) + err = ifHandler.DelInterfaceIP(1, ipNet) + + Expect(err).To(BeNil()) + vppMsg, ok := ctx.MockChannel.Msg.(*vpp_ifs.SwInterfaceAddDelAddress) + Expect(ok).To(BeTrue()) + Expect(vppMsg.SwIfIndex).To(BeEquivalentTo(1)) + Expect(vppMsg.Prefix.Address.Af).To(BeEquivalentTo(ip_types.ADDRESS_IP6)) + copy(ipv6Addr[:], ipNet.IP.To16()) + Expect(vppMsg.Prefix.Address.Un.GetIP6()).To(BeEquivalentTo(ipv6Addr)) + Expect(vppMsg.Prefix.Len).To(BeEquivalentTo(128)) + Expect(vppMsg.DelAll).To(BeFalse()) + Expect(vppMsg.IsAdd).To(BeFalse()) +} + +func TestDelInterfaceInvalidIP(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceAddDelAddressReply{}) + + err := ifHandler.DelInterfaceIP(1, &net.IPNet{ + IP: []byte("invalid-ip"), + }) + + Expect(err).ToNot(BeNil()) +} + +func TestDelInterfaceIPError(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + _, ipNet, err := net.ParseCIDR("10.0.0.1/24") + Expect(err).To(BeNil()) + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceAddDelAddress{}) + + err = ifHandler.DelInterfaceIP(1, ipNet) + + Expect(err).ToNot(BeNil()) +} + +func TestDelInterfaceIPRetval(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + _, ipNet, err := net.ParseCIDR("10.0.0.1/24") + Expect(err).To(BeNil()) + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceAddDelAddressReply{ + Retval: 1, + }) + + err = ifHandler.DelInterfaceIP(1, ipNet) + + Expect(err).ToNot(BeNil()) +} + +func TestSetUnnumberedIP(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceSetUnnumberedReply{}) + + err := ifHandler.SetUnnumberedIP(ctx.Context, 1, 2) + + Expect(err).To(BeNil()) + vppMsg, ok := ctx.MockChannel.Msg.(*vpp_ifs.SwInterfaceSetUnnumbered) + Expect(ok).To(BeTrue()) + Expect(vppMsg.SwIfIndex).To(BeEquivalentTo(2)) + Expect(vppMsg.UnnumberedSwIfIndex).To(BeEquivalentTo(1)) + Expect(vppMsg.IsAdd).To(BeTrue()) +} + +func TestSetUnnumberedIPError(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceSetUnnumbered{}) + + err := ifHandler.SetUnnumberedIP(ctx.Context, 1, 2) + + Expect(err).ToNot(BeNil()) +} + +func TestSetUnnumberedIPRetval(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceSetUnnumberedReply{ + Retval: 1, + }) + + err := ifHandler.SetUnnumberedIP(ctx.Context, 1, 2) + + Expect(err).ToNot(BeNil()) +} + +func TestUnsetUnnumberedIP(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceSetUnnumberedReply{}) + + err := ifHandler.UnsetUnnumberedIP(ctx.Context, 1) + + Expect(err).To(BeNil()) + vppMsg, ok := ctx.MockChannel.Msg.(*vpp_ifs.SwInterfaceSetUnnumbered) + Expect(ok).To(BeTrue()) + Expect(vppMsg.SwIfIndex).To(BeEquivalentTo(0)) + Expect(vppMsg.UnnumberedSwIfIndex).To(BeEquivalentTo(1)) + Expect(vppMsg.IsAdd).To(BeFalse()) +} + +func TestUnsetUnnumberedIPError(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceSetUnnumbered{}) + + err := ifHandler.UnsetUnnumberedIP(ctx.Context, 1) + + Expect(err).ToNot(BeNil()) +} + +func TestUnsetUnnumberedIPRetval(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceSetUnnumberedReply{ + Retval: 1, + }) + + err := ifHandler.UnsetUnnumberedIP(ctx.Context, 1) + + Expect(err).ToNot(BeNil()) +} diff --git a/plugins/vpp/ifplugin/vppcalls/vpp2306/ipip_vppcalls.go b/plugins/vpp/ifplugin/vppcalls/vpp2306/ipip_vppcalls.go new file mode 100644 index 0000000000..0603383adc --- /dev/null +++ b/plugins/vpp/ifplugin/vppcalls/vpp2306/ipip_vppcalls.go @@ -0,0 +1,175 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "fmt" + "net" + + "github.com/pkg/errors" + + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface_types" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip_types" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ipip" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/tunnel_types" + "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/vppcalls" + interfaces "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/interfaces" +) + +// AddIpipTunnel adds new IPIP tunnel interface. +func (h *InterfaceVppHandler) AddIpipTunnel(ifName string, vrf uint32, ipipLink *interfaces.IPIPLink) (uint32, error) { + req := &ipip.IpipAddTunnel{ + Tunnel: ipip.IpipTunnel{ + Instance: ^uint32(0), + TableID: vrf, + }, + } + + if ipipLink == nil { + return 0, errors.New("missing IPIP tunnel information") + } + var srcAddr, dstAddr net.IP + var isSrcIPv6, isDstIPv6 bool + + srcAddr = net.ParseIP(ipipLink.SrcAddr) + if srcAddr == nil { + err := errors.New("bad source address for IPIP tunnel") + return 0, err + } + if srcAddr.To4() == nil { + isSrcIPv6 = true + } + + if ipipLink.TunnelMode == interfaces.IPIPLink_POINT_TO_POINT { + dstAddr = net.ParseIP(ipipLink.DstAddr) + if dstAddr == nil { + err := errors.New("bad destination address for IPIP tunnel") + return 0, err + } + if dstAddr.To4() == nil { + isDstIPv6 = true + } + } + + if !isSrcIPv6 && (dstAddr == nil || !isDstIPv6) { + var src, dst [4]uint8 + copy(src[:], srcAddr.To4()) + req.Tunnel.Src = ip_types.Address{ + Af: ip_types.ADDRESS_IP4, + Un: ip_types.AddressUnionIP4(src), + } + if dstAddr != nil { + copy(dst[:], dstAddr.To4()) + req.Tunnel.Dst = ip_types.Address{ + Af: ip_types.ADDRESS_IP4, + Un: ip_types.AddressUnionIP4(dst), + } + } + } else if isSrcIPv6 && (dstAddr == nil || isDstIPv6) { + var src, dst [16]uint8 + copy(src[:], srcAddr.To16()) + req.Tunnel.Src = ip_types.Address{ + Af: ip_types.ADDRESS_IP6, + Un: ip_types.AddressUnionIP6(src), + } + if dstAddr != nil { + copy(dst[:], dstAddr.To16()) + req.Tunnel.Dst = ip_types.Address{ + Af: ip_types.ADDRESS_IP6, + Un: ip_types.AddressUnionIP6(dst), + } + } + } else { + return 0, errors.New("source and destination addresses must be both either IPv4 or IPv6") + } + + if ipipLink.TunnelMode == interfaces.IPIPLink_POINT_TO_MULTIPOINT { + req.Tunnel.Mode = tunnel_types.TUNNEL_API_MODE_MP + } + + reply := &ipip.IpipAddTunnelReply{} + + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return 0, err + } + swIfIndex := uint32(reply.SwIfIndex) + return swIfIndex, h.SetInterfaceTag(ifName, swIfIndex) +} + +// DelIpipTunnel removes IPIP tunnel interface. +func (h *InterfaceVppHandler) DelIpipTunnel(ifName string, ifIdx uint32) error { + req := &ipip.IpipDelTunnel{ + SwIfIndex: interface_types.InterfaceIndex(ifIdx), + } + reply := &ipip.IpipDelTunnelReply{} + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + return h.RemoveInterfaceTag(ifName, ifIdx) +} + +// dumpIpipDetails dumps IPIP interface details from VPP and fills them into the provided interface map. +func (h *InterfaceVppHandler) dumpIpipDetails(ifc map[uint32]*vppcalls.InterfaceDetails) error { + + reqCtx := h.callsChannel.SendMultiRequest(&ipip.IpipTunnelDump{ + SwIfIndex: ^interface_types.InterfaceIndex(0), + }) + for { + ipipDetails := &ipip.IpipTunnelDetails{} + stop, err := reqCtx.ReceiveReply(ipipDetails) + if stop { + break // Break from the loop. + } + if err != nil { + return fmt.Errorf("failed to dump IPIP tunnel interface details: %v", err) + } + _, ifIdxExists := ifc[uint32(ipipDetails.Tunnel.SwIfIndex)] + if !ifIdxExists { + continue + } + + ipipLink := &interfaces.IPIPLink{} + if ipipDetails.Tunnel.Src.Af == ip_types.ADDRESS_IP6 { + srcAddrArr := ipipDetails.Tunnel.Src.Un.GetIP6() + ipipLink.SrcAddr = net.IP(srcAddrArr[:]).To16().String() + if ipipDetails.Tunnel.Mode == tunnel_types.TUNNEL_API_MODE_P2P { + dstAddrArr := ipipDetails.Tunnel.Dst.Un.GetIP6() + ipipLink.DstAddr = net.IP(dstAddrArr[:]).To16().String() + } + } else { + srcAddrArr := ipipDetails.Tunnel.Src.Un.GetIP4() + ipipLink.SrcAddr = net.IP(srcAddrArr[:4]).To4().String() + if ipipDetails.Tunnel.Mode == tunnel_types.TUNNEL_API_MODE_P2P { + dstAddrArr := ipipDetails.Tunnel.Dst.Un.GetIP4() + ipipLink.DstAddr = net.IP(dstAddrArr[:4]).To4().String() + } + } + + if ipipDetails.Tunnel.Mode == tunnel_types.TUNNEL_API_MODE_MP { + ipipLink.TunnelMode = interfaces.IPIPLink_POINT_TO_MULTIPOINT + } + + // TODO: temporary fix since VPP does not dump the tunnel mode properly. + // If dst address is empty, this must be a multipoint tunnel. + if ipipLink.DstAddr == "0.0.0.0" { + ipipLink.TunnelMode = interfaces.IPIPLink_POINT_TO_MULTIPOINT + ipipLink.DstAddr = "" + } + + ifc[uint32(ipipDetails.Tunnel.SwIfIndex)].Interface.Link = &interfaces.Interface_Ipip{Ipip: ipipLink} + ifc[uint32(ipipDetails.Tunnel.SwIfIndex)].Interface.Type = interfaces.Interface_IPIP_TUNNEL + } + return nil +} diff --git a/plugins/vpp/ifplugin/vppcalls/vpp2306/ipip_vppcalls_test.go b/plugins/vpp/ifplugin/vppcalls/vpp2306/ipip_vppcalls_test.go new file mode 100644 index 0000000000..4bb2b9fb2e --- /dev/null +++ b/plugins/vpp/ifplugin/vppcalls/vpp2306/ipip_vppcalls_test.go @@ -0,0 +1,217 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306_test + +import ( + "testing" + + . "github.com/onsi/gomega" + + vpp_ifs "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip_types" + vpp_ipip "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ipip" + ifs "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/interfaces" +) + +func TestAddIpipTunnel(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ipip.IpipAddTunnelReply{ + SwIfIndex: 1, + }) + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceTagAddDelReply{}) + + swIfIdx, err := ifHandler.AddIpipTunnel("ipiptun1", 0, &ifs.IPIPLink{ + SrcAddr: "10.0.0.1", + DstAddr: "20.0.0.1", + }) + Expect(err).To(BeNil()) + Expect(swIfIdx).To(BeEquivalentTo(1)) + var msgCheck bool + for _, msg := range ctx.MockChannel.Msgs { + vppMsg, ok := msg.(*vpp_ipip.IpipAddTunnel) + if ok { + Expect(vppMsg.Tunnel.Src).To(Equal(ip_types.Address{ + Af: ip_types.ADDRESS_IP4, + Un: ip_types.AddressUnionIP4(ip_types.IP4Address{10, 0, 0, 1}), + })) + Expect(vppMsg.Tunnel.Dst).To(Equal(ip_types.Address{ + Af: ip_types.ADDRESS_IP4, + Un: ip_types.AddressUnionIP4(ip_types.IP4Address{20, 0, 0, 1}), + })) + Expect(vppMsg.Tunnel.TableID).To(BeEquivalentTo(0)) + msgCheck = true + } + } + Expect(msgCheck).To(BeTrue()) +} + +func TestAddIpipTunnelWithVrf(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ipip.IpipAddTunnelReply{ + SwIfIndex: 1, + }) + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceTagAddDelReply{}) + + swIfIdx, err := ifHandler.AddIpipTunnel("ipiptun1", 1, &ifs.IPIPLink{ + SrcAddr: "10.0.0.1", + DstAddr: "20.0.0.1", + }) + Expect(err).To(BeNil()) + Expect(swIfIdx).To(BeEquivalentTo(1)) + var msgCheck bool + for _, msg := range ctx.MockChannel.Msgs { + vppMsg, ok := msg.(*vpp_ipip.IpipAddTunnel) + if ok { + Expect(vppMsg.Tunnel.Src).To(Equal(ip_types.Address{ + Af: ip_types.ADDRESS_IP4, + Un: ip_types.AddressUnionIP4(ip_types.IP4Address{10, 0, 0, 1}), + })) + Expect(vppMsg.Tunnel.Dst).To(Equal(ip_types.Address{ + Af: ip_types.ADDRESS_IP4, + Un: ip_types.AddressUnionIP4(ip_types.IP4Address{20, 0, 0, 1}), + })) + Expect(vppMsg.Tunnel.TableID).To(BeEquivalentTo(1)) + msgCheck = true + } + } + Expect(msgCheck).To(BeTrue()) +} + +func TestAddIpipTunnelIPv6(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ipip.IpipAddTunnelReply{ + SwIfIndex: 1, + }) + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceTagAddDelReply{}) + + swIfIdx, err := ifHandler.AddIpipTunnel("ipiptun1", 0, &ifs.IPIPLink{ + SrcAddr: "2001:db8:0:1:1:1:1:1", + DstAddr: "2002:db8:0:1:1:1:1:1", + }) + Expect(err).To(BeNil()) + Expect(swIfIdx).To(BeEquivalentTo(1)) + var msgCheck bool + for _, msg := range ctx.MockChannel.Msgs { + vppMsg, ok := msg.(*vpp_ipip.IpipAddTunnel) + if ok { + Expect(vppMsg.Tunnel.Src).To(Equal(ip_types.Address{ + Af: ip_types.ADDRESS_IP6, + Un: ip_types.AddressUnionIP6(ip_types.IP6Address{ + 0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0x01, 0, 0x01, 0, 0x01, 0, 0x01, 0, 0x01, + }), + })) + Expect(vppMsg.Tunnel.Dst).To(Equal(ip_types.Address{ + Af: ip_types.ADDRESS_IP6, + Un: ip_types.AddressUnionIP6(ip_types.IP6Address{ + 0x20, 0x02, 0x0d, 0xb8, 0, 0, 0, 0x01, 0, 0x01, 0, 0x01, 0, 0x01, 0, 0x01, + }), + })) + Expect(vppMsg.Tunnel.TableID).To(BeEquivalentTo(0)) + msgCheck = true + } + } + Expect(msgCheck).To(BeTrue()) +} + +func TestAddIpipTunnelIPMismatch(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ipip.IpipAddTunnelReply{ + SwIfIndex: 1, + }) + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceTagAddDelReply{}) + + _, err := ifHandler.AddIpipTunnel("ipiptun1", 0, &ifs.IPIPLink{ + SrcAddr: "10.0.0.1", + DstAddr: "2001:db8:0:1:1:1:1:1", + }) + Expect(err).ToNot(BeNil()) +} + +func TestAddIpipTunnelInvalidIP(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ipip.IpipAddTunnelReply{ + SwIfIndex: 1, + }) + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceTagAddDelReply{}) + + _, err := ifHandler.AddIpipTunnel("ipiptun1", 0, &ifs.IPIPLink{ + SrcAddr: "invalid-ip", + DstAddr: "2001:db8:0:1:1:1:1:1", + }) + Expect(err).ToNot(BeNil()) +} + +func TestAddIpipTunnelNoIPs(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ipip.IpipAddTunnelReply{ + SwIfIndex: 1, + }) + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceTagAddDelReply{}) + + _, err := ifHandler.AddIpipTunnel("ipiptun1", 0, &ifs.IPIPLink{}) + Expect(err).ToNot(BeNil()) +} + +func TestAddIpipTunnelRetval(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ipip.IpipAddTunnelReply{ + Retval: 1, + }) + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceTagAddDelReply{}) + + _, err := ifHandler.AddIpipTunnel("ipiptun1", 0, &ifs.IPIPLink{ + SrcAddr: "10.0.0.1", + DstAddr: "20.0.0.2", + }) + Expect(err).ToNot(BeNil()) +} + +func TestDeleteIpipTunnel(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ipip.IpipDelTunnelReply{}) + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceTagAddDelReply{}) + + err := ifHandler.DelIpipTunnel("ipiptun1", 1) + Expect(err).To(BeNil()) +} + +func TestDeleteIpipTunnelRetval(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ipip.IpipDelTunnelReply{ + Retval: 1, + }) + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceTagAddDelReply{}) + + err := ifHandler.DelIpipTunnel("ipiptun1", 1) + Expect(err).ToNot(BeNil()) +} diff --git a/plugins/vpp/ifplugin/vppcalls/vpp2306/ipsec_vppcalls.go b/plugins/vpp/ifplugin/vppcalls/vpp2306/ipsec_vppcalls.go new file mode 100644 index 0000000000..088f01103a --- /dev/null +++ b/plugins/vpp/ifplugin/vppcalls/vpp2306/ipsec_vppcalls.go @@ -0,0 +1,51 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "context" + + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface_types" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/tunnel_types" + + vpp_ipsec "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ipsec" + ifs "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/interfaces" +) + +// AddIPSecTunnelInterface adds a new IPSec tunnel interface. +func (h *InterfaceVppHandler) AddIPSecTunnelInterface(ctx context.Context, ifName string, ipSecLink *ifs.IPSecLink) (uint32, error) { + reply, err := h.ipsec.IpsecItfCreate(ctx, &vpp_ipsec.IpsecItfCreate{ + Itf: vpp_ipsec.IpsecItf{ + Mode: tunnel_types.TunnelMode(ipSecLink.TunnelMode), + }, + }) + if err != nil { + return 0, err + } + + return uint32(reply.SwIfIndex), nil +} + +// DeleteIPSecTunnelInterface removes existing IPSec tunnel interface. +func (h *InterfaceVppHandler) DeleteIPSecTunnelInterface(ctx context.Context, ifName string, idx uint32, ipSecLink *ifs.IPSecLink) error { + _, err := h.ipsec.IpsecItfDelete(ctx, &vpp_ipsec.IpsecItfDelete{ + SwIfIndex: interface_types.InterfaceIndex(idx), + }) + if err != nil { + return err + } + + return nil +} diff --git a/plugins/vpp/ifplugin/vppcalls/vpp2306/ipsec_vppcalls_test.go b/plugins/vpp/ifplugin/vppcalls/vpp2306/ipsec_vppcalls_test.go new file mode 100644 index 0000000000..596a6bcf9b --- /dev/null +++ b/plugins/vpp/ifplugin/vppcalls/vpp2306/ipsec_vppcalls_test.go @@ -0,0 +1,90 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306_test + +import ( + "testing" + + . "github.com/onsi/gomega" + + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface_types" + vpp_ipsec "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ipsec" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/tunnel_types" + ifs "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/interfaces" +) + +func TestAddIPSecTunnelInterface(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + ctx.MockVpp.MockReply(&vpp_ipsec.IpsecItfCreateReply{ + SwIfIndex: 2, + }) + + ipSecLink := &ifs.IPSecLink{ + TunnelMode: ifs.IPSecLink_POINT_TO_POINT, + } + index, err := ifHandler.AddIPSecTunnelInterface(ctx.Context, "if1", ipSecLink) + Expect(err).To(BeNil()) + Expect(index).To(Equal(uint32(2))) + + vppMsg, ok := ctx.MockChannel.Msg.(*vpp_ipsec.IpsecItfCreate) + Expect(ok).To(BeTrue()) + Expect(vppMsg).ToNot(BeNil()) + + itf := vpp_ipsec.IpsecItf{ + Mode: tunnel_types.TunnelMode(ifs.IPSecLink_POINT_TO_POINT), + } + + Expect(vppMsg.Itf).To(Equal(itf)) +} + +func TestAddIPSecTunnelInterfaceError(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + ctx.MockVpp.MockReply(&vpp_ipsec.IpsecItfCreateReply{ + SwIfIndex: 2, + Retval: 9, + }) + + index, err := ifHandler.AddIPSecTunnelInterface(ctx.Context, "if1", &ifs.IPSecLink{}) + Expect(err).ToNot(BeNil()) + Expect(index).To(Equal(uint32(0))) +} + +func TestDeleteIPSecTunnelInterface(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + ctx.MockVpp.MockReply(&vpp_ipsec.IpsecItfDeleteReply{}) + + err := ifHandler.DeleteIPSecTunnelInterface(ctx.Context, "if1", 2, &ifs.IPSecLink{}) + Expect(err).To(BeNil()) + + vppMsg, ok := ctx.MockChannel.Msg.(*vpp_ipsec.IpsecItfDelete) + Expect(ok).To(BeTrue()) + Expect(vppMsg).ToNot(BeNil()) + + Expect(vppMsg.SwIfIndex).To(Equal(interface_types.InterfaceIndex(2))) +} + +func TestDeleteIPSecTunnelInterfaceError(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + ctx.MockVpp.MockReply(&vpp_ipsec.IpsecItfDeleteReply{ + Retval: 9, + }) + + err := ifHandler.DeleteIPSecTunnelInterface(ctx.Context, "if1", 2, &ifs.IPSecLink{}) + Expect(err).ToNot(BeNil()) +} diff --git a/plugins/vpp/ifplugin/vppcalls/vpp2306/l2_vppcalls.go b/plugins/vpp/ifplugin/vppcalls/vpp2306/l2_vppcalls.go new file mode 100644 index 0000000000..f94852457a --- /dev/null +++ b/plugins/vpp/ifplugin/vppcalls/vpp2306/l2_vppcalls.go @@ -0,0 +1,65 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "fmt" + + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface_types" + vpp_l2 "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/l2" + ifs "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/interfaces" +) + +// TODO: more suitable for the l2 plugin, but the tag-rewrite retrieve is a part of the vpp interface api + +// SetVLanTagRewrite sets an interface tag rewrite +func (h *InterfaceVppHandler) SetVLanTagRewrite(ifIdx uint32, subIf *ifs.SubInterface) error { + req := &vpp_l2.L2InterfaceVlanTagRewrite{ + SwIfIndex: interface_types.InterfaceIndex(ifIdx), + VtrOp: getTagRewriteOption(subIf.TagRwOption), + PushDot1q: uint32(boolToUint(subIf.PushDot1Q)), + Tag1: subIf.Tag1, + Tag2: subIf.Tag2, + } + reply := &vpp_l2.L2InterfaceVlanTagRewriteReply{} + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return fmt.Errorf("%s returned error: %v", reply.GetMessageName(), err) + } + + return nil +} + +func getTagRewriteOption(op ifs.SubInterface_TagRewriteOptions) uint32 { + switch op { + case ifs.SubInterface_PUSH1: + return 1 + case ifs.SubInterface_PUSH2: + return 2 + case ifs.SubInterface_POP1: + return 3 + case ifs.SubInterface_POP2: + return 4 + case ifs.SubInterface_TRANSLATE11: + return 5 + case ifs.SubInterface_TRANSLATE12: + return 6 + case ifs.SubInterface_TRANSLATE21: + return 7 + case ifs.SubInterface_TRANSLATE22: + return 8 + default: // disabled + return 0 + } +} diff --git a/plugins/vpp/ifplugin/vppcalls/vpp2306/loopback_vppcalls.go b/plugins/vpp/ifplugin/vppcalls/vpp2306/loopback_vppcalls.go new file mode 100644 index 0000000000..a5252f8cae --- /dev/null +++ b/plugins/vpp/ifplugin/vppcalls/vpp2306/loopback_vppcalls.go @@ -0,0 +1,45 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + vpp_ifs "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface_types" +) + +func (h *InterfaceVppHandler) AddLoopbackInterface(ifName string) (swIndex uint32, err error) { + req := &vpp_ifs.CreateLoopback{} + reply := &vpp_ifs.CreateLoopbackReply{} + + if err = h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return 0, err + } + + return uint32(reply.SwIfIndex), h.SetInterfaceTag(ifName, uint32(reply.SwIfIndex)) +} + +func (h *InterfaceVppHandler) DeleteLoopbackInterface(ifName string, idx uint32) error { + // Prepare the message. + req := &vpp_ifs.DeleteLoopback{ + SwIfIndex: interface_types.InterfaceIndex(idx), + } + reply := &vpp_ifs.DeleteLoopbackReply{} + + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + + return h.RemoveInterfaceTag(ifName, idx) +} diff --git a/plugins/vpp/ifplugin/vppcalls/vpp2306/loopback_vppcalls_test.go b/plugins/vpp/ifplugin/vppcalls/vpp2306/loopback_vppcalls_test.go new file mode 100644 index 0000000000..4a7421b18e --- /dev/null +++ b/plugins/vpp/ifplugin/vppcalls/vpp2306/loopback_vppcalls_test.go @@ -0,0 +1,100 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306_test + +import ( + "testing" + + . "github.com/onsi/gomega" + + vpp_ifs "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface" +) + +func TestAddLoopbackInterface(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ifs.CreateLoopbackReply{ + SwIfIndex: 1, + }) + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceTagAddDelReply{}) + + swIfIdx, err := ifHandler.AddLoopbackInterface("loopback") + + Expect(err).To(BeNil()) + Expect(swIfIdx).To(BeEquivalentTo(1)) +} + +func TestAddLoopbackInterfaceError(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ifs.CreateLoopback{}) + + swIfIdx, err := ifHandler.AddLoopbackInterface("loopback") + + Expect(err).ToNot(BeNil()) + Expect(swIfIdx).To(BeEquivalentTo(0)) +} + +func TestAddLoopbackInterfaceRetval(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ifs.CreateLoopbackReply{ + Retval: 1, + }) + + swIfIdx, err := ifHandler.AddLoopbackInterface("loopback") + + Expect(err).ToNot(BeNil()) + Expect(swIfIdx).To(BeEquivalentTo(0)) +} + +func TestDeleteLoopbackInterface(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ifs.DeleteLoopbackReply{}) + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceTagAddDelReply{}) + + err := ifHandler.DeleteLoopbackInterface("loopback", 1) + + Expect(err).To(BeNil()) +} + +func TestDeleteLoopbackInterfaceError(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ifs.DeleteLoopback{}) + + err := ifHandler.DeleteLoopbackInterface("loopback", 1) + + Expect(err).ToNot(BeNil()) +} + +func TestDeleteLoopbackInterfaceRetval(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ifs.DeleteLoopbackReply{ + Retval: 1, + }) + + err := ifHandler.DeleteLoopbackInterface("loopback", 1) + + Expect(err).ToNot(BeNil()) +} diff --git a/plugins/vpp/ifplugin/vppcalls/vpp2306/mac_vppcalls.go b/plugins/vpp/ifplugin/vppcalls/vpp2306/mac_vppcalls.go new file mode 100644 index 0000000000..19d36c1aa7 --- /dev/null +++ b/plugins/vpp/ifplugin/vppcalls/vpp2306/mac_vppcalls.go @@ -0,0 +1,53 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "net" + + "github.com/go-errors/errors" + + vpp_ifs "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface_types" +) + +func (h *InterfaceVppHandler) SetInterfaceMac(ifIdx uint32, macAddress string) error { + mac, err := ParseMAC(macAddress) + if err != nil { + return err + } + req := &vpp_ifs.SwInterfaceSetMacAddress{ + SwIfIndex: interface_types.InterfaceIndex(ifIdx), + MacAddress: mac, + } + reply := &vpp_ifs.SwInterfaceSetMacAddressReply{} + + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + + return nil +} + +// parse mac string to uint8 array with len=6 +func ParseMAC(mac string) (parsedMac [6]uint8, err error) { + var hw net.HardwareAddr + if hw, err = net.ParseMAC(mac); err != nil { + err = errors.Errorf("invalid mac address %s: %v", mac, err) + return + } + copy(parsedMac[:], hw[:]) + return +} diff --git a/plugins/vpp/ifplugin/vppcalls/vpp2306/mac_vppcalls_test.go b/plugins/vpp/ifplugin/vppcalls/vpp2306/mac_vppcalls_test.go new file mode 100644 index 0000000000..6d5f907976 --- /dev/null +++ b/plugins/vpp/ifplugin/vppcalls/vpp2306/mac_vppcalls_test.go @@ -0,0 +1,76 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306_test + +import ( + "testing" + + . "github.com/onsi/gomega" + + vpp_ifs "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface" + vpp2306 "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/vppcalls/vpp2306" +) + +func TestSetInterfaceMac(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceSetMacAddressReply{}) + + mac, err := vpp2306.ParseMAC("65:77:BF:72:C9:8D") + Expect(err).To(BeNil()) + err = ifHandler.SetInterfaceMac(1, "65:77:BF:72:C9:8D") + Expect(err).To(BeNil()) + + vppMsg, ok := ctx.MockChannel.Msg.(*vpp_ifs.SwInterfaceSetMacAddress) + Expect(ok).To(BeTrue()) + Expect(vppMsg.SwIfIndex).To(BeEquivalentTo(1)) + Expect(vppMsg.MacAddress).To(BeEquivalentTo(mac)) +} + +func TestSetInterfaceInvalidMac(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceSetMacAddress{}) + + err := ifHandler.SetInterfaceMac(1, "invalid-mac") + + Expect(err).ToNot(BeNil()) +} + +func TestSetInterfaceMacError(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceSetMacAddress{}) + + err := ifHandler.SetInterfaceMac(1, "65:77:BF:72:C9:8D") + + Expect(err).ToNot(BeNil()) +} + +func TestSetInterfaceMacRetval(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceSetMacAddressReply{ + Retval: 1, + }) + + err := ifHandler.SetInterfaceMac(1, "65:77:BF:72:C9:8D") + + Expect(err).ToNot(BeNil()) +} diff --git a/plugins/vpp/ifplugin/vppcalls/vpp2306/memif_vppcalls.go b/plugins/vpp/ifplugin/vppcalls/vpp2306/memif_vppcalls.go new file mode 100644 index 0000000000..2d804c3d83 --- /dev/null +++ b/plugins/vpp/ifplugin/vppcalls/vpp2306/memif_vppcalls.go @@ -0,0 +1,202 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "context" + "fmt" + "io" + "strings" + + "github.com/pkg/errors" + "go.fd.io/govpp/api" + + "go.ligato.io/vpp-agent/v3/plugins/vpp" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface_types" + vpp_memif "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/memif" + "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/vppcalls" + ifs "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/interfaces" +) + +func (h *InterfaceVppHandler) AddMemifInterface(ctx context.Context, ifName string, memIface *ifs.MemifLink, socketID uint32) (swIdx uint32, err error) { + if h.memif == nil { + return 0, errors.WithMessage(vpp.ErrPluginDisabled, "memif") + } + + req := &vpp_memif.MemifCreate{ + ID: memIface.Id, + Mode: memifMode(memIface.Mode), + Secret: memIface.Secret, + SocketID: socketID, + BufferSize: uint16(memIface.BufferSize), + RingSize: memIface.RingSize, + RxQueues: uint8(memIface.RxQueues), + TxQueues: uint8(memIface.TxQueues), + } + if memIface.Master { + req.Role = 0 + } else { + req.Role = 1 + } + // TODO: temporary fix, waiting for https://gerrit.fd.io/r/#/c/7266/ + if req.RxQueues == 0 { + req.RxQueues = 1 + } + if req.TxQueues == 0 { + req.TxQueues = 1 + } + + reply, err := h.memif.MemifCreate(ctx, req) + if err != nil { + return 0, err + } else if err = api.RetvalToVPPApiError(reply.Retval); err != nil { + return 0, err + } + swIdx = uint32(reply.SwIfIndex) + + return swIdx, h.SetInterfaceTag(ifName, swIdx) +} + +func (h *InterfaceVppHandler) DeleteMemifInterface(ctx context.Context, ifName string, idx uint32) error { + if h.memif == nil { + return errors.WithMessage(vpp.ErrPluginDisabled, "memif") + } + + req := &vpp_memif.MemifDelete{ + SwIfIndex: interface_types.InterfaceIndex(idx), + } + if reply, err := h.memif.MemifDelete(ctx, req); err != nil { + return err + } else if err = api.RetvalToVPPApiError(reply.Retval); err != nil { + return err + } + + return h.RemoveInterfaceTag(ifName, idx) +} + +func (h *InterfaceVppHandler) RegisterMemifSocketFilename(ctx context.Context, filename string, id uint32) error { + if h.memif == nil { + return errors.WithMessage(vpp.ErrPluginDisabled, "memif") + } + + req := &vpp_memif.MemifSocketFilenameAddDel{ + SocketFilename: filename, + SocketID: id, + IsAdd: true, // sockets can be added only + } + if reply, err := h.memif.MemifSocketFilenameAddDel(ctx, req); err != nil { + return err + } else if err = api.RetvalToVPPApiError(reply.Retval); err != nil { + return err + } + return nil +} + +func memifMode(mode ifs.MemifLink_MemifMode) vpp_memif.MemifMode { + switch mode { + case ifs.MemifLink_IP: + return vpp_memif.MEMIF_MODE_API_IP + case ifs.MemifLink_PUNT_INJECT: + return vpp_memif.MEMIF_MODE_API_PUNT_INJECT + default: + return vpp_memif.MEMIF_MODE_API_ETHERNET + } +} + +func (h *InterfaceVppHandler) DumpMemifSocketDetails(ctx context.Context) (map[string]uint32, error) { + if h.memif == nil { + return nil, errors.WithMessage(vpp.ErrPluginDisabled, "memif") + } + + dump, err := h.memif.MemifSocketFilenameDump(ctx, &vpp_memif.MemifSocketFilenameDump{}) + if err != nil { + return nil, err + } + memifSocketMap := make(map[string]uint32) + for { + socketDetails, err := dump.Recv() + if err == io.EOF { + break + } + if err != nil { + return nil, err + } + + filename := strings.SplitN(socketDetails.SocketFilename, "\x00", 2)[0] + memifSocketMap[filename] = socketDetails.SocketID + } + + h.log.Debugf("Memif socket dump completed, found %d entries: %v", len(memifSocketMap), memifSocketMap) + + return memifSocketMap, nil +} + +// dumpMemifDetails dumps memif interface details from VPP and fills them into the provided interface map. +func (h *InterfaceVppHandler) dumpMemifDetails(ctx context.Context, interfaces map[uint32]*vppcalls.InterfaceDetails) error { + if h.memif == nil { + // no-op when disabled + return nil + } + + memifSocketMap, err := h.DumpMemifSocketDetails(ctx) + if err != nil { + return fmt.Errorf("dumping memif socket details failed: %v", err) + } + + dump, err := h.memif.MemifDump(ctx, &vpp_memif.MemifDump{}) + if err != nil { + return err + } + for { + memifDetails, err := dump.Recv() + if err == io.EOF { + break + } + if err != nil { + return err + } + + _, ifIdxExists := interfaces[uint32(memifDetails.SwIfIndex)] + if !ifIdxExists { + continue + } + interfaces[uint32(memifDetails.SwIfIndex)].Interface.Link = &ifs.Interface_Memif{ + Memif: &ifs.MemifLink{ + Master: memifDetails.Role == 0, + Mode: memifModetoNB(memifDetails.Mode), + Id: memifDetails.ID, + // Secret: // TODO: Secret - not available in the binary API + SocketFilename: func(socketMap map[string]uint32) (filename string) { + for filename, id := range socketMap { + if memifDetails.SocketID == id { + return filename + } + } + // Socket for configured memif should exist + h.log.Warnf("Socket ID not found for memif %v", memifDetails.SwIfIndex) + return + }(memifSocketMap), + RingSize: memifDetails.RingSize, + BufferSize: uint32(memifDetails.BufferSize), + // TODO: RxQueues, TxQueues - not available in the binary API + // RxQueues: + // TxQueues: + }, + } + interfaces[uint32(memifDetails.SwIfIndex)].Interface.Type = ifs.Interface_MEMIF + } + + return nil +} diff --git a/plugins/vpp/ifplugin/vppcalls/vpp2306/memif_vppcalls_test.go b/plugins/vpp/ifplugin/vppcalls/vpp2306/memif_vppcalls_test.go new file mode 100644 index 0000000000..1f7cb4c4f5 --- /dev/null +++ b/plugins/vpp/ifplugin/vppcalls/vpp2306/memif_vppcalls_test.go @@ -0,0 +1,202 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306_test + +import ( + "testing" + + . "github.com/onsi/gomega" + + vpp_ifs "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface" + vpp_memif "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/memif" + ifs "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/interfaces" +) + +func TestAddMasterMemifInterface(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_memif.MemifCreateReply{ + SwIfIndex: 1, + }) + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceTagAddDelReply{}) + + swIfIdx, err := ifHandler.AddMemifInterface(ctx.Context, "memif", &ifs.MemifLink{ + Id: 1, + Mode: ifs.MemifLink_IP, + Secret: "secret", + Master: true, + }, 5) + + Expect(err).To(BeNil()) + Expect(swIfIdx).To(BeEquivalentTo(1)) + var msgCheck bool + for _, msg := range ctx.MockChannel.Msgs { + vppMsg, ok := msg.(*vpp_memif.MemifCreate) + if ok { + Expect(vppMsg.ID).To(BeEquivalentTo(1)) + Expect(vppMsg.Mode).To(BeEquivalentTo(1)) + Expect(vppMsg.Role).To(BeEquivalentTo(0)) + Expect(vppMsg.SocketID).To(BeEquivalentTo(5)) + Expect(vppMsg.RxQueues).To(BeEquivalentTo(1)) + Expect(vppMsg.TxQueues).To(BeEquivalentTo(1)) + msgCheck = true + } + } + Expect(msgCheck).To(BeTrue()) +} + +func TestAddMasterMemifInterfaceAsSlave(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_memif.MemifCreateReply{ + SwIfIndex: 1, + }) + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceTagAddDelReply{}) + + swIfIdx, err := ifHandler.AddMemifInterface(ctx.Context, "memif", &ifs.MemifLink{ + Id: 1, + Mode: ifs.MemifLink_IP, + Secret: "secret", + Master: false, + }, 5) + + Expect(err).To(BeNil()) + Expect(swIfIdx).To(BeEquivalentTo(1)) + var msgCheck bool + for _, msg := range ctx.MockChannel.Msgs { + vppMsg, ok := msg.(*vpp_memif.MemifCreate) + if ok { + Expect(vppMsg.Role).To(BeEquivalentTo(1)) + msgCheck = true + } + } + Expect(msgCheck).To(BeTrue()) +} + +func TestAddMasterMemifInterfaceError(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_memif.MemifCreate{}) + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceTagAddDelReply{}) + + _, err := ifHandler.AddMemifInterface(ctx.Context, "memif", &ifs.MemifLink{ + Id: 1, + Mode: ifs.MemifLink_IP, + Secret: "secret", + Master: false, + }, 5) + + Expect(err).ToNot(BeNil()) +} + +func TestAddMasterMemifInterfaceRetval(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_memif.MemifCreateReply{ + Retval: 1, + }) + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceTagAddDelReply{}) + + _, err := ifHandler.AddMemifInterface(ctx.Context, "memif", &ifs.MemifLink{ + Id: 1, + Mode: ifs.MemifLink_IP, + Secret: "secret", + Master: false, + }, 5) + + Expect(err).ToNot(BeNil()) +} + +func TestDeleteMemifInterface(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_memif.MemifDeleteReply{}) + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceTagAddDelReply{}) + + err := ifHandler.DeleteMemifInterface(ctx.Context, "memif", 1) + + Expect(err).To(BeNil()) +} + +func TestDeleteMemifInterfaceError(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_memif.MemifDelete{}) + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceTagAddDelReply{}) + + err := ifHandler.DeleteMemifInterface(ctx.Context, "memif", 1) + + Expect(err).ToNot(BeNil()) +} + +func TestDeleteMemifInterfaceRetval(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_memif.MemifDeleteReply{ + Retval: 1, + }) + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceTagAddDelReply{}) + + err := ifHandler.DeleteMemifInterface(ctx.Context, "memif", 1) + + Expect(err).ToNot(BeNil()) +} + +func TestRegisterMemifSocketFilename(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_memif.MemifSocketFilenameAddDelReply{}) + + err := ifHandler.RegisterMemifSocketFilename(ctx.Context, "filename", 1) + + Expect(err).To(BeNil()) + vppMsg, ok := ctx.MockChannel.Msg.(*vpp_memif.MemifSocketFilenameAddDel) + Expect(ok).To(BeTrue()) + Expect(vppMsg.IsAdd).To(BeTrue()) + Expect(vppMsg.SocketID).To(BeEquivalentTo(1)) + Expect(vppMsg.SocketFilename).To(BeEquivalentTo([]byte("filename"))) +} + +func TestRegisterMemifSocketFilenameError(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_memif.MemifSocketFilenameAddDel{}) + + err := ifHandler.RegisterMemifSocketFilename(ctx.Context, "filename", 1) + + Expect(err).ToNot(BeNil()) +} + +func TestRegisterMemifSocketFilenameRetval(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_memif.MemifSocketFilenameAddDelReply{ + Retval: 1, + }) + + err := ifHandler.RegisterMemifSocketFilename(ctx.Context, "filename", 1) + + Expect(err).ToNot(BeNil()) +} diff --git a/plugins/vpp/ifplugin/vppcalls/vpp2306/mtu_vppcalls.go b/plugins/vpp/ifplugin/vppcalls/vpp2306/mtu_vppcalls.go new file mode 100644 index 0000000000..9dfe3c3cb3 --- /dev/null +++ b/plugins/vpp/ifplugin/vppcalls/vpp2306/mtu_vppcalls.go @@ -0,0 +1,34 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + vpp_ifs "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface_types" +) + +func (h *InterfaceVppHandler) SetInterfaceMtu(ifIdx uint32, mtu uint32) error { + req := &vpp_ifs.HwInterfaceSetMtu{ + SwIfIndex: interface_types.InterfaceIndex(ifIdx), + Mtu: uint16(mtu), + } + reply := &vpp_ifs.HwInterfaceSetMtuReply{} + + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + + return nil +} diff --git a/plugins/vpp/ifplugin/vppcalls/vpp2306/mtu_vppcalls_test.go b/plugins/vpp/ifplugin/vppcalls/vpp2306/mtu_vppcalls_test.go new file mode 100644 index 0000000000..4cc8fccd8d --- /dev/null +++ b/plugins/vpp/ifplugin/vppcalls/vpp2306/mtu_vppcalls_test.go @@ -0,0 +1,62 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306_test + +import ( + "testing" + + . "github.com/onsi/gomega" + + vpp_ifs "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface" +) + +func TestSetInterfaceMtu(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ifs.HwInterfaceSetMtuReply{}) + + err := ifHandler.SetInterfaceMtu(1, 1500) + + Expect(err).To(BeNil()) + vppMsg, ok := ctx.MockChannel.Msg.(*vpp_ifs.HwInterfaceSetMtu) + Expect(ok).To(BeTrue()) + Expect(vppMsg.SwIfIndex).To(BeEquivalentTo(1)) + Expect(vppMsg.Mtu).To(BeEquivalentTo(1500)) +} + +func TestSetInterfaceMtuError(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ifs.HwInterfaceSetMtu{}) + + err := ifHandler.SetInterfaceMtu(1, 1500) + + Expect(err).ToNot(BeNil()) +} + +func TestSetInterfaceMtuRetval(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ifs.HwInterfaceSetMtuReply{ + Retval: 1, + }) + + err := ifHandler.SetInterfaceMtu(1, 1500) + + Expect(err).ToNot(BeNil()) +} diff --git a/plugins/vpp/ifplugin/vppcalls/vpp2306/rdma_vppcalls.go b/plugins/vpp/ifplugin/vppcalls/vpp2306/rdma_vppcalls.go new file mode 100644 index 0000000000..be06919c41 --- /dev/null +++ b/plugins/vpp/ifplugin/vppcalls/vpp2306/rdma_vppcalls.go @@ -0,0 +1,80 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "context" + + "github.com/pkg/errors" + + "go.ligato.io/vpp-agent/v3/plugins/vpp" + interfaces "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/interfaces" + + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface_types" + vpp_rdma "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/rdma" +) + +// AddRdmaInterface adds new interface with RDMA driver. +func (h *InterfaceVppHandler) AddRdmaInterface(ctx context.Context, ifName string, rdmaLink *interfaces.RDMALink) (swIdx uint32, err error) { + if h.rdma == nil { + return 0, errors.WithMessage(vpp.ErrPluginDisabled, "rdma") + } + + req := &vpp_rdma.RdmaCreateV3{ + HostIf: rdmaLink.GetHostIfName(), + Name: ifName, + RxqNum: uint16(rdmaLink.GetRxqNum()), + RxqSize: uint16(rdmaLink.GetRxqSize()), + TxqSize: uint16(rdmaLink.GetTxqSize()), + Mode: rdmaMode(rdmaLink.GetMode()), + } + + reply := &vpp_rdma.RdmaCreateV3Reply{} + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return 0, err + } + swIdx = uint32(reply.SwIfIndex) + + return swIdx, h.SetInterfaceTag(ifName, swIdx) +} + +// DeleteRdmaInterface removes interface with RDMA driver. +func (h *InterfaceVppHandler) DeleteRdmaInterface(ctx context.Context, ifName string, ifIdx uint32) error { + if h.rdma == nil { + return errors.WithMessage(vpp.ErrPluginDisabled, "rdma") + } + + req := &vpp_rdma.RdmaDelete{ + SwIfIndex: interface_types.InterfaceIndex(ifIdx), + } + + reply := &vpp_rdma.RdmaDeleteReply{} + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + + return h.RemoveInterfaceTag(ifName, ifIdx) +} + +func rdmaMode(mode interfaces.RDMALink_Mode) vpp_rdma.RdmaMode { + switch mode { + case interfaces.RDMALink_DV: + return vpp_rdma.RDMA_API_MODE_DV + case interfaces.RDMALink_IBV: + return vpp_rdma.RDMA_API_MODE_IBV + default: + return vpp_rdma.RDMA_API_MODE_AUTO + } +} diff --git a/plugins/vpp/ifplugin/vppcalls/vpp2306/rdma_vppcalls_test.go b/plugins/vpp/ifplugin/vppcalls/vpp2306/rdma_vppcalls_test.go new file mode 100644 index 0000000000..bdec59016b --- /dev/null +++ b/plugins/vpp/ifplugin/vppcalls/vpp2306/rdma_vppcalls_test.go @@ -0,0 +1,84 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306_test + +import ( + "testing" + + . "github.com/onsi/gomega" + + vpp_ifs "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface" + vpp_rdma "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/rdma" + + ifs "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/interfaces" +) + +func TestAddRdmaInterface(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_rdma.RdmaCreateV3Reply{ + SwIfIndex: 2, + }) + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceTagAddDelReply{}) + + const qSize = 4096 + const qNum = 4 + rdmaLink := &ifs.RDMALink{ + HostIfName: "ens4", + Mode: ifs.RDMALink_DV, + RxqNum: qNum, + RxqSize: qSize, + TxqSize: qSize, + } + + index, err := ifHandler.AddRdmaInterface(ctx.Context, "rdma1", rdmaLink) + Expect(err).To(BeNil()) + Expect(index).To(Equal(uint32(2))) + var msgCheck bool + for _, msg := range ctx.MockChannel.Msgs { + vppMsg, ok := msg.(*vpp_rdma.RdmaCreateV3) + if ok { + Expect(vppMsg.HostIf).To(BeEquivalentTo("ens4")) + Expect(vppMsg.Name).To(BeEquivalentTo("rdma1")) + Expect(vppMsg.Mode).To(BeEquivalentTo(vpp_rdma.RDMA_API_MODE_DV)) + Expect(vppMsg.RxqNum).To(BeEquivalentTo(qNum)) + Expect(vppMsg.RxqSize).To(BeEquivalentTo(qSize)) + Expect(vppMsg.TxqSize).To(BeEquivalentTo(qSize)) + msgCheck = true + } + } + Expect(msgCheck).To(BeTrue()) +} + +func TestDeleteRdmaInterface(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + ctx.MockVpp.MockReply(&vpp_rdma.RdmaDeleteReply{}) + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceTagAddDelReply{}) + + err := ifHandler.DeleteRdmaInterface(ctx.Context, "rdma1", 2) + Expect(err).To(BeNil()) + + var msgCheck bool + for _, msg := range ctx.MockChannel.Msgs { + vppMsg, ok := msg.(*vpp_rdma.RdmaDelete) + if ok { + Expect(vppMsg.SwIfIndex).To(BeEquivalentTo(2)) + msgCheck = true + } + } + Expect(msgCheck).To(BeTrue()) +} diff --git a/plugins/vpp/ifplugin/vppcalls/vpp2306/rx_mode_vppcalls.go b/plugins/vpp/ifplugin/vppcalls/vpp2306/rx_mode_vppcalls.go new file mode 100644 index 0000000000..6264cd5e01 --- /dev/null +++ b/plugins/vpp/ifplugin/vppcalls/vpp2306/rx_mode_vppcalls.go @@ -0,0 +1,53 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + vpp_ifs "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface_types" + ifs "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/interfaces" +) + +func (h *InterfaceVppHandler) SetRxMode(ifIdx uint32, rxMode *ifs.Interface_RxMode) error { + + req := &vpp_ifs.SwInterfaceSetRxMode{ + SwIfIndex: interface_types.InterfaceIndex(ifIdx), + Mode: setRxMode(rxMode.Mode), + QueueID: rxMode.Queue, + QueueIDValid: !rxMode.DefaultMode, + } + reply := &vpp_ifs.SwInterfaceSetRxModeReply{} + + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + + return nil +} + +func setRxMode(mode ifs.Interface_RxMode_Type) interface_types.RxMode { + switch mode { + case ifs.Interface_RxMode_POLLING: + return interface_types.RX_MODE_API_POLLING + case ifs.Interface_RxMode_INTERRUPT: + return interface_types.RX_MODE_API_INTERRUPT + case ifs.Interface_RxMode_ADAPTIVE: + return interface_types.RX_MODE_API_ADAPTIVE + case ifs.Interface_RxMode_DEFAULT: + return interface_types.RX_MODE_API_DEFAULT + default: + return interface_types.RX_MODE_API_UNKNOWN + } +} diff --git a/plugins/vpp/ifplugin/vppcalls/vpp2306/rx_mode_vppcalls_test.go b/plugins/vpp/ifplugin/vppcalls/vpp2306/rx_mode_vppcalls_test.go new file mode 100644 index 0000000000..d5f172d380 --- /dev/null +++ b/plugins/vpp/ifplugin/vppcalls/vpp2306/rx_mode_vppcalls_test.go @@ -0,0 +1,98 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306_test + +import ( + "testing" + + . "github.com/onsi/gomega" + + vpp_ifs "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface" + ifs "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/interfaces" +) + +func TestSetRxMode(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceSetRxModeReply{}) + + err := ifHandler.SetRxMode(1, &ifs.Interface_RxMode{ + Mode: ifs.Interface_RxMode_DEFAULT, + Queue: 1, + DefaultMode: false, + }) + + Expect(err).To(BeNil()) + vppMsg, ok := ctx.MockChannel.Msg.(*vpp_ifs.SwInterfaceSetRxMode) + Expect(ok).To(BeTrue()) + Expect(vppMsg.SwIfIndex).To(BeEquivalentTo(1)) + Expect(vppMsg.Mode).To(BeEquivalentTo(4)) + Expect(vppMsg.QueueID).To(BeEquivalentTo(1)) + Expect(vppMsg.QueueIDValid).To(BeTrue()) +} + +func TestSetRxModeError(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceSetRxMode{}) + + err := ifHandler.SetRxMode(1, &ifs.Interface_RxMode{ + Mode: ifs.Interface_RxMode_DEFAULT, + Queue: 1, + DefaultMode: false, + }) + + Expect(err).ToNot(BeNil()) +} + +func TestSetRxModeRetval(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceSetRxModeReply{ + Retval: 1, + }) + + err := ifHandler.SetRxMode(1, &ifs.Interface_RxMode{ + Mode: ifs.Interface_RxMode_DEFAULT, + Queue: 1, + DefaultMode: false, + }) + + Expect(err).ToNot(BeNil()) +} + +func TestSetDefaultRxMode(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceSetRxModeReply{}) + + err := ifHandler.SetRxMode(5, &ifs.Interface_RxMode{ + Mode: ifs.Interface_RxMode_POLLING, + Queue: 10, // ignored on the VPP side + DefaultMode: true, + }) + + Expect(err).To(BeNil()) + vppMsg, ok := ctx.MockChannel.Msg.(*vpp_ifs.SwInterfaceSetRxMode) + Expect(ok).To(BeTrue()) + Expect(vppMsg.SwIfIndex).To(BeEquivalentTo(5)) + Expect(vppMsg.Mode).To(BeEquivalentTo(1)) + Expect(vppMsg.QueueID).To(BeEquivalentTo(10)) + Expect(vppMsg.QueueIDValid).To(BeFalse()) +} diff --git a/plugins/vpp/ifplugin/vppcalls/vpp2306/rx_placement_vppcalls.go b/plugins/vpp/ifplugin/vppcalls/vpp2306/rx_placement_vppcalls.go new file mode 100644 index 0000000000..9cfd28b85e --- /dev/null +++ b/plugins/vpp/ifplugin/vppcalls/vpp2306/rx_placement_vppcalls.go @@ -0,0 +1,37 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + vpp_ifs "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface_types" + ifs "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/interfaces" +) + +func (h *InterfaceVppHandler) SetRxPlacement(ifIdx uint32, rxPlacement *ifs.Interface_RxPlacement) error { + req := &vpp_ifs.SwInterfaceSetRxPlacement{ + SwIfIndex: interface_types.InterfaceIndex(ifIdx), + QueueID: rxPlacement.Queue, + WorkerID: rxPlacement.Worker, + IsMain: rxPlacement.MainThread, + } + reply := &vpp_ifs.SwInterfaceSetRxPlacementReply{} + + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + + return nil +} diff --git a/plugins/vpp/ifplugin/vppcalls/vpp2306/rx_placement_vppcalls_test.go b/plugins/vpp/ifplugin/vppcalls/vpp2306/rx_placement_vppcalls_test.go new file mode 100644 index 0000000000..b46c6def5e --- /dev/null +++ b/plugins/vpp/ifplugin/vppcalls/vpp2306/rx_placement_vppcalls_test.go @@ -0,0 +1,98 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306_test + +import ( + "testing" + + . "github.com/onsi/gomega" + + vpp_ifs "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface" + ifs "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/interfaces" +) + +func TestSetRxPlacementForWorker(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceSetRxPlacementReply{}) + + err := ifHandler.SetRxPlacement(1, &ifs.Interface_RxPlacement{ + Queue: 1, + Worker: 2, + MainThread: false, + }) + + Expect(err).To(BeNil()) + vppMsg, ok := ctx.MockChannel.Msg.(*vpp_ifs.SwInterfaceSetRxPlacement) + Expect(ok).To(BeTrue()) + Expect(vppMsg.SwIfIndex).To(BeEquivalentTo(1)) + Expect(vppMsg.QueueID).To(BeEquivalentTo(1)) + Expect(vppMsg.WorkerID).To(BeEquivalentTo(uint32(2))) + Expect(vppMsg.IsMain).To(BeFalse()) +} + +func TestSetRxPlacementForMainThread(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceSetRxPlacementReply{}) + + err := ifHandler.SetRxPlacement(3, &ifs.Interface_RxPlacement{ + Queue: 6, + Worker: 2, // ignored on the VPP side + MainThread: true, + }) + + Expect(err).To(BeNil()) + vppMsg, ok := ctx.MockChannel.Msg.(*vpp_ifs.SwInterfaceSetRxPlacement) + Expect(ok).To(BeTrue()) + Expect(vppMsg.SwIfIndex).To(BeEquivalentTo(3)) + Expect(vppMsg.QueueID).To(BeEquivalentTo(6)) + Expect(vppMsg.WorkerID).To(BeEquivalentTo(uint32(2))) + Expect(vppMsg.IsMain).To(BeTrue()) +} + +func TestSetRxPlacementRetval(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceSetRxPlacementReply{ + Retval: 1, + }) + + err := ifHandler.SetRxPlacement(1, &ifs.Interface_RxPlacement{ + Queue: 1, + Worker: 2, + MainThread: false, + }) + + Expect(err).ToNot(BeNil()) +} + +func TestSetRxPlacementError(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceSetRxPlacement{}) + + err := ifHandler.SetRxPlacement(1, &ifs.Interface_RxPlacement{ + Queue: 1, + Worker: 2, + MainThread: false, + }) + + Expect(err).ToNot(BeNil()) +} diff --git a/plugins/vpp/ifplugin/vppcalls/vpp2306/span_vppcalls.go b/plugins/vpp/ifplugin/vppcalls/vpp2306/span_vppcalls.go new file mode 100644 index 0000000000..3ffe019bea --- /dev/null +++ b/plugins/vpp/ifplugin/vppcalls/vpp2306/span_vppcalls.go @@ -0,0 +1,95 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "fmt" + + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface_types" + vpp_span "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/span" + "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/vppcalls" +) + +// SetSpan enables or disables SPAN on interface +func (h *InterfaceVppHandler) setSpan(ifIdxFrom, ifIdxTo uint32, state uint8, isL2 bool) error { + req := &vpp_span.SwInterfaceSpanEnableDisable{ + SwIfIndexFrom: interface_types.InterfaceIndex(ifIdxFrom), + SwIfIndexTo: interface_types.InterfaceIndex(ifIdxTo), + State: vpp_span.SpanState(state), + IsL2: isL2, + } + reply := &vpp_span.SwInterfaceSpanEnableDisableReply{} + + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + + return nil +} + +// AddSpan enables SPAN on interface +func (h *InterfaceVppHandler) AddSpan(ifIdxFrom, ifIdxTo uint32, direction uint8, isL2 bool) error { + return h.setSpan(ifIdxFrom, ifIdxTo, direction, isL2) +} + +// DelSpan disables SPAN on interface +func (h *InterfaceVppHandler) DelSpan(ifIdxFrom, ifIdxTo uint32, isL2 bool) error { + return h.setSpan(ifIdxFrom, ifIdxTo, 0, isL2) +} + +// DumpSpan dumps all SPAN table +func (h *InterfaceVppHandler) DumpSpan() ([]*vppcalls.InterfaceSpanDetails, error) { + var spans []*vppcalls.InterfaceSpanDetails + + isL2Spans, err := h.dumpSpan(&vpp_span.SwInterfaceSpanDump{IsL2: true}) + if err != nil { + return nil, err + } + spans = append(spans, isL2Spans...) + + isNotL2Spans, err := h.dumpSpan(&vpp_span.SwInterfaceSpanDump{IsL2: false}) + if err != nil { + return nil, err + } + spans = append(spans, isNotL2Spans...) + + return spans, nil +} + +// dumpIsL2Span returns only SPANs with or without L2 set +func (h *InterfaceVppHandler) dumpSpan(msg *vpp_span.SwInterfaceSpanDump) ([]*vppcalls.InterfaceSpanDetails, error) { + var spans []*vppcalls.InterfaceSpanDetails + + reqCtx := h.callsChannel.SendMultiRequest(msg) + for { + spanDetails := &vpp_span.SwInterfaceSpanDetails{} + stop, err := reqCtx.ReceiveReply(spanDetails) + if stop { + break + } + if err != nil { + return nil, fmt.Errorf("failed to dump span: %v", err) + } + + spanData := &vppcalls.InterfaceSpanDetails{ + SwIfIndexFrom: uint32(spanDetails.SwIfIndexFrom), + SwIfIndexTo: uint32(spanDetails.SwIfIndexTo), + Direction: uint8(spanDetails.State), + IsL2: spanDetails.IsL2, + } + spans = append(spans, spanData) + } + return spans, nil +} diff --git a/plugins/vpp/ifplugin/vppcalls/vpp2306/subif_vppcalls.go b/plugins/vpp/ifplugin/vppcalls/vpp2306/subif_vppcalls.go new file mode 100644 index 0000000000..3b8d2a7cce --- /dev/null +++ b/plugins/vpp/ifplugin/vppcalls/vpp2306/subif_vppcalls.go @@ -0,0 +1,49 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + vpp_ifs "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface_types" +) + +// CreateSubif creates sub interface. +func (h *InterfaceVppHandler) CreateSubif(ifIdx, vlanID uint32) (uint32, error) { + req := &vpp_ifs.CreateVlanSubif{ + SwIfIndex: interface_types.InterfaceIndex(ifIdx), + VlanID: vlanID, + } + + reply := &vpp_ifs.CreateVlanSubifReply{} + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return 0, err + } + + return uint32(reply.SwIfIndex), nil +} + +// DeleteSubif deletes sub interface. +func (h *InterfaceVppHandler) DeleteSubif(ifIdx uint32) error { + req := &vpp_ifs.DeleteSubif{ + SwIfIndex: interface_types.InterfaceIndex(ifIdx), + } + + reply := &vpp_ifs.DeleteSubifReply{} + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + + return nil +} diff --git a/plugins/vpp/ifplugin/vppcalls/vpp2306/subif_vppcalls_test.go b/plugins/vpp/ifplugin/vppcalls/vpp2306/subif_vppcalls_test.go new file mode 100644 index 0000000000..42e1c847a6 --- /dev/null +++ b/plugins/vpp/ifplugin/vppcalls/vpp2306/subif_vppcalls_test.go @@ -0,0 +1,76 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306_test + +import ( + "testing" + + . "github.com/onsi/gomega" + + vpp_ifs "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface_types" +) + +func TestCreateSubif(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + ctx.MockVpp.MockReply(&vpp_ifs.CreateVlanSubifReply{ + SwIfIndex: 2, + }) + swifindex, err := ifHandler.CreateSubif(5, 32) + Expect(err).To(BeNil()) + Expect(swifindex).To(Equal(uint32(2))) + vppMsg, ok := ctx.MockChannel.Msg.(*vpp_ifs.CreateVlanSubif) + Expect(ok).To(BeTrue()) + Expect(vppMsg).ToNot(BeNil()) + Expect(vppMsg.SwIfIndex).To(Equal(interface_types.InterfaceIndex(5))) + Expect(vppMsg.VlanID).To(Equal(uint32(32))) +} + +func TestCreateSubifError(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + ctx.MockVpp.MockReply(&vpp_ifs.CreateVlanSubifReply{ + SwIfIndex: 2, + Retval: 9, + }) + swifindex, err := ifHandler.CreateSubif(5, 32) + Expect(err).ToNot(BeNil()) + Expect(swifindex).To(Equal(uint32(0))) +} + +func TestDeleteSubif(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + ctx.MockVpp.MockReply(&vpp_ifs.DeleteSubifReply{ + Retval: 2, + }) + err := ifHandler.DeleteSubif(5) + Expect(err).ToNot(BeNil()) + vppMsg, ok := ctx.MockChannel.Msg.(*vpp_ifs.DeleteSubif) + Expect(ok).To(BeTrue()) + Expect(vppMsg).ToNot(BeNil()) + Expect(vppMsg.SwIfIndex).To(Equal(interface_types.InterfaceIndex(5))) +} + +func TestDeleteSubifError(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + ctx.MockVpp.MockReply(&vpp_ifs.DeleteSubifReply{ + Retval: 2, + }) + err := ifHandler.DeleteSubif(5) + Expect(err).ToNot(BeNil()) +} diff --git a/plugins/vpp/ifplugin/vppcalls/vpp2306/tap_vppcalls.go b/plugins/vpp/ifplugin/vppcalls/vpp2306/tap_vppcalls.go new file mode 100644 index 0000000000..0787577a16 --- /dev/null +++ b/plugins/vpp/ifplugin/vppcalls/vpp2306/tap_vppcalls.go @@ -0,0 +1,83 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "errors" + "fmt" + + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface_types" + vpp_tapv2 "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/tapv2" + ifs "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/interfaces" +) + +func (h *InterfaceVppHandler) AddTapInterface(ifName string, tapIf *ifs.TapLink) (swIfIdx uint32, err error) { + if tapIf == nil || tapIf.HostIfName == "" { + return 0, errors.New("host interface name was not provided for the TAP interface") + } + + if tapIf.Version == 1 { + return 0, errors.New("tap version 1 has been deprecated") + } else if tapIf.Version == 2 { + var flags vpp_tapv2.TapFlags + if tapIf.EnableGso { + flags |= vpp_tapv2.TAP_API_FLAG_GSO + } + if tapIf.EnableTunnel { + flags |= vpp_tapv2.TAP_API_FLAG_TUN + } + + // Configure fast virtio-based TAP interface + req := &vpp_tapv2.TapCreateV2{ + ID: ^uint32(0), + NumRxQueues: 1, + HostIfName: tapIf.HostIfName, + HostIfNameSet: true, + UseRandomMac: true, + RxRingSz: uint16(tapIf.RxRingSize), + TxRingSz: uint16(tapIf.TxRingSize), + TapFlags: flags, + } + + reply := &vpp_tapv2.TapCreateV2Reply{} + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return 0, err + } + swIfIdx = uint32(reply.SwIfIndex) + } else { + return 0, fmt.Errorf("invalid tap version (%v)", tapIf.Version) + } + + return swIfIdx, h.SetInterfaceTag(ifName, swIfIdx) +} + +func (h *InterfaceVppHandler) DeleteTapInterface(ifName string, idx uint32, version uint32) error { + if version == 1 { + return errors.New("tap version 1 has been deprecated") + } else if version == 2 { + req := &vpp_tapv2.TapDeleteV2{ + SwIfIndex: interface_types.InterfaceIndex(idx), + } + + reply := &vpp_tapv2.TapDeleteV2Reply{} + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + } else { + return fmt.Errorf("invalid tap version (%v)", version) + } + + return h.RemoveInterfaceTag(ifName, idx) +} diff --git a/plugins/vpp/ifplugin/vppcalls/vpp2306/tap_vppcalls_test.go b/plugins/vpp/ifplugin/vppcalls/vpp2306/tap_vppcalls_test.go new file mode 100644 index 0000000000..2e942aa533 --- /dev/null +++ b/plugins/vpp/ifplugin/vppcalls/vpp2306/tap_vppcalls_test.go @@ -0,0 +1,74 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306_test + +import ( + "testing" + + . "github.com/onsi/gomega" + + vpp_ifs "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface" + vpp_tapv2 "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/tapv2" + ifs "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/interfaces" +) + +func TestAddTapInterfaceV2(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_tapv2.TapCreateV2Reply{ + SwIfIndex: 1, + }) + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceTagAddDelReply{}) + + swIfIdx, err := ifHandler.AddTapInterface("tapIf", &ifs.TapLink{ + Version: 2, + HostIfName: "hostIf", + RxRingSize: 1, + TxRingSize: 1, + }) + Expect(err).To(BeNil()) + Expect(swIfIdx).To(BeEquivalentTo(1)) + var msgCheck bool + for _, msg := range ctx.MockChannel.Msgs { + vppMsg, ok := msg.(*vpp_tapv2.TapCreateV2) + if ok { + Expect(vppMsg.UseRandomMac).To(BeTrue()) + Expect(vppMsg.HostIfName).To(BeEquivalentTo([]byte("hostIf"))) + msgCheck = true + } + } + Expect(msgCheck).To(BeTrue()) +} + +func TestDeleteTapInterfaceV2(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_tapv2.TapDeleteV2Reply{}) + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceTagAddDelReply{}) + + err := ifHandler.DeleteTapInterface("tapIf", 1, 2) + Expect(err).To(BeNil()) + var msgCheck bool + for _, msg := range ctx.MockChannel.Msgs { + vppMsg, ok := msg.(*vpp_tapv2.TapDeleteV2) + if ok { + Expect(vppMsg.SwIfIndex).To(BeEquivalentTo(1)) + msgCheck = true + } + } + Expect(msgCheck).To(BeTrue()) +} diff --git a/plugins/vpp/ifplugin/vppcalls/vpp2306/vmxnet3_vppcalls.go b/plugins/vpp/ifplugin/vppcalls/vpp2306/vmxnet3_vppcalls.go new file mode 100644 index 0000000000..fe097fce0b --- /dev/null +++ b/plugins/vpp/ifplugin/vppcalls/vpp2306/vmxnet3_vppcalls.go @@ -0,0 +1,129 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "fmt" + + "github.com/pkg/errors" + + "go.ligato.io/vpp-agent/v3/plugins/vpp" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface_types" + vpp_vmxnet3 "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/vmxnet3" + "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/vppcalls" + ifs "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/interfaces" +) + +func (h *InterfaceVppHandler) AddVmxNet3(ifName string, vmxNet3 *ifs.VmxNet3Link) (swIdx uint32, err error) { + if h.vmxnet3 == nil { + return 0, errors.WithMessage(vpp.ErrPluginDisabled, "wmxnet") + } + + var pci uint32 + pci, err = derivePCI(ifName) + if err != nil { + return 0, err + } + + req := &vpp_vmxnet3.Vmxnet3Create{ + PciAddr: pci, + } + // Optional arguments + if vmxNet3 != nil { + req.EnableElog = int32(boolToUint(vmxNet3.EnableElog)) + req.RxqSize = uint16(vmxNet3.RxqSize) + req.TxqSize = uint16(vmxNet3.TxqSize) + } + + reply := &vpp_vmxnet3.Vmxnet3CreateReply{} + if err = h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return 0, errors.Errorf(err.Error()) + } + + return uint32(reply.SwIfIndex), h.SetInterfaceTag(ifName, uint32(reply.SwIfIndex)) +} + +func (h *InterfaceVppHandler) DeleteVmxNet3(ifName string, ifIdx uint32) error { + if h.vmxnet3 == nil { + return errors.WithMessage(vpp.ErrPluginDisabled, "wmxnet") + } + + req := &vpp_vmxnet3.Vmxnet3Delete{ + SwIfIndex: interface_types.InterfaceIndex(ifIdx), + } + reply := &vpp_vmxnet3.Vmxnet3DeleteReply{} + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return errors.Errorf(err.Error()) + } + + return h.RemoveInterfaceTag(ifName, ifIdx) +} + +func derivePCI(ifName string) (uint32, error) { + var function, slot, bus, domain, pci uint32 + + numLen, err := fmt.Sscanf(ifName, "vmxnet3-%x/%x/%x/%x", &domain, &bus, &slot, &function) + if err != nil { + err = errors.Errorf("cannot parse PCI address from the vmxnet3 interface name %s: %v", ifName, err) + return 0, err + } + if numLen != 4 { + err = errors.Errorf("cannot parse PCI address from the interface name %s: expected 4 address elements, received %d", + ifName, numLen) + return 0, err + } + + pci |= function << 29 + pci |= slot << 24 + pci |= bus << 16 + pci |= domain + + return pci, nil +} + +// dumpVmxNet3Details dumps VmxNet3 interface details from VPP and fills them into the provided interface map. +func (h *InterfaceVppHandler) dumpVmxNet3Details(interfaces map[uint32]*vppcalls.InterfaceDetails) error { + if h.vmxnet3 == nil { + // no-op when disabled + return nil + } + + // index of ^interface_types.InterfaceIndex(0) dumps all VmxNet3 details + reqCtx := h.callsChannel.SendMultiRequest(&vpp_vmxnet3.SwVmxnet3InterfaceDump{ + SwIfIndex: ^interface_types.InterfaceIndex(0)}) + for { + vmxnet3Details := &vpp_vmxnet3.SwVmxnet3InterfaceDetails{} + stop, err := reqCtx.ReceiveReply(vmxnet3Details) + if stop { + break // Break from the loop. + } + if err != nil { + return fmt.Errorf("failed to dump VmxNet3 tunnel interface details: %v", err) + } + _, ifIdxExists := interfaces[uint32(vmxnet3Details.SwIfIndex)] + if !ifIdxExists { + continue + } + interfaces[uint32(vmxnet3Details.SwIfIndex)].Interface.Link = &ifs.Interface_VmxNet3{ + VmxNet3: &ifs.VmxNet3Link{ + RxqSize: uint32(vmxnet3Details.RxCount), + TxqSize: uint32(vmxnet3Details.TxCount), + }, + } + interfaces[uint32(vmxnet3Details.SwIfIndex)].Interface.Type = ifs.Interface_VMXNET3_INTERFACE + interfaces[uint32(vmxnet3Details.SwIfIndex)].Meta.Pci = vmxnet3Details.PciAddr + } + return nil +} diff --git a/plugins/vpp/ifplugin/vppcalls/vpp2306/vmxnet3_vppcalls_test.go b/plugins/vpp/ifplugin/vppcalls/vpp2306/vmxnet3_vppcalls_test.go new file mode 100644 index 0000000000..e343abc932 --- /dev/null +++ b/plugins/vpp/ifplugin/vppcalls/vpp2306/vmxnet3_vppcalls_test.go @@ -0,0 +1,115 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306_test + +import ( + "testing" + + . "github.com/onsi/gomega" + + vpp_ifs "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface" + vpp_vmxnet3 "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/vmxnet3" + ifs "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/interfaces" +) + +func TestAddVmxNet3Interface(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_vmxnet3.Vmxnet3CreateReply{ + SwIfIndex: 1, + }) + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceTagAddDelReply{}) + + swIfIdx, err := ifHandler.AddVmxNet3("vmxnet3-face/be/1c/4", &ifs.VmxNet3Link{ + EnableElog: true, + RxqSize: 2048, + TxqSize: 512, + }) + Expect(err).To(BeNil()) + Expect(swIfIdx).To(BeEquivalentTo(1)) + var msgCheck bool + for _, msg := range ctx.MockChannel.Msgs { + vppMsg, ok := msg.(*vpp_vmxnet3.Vmxnet3Create) + if ok { + Expect(vppMsg.PciAddr).To(BeEquivalentTo(2629761742)) + Expect(vppMsg.EnableElog).To(BeEquivalentTo(1)) + Expect(vppMsg.RxqSize).To(BeEquivalentTo(2048)) + Expect(vppMsg.TxqSize).To(BeEquivalentTo(512)) + msgCheck = true + } + } + Expect(msgCheck).To(BeTrue()) +} + +func TestAddVmxNet3InterfacePCIErr(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_vmxnet3.Vmxnet3CreateReply{ + SwIfIndex: 1, + }) + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceTagAddDelReply{}) + + // Name in incorrect format + _, err := ifHandler.AddVmxNet3("vmxnet3-a/14/19", nil) + Expect(err).ToNot(BeNil()) +} + +func TestAddVmxNet3InterfaceRetval(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_vmxnet3.Vmxnet3CreateReply{ + Retval: 1, + }) + + _, err := ifHandler.AddVmxNet3("vmxnet3-a/14/19/1e", nil) + Expect(err).ToNot(BeNil()) +} + +func TestDelVmxNet3Interface(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_vmxnet3.Vmxnet3DeleteReply{ + Retval: 0, + }) + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceTagAddDelReply{}) + + err := ifHandler.DeleteVmxNet3("vmxnet3-a/14/19/1e", 1) + Expect(err).To(BeNil()) + var msgCheck bool + for _, msg := range ctx.MockChannel.Msgs { + vppMsg, ok := msg.(*vpp_vmxnet3.Vmxnet3Delete) + if ok { + Expect(vppMsg.SwIfIndex).To(BeEquivalentTo(1)) + msgCheck = true + } + } + Expect(msgCheck).To(BeTrue()) +} + +func TestDelVmxNet3InterfaceRetval(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_vmxnet3.Vmxnet3DeleteReply{ + Retval: 1, + }) + + err := ifHandler.DeleteVmxNet3("vmxnet3-a/14/19/1e", 1) + Expect(err).ToNot(BeNil()) +} diff --git a/plugins/vpp/ifplugin/vppcalls/vpp2306/vppcalls_handler.go b/plugins/vpp/ifplugin/vppcalls/vpp2306/vppcalls_handler.go new file mode 100644 index 0000000000..e5de6c0012 --- /dev/null +++ b/plugins/vpp/ifplugin/vppcalls/vpp2306/vppcalls_handler.go @@ -0,0 +1,131 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + govppapi "go.fd.io/govpp/api" + "go.ligato.io/cn-infra/v2/logging" + + "go.ligato.io/vpp-agent/v3/plugins/vpp" + vpp2306 "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/af_packet" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/bond" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/dhcp" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/gre" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/gtpu" + interfaces "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip6_nd" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ipsec" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/l2" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/memif" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/rd_cp" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/rdma" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/span" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/tapv2" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/vmxnet3" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/vxlan" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/wireguard" + "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/vppcalls" +) + +var HandlerVersion = vpp.HandlerVersion{ + Version: vpp2306.Version, + Check: func(c vpp.Client) error { + msgs := vpp.Messages( + af_packet.AllMessages, + bond.AllMessages, + dhcp.AllMessages, + interfaces.AllMessages, + ip.AllMessages, + ipsec.AllMessages, + gre.AllMessages, + l2.AllMessages, + span.AllMessages, + tapv2.AllMessages, + vxlan.AllMessages, + ) + if c.IsPluginLoaded(gtpu.APIFile) { + msgs.Add(gtpu.AllMessages) + } + if c.IsPluginLoaded(memif.APIFile) { + msgs.Add(memif.AllMessages) + } + if c.IsPluginLoaded(vmxnet3.APIFile) { + msgs.Add(vmxnet3.AllMessages) + } + if c.IsPluginLoaded(wireguard.APIFile) { + msgs.Add(wireguard.AllMessages) + } + if c.IsPluginLoaded(rdma.APIFile) { + msgs.Add(rdma.AllMessages) + } + return c.CheckCompatiblity(msgs.AllMessages()...) + }, + NewHandler: func(c vpp.Client, a ...interface{}) vpp.HandlerAPI { + return NewInterfaceVppHandler(c, a[0].(logging.Logger)) + }, +} + +func init() { + vppcalls.Handler.AddVersion(HandlerVersion) +} + +// InterfaceVppHandler is accessor for interface-related vppcalls methods +type InterfaceVppHandler struct { + callsChannel govppapi.Channel + interfaces interfaces.RPCService + ipsec ipsec.RPCService + gtpu gtpu.RPCService + memif memif.RPCService + vmxnet3 vmxnet3.RPCService + rpcIP6nd ip6_nd.RPCService + rpcRdCp rd_cp.RPCService + wireguard wireguard.RPCService + rdma rdma.RPCService + log logging.Logger +} + +// NewInterfaceVppHandler returns new InterfaceVppHandler. +func NewInterfaceVppHandler(c vpp.Client, log logging.Logger) vppcalls.InterfaceVppAPI { + ch, err := c.NewAPIChannel() + if err != nil { + return nil + } + h := &InterfaceVppHandler{ + callsChannel: ch, + interfaces: interfaces.NewServiceClient(c), + ipsec: ipsec.NewServiceClient(c), + rpcIP6nd: ip6_nd.NewServiceClient(c), + rpcRdCp: rd_cp.NewServiceClient(c), + log: log, + } + if c.IsPluginLoaded(gtpu.APIFile) { + h.gtpu = gtpu.NewServiceClient(c) + } + if c.IsPluginLoaded(memif.APIFile) { + h.memif = memif.NewServiceClient(c) + } + if c.IsPluginLoaded(vmxnet3.APIFile) { + h.vmxnet3 = vmxnet3.NewServiceClient(c) + } + if c.IsPluginLoaded(wireguard.APIFile) { + h.wireguard = wireguard.NewServiceClient(c) + } + if c.IsPluginLoaded(rdma.APIFile) { + h.rdma = rdma.NewServiceClient(c) + } + return h +} diff --git a/plugins/vpp/ifplugin/vppcalls/vpp2306/vrf_vppcalls.go b/plugins/vpp/ifplugin/vppcalls/vpp2306/vrf_vppcalls.go new file mode 100644 index 0000000000..39c6c52dde --- /dev/null +++ b/plugins/vpp/ifplugin/vppcalls/vpp2306/vrf_vppcalls.go @@ -0,0 +1,69 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + vpp_ifs "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface_types" +) + +func (h *InterfaceVppHandler) SetInterfaceVrf(ifIdx, vrfID uint32) error { + return h.setInterfaceVrf(ifIdx, vrfID, false) +} + +func (h *InterfaceVppHandler) SetInterfaceVrfIPv6(ifIdx, vrfID uint32) error { + return h.setInterfaceVrf(ifIdx, vrfID, true) +} + +func (h *InterfaceVppHandler) GetInterfaceVrf(ifIdx uint32) (vrfID uint32, err error) { + return h.getInterfaceVrf(ifIdx, false) +} + +func (h *InterfaceVppHandler) GetInterfaceVrfIPv6(ifIdx uint32) (vrfID uint32, err error) { + return h.getInterfaceVrf(ifIdx, true) +} + +// Interface is set to VRF table. Table IP version has to be defined. +func (h *InterfaceVppHandler) setInterfaceVrf(ifIdx, vrfID uint32, isIPv6 bool) error { + req := &vpp_ifs.SwInterfaceSetTable{ + SwIfIndex: interface_types.InterfaceIndex(ifIdx), + VrfID: vrfID, + IsIPv6: isIPv6, + } + reply := &vpp_ifs.SwInterfaceSetTableReply{} + + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + + h.log.Debugf("Interface %d set to VRF %d", ifIdx, vrfID) + + return nil +} + +// Returns VRF ID for provided interface. +func (h *InterfaceVppHandler) getInterfaceVrf(ifIdx uint32, isIPv6 bool) (vrfID uint32, err error) { + req := &vpp_ifs.SwInterfaceGetTable{ + SwIfIndex: interface_types.InterfaceIndex(ifIdx), + IsIPv6: isIPv6, + } + reply := &vpp_ifs.SwInterfaceGetTableReply{} + + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return 0, err + } + + return reply.VrfID, nil +} diff --git a/plugins/vpp/ifplugin/vppcalls/vpp2306/vrf_vppcalls_test.go b/plugins/vpp/ifplugin/vppcalls/vpp2306/vrf_vppcalls_test.go new file mode 100644 index 0000000000..f55e9b5a23 --- /dev/null +++ b/plugins/vpp/ifplugin/vppcalls/vpp2306/vrf_vppcalls_test.go @@ -0,0 +1,122 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306_test + +import ( + "testing" + + . "github.com/onsi/gomega" + + vpp_ifs "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface" +) + +func TestGetInterfaceVRF(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceGetTableReply{ + VrfID: 1, + }) + + vrfID, err := ifHandler.GetInterfaceVrf(1) + Expect(err).To(BeNil()) + Expect(vrfID).To(BeEquivalentTo(1)) +} + +func TestGetInterfaceIPv6VRF(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceGetTableReply{ + VrfID: 1, + }) + + vrfID, err := ifHandler.GetInterfaceVrfIPv6(1) + Expect(err).To(BeNil()) + Expect(vrfID).To(BeEquivalentTo(1)) +} + +func TestGetInterfaceVRFError(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceGetTable{}) + + _, err := ifHandler.GetInterfaceVrf(1) + Expect(err).ToNot(BeNil()) +} + +func TestGetInterfaceVRFRetval(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceGetTableReply{ + Retval: 1, + }) + + _, err := ifHandler.GetInterfaceVrf(1) + Expect(err).ToNot(BeNil()) +} + +func TestSetInterfaceVRF(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceSetTableReply{}) + + err := ifHandler.SetInterfaceVrf(1, 2) + Expect(err).To(BeNil()) + vppMsg, ok := ctx.MockChannel.Msg.(*vpp_ifs.SwInterfaceSetTable) + Expect(ok).To(BeTrue()) + Expect(vppMsg.SwIfIndex).To(BeEquivalentTo(1)) + Expect(vppMsg.VrfID).To(BeEquivalentTo(2)) +} + +func TestSetInterfaceIPv6VRF(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceSetTableReply{}) + + err := ifHandler.SetInterfaceVrfIPv6(1, 2) + Expect(err).To(BeNil()) + vppMsg, ok := ctx.MockChannel.Msg.(*vpp_ifs.SwInterfaceSetTable) + Expect(ok).To(BeTrue()) + Expect(vppMsg.SwIfIndex).To(BeEquivalentTo(1)) + Expect(vppMsg.VrfID).To(BeEquivalentTo(2)) + Expect(vppMsg.IsIPv6).To(BeTrue()) +} + +func TestSetInterfaceVRFError(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceSetTable{}) + + err := ifHandler.SetInterfaceVrf(1, 2) + Expect(err).To(HaveOccurred()) +} + +func TestSetInterfaceVRFRetval(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceSetTableReply{ + Retval: 1, + }) + + err := ifHandler.SetInterfaceVrf(1, 2) + Expect(err).ToNot(BeNil()) +} diff --git a/plugins/vpp/ifplugin/vppcalls/vpp2306/vxlan_gpe_vppcalls.go b/plugins/vpp/ifplugin/vppcalls/vpp2306/vxlan_gpe_vppcalls.go new file mode 100644 index 0000000000..7d5f437256 --- /dev/null +++ b/plugins/vpp/ifplugin/vppcalls/vpp2306/vxlan_gpe_vppcalls.go @@ -0,0 +1,79 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "fmt" + "net" + + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface_types" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip_types" + vpp_gpe "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/vxlan_gpe" + ifs "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/interfaces" +) + +func (h *InterfaceVppHandler) vxLanGpeAddDelTunnel(isAdd bool, vxLan *ifs.VxlanLink, vrf, multicastIf uint32) (uint32, error) { + req := &vpp_gpe.VxlanGpeAddDelTunnel{ + McastSwIfIndex: interface_types.InterfaceIndex(multicastIf), + EncapVrfID: vrf, + DecapVrfID: vxLan.Gpe.DecapVrfId, + Protocol: ip_types.IPProto(vxLan.Gpe.Protocol), + Vni: vxLan.Vni, + IsAdd: isAdd, + } + + if vxLan.SrcAddress == vxLan.DstAddress { + return 0, fmt.Errorf("source and destination addresses must not be the same") + } + srcAddr := net.ParseIP(vxLan.SrcAddress).To4() + dstAddr := net.ParseIP(vxLan.DstAddress).To4() + if srcAddr == nil && dstAddr == nil { + srcAddr = net.ParseIP(vxLan.SrcAddress).To16() + dstAddr = net.ParseIP(vxLan.DstAddress).To16() + if srcAddr == nil || dstAddr == nil { + return 0, fmt.Errorf("invalid VXLAN address, src: %s, dst: %s", srcAddr, dstAddr) + } + } else if srcAddr == nil && dstAddr != nil || srcAddr != nil && dstAddr == nil { + return 0, fmt.Errorf("IP version mismatch for VXLAN destination and source IP addresses") + } + + req.Local, _ = IPToAddress(srcAddr.String()) + req.Remote, _ = IPToAddress(dstAddr.String()) + + reply := &vpp_gpe.VxlanGpeAddDelTunnelReply{} + + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return 0, err + } + return uint32(reply.SwIfIndex), nil +} + +// AddVxLanGpeTunnel adds new VxLAN-GPE interface. +func (h *InterfaceVppHandler) AddVxLanGpeTunnel(ifName string, vrf, multicastIf uint32, vxLan *ifs.VxlanLink) (uint32, error) { + swIfIndex, err := h.vxLanGpeAddDelTunnel(true, vxLan, vrf, multicastIf) + if err != nil { + return 0, err + } + return swIfIndex, h.SetInterfaceTag(ifName, swIfIndex) +} + +// DeleteVxLanGpeTunnel removes VxLAN-GPE interface. +func (h *InterfaceVppHandler) DeleteVxLanGpeTunnel(ifName string, vxLan *ifs.VxlanLink) error { + swIfIndex, err := h.vxLanGpeAddDelTunnel(false, vxLan, 0, 0) + if err != nil { + return err + } + return h.RemoveInterfaceTag(ifName, swIfIndex) +} diff --git a/plugins/vpp/ifplugin/vppcalls/vpp2306/vxlan_vppcalls.go b/plugins/vpp/ifplugin/vppcalls/vpp2306/vxlan_vppcalls.go new file mode 100644 index 0000000000..64cf473de9 --- /dev/null +++ b/plugins/vpp/ifplugin/vppcalls/vpp2306/vxlan_vppcalls.go @@ -0,0 +1,75 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "fmt" + "net" + + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface_types" + vpp_vxlan "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/vxlan" + ifs "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/interfaces" +) + +func (h *InterfaceVppHandler) addDelVxLanTunnel(vxLan *ifs.VxlanLink, vrf, multicastIf uint32, isAdd bool) (swIdx uint32, err error) { + req := &vpp_vxlan.VxlanAddDelTunnel{ + IsAdd: isAdd, + Vni: vxLan.Vni, + DecapNextIndex: 0xFFFFFFFF, + Instance: ^uint32(0), + EncapVrfID: vrf, + McastSwIfIndex: interface_types.InterfaceIndex(multicastIf), + } + + srcAddr := net.ParseIP(vxLan.SrcAddress).To4() + dstAddr := net.ParseIP(vxLan.DstAddress).To4() + if srcAddr == nil && dstAddr == nil { + srcAddr = net.ParseIP(vxLan.SrcAddress).To16() + dstAddr = net.ParseIP(vxLan.DstAddress).To16() + if srcAddr == nil || dstAddr == nil { + return 0, fmt.Errorf("invalid VXLAN address, src: %s, dst: %s", srcAddr, dstAddr) + } + } else if srcAddr == nil && dstAddr != nil || srcAddr != nil && dstAddr == nil { + return 0, fmt.Errorf("IP version mismatch for VXLAN destination and source IP addresses") + } + + req.SrcAddress, _ = IPToAddress(srcAddr.String()) + req.DstAddress, _ = IPToAddress(dstAddr.String()) + + reply := &vpp_vxlan.VxlanAddDelTunnelReply{} + if err = h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return 0, err + } + + return uint32(reply.SwIfIndex), nil +} + +// AddVxLanTunnel implements VxLan handler. +func (h *InterfaceVppHandler) AddVxLanTunnel(ifName string, vrf, multicastIf uint32, vxLan *ifs.VxlanLink) (swIndex uint32, err error) { + swIfIdx, err := h.addDelVxLanTunnel(vxLan, vrf, multicastIf, true) + if err != nil { + return 0, err + } + return swIfIdx, h.SetInterfaceTag(ifName, swIfIdx) +} + +// DeleteVxLanTunnel implements VxLan handler. +func (h *InterfaceVppHandler) DeleteVxLanTunnel(ifName string, idx, vrf uint32, vxLan *ifs.VxlanLink) error { + // Multicast does not need to be set + if _, err := h.addDelVxLanTunnel(vxLan, vrf, 0, false); err != nil { + return err + } + return h.RemoveInterfaceTag(ifName, idx) +} diff --git a/plugins/vpp/ifplugin/vppcalls/vpp2306/vxlan_vppcalls_test.go b/plugins/vpp/ifplugin/vppcalls/vpp2306/vxlan_vppcalls_test.go new file mode 100644 index 0000000000..af81f20cce --- /dev/null +++ b/plugins/vpp/ifplugin/vppcalls/vpp2306/vxlan_vppcalls_test.go @@ -0,0 +1,235 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306_test + +import ( + "testing" + + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip_types" + + . "github.com/onsi/gomega" + + vpp_ifs "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface" + vpp_vxlan "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/vxlan" + ifs "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/interfaces" +) + +func TestAddVxlanTunnel(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_vxlan.VxlanAddDelTunnelReply{ + SwIfIndex: 1, + }) + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceTagAddDelReply{}) + + swIfIdx, err := ifHandler.AddVxLanTunnel("ifName", 0, 2, &ifs.VxlanLink{ + SrcAddress: "10.0.0.1", + DstAddress: "20.0.0.1", + Vni: 1, + }) + Expect(err).To(BeNil()) + Expect(swIfIdx).To(BeEquivalentTo(1)) + var msgCheck bool + for _, msg := range ctx.MockChannel.Msgs { + vppMsg, ok := msg.(*vpp_vxlan.VxlanAddDelTunnel) + if ok { + Expect(vppMsg.SrcAddress.Un.GetIP4()).To(BeEquivalentTo(ip_types.IP4Address{10, 0, 0, 1})) + Expect(vppMsg.DstAddress.Un.GetIP4()).To(BeEquivalentTo(ip_types.IP4Address{20, 0, 0, 1})) + Expect(vppMsg.IsAdd).To(BeEquivalentTo(true)) + Expect(vppMsg.EncapVrfID).To(BeEquivalentTo(0)) + Expect(vppMsg.McastSwIfIndex).To(BeEquivalentTo(2)) + Expect(vppMsg.Vni).To(BeEquivalentTo(1)) + msgCheck = true + } + } + Expect(msgCheck).To(BeTrue()) +} + +func TestAddVxlanTunnelWithVrf(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + // VxLAN resolution + ctx.MockVpp.MockReply(&vpp_vxlan.VxlanAddDelTunnelReply{ + SwIfIndex: 1, + }) + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceTagAddDelReply{}) + + swIfIdx, err := ifHandler.AddVxLanTunnel("ifName", 1, 1, &ifs.VxlanLink{ + SrcAddress: "10.0.0.1", + DstAddress: "20.0.0.1", + Vni: 1, + }) + Expect(err).To(BeNil()) + Expect(swIfIdx).To(BeEquivalentTo(1)) + var msgCheck bool + for _, msg := range ctx.MockChannel.Msgs { + vppMsg, ok := msg.(*vpp_vxlan.VxlanAddDelTunnel) + if ok { + Expect(vppMsg.SrcAddress.Un.GetIP4()).To(BeEquivalentTo(ip_types.IP4Address{10, 0, 0, 1})) + Expect(vppMsg.DstAddress.Un.GetIP4()).To(BeEquivalentTo(ip_types.IP4Address{20, 0, 0, 1})) + Expect(vppMsg.IsAdd).To(BeEquivalentTo(true)) + Expect(vppMsg.EncapVrfID).To(BeEquivalentTo(1)) + Expect(vppMsg.McastSwIfIndex).To(BeEquivalentTo(1)) + Expect(vppMsg.Vni).To(BeEquivalentTo(1)) + msgCheck = true + } + } + Expect(msgCheck).To(BeTrue()) +} + +func TestAddVxlanTunnelIPv6(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_vxlan.VxlanAddDelTunnelReply{ + SwIfIndex: 1, + }) + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceTagAddDelReply{}) + + swIfIdx, err := ifHandler.AddVxLanTunnel("ifName", 0, 0, &ifs.VxlanLink{ + SrcAddress: "2001:db8:0:1:1:1:1:1", + DstAddress: "2002:db8:0:1:1:1:1:1", + Vni: 1, + }) + Expect(err).To(BeNil()) + Expect(swIfIdx).To(BeEquivalentTo(1)) + var msgCheck bool + for _, msg := range ctx.MockChannel.Msgs { + vppMsg, ok := msg.(*vpp_vxlan.VxlanAddDelTunnel) + if ok { + Expect(vppMsg.SrcAddress).To(BeEquivalentTo(ipToAddr("2001:db8:0:1:1:1:1:1"))) + Expect(vppMsg.DstAddress).To(BeEquivalentTo(ipToAddr("2002:db8:0:1:1:1:1:1"))) + msgCheck = true + } + } + Expect(msgCheck).To(BeTrue()) +} + +func TestAddVxlanTunnelIPMismatch(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_vxlan.VxlanAddDelTunnelReply{ + SwIfIndex: 1, + }) + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceTagAddDelReply{}) + + _, err := ifHandler.AddVxLanTunnel("ifName", 0, 0, &ifs.VxlanLink{ + SrcAddress: "10.0.0.1", + DstAddress: "2001:db8:0:1:1:1:1:1", + Vni: 1, + }) + Expect(err).ToNot(BeNil()) +} + +func TestAddVxlanTunnelInvalidIP(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_vxlan.VxlanAddDelTunnelReply{ + SwIfIndex: 1, + }) + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceTagAddDelReply{}) + + _, err := ifHandler.AddVxLanTunnel("ifName", 0, 0, &ifs.VxlanLink{ + SrcAddress: "invalid-ip", + DstAddress: "2001:db8:0:1:1:1:1:1", + Vni: 1, + }) + Expect(err).ToNot(BeNil()) +} + +func TestAddVxlanTunnelError(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_vxlan.VxlanAddDelTunnel{}) + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceTagAddDelReply{}) + + _, err := ifHandler.AddVxLanTunnel("ifName", 0, 0, &ifs.VxlanLink{ + SrcAddress: "10.0.0.1", + DstAddress: "20.0.0.2", + Vni: 1, + }) + Expect(err).ToNot(BeNil()) +} + +func TestAddVxlanTunnelRetval(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_vxlan.VxlanAddDelTunnelReply{ + Retval: 1, + }) + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceTagAddDelReply{}) + + _, err := ifHandler.AddVxLanTunnel("ifName", 0, 0, &ifs.VxlanLink{ + SrcAddress: "10.0.0.1", + DstAddress: "20.0.0.2", + Vni: 1, + }) + Expect(err).ToNot(BeNil()) +} + +func TestDeleteVxlanTunnel(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_vxlan.VxlanAddDelTunnelReply{ + SwIfIndex: 1, + }) + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceTagAddDelReply{}) + + err := ifHandler.DeleteVxLanTunnel("ifName", 1, 0, &ifs.VxlanLink{ + SrcAddress: "10.0.0.1", + DstAddress: "20.0.0.1", + Vni: 1, + }) + Expect(err).To(BeNil()) +} + +func TestDeleteVxlanTunnelError(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_vxlan.VxlanAddDelTunnel{}) + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceTagAddDelReply{}) + + err := ifHandler.DeleteVxLanTunnel("ifName", 1, 0, &ifs.VxlanLink{ + SrcAddress: "10.0.0.1", + DstAddress: "20.0.0.1", + Vni: 1, + }) + Expect(err).ToNot(BeNil()) +} + +func TestDeleteVxlanTunnelRetval(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_vxlan.VxlanAddDelTunnelReply{ + Retval: 1, + }) + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceTagAddDelReply{}) + + err := ifHandler.DeleteVxLanTunnel("ifName", 1, 0, &ifs.VxlanLink{ + SrcAddress: "10.0.0.1", + DstAddress: "20.0.0.1", + Vni: 1, + }) + Expect(err).ToNot(BeNil()) +} diff --git a/plugins/vpp/ifplugin/vppcalls/vpp2306/watch_vppcalls.go b/plugins/vpp/ifplugin/vppcalls/vpp2306/watch_vppcalls.go new file mode 100644 index 0000000000..64d2f8abd6 --- /dev/null +++ b/plugins/vpp/ifplugin/vppcalls/vpp2306/watch_vppcalls.go @@ -0,0 +1,199 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "context" + "net" + "os" + "strings" + "time" + + "github.com/pkg/errors" + govppapi "go.fd.io/govpp/api" + + vpp_dhcp "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/dhcp" + vpp_ifs "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface_types" + "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/vppcalls" +) + +var ( + // EventDeliverTimeout defines maximum time to deliver event upstream. + EventDeliverTimeout = time.Second + // NotifChanBufferSize defines size of notification channel buffer. + NotifChanBufferSize = 10 +) + +func (h *InterfaceVppHandler) WatchInterfaceEvents(ctx context.Context, eventsCh chan<- *vppcalls.InterfaceEvent) error { + notifChan := make(chan govppapi.Message, NotifChanBufferSize) + + // subscribe to SwInterfaceEvent notifications + sub, err := h.callsChannel.SubscribeNotification(notifChan, &vpp_ifs.SwInterfaceEvent{}) + if err != nil { + return errors.Errorf("subscribing to VPP notification (sw_interface_event) failed: %v", err) + } + unsub := func() { + if err := sub.Unsubscribe(); err != nil { + h.log.Warnf("unsubscribing VPP notification (sw_interface_event) failed: %v", err) + } + } + + go func() { + h.log.Debugf("start watching interface events") + defer h.log.Debugf("done watching interface events (%v)", ctx.Err()) + + for { + select { + case e, open := <-notifChan: + if !open { + h.log.Debugf("interface events channel was closed") + unsub() + return + } + + ifEvent, ok := e.(*vpp_ifs.SwInterfaceEvent) + if !ok { + h.log.Debugf("unexpected notification type: %#v", ifEvent) + continue + } + + // try to send event + select { + case eventsCh <- toInterfaceEvent(ifEvent): + // sent ok + case <-ctx.Done(): + unsub() + return + default: + // channel full send event in goroutine for later processing + go func() { + select { + case eventsCh <- toInterfaceEvent(ifEvent): + // sent ok + case <-time.After(EventDeliverTimeout): + h.log.Warnf("unable to deliver interface event, dropping it: %+v", ifEvent) + } + }() + } + case <-ctx.Done(): + unsub() + return + } + } + }() + + // enable interface events from VPP + if _, err := h.interfaces.WantInterfaceEvents(ctx, &vpp_ifs.WantInterfaceEvents{ + PID: uint32(os.Getpid()), + EnableDisable: 1, + }); err != nil { + if errors.Is(err, govppapi.VPPApiError(govppapi.INVALID_REGISTRATION)) { + h.log.Warnf("already subscribed to interface events: %v", err) + return nil + } + return errors.Errorf("failed to watch interface events: %v", err) + } + + return nil +} + +func (h *InterfaceVppHandler) WatchDHCPLeases(ctx context.Context, leasesCh chan<- *vppcalls.Lease) error { + notifChan := make(chan govppapi.Message, NotifChanBufferSize) + + // subscribe for receiving DHCPComplEvent notifications + sub, err := h.callsChannel.SubscribeNotification(notifChan, &vpp_dhcp.DHCPComplEvent{}) + if err != nil { + return errors.Errorf("subscribing to VPP notification (dhcp_compl_event) failed: %v", err) + } + unsub := func() { + if err := sub.Unsubscribe(); err != nil { + h.log.Warnf("unsubscribing VPP notification (dhcp_compl_event) failed: %v", err) + } + } + + go func() { + h.log.Debugf("start watching DHCP leases") + defer h.log.Debugf("done watching DHCP lease (%v)", ctx.Err()) + + for { + select { + case e, open := <-notifChan: + if !open { + h.log.Debugf("interface notification channel was closed") + unsub() + return + } + + dhcpEvent, ok := e.(*vpp_dhcp.DHCPComplEvent) + if !ok { + h.log.Debugf("unexpected notification type: %#v", dhcpEvent) + continue + } + + // try to send event + select { + case leasesCh <- toDHCPLease(dhcpEvent): + // sent ok + case <-ctx.Done(): + unsub() + return + default: + // channel full send event in goroutine for later processing + go func() { + select { + case leasesCh <- toDHCPLease(dhcpEvent): + // sent ok + case <-time.After(EventDeliverTimeout): + h.log.Warnf("unable to deliver DHCP lease event, dropping it: %+v", dhcpEvent) + } + }() + } + case <-ctx.Done(): + unsub() + return + } + } + }() + + return nil +} + +func toInterfaceEvent(ifEvent *vpp_ifs.SwInterfaceEvent) *vppcalls.InterfaceEvent { + event := &vppcalls.InterfaceEvent{ + SwIfIndex: uint32(ifEvent.SwIfIndex), + Deleted: ifEvent.Deleted, + } + if ifEvent.Flags&interface_types.IF_STATUS_API_FLAG_ADMIN_UP == interface_types.IF_STATUS_API_FLAG_ADMIN_UP { + event.AdminState = 1 + } + if ifEvent.Flags&interface_types.IF_STATUS_API_FLAG_LINK_UP == interface_types.IF_STATUS_API_FLAG_LINK_UP { + event.LinkState = 1 + } + return event +} + +func toDHCPLease(dhcpEvent *vpp_dhcp.DHCPComplEvent) *vppcalls.Lease { + lease := dhcpEvent.Lease + return &vppcalls.Lease{ + SwIfIndex: uint32(lease.SwIfIndex), + State: uint8(lease.State), + Hostname: strings.TrimRight(lease.Hostname, "\x00"), + IsIPv6: lease.IsIPv6, + HostAddress: dhcpAddressToString(lease.HostAddress, uint32(lease.MaskWidth), lease.IsIPv6), + RouterAddress: dhcpAddressToString(lease.RouterAddress, uint32(lease.MaskWidth), lease.IsIPv6), + HostMac: net.HardwareAddr(lease.HostMac[:]).String(), + } +} diff --git a/plugins/vpp/ifplugin/vppcalls/vpp2306/watch_vppcalls_test.go b/plugins/vpp/ifplugin/vppcalls/vpp2306/watch_vppcalls_test.go new file mode 100644 index 0000000000..c080e4025d --- /dev/null +++ b/plugins/vpp/ifplugin/vppcalls/vpp2306/watch_vppcalls_test.go @@ -0,0 +1,143 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306_test + +import ( + "net" + "testing" + + . "github.com/onsi/gomega" + + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/dhcp" + interfaces "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip_types" + "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/vppcalls" +) + +func TestWatchInterfaceEvents(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + ctx.MockVpp.MockReply(&interfaces.WantInterfaceEventsReply{}) + eventsChan := make(chan *vppcalls.InterfaceEvent) + err := ifHandler.WatchInterfaceEvents(ctx.Context, eventsChan) + notifChan := ctx.MockChannel.GetChannel() + Expect(notifChan).ToNot(BeNil()) + Expect(err).To(BeNil()) + + notifChan <- &interfaces.SwInterfaceEvent{ + SwIfIndex: 1, + Flags: 3, + Deleted: true, + } + var result *vppcalls.InterfaceEvent + Eventually(eventsChan, 2).Should(Receive(&result)) + Expect(result).To(Equal(&vppcalls.InterfaceEvent{ + SwIfIndex: 1, + AdminState: 1, + LinkState: 1, + Deleted: true, + })) + + notifChan <- &interfaces.SwInterfaceEvent{ + SwIfIndex: 2, + Flags: 1, + Deleted: true, + } + result = &vppcalls.InterfaceEvent{} + Eventually(eventsChan, 2).Should(Receive(&result)) + Expect(result).To(Equal(&vppcalls.InterfaceEvent{SwIfIndex: 2, AdminState: 1, LinkState: 0, Deleted: true})) + + notifChan <- &interfaces.SwInterfaceEvent{ + SwIfIndex: 3, + Flags: 3, + Deleted: false, + } + result = &vppcalls.InterfaceEvent{} + Eventually(eventsChan, 2).Should(Receive(&result)) + Expect(result).To(Equal(&vppcalls.InterfaceEvent{ + SwIfIndex: 3, + AdminState: 1, + LinkState: 1, + Deleted: false, + })) + + close(notifChan) +} + +func TestWatchDHCPLeases(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + leasesChChan := make(chan *vppcalls.Lease) + err := ifHandler.WatchDHCPLeases(ctx.Context, leasesChChan) + notifChan := ctx.MockChannel.GetChannel() + Expect(notifChan).ToNot(BeNil()) + Expect(err).To(BeNil()) + + var hostAddr, routerAddr [16]byte + copy(hostAddr[:], net.ParseIP("10.10.10.5").To4()) + copy(routerAddr[:], net.ParseIP("10.10.10.1").To4()) + + notifChan <- &dhcp.DHCPComplEvent{ + PID: 50, + Lease: dhcp.DHCPLease{ + SwIfIndex: 1, + State: 1, + Hostname: "host1", + IsIPv6: false, + MaskWidth: 24, + HostAddress: ip_types.Address{Un: ip_types.AddressUnion{XXX_UnionData: hostAddr}}, + RouterAddress: ip_types.Address{Un: ip_types.AddressUnion{XXX_UnionData: routerAddr}}, + HostMac: [6]byte{16, 16, 32, 32, 48, 48}, + }, + } + var result *vppcalls.Lease + Eventually(leasesChChan, 50).Should(Receive(&result)) + Expect(result).To(Equal(&vppcalls.Lease{ + SwIfIndex: 1, + State: 1, + Hostname: "host1", + HostAddress: "10.10.10.5/24", + RouterAddress: "10.10.10.1/24", + HostMac: "10:10:20:20:30:30", + })) + + copy(hostAddr[:], net.ParseIP("1234::").To16()) + copy(routerAddr[:], net.ParseIP("abcd::").To16()) + + notifChan <- &dhcp.DHCPComplEvent{ + PID: 50, + Lease: dhcp.DHCPLease{ + SwIfIndex: 2, + State: 0, + Hostname: "host2", + IsIPv6: true, + MaskWidth: 64, + HostAddress: ip_types.Address{Un: ip_types.AddressUnion{XXX_UnionData: hostAddr}}, + RouterAddress: ip_types.Address{Un: ip_types.AddressUnion{XXX_UnionData: routerAddr}}, + HostMac: [6]byte{16, 16, 32, 32, 64, 64}, + }, + } + Eventually(leasesChChan, 2).Should(Receive(&result)) + Expect(result).To(Equal(&vppcalls.Lease{ + SwIfIndex: 2, + Hostname: "host2", + IsIPv6: true, + HostAddress: "1234::/64", + RouterAddress: "abcd::/64", + HostMac: "10:10:20:20:40:40", + })) + + close(leasesChChan) +} diff --git a/plugins/vpp/ifplugin/vppcalls/vpp2306/wireguard_vppcalls.go b/plugins/vpp/ifplugin/vppcalls/vpp2306/wireguard_vppcalls.go new file mode 100644 index 0000000000..9a744ecd10 --- /dev/null +++ b/plugins/vpp/ifplugin/vppcalls/vpp2306/wireguard_vppcalls.go @@ -0,0 +1,132 @@ +// Copyright (c) 2022 Doc.ai and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "encoding/base64" + "fmt" + + "github.com/pkg/errors" + "go.ligato.io/vpp-agent/v3/plugins/vpp" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface_types" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/wireguard" + "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/vppcalls" + interfaces "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/interfaces" +) + +// AddWireguardTunnel adds a new wireguard tunnel interface. +func (h *InterfaceVppHandler) AddWireguardTunnel(ifName string, wireguardLink *interfaces.WireguardLink) (uint32, error) { + invalidIdx := ^uint32(0) + if h.wireguard == nil { + return invalidIdx, errors.WithMessage(vpp.ErrPluginDisabled, "wireguard") + } + + wgItf := wireguard.WireguardInterface{ + UserInstance: ^uint32(0), + Port: uint16(wireguardLink.Port), + } + + genKey := false + if len(wireguardLink.PrivateKey) > 0 { + publicKeyBin, err := base64.StdEncoding.DecodeString(wireguardLink.PrivateKey) + if err != nil { + return invalidIdx, err + } + wgItf.PrivateKey = publicKeyBin + } else { + genKey = true + } + + srcAddr, err := IPToAddress(wireguardLink.SrcAddr) + if err != nil { + return invalidIdx, err + } + wgItf.SrcIP = srcAddr + + req := &wireguard.WireguardInterfaceCreate{ + Interface: wgItf, + GenerateKey: genKey, + } + + // prepare reply + reply := &wireguard.WireguardInterfaceCreateReply{} + // send request and obtain reply + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return invalidIdx, err + } + retSwIfIndex := uint32(reply.SwIfIndex) + return retSwIfIndex, h.SetInterfaceTag(ifName, retSwIfIndex) +} + +// DeleteWireguardTunnel removes wireguard tunnel interface. +func (h *InterfaceVppHandler) DeleteWireguardTunnel(ifName string, ifIdx uint32) error { + if h.wireguard == nil { + return errors.WithMessage(vpp.ErrPluginDisabled, "wireguard") + } + + req := &wireguard.WireguardInterfaceDelete{ + SwIfIndex: interface_types.InterfaceIndex(ifIdx), + } + // prepare reply + reply := &wireguard.WireguardInterfaceDeleteReply{} + // send request and obtain reply + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + return h.RemoveInterfaceTag(ifName, ifIdx) +} + +// dumpWireguardDetails dumps wireguard interface details from VPP. +func (h *InterfaceVppHandler) dumpWireguardDetails(ifc map[uint32]*vppcalls.InterfaceDetails) error { + if h.wireguard == nil { + return nil + } + + // index of ^uint32(0) dumps all interfaces + req := &wireguard.WireguardInterfaceDump{ + SwIfIndex: interface_types.InterfaceIndex(^uint32(0)), + } + reqCtx := h.callsChannel.SendMultiRequest(req) + + for { + wgDetails := &wireguard.WireguardInterfaceDetails{} + stop, err := reqCtx.ReceiveReply(wgDetails) + if stop { + break // break from the loop + } + if err != nil { + return fmt.Errorf("failed to dump wireguard interface details: %v", err) + } + _, ifIdxExists := ifc[uint32(wgDetails.Interface.SwIfIndex)] + if !ifIdxExists { + h.log.Warnf("Wireguard interface dump: interface name for index %d not found", wgDetails.Interface.SwIfIndex) + continue + } + + wgLink := &interfaces.WireguardLink{ + Port: uint32(wgDetails.Interface.Port), + } + wgLink.PrivateKey = base64.StdEncoding.EncodeToString(wgDetails.Interface.PrivateKey) + + srcAddr := wgDetails.Interface.SrcIP.ToIP() + if !srcAddr.IsUnspecified() { + wgLink.SrcAddr = srcAddr.String() + } + + ifc[uint32(wgDetails.Interface.SwIfIndex)].Interface.Link = &interfaces.Interface_Wireguard{Wireguard: wgLink} + ifc[uint32(wgDetails.Interface.SwIfIndex)].Interface.Type = interfaces.Interface_WIREGUARD_TUNNEL + } + return nil +} diff --git a/plugins/vpp/ifplugin/vppcalls/vpp2306/wireguard_vppcalls_test.go b/plugins/vpp/ifplugin/vppcalls/vpp2306/wireguard_vppcalls_test.go new file mode 100644 index 0000000000..5a3beb8734 --- /dev/null +++ b/plugins/vpp/ifplugin/vppcalls/vpp2306/wireguard_vppcalls_test.go @@ -0,0 +1,102 @@ +// Copyright (c) 2022 Doc.ai and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306_test + +import ( + "encoding/base64" + "testing" + + . "github.com/onsi/gomega" + + vpp_ifs "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip_types" + vpp_wg "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/wireguard" + + ifs "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/interfaces" +) + +func TestAddWgTunnelInterface(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_wg.WireguardInterfaceCreateReply{ + SwIfIndex: 2, + }) + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceTagAddDelReply{}) + + wgLink := &ifs.WireguardLink{ + PrivateKey: "gIjXzrQfIFf80d0O8Hd2KhcfkKLRncc+8C70OjotIW8=", + Port: 12312, + SrcAddr: "10.0.0.1", + } + + index, err := ifHandler.AddWireguardTunnel("wg1", wgLink) + Expect(err).To(BeNil()) + Expect(index).To(Equal(uint32(2))) + var msgCheck bool + for _, msg := range ctx.MockChannel.Msgs { + vppMsg, ok := msg.(*vpp_wg.WireguardInterfaceCreate) + if ok { + privKeyBin, _ := base64.StdEncoding.DecodeString("gIjXzrQfIFf80d0O8Hd2KhcfkKLRncc+8C70OjotIW8=") + Expect(vppMsg.GenerateKey).To(BeEquivalentTo(false)) + Expect(vppMsg.Interface.PrivateKey).To(BeEquivalentTo(privKeyBin)) + Expect(vppMsg.Interface.Port).To(BeEquivalentTo(12312)) + Expect(vppMsg.Interface.SrcIP.Un.GetIP4()).To(BeEquivalentTo(ip_types.IP4Address{10, 0, 0, 1})) + msgCheck = true + } + } + Expect(msgCheck).To(BeTrue()) +} + +func TestAddWgTunnelInterfaceWithGenKey(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_wg.WireguardInterfaceCreateReply{ + SwIfIndex: 2, + }) + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceTagAddDelReply{}) + + wgLink := &ifs.WireguardLink{ + Port: 12312, + SrcAddr: "10.0.0.1", + } + + index, err := ifHandler.AddWireguardTunnel("wg1", wgLink) + Expect(err).To(BeNil()) + Expect(index).To(Equal(uint32(2))) + var msgCheck bool + for _, msg := range ctx.MockChannel.Msgs { + vppMsg, ok := msg.(*vpp_wg.WireguardInterfaceCreate) + if ok { + Expect(vppMsg.GenerateKey).To(BeEquivalentTo(true)) + Expect(vppMsg.Interface.Port).To(BeEquivalentTo(12312)) + Expect(vppMsg.Interface.SrcIP.Un.GetIP4()).To(BeEquivalentTo(ip_types.IP4Address{10, 0, 0, 1})) + msgCheck = true + } + } + Expect(msgCheck).To(BeTrue()) +} + +func TestDeleteWgTunnelInterface(t *testing.T) { + ctx, ifHandler := ifTestSetup(t) + defer ctx.TeardownTestCtx() + ctx.MockVpp.MockReply(&vpp_wg.WireguardInterfaceDeleteReply{}) + ctx.MockVpp.MockReply(&vpp_ifs.SwInterfaceTagAddDelReply{}) + + err := ifHandler.DeleteWireguardTunnel("wg1", 2) + + Expect(err).To(BeNil()) +} diff --git a/plugins/vpp/ipfixplugin/ipfixplugin.go b/plugins/vpp/ipfixplugin/ipfixplugin.go index fc208753db..e9d205dfc4 100644 --- a/plugins/vpp/ipfixplugin/ipfixplugin.go +++ b/plugins/vpp/ipfixplugin/ipfixplugin.go @@ -31,6 +31,7 @@ import ( _ "go.ligato.io/vpp-agent/v3/plugins/vpp/ipfixplugin/vppcalls/vpp2202" _ "go.ligato.io/vpp-agent/v3/plugins/vpp/ipfixplugin/vppcalls/vpp2210" + _ "go.ligato.io/vpp-agent/v3/plugins/vpp/ipfixplugin/vppcalls/vpp2306" ) // IPFIXPlugin is a plugin that manages IPFIX configuration in VPP. diff --git a/plugins/vpp/ipfixplugin/vppcalls/vpp2306/dump_vppcalls.go b/plugins/vpp/ipfixplugin/vppcalls/vpp2306/dump_vppcalls.go new file mode 100644 index 0000000000..b42b021f30 --- /dev/null +++ b/plugins/vpp/ipfixplugin/vppcalls/vpp2306/dump_vppcalls.go @@ -0,0 +1,64 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "fmt" + "net" + + vpp_ipfix "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ipfix_export" + ipfix "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/ipfix" +) + +// DumpExporters returns configured IPFIX. +// Since it always only one IPFIX configuration, this method signature +// defined as it is to keep consistensy between different vppcalls packages. +// +// Caution: VPP does not support IPv6 addresses for IPFIX configuration (tested +// with VPP 22.10), but this may change in future versions. Be careful porting +// this method to another version of VPP. +func (h *IpfixVppHandler) DumpExporters() ([]*ipfix.IPFIX, error) { + var ipfixes []*ipfix.IPFIX + reqCtx := h.callsChannel.SendMultiRequest(&vpp_ipfix.IpfixExporterDump{}) + for { + details := &vpp_ipfix.IpfixExporterDetails{} + stop, err := reqCtx.ReceiveReply(details) + if stop { + break + } + if err != nil { + return nil, fmt.Errorf("failed to dump IPFIX: %v", err) + } + + collectorIPAddr := details.CollectorAddress.Un.GetIP4() + sourceIPAddr := details.SrcAddress.Un.GetIP4() + + ipfixes = append(ipfixes, + &ipfix.IPFIX{ + Collector: &ipfix.IPFIX_Collector{ + Address: net.IP(collectorIPAddr[:]).To4().String(), + Port: uint32(details.CollectorPort), + }, + SourceAddress: net.IP(sourceIPAddr[:]).To4().String(), + VrfId: details.VrfID, + PathMtu: details.PathMtu, + TemplateInterval: details.TemplateInterval, + }, + ) + + } + + return ipfixes, nil +} diff --git a/plugins/vpp/ipfixplugin/vppcalls/vpp2306/flowprobe_vppcalls.go b/plugins/vpp/ipfixplugin/vppcalls/vpp2306/flowprobe_vppcalls.go new file mode 100644 index 0000000000..cf82a4b0f6 --- /dev/null +++ b/plugins/vpp/ipfixplugin/vppcalls/vpp2306/flowprobe_vppcalls.go @@ -0,0 +1,102 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "github.com/pkg/errors" + + vpp_flowprobe "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/flowprobe" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface_types" + ipfix "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/ipfix" +) + +// SetFPParams sends message with configuration for Flowprobe. +func (h *IpfixVppHandler) SetFPParams(conf *ipfix.FlowProbeParams) error { + var flags vpp_flowprobe.FlowprobeRecordFlags + + if conf.GetRecordL2() { + flags |= vpp_flowprobe.FLOWPROBE_RECORD_FLAG_L2 + } + + if conf.GetRecordL3() { + flags |= vpp_flowprobe.FLOWPROBE_RECORD_FLAG_L3 + } + + if conf.GetRecordL4() { + flags |= vpp_flowprobe.FLOWPROBE_RECORD_FLAG_L4 + } + + if flags == 0 { + err := errors.New("one of the record fields (l2, l3 or l4) must be enabled") + return err + } + + req := &vpp_flowprobe.FlowprobeParams{ + RecordFlags: flags, + ActiveTimer: conf.GetActiveTimer(), + PassiveTimer: conf.GetPassiveTimer(), + } + reply := &vpp_flowprobe.FlowprobeParamsReply{} + + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + + return nil +} + +func (h *IpfixVppHandler) sendFPFeature(isAdd bool, conf *ipfix.FlowProbeFeature) error { + meta, found := h.ifIndexes.LookupByName(conf.Interface) + if !found { + return errors.Errorf("interface %s not found", conf.Interface) + } + + var flags vpp_flowprobe.FlowprobeWhichFlags + + if conf.GetL2() { + flags |= vpp_flowprobe.FLOWPROBE_WHICH_FLAG_L2 + } + + if conf.GetIp4() { + flags |= vpp_flowprobe.FLOWPROBE_WHICH_FLAG_IP4 + } + + if conf.GetIp6() { + flags |= vpp_flowprobe.FLOWPROBE_WHICH_FLAG_IP6 + } + + req := &vpp_flowprobe.FlowprobeTxInterfaceAddDel{ + IsAdd: isAdd, + Which: flags, + SwIfIndex: interface_types.InterfaceIndex(meta.SwIfIndex), + } + reply := &vpp_flowprobe.FlowprobeTxInterfaceAddDelReply{} + + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + + return nil +} + +// AddFPFeature sends message to enable Flowprobe on interface. +func (h *IpfixVppHandler) AddFPFeature(conf *ipfix.FlowProbeFeature) error { + return h.sendFPFeature(true, conf) +} + +// DelFPFeature sends message to disable Flowprobe on interface. +func (h *IpfixVppHandler) DelFPFeature(conf *ipfix.FlowProbeFeature) error { + return h.sendFPFeature(false, conf) +} diff --git a/plugins/vpp/ipfixplugin/vppcalls/vpp2306/ipfix_vppcalls.go b/plugins/vpp/ipfixplugin/vppcalls/vpp2306/ipfix_vppcalls.go new file mode 100644 index 0000000000..c181f2e635 --- /dev/null +++ b/plugins/vpp/ipfixplugin/vppcalls/vpp2306/ipfix_vppcalls.go @@ -0,0 +1,107 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "bytes" + "errors" + "fmt" + "net" + + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip_types" + vpp_ipfix "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ipfix_export" + "go.ligato.io/vpp-agent/v3/plugins/vpp/ipfixplugin/vppcalls" + ipfix "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/ipfix" +) + +// SetExporter configures IP Flow Information eXport (IPFIX). +func (h *IpfixVppHandler) SetExporter(conf *ipfix.IPFIX) error { + collectorAddr, err := prepareAddress(conf.GetCollector().GetAddress()) + if err != nil { + return fmt.Errorf("bad collector address: %v", err) + } + + sourceAddr, err := prepareAddress(conf.GetSourceAddress()) + if err != nil { + return fmt.Errorf("bad source address: %v", err) + } + + collectorPort := uint16(conf.GetCollector().GetPort()) + if collectorPort == 0 { + // Will be set by VPP to the default value: 4739. + collectorPort = ^uint16(0) + } + + mtu := conf.GetPathMtu() + if mtu == 0 { + // Will be set by VPP to the default value: 512 bytes. + mtu = ^uint32(0) + } else if mtu < vppcalls.MinPathMTU || mtu > vppcalls.MaxPathMTU { + err := errors.New("path MTU is not in allowed range") + return err + } + + tmplInterval := conf.GetTemplateInterval() + if tmplInterval == 0 { + // Will be set by VPP to the default value: 20 sec. + tmplInterval = ^uint32(0) + } + + req := &vpp_ipfix.SetIpfixExporter{ + CollectorAddress: collectorAddr, + CollectorPort: collectorPort, + SrcAddress: sourceAddr, + VrfID: conf.GetVrfId(), + PathMtu: mtu, + TemplateInterval: tmplInterval, + } + reply := &vpp_ipfix.SetIpfixExporterReply{} + + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + + return nil +} + +// prepareAddress validates and converts IP address, defined as a string, +// to the type which represents address in VPP binary API. +func prepareAddress(addrStr string) (ip_types.Address, error) { + var a ip_types.Address + + addr := net.ParseIP(addrStr) + if addr == nil { + err := errors.New("can not parse address") + return a, err + } + if addr.To4() == nil { + err := errors.New("IPv6 is not supported") + return a, err + } + + var addrBytes [4]byte + copy(addrBytes[:], addr.To4()) + if bytes.Equal(addrBytes[:], []byte{0, 0, 0, 0}) { + err := errors.New("address must not be all zeros") + return a, err + } + + a = ip_types.Address{ + Af: ip_types.ADDRESS_IP4, + Un: ip_types.AddressUnionIP4(addrBytes), + } + + return a, nil +} diff --git a/plugins/vpp/ipfixplugin/vppcalls/vpp2306/vppcalls_handlers.go b/plugins/vpp/ipfixplugin/vppcalls/vpp2306/vppcalls_handlers.go new file mode 100644 index 0000000000..7beeedbd8d --- /dev/null +++ b/plugins/vpp/ipfixplugin/vppcalls/vpp2306/vppcalls_handlers.go @@ -0,0 +1,52 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + govppapi "go.fd.io/govpp/api" + "go.ligato.io/cn-infra/v2/logging" + + vpp2306 "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306" + vpp_fp "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/flowprobe" + vpp_ipfix "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ipfix_export" + "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/ifaceidx" + "go.ligato.io/vpp-agent/v3/plugins/vpp/ipfixplugin/vppcalls" +) + +func init() { + var msgs []govppapi.Message + msgs = append(msgs, vpp_ipfix.AllMessages()...) + msgs = append(msgs, vpp_fp.AllMessages()...) + + vppcalls.AddIpfixHandlerVersion(vpp2306.Version, msgs, NewIpfixVppHandler) +} + +// IpfixVppHandler is accessor for IPFIX-related vppcalls methods. +type IpfixVppHandler struct { + callsChannel govppapi.Channel + ifIndexes ifaceidx.IfaceMetadataIndex + log logging.Logger +} + +// NewIpfixVppHandler creates new instance of IPFIX vppcalls handler. +func NewIpfixVppHandler(callsChan govppapi.Channel, + ifIndexes ifaceidx.IfaceMetadataIndex, log logging.Logger, +) vppcalls.IpfixVppAPI { + return &IpfixVppHandler{ + callsChannel: callsChan, + ifIndexes: ifIndexes, + log: log, + } +} diff --git a/plugins/vpp/ipsecplugin/ipsecplugin.go b/plugins/vpp/ipsecplugin/ipsecplugin.go index 50a4a8fd5e..0fbeb18f00 100644 --- a/plugins/vpp/ipsecplugin/ipsecplugin.go +++ b/plugins/vpp/ipsecplugin/ipsecplugin.go @@ -36,6 +36,7 @@ import ( _ "go.ligato.io/vpp-agent/v3/plugins/vpp/ipsecplugin/vppcalls/vpp2202" _ "go.ligato.io/vpp-agent/v3/plugins/vpp/ipsecplugin/vppcalls/vpp2210" + _ "go.ligato.io/vpp-agent/v3/plugins/vpp/ipsecplugin/vppcalls/vpp2306" ) func init() { diff --git a/plugins/vpp/ipsecplugin/vppcalls/vpp2306/dump_vppcalls.go b/plugins/vpp/ipsecplugin/vppcalls/vpp2306/dump_vppcalls.go new file mode 100644 index 0000000000..90333f5195 --- /dev/null +++ b/plugins/vpp/ipsecplugin/vppcalls/vpp2306/dump_vppcalls.go @@ -0,0 +1,315 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "encoding/hex" + "net" + + "github.com/pkg/errors" + + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface_types" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip_types" + vpp_ipsec "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ipsec" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ipsec_types" + "go.ligato.io/vpp-agent/v3/plugins/vpp/ipsecplugin/vppcalls" + ipsec "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/ipsec" +) + +// DumpIPSecSA implements IPSec handler. +func (h *IPSecVppHandler) DumpIPSecSA() (saList []*vppcalls.IPSecSaDetails, err error) { + return h.DumpIPSecSAWithIndex(^uint32(0)) // Get everything +} + +// DumpIPSecSAWithIndex implements IPSec handler. +func (h *IPSecVppHandler) DumpIPSecSAWithIndex(saID uint32) (saList []*vppcalls.IPSecSaDetails, err error) { + saDetails, err := h.dumpSecurityAssociations(saID) + if err != nil { + return nil, err + } + + for _, saData := range saDetails { + // Skip tunnel interfaces + if uint32(saData.SwIfIndex) != ^uint32(0) { + continue + } + + var tunnelSrcAddr, tunnelDstAddr net.IP + if saData.Entry.Tunnel.Dst.Af == ip_types.ADDRESS_IP6 { + src := saData.Entry.Tunnel.Src.Un.GetIP6() + dst := saData.Entry.Tunnel.Dst.Un.GetIP6() + tunnelSrcAddr, tunnelDstAddr = net.IP(src[:]), net.IP(dst[:]) + } else { + src := saData.Entry.Tunnel.Src.Un.GetIP4() + dst := saData.Entry.Tunnel.Dst.Un.GetIP4() + tunnelSrcAddr, tunnelDstAddr = net.IP(src[:]), net.IP(dst[:]) + } + + sa := &ipsec.SecurityAssociation{ + Index: saData.Entry.SadID, + Spi: saData.Entry.Spi, + Protocol: ipsecProtoToProtocol(saData.Entry.Protocol), + CryptoAlg: ipsec.CryptoAlg(saData.Entry.CryptoAlgorithm), + CryptoKey: hex.EncodeToString(saData.Entry.CryptoKey.Data[:saData.Entry.CryptoKey.Length]), + CryptoSalt: saData.Entry.Salt, + IntegAlg: ipsec.IntegAlg(saData.Entry.IntegrityAlgorithm), + IntegKey: hex.EncodeToString(saData.Entry.IntegrityKey.Data[:saData.Entry.IntegrityKey.Length]), + UseEsn: (saData.Entry.Flags & ipsec_types.IPSEC_API_SAD_FLAG_USE_ESN) != 0, + UseAntiReplay: (saData.Entry.Flags & ipsec_types.IPSEC_API_SAD_FLAG_USE_ANTI_REPLAY) != 0, + EnableUdpEncap: (saData.Entry.Flags & ipsec_types.IPSEC_API_SAD_FLAG_UDP_ENCAP) != 0, + TunnelSrcPort: uint32(saData.Entry.UDPSrcPort), + TunnelDstPort: uint32(saData.Entry.UDPDstPort), + } + if !tunnelSrcAddr.IsUnspecified() { + sa.TunnelSrcAddr = tunnelSrcAddr.String() + } + if !tunnelDstAddr.IsUnspecified() { + sa.TunnelDstAddr = tunnelDstAddr.String() + } + meta := &vppcalls.IPSecSaMeta{ + SaID: saData.Entry.SadID, + IfIdx: uint32(saData.SwIfIndex), + SeqOutbound: saData.SeqOutbound, + LastSeqInbound: saData.LastSeqInbound, + ReplayWindow: saData.ReplayWindow, + } + saList = append(saList, &vppcalls.IPSecSaDetails{ + Sa: sa, + Meta: meta, + }) + } + + return saList, nil +} + +// DumpIPSecSPD returns a list of IPSec security policy databases +func (h *IPSecVppHandler) DumpIPSecSPD() (spdList []*ipsec.SecurityPolicyDatabase, err error) { + /* TODO: dumping of SPD interfaces is broken in VPP + - instead of the SPD index value given by control-plane, the index to the VPP's internal array of SPDs + is returned, which is useless + spdInterfaces, err := h.dumpSpdInterfaces() + if err != nil { + err = fmt.Errorf("dumping of SPD interfaces failed: %v", err) + return nil, err + } + */ + + // Get all VPP SPD indexes + spdIndexes, err := h.dumpSpdIndexes() + if err != nil { + return nil, errors.Errorf("failed to dump SPD indexes: %v", err) + } + + for spdIdx := range spdIndexes { + spd := &ipsec.SecurityPolicyDatabase{ + Index: spdIdx, + } + /* + for _, swIfIndex := range spdInterfaces[spdIdx] { + name, _, found := h.ifIndexes.LookupBySwIfIndex(swIfIndex) + if !found { + h.log.Warnf("Failed to find interface with sw_if_index %d", swIfIndex) + continue + } + spd.Interfaces = append(spd.Interfaces, &ipsec.SecurityPolicyDatabase_Interface{ + Name: name, + }) + } + */ + spdList = append(spdList, spd) + } + + return spdList, nil +} + +// DumpIPSecSP returns a list of configured security policies +func (h *IPSecVppHandler) DumpIPSecSP() (spList []*ipsec.SecurityPolicy, err error) { + // Get all VPP SPD indexes + spdIndexes, err := h.dumpSpdIndexes() + if err != nil { + return nil, errors.Errorf("failed to dump SPD indexes: %v", err) + } + for spdIdx := range spdIndexes { + req := &vpp_ipsec.IpsecSpdDump{ + SpdID: spdIdx, + SaID: ^uint32(0), + } + requestCtx := h.callsChannel.SendMultiRequest(req) + + for { + spdDetails := &vpp_ipsec.IpsecSpdDetails{} + stop, err := requestCtx.ReceiveReply(spdDetails) + if stop { + break + } + if err != nil { + return nil, err + } + + // Addresses + remoteStartAddr := ipsecAddrToIP(spdDetails.Entry.RemoteAddressStart) + remoteStopAddr := ipsecAddrToIP(spdDetails.Entry.RemoteAddressStop) + localStartAddr := ipsecAddrToIP(spdDetails.Entry.LocalAddressStart) + localStopAddr := ipsecAddrToIP(spdDetails.Entry.LocalAddressStop) + + // Prepare policy entry and put to the SPD + sp := &ipsec.SecurityPolicy{ + SpdIndex: spdIdx, + SaIndex: spdDetails.Entry.SaID, + Priority: spdDetails.Entry.Priority, + IsOutbound: spdDetails.Entry.IsOutbound, + RemoteAddrStart: remoteStartAddr.String(), + RemoteAddrStop: remoteStopAddr.String(), + LocalAddrStart: localStartAddr.String(), + LocalAddrStop: localStopAddr.String(), + Protocol: uint32(spdDetails.Entry.Protocol), + RemotePortStart: uint32(spdDetails.Entry.RemotePortStart), + RemotePortStop: resetPort(spdDetails.Entry.RemotePortStop), + LocalPortStart: uint32(spdDetails.Entry.LocalPortStart), + LocalPortStop: resetPort(spdDetails.Entry.LocalPortStop), + Action: ipsec.SecurityPolicy_Action(spdDetails.Entry.Policy), + } + spList = append(spList, sp) + } + } + + return spList, nil +} + +// DumpTunnelProtections returns configured IPSec tunnel protections. +func (h *IPSecVppHandler) DumpTunnelProtections() (tpList []*ipsec.TunnelProtection, err error) { + req := &vpp_ipsec.IpsecTunnelProtectDump{ + SwIfIndex: interface_types.InterfaceIndex(^uint32(0)), + } + requestCtx := h.callsChannel.SendMultiRequest(req) + for { + tpDetails := &vpp_ipsec.IpsecTunnelProtectDetails{} + stop, err := requestCtx.ReceiveReply(tpDetails) + if stop { + break + } + if err != nil { + return nil, err + } + ifName, _, exists := h.ifIndexes.LookupBySwIfIndex(uint32(tpDetails.Tun.SwIfIndex)) + if !exists { + h.log.Warnf("Tunnel protection dump: interface name for index %d not found", tpDetails.Tun.SwIfIndex) + continue + } + tp := &ipsec.TunnelProtection{ + Interface: ifName, + SaOut: []uint32{tpDetails.Tun.SaOut}, + } + tp.SaIn = append(tp.SaIn, tpDetails.Tun.SaIn...) + tpList = append(tpList, tp) + + if tpDetails.Tun.Nh.Af == ip_types.ADDRESS_IP6 { + nhAddrArr := tpDetails.Tun.Nh.Un.GetIP6() + nhAddr := net.IP(nhAddrArr[:]).To16() + if !nhAddr.IsUnspecified() { + tp.NextHopAddr = nhAddr.String() + } + } else { + nhAddrArr := tpDetails.Tun.Nh.Un.GetIP4() + nhAddr := net.IP(nhAddrArr[:4]).To4() + if !nhAddr.IsUnspecified() { + tp.NextHopAddr = nhAddr.String() + } + } + } + return +} + +// TODO: dumping of SPD interfaces is broken in VPP. Instead of the SPD index value given by control-plane, +// the index to the VPP's internal array of SPDs is returned, which is useless. + +// Get all interfaces of SPD configured on the VPP +// func (h *IPSecVppHandler) dumpSpdInterfaces() (map[uint32][]uint32, error) { +// // SPD index to interface indexes +// spdInterfaces := make(map[uint32][]uint32) + +// req := &vpp_ipsec.IpsecSpdInterfaceDump{} +// reqCtx := h.callsChannel.SendMultiRequest(req) + +// for { +// spdDetails := &vpp_ipsec.IpsecSpdInterfaceDetails{} +// stop, err := reqCtx.ReceiveReply(spdDetails) +// if stop { +// break +// } +// if err != nil { +// return nil, err +// } + +// spdInterfaces[spdDetails.SpdIndex] = append(spdInterfaces[spdDetails.SpdIndex], uint32(spdDetails.SwIfIndex)) +// } + +// return spdInterfaces, nil +// } + +// Get all indexes of SPD configured on the VPP +func (h *IPSecVppHandler) dumpSpdIndexes() (map[uint32]uint32, error) { + // SPD index to number of policies + spdIndexes := make(map[uint32]uint32) + + req := &vpp_ipsec.IpsecSpdsDump{} + reqCtx := h.callsChannel.SendMultiRequest(req) + + for { + spdDetails := &vpp_ipsec.IpsecSpdsDetails{} + stop, err := reqCtx.ReceiveReply(spdDetails) + if stop { + break + } + if err != nil { + return nil, err + } + + spdIndexes[spdDetails.SpdID] = spdDetails.Npolicies + } + + return spdIndexes, nil +} + +// Get all security association (used also for tunnel interfaces) in binary api format +func (h *IPSecVppHandler) dumpSecurityAssociations(saID uint32) (saList []*vpp_ipsec.IpsecSaV3Details, err error) { + req := &vpp_ipsec.IpsecSaV3Dump{ + SaID: saID, + } + requestCtx := h.callsChannel.SendMultiRequest(req) + + for { + saDetails := &vpp_ipsec.IpsecSaV3Details{} + stop, err := requestCtx.ReceiveReply(saDetails) + if stop { + break + } + if err != nil { + return nil, err + } + + saList = append(saList, saDetails) + } + + return saList, nil +} + +// ResetPort returns 0 if stop port has maximum value (default VPP value if stop port is not defined) +func resetPort(port uint16) uint32 { + if port == ^uint16(0) { + return 0 + } + return uint32(port) +} diff --git a/plugins/vpp/ipsecplugin/vppcalls/vpp2306/ipsec_vppcalls.go b/plugins/vpp/ipsecplugin/vppcalls/vpp2306/ipsec_vppcalls.go new file mode 100644 index 0000000000..c9ffe72164 --- /dev/null +++ b/plugins/vpp/ipsecplugin/vppcalls/vpp2306/ipsec_vppcalls.go @@ -0,0 +1,340 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "encoding/hex" + + "github.com/pkg/errors" + "go.ligato.io/cn-infra/v2/utils/addrs" + + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface_types" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip_types" + vpp_ipsec "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ipsec" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ipsec_types" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/tunnel_types" + ipsec "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/ipsec" +) + +// AddSPD implements IPSec handler. +func (h *IPSecVppHandler) AddSPD(spdID uint32) error { + return h.spdAddDel(spdID, true) +} + +// DeleteSPD implements IPSec handler. +func (h *IPSecVppHandler) DeleteSPD(spdID uint32) error { + return h.spdAddDel(spdID, false) +} + +// AddSP implements IPSec handler. +func (h *IPSecVppHandler) AddSP(sp *ipsec.SecurityPolicy) error { + return h.spdAddDelEntry(sp, true) +} + +// DeleteSP implements IPSec handler. +func (h *IPSecVppHandler) DeleteSP(sp *ipsec.SecurityPolicy) error { + return h.spdAddDelEntry(sp, false) +} + +// AddSPDInterface implements IPSec handler. +func (h *IPSecVppHandler) AddSPDInterface(spdID uint32, ifaceCfg *ipsec.SecurityPolicyDatabase_Interface) error { + ifaceMeta, found := h.ifIndexes.LookupByName(ifaceCfg.Name) + if !found { + return errors.New("failed to get interface metadata") + } + return h.interfaceAddDelSpd(spdID, ifaceMeta.SwIfIndex, true) +} + +// DeleteSPDInterface implements IPSec handler. +func (h *IPSecVppHandler) DeleteSPDInterface(spdID uint32, ifaceCfg *ipsec.SecurityPolicyDatabase_Interface) error { + ifaceMeta, found := h.ifIndexes.LookupByName(ifaceCfg.Name) + if !found { + return errors.New("failed to get interface metadata") + } + return h.interfaceAddDelSpd(spdID, ifaceMeta.SwIfIndex, false) +} + +// AddSA implements IPSec handler. +func (h *IPSecVppHandler) AddSA(sa *ipsec.SecurityAssociation) error { + return h.sadAddDelEntry(sa, true) +} + +// DeleteSA implements IPSec handler. +func (h *IPSecVppHandler) DeleteSA(sa *ipsec.SecurityAssociation) error { + return h.sadAddDelEntry(sa, false) +} + +// AddTunnelProtection implements IPSec handler for adding a tunnel protection. +func (h *IPSecVppHandler) AddTunnelProtection(tp *ipsec.TunnelProtection) error { + ifaceMeta, found := h.ifIndexes.LookupByName(tp.Interface) + if !found { + return errors.New("failed to get interface metadata") + } + return h.tunProtectAddUpdateEntry(tp, ifaceMeta.SwIfIndex) +} + +// UpdateTunnelProtection implements IPSec handler for updating a tunnel protection. +func (h *IPSecVppHandler) UpdateTunnelProtection(tp *ipsec.TunnelProtection) error { + ifaceMeta, found := h.ifIndexes.LookupByName(tp.Interface) + if !found { + return errors.New("failed to get interface metadata") + } + return h.tunProtectAddUpdateEntry(tp, ifaceMeta.SwIfIndex) +} + +// DeleteTunnelProtection implements IPSec handler for deleting a tunnel protection. +func (h *IPSecVppHandler) DeleteTunnelProtection(tp *ipsec.TunnelProtection) error { + ifaceMeta, found := h.ifIndexes.LookupByName(tp.Interface) + if !found { + return errors.New("failed to get interface metadata") + } + return h.tunProtectDelEntry(tp, ifaceMeta.SwIfIndex) +} + +func (h *IPSecVppHandler) spdAddDel(spdID uint32, isAdd bool) error { + req := &vpp_ipsec.IpsecSpdAddDel{ + IsAdd: isAdd, + SpdID: spdID, + } + reply := &vpp_ipsec.IpsecSpdAddDelReply{} + + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + + return nil +} + +func (h *IPSecVppHandler) spdAddDelEntry(sp *ipsec.SecurityPolicy, isAdd bool) error { + req := &vpp_ipsec.IpsecSpdEntryAddDelV2{ + IsAdd: isAdd, + Entry: ipsec_types.IpsecSpdEntryV2{ + SpdID: sp.SpdIndex, + Priority: sp.Priority, + IsOutbound: sp.IsOutbound, + Protocol: uint8(sp.Protocol), + RemotePortStart: uint16(sp.RemotePortStart), + RemotePortStop: uint16(sp.RemotePortStop), + LocalPortStart: uint16(sp.LocalPortStart), + LocalPortStop: uint16(sp.LocalPortStop), + Policy: ipsec_types.IpsecSpdAction(sp.Action), + SaID: sp.SaIndex, + }, + } + if req.Entry.RemotePortStop == 0 { + req.Entry.RemotePortStop = ^req.Entry.RemotePortStop + } + if req.Entry.LocalPortStop == 0 { + req.Entry.LocalPortStop = ^req.Entry.LocalPortStop + } + + var err error + req.Entry.RemoteAddressStart, err = IPToAddress(ipOr(sp.RemoteAddrStart, "0.0.0.0")) + if err != nil { + return err + } + req.Entry.RemoteAddressStop, err = IPToAddress(ipOr(sp.RemoteAddrStop, "255.255.255.255")) + if err != nil { + return err + } + req.Entry.LocalAddressStart, err = IPToAddress(ipOr(sp.LocalAddrStart, "0.0.0.0")) + if err != nil { + return err + } + req.Entry.LocalAddressStop, err = IPToAddress(ipOr(sp.LocalAddrStop, "255.255.255.255")) + if err != nil { + return err + } + + reply := &vpp_ipsec.IpsecSpdEntryAddDelV2Reply{} + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + + return nil +} + +func ipOr(s, o string) string { + if s != "" { + return s + } + return o +} + +func (h *IPSecVppHandler) interfaceAddDelSpd(spdID, swIfIdx uint32, isAdd bool) error { + req := &vpp_ipsec.IpsecInterfaceAddDelSpd{ + IsAdd: isAdd, + SwIfIndex: interface_types.InterfaceIndex(swIfIdx), + SpdID: spdID, + } + reply := &vpp_ipsec.IpsecInterfaceAddDelSpdReply{} + + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + + return nil +} + +func (h *IPSecVppHandler) sadAddDelEntry(sa *ipsec.SecurityAssociation, isAdd bool) error { + cryptoKey, err := hex.DecodeString(sa.CryptoKey) + if err != nil { + return err + } + integKey, err := hex.DecodeString(sa.IntegKey) + if err != nil { + return err + } + + var flags ipsec_types.IpsecSadFlags + if sa.UseEsn { + flags |= ipsec_types.IPSEC_API_SAD_FLAG_USE_ESN + } + if sa.UseAntiReplay { + flags |= ipsec_types.IPSEC_API_SAD_FLAG_USE_ANTI_REPLAY + } + if sa.EnableUdpEncap { + flags |= ipsec_types.IPSEC_API_SAD_FLAG_UDP_ENCAP + } + var tunnelSrc, tunnelDst ip_types.Address + if sa.TunnelSrcAddr != "" { + flags |= ipsec_types.IPSEC_API_SAD_FLAG_IS_TUNNEL + isIPv6, err := addrs.IsIPv6(sa.TunnelSrcAddr) + if err != nil { + return err + } + if isIPv6 { + flags |= ipsec_types.IPSEC_API_SAD_FLAG_IS_TUNNEL_V6 + } + tunnelSrc, err = IPToAddress(sa.TunnelSrcAddr) + if err != nil { + return err + } + tunnelDst, err = IPToAddress(sa.TunnelDstAddr) + if err != nil { + return err + } + } + const undefinedPort = ^uint16(0) + udpSrcPort := undefinedPort + if sa.TunnelSrcPort != 0 { + udpSrcPort = uint16(sa.TunnelSrcPort) + } + udpDstPort := undefinedPort + if sa.TunnelDstPort != 0 { + udpDstPort = uint16(sa.TunnelDstPort) + } + + req := &vpp_ipsec.IpsecSadEntryAddDelV3{ + IsAdd: isAdd, + Entry: ipsec_types.IpsecSadEntryV3{ + SadID: sa.Index, + Spi: sa.Spi, + Protocol: protocolToIpsecProto(sa.Protocol), + CryptoAlgorithm: ipsec_types.IpsecCryptoAlg(sa.CryptoAlg), + CryptoKey: ipsec_types.Key{ + Data: cryptoKey, + Length: uint8(len(cryptoKey)), + }, + Salt: sa.CryptoSalt, + IntegrityAlgorithm: ipsec_types.IpsecIntegAlg(sa.IntegAlg), + IntegrityKey: ipsec_types.Key{ + Data: integKey, + Length: uint8(len(integKey)), + }, + Tunnel: tunnel_types.Tunnel{ + Src: tunnelSrc, + Dst: tunnelDst, + }, + Flags: flags, + UDPSrcPort: udpSrcPort, + UDPDstPort: udpDstPort, + }, + } + reply := &vpp_ipsec.IpsecSadEntryAddDelV3Reply{} + + if err = h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + + return nil +} + +func ipsecProtoToProtocol(ipsecProto ipsec_types.IpsecProto) ipsec.SecurityAssociation_IPSecProtocol { + switch ipsecProto { + case ipsec_types.IPSEC_API_PROTO_AH: + return ipsec.SecurityAssociation_AH + case ipsec_types.IPSEC_API_PROTO_ESP: + return ipsec.SecurityAssociation_ESP + default: + return 0 + } +} + +func protocolToIpsecProto(protocol ipsec.SecurityAssociation_IPSecProtocol) ipsec_types.IpsecProto { + switch protocol { + case ipsec.SecurityAssociation_AH: + return ipsec_types.IPSEC_API_PROTO_AH + case ipsec.SecurityAssociation_ESP: + return ipsec_types.IPSEC_API_PROTO_ESP + default: + return 0 + } +} + +func (h *IPSecVppHandler) tunProtectAddUpdateEntry(tp *ipsec.TunnelProtection, swIfIndex uint32) error { + if len(tp.SaOut) == 0 || len(tp.SaIn) == 0 { + return errors.New("missing outbound/inbound SA") + } + if len(tp.SaIn) > int(^uint8(0)) { + return errors.New("invalid number of inbound SAs") + } + req := &vpp_ipsec.IpsecTunnelProtectUpdate{Tunnel: vpp_ipsec.IpsecTunnelProtect{ + SwIfIndex: interface_types.InterfaceIndex(swIfIndex), + SaOut: tp.SaOut[0], + SaIn: tp.SaIn, + NSaIn: uint8(len(tp.SaIn)), + }} + if tp.NextHopAddr != "" { + nh, err := IPToAddress(tp.NextHopAddr) + if err != nil { + return err + } + req.Tunnel.Nh = nh + } + reply := &vpp_ipsec.IpsecTunnelProtectUpdateReply{} + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + return nil +} + +func (h *IPSecVppHandler) tunProtectDelEntry(tp *ipsec.TunnelProtection, swIfIndex uint32) error { + req := &vpp_ipsec.IpsecTunnelProtectDel{ + SwIfIndex: interface_types.InterfaceIndex(swIfIndex), + } + if tp.NextHopAddr != "" { + nh, err := IPToAddress(tp.NextHopAddr) + if err != nil { + return err + } + req.Nh = nh + } + reply := &vpp_ipsec.IpsecTunnelProtectDelReply{} + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + return nil +} diff --git a/plugins/vpp/ipsecplugin/vppcalls/vpp2306/ipsec_vppcalls_test.go b/plugins/vpp/ipsecplugin/vppcalls/vpp2306/ipsec_vppcalls_test.go new file mode 100644 index 0000000000..b502b223ce --- /dev/null +++ b/plugins/vpp/ipsecplugin/vppcalls/vpp2306/ipsec_vppcalls_test.go @@ -0,0 +1,400 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306_test + +import ( + "encoding/hex" + "fmt" + "testing" + + . "github.com/onsi/gomega" + "go.ligato.io/cn-infra/v2/logging/logrus" + + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip_types" + vpp_ipsec "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ipsec" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ipsec_types" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/tunnel_types" + "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/ifaceidx" + "go.ligato.io/vpp-agent/v3/plugins/vpp/ipsecplugin/vppcalls" + "go.ligato.io/vpp-agent/v3/plugins/vpp/ipsecplugin/vppcalls/vpp2306" + "go.ligato.io/vpp-agent/v3/plugins/vpp/vppmock" + ipsec "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/ipsec" +) + +func ipToAddr(ip string) ip_types.Address { + addr, err := vpp2306.IPToAddress(ip) + if err != nil { + panic(fmt.Sprintf("invalid IP: %s", ip)) + } + return addr +} + +func TestVppAddSPD(t *testing.T) { + ctx, ipSecHandler, _ := ipSecTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ipsec.IpsecSpdAddDelReply{}) + + err := ipSecHandler.AddSPD(10) + + Expect(err).ShouldNot(HaveOccurred()) + Expect(ctx.MockChannel.Msg).To(BeEquivalentTo(&vpp_ipsec.IpsecSpdAddDel{ + IsAdd: true, + SpdID: 10, + })) +} + +func TestVppDelSPD(t *testing.T) { + ctx, ipSecHandler, _ := ipSecTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ipsec.IpsecSpdAddDelReply{}) + + err := ipSecHandler.DeleteSPD(10) + + Expect(err).ShouldNot(HaveOccurred()) + Expect(ctx.MockChannel.Msg).To(BeEquivalentTo(&vpp_ipsec.IpsecSpdAddDel{ + IsAdd: false, + SpdID: 10, + })) +} + +func TestVppAddSP(t *testing.T) { + ctx, ipSecHandler, _ := ipSecTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ipsec.IpsecSpdEntryAddDelV2Reply{}) + + err := ipSecHandler.AddSP(&ipsec.SecurityPolicy{ + SaIndex: 5, + SpdIndex: 10, + Priority: 10, + IsOutbound: true, + }) + + Expect(err).ShouldNot(HaveOccurred()) + Expect(ctx.MockChannel.Msg).To(BeEquivalentTo(&vpp_ipsec.IpsecSpdEntryAddDelV2{ + IsAdd: true, + Entry: ipsec_types.IpsecSpdEntryV2{ + SpdID: 10, + SaID: 5, + Priority: 10, + IsOutbound: true, + RemoteAddressStart: ipToAddr("0.0.0.0"), + RemoteAddressStop: ipToAddr("255.255.255.255"), + LocalAddressStart: ipToAddr("0.0.0.0"), + LocalAddressStop: ipToAddr("255.255.255.255"), + RemotePortStop: 65535, + LocalPortStop: 65535, + }, + })) +} + +func TestVppDelSP(t *testing.T) { + ctx, ipSecHandler, _ := ipSecTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ipsec.IpsecSpdEntryAddDelV2Reply{}) + + err := ipSecHandler.DeleteSP(&ipsec.SecurityPolicy{ + SpdIndex: 10, + SaIndex: 2, + Priority: 5, + IsOutbound: true, + }) + + Expect(err).ShouldNot(HaveOccurred()) + Expect(ctx.MockChannel.Msg).To(BeEquivalentTo(&vpp_ipsec.IpsecSpdEntryAddDelV2{ + IsAdd: false, + Entry: ipsec_types.IpsecSpdEntryV2{ + SpdID: 10, + SaID: 2, + Priority: 5, + IsOutbound: true, + RemoteAddressStart: ipToAddr("0.0.0.0"), + RemoteAddressStop: ipToAddr("255.255.255.255"), + LocalAddressStart: ipToAddr("0.0.0.0"), + LocalAddressStop: ipToAddr("255.255.255.255"), + RemotePortStop: 65535, + LocalPortStop: 65535, + }, + })) +} + +func TestVppInterfaceAddSPD(t *testing.T) { + ctx, ipSecHandler, ifIndex := ipSecTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ipsec.IpsecInterfaceAddDelSpdReply{}) + + ifIndex.Put("if1", &ifaceidx.IfaceMetadata{SwIfIndex: 2}) + + err := ipSecHandler.AddSPDInterface(10, &ipsec.SecurityPolicyDatabase_Interface{ + Name: "if1", + }) + + Expect(err).ShouldNot(HaveOccurred()) + Expect(ctx.MockChannel.Msg).To(BeEquivalentTo(&vpp_ipsec.IpsecInterfaceAddDelSpd{ + IsAdd: true, + SpdID: 10, + SwIfIndex: 2, + })) +} + +func TestVppInterfaceDelSPD(t *testing.T) { + ctx, ipSecHandler, ifIndex := ipSecTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ipsec.IpsecInterfaceAddDelSpdReply{}) + + ifIndex.Put("if1", &ifaceidx.IfaceMetadata{SwIfIndex: 2}) + + err := ipSecHandler.DeleteSPDInterface(10, &ipsec.SecurityPolicyDatabase_Interface{ + Name: "if1", + }) + + Expect(err).ShouldNot(HaveOccurred()) + Expect(ctx.MockChannel.Msg).To(BeEquivalentTo(&vpp_ipsec.IpsecInterfaceAddDelSpd{ + IsAdd: false, + SpdID: 10, + SwIfIndex: 2, + })) +} + +func ipSecTestSetup(t *testing.T) (*vppmock.TestCtx, vppcalls.IPSecVppAPI, ifaceidx.IfaceMetadataIndexRW) { + ctx := vppmock.SetupTestCtx(t) + log := logrus.NewLogger("test-log") + ifIndex := ifaceidx.NewIfaceIndex(log, "ipsec-test-ifidx") + ipSecHandler := vpp2306.NewIPSecVppHandler(ctx.MockChannel, ifIndex, log) + return ctx, ipSecHandler, ifIndex +} + +func TestVppAddSA(t *testing.T) { + ctx, ipSecHandler, _ := ipSecTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ipsec.IpsecSadEntryAddDelV3Reply{}) + + cryptoKey, err := hex.DecodeString("") + Expect(err).To(BeNil()) + + err = ipSecHandler.AddSA(&ipsec.SecurityAssociation{ + Index: 1, + Spi: uint32(1001), + UseEsn: true, + UseAntiReplay: true, + Protocol: ipsec.SecurityAssociation_ESP, + }) + + Expect(err).ShouldNot(HaveOccurred()) + Expect(ctx.MockChannel.Msg).To(BeEquivalentTo(&vpp_ipsec.IpsecSadEntryAddDelV3{ + IsAdd: true, + Entry: ipsec_types.IpsecSadEntryV3{ + SadID: 1, + Spi: 1001, + CryptoKey: ipsec_types.Key{ + Length: uint8(len(cryptoKey)), + Data: cryptoKey, + }, + IntegrityKey: ipsec_types.Key{ + Length: uint8(len(cryptoKey)), + Data: cryptoKey, + }, + Flags: ipsec_types.IPSEC_API_SAD_FLAG_USE_ESN | ipsec_types.IPSEC_API_SAD_FLAG_USE_ANTI_REPLAY, + Protocol: ipsec_types.IPSEC_API_PROTO_ESP, + UDPSrcPort: ^uint16(0), + UDPDstPort: ^uint16(0), + }, + })) +} + +func TestVppDelSA(t *testing.T) { + ctx, ipSecHandler, _ := ipSecTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ipsec.IpsecSadEntryAddDelV3Reply{}) + + cryptoKey, err := hex.DecodeString("") + Expect(err).To(BeNil()) + + err = ipSecHandler.DeleteSA(&ipsec.SecurityAssociation{ + Index: 1, + Spi: uint32(1001), + UseEsn: true, + UseAntiReplay: true, + Protocol: ipsec.SecurityAssociation_ESP, + }) + + Expect(err).ShouldNot(HaveOccurred()) + Expect(ctx.MockChannel.Msg).To(BeEquivalentTo(&vpp_ipsec.IpsecSadEntryAddDelV3{ + IsAdd: false, + Entry: ipsec_types.IpsecSadEntryV3{ + SadID: 1, + Spi: 1001, + CryptoKey: ipsec_types.Key{ + Length: uint8(len(cryptoKey)), + Data: cryptoKey, + }, + IntegrityKey: ipsec_types.Key{ + Length: uint8(len(cryptoKey)), + Data: cryptoKey, + }, + Flags: ipsec_types.IPSEC_API_SAD_FLAG_USE_ESN | ipsec_types.IPSEC_API_SAD_FLAG_USE_ANTI_REPLAY, + Protocol: ipsec_types.IPSEC_API_PROTO_ESP, + UDPSrcPort: ^uint16(0), + UDPDstPort: ^uint16(0), + }, + })) +} + +func TestVppAddSATunnelMode(t *testing.T) { + ctx, ipSecHandler, _ := ipSecTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ipsec.IpsecSadEntryAddDelV3Reply{}) + + cryptoKey, err := hex.DecodeString("") + Expect(err).To(BeNil()) + + err = ipSecHandler.AddSA(&ipsec.SecurityAssociation{ + Index: 1, + Spi: uint32(1001), + TunnelSrcAddr: "10.1.0.1", + TunnelDstAddr: "20.1.0.1", + Protocol: ipsec.SecurityAssociation_ESP, + }) + + Expect(err).ShouldNot(HaveOccurred()) + Expect(ctx.MockChannel.Msg).To(BeEquivalentTo(&vpp_ipsec.IpsecSadEntryAddDelV3{ + IsAdd: true, + Entry: ipsec_types.IpsecSadEntryV3{ + SadID: 1, + Spi: 1001, + CryptoKey: ipsec_types.Key{ + Length: uint8(len(cryptoKey)), + Data: cryptoKey, + }, + IntegrityKey: ipsec_types.Key{ + Length: uint8(len(cryptoKey)), + Data: cryptoKey, + }, + Tunnel: tunnel_types.Tunnel{ + Src: ip_types.Address{ + Af: ip_types.ADDRESS_IP4, + Un: ip_types.AddressUnionIP4(ip_types.IP4Address{10, 1, 0, 1}), + }, + Dst: ip_types.Address{ + Af: ip_types.ADDRESS_IP4, + Un: ip_types.AddressUnionIP4(ip_types.IP4Address{20, 1, 0, 1}), + }, + }, + Flags: ipsec_types.IPSEC_API_SAD_FLAG_IS_TUNNEL, + Protocol: ipsec_types.IPSEC_API_PROTO_ESP, + UDPSrcPort: ^uint16(0), + UDPDstPort: ^uint16(0), + }, + })) +} + +func TestVppAddSATunnelModeIPv6(t *testing.T) { + ctx, ipSecHandler, _ := ipSecTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ipsec.IpsecSadEntryAddDelV3Reply{}) + + cryptoKey, err := hex.DecodeString("") + Expect(err).To(BeNil()) + + err = ipSecHandler.AddSA(&ipsec.SecurityAssociation{ + Index: 1, + Spi: uint32(1001), + TunnelSrcAddr: "1234::", + TunnelDstAddr: "abcd::", + Protocol: ipsec.SecurityAssociation_ESP, + }) + + Expect(err).ShouldNot(HaveOccurred()) + Expect(ctx.MockChannel.Msg).To(BeEquivalentTo(&vpp_ipsec.IpsecSadEntryAddDelV3{ + IsAdd: true, + Entry: ipsec_types.IpsecSadEntryV3{ + SadID: 1, + Spi: 1001, + CryptoKey: ipsec_types.Key{ + Length: uint8(len(cryptoKey)), + Data: cryptoKey, + }, + IntegrityKey: ipsec_types.Key{ + Length: uint8(len(cryptoKey)), + Data: cryptoKey, + }, + Tunnel: tunnel_types.Tunnel{ + Src: ip_types.Address{ + Af: ip_types.ADDRESS_IP6, + Un: ip_types.AddressUnion{XXX_UnionData: [16]byte{18, 52}}, + }, + Dst: ip_types.Address{ + Af: ip_types.ADDRESS_IP6, + Un: ip_types.AddressUnion{XXX_UnionData: [16]byte{171, 205}}, + }, + }, + Flags: ipsec_types.IPSEC_API_SAD_FLAG_IS_TUNNEL | ipsec_types.IPSEC_API_SAD_FLAG_IS_TUNNEL_V6, + Protocol: ipsec_types.IPSEC_API_PROTO_ESP, + UDPSrcPort: ^uint16(0), + UDPDstPort: ^uint16(0), + }, + })) +} + +func TestVppAddTunnelProtection(t *testing.T) { + ctx, ipSecHandler, ifIndex := ipSecTestSetup(t) + defer ctx.TeardownTestCtx() + + ifIndex.Put("ipip-tunnel", &ifaceidx.IfaceMetadata{SwIfIndex: 5}) + + ctx.MockVpp.MockReply(&vpp_ipsec.IpsecTunnelProtectUpdateReply{}) + err := ipSecHandler.AddTunnelProtection(&ipsec.TunnelProtection{ + Interface: "ipip-tunnel", + SaOut: []uint32{10}, + SaIn: []uint32{20, 30}, + }) + + Expect(err).ShouldNot(HaveOccurred()) + Expect(ctx.MockChannel.Msg).To(BeEquivalentTo(&vpp_ipsec.IpsecTunnelProtectUpdate{ + Tunnel: vpp_ipsec.IpsecTunnelProtect{ + SwIfIndex: 5, + SaOut: 10, + NSaIn: 2, + SaIn: []uint32{20, 30}, + }, + })) +} + +func TestVppDelTunnelProtection(t *testing.T) { + ctx, ipSecHandler, ifIndex := ipSecTestSetup(t) + defer ctx.TeardownTestCtx() + + ifIndex.Put("ipip-tunnel", &ifaceidx.IfaceMetadata{SwIfIndex: 5}) + + ctx.MockVpp.MockReply(&vpp_ipsec.IpsecTunnelProtectDelReply{}) + + err := ipSecHandler.DeleteTunnelProtection(&ipsec.TunnelProtection{ + Interface: "ipip-tunnel", + }) + + Expect(err).ShouldNot(HaveOccurred()) + Expect(ctx.MockChannel.Msg).To(BeEquivalentTo(&vpp_ipsec.IpsecTunnelProtectDel{ + SwIfIndex: 5, + })) +} diff --git a/plugins/vpp/ipsecplugin/vppcalls/vpp2306/vppcalls_handlers.go b/plugins/vpp/ipsecplugin/vppcalls/vpp2306/vppcalls_handlers.go new file mode 100644 index 0000000000..ed375ce11a --- /dev/null +++ b/plugins/vpp/ipsecplugin/vppcalls/vpp2306/vppcalls_handlers.go @@ -0,0 +1,75 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "fmt" + "net" + + govppapi "go.fd.io/govpp/api" + "go.ligato.io/cn-infra/v2/logging" + + vpp2306 "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip_types" + vpp_ipsec "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ipsec" + "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/ifaceidx" + "go.ligato.io/vpp-agent/v3/plugins/vpp/ipsecplugin/vppcalls" +) + +func init() { + var msgs []govppapi.Message + msgs = append(msgs, vpp_ipsec.AllMessages()...) + + vppcalls.AddHandlerVersion(vpp2306.Version, msgs, NewIPSecVppHandler) +} + +// IPSecVppHandler is accessor for IPSec-related vppcalls methods +type IPSecVppHandler struct { + callsChannel govppapi.Channel + ifIndexes ifaceidx.IfaceMetadataIndex + log logging.Logger +} + +func NewIPSecVppHandler(ch govppapi.Channel, ifIdx ifaceidx.IfaceMetadataIndex, log logging.Logger) vppcalls.IPSecVppAPI { + return &IPSecVppHandler{ch, ifIdx, log} +} + +func ipsecAddrToIP(addr ip_types.Address) net.IP { + if addr.Af == ip_types.ADDRESS_IP6 { + addrIP := addr.Un.GetIP6() + return net.IP(addrIP[:]) + } + addrIP := addr.Un.GetIP4() + return net.IP(addrIP[:]) +} + +func IPToAddress(ipstr string) (addr ip_types.Address, err error) { + netIP := net.ParseIP(ipstr) + if netIP == nil { + return ip_types.Address{}, fmt.Errorf("invalid IP: %q", ipstr) + } + if ip4 := netIP.To4(); ip4 == nil { + addr.Af = ip_types.ADDRESS_IP6 + var ip6addr ip_types.IP6Address + copy(ip6addr[:], netIP.To16()) + addr.Un.SetIP6(ip6addr) + } else { + addr.Af = ip_types.ADDRESS_IP4 + var ip4addr ip_types.IP4Address + copy(ip4addr[:], ip4) + addr.Un.SetIP4(ip4addr) + } + return +} diff --git a/plugins/vpp/l2plugin/l2plugin.go b/plugins/vpp/l2plugin/l2plugin.go index b141e511e6..126230f5fe 100644 --- a/plugins/vpp/l2plugin/l2plugin.go +++ b/plugins/vpp/l2plugin/l2plugin.go @@ -34,6 +34,7 @@ import ( _ "go.ligato.io/vpp-agent/v3/plugins/vpp/l2plugin/vppcalls/vpp2202" _ "go.ligato.io/vpp-agent/v3/plugins/vpp/l2plugin/vppcalls/vpp2210" + _ "go.ligato.io/vpp-agent/v3/plugins/vpp/l2plugin/vppcalls/vpp2306" ) // L2Plugin configures VPP bridge domains, L2 FIBs and xConnects using GoVPP. diff --git a/plugins/vpp/l2plugin/vppcalls/vpp2306/arp_term_vppcalls.go b/plugins/vpp/l2plugin/vppcalls/vpp2306/arp_term_vppcalls.go new file mode 100644 index 0000000000..04aaa553e9 --- /dev/null +++ b/plugins/vpp/l2plugin/vppcalls/vpp2306/arp_term_vppcalls.go @@ -0,0 +1,67 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "net" + + vpp_l2 "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/l2" +) + +func (h *BridgeDomainVppHandler) callBdIPMacAddDel(isAdd bool, bdID uint32, mac string, ip string) error { + ipAddr, err := ipToAddress(ip) + if err != nil { + return err + } + macAddr, err := net.ParseMAC(mac) + if err != nil { + return err + } + bdEntry := vpp_l2.BdIPMac{ + BdID: bdID, + IP: ipAddr, + } + copy(bdEntry.Mac[:], macAddr) + + req := &vpp_l2.BdIPMacAddDel{ + IsAdd: isAdd, + Entry: bdEntry, + } + + reply := &vpp_l2.BdIPMacAddDelReply{} + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + + return nil +} + +// AddArpTerminationTableEntry creates ARP termination entry for bridge domain. +func (h *BridgeDomainVppHandler) AddArpTerminationTableEntry(bdID uint32, mac string, ip string) error { + err := h.callBdIPMacAddDel(true, bdID, mac, ip) + if err != nil { + return err + } + return nil +} + +// RemoveArpTerminationTableEntry removes ARP termination entry from bridge domain. +func (h *BridgeDomainVppHandler) RemoveArpTerminationTableEntry(bdID uint32, mac string, ip string) error { + err := h.callBdIPMacAddDel(false, bdID, mac, ip) + if err != nil { + return err + } + return nil +} diff --git a/plugins/vpp/l2plugin/vppcalls/vpp2306/arp_term_vppcalls_test.go b/plugins/vpp/l2plugin/vppcalls/vpp2306/arp_term_vppcalls_test.go new file mode 100644 index 0000000000..8edd1ccbc2 --- /dev/null +++ b/plugins/vpp/l2plugin/vppcalls/vpp2306/arp_term_vppcalls_test.go @@ -0,0 +1,141 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306_test + +import ( + "testing" + + . "github.com/onsi/gomega" + + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ethernet_types" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip_types" + vpp_l2 "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/l2" +) + +func TestVppAddArpTerminationTableEntry(t *testing.T) { + ctx, bdHandler, _ := bdTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_l2.BdIPMacAddDelReply{}) + + err := bdHandler.AddArpTerminationTableEntry( + 4, "FF:FF:FF:FF:FF:FF", "192.168.4.4") + + Expect(err).ShouldNot(HaveOccurred()) + Expect(ctx.MockChannel.Msg).To(Equal(&vpp_l2.BdIPMacAddDel{ + Entry: vpp_l2.BdIPMac{ + BdID: 4, + IP: ip_types.Address{ + Af: ip_types.ADDRESS_IP4, + Un: ip_types.AddressUnionIP4( + ip_types.IP4Address{192, 168, 4, 4}, + ), + }, + Mac: ethernet_types.MacAddress{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, + }, + IsAdd: true, + })) +} + +func TestVppAddArpTerminationTableEntryIPv6(t *testing.T) { + ctx, bdHandler, _ := bdTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_l2.BdIPMacAddDelReply{}) + + err := bdHandler.AddArpTerminationTableEntry(4, "FF:FF:FF:FF:FF:FF", "2001:db9::54") + + Expect(err).ShouldNot(HaveOccurred()) + Expect(ctx.MockChannel.Msg).To(Equal(&vpp_l2.BdIPMacAddDel{ + Entry: vpp_l2.BdIPMac{ + BdID: 4, + IP: ip_types.Address{ + Af: ip_types.ADDRESS_IP6, + Un: ip_types.AddressUnionIP6( + ip_types.IP6Address{32, 1, 13, 185, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 84}, + ), + }, + Mac: ethernet_types.MacAddress{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, + }, + IsAdd: true, + })) +} + +func TestVppRemoveArpTerminationTableEntry(t *testing.T) { + ctx, bdHandler, _ := bdTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_l2.BdIPMacAddDelReply{}) + + err := bdHandler.RemoveArpTerminationTableEntry(4, "FF:FF:FF:FF:FF:FF", "192.168.4.4") + + Expect(err).ShouldNot(HaveOccurred()) + Expect(ctx.MockChannel.Msg).To(Equal(&vpp_l2.BdIPMacAddDel{ + Entry: vpp_l2.BdIPMac{ + BdID: 4, + IP: ip_types.Address{ + Af: ip_types.ADDRESS_IP4, + Un: ip_types.AddressUnionIP4( + ip_types.IP4Address{192, 168, 4, 4}, + ), + }, + Mac: ethernet_types.MacAddress{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, + }, + IsAdd: false, + })) +} + +func TestVppArpTerminationTableEntryMacError(t *testing.T) { + ctx, bdHandler, _ := bdTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_l2.BdIPMacAddDelReply{}) + + err := bdHandler.AddArpTerminationTableEntry(4, "in:va:li:d:ma:c", "192.168.4.4") + Expect(err).Should(HaveOccurred()) + + err = bdHandler.RemoveArpTerminationTableEntry(4, "in:va:li:d:ma:c", "192.168.4.4") + Expect(err).Should(HaveOccurred()) +} + +func TestVppArpTerminationTableEntryIpError(t *testing.T) { + ctx, bdHandler, _ := bdTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_l2.BdIPMacAddDelReply{}) + + err := bdHandler.AddArpTerminationTableEntry(4, "FF:FF:FF:FF:FF:FF", "") + Expect(err).Should(HaveOccurred()) + + err = bdHandler.RemoveArpTerminationTableEntry(4, "FF:FF:FF:FF:FF:FF", "") + Expect(err).Should(HaveOccurred()) +} + +func TestVppArpTerminationTableEntryError(t *testing.T) { + ctx, bdHandler, _ := bdTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_l2.BdIPMacAddDelReply{ + Retval: 1, + }) + + err := bdHandler.AddArpTerminationTableEntry(4, "FF:FF:FF:FF:FF:FF", "192.168.4.4") + Expect(err).Should(HaveOccurred()) + + ctx.MockVpp.MockReply(&vpp_l2.BridgeDomainAddDelReply{}) + + err = bdHandler.RemoveArpTerminationTableEntry(4, "FF:FF:FF:FF:FF:FF", "192.168.4.4") + Expect(err).Should(HaveOccurred()) +} diff --git a/plugins/vpp/l2plugin/vppcalls/vpp2306/bridge_domain_vppcalls.go b/plugins/vpp/l2plugin/vppcalls/vpp2306/bridge_domain_vppcalls.go new file mode 100644 index 0000000000..3e650d9fa2 --- /dev/null +++ b/plugins/vpp/l2plugin/vppcalls/vpp2306/bridge_domain_vppcalls.go @@ -0,0 +1,57 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + vpp_l2 "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/l2" + l2 "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/l2" +) + +// AddBridgeDomain adds new bridge domain. +func (h *BridgeDomainVppHandler) AddBridgeDomain(bdIdx uint32, bd *l2.BridgeDomain) error { + req := &vpp_l2.BridgeDomainAddDel{ + IsAdd: true, + BdID: bdIdx, + Learn: bd.Learn, + ArpTerm: bd.ArpTermination, + Flood: bd.Flood, + UuFlood: bd.UnknownUnicastFlood, + Forward: bd.Forward, + MacAge: uint8(bd.MacAge), + BdTag: bd.Name, + } + reply := &vpp_l2.BridgeDomainAddDelReply{} + + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + + return nil +} + +// DeleteBridgeDomain removes existing bridge domain. +func (h *BridgeDomainVppHandler) DeleteBridgeDomain(bdIdx uint32) error { + req := &vpp_l2.BridgeDomainAddDel{ + IsAdd: false, + BdID: bdIdx, + } + reply := &vpp_l2.BridgeDomainAddDelReply{} + + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + + return nil +} diff --git a/plugins/vpp/l2plugin/vppcalls/vpp2306/bridge_domain_vppcalls_test.go b/plugins/vpp/l2plugin/vppcalls/vpp2306/bridge_domain_vppcalls_test.go new file mode 100644 index 0000000000..e757333e8b --- /dev/null +++ b/plugins/vpp/l2plugin/vppcalls/vpp2306/bridge_domain_vppcalls_test.go @@ -0,0 +1,122 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306_test + +import ( + "testing" + + . "github.com/onsi/gomega" + "go.ligato.io/cn-infra/v2/logging/logrus" + + vpp_l2 "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/l2" + "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/ifaceidx" + "go.ligato.io/vpp-agent/v3/plugins/vpp/l2plugin/vppcalls" + vpp2306 "go.ligato.io/vpp-agent/v3/plugins/vpp/l2plugin/vppcalls/vpp2306" + "go.ligato.io/vpp-agent/v3/plugins/vpp/vppmock" + l2 "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/l2" +) + +const ( + dummyBridgeDomain = 4 + dummyBridgeDomainName = "bridge_domain" +) + +// Input test data for creating bridge domain +var createTestDataInBD = &l2.BridgeDomain{ + Name: dummyBridgeDomainName, + Flood: true, + UnknownUnicastFlood: true, + Forward: true, + Learn: true, + ArpTermination: true, + MacAge: 45, +} + +// Output test data for creating bridge domain +var createTestDataOutBD = &vpp_l2.BridgeDomainAddDel{ + BdID: dummyBridgeDomain, + Flood: true, + UuFlood: true, + Forward: true, + Learn: true, + ArpTerm: true, + MacAge: 45, + BdTag: dummyBridgeDomainName, + IsAdd: true, +} + +// Output test data for deleting bridge domain +var deleteTestDataOutBd = &vpp_l2.BridgeDomainAddDel{ + BdID: dummyBridgeDomain, + IsAdd: false, +} + +func TestVppAddBridgeDomain(t *testing.T) { + ctx, bdHandler, _ := bdTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_l2.BridgeDomainAddDelReply{}) + err := bdHandler.AddBridgeDomain(dummyBridgeDomain, createTestDataInBD) + + Expect(err).ShouldNot(HaveOccurred()) + Expect(ctx.MockChannel.Msg).To(Equal(createTestDataOutBD)) +} + +func TestVppAddBridgeDomainError(t *testing.T) { + ctx, bdHandler, _ := bdTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_l2.BridgeDomainAddDelReply{Retval: 1}) + ctx.MockVpp.MockReply(&vpp_l2.SwInterfaceSetL2Bridge{}) + + err := bdHandler.AddBridgeDomain(dummyBridgeDomain, createTestDataInBD) + Expect(err).Should(HaveOccurred()) + + err = bdHandler.AddBridgeDomain(dummyBridgeDomain, createTestDataInBD) + Expect(err).Should(HaveOccurred()) +} + +func TestVppDeleteBridgeDomain(t *testing.T) { + ctx, bdHandler, _ := bdTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_l2.BridgeDomainAddDelReply{}) + err := bdHandler.DeleteBridgeDomain(dummyBridgeDomain) + + Expect(err).ShouldNot(HaveOccurred()) + Expect(ctx.MockChannel.Msg).To(Equal(deleteTestDataOutBd)) +} + +func TestVppDeleteBridgeDomainError(t *testing.T) { + ctx, bdHandler, _ := bdTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_l2.BridgeDomainAddDelReply{Retval: 1}) + ctx.MockVpp.MockReply(&vpp_l2.SwInterfaceSetL2Bridge{}) + + err := bdHandler.DeleteBridgeDomain(dummyBridgeDomain) + Expect(err).Should(HaveOccurred()) + + err = bdHandler.DeleteBridgeDomain(dummyBridgeDomain) + Expect(err).Should(HaveOccurred()) +} + +func bdTestSetup(t *testing.T) (*vppmock.TestCtx, vppcalls.BridgeDomainVppAPI, ifaceidx.IfaceMetadataIndexRW) { + ctx := vppmock.SetupTestCtx(t) + log := logrus.NewLogger("test-log") + ifIndex := ifaceidx.NewIfaceIndex(log, "bd-test-ifidx") + bdHandler := vpp2306.NewL2VppHandler(ctx.MockChannel, ifIndex, nil, log) + return ctx, bdHandler, ifIndex +} diff --git a/plugins/vpp/l2plugin/vppcalls/vpp2306/doc.go b/plugins/vpp/l2plugin/vppcalls/vpp2306/doc.go new file mode 100644 index 0000000000..020a100e96 --- /dev/null +++ b/plugins/vpp/l2plugin/vppcalls/vpp2306/doc.go @@ -0,0 +1,17 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package vpp2306 contains wrappers over VPP binary APIs for bridge-domains, +// and L2 FIBs and XConnect pairs and helpers for dumping them. +package vpp2306 diff --git a/plugins/vpp/l2plugin/vppcalls/vpp2306/dump_vppcalls.go b/plugins/vpp/l2plugin/vppcalls/vpp2306/dump_vppcalls.go new file mode 100644 index 0000000000..fd239ef522 --- /dev/null +++ b/plugins/vpp/l2plugin/vppcalls/vpp2306/dump_vppcalls.go @@ -0,0 +1,245 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "net" + "strings" + + "github.com/pkg/errors" + + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface_types" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip_types" + vpp_l2 "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/l2" + "go.ligato.io/vpp-agent/v3/plugins/vpp/l2plugin/vppcalls" + l2 "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/l2" +) + +// DumpBridgeDomains implements bridge domain handler. +func (h *BridgeDomainVppHandler) DumpBridgeDomains() ([]*vppcalls.BridgeDomainDetails, error) { + // At first prepare bridge domain ARP termination table which needs to be dumped separately. + bdArpTab, err := h.dumpBridgeDomainMacTable() + if err != nil { + return nil, errors.Errorf("failed to dump arp termination table: %v", err) + } + + // list of resulting BDs + var bds []*vppcalls.BridgeDomainDetails + + // dump bridge domains + reqCtx := h.callsChannel.SendMultiRequest(&vpp_l2.BridgeDomainDump{ + BdID: ^uint32(0), + SwIfIndex: ^interface_types.InterfaceIndex(0), + }) + + for { + bdDetails := &vpp_l2.BridgeDomainDetails{} + stop, err := reqCtx.ReceiveReply(bdDetails) + if stop { + break + } + if err != nil { + return nil, err + } + + // bridge domain metadata + bdData := &vppcalls.BridgeDomainDetails{ + Bd: &l2.BridgeDomain{ + Name: strings.Trim(bdDetails.BdTag, "\x00"), + Flood: bdDetails.Flood, + UnknownUnicastFlood: bdDetails.UuFlood, + Forward: bdDetails.Forward, + Learn: bdDetails.Learn, + ArpTermination: bdDetails.ArpTerm, + MacAge: uint32(bdDetails.MacAge), + }, + Meta: &vppcalls.BridgeDomainMeta{ + BdID: bdDetails.BdID, + }, + } + + // bridge domain interfaces + for _, iface := range bdDetails.SwIfDetails { + ifaceName, _, exists := h.ifIndexes.LookupBySwIfIndex(uint32(iface.SwIfIndex)) + if !exists { + h.log.Warnf("Bridge domain dump: interface name for index %d not found", iface.SwIfIndex) + continue + } + // Bvi + var bvi bool + if iface.SwIfIndex == bdDetails.BviSwIfIndex { + bvi = true + } + // add interface entry + bdData.Bd.Interfaces = append(bdData.Bd.Interfaces, &l2.BridgeDomain_Interface{ + Name: ifaceName, + BridgedVirtualInterface: bvi, + SplitHorizonGroup: uint32(iface.Shg), + }) + } + + // Add ARP termination entries. + arpTable, ok := bdArpTab[bdDetails.BdID] + if ok { + bdData.Bd.ArpTerminationTable = arpTable + } + + bds = append(bds, bdData) + } + + return bds, nil +} + +// Reads ARP termination table from all bridge domains. Result is then added to bridge domains. +func (h *BridgeDomainVppHandler) dumpBridgeDomainMacTable() (map[uint32][]*l2.BridgeDomain_ArpTerminationEntry, error) { + bdArpTable := make(map[uint32][]*l2.BridgeDomain_ArpTerminationEntry) + req := &vpp_l2.BdIPMacDump{BdID: ^uint32(0)} + + reqCtx := h.callsChannel.SendMultiRequest(req) + for { + msg := &vpp_l2.BdIPMacDetails{} + stop, err := reqCtx.ReceiveReply(msg) + if err != nil { + return nil, err + } + if stop { + break + } + + // Prepare ARP entry + arpEntry := &l2.BridgeDomain_ArpTerminationEntry{} + arpEntry.IpAddress = parseAddressToString(msg.Entry.IP) + arpEntry.PhysAddress = net.HardwareAddr(msg.Entry.Mac[:]).String() + + // Add ARP entry to result map + bdArpTable[msg.Entry.BdID] = append(bdArpTable[msg.Entry.BdID], arpEntry) + } + + return bdArpTable, nil +} + +// DumpL2FIBs dumps VPP L2 FIB table entries into the northbound API +// data structure map indexed by destination MAC address. +func (h *FIBVppHandler) DumpL2FIBs() (map[string]*vppcalls.FibTableDetails, error) { + // map for the resulting FIBs + fibs := make(map[string]*vppcalls.FibTableDetails) + + reqCtx := h.callsChannel.SendMultiRequest(&vpp_l2.L2FibTableDump{BdID: ^uint32(0)}) + for { + fibDetails := &vpp_l2.L2FibTableDetails{} + stop, err := reqCtx.ReceiveReply(fibDetails) + if stop { + break // Break from the loop. + } + if err != nil { + return nil, err + } + + mac := net.HardwareAddr(fibDetails.Mac[:]).String() + var action l2.FIBEntry_Action + if fibDetails.FilterMac { + action = l2.FIBEntry_DROP + } else { + action = l2.FIBEntry_FORWARD + } + + // Interface name (only for FORWARD entries) + var ifName string + if action == l2.FIBEntry_FORWARD { + var exists bool + ifName, _, exists = h.ifIndexes.LookupBySwIfIndex(uint32(fibDetails.SwIfIndex)) + if !exists { + h.log.Warnf("FIB dump: interface name for index %d not found", fibDetails.SwIfIndex) + continue + } + } + // Bridge domain name + bdName, _, exists := h.bdIndexes.LookupByIndex(fibDetails.BdID) + if !exists { + h.log.Warnf("FIB dump: bridge domain name for index %d not found", fibDetails.BdID) + continue + } + + fibs[mac] = &vppcalls.FibTableDetails{ + Fib: &l2.FIBEntry{ + PhysAddress: mac, + BridgeDomain: bdName, + Action: action, + OutgoingInterface: ifName, + StaticConfig: fibDetails.StaticMac, + BridgedVirtualInterface: fibDetails.BviMac, + }, + Meta: &vppcalls.FibMeta{ + BdID: fibDetails.BdID, + IfIdx: uint32(fibDetails.SwIfIndex), + }, + } + } + + return fibs, nil +} + +// DumpXConnectPairs implements xconnect handler. +func (h *XConnectVppHandler) DumpXConnectPairs() (map[uint32]*vppcalls.XConnectDetails, error) { + // map for the resulting xconnect pairs + xpairs := make(map[uint32]*vppcalls.XConnectDetails) + reqCtx := h.callsChannel.SendMultiRequest(&vpp_l2.L2XconnectDump{}) + for { + pairs := &vpp_l2.L2XconnectDetails{} + stop, err := reqCtx.ReceiveReply(pairs) + if stop { + break + } + if err != nil { + return nil, err + } + + // Find interface names + rxIfaceName, _, exists := h.ifIndexes.LookupBySwIfIndex(uint32(pairs.RxSwIfIndex)) + if !exists { + h.log.Warnf("XConnect dump: rx interface name for index %d not found", pairs.RxSwIfIndex) + continue + } + txIfaceName, _, exists := h.ifIndexes.LookupBySwIfIndex(uint32(pairs.TxSwIfIndex)) + if !exists { + h.log.Warnf("XConnect dump: tx interface name for index %d not found", pairs.TxSwIfIndex) + continue + } + + xpairs[uint32(pairs.RxSwIfIndex)] = &vppcalls.XConnectDetails{ + Xc: &l2.XConnectPair{ + ReceiveInterface: rxIfaceName, + TransmitInterface: txIfaceName, + }, + Meta: &vppcalls.XcMeta{ + ReceiveInterfaceSwIfIdx: uint32(pairs.RxSwIfIndex), + TransmitInterfaceSwIfIdx: uint32(pairs.TxSwIfIndex), + }, + } + } + return xpairs, nil +} + +func parseAddressToString(address ip_types.Address) string { + var nhIP net.IP = make([]byte, 16) + copy(nhIP[:], address.Un.XXX_UnionData[:]) + if address.Af == ip_types.ADDRESS_IP4 { + return nhIP[:4].To4().String() + } + if address.Af == ip_types.ADDRESS_IP6 { + return nhIP.To16().String() + } + return "" +} diff --git a/plugins/vpp/l2plugin/vppcalls/vpp2306/dump_vppcalls_test.go b/plugins/vpp/l2plugin/vppcalls/vpp2306/dump_vppcalls_test.go new file mode 100644 index 0000000000..c71aedd2a4 --- /dev/null +++ b/plugins/vpp/l2plugin/vppcalls/vpp2306/dump_vppcalls_test.go @@ -0,0 +1,307 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306_test + +import ( + "testing" + + . "github.com/onsi/gomega" + govppapi "go.fd.io/govpp/api" + + "go.ligato.io/vpp-agent/v3/pkg/idxvpp" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ethernet_types" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface_types" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip_types" + vpp_l2 "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/l2" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/memclnt" + "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/ifaceidx" + "go.ligato.io/vpp-agent/v3/plugins/vpp/l2plugin/vppcalls" + "go.ligato.io/vpp-agent/v3/plugins/vpp/vppmock" + l2 "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/l2" +) + +var testDataInMessagesBDs = []govppapi.Message{ + &vpp_l2.BridgeDomainDetails{ + BdID: 4, + Flood: true, UuFlood: true, Forward: true, Learn: true, ArpTerm: true, MacAge: 140, + SwIfDetails: []vpp_l2.BridgeDomainSwIf{ + {SwIfIndex: 5}, + {SwIfIndex: 7}, + }, + }, + &vpp_l2.BridgeDomainDetails{ + BdID: 5, + Flood: false, UuFlood: false, Forward: false, Learn: false, ArpTerm: false, MacAge: 141, + SwIfDetails: []vpp_l2.BridgeDomainSwIf{ + {SwIfIndex: 5}, + {SwIfIndex: 8}, + }, + }, +} + +var testDataOutMessage = []*vppcalls.BridgeDomainDetails{ + { + Bd: &l2.BridgeDomain{ + Flood: true, + UnknownUnicastFlood: true, + Forward: true, + Learn: true, + ArpTermination: true, + MacAge: 140, + Interfaces: []*l2.BridgeDomain_Interface{ + { + Name: "if1", + }, + { + Name: "if2", + }, + }, + }, + Meta: &vppcalls.BridgeDomainMeta{ + BdID: 4, + }, + }, { + Bd: &l2.BridgeDomain{ + Flood: false, + UnknownUnicastFlood: false, + Forward: false, + Learn: false, + ArpTermination: false, + MacAge: 141, + Interfaces: []*l2.BridgeDomain_Interface{ + { + Name: "if1", + }, + { + Name: "if3", + }, + }, + ArpTerminationTable: []*l2.BridgeDomain_ArpTerminationEntry{ + { + IpAddress: "192.168.0.1", + PhysAddress: "aa:aa:aa:aa:aa:aa", + }, + }, + }, + Meta: &vppcalls.BridgeDomainMeta{ + BdID: 5, + }, + }, +} + +// TestDumpBridgeDomains tests DumpBridgeDomains method +func TestDumpBridgeDomains(t *testing.T) { + ctx, bdHandler, ifIndexes := bdTestSetup(t) + defer ctx.TeardownTestCtx() + + ifIndexes.Put("if1", &ifaceidx.IfaceMetadata{SwIfIndex: 5}) + ifIndexes.Put("if2", &ifaceidx.IfaceMetadata{SwIfIndex: 7}) + + ctx.MockReplies([]*vppmock.HandleReplies{ + { + Name: (&vpp_l2.BdIPMacDump{}).GetMessageName(), + Ping: true, + Message: &vpp_l2.BdIPMacDetails{}, + }, + { + Name: (&vpp_l2.BridgeDomainDump{}).GetMessageName(), + Ping: true, + Message: testDataInMessagesBDs[0], + }, + }) + + bridgeDomains, err := bdHandler.DumpBridgeDomains() + + Expect(err).To(BeNil()) + Expect(bridgeDomains).To(HaveLen(1)) + Expect(bridgeDomains[0]).To(Equal(testDataOutMessage[0])) + + ctx.MockVpp.MockReply(&vpp_l2.BridgeDomainAddDelReply{}) + _, err = bdHandler.DumpBridgeDomains() + Expect(err).Should(HaveOccurred()) +} + +// TestDumpBridgeDomains tests DumpBridgeDomains method +func TestDumpBridgeDomainsWithARP(t *testing.T) { + ctx, bdHandler, ifIndexes := bdTestSetup(t) + defer ctx.TeardownTestCtx() + + ifIndexes.Put("if1", &ifaceidx.IfaceMetadata{SwIfIndex: 5}) + ifIndexes.Put("if3", &ifaceidx.IfaceMetadata{SwIfIndex: 8}) + + ctx.MockReplies([]*vppmock.HandleReplies{ + { + Name: (&vpp_l2.BdIPMacDump{}).GetMessageName(), + Ping: true, + Message: &vpp_l2.BdIPMacDetails{ + Entry: vpp_l2.BdIPMac{ + BdID: 5, + IP: ip_types.Address{ + Af: ip_types.ADDRESS_IP4, + Un: ip_types.AddressUnionIP4( + ip_types.IP4Address{192, 168, 0, 1}, + ), + }, + Mac: ethernet_types.MacAddress{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, + }, + }, + }, + { + Name: (&vpp_l2.BridgeDomainDump{}).GetMessageName(), + Ping: true, + Message: testDataInMessagesBDs[1], + }, + }) + + bridgeDomains, err := bdHandler.DumpBridgeDomains() + + Expect(err).To(BeNil()) + Expect(bridgeDomains).To(HaveLen(1)) + Expect(bridgeDomains[0]).To(Equal(testDataOutMessage[1])) + + ctx.MockVpp.MockReply(&vpp_l2.BridgeDomainAddDelReply{}) + _, err = bdHandler.DumpBridgeDomains() + Expect(err).Should(HaveOccurred()) +} + +var testDataInMessagesFIBs = []govppapi.Message{ + &vpp_l2.L2FibTableDetails{ + BdID: 10, + Mac: ethernet_types.MacAddress{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, + BviMac: false, SwIfIndex: ^interface_types.InterfaceIndex(0), FilterMac: true, StaticMac: false, + }, + &vpp_l2.L2FibTableDetails{ + BdID: 20, + Mac: ethernet_types.MacAddress{0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB}, + BviMac: true, SwIfIndex: 1, FilterMac: false, StaticMac: true, + }, +} + +var testDataOutFIBs = []*vppcalls.FibTableDetails{ + { + Fib: &l2.FIBEntry{ + PhysAddress: "aa:aa:aa:aa:aa:aa", + BridgeDomain: "bd1", + Action: l2.FIBEntry_DROP, + StaticConfig: false, + BridgedVirtualInterface: false, + OutgoingInterface: "", + }, + Meta: &vppcalls.FibMeta{ + BdID: 10, + IfIdx: ^uint32(0), + }, + }, + { + Fib: &l2.FIBEntry{ + PhysAddress: "bb:bb:bb:bb:bb:bb", + BridgeDomain: "bd2", + Action: l2.FIBEntry_FORWARD, + StaticConfig: true, + BridgedVirtualInterface: true, + OutgoingInterface: "if1", + }, + Meta: &vppcalls.FibMeta{ + BdID: 20, + IfIdx: 1, + }, + }, +} + +// Scenario: +// - 2 FIB entries in VPP +// TestDumpFIBTableEntries tests DumpFIBTableEntries method +func TestDumpFIBTableEntries(t *testing.T) { + ctx, fibHandler, ifIndexes, bdIndexes := fibTestSetup(t) + defer ctx.TeardownTestCtx() + + ifIndexes.Put("if1", &ifaceidx.IfaceMetadata{SwIfIndex: 1}) + bdIndexes.Put("bd1", &idxvpp.OnlyIndex{Index: 10}) + bdIndexes.Put("bd2", &idxvpp.OnlyIndex{Index: 20}) + + ctx.MockVpp.MockReply(testDataInMessagesFIBs...) + ctx.MockVpp.MockReply(&memclnt.ControlPingReply{}) + + fibTable, err := fibHandler.DumpL2FIBs() + Expect(err).To(BeNil()) + Expect(fibTable).To(HaveLen(2)) + Expect(fibTable["aa:aa:aa:aa:aa:aa"]).To(Equal(testDataOutFIBs[0])) + Expect(fibTable["bb:bb:bb:bb:bb:bb"]).To(Equal(testDataOutFIBs[1])) + + ctx.MockVpp.MockReply(&vpp_l2.BridgeDomainAddDelReply{}) + _, err = fibHandler.DumpL2FIBs() + Expect(err).Should(HaveOccurred()) +} + +var testDataInXConnect = []govppapi.Message{ + &vpp_l2.L2XconnectDetails{ + RxSwIfIndex: 1, + TxSwIfIndex: 2, + }, + &vpp_l2.L2XconnectDetails{ + RxSwIfIndex: 3, + TxSwIfIndex: 4, + }, +} + +var testDataOutXconnect = []*vppcalls.XConnectDetails{ + { + Xc: &l2.XConnectPair{ + ReceiveInterface: "if1", + TransmitInterface: "if2", + }, + Meta: &vppcalls.XcMeta{ + ReceiveInterfaceSwIfIdx: 1, + TransmitInterfaceSwIfIdx: 2, + }, + }, + { + Xc: &l2.XConnectPair{ + ReceiveInterface: "if3", + TransmitInterface: "if4", + }, + Meta: &vppcalls.XcMeta{ + ReceiveInterfaceSwIfIdx: 3, + TransmitInterfaceSwIfIdx: 4, + }, + }, +} + +// Scenario: +// - 2 Xconnect entries in VPP +// TestDumpXConnectPairs tests DumpXConnectPairs method +func TestDumpXConnectPairs(t *testing.T) { + ctx, xcHandler, ifIndex := xcTestSetup(t) + defer ctx.TeardownTestCtx() + + ifIndex.Put("if1", &ifaceidx.IfaceMetadata{SwIfIndex: 1}) + ifIndex.Put("if2", &ifaceidx.IfaceMetadata{SwIfIndex: 2}) + ifIndex.Put("if3", &ifaceidx.IfaceMetadata{SwIfIndex: 3}) + ifIndex.Put("if4", &ifaceidx.IfaceMetadata{SwIfIndex: 4}) + + ctx.MockVpp.MockReply(testDataInXConnect...) + ctx.MockVpp.MockReply(&memclnt.ControlPingReply{}) + + xConnectPairs, err := xcHandler.DumpXConnectPairs() + + Expect(err).To(BeNil()) + Expect(xConnectPairs).To(HaveLen(2)) + Expect(xConnectPairs[1]).To(Equal(testDataOutXconnect[0])) + Expect(xConnectPairs[3]).To(Equal(testDataOutXconnect[1])) + + ctx.MockVpp.MockReply(&vpp_l2.BridgeDomainAddDelReply{}) + _, err = xcHandler.DumpXConnectPairs() + Expect(err).Should(HaveOccurred()) +} diff --git a/plugins/vpp/l2plugin/vppcalls/vpp2306/interface_vppcalls.go b/plugins/vpp/l2plugin/vppcalls/vpp2306/interface_vppcalls.go new file mode 100644 index 0000000000..2a0a43e1ea --- /dev/null +++ b/plugins/vpp/l2plugin/vppcalls/vpp2306/interface_vppcalls.go @@ -0,0 +1,70 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "fmt" + + "github.com/pkg/errors" + + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface_types" + vpp_l2 "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/l2" + l2 "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/l2" +) + +// AddInterfaceToBridgeDomain puts interface into bridge domain. +func (h *BridgeDomainVppHandler) AddInterfaceToBridgeDomain(bdIdx uint32, ifaceCfg *l2.BridgeDomain_Interface) error { + ifaceMeta, found := h.ifIndexes.LookupByName(ifaceCfg.Name) + if !found { + return errors.New("failed to get interface metadata") + } + if err := h.addDelInterfaceToBridgeDomain(bdIdx, ifaceCfg, ifaceMeta.GetIndex(), true); err != nil { + return err + } + return nil +} + +// DeleteInterfaceFromBridgeDomain removes interface from bridge domain. +func (h *BridgeDomainVppHandler) DeleteInterfaceFromBridgeDomain(bdIdx uint32, ifaceCfg *l2.BridgeDomain_Interface) error { + ifaceMeta, found := h.ifIndexes.LookupByName(ifaceCfg.Name) + if !found { + return errors.New("failed to get interface metadata") + } + if err := h.addDelInterfaceToBridgeDomain(bdIdx, ifaceCfg, ifaceMeta.GetIndex(), false); err != nil { + return err + } + return nil +} + +func (h *BridgeDomainVppHandler) addDelInterfaceToBridgeDomain(bdIdx uint32, ifaceCfg *l2.BridgeDomain_Interface, + ifIdx uint32, add bool) error { + req := &vpp_l2.SwInterfaceSetL2Bridge{ + BdID: bdIdx, + RxSwIfIndex: interface_types.InterfaceIndex(ifIdx), + Shg: uint8(ifaceCfg.SplitHorizonGroup), + Enable: add, + } + // Set as BVI. + if ifaceCfg.BridgedVirtualInterface { + req.PortType = vpp_l2.L2_API_PORT_TYPE_BVI + } + reply := &vpp_l2.SwInterfaceSetL2BridgeReply{} + + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return fmt.Errorf("%s returned error: %v", reply.GetMessageName(), err) + } + + return nil +} diff --git a/plugins/vpp/l2plugin/vppcalls/vpp2306/interface_vppcalls_test.go b/plugins/vpp/l2plugin/vppcalls/vpp2306/interface_vppcalls_test.go new file mode 100644 index 0000000000..659d8a558a --- /dev/null +++ b/plugins/vpp/l2plugin/vppcalls/vpp2306/interface_vppcalls_test.go @@ -0,0 +1,144 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306_test + +import ( + "testing" + + . "github.com/onsi/gomega" + + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface_types" + vpp_l2 "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/l2" + "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/ifaceidx" + l2 "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/l2" +) + +func TestAddInterfaceToBridgeDomain(t *testing.T) { + ctx, bdHandler, ifaceIdx := bdTestSetup(t) + defer ctx.TeardownTestCtx() + + ifaceIdx.Put("if1", &ifaceidx.IfaceMetadata{SwIfIndex: 1}) + + ctx.MockVpp.MockReply(&vpp_l2.SwInterfaceSetL2BridgeReply{}) + err := bdHandler.AddInterfaceToBridgeDomain(1, &l2.BridgeDomain_Interface{ + Name: "if1", + BridgedVirtualInterface: true, + SplitHorizonGroup: 0, + }) + + Expect(err).To(BeNil()) + Expect(ctx.MockChannel.Msgs).To(HaveLen(1)) + msg := ctx.MockChannel.Msgs[0] + Expect(msg).To(BeEquivalentTo(&vpp_l2.SwInterfaceSetL2Bridge{ + RxSwIfIndex: interface_types.InterfaceIndex(1), + BdID: 1, + Shg: uint8(0), + PortType: vpp_l2.L2_API_PORT_TYPE_BVI, + Enable: true, + })) +} + +func TestAddMissingInterfaceToBridgeDomain(t *testing.T) { + ctx, bdHandler, _ := bdTestSetup(t) + defer ctx.TeardownTestCtx() + + // missing: ifaceIdx.Put("if1", &ifaceidx.IfaceMetadata{SwIfIndex: 1}) + + ctx.MockVpp.MockReply(&vpp_l2.SwInterfaceSetL2BridgeReply{}) + err := bdHandler.AddInterfaceToBridgeDomain(1, &l2.BridgeDomain_Interface{ + Name: "if1", + BridgedVirtualInterface: true, + SplitHorizonGroup: 0, + }) + + Expect(err).ToNot(BeNil()) + Expect(ctx.MockChannel.Msgs).To(BeEmpty()) +} + +func TestAddInterfaceToBridgeDomainWithError(t *testing.T) { + ctx, bdHandler, ifaceIdx := bdTestSetup(t) + defer ctx.TeardownTestCtx() + + ifaceIdx.Put("if1", &ifaceidx.IfaceMetadata{SwIfIndex: 1}) + + ctx.MockVpp.MockReply(&vpp_l2.SwInterfaceSetL2Bridge{}) // wrong reply message type + err := bdHandler.AddInterfaceToBridgeDomain(1, &l2.BridgeDomain_Interface{ + Name: "if1", + BridgedVirtualInterface: true, + SplitHorizonGroup: 0, + }) + + Expect(err).ToNot(BeNil()) + Expect(ctx.MockChannel.Msgs).To(HaveLen(1)) +} + +func TestAddInterfaceToBridgeDomainWithNonZeroRetval(t *testing.T) { + ctx, bdHandler, ifaceIdx := bdTestSetup(t) + defer ctx.TeardownTestCtx() + + ifaceIdx.Put("if1", &ifaceidx.IfaceMetadata{SwIfIndex: 1}) + + ctx.MockVpp.MockReply(&vpp_l2.SwInterfaceSetL2BridgeReply{ + Retval: 1, + }) + err := bdHandler.AddInterfaceToBridgeDomain(1, &l2.BridgeDomain_Interface{ + Name: "if1", + BridgedVirtualInterface: true, + SplitHorizonGroup: 0, + }) + + Expect(err).ToNot(BeNil()) + Expect(ctx.MockChannel.Msgs).To(HaveLen(1)) +} + +func TestDeleteInterfaceFromBridgeDomain(t *testing.T) { + ctx, bdHandler, ifaceIdx := bdTestSetup(t) + defer ctx.TeardownTestCtx() + + ifaceIdx.Put("if1", &ifaceidx.IfaceMetadata{SwIfIndex: 10}) + + ctx.MockVpp.MockReply(&vpp_l2.SwInterfaceSetL2BridgeReply{}) + err := bdHandler.DeleteInterfaceFromBridgeDomain(4, &l2.BridgeDomain_Interface{ + Name: "if1", + SplitHorizonGroup: 12, + }) + + Expect(err).To(BeNil()) + Expect(ctx.MockChannel.Msgs).To(HaveLen(1)) + msg := ctx.MockChannel.Msgs[0] + Expect(msg).To(BeEquivalentTo(&vpp_l2.SwInterfaceSetL2Bridge{ + RxSwIfIndex: interface_types.InterfaceIndex(10), + BdID: 4, + Shg: uint8(12), + PortType: vpp_l2.L2_API_PORT_TYPE_NORMAL, + Enable: false, + })) +} + +func TestDeleteMissingInterfaceFromBridgeDomain(t *testing.T) { + ctx, bdHandler, _ := bdTestSetup(t) + defer ctx.TeardownTestCtx() + + // missing: ifaceIdx.Put("if1", &ifaceidx.IfaceMetadata{SwIfIndex: 10}) + + ctx.MockVpp.MockReply(&vpp_l2.SwInterfaceSetL2BridgeReply{}) + err := bdHandler.DeleteInterfaceFromBridgeDomain(4, &l2.BridgeDomain_Interface{ + Name: "if1", + SplitHorizonGroup: 12, + }) + + Expect(err).ToNot(BeNil()) + Expect(ctx.MockChannel.Msgs).To(BeEmpty()) +} diff --git a/plugins/vpp/l2plugin/vppcalls/vpp2306/l2fib_vppcalls.go b/plugins/vpp/l2plugin/vppcalls/vpp2306/l2fib_vppcalls.go new file mode 100644 index 0000000000..b51c04a1bb --- /dev/null +++ b/plugins/vpp/l2plugin/vppcalls/vpp2306/l2fib_vppcalls.go @@ -0,0 +1,83 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "errors" + "net" + + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ethernet_types" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface_types" + vpp_l2 "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/l2" + l2 "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/l2" +) + +// AddL2FIB creates L2 FIB table entry. +func (h *FIBVppHandler) AddL2FIB(fib *l2.FIBEntry) error { + return h.l2fibAddDel(fib, true) +} + +// DeleteL2FIB removes existing L2 FIB table entry. +func (h *FIBVppHandler) DeleteL2FIB(fib *l2.FIBEntry) error { + return h.l2fibAddDel(fib, false) +} + +func (h *FIBVppHandler) l2fibAddDel(fib *l2.FIBEntry, isAdd bool) (err error) { + // get bridge domain metadata + bdMeta, found := h.bdIndexes.LookupByName(fib.BridgeDomain) + if !found { + return errors.New("failed to get bridge domain metadata") + } + + // get outgoing interface index + swIfIndex := ^uint32(0) // ~0 is used by DROP entries + if fib.Action == l2.FIBEntry_FORWARD { + ifaceMeta, found := h.ifIndexes.LookupByName(fib.OutgoingInterface) + if !found { + return errors.New("failed to get interface metadata") + } + swIfIndex = ifaceMeta.GetIndex() + } + + // parse MAC address + var mac []byte + if fib.PhysAddress != "" { + mac, err = net.ParseMAC(fib.PhysAddress) + if err != nil { + return err + } + } + + var macAddr ethernet_types.MacAddress + copy(macAddr[:], mac) + + // add L2 FIB + req := &vpp_l2.L2fibAddDel{ + IsAdd: isAdd, + Mac: macAddr, + BdID: bdMeta.GetIndex(), + SwIfIndex: interface_types.InterfaceIndex(swIfIndex), + BviMac: fib.BridgedVirtualInterface, + StaticMac: fib.StaticConfig, + FilterMac: fib.Action == l2.FIBEntry_DROP, + } + reply := &vpp_l2.L2fibAddDelReply{} + + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + + return nil +} diff --git a/plugins/vpp/l2plugin/vppcalls/vpp2306/l2fib_vppcalls_test.go b/plugins/vpp/l2plugin/vppcalls/vpp2306/l2fib_vppcalls_test.go new file mode 100644 index 0000000000..4117af4c29 --- /dev/null +++ b/plugins/vpp/l2plugin/vppcalls/vpp2306/l2fib_vppcalls_test.go @@ -0,0 +1,113 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306_test + +import ( + "testing" + + . "github.com/onsi/gomega" + "go.ligato.io/cn-infra/v2/logging/logrus" + + "go.ligato.io/vpp-agent/v3/pkg/idxvpp" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ethernet_types" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface_types" + vpp_l2 "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/l2" + "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/ifaceidx" + "go.ligato.io/vpp-agent/v3/plugins/vpp/l2plugin/vppcalls" + vpp2306 "go.ligato.io/vpp-agent/v3/plugins/vpp/l2plugin/vppcalls/vpp2306" + "go.ligato.io/vpp-agent/v3/plugins/vpp/vppmock" + + l2 "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/l2" +) + +var testDataInFib = []*l2.FIBEntry{ + {PhysAddress: "FF:FF:FF:FF:FF:FF", BridgeDomain: "bd1", OutgoingInterface: "if1", Action: l2.FIBEntry_FORWARD, StaticConfig: true, BridgedVirtualInterface: true}, + {PhysAddress: "AA:AA:AA:AA:AA:AA", BridgeDomain: "bd1", OutgoingInterface: "if1", Action: l2.FIBEntry_FORWARD, StaticConfig: true}, + {PhysAddress: "BB:BB:BB:BB:BB:BB", BridgeDomain: "bd1", Action: l2.FIBEntry_DROP}, + {PhysAddress: "CC:CC:CC:CC:CC:CC", BridgeDomain: "bd1", OutgoingInterface: "if1", Action: l2.FIBEntry_FORWARD}, +} + +var testDatasOutFib = []*vpp_l2.L2fibAddDel{ + {BdID: 5, SwIfIndex: 55, BviMac: true, Mac: ethernet_types.MacAddress{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, StaticMac: true, FilterMac: false}, + {BdID: 5, SwIfIndex: 55, BviMac: false, Mac: ethernet_types.MacAddress{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, StaticMac: true, FilterMac: false}, + {BdID: 5, SwIfIndex: ^interface_types.InterfaceIndex(0), BviMac: false, Mac: ethernet_types.MacAddress{0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB}, StaticMac: false, FilterMac: true}, + {BdID: 5, SwIfIndex: 55, BviMac: false, Mac: ethernet_types.MacAddress{0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC}, StaticMac: false, FilterMac: false}, +} + +func TestL2FibAdd(t *testing.T) { + ctx, fibHandler, ifaceIdx, bdIndexes := fibTestSetup(t) + defer ctx.TeardownTestCtx() + + ifaceIdx.Put("if1", &ifaceidx.IfaceMetadata{SwIfIndex: 55}) + bdIndexes.Put("bd1", &idxvpp.OnlyIndex{Index: 5}) + + for i := 0; i < len(testDataInFib); i++ { + ctx.MockVpp.MockReply(&vpp_l2.L2fibAddDelReply{}) + err := fibHandler.AddL2FIB(testDataInFib[i]) + Expect(err).ShouldNot(HaveOccurred()) + testDatasOutFib[i].IsAdd = true + Expect(ctx.MockChannel.Msg).To(Equal(testDatasOutFib[i])) + } +} + +func TestL2FibAddError(t *testing.T) { + ctx, fibHandler, ifaceIdx, bdIndexes := fibTestSetup(t) + defer ctx.TeardownTestCtx() + + ifaceIdx.Put("if1", &ifaceidx.IfaceMetadata{SwIfIndex: 55}) + bdIndexes.Put("bd1", &idxvpp.OnlyIndex{Index: 5}) + + err := fibHandler.AddL2FIB(&l2.FIBEntry{PhysAddress: "not:mac:addr", BridgeDomain: "bd1", OutgoingInterface: "if1"}) + Expect(err).Should(HaveOccurred()) + + ctx.MockVpp.MockReply(&vpp_l2.L2fibAddDelReply{Retval: 1}) + err = fibHandler.AddL2FIB(testDataInFib[0]) + Expect(err).Should(HaveOccurred()) + + ctx.MockVpp.MockReply(&vpp_l2.BridgeDomainAddDelReply{}) + err = fibHandler.AddL2FIB(testDataInFib[0]) + Expect(err).Should(HaveOccurred()) + + err = fibHandler.AddL2FIB(&l2.FIBEntry{PhysAddress: "CC:CC:CC:CC:CC:CC", BridgeDomain: "non-existing-bd", OutgoingInterface: "if1"}) + Expect(err).Should(HaveOccurred()) + + err = fibHandler.AddL2FIB(&l2.FIBEntry{PhysAddress: "CC:CC:CC:CC:CC:CC", BridgeDomain: "bd1", OutgoingInterface: "non-existing-iface"}) + Expect(err).Should(HaveOccurred()) +} + +func TestL2FibDelete(t *testing.T) { + ctx, fibHandler, ifaceIdx, bdIndexes := fibTestSetup(t) + defer ctx.TeardownTestCtx() + + ifaceIdx.Put("if1", &ifaceidx.IfaceMetadata{SwIfIndex: 55}) + bdIndexes.Put("bd1", &idxvpp.OnlyIndex{Index: 5}) + + for i := 0; i < len(testDataInFib); i++ { + ctx.MockVpp.MockReply(&vpp_l2.L2fibAddDelReply{}) + err := fibHandler.DeleteL2FIB(testDataInFib[i]) + Expect(err).ShouldNot(HaveOccurred()) + testDatasOutFib[i].IsAdd = false + Expect(ctx.MockChannel.Msg).To(Equal(testDatasOutFib[i])) + } +} + +func fibTestSetup(t *testing.T) (*vppmock.TestCtx, vppcalls.FIBVppAPI, ifaceidx.IfaceMetadataIndexRW, idxvpp.NameToIndexRW) { + ctx := vppmock.SetupTestCtx(t) + logger := logrus.NewLogger("test-log") + ifaceIdx := ifaceidx.NewIfaceIndex(logger, "fib-if-idx") + bdIndexes := idxvpp.NewNameToIndex(logger, "fib-bd-idx", nil) + fibHandler := vpp2306.NewL2VppHandler(ctx.MockChannel, ifaceIdx, bdIndexes, logger) + return ctx, fibHandler, ifaceIdx, bdIndexes +} diff --git a/plugins/vpp/l2plugin/vppcalls/vpp2306/vppcalls_handler.go b/plugins/vpp/l2plugin/vppcalls/vpp2306/vppcalls_handler.go new file mode 100644 index 0000000000..8daa6959e8 --- /dev/null +++ b/plugins/vpp/l2plugin/vppcalls/vpp2306/vppcalls_handler.go @@ -0,0 +1,119 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "fmt" + "net" + + govppapi "go.fd.io/govpp/api" + "go.ligato.io/cn-infra/v2/logging" + + "go.ligato.io/vpp-agent/v3/pkg/idxvpp" + vpp2306 "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip_types" + l2ba "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/l2" + "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/ifaceidx" + "go.ligato.io/vpp-agent/v3/plugins/vpp/l2plugin/vppcalls" +) + +func init() { + vppcalls.AddHandlerVersion(vpp2306.Version, l2ba.AllMessages(), NewL2VppHandler) +} + +type L2VppHandler struct { + *BridgeDomainVppHandler + *FIBVppHandler + *XConnectVppHandler +} + +func NewL2VppHandler(ch govppapi.Channel, + ifIdx ifaceidx.IfaceMetadataIndex, bdIdx idxvpp.NameToIndex, log logging.Logger, +) vppcalls.L2VppAPI { + return &L2VppHandler{ + BridgeDomainVppHandler: newBridgeDomainVppHandler(ch, ifIdx, log), + FIBVppHandler: newFIBVppHandler(ch, ifIdx, bdIdx, log), + XConnectVppHandler: newXConnectVppHandler(ch, ifIdx, log), + } +} + +// BridgeDomainVppHandler is accessor for bridge domain-related vppcalls methods. +type BridgeDomainVppHandler struct { + callsChannel govppapi.Channel + ifIndexes ifaceidx.IfaceMetadataIndex + log logging.Logger +} + +// FIBVppHandler is accessor for FIB-related vppcalls methods. +type FIBVppHandler struct { + callsChannel govppapi.Channel + ifIndexes ifaceidx.IfaceMetadataIndex + bdIndexes idxvpp.NameToIndex + log logging.Logger +} + +// XConnectVppHandler is accessor for cross-connect-related vppcalls methods. +type XConnectVppHandler struct { + callsChannel govppapi.Channel + ifIndexes ifaceidx.IfaceMetadataIndex + log logging.Logger +} + +// NewBridgeDomainVppHandler creates new instance of bridge domain vppcalls handler. +func newBridgeDomainVppHandler(ch govppapi.Channel, ifIdx ifaceidx.IfaceMetadataIndex, log logging.Logger) *BridgeDomainVppHandler { + return &BridgeDomainVppHandler{ + callsChannel: ch, + ifIndexes: ifIdx, + log: log, + } +} + +// NewFIBVppHandler creates new instance of FIB vppcalls handler. +func newFIBVppHandler(ch govppapi.Channel, ifIdx ifaceidx.IfaceMetadataIndex, bdIndexes idxvpp.NameToIndex, log logging.Logger) *FIBVppHandler { + return &FIBVppHandler{ + callsChannel: ch, + ifIndexes: ifIdx, + bdIndexes: bdIndexes, + log: log, + } +} + +// NewXConnectVppHandler creates new instance of cross connect vppcalls handler. +func newXConnectVppHandler(ch govppapi.Channel, ifIdx ifaceidx.IfaceMetadataIndex, log logging.Logger) *XConnectVppHandler { + return &XConnectVppHandler{ + callsChannel: ch, + ifIndexes: ifIdx, + log: log, + } +} + +func ipToAddress(ipstr string) (addr ip_types.Address, err error) { + netIP := net.ParseIP(ipstr) + if netIP == nil { + return ip_types.Address{}, fmt.Errorf("invalid IP: %q", ipstr) + } + if ip4 := netIP.To4(); ip4 == nil { + addr.Af = ip_types.ADDRESS_IP6 + var ip6addr ip_types.IP6Address + copy(ip6addr[:], netIP.To16()) + addr.Un.SetIP6(ip6addr) + } else { + addr.Af = ip_types.ADDRESS_IP4 + var ip4addr ip_types.IP4Address + copy(ip4addr[:], ip4) + addr.Un.SetIP4(ip4addr) + } + return +} diff --git a/plugins/vpp/l2plugin/vppcalls/vpp2306/xconnect_vppcalls.go b/plugins/vpp/l2plugin/vppcalls/vpp2306/xconnect_vppcalls.go new file mode 100644 index 0000000000..75c5260ef7 --- /dev/null +++ b/plugins/vpp/l2plugin/vppcalls/vpp2306/xconnect_vppcalls.go @@ -0,0 +1,60 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "errors" + + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface_types" + vpp_l2 "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/l2" +) + +// AddL2XConnect creates xConnect between two existing interfaces. +func (h *XConnectVppHandler) AddL2XConnect(rxIface, txIface string) error { + return h.addDelXConnect(rxIface, txIface, true) +} + +// DeleteL2XConnect removes xConnect between two interfaces. +func (h *XConnectVppHandler) DeleteL2XConnect(rxIface, txIface string) error { + return h.addDelXConnect(rxIface, txIface, false) +} + +func (h *XConnectVppHandler) addDelXConnect(rxIface, txIface string, enable bool) error { + // get Rx interface metadata + rxIfaceMeta, found := h.ifIndexes.LookupByName(rxIface) + if !found { + return errors.New("failed to get Rx interface metadata") + } + + // get Tx interface metadata + txIfaceMeta, found := h.ifIndexes.LookupByName(txIface) + if !found { + return errors.New("failed to get Tx interface metadata") + } + + // add/del xConnect pair + req := &vpp_l2.SwInterfaceSetL2Xconnect{ + Enable: enable, + TxSwIfIndex: interface_types.InterfaceIndex(txIfaceMeta.GetIndex()), + RxSwIfIndex: interface_types.InterfaceIndex(rxIfaceMeta.GetIndex()), + } + reply := &vpp_l2.SwInterfaceSetL2XconnectReply{} + + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + + return nil +} diff --git a/plugins/vpp/l2plugin/vppcalls/vpp2306/xconnect_vppcalls_test.go b/plugins/vpp/l2plugin/vppcalls/vpp2306/xconnect_vppcalls_test.go new file mode 100644 index 0000000000..d9965fa72b --- /dev/null +++ b/plugins/vpp/l2plugin/vppcalls/vpp2306/xconnect_vppcalls_test.go @@ -0,0 +1,129 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306_test + +import ( + "testing" + + . "github.com/onsi/gomega" + govppapi "go.fd.io/govpp/api" + "go.ligato.io/cn-infra/v2/logging/logrus" + + vpp_l2 "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/l2" + "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/ifaceidx" + "go.ligato.io/vpp-agent/v3/plugins/vpp/l2plugin/vppcalls" + vpp2306 "go.ligato.io/vpp-agent/v3/plugins/vpp/l2plugin/vppcalls/vpp2306" + "go.ligato.io/vpp-agent/v3/plugins/vpp/vppmock" +) + +var inTestDataXConnect = []struct { + receiveIfaceIndex string + transmitIfaceIndex string + message govppapi.Message +}{ + {"rxIf1", "txIf1", &vpp_l2.SwInterfaceSetL2XconnectReply{}}, + {"rxIf2", "txIf2", &vpp_l2.SwInterfaceSetL2XconnectReply{Retval: 1}}, + {"rxIf2", "txIf2", &vpp_l2.BridgeDomainAddDelReply{}}, +} + +var outTestDataXConnect = []struct { + outData *vpp_l2.SwInterfaceSetL2Xconnect + isResultOk bool +}{ + {&vpp_l2.SwInterfaceSetL2Xconnect{ + RxSwIfIndex: 100, + TxSwIfIndex: 200, + }, true}, + {&vpp_l2.SwInterfaceSetL2Xconnect{ + RxSwIfIndex: 101, + TxSwIfIndex: 201, + }, false}, + {&vpp_l2.SwInterfaceSetL2Xconnect{ + RxSwIfIndex: 101, + TxSwIfIndex: 201, + }, false}, +} + +/** +scenarios: +- enabling xconnect + - ok + - retvalue != 0 + - returned VPP message != what is expected +*/ +// TestVppSetL2XConnect tests VppSetL2XConnect method +func TestVppSetL2XConnect(t *testing.T) { + ctx, xcHandler, ifaceIdx := xcTestSetup(t) + defer ctx.TeardownTestCtx() + + ifaceIdx.Put("rxIf1", &ifaceidx.IfaceMetadata{SwIfIndex: 100}) + ifaceIdx.Put("rxIf2", &ifaceidx.IfaceMetadata{SwIfIndex: 101}) + ifaceIdx.Put("txIf1", &ifaceidx.IfaceMetadata{SwIfIndex: 200}) + ifaceIdx.Put("txIf2", &ifaceidx.IfaceMetadata{SwIfIndex: 201}) + + for i := 0; i < len(inTestDataXConnect); i++ { + ctx.MockVpp.MockReply(inTestDataXConnect[i].message) + err := xcHandler.AddL2XConnect(inTestDataXConnect[i].receiveIfaceIndex, + inTestDataXConnect[i].transmitIfaceIndex) + + if outTestDataXConnect[i].isResultOk { + Expect(err).To(BeNil()) + } else { + Expect(err).NotTo(BeNil()) + } + outTestDataXConnect[i].outData.Enable = true + Expect(ctx.MockChannel.Msg).To(Equal(outTestDataXConnect[i].outData)) + } +} + +/** +scenarios: +- disabling xconnect + - ok + - retvalue != 0 + - returned VPP message != what is expected +*/ +// TestVppUnsetL2XConnect tests VppUnsetL2XConnect method +func TestVppUnsetL2XConnect(t *testing.T) { + ctx, xcHandler, ifaceIdx := xcTestSetup(t) + defer ctx.TeardownTestCtx() + + ifaceIdx.Put("rxIf1", &ifaceidx.IfaceMetadata{SwIfIndex: 100}) + ifaceIdx.Put("rxIf2", &ifaceidx.IfaceMetadata{SwIfIndex: 101}) + ifaceIdx.Put("txIf1", &ifaceidx.IfaceMetadata{SwIfIndex: 200}) + ifaceIdx.Put("txIf2", &ifaceidx.IfaceMetadata{SwIfIndex: 201}) + + for i := 0; i < len(inTestDataXConnect); i++ { + ctx.MockVpp.MockReply(inTestDataXConnect[i].message) + err := xcHandler.DeleteL2XConnect(inTestDataXConnect[i].receiveIfaceIndex, + inTestDataXConnect[i].transmitIfaceIndex) + + if outTestDataXConnect[i].isResultOk { + Expect(err).To(BeNil()) + } else { + Expect(err).NotTo(BeNil()) + } + outTestDataXConnect[i].outData.Enable = false + Expect(ctx.MockChannel.Msg).To(Equal(outTestDataXConnect[i].outData)) + } +} + +func xcTestSetup(t *testing.T) (*vppmock.TestCtx, vppcalls.XConnectVppAPI, ifaceidx.IfaceMetadataIndexRW) { + ctx := vppmock.SetupTestCtx(t) + log := logrus.NewLogger("test-log") + ifaceIdx := ifaceidx.NewIfaceIndex(log, "xc-if-idx") + xcHandler := vpp2306.NewL2VppHandler(ctx.MockChannel, ifaceIdx, nil, log) + return ctx, xcHandler, ifaceIdx +} diff --git a/plugins/vpp/l3plugin/l3plugin.go b/plugins/vpp/l3plugin/l3plugin.go index 72be95392d..4664374da8 100644 --- a/plugins/vpp/l3plugin/l3plugin.go +++ b/plugins/vpp/l3plugin/l3plugin.go @@ -41,6 +41,7 @@ import ( _ "go.ligato.io/vpp-agent/v3/plugins/vpp/l3plugin/vppcalls/vpp2202" _ "go.ligato.io/vpp-agent/v3/plugins/vpp/l3plugin/vppcalls/vpp2210" + _ "go.ligato.io/vpp-agent/v3/plugins/vpp/l3plugin/vppcalls/vpp2306" ) func init() { diff --git a/plugins/vpp/l3plugin/vppcalls/vpp2306/arp_dump.go b/plugins/vpp/l3plugin/vppcalls/vpp2306/arp_dump.go new file mode 100644 index 0000000000..7cb8201537 --- /dev/null +++ b/plugins/vpp/l3plugin/vppcalls/vpp2306/arp_dump.go @@ -0,0 +1,93 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "net" + + vpp_ip_neighbor "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip_neighbor" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip_types" + "go.ligato.io/vpp-agent/v3/plugins/vpp/l3plugin/vppcalls" + l3 "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/l3" +) + +// DumpArpEntries implements arp handler. +func (h *ArpVppHandler) DumpArpEntries() ([]*vppcalls.ArpDetails, error) { + arpV4Entries, err := h.dumpArpEntries(false) + if err != nil { + return nil, err + } + arpV6Entries, err := h.dumpArpEntries(true) + if err != nil { + return nil, err + } + return append(arpV4Entries, arpV6Entries...), nil +} + +func (h *ArpVppHandler) dumpArpEntries(isIPv6 bool) ([]*vppcalls.ArpDetails, error) { + var entries []*vppcalls.ArpDetails + req := &vpp_ip_neighbor.IPNeighborDump{ + SwIfIndex: 0xffffffff, // Send multirequest to get all ARP entries for given IP version + } + if isIPv6 { + req.Af = ip_types.ADDRESS_IP6 + } + reqCtx := h.callsChannel.SendMultiRequest(req) + for { + arpDetails := &vpp_ip_neighbor.IPNeighborDetails{} + stop, err := reqCtx.ReceiveReply(arpDetails) + if stop { + break + } + if err != nil { + h.log.Error(err) + return nil, err + } + + // ARP interface + ifName, _, exists := h.ifIndexes.LookupBySwIfIndex(uint32(arpDetails.Neighbor.SwIfIndex)) + if !exists { + h.log.Warnf("ARP dump: interface name not found for index %d", arpDetails.Neighbor.SwIfIndex) + } + // IP & MAC address + var ip string + if arpDetails.Neighbor.IPAddress.Af == ip_types.ADDRESS_IP6 { + addr := arpDetails.Neighbor.IPAddress.Un.GetIP6() + ip = net.IP(addr[:]).To16().String() + } else { + addr := arpDetails.Neighbor.IPAddress.Un.GetIP4() + ip = net.IP(addr[:]).To4().String() + } + + // ARP entry + arp := &l3.ARPEntry{ + Interface: ifName, + IpAddress: ip, + PhysAddress: net.HardwareAddr(arpDetails.Neighbor.MacAddress[:]).String(), + Static: arpDetails.Neighbor.Flags&vpp_ip_neighbor.IP_API_NEIGHBOR_FLAG_STATIC != 0, + } + // ARP meta + meta := &vppcalls.ArpMeta{ + SwIfIndex: uint32(arpDetails.Neighbor.SwIfIndex), + } + + entries = append(entries, &vppcalls.ArpDetails{ + Arp: arp, + Meta: meta, + }) + } + + return entries, nil +} diff --git a/plugins/vpp/l3plugin/vppcalls/vpp2306/arp_vppcalls.go b/plugins/vpp/l3plugin/vppcalls/vpp2306/arp_vppcalls.go new file mode 100644 index 0000000000..098449dd14 --- /dev/null +++ b/plugins/vpp/l3plugin/vppcalls/vpp2306/arp_vppcalls.go @@ -0,0 +1,76 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "net" + + "github.com/pkg/errors" + + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface_types" + vpp_ip_neighbor "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip_neighbor" + l3 "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/l3" +) + +// vppAddDelArp adds or removes ARP entry according to provided input +func (h *ArpVppHandler) vppAddDelArp(entry *l3.ARPEntry, delete bool) error { + meta, found := h.ifIndexes.LookupByName(entry.Interface) + if !found { + return errors.Errorf("interface %s not found", entry.Interface) + } + + var flags vpp_ip_neighbor.IPNeighborFlags + flags |= vpp_ip_neighbor.IP_API_NEIGHBOR_FLAG_NO_FIB_ENTRY + if entry.Static { + flags |= vpp_ip_neighbor.IP_API_NEIGHBOR_FLAG_STATIC + } + + req := &vpp_ip_neighbor.IPNeighborAddDel{ + IsAdd: !delete, + Neighbor: vpp_ip_neighbor.IPNeighbor{ + SwIfIndex: interface_types.InterfaceIndex(meta.SwIfIndex), + Flags: flags, + }, + } + + var err error + req.Neighbor.IPAddress, err = ipToAddress(entry.IpAddress) + if err != nil { + return errors.WithStack(err) + } + + macAddr, err := net.ParseMAC(entry.PhysAddress) + if err != nil { + return err + } + copy(req.Neighbor.MacAddress[:], macAddr) + + reply := &vpp_ip_neighbor.IPNeighborAddDelReply{} + if err = h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + + return nil +} + +// VppAddArp implements arp handler. +func (h *ArpVppHandler) VppAddArp(entry *l3.ARPEntry) error { + return h.vppAddDelArp(entry, false) +} + +// VppDelArp implements arp handler. +func (h *ArpVppHandler) VppDelArp(entry *l3.ARPEntry) error { + return h.vppAddDelArp(entry, true) +} diff --git a/plugins/vpp/l3plugin/vppcalls/vpp2306/arp_vppcalls_test.go b/plugins/vpp/l3plugin/vppcalls/vpp2306/arp_vppcalls_test.go new file mode 100644 index 0000000000..a7f7dcc672 --- /dev/null +++ b/plugins/vpp/l3plugin/vppcalls/vpp2306/arp_vppcalls_test.go @@ -0,0 +1,92 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306_test + +import ( + "testing" + + . "github.com/onsi/gomega" + "go.ligato.io/cn-infra/v2/logging/logrus" + + vpp_ip_neighbor "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip_neighbor" + "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/ifaceidx" + "go.ligato.io/vpp-agent/v3/plugins/vpp/l3plugin/vppcalls" + vpp2306 "go.ligato.io/vpp-agent/v3/plugins/vpp/l3plugin/vppcalls/vpp2306" + "go.ligato.io/vpp-agent/v3/plugins/vpp/vppmock" + l3 "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/l3" +) + +var arpEntries = []*l3.ARPEntry{ + { + Interface: "if1", + IpAddress: "192.168.10.21", + PhysAddress: "59:6C:45:59:8E:BD", + Static: true, + }, + { + Interface: "if1", + IpAddress: "192.168.10.22", + PhysAddress: "6C:45:59:59:8E:BD", + Static: false, + }, + { + Interface: "if1", + IpAddress: "dead::1", + PhysAddress: "8E:BD:6C:45:59:59", + Static: false, + }, +} + +// Test adding of ARP +func TestAddArp(t *testing.T) { + ctx, ifIndexes, arpHandler := arpTestSetup(t) + defer ctx.TeardownTestCtx() + + ifIndexes.Put("if1", &ifaceidx.IfaceMetadata{SwIfIndex: 1}) + + ctx.MockVpp.MockReply(&vpp_ip_neighbor.IPNeighborAddDelReply{}) + err := arpHandler.VppAddArp(arpEntries[0]) + Expect(err).To(Succeed()) + ctx.MockVpp.MockReply(&vpp_ip_neighbor.IPNeighborAddDelReply{}) + err = arpHandler.VppAddArp(arpEntries[1]) + Expect(err).To(Succeed()) + ctx.MockVpp.MockReply(&vpp_ip_neighbor.IPNeighborAddDelReply{}) + err = arpHandler.VppAddArp(arpEntries[2]) + Expect(err).To(Succeed()) + + ctx.MockVpp.MockReply(&vpp_ip_neighbor.IPNeighborAddDelReply{Retval: 1}) + err = arpHandler.VppAddArp(arpEntries[0]) + Expect(err).NotTo(BeNil()) +} + +// Test deleting of ARP +func TestDelArp(t *testing.T) { + ctx, ifIndexes, arpHandler := arpTestSetup(t) + defer ctx.TeardownTestCtx() + + ifIndexes.Put("if1", &ifaceidx.IfaceMetadata{SwIfIndex: 1}) + + ctx.MockVpp.MockReply(&vpp_ip_neighbor.IPNeighborAddDelReply{}) + err := arpHandler.VppDelArp(arpEntries[0]) + Expect(err).To(Succeed()) +} + +func arpTestSetup(t *testing.T) (*vppmock.TestCtx, ifaceidx.IfaceMetadataIndexRW, vppcalls.ArpVppAPI) { + ctx := vppmock.SetupTestCtx(t) + log := logrus.NewLogger("test-log") + ifIndexes := ifaceidx.NewIfaceIndex(logrus.NewLogger("test"), "test") + arpHandler := vpp2306.NewArpVppHandler(ctx.MockChannel, ifIndexes, log) + return ctx, ifIndexes, arpHandler +} diff --git a/plugins/vpp/l3plugin/vppcalls/vpp2306/dhcpproxy_dump.go b/plugins/vpp/l3plugin/vppcalls/vpp2306/dhcpproxy_dump.go new file mode 100644 index 0000000000..4cb69a500e --- /dev/null +++ b/plugins/vpp/l3plugin/vppcalls/vpp2306/dhcpproxy_dump.go @@ -0,0 +1,80 @@ +// Copyright (c) 2022 PANTHEON.tech +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "net" + + vpp_dhcp "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/dhcp" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip_types" + "go.ligato.io/vpp-agent/v3/plugins/vpp/l3plugin/vppcalls" + l3 "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/l3" +) + +func (h *DHCPProxyHandler) DumpDHCPProxy() (entry []*vppcalls.DHCPProxyDetails, err error) { + ipv4Entries, err := h.dumpDHCPProxyForIPVersion(false) + if err != nil { + return nil, err + } + entry = append(entry, ipv4Entries...) + ipv6Entries, err := h.dumpDHCPProxyForIPVersion(true) + if err != nil { + return nil, err + } + + entry = append(entry, ipv6Entries...) + return entry, nil +} + +func (h *DHCPProxyHandler) dumpDHCPProxyForIPVersion(isIPv6 bool) (entry []*vppcalls.DHCPProxyDetails, err error) { + reqCtx := h.callsChannel.SendMultiRequest(&vpp_dhcp.DHCPProxyDump{IsIP6: isIPv6}) + for { + dhcpProxyDetails := &vpp_dhcp.DHCPProxyDetails{} + stop, err := reqCtx.ReceiveReply(dhcpProxyDetails) + if stop { + break + } + if err != nil { + h.log.Error(err) + return nil, err + } + proxy := &l3.DHCPProxy{ + RxVrfId: dhcpProxyDetails.RxVrfID, + SourceIpAddress: addressToString(dhcpProxyDetails.DHCPSrcAddress), + } + + for _, server := range dhcpProxyDetails.Servers { + proxyServer := &l3.DHCPProxy_DHCPServer{ + IpAddress: addressToString(server.DHCPServer), + VrfId: server.ServerVrfID, + } + proxy.Servers = append(proxy.Servers, proxyServer) + } + + entry = append(entry, &vppcalls.DHCPProxyDetails{ + DHCPProxy: proxy, + }) + } + return +} + +func addressToString(address ip_types.Address) string { + if address.Af == ip_types.ADDRESS_IP6 { + ipAddr := address.Un.GetIP6() + return net.IP(ipAddr[:]).To16().String() + } + ipAddr := address.Un.GetIP4() + return net.IP(ipAddr[:]).To4().String() +} diff --git a/plugins/vpp/l3plugin/vppcalls/vpp2306/dhcpproxy_vppcalls.go b/plugins/vpp/l3plugin/vppcalls/vpp2306/dhcpproxy_vppcalls.go new file mode 100644 index 0000000000..04f8c34fa7 --- /dev/null +++ b/plugins/vpp/l3plugin/vppcalls/vpp2306/dhcpproxy_vppcalls.go @@ -0,0 +1,75 @@ +// Copyright (c) 2022 PANTHEON.tech +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "fmt" + "net" + + "github.com/pkg/errors" + + vpp_dhcp "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/dhcp" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip_types" + l3 "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/l3" +) + +func (h *DHCPProxyHandler) createDeleteDHCPProxy(entry *l3.DHCPProxy, delete bool) (err error) { + config := &vpp_dhcp.DHCPProxyConfig{ + RxVrfID: entry.RxVrfId, + IsAdd: !delete, + } + if config.DHCPSrcAddress, err = ipToDHCPAddress(entry.SourceIpAddress); err != nil { + return errors.Errorf("Invalid source IP address: %q", entry.SourceIpAddress) + } + for _, server := range entry.Servers { + config.ServerVrfID = server.VrfId + if config.DHCPServer, err = ipToDHCPAddress(server.IpAddress); err != nil { + return errors.Errorf("Invalid server IP address: %q", server.IpAddress) + } + reply := &vpp_dhcp.DHCPProxyConfigReply{} + if err := h.callsChannel.SendRequest(config).ReceiveReply(reply); err != nil { + return err + } + } + + return nil +} + +func (h *DHCPProxyHandler) CreateDHCPProxy(entry *l3.DHCPProxy) error { + return h.createDeleteDHCPProxy(entry, false) +} + +func (h *DHCPProxyHandler) DeleteDHCPProxy(entry *l3.DHCPProxy) error { + return h.createDeleteDHCPProxy(entry, true) +} + +func ipToDHCPAddress(address string) (dhcpAddr ip_types.Address, err error) { + netIP := net.ParseIP(address) + if netIP == nil { + return ip_types.Address{}, fmt.Errorf("invalid IP: %q", address) + } + if ip4 := netIP.To4(); ip4 == nil { + dhcpAddr.Af = ip_types.ADDRESS_IP6 + var ip6addr ip_types.IP6Address + copy(ip6addr[:], netIP.To16()) + dhcpAddr.Un.SetIP6(ip6addr) + } else { + dhcpAddr.Af = ip_types.ADDRESS_IP4 + var ip4addr ip_types.IP4Address + copy(ip4addr[:], ip4) + dhcpAddr.Un.SetIP4(ip4addr) + } + return +} diff --git a/plugins/vpp/l3plugin/vppcalls/vpp2306/doc.go b/plugins/vpp/l3plugin/vppcalls/vpp2306/doc.go new file mode 100644 index 0000000000..9e15c04ebf --- /dev/null +++ b/plugins/vpp/l3plugin/vppcalls/vpp2306/doc.go @@ -0,0 +1,16 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package vpp2306 contains wrappers over VPP binary APIs for ARPs, proxy ARPs, L3 FIBs and helpers for dumping them. +package vpp2306 diff --git a/plugins/vpp/l3plugin/vppcalls/vpp2306/ipneigh_vppcalls.go b/plugins/vpp/l3plugin/vppcalls/vpp2306/ipneigh_vppcalls.go new file mode 100644 index 0000000000..20ce770b49 --- /dev/null +++ b/plugins/vpp/l3plugin/vppcalls/vpp2306/ipneigh_vppcalls.go @@ -0,0 +1,151 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + //vpp_ip_neighbor "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip_neighbor" + //"go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip_types" + "go.ligato.io/vpp-agent/v3/plugins/vpp/l3plugin/vppcalls" + l3 "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/l3" +) + +/* + FIXME: IP neighbor configuraton is not implemented for 20.01, because + of breaking change in the API. + New proto model must be defined to support configuring this properly. + The current model does not allow separated config for IPv4 and IPv6. +*/ + +// DefaultIPScanNeighbor implements ip neigh handler. +func (h *IPNeighHandler) DefaultIPScanNeighbor() *l3.IPScanNeighbor { + return nil + + /*return &l3.IPScanNeighbor{ + Mode: l3.IPScanNeighbor_DISABLED, + MaxProcTime: 0, + MaxUpdate: 50000, + ScanInterval: 0, + ScanIntDelay: 0, + StaleThreshold: 0, + }*/ +} + +// SetIPScanNeighbor implements ip neigh handler. +func (h *IPNeighHandler) SetIPScanNeighbor(data *l3.IPScanNeighbor) (err error) { + return vppcalls.ErrIPNeighborNotImplemented + + /*switch data.Mode { + case l3.IPScanNeighbor_IPV4: + return h.setIPScanNeighbor(ip_types.ADDRESS_IP4, data.MaxUpdate, data.MaxProcTime, recycle) + case l3.IPScanNeighbor_IPV6: + return h.setIPScanNeighbor(ip_types.ADDRESS_IP6, data.MaxUpdate, data.MaxProcTime, recycle) + case l3.IPScanNeighbor_BOTH: + err = h.setIPScanNeighbor(ip_types.ADDRESS_IP4, data.MaxUpdate, data.MaxProcTime, recycle) + if err != nil { + return err + } + err = h.setIPScanNeighbor(ip_types.ADDRESS_IP6, data.MaxUpdate, data.MaxProcTime, recycle) + if err != nil { + return err + } + case l3.IPScanNeighbor_DISABLED: + err = h.setIPScanNeighbor(ip_types.ADDRESS_IP4, 0, 0, false) + if err != nil { + return err + } + err = h.setIPScanNeighbor(ip_types.ADDRESS_IP6, 0, 0, false) + if err != nil { + return err + } + default: + return fmt.Errorf("unknown IP Scan Neighbor mode: %v", data.Mode) + } + return nil*/ +} + +/*func (h *IPNeighHandler) setIPScanNeighbor(af ip_types.AddressFamily, maxNum, maxAge uint32, recycle bool) error { + req := &vpp_ip_neighbor.IPNeighborConfig{ + Af: af, + MaxNumber: maxNum, + MaxAge: maxAge, + Recycle: recycle, + } + reply := &vpp_ip_neighbor.IPNeighborConfigReply{} + + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + + return nil +}*/ + +var ( +/* + Sample outputs for VPP CLI 'show ip neighbor-config' + --- + ip4: + limit:50000, age:0, recycle:0 + ip6: + limit:50000, age:0, recycle:0 + --- +*/ +//cliIPScanNeighRe = regexp.MustCompile(`(ip4|ip6):\n\s+limit:([0-9]+),\s+age:([0-9]+),\s+recycle:([0-9]+)\s+`) +) + +// GetIPScanNeighbor dumps current IP Scan Neighbor configuration. +func (h *IPNeighHandler) GetIPScanNeighbor() (*l3.IPScanNeighbor, error) { + return nil, vppcalls.ErrIPNeighborNotImplemented + + /*data, err := h.RunCli(context.TODO(), "show ip neighbor-config") + if err != nil { + return nil, err + } + + allMatches := cliIPScanNeighRe.FindAllStringSubmatch(data, 2) + + fmt.Printf("%d MATCHES:\n%q\n", len(allMatches), allMatches) + + if len(allMatches) != 2 || len(allMatches[0]) != 5 || len(allMatches[1]) != 5 { + h.log.Warnf("invalid 'show ip neighbor-config' output: %q", data) + return nil, errors.Errorf("invalid VPP CLI output for ip neighbor config") + } + + ipScanNeigh := &l3.IPScanNeighbor{} + + for _, matches := range allMatches { + switch matches[1] { + case "ip4": + ipScanNeigh.Mode = l3.IPScanNeighbor_IPV4 + case "ip6": + ipScanNeigh.Mode = l3.IPScanNeighbor_IPV6 + } + ipScanNeigh.MaxUpdate = h.strToUint32(matches[2]) + ipScanNeigh.MaxProcTime = h.strToUint32(matches[3]) + ipScanNeigh.ScanInterval = h.strToUint32(matches[4]) + } + + return ipScanNeigh, nil*/ +} + +// func (h *IPNeighHandler) strToUint32(s string) uint32 { +// if s == "" { +// return 0 +// } +// n, err := strconv.ParseUint(s, 10, 32) +// if err != nil { +// h.log.Error(err) +// } +// return uint32(n) +// } diff --git a/plugins/vpp/l3plugin/vppcalls/vpp2306/ipneigh_vppcalls_test.go b/plugins/vpp/l3plugin/vppcalls/vpp2306/ipneigh_vppcalls_test.go new file mode 100644 index 0000000000..4c36131e1a --- /dev/null +++ b/plugins/vpp/l3plugin/vppcalls/vpp2306/ipneigh_vppcalls_test.go @@ -0,0 +1,70 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +/* +import ( + "testing" + + . "github.com/onsi/gomega" + + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/vlib" + "go.ligato.io/vpp-agent/v3/plugins/vpp/vppmock" + l3 "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/l3" +) + + +func TestGetIPScanNeighbor(t *testing.T) { + tests := []struct { + name string + cliReply string + expected l3.IPScanNeighbor + }{ + { + name: "default", + cliReply: `ip4: + limit:50000, age:0, recycle:0 +ip6: + limit:50000, age:0, recycle:0 +`, + expected: l3.IPScanNeighbor{ + Mode: l3.IPScanNeighbor_DISABLED, + MaxUpdate: 50000, + }, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + ctx := vppmock.SetupTestCtx(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vlib.CliInbandReply{ + Reply: test.cliReply, + }) + + handler := NewIPNeighVppHandler(ctx.MockChannel, nil) + + ipNeigh, err := handler.GetIPScanNeighbor() + Expect(err).ShouldNot(HaveOccurred()) + Expect(ipNeigh.Mode).To(Equal(test.expected.Mode)) + Expect(ipNeigh.ScanInterval).To(Equal(test.expected.ScanInterval)) + Expect(ipNeigh.ScanIntDelay).To(Equal(test.expected.ScanIntDelay)) + Expect(ipNeigh.StaleThreshold).To(Equal(test.expected.StaleThreshold)) + Expect(ipNeigh.MaxUpdate).To(Equal(test.expected.MaxUpdate)) + Expect(ipNeigh.MaxProcTime).To(Equal(test.expected.MaxProcTime)) + }) + } +} +*/ diff --git a/plugins/vpp/l3plugin/vppcalls/vpp2306/l3xc_vppcalls.go b/plugins/vpp/l3plugin/vppcalls/vpp2306/l3xc_vppcalls.go new file mode 100644 index 0000000000..409e32e911 --- /dev/null +++ b/plugins/vpp/l3plugin/vppcalls/vpp2306/l3xc_vppcalls.go @@ -0,0 +1,144 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "context" + "io" + "net" + + "github.com/pkg/errors" + + "go.ligato.io/vpp-agent/v3/plugins/vpp" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/fib_types" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface_types" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip_types" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/l3xc" + "go.ligato.io/vpp-agent/v3/plugins/vpp/l3plugin/vppcalls" +) + +func (h *L3XCHandler) DumpAllL3XC(ctx context.Context) ([]vppcalls.L3XC, error) { + return h.DumpL3XC(ctx, ^uint32(0)) +} + +func (h *L3XCHandler) DumpL3XC(ctx context.Context, index uint32) ([]vppcalls.L3XC, error) { + if h.l3xc == nil { + // no-op when disabled + return nil, nil + } + + dump, err := h.l3xc.L3xcDump(ctx, &l3xc.L3xcDump{ + SwIfIndex: interface_types.InterfaceIndex(index), + }) + if err != nil { + return nil, err + } + l3xcs := make([]vppcalls.L3XC, 0) + for { + recv, err := dump.Recv() + if err == io.EOF { + break + } else if err != nil { + return nil, err + } + paths := make([]vppcalls.Path, len(recv.L3xc.Paths)) + for i, p := range recv.L3xc.Paths { + var nextHop net.IP + if p.Proto == fib_types.FIB_API_PATH_NH_PROTO_IP6 { + ip6Addr := p.Nh.Address.GetIP6() + nextHop = net.IP(ip6Addr[:]).To16() + } else { + ip4Addr := p.Nh.Address.GetIP4() + nextHop = net.IP(ip4Addr[:4]).To4() + } + paths[i] = vppcalls.Path{ + SwIfIndex: p.SwIfIndex, + Weight: p.Weight, + Preference: p.Preference, + NextHop: nextHop, + } + } + l3xcs = append(l3xcs, vppcalls.L3XC{ + SwIfIndex: uint32(recv.L3xc.SwIfIndex), + IsIPv6: recv.L3xc.IsIP6, + Paths: paths, + }) + } + return l3xcs, nil +} + +func (h *L3XCHandler) UpdateL3XC(ctx context.Context, xc *vppcalls.L3XC) error { + if h.l3xc == nil { + return errors.Wrap(vpp.ErrPluginDisabled, "l3xc") + } + + paths := make([]fib_types.FibPath, len(xc.Paths)) + for i, p := range xc.Paths { + fibPath := fib_types.FibPath{ + SwIfIndex: p.SwIfIndex, + Weight: p.Weight, + Preference: p.Preference, + Type: fib_types.FIB_API_PATH_TYPE_NORMAL, + } + fibPath.Nh, fibPath.Proto = getL3XCFibPathNhAndProto(p.NextHop) + paths[i] = fibPath + } + _, err := h.l3xc.L3xcUpdate(ctx, &l3xc.L3xcUpdate{ + L3xc: l3xc.L3xc{ + SwIfIndex: interface_types.InterfaceIndex(xc.SwIfIndex), + IsIP6: xc.IsIPv6, + Paths: paths, + }, + }) + if err != nil { + return err + } + return nil +} + +func (h *L3XCHandler) DeleteL3XC(ctx context.Context, index uint32, ipv6 bool) error { + if h.l3xc == nil { + return errors.Wrap(vpp.ErrPluginDisabled, "l3xc") + } + + _, err := h.l3xc.L3xcDel(ctx, &l3xc.L3xcDel{ + SwIfIndex: interface_types.InterfaceIndex(index), + IsIP6: ipv6, + }) + if err != nil { + return err + } + return nil +} + +func getL3XCFibPathNhAndProto(netIP net.IP) (nh fib_types.FibPathNh, proto fib_types.FibPathNhProto) { + var addrUnion ip_types.AddressUnion + if netIP.To4() == nil { + proto = fib_types.FIB_API_PATH_NH_PROTO_IP6 + var ip6addr ip_types.IP6Address + copy(ip6addr[:], netIP.To16()) + addrUnion.SetIP6(ip6addr) + } else { + proto = fib_types.FIB_API_PATH_NH_PROTO_IP4 + var ip4addr ip_types.IP4Address + copy(ip4addr[:], netIP.To4()) + addrUnion.SetIP4(ip4addr) + } + return fib_types.FibPathNh{ + Address: addrUnion, + ViaLabel: NextHopViaLabelUnset, + ClassifyTableIndex: ClassifyTableIndexUnset, + }, proto +} diff --git a/plugins/vpp/l3plugin/vppcalls/vpp2306/proxyarp_dump.go b/plugins/vpp/l3plugin/vppcalls/vpp2306/proxyarp_dump.go new file mode 100644 index 0000000000..afb19f91b7 --- /dev/null +++ b/plugins/vpp/l3plugin/vppcalls/vpp2306/proxyarp_dump.go @@ -0,0 +1,86 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "net" + + vpp_arp "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/arp" + "go.ligato.io/vpp-agent/v3/plugins/vpp/l3plugin/vppcalls" + l3 "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/l3" +) + +// DumpProxyArpRanges implements proxy arp handler. +func (h *ProxyArpVppHandler) DumpProxyArpRanges() (pArpRngs []*vppcalls.ProxyArpRangesDetails, err error) { + reqCtx := h.callsChannel.SendMultiRequest(&vpp_arp.ProxyArpDump{}) + + for { + proxyArpDetails := &vpp_arp.ProxyArpDetails{} + stop, err := reqCtx.ReceiveReply(proxyArpDetails) + if stop { + break + } + if err != nil { + h.log.Error(err) + return nil, err + } + + pArpRngs = append(pArpRngs, &vppcalls.ProxyArpRangesDetails{ + Range: &l3.ProxyARP_Range{ + FirstIpAddr: net.IP(proxyArpDetails.Proxy.Low[:]).To4().String(), + LastIpAddr: net.IP(proxyArpDetails.Proxy.Hi[:]).To4().String(), + VrfId: proxyArpDetails.Proxy.TableID, + }, + }) + } + + return pArpRngs, nil +} + +// DumpProxyArpInterfaces implements proxy arp handler. +func (h *ProxyArpVppHandler) DumpProxyArpInterfaces() (pArpIfs []*vppcalls.ProxyArpInterfaceDetails, err error) { + reqCtx := h.callsChannel.SendMultiRequest(&vpp_arp.ProxyArpIntfcDump{}) + + for { + proxyArpDetails := &vpp_arp.ProxyArpIntfcDetails{} + stop, err := reqCtx.ReceiveReply(proxyArpDetails) + if stop { + break + } + if err != nil { + h.log.Error(err) + return nil, err + } + + // Interface + ifName, _, exists := h.ifIndexes.LookupBySwIfIndex(proxyArpDetails.SwIfIndex) + if !exists { + h.log.Warnf("Proxy ARP interface dump: missing name for interface index %d", proxyArpDetails.SwIfIndex) + } + + // Create entry + pArpIfs = append(pArpIfs, &vppcalls.ProxyArpInterfaceDetails{ + Interface: &l3.ProxyARP_Interface{ + Name: ifName, + }, + Meta: &vppcalls.ProxyArpInterfaceMeta{ + SwIfIndex: proxyArpDetails.SwIfIndex, + }, + }) + + } + + return pArpIfs, nil +} diff --git a/plugins/vpp/l3plugin/vppcalls/vpp2306/proxyarp_vppcalls.go b/plugins/vpp/l3plugin/vppcalls/vpp2306/proxyarp_vppcalls.go new file mode 100644 index 0000000000..d3497585b6 --- /dev/null +++ b/plugins/vpp/l3plugin/vppcalls/vpp2306/proxyarp_vppcalls.go @@ -0,0 +1,104 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "fmt" + "net" + + "github.com/pkg/errors" + + vpp_arp "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/arp" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface_types" +) + +// EnableProxyArpInterface implements proxy arp handler. +func (h *ProxyArpVppHandler) EnableProxyArpInterface(ifName string) error { + return h.vppAddDelProxyArpInterface(ifName, true) +} + +// DisableProxyArpInterface implements proxy arp handler. +func (h *ProxyArpVppHandler) DisableProxyArpInterface(ifName string) error { + return h.vppAddDelProxyArpInterface(ifName, false) +} + +// AddProxyArpRange implements proxy arp handler. +func (h *ProxyArpVppHandler) AddProxyArpRange(firstIP, lastIP []byte, vrfID uint32) error { + return h.vppAddDelProxyArpRange(firstIP, lastIP, vrfID, true) +} + +// DeleteProxyArpRange implements proxy arp handler. +func (h *ProxyArpVppHandler) DeleteProxyArpRange(firstIP, lastIP []byte, vrfID uint32) error { + return h.vppAddDelProxyArpRange(firstIP, lastIP, vrfID, false) +} + +// vppAddDelProxyArpInterface adds or removes proxy ARP interface entry according to provided input +func (h *ProxyArpVppHandler) vppAddDelProxyArpInterface(ifName string, enable bool) error { + meta, found := h.ifIndexes.LookupByName(ifName) + if !found { + return errors.Errorf("interface %s not found", ifName) + } + + req := &vpp_arp.ProxyArpIntfcEnableDisable{ + Enable: enable, + SwIfIndex: interface_types.InterfaceIndex(meta.SwIfIndex), + } + + reply := &vpp_arp.ProxyArpIntfcEnableDisableReply{} + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + + h.log.Debugf("interface %v enabled for proxy arp: %v", req.SwIfIndex, enable) + + return nil +} + +// vppAddDelProxyArpRange adds or removes proxy ARP range according to provided input. +func (h *ProxyArpVppHandler) vppAddDelProxyArpRange(firstIP, lastIP []byte, vrfID uint32, isAdd bool) error { + validateIPBytes := func(b []byte) error { + ip := net.IP(b) + if ip.To4() == nil { + return fmt.Errorf("IP bytes %v are not valid IPv4 address", b) + } + return nil + } + if err := validateIPBytes(firstIP); err != nil { + return fmt.Errorf("bad first IP: %v", err) + } + if err := validateIPBytes(lastIP); err != nil { + return fmt.Errorf("bad last IP: %v", err) + } + + proxy := vpp_arp.ProxyArp{ + TableID: vrfID, + } + copy(proxy.Low[:], firstIP) + copy(proxy.Hi[:], lastIP) + + req := &vpp_arp.ProxyArpAddDel{ + IsAdd: isAdd, + Proxy: proxy, + } + + reply := &vpp_arp.ProxyArpAddDelReply{} + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + + h.log.Debugf("proxy arp range: %v - %v (vrf: %d) added: %v", proxy.Low, proxy.Hi, proxy.TableID, isAdd) + + return nil +} diff --git a/plugins/vpp/l3plugin/vppcalls/vpp2306/proxyarp_vppcalls_test.go b/plugins/vpp/l3plugin/vppcalls/vpp2306/proxyarp_vppcalls_test.go new file mode 100644 index 0000000000..aabb2cddbc --- /dev/null +++ b/plugins/vpp/l3plugin/vppcalls/vpp2306/proxyarp_vppcalls_test.go @@ -0,0 +1,129 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306_test + +import ( + "testing" + + . "github.com/onsi/gomega" + "go.ligato.io/cn-infra/v2/logging/logrus" + + vpp_arp "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/arp" + "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/ifaceidx" + "go.ligato.io/vpp-agent/v3/plugins/vpp/l3plugin/vppcalls" + vpp2306 "go.ligato.io/vpp-agent/v3/plugins/vpp/l3plugin/vppcalls/vpp2306" + "go.ligato.io/vpp-agent/v3/plugins/vpp/vppmock" +) + +// Test enable/disable proxy arp +func TestProxyArp(t *testing.T) { + ctx, ifIndexes, pArpHandler := pArpTestSetup(t) + defer ctx.TeardownTestCtx() + + ifIndexes.Put("if1", &ifaceidx.IfaceMetadata{SwIfIndex: 1}) + + ctx.MockVpp.MockReply(&vpp_arp.ProxyArpIntfcEnableDisableReply{}) + err := pArpHandler.EnableProxyArpInterface("if1") + Expect(err).To(Succeed()) + + ctx.MockVpp.MockReply(&vpp_arp.ProxyArpIntfcEnableDisableReply{}) + err = pArpHandler.DisableProxyArpInterface("if1") + Expect(err).To(Succeed()) + + ctx.MockVpp.MockReply(&vpp_arp.ProxyArpIntfcEnableDisableReply{Retval: 1}) + err = pArpHandler.DisableProxyArpInterface("if1") + Expect(err).NotTo(BeNil()) +} + +func TestProxyArpRange(t *testing.T) { + ctx, _, pArpHandler := pArpTestSetup(t) + defer ctx.TeardownTestCtx() + + t.Run("Add: success case", func(t *testing.T) { + ctx.MockVpp.MockReply(&vpp_arp.ProxyArpAddDelReply{Retval: 0}) + + Expect(pArpHandler.AddProxyArpRange( + []byte{192, 168, 10, 20}, []byte{192, 168, 10, 30}, 0, + )).To(Succeed()) + }) + + testAddProxyARPRangeError := func(firstIP, lastIP []byte, vrf uint32) { + t.Helper() + + ctx.MockVpp.MockReply(&vpp_arp.ProxyArpAddDelReply{Retval: 0}) + Expect(pArpHandler.AddProxyArpRange(firstIP, lastIP, vrf)).ToNot(Succeed()) + + //Get mocked reply, since VPP call should not happen before. + Expect( + ctx.MockVPPClient.SendRequest(&vpp_arp.ProxyArpAddDel{}).ReceiveReply(&vpp_arp.ProxyArpAddDelReply{}), + ).To(Succeed()) + } + t.Run("Add: error cases", func(t *testing.T) { + // Bad first IP address. + testAddProxyARPRangeError([]byte{192, 168, 20}, []byte{192, 168, 10, 30}, 0) + // Bad last IP address. + testAddProxyARPRangeError([]byte{192, 168, 10, 20}, []byte{192, 168, 30}, 0) + // Bad both IP addresses. + testAddProxyARPRangeError([]byte{192, 168, 20}, []byte{192, 168, 30}, 0) + }) + + t.Run("Delete: success case", func(t *testing.T) { + ctx.MockVpp.MockReply(&vpp_arp.ProxyArpAddDelReply{Retval: 0}) + + Expect(pArpHandler.DeleteProxyArpRange( + []byte{192, 168, 10, 20}, []byte{192, 168, 10, 30}, 0, + )).To(Succeed()) + }) + + testDelProxyARPRangeError := func(firstIP, lastIP []byte, vrf uint32) { + t.Helper() + + ctx.MockVpp.MockReply(&vpp_arp.ProxyArpAddDelReply{Retval: 0}) + Expect(pArpHandler.DeleteProxyArpRange(firstIP, lastIP, vrf)).ToNot(Succeed()) + + //Get mocked reply, since VPP call should not happen before. + Expect( + ctx.MockVPPClient.SendRequest(&vpp_arp.ProxyArpAddDel{}).ReceiveReply(&vpp_arp.ProxyArpAddDelReply{}), + ).To(Succeed()) + } + t.Run("Delete: error cases", func(t *testing.T) { + // Bad first IP address. + testDelProxyARPRangeError([]byte{192, 168, 20}, []byte{192, 168, 10, 30}, 0) + // Bad last IP address. + testDelProxyARPRangeError([]byte{192, 168, 10, 20}, []byte{192, 168, 30}, 0) + // Bad both IP addresses. + testDelProxyARPRangeError([]byte{192, 168, 20}, []byte{192, 168, 30}, 0) + }) + + // Test retval in "add" scenario. + ctx.MockVpp.MockReply(&vpp_arp.ProxyArpAddDelReply{Retval: 1}) + Expect(pArpHandler.AddProxyArpRange( + []byte{192, 168, 10, 20}, []byte{192, 168, 10, 30}, 0, + )).ToNot(Succeed()) + + // Test retval in "delete" scenario. + ctx.MockVpp.MockReply(&vpp_arp.ProxyArpAddDelReply{Retval: 1}) + Expect(pArpHandler.DeleteProxyArpRange( + []byte{192, 168, 10, 20}, []byte{192, 168, 10, 30}, 0, + )).ToNot(Succeed()) +} + +func pArpTestSetup(t *testing.T) (*vppmock.TestCtx, ifaceidx.IfaceMetadataIndexRW, vppcalls.ProxyArpVppAPI) { + ctx := vppmock.SetupTestCtx(t) + log := logrus.NewLogger("test-log") + ifIndexes := ifaceidx.NewIfaceIndex(logrus.NewLogger("test"), "test") + pArpHandler := vpp2306.NewProxyArpVppHandler(ctx.MockChannel, ifIndexes, log) + return ctx, ifIndexes, pArpHandler +} diff --git a/plugins/vpp/l3plugin/vppcalls/vpp2306/route_dump.go b/plugins/vpp/l3plugin/vppcalls/vpp2306/route_dump.go new file mode 100644 index 0000000000..438a0e2e3a --- /dev/null +++ b/plugins/vpp/l3plugin/vppcalls/vpp2306/route_dump.go @@ -0,0 +1,205 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "fmt" + "net" + + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/fib_types" + vpp_ip "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip_types" + "go.ligato.io/vpp-agent/v3/plugins/vpp/l3plugin/vppcalls" + l3 "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/l3" +) + +// DumpRoutes implements route handler. +func (h *RouteHandler) DumpRoutes() (routes []*vppcalls.RouteDetails, err error) { + // dump routes for every VRF and for both IP versions + for _, vrfMeta := range h.vrfIndexes.ListAllVrfMetadata() { + ipRoutes, err := h.dumpRoutesForVrfAndIP(vrfMeta.GetIndex(), vrfMeta.GetProtocol()) + if err != nil { + return nil, err + } + routes = append(routes, ipRoutes...) + } + return routes, nil +} + +// dumpRoutesForVrf returns routes for given VRF and IP versiob +func (h *RouteHandler) dumpRoutesForVrfAndIP(vrfID uint32, proto l3.VrfTable_Protocol) (routes []*vppcalls.RouteDetails, err error) { + reqCtx := h.callsChannel.SendMultiRequest(&vpp_ip.IPRouteDump{ + Table: vpp_ip.IPTable{ + TableID: vrfID, + IsIP6: proto == l3.VrfTable_IPV6, + }, + }) + for { + fibDetails := &vpp_ip.IPRouteDetails{} + stop, err := reqCtx.ReceiveReply(fibDetails) + if stop { + break + } + if err != nil { + return nil, err + } + ipRoute, err := h.dumpRouteIPDetails(fibDetails.Route) + if err != nil { + return nil, err + } + routes = append(routes, ipRoute...) + } + + return routes, nil +} + +// dumpRouteIPDetails processes static route details and returns a route objects. Number of routes returned +// depends on size of path list. +func (h *RouteHandler) dumpRouteIPDetails(ipRoute vpp_ip.IPRoute) ([]*vppcalls.RouteDetails, error) { + // Common fields for every route path (destination IP, VRF) + var dstIP string + if ipRoute.Prefix.Address.Af == ip_types.ADDRESS_IP6 { + ip6Addr := ipRoute.Prefix.Address.Un.GetIP6() + dstIP = fmt.Sprintf("%s/%d", net.IP(ip6Addr[:]).To16().String(), uint32(ipRoute.Prefix.Len)) + } else { + ip4Addr := ipRoute.Prefix.Address.Un.GetIP4() + dstIP = fmt.Sprintf("%s/%d", net.IP(ip4Addr[:4]).To4().String(), uint32(ipRoute.Prefix.Len)) + } + + var routeDetails []*vppcalls.RouteDetails + + // Paths + if ipRoute.NPaths > 0 { + for _, path := range ipRoute.Paths { + // Next hop IP address + var nextHopIP string + netIP := make([]byte, 16) + copy(netIP[:], path.Nh.Address.XXX_UnionData[:]) + if path.Proto == fib_types.FIB_API_PATH_NH_PROTO_IP6 { + nextHopIP = net.IP(netIP).To16().String() + } else { + nextHopIP = net.IP(netIP[:4]).To4().String() + } + + // Route type (if via VRF is used) + var routeType l3.Route_RouteType + var viaVrfID uint32 + if path.Type == fib_types.FIB_API_PATH_TYPE_DROP { + routeType = l3.Route_DROP + } else if path.SwIfIndex == NextHopOutgoingIfUnset && path.TableID != ipRoute.TableID { + // outgoing interface not specified and path table is not equal to route table id = inter-VRF route + routeType = l3.Route_INTER_VRF + viaVrfID = path.TableID + } else { + routeType = l3.Route_INTRA_VRF // default + } + + // Outgoing interface + var ifName string + var ifIdx uint32 + if path.SwIfIndex == NextHopOutgoingIfUnset { + ifIdx = NextHopOutgoingIfUnset + } else { + var exists bool + ifIdx = path.SwIfIndex + if ifName, _, exists = h.ifIndexes.LookupBySwIfIndex(path.SwIfIndex); !exists { + h.log.Warnf("Static route dump: interface name for index %d not found", path.SwIfIndex) + } + } + + // Route configuration + route := &l3.Route{ + Type: routeType, + VrfId: ipRoute.TableID, + DstNetwork: dstIP, + NextHopAddr: nextHopIP, + OutgoingInterface: ifName, + Weight: uint32(path.Weight), + Preference: uint32(path.Preference), + ViaVrfId: viaVrfID, + } + + labelStack := make([]vppcalls.FibMplsLabel, len(path.LabelStack)) + for i, l := range path.LabelStack { + labelStack[i] = vppcalls.FibMplsLabel{ + IsUniform: uintToBool(l.IsUniform), + Label: l.Label, + TTL: l.TTL, + Exp: l.Exp, + } + } + + // Route metadata + meta := &vppcalls.RouteMeta{ + OutgoingIfIdx: ifIdx, + IsIPv6: path.Proto == fib_types.FIB_API_PATH_NH_PROTO_IP6, + NextHopID: path.Nh.ObjID, + RpfID: path.RpfID, + LabelStack: labelStack, + } + resolvePathType(meta, path.Type) + resolvePathFlags(meta, path.Flags) + // Note: VPP does not return table name as in older versions, the field + // is filled using index map + vrfName, _, exists := h.vrfIndexes.LookupByVRFIndex(ipRoute.TableID) + if exists { + meta.TableName = vrfName + } + + routeDetails = append(routeDetails, &vppcalls.RouteDetails{ + Route: route, + Meta: meta, + }) + } + } else { + // Return route without path fields, but this is not a valid configuration + h.log.Warnf("Route with destination IP %s (VRF %d) has no path specified", dstIP, ipRoute.TableID) + routeDetails = append(routeDetails, &vppcalls.RouteDetails{ + Route: &l3.Route{ + Type: l3.Route_INTRA_VRF, // default + VrfId: ipRoute.TableID, + DstNetwork: dstIP, + }, + }) + } + + return routeDetails, nil +} + +func resolvePathType(meta *vppcalls.RouteMeta, pathType fib_types.FibPathType) { + switch pathType { + case fib_types.FIB_API_PATH_TYPE_LOCAL: + meta.IsLocal = true + case fib_types.FIB_API_PATH_TYPE_UDP_ENCAP: + meta.IsUDPEncap = true + case fib_types.FIB_API_PATH_TYPE_ICMP_UNREACH: + meta.IsUnreach = true + case fib_types.FIB_API_PATH_TYPE_ICMP_PROHIBIT: + meta.IsProhibit = true + case fib_types.FIB_API_PATH_TYPE_DVR: + meta.IsDvr = true + case fib_types.FIB_API_PATH_TYPE_SOURCE_LOOKUP: + meta.IsSourceLookup = true + } +} + +func resolvePathFlags(meta *vppcalls.RouteMeta, pathFlags fib_types.FibPathFlags) { + switch pathFlags { + case fib_types.FIB_API_PATH_FLAG_RESOLVE_VIA_HOST: + meta.IsResolveHost = true + case fib_types.FIB_API_PATH_FLAG_RESOLVE_VIA_ATTACHED: + meta.IsResolveAttached = true + } +} diff --git a/plugins/vpp/l3plugin/vppcalls/vpp2306/route_dump_test.go b/plugins/vpp/l3plugin/vppcalls/vpp2306/route_dump_test.go new file mode 100644 index 0000000000..216866b4f5 --- /dev/null +++ b/plugins/vpp/l3plugin/vppcalls/vpp2306/route_dump_test.go @@ -0,0 +1,88 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "testing" + + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/fib_types" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip_types" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/memclnt" + l3 "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/l3" + + "go.ligato.io/vpp-agent/v3/plugins/vpp/l3plugin/vrfidx" + + . "github.com/onsi/gomega" + "go.ligato.io/cn-infra/v2/logging/logrus" + + netallock_mock "go.ligato.io/vpp-agent/v3/plugins/netalloc/mock" + vpp_ip "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip" + "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/ifaceidx" + "go.ligato.io/vpp-agent/v3/plugins/vpp/vppmock" +) + +// Test dumping routes +func TestDumpStaticRoutes(t *testing.T) { + ctx := vppmock.SetupTestCtx(t) + defer ctx.TeardownTestCtx() + ifIndexes := ifaceidx.NewIfaceIndex(logrus.NewLogger("test-if"), "test-if") + vrfIndexes := vrfidx.NewVRFIndex(logrus.NewLogger("test-vrf"), "test-vrf") + l3handler := NewRouteVppHandler(ctx.MockChannel, ifIndexes, vrfIndexes, netallock_mock.NewMockNetAlloc(), + logrus.DefaultLogger()) + + vrfIndexes.Put("vrf1-ipv4", &vrfidx.VRFMetadata{Index: 0, Protocol: l3.VrfTable_IPV4}) + vrfIndexes.Put("vrf1-ipv6", &vrfidx.VRFMetadata{Index: 0, Protocol: l3.VrfTable_IPV6}) + ifIndexes.Put("if1", &ifaceidx.IfaceMetadata{SwIfIndex: 1}) + ifIndexes.Put("if2", &ifaceidx.IfaceMetadata{SwIfIndex: 2}) + + ctx.MockVpp.MockReply(&vpp_ip.IPRouteDetails{ + Route: vpp_ip.IPRoute{ + Prefix: ip_types.Prefix{ + Address: ip_types.Address{ + Af: ip_types.ADDRESS_IP4, + Un: ip_types.AddressUnionIP4([4]uint8{10, 0, 0, 1}), + }, + }, + Paths: []fib_types.FibPath{ + { + SwIfIndex: 2, + }, + }, + }, + }) + ctx.MockVpp.MockReply(&memclnt.ControlPingReply{}) + ctx.MockVpp.MockReply(&vpp_ip.IPRouteDetails{ + Route: vpp_ip.IPRoute{ + Prefix: ip_types.Prefix{ + Address: ip_types.Address{ + Af: ip_types.ADDRESS_IP6, + Un: ip_types.AddressUnionIP6([16]uint8{255, 255, 10, 1}), + }, + }, + Paths: []fib_types.FibPath{ + { + SwIfIndex: 1, + }, + }, + }, + }) + ctx.MockVpp.MockReply(&memclnt.ControlPingReply{}) + + rtDetails, err := l3handler.DumpRoutes() + Expect(err).To(Succeed()) + Expect(rtDetails).To(HaveLen(2)) + Expect(rtDetails[0].Route.OutgoingInterface).To(Equal("if2")) + Expect(rtDetails[1].Route.OutgoingInterface).To(Equal("if1")) +} diff --git a/plugins/vpp/l3plugin/vppcalls/vpp2306/route_vppcalls.go b/plugins/vpp/l3plugin/vppcalls/vpp2306/route_vppcalls.go new file mode 100644 index 0000000000..4e8e7efaa0 --- /dev/null +++ b/plugins/vpp/l3plugin/vppcalls/vpp2306/route_vppcalls.go @@ -0,0 +1,150 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "context" + "net" + + "github.com/pkg/errors" + + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/fib_types" + vpp_ip "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip_types" + "go.ligato.io/vpp-agent/v3/proto/ligato/netalloc" + l3 "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/l3" +) + +const ( + // NextHopViaLabelUnset constant has to be assigned into the field next hop + // via label in ip_add_del_route binary message if next hop via label is not defined. + // Equals to MPLS_LABEL_INVALID defined in VPP + NextHopViaLabelUnset uint32 = 0xfffff + 1 + + // ClassifyTableIndexUnset is a default value for field classify_table_index in ip_add_del_route binary message. + ClassifyTableIndexUnset = ^uint32(0) + + // NextHopOutgoingIfUnset constant has to be assigned into the field next_hop_outgoing_interface + // in ip_add_del_route binary message if outgoing interface for next hop is not defined. + NextHopOutgoingIfUnset = ^uint32(0) +) + +// vppAddDelRoute adds or removes route, according to provided input. Every route has to contain VRF ID (default is 0). +func (h *RouteHandler) vppAddDelRoute(route *l3.Route, rtIfIdx uint32, delete bool) error { + req := &vpp_ip.IPRouteAddDel{ + // Multi path is always true + IsMultipath: true, + IsAdd: !delete, + } + + // Common route parameters + fibPath := fib_types.FibPath{ + Weight: uint8(route.Weight), + Preference: uint8(route.Preference), + } + if route.NextHopAddr != "" { + nextHop, err := h.addrAlloc.GetOrParseIPAddress(route.NextHopAddr, + route.OutgoingInterface, netalloc.IPAddressForm_ADDR_ONLY) + if err != nil { + return err + } + fibPath.Nh, fibPath.Proto = setFibPathNhAndProto(nextHop.IP) + } + + // VRF/Other route parameters based on type + if route.Type == l3.Route_INTER_VRF { + fibPath.SwIfIndex = rtIfIdx + fibPath.TableID = route.ViaVrfId + } else if route.Type == l3.Route_DROP { + fibPath.Type = fib_types.FIB_API_PATH_TYPE_DROP + } else { + fibPath.SwIfIndex = rtIfIdx + fibPath.TableID = route.VrfId + } + // Destination address + dstNet, err := h.addrAlloc.GetOrParseIPAddress(route.DstNetwork, + "", netalloc.IPAddressForm_ADDR_NET) + if err != nil { + return err + } + prefix := networkToPrefix(dstNet) + + req.Route = vpp_ip.IPRoute{ + TableID: route.VrfId, + Prefix: prefix, + NPaths: 1, + Paths: []fib_types.FibPath{fibPath}, + } + + reply := &vpp_ip.IPRouteAddDelReply{} + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + + return nil +} + +// VppAddRoute implements route handler. +func (h *RouteHandler) VppAddRoute(ctx context.Context, route *l3.Route) error { + swIfIdx, err := h.getRouteSwIfIndex(route.OutgoingInterface) + if err != nil { + return err + } + + return h.vppAddDelRoute(route, swIfIdx, false) +} + +// VppDelRoute implements route handler. +func (h *RouteHandler) VppDelRoute(ctx context.Context, route *l3.Route) error { + swIfIdx, err := h.getRouteSwIfIndex(route.OutgoingInterface) + if err != nil { + return err + } + + return h.vppAddDelRoute(route, swIfIdx, true) +} + +func setFibPathNhAndProto(netIP net.IP) (nh fib_types.FibPathNh, proto fib_types.FibPathNhProto) { + var addrUnion ip_types.AddressUnion + if netIP.To4() == nil { + proto = fib_types.FIB_API_PATH_NH_PROTO_IP6 + var ip6addr ip_types.IP6Address + copy(ip6addr[:], netIP.To16()) + addrUnion.SetIP6(ip6addr) + } else { + proto = fib_types.FIB_API_PATH_NH_PROTO_IP4 + var ip4addr ip_types.IP4Address + copy(ip4addr[:], netIP.To4()) + addrUnion.SetIP4(ip4addr) + } + + return fib_types.FibPathNh{ + Address: addrUnion, + ViaLabel: NextHopViaLabelUnset, + ClassifyTableIndex: ClassifyTableIndexUnset, + }, proto +} + +func (h *RouteHandler) getRouteSwIfIndex(ifName string) (swIfIdx uint32, err error) { + swIfIdx = NextHopOutgoingIfUnset + if ifName != "" { + meta, found := h.ifIndexes.LookupByName(ifName) + if !found { + return 0, errors.Errorf("interface %s not found", ifName) + } + swIfIdx = meta.SwIfIndex + } + return +} diff --git a/plugins/vpp/l3plugin/vppcalls/vpp2306/route_vppcalls_test.go b/plugins/vpp/l3plugin/vppcalls/vpp2306/route_vppcalls_test.go new file mode 100644 index 0000000000..d3878c783e --- /dev/null +++ b/plugins/vpp/l3plugin/vppcalls/vpp2306/route_vppcalls_test.go @@ -0,0 +1,99 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306_test + +import ( + "testing" + + "go.ligato.io/vpp-agent/v3/plugins/vpp/l3plugin/vrfidx" + + . "github.com/onsi/gomega" + "go.ligato.io/cn-infra/v2/logging/logrus" + + netallock_mock "go.ligato.io/vpp-agent/v3/plugins/netalloc/mock" + vpp_ip "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip" + "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/ifaceidx" + ifvppcalls "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/vppcalls" + ifvpp2306 "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/vppcalls/vpp2306" + "go.ligato.io/vpp-agent/v3/plugins/vpp/l3plugin/vppcalls" + vpp2306 "go.ligato.io/vpp-agent/v3/plugins/vpp/l3plugin/vppcalls/vpp2306" + "go.ligato.io/vpp-agent/v3/plugins/vpp/vppmock" + l3 "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/l3" +) + +var routes = []*l3.Route{ + { + VrfId: 1, + DstNetwork: "192.168.10.21/24", + NextHopAddr: "192.168.30.1", + OutgoingInterface: "iface1", + }, + { + VrfId: 2, + DstNetwork: "10.0.0.1/24", + NextHopAddr: "192.168.30.1", + }, + { + VrfId: 2, + DstNetwork: "10.11.0.1/16", + NextHopAddr: "192.168.30.1", + OutgoingInterface: "iface3", + }, +} + +// Test adding routes +func TestAddRoute(t *testing.T) { + ctx, _, rtHandler := routeTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ip.IPRouteAddDelReply{}) + err := rtHandler.VppAddRoute(ctx.Context, routes[0]) + Expect(err).To(Succeed()) + + ctx.MockVpp.MockReply(&vpp_ip.IPRouteAddDelReply{}) + err = rtHandler.VppAddRoute(ctx.Context, routes[2]) + Expect(err).To(Not(BeNil())) // unknown interface +} + +// Test deleting routes +func TestDeleteRoute(t *testing.T) { + ctx, _, rtHandler := routeTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ip.IPRouteAddDelReply{}) + err := rtHandler.VppDelRoute(ctx.Context, routes[0]) + Expect(err).To(Succeed()) + + ctx.MockVpp.MockReply(&vpp_ip.IPRouteAddDelReply{}) + err = rtHandler.VppDelRoute(ctx.Context, routes[1]) + Expect(err).To(Succeed()) + + ctx.MockVpp.MockReply(&vpp_ip.IPRouteAddDelReply{Retval: 1}) + err = rtHandler.VppDelRoute(ctx.Context, routes[0]) + Expect(err).To(Not(BeNil())) +} + +func routeTestSetup(t *testing.T) (*vppmock.TestCtx, ifvppcalls.InterfaceVppAPI, vppcalls.RouteVppAPI) { + ctx := vppmock.SetupTestCtx(t) + log := logrus.NewLogger("test-log") + ifHandler := ifvpp2306.NewInterfaceVppHandler(ctx.MockVPPClient, log) + ifIndexes := ifaceidx.NewIfaceIndex(logrus.NewLogger("test-if"), "test-if") + vrfIndexes := vrfidx.NewVRFIndex(logrus.NewLogger("test-vrf"), "test-vrf") + ifIndexes.Put("iface1", &ifaceidx.IfaceMetadata{ + SwIfIndex: 1, + }) + rtHandler := vpp2306.NewRouteVppHandler(ctx.MockChannel, ifIndexes, vrfIndexes, netallock_mock.NewMockNetAlloc(), log) + return ctx, ifHandler, rtHandler +} diff --git a/plugins/vpp/l3plugin/vppcalls/vpp2306/teib_vppcalls.go b/plugins/vpp/l3plugin/vppcalls/vpp2306/teib_vppcalls.go new file mode 100644 index 0000000000..b8646ea2ca --- /dev/null +++ b/plugins/vpp/l3plugin/vppcalls/vpp2306/teib_vppcalls.go @@ -0,0 +1,104 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "context" + "fmt" + "net" + + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface_types" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip_types" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/teib" + l3 "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/l3" +) + +// VppAddTeibEntry adds a new TEIB entry. +func (h *TeibHandler) VppAddTeibEntry(ctx context.Context, entry *l3.TeibEntry) error { + return h.vppAddDelTeibEntry(entry, false) +} + +// VppDelTeibEntry removes an existing TEIB entry. +func (h *TeibHandler) VppDelTeibEntry(ctx context.Context, entry *l3.TeibEntry) error { + return h.vppAddDelTeibEntry(entry, true) +} + +func (h *TeibHandler) vppAddDelTeibEntry(entry *l3.TeibEntry, delete bool) error { + peer, err := ipToAddress(entry.PeerAddr) + if err != nil { + return err + } + nh, err := ipToAddress(entry.NextHopAddr) + if err != nil { + return err + } + + meta, found := h.ifIndexes.LookupByName(entry.Interface) + if !found { + return fmt.Errorf("interface %s not found", entry.Interface) + } + + req := &teib.TeibEntryAddDel{ + Entry: teib.TeibEntry{ + SwIfIndex: interface_types.InterfaceIndex(meta.SwIfIndex), + Peer: peer, + Nh: nh, + NhTableID: entry.VrfId, + }, + } + if !delete { + req.IsAdd = 1 + } + + reply := &teib.TeibEntryAddDelReply{} + return h.callsChannel.SendRequest(req).ReceiveReply(reply) +} + +// DumpTeib dumps TEIB entries from VPP and fills them into the provided TEIB entry map. +func (h *TeibHandler) DumpTeib() (entries []*l3.TeibEntry, err error) { + reqCtx := h.callsChannel.SendMultiRequest(&teib.TeibDump{}) + for { + teibDetails := &teib.TeibDetails{} + stop, err := reqCtx.ReceiveReply(teibDetails) + if stop { + break + } + if err != nil { + return nil, err + } + entry := &l3.TeibEntry{ + PeerAddr: ipAddrToStr(teibDetails.Entry.Peer), + NextHopAddr: ipAddrToStr(teibDetails.Entry.Nh), + VrfId: teibDetails.Entry.NhTableID, + } + if ifName, _, exists := h.ifIndexes.LookupBySwIfIndex(uint32(teibDetails.Entry.SwIfIndex)); !exists { + h.log.Warnf("TEIB dump: interface name for index %d not found", teibDetails.Entry.SwIfIndex) + } else { + entry.Interface = ifName + } + entries = append(entries, entry) + } + return +} + +func ipAddrToStr(addr ip_types.Address) string { + if addr.Af == ip_types.ADDRESS_IP6 { + ip6Addr := addr.Un.GetIP6() + return net.IP(ip6Addr[:]).To16().String() + } else { + ip4Addr := addr.Un.GetIP4() + return net.IP(ip4Addr[:4]).To4().String() + } +} diff --git a/plugins/vpp/l3plugin/vppcalls/vpp2306/vppcalls_handlers.go b/plugins/vpp/l3plugin/vppcalls/vpp2306/vppcalls_handlers.go new file mode 100644 index 0000000000..63f3407b8e --- /dev/null +++ b/plugins/vpp/l3plugin/vppcalls/vpp2306/vppcalls_handlers.go @@ -0,0 +1,310 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "fmt" + "net" + + govppapi "go.fd.io/govpp/api" + "go.ligato.io/cn-infra/v2/logging" + "go.ligato.io/cn-infra/v2/logging/logrus" + + corevppcalls "go.ligato.io/vpp-agent/v3/plugins/govppmux/vppcalls" + vpe_vpp2306 "go.ligato.io/vpp-agent/v3/plugins/govppmux/vppcalls/vpp2306" + "go.ligato.io/vpp-agent/v3/plugins/netalloc" + "go.ligato.io/vpp-agent/v3/plugins/vpp" + vpp2306 "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306" + vpp_dhcp "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/dhcp" + vpp_ip "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip" + vpp_ip_neighbor "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip_neighbor" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip_types" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/l3xc" + vpp_vpe "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/vpe" + "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/ifaceidx" + "go.ligato.io/vpp-agent/v3/plugins/vpp/l3plugin/vppcalls" + "go.ligato.io/vpp-agent/v3/plugins/vpp/l3plugin/vrfidx" +) + +func init() { + var msgs []govppapi.Message + msgs = append(msgs, vpp_ip.AllMessages()...) + msgs = append(msgs, vpp_ip_neighbor.AllMessages()...) + msgs = append(msgs, vpp_vpe.AllMessages()...) + msgs = append(msgs, vpp_dhcp.AllMessages()...) + + vppcalls.AddHandlerVersion(vpp2306.Version, msgs, NewL3VppHandler) +} + +type L3VppHandler struct { + *ArpVppHandler + *ProxyArpVppHandler + *RouteHandler + *IPNeighHandler + *VrfTableHandler + *DHCPProxyHandler + *L3XCHandler + *TeibHandler + *VrrpVppHandler +} + +func NewL3VppHandler( + c vpp.Client, + ifIdx ifaceidx.IfaceMetadataIndex, + vrfIdx vrfidx.VRFMetadataIndex, + addrAlloc netalloc.AddressAllocator, + log logging.Logger, +) vppcalls.L3VppAPI { + ch, err := c.NewAPIChannel() + if err != nil { + logging.Warnf("creating channel failed: %v", err) + return nil + } + return &L3VppHandler{ + ArpVppHandler: NewArpVppHandler(ch, ifIdx, log), + ProxyArpVppHandler: NewProxyArpVppHandler(ch, ifIdx, log), + RouteHandler: NewRouteVppHandler(ch, ifIdx, vrfIdx, addrAlloc, log), + IPNeighHandler: NewIPNeighVppHandler(c, ch, log), + VrfTableHandler: NewVrfTableVppHandler(ch, log), + DHCPProxyHandler: NewDHCPProxyHandler(ch, log), + L3XCHandler: NewL3XCHandler(c, ifIdx, log), + TeibHandler: NewTeibVppHandler(ch, ifIdx, log), + VrrpVppHandler: NewVrrpVppHandler(ch, ifIdx, log), + } +} + +// ArpVppHandler is accessor for ARP-related vppcalls methods +type ArpVppHandler struct { + callsChannel govppapi.Channel + ifIndexes ifaceidx.IfaceMetadataIndex + log logging.Logger +} + +// DHCPProxyHandler is accessor for DHCP proxy-related vppcalls methods +type DHCPProxyHandler struct { + callsChannel govppapi.Channel + log logging.Logger +} + +// ProxyArpVppHandler is accessor for proxy ARP-related vppcalls methods +type ProxyArpVppHandler struct { + callsChannel govppapi.Channel + ifIndexes ifaceidx.IfaceMetadataIndex + log logging.Logger +} + +// RouteHandler is accessor for route-related vppcalls methods +type RouteHandler struct { + callsChannel govppapi.Channel + ifIndexes ifaceidx.IfaceMetadataIndex + vrfIndexes vrfidx.VRFMetadataIndex + addrAlloc netalloc.AddressAllocator + log logging.Logger +} + +// IPNeighHandler is accessor for ip-neighbor-related vppcalls methods +type IPNeighHandler struct { + callsChannel govppapi.Channel + log logging.Logger + corevppcalls.VppCoreAPI +} + +// VrfTableHandler is accessor for vrf-related vppcalls methods +type VrfTableHandler struct { + callsChannel govppapi.Channel + log logging.Logger +} + +// TeibHandler is accessor for TEIB-related vppcalls methods +type TeibHandler struct { + callsChannel govppapi.Channel + ifIndexes ifaceidx.IfaceMetadataIndex + log logging.Logger +} + +// VrrpVppHandler is accessor for vrrp-related vppcalls methods +type VrrpVppHandler struct { + callsChannel govppapi.Channel + ifIndexes ifaceidx.IfaceMetadataIndex + log logging.Logger +} + +// NewArpVppHandler creates new instance of IPsec vppcalls handler +func NewArpVppHandler(callsChan govppapi.Channel, ifIndexes ifaceidx.IfaceMetadataIndex, log logging.Logger) *ArpVppHandler { + if log == nil { + log = logrus.NewLogger("arp-handler") + } + return &ArpVppHandler{ + callsChannel: callsChan, + ifIndexes: ifIndexes, + log: log, + } +} + +// NewProxyArpVppHandler creates new instance of proxy ARP vppcalls handler +func NewProxyArpVppHandler(callsChan govppapi.Channel, ifIndexes ifaceidx.IfaceMetadataIndex, log logging.Logger) *ProxyArpVppHandler { + if log == nil { + log = logrus.NewLogger("proxy-arp-handler") + } + return &ProxyArpVppHandler{ + callsChannel: callsChan, + ifIndexes: ifIndexes, + log: log, + } +} + +// NewRouteVppHandler creates new instance of route vppcalls handler +func NewRouteVppHandler(callsChan govppapi.Channel, ifIndexes ifaceidx.IfaceMetadataIndex, + vrfIdx vrfidx.VRFMetadataIndex, addrAlloc netalloc.AddressAllocator, log logging.Logger) *RouteHandler { + if log == nil { + log = logrus.NewLogger("route-handler") + } + return &RouteHandler{ + callsChannel: callsChan, + ifIndexes: ifIndexes, + vrfIndexes: vrfIdx, + addrAlloc: addrAlloc, + log: log, + } +} + +// NewIPNeighVppHandler creates new instance of ip neighbor vppcalls handler +func NewIPNeighVppHandler(c vpp.Client, callsChan govppapi.Channel, log logging.Logger) *IPNeighHandler { + if log == nil { + log = logrus.NewLogger("ip-neigh") + } + return &IPNeighHandler{ + callsChannel: callsChan, + log: log, + VppCoreAPI: vpe_vpp2306.NewVpeHandler(c), + } +} + +// NewVrfTableVppHandler creates new instance of vrf-table vppcalls handler +func NewVrfTableVppHandler(callsChan govppapi.Channel, log logging.Logger) *VrfTableHandler { + if log == nil { + log = logrus.NewLogger("vrf-table-handler") + } + return &VrfTableHandler{ + callsChannel: callsChan, + log: log, + } +} + +// NewDHCPProxyHandler creates new instance of vrf-table vppcalls handler +func NewDHCPProxyHandler(callsChan govppapi.Channel, log logging.Logger) *DHCPProxyHandler { + if log == nil { + log = logrus.NewLogger("dhcp-proxy-handler") + } + return &DHCPProxyHandler{ + callsChannel: callsChan, + log: log, + } +} + +// NewTeibVppHandler creates new instance of TEIB vppcalls handler +func NewTeibVppHandler(callsChan govppapi.Channel, ifIndexes ifaceidx.IfaceMetadataIndex, log logging.Logger) *TeibHandler { + if log == nil { + log = logrus.NewLogger("teib-handler") + } + return &TeibHandler{ + callsChannel: callsChan, + ifIndexes: ifIndexes, + log: log, + } +} + +type L3XCHandler struct { + l3xc l3xc.RPCService + ifIndexes ifaceidx.IfaceMetadataIndex + log logging.Logger +} + +// NewL3XCHandler creates new instance of L3XC vppcalls handler +func NewL3XCHandler(c vpp.Client, ifIndexes ifaceidx.IfaceMetadataIndex, log logging.Logger) *L3XCHandler { + if log == nil { + log = logrus.NewLogger("l3xc-handler") + } + h := &L3XCHandler{ + ifIndexes: ifIndexes, + log: log, + } + if c.IsPluginLoaded(l3xc.APIFile) { + h.l3xc = l3xc.NewServiceClient(c) + } + return h +} + +// NewVrrpVppHandler creates new instance of VRRP handler +func NewVrrpVppHandler(callsChan govppapi.Channel, ifIndexes ifaceidx.IfaceMetadataIndex, log logging.Logger) *VrrpVppHandler { + if log == nil { + log = logrus.NewLogger("vrrp-handler") + } + return &VrrpVppHandler{ + callsChannel: callsChan, + ifIndexes: ifIndexes, + log: log, + } +} + +func ipToAddress(ipstr string) (addr ip_types.Address, err error) { + netIP := net.ParseIP(ipstr) + if netIP == nil { + return ip_types.Address{}, fmt.Errorf("invalid IP: %q", ipstr) + } + if ip4 := netIP.To4(); ip4 == nil { + addr.Af = ip_types.ADDRESS_IP6 + var ip6addr ip_types.IP6Address + copy(ip6addr[:], netIP.To16()) + addr.Un.SetIP6(ip6addr) + } else { + addr.Af = ip_types.ADDRESS_IP4 + var ip4addr ip_types.IP4Address + copy(ip4addr[:], ip4) + addr.Un.SetIP4(ip4addr) + } + return +} + +func networkToPrefix(dstNetwork *net.IPNet) ip_types.Prefix { + var addr ip_types.Address + if dstNetwork.IP.To4() == nil { + addr.Af = ip_types.ADDRESS_IP6 + var ip6addr ip_types.IP6Address + copy(ip6addr[:], dstNetwork.IP.To16()) + addr.Un.SetIP6(ip6addr) + } else { + addr.Af = ip_types.ADDRESS_IP4 + var ip4addr ip_types.IP4Address + copy(ip4addr[:], dstNetwork.IP.To4()) + addr.Un.SetIP4(ip4addr) + } + mask, _ := dstNetwork.Mask.Size() + return ip_types.Prefix{ + Address: addr, + Len: uint8(mask), + } +} + +func uintToBool(value uint8) bool { + return value != 0 +} + +func boolToUint(input bool) uint8 { + if input { + return 1 + } + return 0 +} diff --git a/plugins/vpp/l3plugin/vppcalls/vpp2306/vrf_dump.go b/plugins/vpp/l3plugin/vppcalls/vpp2306/vrf_dump.go new file mode 100644 index 0000000000..7e1cc89f9f --- /dev/null +++ b/plugins/vpp/l3plugin/vppcalls/vpp2306/vrf_dump.go @@ -0,0 +1,52 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "strings" + + vpp_ip "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip" + l3 "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/l3" +) + +// DumpVrfTables dumps all configured VRF tables. +func (h *VrfTableHandler) DumpVrfTables() (tables []*l3.VrfTable, err error) { + // dump IPv4 VRF tables + reqCtx := h.callsChannel.SendMultiRequest(&vpp_ip.IPTableDump{}) + for { + fibDetails := &vpp_ip.IPTableDetails{} + stop, err := reqCtx.ReceiveReply(fibDetails) + if stop { + break + } + if err != nil { + return nil, err + } + tables = append(tables, &l3.VrfTable{ + Id: fibDetails.Table.TableID, + Protocol: getTableProto(fibDetails.Table.IsIP6), + Label: strings.Trim(fibDetails.Table.Name, "\x00"), + }) + } + + return tables, nil +} + +func getTableProto(isIPv6 bool) l3.VrfTable_Protocol { + if isIPv6 { + return l3.VrfTable_IPV6 + } + return l3.VrfTable_IPV4 +} diff --git a/plugins/vpp/l3plugin/vppcalls/vpp2306/vrf_dump_test.go b/plugins/vpp/l3plugin/vppcalls/vpp2306/vrf_dump_test.go new file mode 100644 index 0000000000..3a85f04a70 --- /dev/null +++ b/plugins/vpp/l3plugin/vppcalls/vpp2306/vrf_dump_test.go @@ -0,0 +1,79 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "testing" + + . "github.com/onsi/gomega" + "go.ligato.io/cn-infra/v2/logging/logrus" + + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/fib_types" + vpp_ip "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/memclnt" + "go.ligato.io/vpp-agent/v3/plugins/vpp/vppmock" + l3 "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/l3" +) + +func TestDumpVrfTables(t *testing.T) { + ctx := vppmock.SetupTestCtx(t) + defer ctx.TeardownTestCtx() + vthandler := NewVrfTableVppHandler(ctx.MockChannel, logrus.DefaultLogger()) + + ctx.MockVpp.MockReply( + &vpp_ip.IPTableDetails{ + Table: vpp_ip.IPTable{ + TableID: 1, + Name: "table3", + IsIP6: false, + }, + }, + &vpp_ip.IPTableDetails{ + Table: vpp_ip.IPTable{ + TableID: 2, + Name: "table3", + IsIP6: false, + }, + }, + &vpp_ip.IPTableDetails{ + Table: vpp_ip.IPTable{ + TableID: 3, + Name: "table2", + IsIP6: true, + }, + }, + ) + ctx.MockVpp.MockReply(&memclnt.ControlPingReply{}) + ctx.MockVpp.MockReply( + &vpp_ip.IPRouteDetails{ + Route: vpp_ip.IPRoute{ + TableID: 2, + Paths: []fib_types.FibPath{{SwIfIndex: 5}}, + }, + }) + ctx.MockVpp.MockReply(&memclnt.ControlPingReply{}) + + vrfTables, err := vthandler.DumpVrfTables() + Expect(err).To(Succeed()) + Expect(vrfTables).To(HaveLen(3)) + if vrfTables[0].Label == "table2" { + Expect(vrfTables[1]).To(Equal(&l3.VrfTable{Id: 3, Protocol: l3.VrfTable_IPV4, Label: "table3"})) + Expect(vrfTables[0]).To(Equal(&l3.VrfTable{Id: 2, Protocol: l3.VrfTable_IPV4, Label: "table2"})) + } else { + Expect(vrfTables[0]).To(Equal(&l3.VrfTable{Id: 1, Protocol: l3.VrfTable_IPV4, Label: "table3"})) + Expect(vrfTables[1]).To(Equal(&l3.VrfTable{Id: 2, Protocol: l3.VrfTable_IPV4, Label: "table3"})) + } + Expect(vrfTables[2]).To(Equal(&l3.VrfTable{Id: 3, Protocol: l3.VrfTable_IPV6, Label: "table2"})) +} diff --git a/plugins/vpp/l3plugin/vppcalls/vpp2306/vrf_vppcalls.go b/plugins/vpp/l3plugin/vppcalls/vpp2306/vrf_vppcalls.go new file mode 100644 index 0000000000..e80ed120be --- /dev/null +++ b/plugins/vpp/l3plugin/vppcalls/vpp2306/vrf_vppcalls.go @@ -0,0 +1,92 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + vpp_ip "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip_types" + l3 "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/l3" +) + +// AddVrfTable adds new VRF table. +func (h *VrfTableHandler) AddVrfTable(table *l3.VrfTable) error { + return h.addDelVrfTable(table, true) +} + +// DelVrfTable deletes existing VRF table. +func (h *VrfTableHandler) DelVrfTable(table *l3.VrfTable) error { + return h.addDelVrfTable(table, false) +} + +func (h *VrfTableHandler) addDelVrfTable(table *l3.VrfTable, isAdd bool) error { + req := &vpp_ip.IPTableAddDel{ + Table: vpp_ip.IPTable{ + TableID: table.Id, + IsIP6: table.GetProtocol() == l3.VrfTable_IPV6, + Name: table.Label, + }, + IsAdd: isAdd, + } + reply := &vpp_ip.IPTableAddDelReply{} + + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + + return nil +} + +// SetVrfFlowHashSettings sets IP flow hash settings for a VRF table. +func (h *VrfTableHandler) SetVrfFlowHashSettings(vrfID uint32, isIPv6 bool, hashFields *l3.VrfTable_FlowHashSettings) error { + af := ip_types.ADDRESS_IP4 + if isIPv6 { + af = ip_types.ADDRESS_IP6 + } + req := &vpp_ip.SetIPFlowHashV2{ + TableID: vrfID, + Af: af, + FlowHashConfig: setFlowHashConfig(hashFields), + } + reply := &vpp_ip.SetIPFlowHashV2Reply{} + + err := h.callsChannel.SendRequest(req).ReceiveReply(reply) + return err +} + +func setFlowHashConfig(fields *l3.VrfTable_FlowHashSettings) vpp_ip.IPFlowHashConfig { + var config vpp_ip.IPFlowHashConfig + if fields.UseSrcIp { + config |= vpp_ip.IP_API_FLOW_HASH_SRC_IP + } + if fields.UseDstIp { + config |= vpp_ip.IP_API_FLOW_HASH_DST_IP + } + if fields.UseSrcPort { + config |= vpp_ip.IP_API_FLOW_HASH_SRC_PORT + } + if fields.UseDstPort { + config |= vpp_ip.IP_API_FLOW_HASH_DST_PORT + } + if fields.UseProtocol { + config |= vpp_ip.IP_API_FLOW_HASH_PROTO + } + if fields.Reverse { + config |= vpp_ip.IP_API_FLOW_HASH_REVERSE + } + if fields.Symmetric { + config |= vpp_ip.IP_API_FLOW_HASH_SYMETRIC + } + return config +} diff --git a/plugins/vpp/l3plugin/vppcalls/vpp2306/vrf_vppcalls_test.go b/plugins/vpp/l3plugin/vppcalls/vpp2306/vrf_vppcalls_test.go new file mode 100644 index 0000000000..a8379b82c9 --- /dev/null +++ b/plugins/vpp/l3plugin/vppcalls/vpp2306/vrf_vppcalls_test.go @@ -0,0 +1,163 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306_test + +import ( + "testing" + + . "github.com/onsi/gomega" + "go.fd.io/govpp/binapi/ip_types" + "go.ligato.io/cn-infra/v2/logging/logrus" + + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip" + vpp_ip "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip" + "go.ligato.io/vpp-agent/v3/plugins/vpp/l3plugin/vppcalls" + vpp2306 "go.ligato.io/vpp-agent/v3/plugins/vpp/l3plugin/vppcalls/vpp2306" + "go.ligato.io/vpp-agent/v3/plugins/vpp/vppmock" + l3 "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/l3" +) + +var vrfTables = []*l3.VrfTable{ + { + Id: 1, + Protocol: l3.VrfTable_IPV4, + Label: "table1", + }, + { + Id: 1, + Protocol: l3.VrfTable_IPV6, + Label: "table1", + }, + { + Id: 2, + Protocol: l3.VrfTable_IPV6, + Label: "table2", + }, +} + +// Test adding routes +func TestAddVrfTable(t *testing.T) { + ctx, vtHandler := vrfTableTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ip.IPTableAddDelReply{}) + err := vtHandler.AddVrfTable(vrfTables[0]) + Expect(err).To(Succeed()) + + vppMsg, ok := ctx.MockChannel.Msg.(*vpp_ip.IPTableAddDel) + Expect(ok).To(BeTrue()) + Expect(vppMsg.Table.TableID).To(BeEquivalentTo(1)) + Expect(vppMsg.Table.IsIP6).To(BeFalse()) + Expect(vppMsg.IsAdd).To(BeTrue()) + Expect(vppMsg.Table.Name).To(BeEquivalentTo([]byte("table1"))) + + ctx.MockVpp.MockReply(&vpp_ip.IPTableAddDelReply{}) + err = vtHandler.AddVrfTable(vrfTables[1]) + Expect(err).To(Succeed()) + + vppMsg, ok = ctx.MockChannel.Msg.(*vpp_ip.IPTableAddDel) + Expect(ok).To(BeTrue()) + Expect(vppMsg.Table.TableID).To(BeEquivalentTo(1)) + Expect(vppMsg.Table.IsIP6).To(BeTrue()) + Expect(vppMsg.IsAdd).To(BeTrue()) + Expect(vppMsg.Table.Name).To(BeEquivalentTo([]byte("table1"))) + + ctx.MockVpp.MockReply(&vpp_ip.IPTableAddDelReply{}) + err = vtHandler.AddVrfTable(vrfTables[2]) + Expect(err).To(Succeed()) + + vppMsg, ok = ctx.MockChannel.Msg.(*vpp_ip.IPTableAddDel) + Expect(ok).To(BeTrue()) + Expect(vppMsg.Table.TableID).To(BeEquivalentTo(2)) + Expect(vppMsg.Table.IsIP6).To(BeTrue()) + Expect(vppMsg.IsAdd).To(BeTrue()) + Expect(vppMsg.Table.Name).To(BeEquivalentTo([]byte("table2"))) + + ctx.MockVpp.MockReply(&vpp_ip.IPTableAddDelReply{Retval: 1}) + err = vtHandler.AddVrfTable(vrfTables[0]) + Expect(err).To(Not(BeNil())) +} + +// Test deleting routes +func TestDeleteVrfTable(t *testing.T) { + ctx, vtHandler := vrfTableTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ip.IPTableAddDelReply{}) + err := vtHandler.DelVrfTable(vrfTables[0]) + Expect(err).To(Succeed()) + + vppMsg, ok := ctx.MockChannel.Msg.(*vpp_ip.IPTableAddDel) + Expect(ok).To(BeTrue()) + Expect(vppMsg.Table.TableID).To(BeEquivalentTo(1)) + Expect(vppMsg.Table.IsIP6).To(BeFalse()) + Expect(vppMsg.IsAdd).To(BeFalse()) + Expect(vppMsg.Table.Name).To(BeEquivalentTo([]byte("table1"))) + + ctx.MockVpp.MockReply(&vpp_ip.IPTableAddDelReply{}) + err = vtHandler.DelVrfTable(vrfTables[1]) + Expect(err).To(Succeed()) + + vppMsg, ok = ctx.MockChannel.Msg.(*vpp_ip.IPTableAddDel) + Expect(ok).To(BeTrue()) + Expect(vppMsg.Table.TableID).To(BeEquivalentTo(1)) + Expect(vppMsg.Table.IsIP6).To(BeTrue()) + Expect(vppMsg.IsAdd).To(BeFalse()) + Expect(vppMsg.Table.Name).To(BeEquivalentTo([]byte("table1"))) + + ctx.MockVpp.MockReply(&vpp_ip.IPTableAddDelReply{}) + err = vtHandler.DelVrfTable(vrfTables[2]) + Expect(err).To(Succeed()) + + vppMsg, ok = ctx.MockChannel.Msg.(*vpp_ip.IPTableAddDel) + Expect(ok).To(BeTrue()) + Expect(vppMsg.Table.TableID).To(BeEquivalentTo(2)) + Expect(vppMsg.Table.IsIP6).To(BeTrue()) + Expect(vppMsg.IsAdd).To(BeFalse()) + Expect(vppMsg.Table.Name).To(BeEquivalentTo([]byte("table2"))) + + ctx.MockVpp.MockReply(&vpp_ip.IPTableAddDelReply{Retval: 1}) + err = vtHandler.DelVrfTable(vrfTables[0]) + Expect(err).To(Not(BeNil())) +} + +// Test VRF flow hash settings +func TestVrfFlowHashSettings(t *testing.T) { + ctx, vtHandler := vrfTableTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ip.SetIPFlowHashV2Reply{}) + err := vtHandler.SetVrfFlowHashSettings(5, true, + &l3.VrfTable_FlowHashSettings{ + UseSrcIp: true, + UseSrcPort: true, + Symmetric: true, + }) + Expect(err).To(Succeed()) + + vppMsg, ok := ctx.MockChannel.Msg.(*vpp_ip.SetIPFlowHashV2) + Expect(ok).To(BeTrue()) + Expect(vppMsg.TableID).To(BeEquivalentTo(5)) + Expect(vppMsg.Af).To(BeEquivalentTo(ip_types.ADDRESS_IP6)) + Expect(vppMsg.FlowHashConfig).To(BeEquivalentTo( + ip.IP_API_FLOW_HASH_SRC_IP + ip.IP_API_FLOW_HASH_SRC_PORT + ip.IP_API_FLOW_HASH_SYMETRIC)) +} + +func vrfTableTestSetup(t *testing.T) (*vppmock.TestCtx, vppcalls.VrfTableVppAPI) { + ctx := vppmock.SetupTestCtx(t) + log := logrus.NewLogger("test-log") + vtHandler := vpp2306.NewVrfTableVppHandler(ctx.MockChannel, log) + return ctx, vtHandler +} diff --git a/plugins/vpp/l3plugin/vppcalls/vpp2306/vrrp_dump.go b/plugins/vpp/l3plugin/vppcalls/vpp2306/vrrp_dump.go new file mode 100644 index 0000000000..390cd87736 --- /dev/null +++ b/plugins/vpp/l3plugin/vppcalls/vpp2306/vrrp_dump.go @@ -0,0 +1,79 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/vrrp" + "go.ligato.io/vpp-agent/v3/plugins/vpp/l3plugin/vppcalls" + l3 "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/l3" +) + +// DumpVrrpEntries dumps all configured VRRP entries. +func (h *VrrpVppHandler) DumpVrrpEntries() (entries []*vppcalls.VrrpDetails, err error) { + req := &vrrp.VrrpVrDump{ + SwIfIndex: 0xffffffff, // Send multirequest to get all VRRP entries + } + reqCtx := h.callsChannel.SendMultiRequest(req) + for { + vrrpDetails := &vrrp.VrrpVrDetails{} + stop, err := reqCtx.ReceiveReply(vrrpDetails) + if stop { + break + } + if err != nil { + return nil, err + } + + // VRRP interface + ifName, _, exists := h.ifIndexes.LookupBySwIfIndex(uint32(vrrpDetails.Config.SwIfIndex)) + if !exists { + h.log.Warnf("VRRP dump: interface name not found for index %d", vrrpDetails.Config.SwIfIndex) + } + + var isEnabled bool + if vrrpDetails.Runtime.State != vrrp.VRRP_API_VR_STATE_INIT { + isEnabled = true + } + + isPreempt := (vrrpDetails.Config.Flags & vrrp.VRRP_API_VR_PREEMPT) == vrrp.VRRP_API_VR_PREEMPT + isAccept := (vrrpDetails.Config.Flags & vrrp.VRRP_API_VR_ACCEPT) == vrrp.VRRP_API_VR_ACCEPT + isUnicast := (vrrpDetails.Config.Flags & vrrp.VRRP_API_VR_UNICAST) == vrrp.VRRP_API_VR_UNICAST + + ipStrs := make([]string, 0, len(vrrpDetails.Addrs)) + for _, v := range vrrpDetails.Addrs { + ipStrs = append(ipStrs, v.String()) + } + + // VRRP entry + vrrp := &vppcalls.VrrpDetails{ + Vrrp: &l3.VRRPEntry{ + Interface: ifName, + VrId: uint32(vrrpDetails.Config.VrID), + Priority: uint32(vrrpDetails.Config.Priority), + Interval: uint32(vrrpDetails.Config.Interval) * centiMilliRatio, + Preempt: isPreempt, + Accept: isAccept, + Unicast: isUnicast, + IpAddresses: ipStrs, + Enabled: isEnabled, + }, + Meta: &vppcalls.VrrpMeta{}, + } + + entries = append(entries, vrrp) + } + + return entries, nil +} diff --git a/plugins/vpp/l3plugin/vppcalls/vpp2306/vrrp_vppcalls.go b/plugins/vpp/l3plugin/vppcalls/vpp2306/vrrp_vppcalls.go new file mode 100644 index 0000000000..8ec892b852 --- /dev/null +++ b/plugins/vpp/l3plugin/vppcalls/vpp2306/vrrp_vppcalls.go @@ -0,0 +1,136 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "fmt" + + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface_types" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip_types" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/vrrp" + l3 "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/l3" +) + +const ( + centiMilliRatio uint32 = 10 +) + +func (h *VrrpVppHandler) vppAddDelVrrp(entry *l3.VRRPEntry, isAdd uint8) error { + var addrs []ip_types.Address + var isIpv6 bool + for idx, addr := range entry.IpAddresses { + ip, err := ip_types.ParseAddress(addr) + if err != nil { + return err + } + + if idx == 0 && ip.Af == ip_types.ADDRESS_IP6 { + isIpv6 = true + } + + addrs = append(addrs, ip) + } + + md, exist := h.ifIndexes.LookupByName(entry.Interface) + if !exist { + return fmt.Errorf("interface does not exist: %v", entry.Interface) + } + + var flags uint32 + if entry.Preempt { + flags |= uint32(vrrp.VRRP_API_VR_PREEMPT) + } + if entry.Accept { + flags |= uint32(vrrp.VRRP_API_VR_ACCEPT) + } + if entry.Unicast { + flags |= uint32(vrrp.VRRP_API_VR_UNICAST) + } + if isIpv6 { + flags |= uint32(vrrp.VRRP_API_VR_IPV6) + } + + req := &vrrp.VrrpVrAddDel{ + IsAdd: isAdd, + SwIfIndex: interface_types.InterfaceIndex(md.SwIfIndex), + VrID: uint8(entry.GetVrId()), + Priority: uint8(entry.GetPriority()), + Interval: uint16(entry.GetInterval() / centiMilliRatio), + Flags: vrrp.VrrpVrFlags(flags), + NAddrs: uint8(len(addrs)), + Addrs: addrs, + } + + reply := &vrrp.VrrpVrAddDelReply{} + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + + return nil +} + +// VppAddVrrp implements VRRP handler. +func (h *VrrpVppHandler) VppAddVrrp(entry *l3.VRRPEntry) error { + return h.vppAddDelVrrp(entry, 1) +} + +// VppDelVrrp implements VRRP handler. +func (h *VrrpVppHandler) VppDelVrrp(entry *l3.VRRPEntry) error { + return h.vppAddDelVrrp(entry, 0) +} + +func (h *VrrpVppHandler) vppStartStopVrrp(entry *l3.VRRPEntry, isStart uint8) error { + + md, exist := h.ifIndexes.LookupByName(entry.Interface) + if !exist { + return fmt.Errorf("interface does not exist: %v", entry.Interface) + } + + var isIpv6 bool + for idx, addr := range entry.IpAddresses { + ip, err := ipToAddress(addr) + if err != nil { + return err + } + + if idx == 0 && ip.Af == ip_types.ADDRESS_IP6 { + isIpv6 = true + } + } + + req := &vrrp.VrrpVrStartStop{ + SwIfIndex: interface_types.InterfaceIndex(md.SwIfIndex), + VrID: uint8(entry.VrId), + IsIPv6: boolToUint(isIpv6), + IsStart: isStart, + } + + reply := &vrrp.VrrpVrStartStopReply{} + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + + return nil +} + +// VppStartVrrp implements VRRP handler. +func (h *VrrpVppHandler) VppStartVrrp(entry *l3.VRRPEntry) error { + return h.vppStartStopVrrp(entry, 1) +} + +// VppStopVrrp implements VRRP handler. +func (h *VrrpVppHandler) VppStopVrrp(entry *l3.VRRPEntry) error { + return h.vppStartStopVrrp(entry, 0) +} diff --git a/plugins/vpp/l3plugin/vppcalls/vpp2306/vrrp_vppcalls_test.go b/plugins/vpp/l3plugin/vppcalls/vpp2306/vrrp_vppcalls_test.go new file mode 100644 index 0000000000..9b2caabf03 --- /dev/null +++ b/plugins/vpp/l3plugin/vppcalls/vpp2306/vrrp_vppcalls_test.go @@ -0,0 +1,136 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306_test + +import ( + "testing" + + . "github.com/onsi/gomega" + "go.ligato.io/cn-infra/v2/logging/logrus" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/vrrp" + "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/ifaceidx" + "go.ligato.io/vpp-agent/v3/plugins/vpp/l3plugin/vppcalls" + "go.ligato.io/vpp-agent/v3/plugins/vpp/l3plugin/vppcalls/vpp2306" + "go.ligato.io/vpp-agent/v3/plugins/vpp/vppmock" + l3 "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/l3" +) + +var vrrpEntries = []*l3.VRRPEntry{ + { + Interface: "if1", + VrId: 4, + Priority: 100, + Interval: 100, + Preempt: false, + Accept: false, + Unicast: false, + IpAddresses: []string{"192.168.10.21"}, + Enabled: true, + }, + { + Interface: "if1", + VrId: 4, + Priority: 200, + Interval: 300, + Preempt: false, + Accept: false, + Unicast: false, + IpAddresses: []string{"192.168.10.22", "192.168.10.23"}, + Enabled: false, + }, + { + Interface: "if1", + VrId: 6, + Priority: 50, + Interval: 50, + Preempt: false, + Accept: false, + Unicast: false, + IpAddresses: []string{"192.168.10.21"}, + Enabled: true, + }, +} + +// Test an adding of the VRRP +func TestAddVrrp(t *testing.T) { + ctx, ifIndexes, vrrpHandler := vrrpTestSetup(t) + defer ctx.TeardownTestCtx() + + ifIndexes.Put("if1", &ifaceidx.IfaceMetadata{SwIfIndex: 1}) + + ctx.MockVpp.MockReply(&vrrp.VrrpVrAddDelReply{}) + err := vrrpHandler.VppAddVrrp(vrrpEntries[0]) + Expect(err).To(Succeed()) + + ctx.MockVpp.MockReply(&vrrp.VrrpVrAddDelReply{}) + err = vrrpHandler.VppAddVrrp(vrrpEntries[1]) + Expect(err).To(Succeed()) + + ctx.MockVpp.MockReply(&vrrp.VrrpVrAddDelReply{}) + err = vrrpHandler.VppAddVrrp(vrrpEntries[2]) + Expect(err).To(Succeed()) +} + +// Test a deletion of the VRRP +func TestDelVrrp(t *testing.T) { + ctx, ifIndexes, vrrpHandler := vrrpTestSetup(t) + defer ctx.TeardownTestCtx() + + ifIndexes.Put("if1", &ifaceidx.IfaceMetadata{SwIfIndex: 1}) + + ctx.MockVpp.MockReply(&vrrp.VrrpVrAddDelReply{}) + err := vrrpHandler.VppDelVrrp(vrrpEntries[0]) + Expect(err).To(Succeed()) +} + +// Test a start of the VRRP +func TestStartVrrp(t *testing.T) { + ctx, ifIndexes, vrrpHandler := vrrpTestSetup(t) + defer ctx.TeardownTestCtx() + + ifIndexes.Put("if1", &ifaceidx.IfaceMetadata{SwIfIndex: 1}) + + ctx.MockVpp.MockReply(&vrrp.VrrpVrStartStopReply{}) + err := vrrpHandler.VppStartVrrp(vrrpEntries[0]) + Expect(err).To(Succeed()) + + ctx.MockVpp.MockReply(&vrrp.VrrpVrStartStopReply{}) + err = vrrpHandler.VppStartVrrp(vrrpEntries[1]) + Expect(err).To(Succeed()) +} + +// Test a stop of the VRRP +func TestStopVrrp(t *testing.T) { + ctx, ifIndexes, vrrpHandler := vrrpTestSetup(t) + defer ctx.TeardownTestCtx() + + ifIndexes.Put("if1", &ifaceidx.IfaceMetadata{SwIfIndex: 1}) + + ctx.MockVpp.MockReply(&vrrp.VrrpVrStartStopReply{}) + err := vrrpHandler.VppStopVrrp(vrrpEntries[0]) + Expect(err).To(Succeed()) + + ctx.MockVpp.MockReply(&vrrp.VrrpVrStartStopReply{}) + err = vrrpHandler.VppStopVrrp(vrrpEntries[1]) + Expect(err).To(Succeed()) +} + +func vrrpTestSetup(t *testing.T) (*vppmock.TestCtx, ifaceidx.IfaceMetadataIndexRW, vppcalls.VrrpVppAPI) { + ctx := vppmock.SetupTestCtx(t) + log := logrus.NewLogger("test-log") + ifIndexes := ifaceidx.NewIfaceIndex(logrus.NewLogger("test"), "test") + vrrpHandler := vpp2306.NewVrrpVppHandler(ctx.MockChannel, ifIndexes, log) + return ctx, ifIndexes, vrrpHandler +} diff --git a/plugins/vpp/natplugin/natplugin.go b/plugins/vpp/natplugin/natplugin.go index 705788304a..784c407882 100644 --- a/plugins/vpp/natplugin/natplugin.go +++ b/plugins/vpp/natplugin/natplugin.go @@ -37,6 +37,7 @@ import ( _ "go.ligato.io/vpp-agent/v3/plugins/vpp/natplugin/vppcalls/vpp2202" _ "go.ligato.io/vpp-agent/v3/plugins/vpp/natplugin/vppcalls/vpp2210" + _ "go.ligato.io/vpp-agent/v3/plugins/vpp/natplugin/vppcalls/vpp2306" ) // NATPlugin configures VPP NAT. diff --git a/plugins/vpp/natplugin/vppcalls/vpp2306/dump_nat_vppcalls.go b/plugins/vpp/natplugin/vppcalls/vpp2306/dump_nat_vppcalls.go new file mode 100644 index 0000000000..8bc51d9c45 --- /dev/null +++ b/plugins/vpp/natplugin/vppcalls/vpp2306/dump_nat_vppcalls.go @@ -0,0 +1,1236 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "context" + "fmt" + "io" + "math/bits" + "net" + "sort" + "strings" + + "google.golang.org/protobuf/proto" + + vpp_nat_ed "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/nat44_ed" + vpp_nat_ei "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/nat44_ei" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/nat_types" + "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/ifaceidx" + ifs "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/interfaces" + nat "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/nat" +) + +// DNATs sorted by tags +type dnatMap map[string]*nat.DNat44 + +// static mappings sorted by tags +type stMappingMap map[string][]*nat.DNat44_StaticMapping + +// identity mappings sorted by tags +type idMappingMap map[string][]*nat.DNat44_IdentityMapping + +// WithLegacyStartupConf returns true if the loaded VPP NAT plugin is still using +// the legacy startup NAT configuration (this is the case for VPP <= 20.09). +func (h *NatVppHandler) WithLegacyStartupConf() bool { + return false +} + +func (h *NatVppHandler) DefaultNat44GlobalConfig() *nat.Nat44Global { + return &nat.Nat44Global{ + Forwarding: false, + EndpointIndependent: false, + NatInterfaces: nil, + AddressPool: nil, + VirtualReassembly: nil, // VirtualReassembly is not part of NAT API in VPP 20.01+ anymore + } +} + +func (h *NatVppHandler) nat44EiGlobalConfigDump(dumpDeprecated bool) (cfg *nat.Nat44Global, err error) { + cfg = &nat.Nat44Global{} + req := &vpp_nat_ei.Nat44EiShowRunningConfig{} + reply := &vpp_nat_ei.Nat44EiShowRunningConfigReply{} + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return nil, err + } + cfg.EndpointIndependent = true + cfg.Forwarding = reply.ForwardingEnabled + cfg.VirtualReassembly, _, err = h.virtualReassemblyDump() + if err != nil { + return nil, err + } + if dumpDeprecated { + cfg.NatInterfaces, err = h.nat44InterfaceDump() + if err != nil { + return nil, err + } + cfg.AddressPool, err = h.nat44AddressDump() + if err != nil { + return nil, err + } + } + return +} + +func (h *NatVppHandler) nat44EdGlobalConfigDump(dumpDeprecated bool) (cfg *nat.Nat44Global, err error) { + cfg = &nat.Nat44Global{} + req := &vpp_nat_ed.Nat44ShowRunningConfig{} + reply := &vpp_nat_ed.Nat44ShowRunningConfigReply{} + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return nil, err + } + cfg.EndpointIndependent = false + cfg.Forwarding = reply.ForwardingEnabled + cfg.VirtualReassembly, _, err = h.virtualReassemblyDump() + if err != nil { + return nil, err + } + if dumpDeprecated { + cfg.NatInterfaces, err = h.nat44InterfaceDump() + if err != nil { + return nil, err + } + cfg.AddressPool, err = h.nat44AddressDump() + if err != nil { + return nil, err + } + } + return +} + +// Nat44GlobalConfigDump dumps global NAT44 config in NB format. +func (h *NatVppHandler) Nat44GlobalConfigDump(dumpDeprecated bool) (cfg *nat.Nat44Global, err error) { + if h.ed { + return h.nat44EdGlobalConfigDump(dumpDeprecated) + } else { + return h.nat44EiGlobalConfigDump(dumpDeprecated) + } + +} + +// DNat44Dump dumps all configured DNAT-44 configurations ordered by label. +func (h *NatVppHandler) DNat44Dump() (dnats []*nat.DNat44, err error) { + dnatMap := make(dnatMap) + + // Static mappings + natStMappings, err := h.nat44StaticMappingDump() + if err != nil { + return nil, fmt.Errorf("failed to dump NAT44 static mappings: %v", err) + } + for label, mappings := range natStMappings { + dnat := getOrCreateDNAT(dnatMap, label) + dnat.StMappings = append(dnat.StMappings, mappings...) + } + + // Static mappings with load balancer + natStLbMappings, err := h.nat44StaticMappingLbDump() + if err != nil { + return nil, fmt.Errorf("failed to dump NAT44 static mappings with load balancer: %v", err) + } + for label, mappings := range natStLbMappings { + dnat := getOrCreateDNAT(dnatMap, label) + dnat.StMappings = append(dnat.StMappings, mappings...) + } + + // Identity mappings + natIDMappings, err := h.nat44IdentityMappingDump() + if err != nil { + return nil, fmt.Errorf("failed to dump NAT44 identity mappings: %v", err) + } + for label, mappings := range natIDMappings { + dnat := getOrCreateDNAT(dnatMap, label) + dnat.IdMappings = append(dnat.IdMappings, mappings...) + } + + // Convert map of DNAT configurations into a list. + for _, dnat := range dnatMap { + dnats = append(dnats, dnat) + } + + // sort to simplify testing + sort.Slice(dnats, func(i, j int) bool { return dnats[i].Label < dnats[j].Label }) + + return dnats, nil +} + +func (h *NatVppHandler) nat44EiInterfacesDump() ([]*nat.Nat44Interface, error) { + var natIfs []*nat.Nat44Interface + + // dump non-output NAT interfaces + req1 := &vpp_nat_ei.Nat44EiInterfaceDump{} + reqContext := h.callsChannel.SendMultiRequest(req1) + for { + msg := &vpp_nat_ei.Nat44EiInterfaceDetails{} + stop, err := reqContext.ReceiveReply(msg) + if err != nil { + return nil, fmt.Errorf("failed to dump NAT44 interface: %v", err) + } + if stop { + break + } + ifName, _, found := h.ifIndexes.LookupBySwIfIndex(uint32(msg.SwIfIndex)) + if !found { + h.log.Warnf("Interface with index %d not found in the mapping", msg.SwIfIndex) + continue + } + flags := getNat44EiFlags(msg.Flags) + natIf := &nat.Nat44Interface{ + Name: ifName, + NatInside: flags.eiIfInside, + NatOutside: flags.eiIfOutside, + OutputFeature: false, + } + natIfs = append(natIfs, natIf) + } + + // dump output NAT interfaces + var cursor uint32 + for { + rpcServ, err := h.natEi.Nat44EiOutputInterfaceGet(context.Background(), &vpp_nat_ei.Nat44EiOutputInterfaceGet{ + Cursor: cursor, + }) + if err != nil { + return nil, fmt.Errorf("failed to dump NAT44 interface output feature: %v", err) + } + RecvLoop: + for { + details, reply, err := rpcServ.Recv() + if err != nil && err != io.EOF { + return nil, fmt.Errorf("failed to dump NAT44 interface output feature: %v", err) + } + if reply != nil { + // TODO: Possible bug in VPP? When VPP contains no NAT interfaces this + // reply often returns cursor value of 0 repeatedly even though it should return + // ^uint32(0). So if the reply contains cursor that is the same as cursor + // from previous reply, return. + if reply.Cursor == cursor || reply.Cursor == ^uint32(0) { + return natIfs, nil + } + cursor = reply.Cursor + break RecvLoop + } + if details != nil { + ifName, _, found := h.ifIndexes.LookupBySwIfIndex(uint32(details.SwIfIndex)) + if !found { + h.log.Warnf("Interface with index %d not found in the mapping", details.SwIfIndex) + } + natIfs = append(natIfs, &nat.Nat44Interface{ + Name: ifName, + OutputFeature: true, + }) + } + } + } +} + +func (h *NatVppHandler) nat44EdInterfacesDump() ([]*nat.Nat44Interface, error) { + var natIfs []*nat.Nat44Interface + + // dump non-output NAT interfaces + req1 := &vpp_nat_ed.Nat44InterfaceDump{} + reqContext := h.callsChannel.SendMultiRequest(req1) + for { + msg := &vpp_nat_ed.Nat44InterfaceDetails{} + stop, err := reqContext.ReceiveReply(msg) + if err != nil { + return nil, fmt.Errorf("failed to dump NAT44 interface: %v", err) + } + if stop { + break + } + ifName, _, found := h.ifIndexes.LookupBySwIfIndex(uint32(msg.SwIfIndex)) + if !found { + h.log.Warnf("Interface with index %d not found in the mapping", msg.SwIfIndex) + continue + } + flags := getNat44Flags(msg.Flags) + natIf := &nat.Nat44Interface{ + Name: ifName, + NatInside: flags.isInside, + NatOutside: flags.isOutside, + OutputFeature: false, + } + natIfs = append(natIfs, natIf) + } + + // dump output NAT interfaces + var cursor uint32 + for { + rpcServ, err := h.natEd.Nat44EdOutputInterfaceGet(context.Background(), &vpp_nat_ed.Nat44EdOutputInterfaceGet{ + Cursor: cursor, + }) + if err != nil { + return nil, fmt.Errorf("failed to dump NAT44 interface output feature: %v", err) + } + RecvLoop: + for { + details, reply, err := rpcServ.Recv() + if err != nil && err != io.EOF { + return nil, fmt.Errorf("failed to dump NAT44 interface output feature: %v", err) + } + if reply != nil { + // TODO: Possible bug in VPP? When VPP contains no NAT interfaces this + // reply often returns cursor value of 0 repeatedly even though it should return + // ^uint32(0). So if the reply contains cursor that is the same as cursor + // from previous reply, return. + if reply.Cursor == cursor || reply.Cursor == ^uint32(0) { + return natIfs, nil + } + cursor = reply.Cursor + break RecvLoop + } + if details != nil { + ifName, _, found := h.ifIndexes.LookupBySwIfIndex(uint32(details.SwIfIndex)) + if !found { + h.log.Warnf("Interface with index %d not found in the mapping", details.SwIfIndex) + } + natIfs = append(natIfs, &nat.Nat44Interface{ + Name: ifName, + OutputFeature: true, + }) + } + } + } +} + +// Nat44InterfacesDump dumps NAT44 config of all NAT44-enabled interfaces. +func (h *NatVppHandler) Nat44InterfacesDump() (natIfs []*nat.Nat44Interface, err error) { + if h.ed { + return h.nat44EdInterfacesDump() + } else { + return h.nat44EiInterfacesDump() + } +} + +func (h *NatVppHandler) nat44EiAddressPoolsDump() (natPools []*nat.Nat44AddressPool, err error) { + var curPool *nat.Nat44AddressPool + var lastIP net.IP + + req := &vpp_nat_ei.Nat44EiAddressDump{} + reqContext := h.callsChannel.SendMultiRequest(req) + + for { + msg := &vpp_nat_ei.Nat44EiAddressDetails{} + stop, err := reqContext.ReceiveReply(msg) + if err != nil { + return nil, fmt.Errorf("failed to dump NAT44 Address pool: %v", err) + } + if stop { + break + } + ip := net.IP(msg.IPAddress[:]) + // merge subsequent IPs into a single pool + if curPool != nil && curPool.VrfId == msg.VrfID && ip.Equal(incIP(lastIP)) { + // update current pool + curPool.LastIp = ip.String() + } else { + // start a new pool + pool := &nat.Nat44AddressPool{ + FirstIp: ip.String(), + VrfId: msg.VrfID, + TwiceNat: false, + } + curPool = pool + natPools = append(natPools, pool) + } + lastIP = ip + } + return +} + +func (h *NatVppHandler) nat44EdAddressPoolsDump() (natPools []*nat.Nat44AddressPool, err error) { + var curPool *nat.Nat44AddressPool + var lastIP net.IP + + req := &vpp_nat_ed.Nat44AddressDump{} + reqContext := h.callsChannel.SendMultiRequest(req) + + for { + msg := &vpp_nat_ed.Nat44AddressDetails{} + stop, err := reqContext.ReceiveReply(msg) + if err != nil { + return nil, fmt.Errorf("failed to dump NAT44 Address pool: %v", err) + } + if stop { + break + } + ip := net.IP(msg.IPAddress[:]) + isTwiceNat := getNat44Flags(msg.Flags).isTwiceNat + // merge subsequent IPs into a single pool + if curPool != nil && curPool.VrfId == msg.VrfID && curPool.TwiceNat == isTwiceNat && ip.Equal(incIP(lastIP)) { + // update current pool + curPool.LastIp = ip.String() + } else { + // start a new pool + pool := &nat.Nat44AddressPool{ + FirstIp: ip.String(), + VrfId: msg.VrfID, + TwiceNat: isTwiceNat, + } + curPool = pool + natPools = append(natPools, pool) + } + lastIP = ip + } + return +} + +// Nat44AddressPoolsDump dumps all configured NAT44 address pools. +func (h *NatVppHandler) Nat44AddressPoolsDump() (natPools []*nat.Nat44AddressPool, err error) { + if h.ed { + return h.nat44EdAddressPoolsDump() + } else { + return h.nat44EiAddressPoolsDump() + } +} + +func (h *NatVppHandler) nat44EiAddressDump() (addressPool []*nat.Nat44Global_Address, err error) { + req := &vpp_nat_ei.Nat44EiAddressDump{} + reqContext := h.callsChannel.SendMultiRequest(req) + + for { + msg := &vpp_nat_ei.Nat44EiAddressDetails{} + stop, err := reqContext.ReceiveReply(msg) + if err != nil { + return nil, fmt.Errorf("failed to dump NAT44 Address pool: %v", err) + } + if stop { + break + } + + addressPool = append(addressPool, &nat.Nat44Global_Address{ + Address: net.IP(msg.IPAddress[:]).String(), + VrfId: msg.VrfID, + TwiceNat: false, + }) + } + + return +} + +func (h *NatVppHandler) nat44EdAddressDump() (addressPool []*nat.Nat44Global_Address, err error) { + req := &vpp_nat_ed.Nat44AddressDump{} + reqContext := h.callsChannel.SendMultiRequest(req) + + for { + msg := &vpp_nat_ed.Nat44AddressDetails{} + stop, err := reqContext.ReceiveReply(msg) + if err != nil { + return nil, fmt.Errorf("failed to dump NAT44 Address pool: %v", err) + } + if stop { + break + } + + addressPool = append(addressPool, &nat.Nat44Global_Address{ + Address: net.IP(msg.IPAddress[:]).String(), + VrfId: msg.VrfID, + TwiceNat: getNat44Flags(msg.Flags).isTwiceNat, + }) + } + + return +} + +// nat44AddressDump returns NAT44 address pool configured in the VPP. +// Deprecated. Functionality moved to Nat44AddressPoolsDump. Kept for backward compatibility. +func (h *NatVppHandler) nat44AddressDump() (addressPool []*nat.Nat44Global_Address, err error) { + if h.ed { + return h.nat44EdAddressDump() + } else { + return h.nat44EiAddressDump() + } +} + +// virtualReassemblyDump returns current NAT virtual-reassembly configuration. +func (h *NatVppHandler) virtualReassemblyDump() (vrIPv4 *nat.VirtualReassembly, vrIPv6 *nat.VirtualReassembly, err error) { + /*ipv4vr, err := h.ip.IPReassemblyGet(context.TODO(), &vpp_ip.IPReassemblyGet{IsIP6: false}) + if err != nil { + return nil, nil, fmt.Errorf("getting virtual reassembly IPv4 config failed: %w", err) + } + h.log.Debugf("IP Reassembly config IPv4: %+v\n", ipv4vr) + ipv6vr, err := h.ip.IPReassemblyGet(context.TODO(), &vpp_ip.IPReassemblyGet{IsIP6: true}) + if err != nil { + return nil, nil, fmt.Errorf("getting virtual reassembly IPv6 config failed: %w", err) + } + h.log.Debugf("IP Reassembly config IPv6: %+v\n", ipv6vr)*/ + + // Virtual Reassembly has been removed from NAT API in VPP (moved to IP API) + // TODO: define IPReassembly model in L3 plugin + return nil, nil, nil + /*vrIPv4 = &nat.VirtualReassembly{ + Timeout: reply.IP4Timeout, + MaxReassemblies: uint32(reply.IP4MaxReass), + MaxFragments: uint32(reply.IP4MaxFrag), + DropFragments: uintToBool(reply.IP4DropFrag), + } + vrIPv6 = &nat.VirtualReassembly{ + Timeout: reply.IP6Timeout, + MaxReassemblies: uint32(reply.IP6MaxReass), + MaxFragments: uint32(reply.IP6MaxFrag), + DropFragments: uintToBool(reply.IP6DropFrag), + } + return*/ +} + +func (h *NatVppHandler) nat44EiStaticMappingDump() (entries stMappingMap, err error) { + entries = make(stMappingMap) + childMappings := make(stMappingMap) + req := &vpp_nat_ei.Nat44EiStaticMappingDump{} + reqContext := h.callsChannel.SendMultiRequest(req) + + for { + msg := &vpp_nat_ei.Nat44EiStaticMappingDetails{} + stop, err := reqContext.ReceiveReply(msg) + if err != nil { + return nil, fmt.Errorf("failed to dump NAT44 static mapping: %v", err) + } + if stop { + break + } + lcIPAddress := net.IP(msg.LocalIPAddress[:]).String() + exIPAddress := net.IP(msg.ExternalIPAddress[:]).String() + + // Parse tag (DNAT label) + tag := strings.TrimRight(msg.Tag, "\x00") + if _, hasTag := entries[tag]; !hasTag { + entries[tag] = []*nat.DNat44_StaticMapping{} + childMappings[tag] = []*nat.DNat44_StaticMapping{} + } + + // resolve interface name + var ( + found bool + extIfaceName string + extIfaceMeta *ifaceidx.IfaceMetadata + ) + if msg.ExternalSwIfIndex != NoInterface { + extIfaceName, extIfaceMeta, found = h.ifIndexes.LookupBySwIfIndex(uint32(msg.ExternalSwIfIndex)) + if !found { + h.log.Warnf("Interface with index %v not found in the mapping", msg.ExternalSwIfIndex) + continue + } + } + + // Add mapping into the map. + mapping := &nat.DNat44_StaticMapping{ + ExternalInterface: extIfaceName, + ExternalIp: exIPAddress, + ExternalPort: uint32(msg.ExternalPort), + LocalIps: []*nat.DNat44_StaticMapping_LocalIP{ // single-value + { + VrfId: msg.VrfID, + LocalIp: lcIPAddress, + LocalPort: uint32(msg.LocalPort), + }, + }, + Protocol: h.protocolNumberToNBValue(msg.Protocol), + TwiceNat: h.getTwiceNatMode(false, false), + // if there is only one backend the affinity can not be set + SessionAffinity: 0, + } + entries[tag] = append(entries[tag], mapping) + + if msg.ExternalSwIfIndex != NoInterface { + // collect auto-generated "child" mappings (interface replaced with every assigned IP address) + for _, ipAddr := range h.getInterfaceIPAddresses(extIfaceName, extIfaceMeta) { + childMapping := proto.Clone(mapping).(*nat.DNat44_StaticMapping) + childMapping.ExternalIp = ipAddr + childMapping.ExternalInterface = "" + childMappings[tag] = append(childMappings[tag], childMapping) + } + } + } + + // do not dump auto-generated child mappings + for tag, mappings := range entries { + var filtered []*nat.DNat44_StaticMapping + for _, mapping := range mappings { + isChild := false + for _, child := range childMappings[tag] { + if proto.Equal(mapping, child) { + isChild = true + break + } + } + if !isChild { + filtered = append(filtered, mapping) + } + } + entries[tag] = filtered + } + return entries, nil +} + +func (h *NatVppHandler) nat44EdStaticMappingDump() (entries stMappingMap, err error) { + entries = make(stMappingMap) + childMappings := make(stMappingMap) + req := &vpp_nat_ed.Nat44StaticMappingDump{} + reqContext := h.callsChannel.SendMultiRequest(req) + + for { + msg := &vpp_nat_ed.Nat44StaticMappingDetails{} + stop, err := reqContext.ReceiveReply(msg) + if err != nil { + return nil, fmt.Errorf("failed to dump NAT44 static mapping: %v", err) + } + if stop { + break + } + lcIPAddress := net.IP(msg.LocalIPAddress[:]).String() + exIPAddress := net.IP(msg.ExternalIPAddress[:]).String() + + // Parse tag (DNAT label) + tag := strings.TrimRight(msg.Tag, "\x00") + if _, hasTag := entries[tag]; !hasTag { + entries[tag] = []*nat.DNat44_StaticMapping{} + childMappings[tag] = []*nat.DNat44_StaticMapping{} + } + + // resolve interface name + var ( + found bool + extIfaceName string + extIfaceMeta *ifaceidx.IfaceMetadata + ) + if msg.ExternalSwIfIndex != NoInterface { + extIfaceName, extIfaceMeta, found = h.ifIndexes.LookupBySwIfIndex(uint32(msg.ExternalSwIfIndex)) + if !found { + h.log.Warnf("Interface with index %v not found in the mapping", msg.ExternalSwIfIndex) + continue + } + } + + flags := getNat44Flags(msg.Flags) + + // Add mapping into the map. + mapping := &nat.DNat44_StaticMapping{ + ExternalInterface: extIfaceName, + ExternalIp: exIPAddress, + ExternalPort: uint32(msg.ExternalPort), + LocalIps: []*nat.DNat44_StaticMapping_LocalIP{ // single-value + { + VrfId: msg.VrfID, + LocalIp: lcIPAddress, + LocalPort: uint32(msg.LocalPort), + }, + }, + Protocol: h.protocolNumberToNBValue(msg.Protocol), + TwiceNat: h.getTwiceNatMode(flags.isTwiceNat, flags.isSelfTwiceNat), + // if there is only one backend the affinity can not be set + SessionAffinity: 0, + } + entries[tag] = append(entries[tag], mapping) + + if msg.ExternalSwIfIndex != NoInterface { + // collect auto-generated "child" mappings (interface replaced with every assigned IP address) + for _, ipAddr := range h.getInterfaceIPAddresses(extIfaceName, extIfaceMeta) { + childMapping := proto.Clone(mapping).(*nat.DNat44_StaticMapping) + childMapping.ExternalIp = ipAddr + childMapping.ExternalInterface = "" + childMappings[tag] = append(childMappings[tag], childMapping) + } + } + } + + // do not dump auto-generated child mappings + for tag, mappings := range entries { + var filtered []*nat.DNat44_StaticMapping + for _, mapping := range mappings { + isChild := false + for _, child := range childMappings[tag] { + if proto.Equal(mapping, child) { + isChild = true + break + } + } + if !isChild { + filtered = append(filtered, mapping) + } + } + entries[tag] = filtered + } + return entries, nil +} + +// nat44StaticMappingDump returns a map of NAT44 static mappings sorted by tags +func (h *NatVppHandler) nat44StaticMappingDump() (entries stMappingMap, err error) { + if h.ed { + return h.nat44EdStaticMappingDump() + } else { + return h.nat44EiStaticMappingDump() + } +} + +func (h *NatVppHandler) nat44EiStaticMappingLbDump() (entries stMappingMap, err error) { + return make(stMappingMap), nil +} + +func (h *NatVppHandler) nat44EdStaticMappingLbDump() (entries stMappingMap, err error) { + entries = make(stMappingMap) + req := &vpp_nat_ed.Nat44LbStaticMappingDump{} + reqContext := h.callsChannel.SendMultiRequest(req) + + for { + msg := &vpp_nat_ed.Nat44LbStaticMappingDetails{} + stop, err := reqContext.ReceiveReply(msg) + if err != nil { + return nil, fmt.Errorf("failed to dump NAT44 lb-static mapping: %v", err) + } + if stop { + break + } + + // Parse tag (DNAT label) + tag := strings.TrimRight(msg.Tag, "\x00") + if _, hasTag := entries[tag]; !hasTag { + entries[tag] = []*nat.DNat44_StaticMapping{} + } + + // Prepare localIPs + var locals []*nat.DNat44_StaticMapping_LocalIP + for _, localIPVal := range msg.Locals { + locals = append(locals, &nat.DNat44_StaticMapping_LocalIP{ + VrfId: localIPVal.VrfID, + LocalIp: net.IP(localIPVal.Addr[:]).String(), + LocalPort: uint32(localIPVal.Port), + Probability: uint32(localIPVal.Probability), + }) + } + exIPAddress := net.IP(msg.ExternalAddr[:]).String() + + flags := getNat44Flags(msg.Flags) + + // Add mapping into the map. + mapping := &nat.DNat44_StaticMapping{ + ExternalIp: exIPAddress, + ExternalPort: uint32(msg.ExternalPort), + LocalIps: locals, + Protocol: h.protocolNumberToNBValue(msg.Protocol), + TwiceNat: h.getTwiceNatMode(flags.isTwiceNat, flags.isSelfTwiceNat), + SessionAffinity: msg.Affinity, + } + entries[tag] = append(entries[tag], mapping) + } + + return entries, nil +} + +// nat44StaticMappingLbDump returns a map of NAT44 static mapping with load balancing sorted by tags. +func (h *NatVppHandler) nat44StaticMappingLbDump() (entries stMappingMap, err error) { + if h.ed { + return h.nat44EdStaticMappingLbDump() + } else { + return h.nat44EiStaticMappingLbDump() + } +} + +func (h *NatVppHandler) nat44EiIdentityMappingDump() (entries idMappingMap, err error) { + entries = make(idMappingMap) + childMappings := make(idMappingMap) + req := &vpp_nat_ei.Nat44EiIdentityMappingDump{} + reqContext := h.callsChannel.SendMultiRequest(req) + + for { + msg := &vpp_nat_ei.Nat44EiIdentityMappingDetails{} + stop, err := reqContext.ReceiveReply(msg) + if err != nil { + return nil, fmt.Errorf("failed to dump NAT44 identity mapping: %v", err) + } + if stop { + break + } + + // Parse tag (DNAT label) + tag := strings.TrimRight(msg.Tag, "\x00") + if _, hasTag := entries[tag]; !hasTag { + entries[tag] = []*nat.DNat44_IdentityMapping{} + childMappings[tag] = []*nat.DNat44_IdentityMapping{} + } + + // resolve interface name + var ( + found bool + ifaceName string + ifaceMeta *ifaceidx.IfaceMetadata + ) + if msg.SwIfIndex != NoInterface { + ifaceName, ifaceMeta, found = h.ifIndexes.LookupBySwIfIndex(uint32(msg.SwIfIndex)) + if !found { + h.log.Warnf("Interface with index %v not found in the mapping", msg.SwIfIndex) + continue + } + } + + // Add mapping into the map. + mapping := &nat.DNat44_IdentityMapping{ + IpAddress: net.IP(msg.IPAddress[:]).String(), + VrfId: msg.VrfID, + Interface: ifaceName, + Port: uint32(msg.Port), + Protocol: h.protocolNumberToNBValue(msg.Protocol), + } + entries[tag] = append(entries[tag], mapping) + + if msg.SwIfIndex != NoInterface { + // collect auto-generated "child" mappings (interface replaced with every assigned IP address) + for _, ipAddr := range h.getInterfaceIPAddresses(ifaceName, ifaceMeta) { + childMapping := proto.Clone(mapping).(*nat.DNat44_IdentityMapping) + childMapping.IpAddress = ipAddr + childMapping.Interface = "" + childMappings[tag] = append(childMappings[tag], childMapping) + } + } + } + + // do not dump auto-generated child mappings + for tag, mappings := range entries { + var filtered []*nat.DNat44_IdentityMapping + for _, mapping := range mappings { + isChild := false + for _, child := range childMappings[tag] { + if proto.Equal(mapping, child) { + isChild = true + break + } + } + if !isChild { + filtered = append(filtered, mapping) + } + } + entries[tag] = filtered + } + + return entries, nil +} + +func (h *NatVppHandler) nat44EdIdentityMappingDump() (entries idMappingMap, err error) { + entries = make(idMappingMap) + childMappings := make(idMappingMap) + req := &vpp_nat_ed.Nat44IdentityMappingDump{} + reqContext := h.callsChannel.SendMultiRequest(req) + + for { + msg := &vpp_nat_ed.Nat44IdentityMappingDetails{} + stop, err := reqContext.ReceiveReply(msg) + if err != nil { + return nil, fmt.Errorf("failed to dump NAT44 identity mapping: %v", err) + } + if stop { + break + } + + // Parse tag (DNAT label) + tag := strings.TrimRight(msg.Tag, "\x00") + if _, hasTag := entries[tag]; !hasTag { + entries[tag] = []*nat.DNat44_IdentityMapping{} + childMappings[tag] = []*nat.DNat44_IdentityMapping{} + } + + // resolve interface name + var ( + found bool + ifaceName string + ifaceMeta *ifaceidx.IfaceMetadata + ) + if msg.SwIfIndex != NoInterface { + ifaceName, ifaceMeta, found = h.ifIndexes.LookupBySwIfIndex(uint32(msg.SwIfIndex)) + if !found { + h.log.Warnf("Interface with index %v not found in the mapping", msg.SwIfIndex) + continue + } + } + + // Add mapping into the map. + mapping := &nat.DNat44_IdentityMapping{ + IpAddress: net.IP(msg.IPAddress[:]).String(), + VrfId: msg.VrfID, + Interface: ifaceName, + Port: uint32(msg.Port), + Protocol: h.protocolNumberToNBValue(msg.Protocol), + } + entries[tag] = append(entries[tag], mapping) + + if msg.SwIfIndex != NoInterface { + // collect auto-generated "child" mappings (interface replaced with every assigned IP address) + for _, ipAddr := range h.getInterfaceIPAddresses(ifaceName, ifaceMeta) { + childMapping := proto.Clone(mapping).(*nat.DNat44_IdentityMapping) + childMapping.IpAddress = ipAddr + childMapping.Interface = "" + childMappings[tag] = append(childMappings[tag], childMapping) + } + } + } + + // do not dump auto-generated child mappings + for tag, mappings := range entries { + var filtered []*nat.DNat44_IdentityMapping + for _, mapping := range mappings { + isChild := false + for _, child := range childMappings[tag] { + if proto.Equal(mapping, child) { + isChild = true + break + } + } + if !isChild { + filtered = append(filtered, mapping) + } + } + entries[tag] = filtered + } + + return entries, nil +} + +// nat44IdentityMappingDump returns a map of NAT44 identity mappings sorted by tags. +func (h *NatVppHandler) nat44IdentityMappingDump() (entries idMappingMap, err error) { + if h.ed { + return h.nat44EdIdentityMappingDump() + } else { + return h.nat44EiIdentityMappingDump() + } +} + +func (h *NatVppHandler) nat44EiInterfaceDump() ([]*nat.Nat44Global_Interface, error) { + var natIfs []*nat.Nat44Global_Interface + + // dump non-output NAT interfaces + req1 := &vpp_nat_ei.Nat44EiInterfaceDump{} + reqContext := h.callsChannel.SendMultiRequest(req1) + + for { + msg := &vpp_nat_ei.Nat44EiInterfaceDetails{} + stop, err := reqContext.ReceiveReply(msg) + if err != nil { + return nil, fmt.Errorf("failed to dump NAT44 interface: %v", err) + } + if stop { + break + } + + // Find interface name + ifName, _, found := h.ifIndexes.LookupBySwIfIndex(uint32(msg.SwIfIndex)) + if !found { + h.log.Warnf("Interface with index %d not found in the mapping", msg.SwIfIndex) + continue + } + + flags := getNat44EiFlags(msg.Flags) + + if flags.eiIfInside { + natIfs = append(natIfs, &nat.Nat44Global_Interface{ + Name: ifName, + IsInside: true, + }) + } else { + natIfs = append(natIfs, &nat.Nat44Global_Interface{ + Name: ifName, + IsInside: false, + }) + } + } + + // dump output NAT interfaces + var cursor uint32 + for { + rpcServ, err := h.natEi.Nat44EiOutputInterfaceGet(context.Background(), &vpp_nat_ei.Nat44EiOutputInterfaceGet{ + Cursor: cursor, + }) + if err != nil { + return nil, fmt.Errorf("failed to dump NAT44 interface output feature: %v", err) + } + RecvLoop: + for { + details, reply, err := rpcServ.Recv() + if err != nil && err != io.EOF { + return nil, fmt.Errorf("failed to dump NAT44 interface output feature: %v", err) + } + if reply != nil { + // TODO: Possible bug in VPP? When VPP contains no NAT interfaces this + // reply often returns cursor value of 0 repeatedly even though it should return + // ^uint32(0). So if the reply contains cursor that is the same as cursor + // from previous reply, return. + if reply.Cursor == cursor || reply.Cursor == ^uint32(0) { + return natIfs, nil + } + cursor = reply.Cursor + break RecvLoop + } + if details != nil { + ifName, _, found := h.ifIndexes.LookupBySwIfIndex(uint32(details.SwIfIndex)) + if !found { + h.log.Warnf("Interface with index %d not found in the mapping", details.SwIfIndex) + } + natIfs = append(natIfs, &nat.Nat44Global_Interface{ + Name: ifName, + OutputFeature: true, + }) + } + } + } +} + +func (h *NatVppHandler) nat44EdInterfaceDump() ([]*nat.Nat44Global_Interface, error) { + var natIfs []*nat.Nat44Global_Interface + + // dump non-output NAT interfaces + req1 := &vpp_nat_ed.Nat44InterfaceDump{} + reqContext := h.callsChannel.SendMultiRequest(req1) + + for { + msg := &vpp_nat_ed.Nat44InterfaceDetails{} + stop, err := reqContext.ReceiveReply(msg) + if err != nil { + return nil, fmt.Errorf("failed to dump NAT44 interface: %v", err) + } + if stop { + break + } + + // Find interface name + ifName, _, found := h.ifIndexes.LookupBySwIfIndex(uint32(msg.SwIfIndex)) + if !found { + h.log.Warnf("Interface with index %d not found in the mapping", msg.SwIfIndex) + continue + } + + flags := getNat44Flags(msg.Flags) + + if flags.isInside { + natIfs = append(natIfs, &nat.Nat44Global_Interface{ + Name: ifName, + IsInside: true, + }) + } else { + natIfs = append(natIfs, &nat.Nat44Global_Interface{ + Name: ifName, + IsInside: false, + }) + } + } + + // dump output NAT interfaces + var cursor uint32 + for { + rpcServ, err := h.natEd.Nat44EdOutputInterfaceGet(context.Background(), &vpp_nat_ed.Nat44EdOutputInterfaceGet{ + Cursor: cursor, + }) + if err != nil { + return nil, fmt.Errorf("failed to dump NAT44 interface output feature: %v", err) + } + RecvLoop: + for { + details, reply, err := rpcServ.Recv() + if err != nil && err != io.EOF { + return nil, fmt.Errorf("failed to dump NAT44 interface output feature: %v", err) + } + if reply != nil { + // TODO: Possible bug in VPP? When VPP contains no NAT interfaces this + // reply often returns cursor value of 0 repeatedly even though it should return + // ^uint32(0). So if the reply contains cursor that is the same as cursor + // from previous reply, return. + if reply.Cursor == cursor || reply.Cursor == ^uint32(0) { + return natIfs, nil + } + cursor = reply.Cursor + break RecvLoop + } + if details != nil { + ifName, _, found := h.ifIndexes.LookupBySwIfIndex(uint32(details.SwIfIndex)) + if !found { + h.log.Warnf("Interface with index %d not found in the mapping", details.SwIfIndex) + } + natIfs = append(natIfs, &nat.Nat44Global_Interface{ + Name: ifName, + OutputFeature: true, + }) + } + } + } +} + +// nat44InterfaceDump dumps NAT44 interface features. +// Deprecated. Functionality moved to Nat44Nat44InterfacesDump. Kept for backward compatibility. +func (h *NatVppHandler) nat44InterfaceDump() ([]*nat.Nat44Global_Interface, error) { + if h.ed { + return h.nat44EdInterfaceDump() + } else { + return h.nat44EiInterfaceDump() + } +} + +func (h *NatVppHandler) getInterfaceIPAddresses(ifaceName string, ifaceMeta *ifaceidx.IfaceMetadata) (ipAddrs []string) { + ipAddrNets := ifaceMeta.IPAddresses + dhcpLease, hasDHCPLease := h.dhcpIndex.GetValue(ifaceName) + if hasDHCPLease { + lease := dhcpLease.(*ifs.DHCPLease) + ipAddrNets = append(ipAddrNets, lease.HostIpAddress) + } + for _, ipAddrNet := range ipAddrNets { + ipAddr := strings.Split(ipAddrNet, "/")[0] + ipAddrs = append(ipAddrs, ipAddr) + } + return ipAddrs +} + +// protocolNumberToNBValue converts protocol numeric representation into the corresponding enum +// enum value from the NB model. +func (h *NatVppHandler) protocolNumberToNBValue(protocol uint8) (proto nat.DNat44_Protocol) { + switch protocol { + case TCP: + return nat.DNat44_TCP + case UDP: + return nat.DNat44_UDP + case ICMP: + return nat.DNat44_ICMP + default: + h.log.Warnf("Unknown protocol %v", protocol) + return 0 + } +} + +// protocolNBValueToNumber converts protocol enum value from the NB model into the +// corresponding numeric representation. +func (h *NatVppHandler) protocolNBValueToNumber(protocol nat.DNat44_Protocol) (proto uint8) { + switch protocol { + case nat.DNat44_TCP: + return TCP + case nat.DNat44_UDP: + return UDP + case nat.DNat44_ICMP: + return ICMP + default: + h.log.Warnf("Unknown protocol %v, defaulting to TCP", protocol) + return TCP + } +} + +func (h *NatVppHandler) getTwiceNatMode(twiceNat, selfTwiceNat bool) nat.DNat44_StaticMapping_TwiceNatMode { + if twiceNat { + if selfTwiceNat { + h.log.Warnf("Both TwiceNAT and self-TwiceNAT are enabled") + return 0 + } + return nat.DNat44_StaticMapping_ENABLED + } + if selfTwiceNat { + return nat.DNat44_StaticMapping_SELF + } + return nat.DNat44_StaticMapping_DISABLED +} + +func (h *NatVppHandler) Nat44VrfTablesDump() ([]*nat.Nat44VrfTable, error) { + var msgs []*nat.Nat44VrfTable + + req1 := &vpp_nat_ed.Nat44EdVrfTablesDump{} + reqContext := h.callsChannel.SendMultiRequest(req1) + + for { + msg := &vpp_nat_ed.Nat44EdVrfTablesDetails{} + stop, err := reqContext.ReceiveReply(msg) + if err != nil { + return msgs, fmt.Errorf("failed to dump NAT44 VRF tables: %v", err) + } + if stop { + break + } + msgs = append(msgs, &nat.Nat44VrfTable{ + SrcVrfId: msg.TableVrfID, + DestVrfIds: msg.VrfIds}) + // fixing bad endianess, Check if new version of VPP doesn't contain nat44_ed_vrf_tables_v2_dump + for i := 0; i < int(len(msgs[len(msgs)-1].DestVrfIds)); i++ { + msgs[len(msgs)-1].DestVrfIds[i] = bits.ReverseBytes32(msgs[len(msgs)-1].DestVrfIds[i]) + } + + } + return msgs, nil +} + +func getOrCreateDNAT(dnats dnatMap, label string) *nat.DNat44 { + if _, created := dnats[label]; !created { + dnats[label] = &nat.DNat44{Label: label} + } + return dnats[label] +} + +func getNat44Flags(flags nat_types.NatConfigFlags) *nat44EdFlags { + natFlags := &nat44EdFlags{} + if flags&nat_types.NAT_IS_EXT_HOST_VALID != 0 { + natFlags.isExtHostValid = true + } + if flags&nat_types.NAT_IS_STATIC != 0 { + natFlags.isStatic = true + } + if flags&nat_types.NAT_IS_INSIDE != 0 { + natFlags.isInside = true + } + if flags&nat_types.NAT_IS_OUTSIDE != 0 { + natFlags.isOutside = true + } + if flags&nat_types.NAT_IS_ADDR_ONLY != 0 { + natFlags.isAddrOnly = true + } + if flags&nat_types.NAT_IS_OUT2IN_ONLY != 0 { + natFlags.isOut2In = true + } + if flags&nat_types.NAT_IS_SELF_TWICE_NAT != 0 { + natFlags.isSelfTwiceNat = true + } + if flags&nat_types.NAT_IS_TWICE_NAT != 0 { + natFlags.isTwiceNat = true + } + return natFlags +} + +func getNat44EiFlags(flags vpp_nat_ei.Nat44EiConfigFlags) *nat44EiFlags { + natFlags := &nat44EiFlags{} + if flags&vpp_nat_ei.NAT44_EI_STATIC_MAPPING_ONLY != 0 { + natFlags.eiStaticMappingOnly = true + } + if flags&vpp_nat_ei.NAT44_EI_CONNECTION_TRACKING != 0 { + natFlags.eiConnectionTracking = true + } + if flags&vpp_nat_ei.NAT44_EI_OUT2IN_DPO != 0 { + natFlags.eiOut2InDpo = true + } + if flags&vpp_nat_ei.NAT44_EI_ADDR_ONLY_MAPPING != 0 { + natFlags.eiAddrOnlyMapping = true + } + if flags&vpp_nat_ei.NAT44_EI_IF_INSIDE != 0 { + natFlags.eiIfInside = true + } + if flags&vpp_nat_ei.NAT44_EI_IF_OUTSIDE != 0 { + natFlags.eiIfOutside = true + } + if flags&vpp_nat_ei.NAT44_EI_STATIC_MAPPING != 0 { + natFlags.eiStaticMapping = true + } + return natFlags +} + +// incIP increments IP address and returns it. +// Based on: https://play.golang.org/p/m8TNTtygK0 +func incIP(ip net.IP) net.IP { + retIP := make(net.IP, len(ip)) + copy(retIP, ip) + for j := len(retIP) - 1; j >= 0; j-- { + retIP[j]++ + if retIP[j] > 0 { + break + } + } + return retIP +} diff --git a/plugins/vpp/natplugin/vppcalls/vpp2306/dump_nat_vppcalls_test.go b/plugins/vpp/natplugin/vppcalls/vpp2306/dump_nat_vppcalls_test.go new file mode 100644 index 0000000000..86d1e532b6 --- /dev/null +++ b/plugins/vpp/natplugin/vppcalls/vpp2306/dump_nat_vppcalls_test.go @@ -0,0 +1,626 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306_test + +import ( + "math/bits" + "net" + "testing" + + . "github.com/onsi/gomega" + + ifs "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/interfaces" + nat "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/nat" + + "go.ligato.io/cn-infra/v2/idxmap" + idxmap_mem "go.ligato.io/cn-infra/v2/idxmap/mem" + "go.ligato.io/cn-infra/v2/logging/logrus" + + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip_types" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/memclnt" + vpp_nat_ed "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/nat44_ed" + vpp_nat_ei "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/nat44_ei" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/nat_types" + "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/ifaceidx" + "go.ligato.io/vpp-agent/v3/plugins/vpp/natplugin/vppcalls" + "go.ligato.io/vpp-agent/v3/plugins/vpp/natplugin/vppcalls/vpp2306" + "go.ligato.io/vpp-agent/v3/plugins/vpp/vppmock" +) + +func TestNat44EdGlobalConfigDump(t *testing.T) { + ctx, natHandler, swIfIndexes, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44EdPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: true}) + Expect(err).ShouldNot(HaveOccurred()) + + // forwarding + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44ShowRunningConfigReply{ + ForwardingEnabled: true, + }) + + // virtual reassembly + /*ctx.MockVpp.MockReply(&vpp_nat_ed.NatGetReassReply{ + // IPv4 + IP4Timeout: 10, + IP4MaxReass: 5, + IP4MaxFrag: 7, + IP4DropFrag: 1, + // IPv6 + IP6Timeout: 20, + IP6MaxReass: 8, + IP6MaxFrag: 13, + IP6DropFrag: 0,* + })*/ + + // non-output interfaces + ctx.MockVpp.MockReply( + &vpp_nat_ed.Nat44InterfaceDetails{ + SwIfIndex: 1, + }, + &vpp_nat_ed.Nat44InterfaceDetails{ + SwIfIndex: 2, + Flags: nat_types.NAT_IS_INSIDE, + }) + ctx.MockVpp.MockReply(&memclnt.ControlPingReply{}) + + // output interfaces + ctx.MockVpp.MockReply( + &vpp_nat_ed.Nat44EdOutputInterfaceDetails{ + SwIfIndex: 3, + }, + &vpp_nat_ed.Nat44EdOutputInterfaceGetReply{ + Retval: 0, + Cursor: ^uint32(0), + }) + + // address pool + ctx.MockVpp.MockReply( + &vpp_nat_ed.Nat44AddressDetails{ + IPAddress: ipTo4Address("192.168.10.1"), + Flags: nat_types.NAT_IS_TWICE_NAT, + VrfID: 1, + }, + &vpp_nat_ed.Nat44AddressDetails{ + IPAddress: ipTo4Address("192.168.10.2"), + VrfID: 2, + }) + ctx.MockVpp.MockReply(&memclnt.ControlPingReply{}) + + swIfIndexes.Put("if0", &ifaceidx.IfaceMetadata{SwIfIndex: 1}) + swIfIndexes.Put("if1", &ifaceidx.IfaceMetadata{SwIfIndex: 2}) + swIfIndexes.Put("if2", &ifaceidx.IfaceMetadata{SwIfIndex: 3}) + + globalCfg, err := natHandler.Nat44GlobalConfigDump(true) + Expect(err).To(Succeed()) + + Expect(globalCfg.Forwarding).To(BeTrue()) + + Expect(globalCfg.AddressPool).To(HaveLen(2)) + Expect(globalCfg.AddressPool[0].Address).To(Equal("192.168.10.1")) + Expect(globalCfg.AddressPool[0].TwiceNat).To(BeTrue()) + Expect(globalCfg.AddressPool[0].VrfId).To(BeEquivalentTo(1)) + Expect(globalCfg.AddressPool[1].Address).To(Equal("192.168.10.2")) + Expect(globalCfg.AddressPool[1].TwiceNat).To(BeFalse()) + Expect(globalCfg.AddressPool[1].VrfId).To(BeEquivalentTo(2)) + + Expect(globalCfg.NatInterfaces).To(HaveLen(3)) + Expect(globalCfg.NatInterfaces[0].Name).To(Equal("if0")) + Expect(globalCfg.NatInterfaces[0].IsInside).To(BeFalse()) + Expect(globalCfg.NatInterfaces[0].OutputFeature).To(BeFalse()) + Expect(globalCfg.NatInterfaces[1].Name).To(Equal("if1")) + Expect(globalCfg.NatInterfaces[1].IsInside).To(BeTrue()) + Expect(globalCfg.NatInterfaces[1].OutputFeature).To(BeFalse()) + Expect(globalCfg.NatInterfaces[2].Name).To(Equal("if2")) + Expect(globalCfg.NatInterfaces[2].IsInside).To(BeFalse()) + Expect(globalCfg.NatInterfaces[2].OutputFeature).To(BeTrue()) + + /*Expect(globalCfg.VirtualReassembly).ToNot(BeNil()) + Expect(globalCfg.VirtualReassembly.Timeout).To(BeEquivalentTo(10)) + Expect(globalCfg.VirtualReassembly.MaxReassemblies).To(BeEquivalentTo(5)) + Expect(globalCfg.VirtualReassembly.MaxFragments).To(BeEquivalentTo(7)) + Expect(globalCfg.VirtualReassembly.DropFragments).To(BeTrue())*/ +} + +func TestNat44EdInterfacesDump(t *testing.T) { + ctx, natHandler, swIfIndexes, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44EdPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: true}) + Expect(err).ShouldNot(HaveOccurred()) + + // non-output interfaces + ctx.MockVpp.MockReply( + &vpp_nat_ed.Nat44InterfaceDetails{ + SwIfIndex: 1, + Flags: nat_types.NAT_IS_OUTSIDE, + }, + &vpp_nat_ed.Nat44InterfaceDetails{ + SwIfIndex: 2, + Flags: nat_types.NAT_IS_INSIDE, + }) + ctx.MockVpp.MockReply(&memclnt.ControlPingReply{}) + + // output interfaces + ctx.MockVpp.MockReply( + &vpp_nat_ed.Nat44EdOutputInterfaceDetails{ + SwIfIndex: 3, + }, + &vpp_nat_ed.Nat44EdOutputInterfaceGetReply{ + Retval: 0, + Cursor: ^uint32(0), + }) + + swIfIndexes.Put("if0", &ifaceidx.IfaceMetadata{SwIfIndex: 1}) + swIfIndexes.Put("if1", &ifaceidx.IfaceMetadata{SwIfIndex: 2}) + swIfIndexes.Put("if2", &ifaceidx.IfaceMetadata{SwIfIndex: 3}) + + interfaces, err := natHandler.Nat44InterfacesDump() + Expect(err).To(Succeed()) + + Expect(interfaces).To(HaveLen(3)) + + Expect(interfaces[0].Name).To(Equal("if0")) + Expect(interfaces[0].NatInside).To(BeFalse()) + Expect(interfaces[0].NatOutside).To(BeTrue()) + Expect(interfaces[0].OutputFeature).To(BeFalse()) + + Expect(interfaces[1].Name).To(Equal("if1")) + Expect(interfaces[1].NatInside).To(BeTrue()) + Expect(interfaces[1].NatOutside).To(BeFalse()) + Expect(interfaces[1].OutputFeature).To(BeFalse()) + + Expect(interfaces[2].Name).To(Equal("if2")) + Expect(interfaces[2].NatInside).To(BeFalse()) + Expect(interfaces[2].NatOutside).To(BeFalse()) + Expect(interfaces[2].OutputFeature).To(BeTrue()) +} + +func TestNat44EiInterfacesDump(t *testing.T) { + ctx, natHandler, swIfIndexes, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: false}) + Expect(err).ShouldNot(HaveOccurred()) + + // non-output interfaces + ctx.MockVpp.MockReply( + &vpp_nat_ei.Nat44EiInterfaceDetails{ + SwIfIndex: 1, + Flags: vpp_nat_ei.NAT44_EI_IF_OUTSIDE, + }, + &vpp_nat_ei.Nat44EiInterfaceDetails{ + SwIfIndex: 2, + Flags: vpp_nat_ei.NAT44_EI_IF_INSIDE, + }) + ctx.MockVpp.MockReply(&memclnt.ControlPingReply{}) + + // output interfaces + ctx.MockVpp.MockReply( + &vpp_nat_ei.Nat44EiOutputInterfaceDetails{ + SwIfIndex: 3, + }, + &vpp_nat_ei.Nat44EiOutputInterfaceGetReply{ + Retval: 0, + Cursor: ^uint32(0), + }) + + swIfIndexes.Put("if0", &ifaceidx.IfaceMetadata{SwIfIndex: 1}) + swIfIndexes.Put("if1", &ifaceidx.IfaceMetadata{SwIfIndex: 2}) + swIfIndexes.Put("if2", &ifaceidx.IfaceMetadata{SwIfIndex: 3}) + + interfaces, err := natHandler.Nat44InterfacesDump() + Expect(err).To(Succeed()) + + Expect(interfaces).To(HaveLen(3)) + + Expect(interfaces[0].Name).To(Equal("if0")) + Expect(interfaces[0].NatInside).To(BeFalse()) + Expect(interfaces[0].NatOutside).To(BeTrue()) + Expect(interfaces[0].OutputFeature).To(BeFalse()) + + Expect(interfaces[1].Name).To(Equal("if1")) + Expect(interfaces[1].NatInside).To(BeTrue()) + Expect(interfaces[1].NatOutside).To(BeFalse()) + Expect(interfaces[1].OutputFeature).To(BeFalse()) + + Expect(interfaces[2].Name).To(Equal("if2")) + Expect(interfaces[2].NatInside).To(BeFalse()) + Expect(interfaces[2].NatOutside).To(BeFalse()) + Expect(interfaces[2].OutputFeature).To(BeTrue()) +} + +func TestNat44EdAddressPoolsDump(t *testing.T) { + ctx, natHandler, _, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44EdPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: true}) + Expect(err).ShouldNot(HaveOccurred()) + + // address pool + ctx.MockVpp.MockReply( + &vpp_nat_ed.Nat44AddressDetails{ + IPAddress: ipTo4Address("192.168.10.1"), + Flags: nat_types.NAT_IS_TWICE_NAT, + VrfID: 1, + }, + &vpp_nat_ed.Nat44AddressDetails{ + IPAddress: ipTo4Address("192.168.10.2"), + VrfID: 2, + }, + &vpp_nat_ed.Nat44AddressDetails{ + IPAddress: ipTo4Address("192.168.10.3"), + VrfID: 2, + }, + &vpp_nat_ed.Nat44AddressDetails{ + IPAddress: ipTo4Address("192.168.10.4"), + VrfID: 2, + }) + ctx.MockVpp.MockReply(&memclnt.ControlPingReply{}) + + pools, err := natHandler.Nat44AddressPoolsDump() + Expect(err).To(Succeed()) + + Expect(pools).To(HaveLen(2)) + + Expect(pools[0].FirstIp).To(Equal("192.168.10.1")) + Expect(pools[0].LastIp).To(Equal("")) + Expect(pools[0].TwiceNat).To(BeTrue()) + Expect(pools[0].VrfId).To(BeEquivalentTo(1)) + + Expect(pools[1].FirstIp).To(Equal("192.168.10.2")) + Expect(pools[1].LastIp).To(Equal("192.168.10.4")) + Expect(pools[1].TwiceNat).To(BeFalse()) + Expect(pools[1].VrfId).To(BeEquivalentTo(2)) +} + +func TestNat44EiAddressPoolsDump(t *testing.T) { + ctx, natHandler, _, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: false}) + Expect(err).ShouldNot(HaveOccurred()) + + // address pool + ctx.MockVpp.MockReply( + &vpp_nat_ei.Nat44EiAddressDetails{ + IPAddress: ipTo4Address("192.168.10.1"), + VrfID: 1, + }, + &vpp_nat_ei.Nat44EiAddressDetails{ + IPAddress: ipTo4Address("192.168.10.2"), + VrfID: 2, + }, + &vpp_nat_ei.Nat44EiAddressDetails{ + IPAddress: ipTo4Address("192.168.10.3"), + VrfID: 2, + }, + &vpp_nat_ei.Nat44EiAddressDetails{ + IPAddress: ipTo4Address("192.168.10.4"), + VrfID: 2, + }) + ctx.MockVpp.MockReply(&memclnt.ControlPingReply{}) + + pools, err := natHandler.Nat44AddressPoolsDump() + Expect(err).To(Succeed()) + + Expect(pools).To(HaveLen(2)) + + Expect(pools[0].FirstIp).To(Equal("192.168.10.1")) + Expect(pools[0].LastIp).To(Equal("")) + Expect(pools[0].VrfId).To(BeEquivalentTo(1)) + + Expect(pools[1].FirstIp).To(Equal("192.168.10.2")) + Expect(pools[1].LastIp).To(Equal("192.168.10.4")) + Expect(pools[1].TwiceNat).To(BeFalse()) + Expect(pools[1].VrfId).To(BeEquivalentTo(2)) +} + +func TestDNATDump(t *testing.T) { + ctx, natHandler, swIfIndexes, dhcpIndexes := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44EdPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: true}) + Expect(err).ShouldNot(HaveOccurred()) + + // non-LB static mappings + ctx.MockVpp.MockReply( + &vpp_nat_ed.Nat44StaticMappingDetails{ + LocalIPAddress: ipTo4Address("10.10.11.120"), + ExternalIPAddress: ipTo4Address("10.36.20.20"), + Protocol: 6, + LocalPort: 8080, + ExternalPort: 80, + ExternalSwIfIndex: vpp2306.NoInterface, + VrfID: 1, + Tag: "DNAT 1", + Flags: nat_types.NAT_IS_TWICE_NAT, + }, + &vpp_nat_ed.Nat44StaticMappingDetails{ + LocalIPAddress: ipTo4Address("10.10.11.120"), + Protocol: 6, + LocalPort: 8080, + ExternalPort: 80, + ExternalSwIfIndex: 1, + VrfID: 1, + Tag: "DNAT 1", + Flags: nat_types.NAT_IS_TWICE_NAT, + }, + &vpp_nat_ed.Nat44StaticMappingDetails{ + LocalIPAddress: ipTo4Address("10.10.11.140"), + Protocol: 6, + LocalPort: 8081, + ExternalPort: 80, + ExternalSwIfIndex: 2, + VrfID: 1, + Tag: "DNAT 2", + Flags: nat_types.NAT_IS_SELF_TWICE_NAT, + }, + // auto-generated mappings with interface replaced by all assigned IP addresses + &vpp_nat_ed.Nat44StaticMappingDetails{ + LocalIPAddress: ipTo4Address("10.10.11.120"), + ExternalIPAddress: ipTo4Address("10.36.20.30"), + Protocol: 6, + LocalPort: 8080, + ExternalPort: 80, + ExternalSwIfIndex: vpp2306.NoInterface, + VrfID: 1, + Tag: "DNAT 1", + Flags: nat_types.NAT_IS_TWICE_NAT, + }, + &vpp_nat_ed.Nat44StaticMappingDetails{ + LocalIPAddress: ipTo4Address("10.10.11.120"), + ExternalIPAddress: ipTo4Address("10.36.20.31"), + Protocol: 6, + LocalPort: 8080, + ExternalPort: 80, + ExternalSwIfIndex: vpp2306.NoInterface, + VrfID: 1, + Tag: "DNAT 1", + Flags: nat_types.NAT_IS_TWICE_NAT, + }, + &vpp_nat_ed.Nat44StaticMappingDetails{ + LocalIPAddress: ipTo4Address("10.10.11.140"), + ExternalIPAddress: ipTo4Address("10.36.40.10"), + Protocol: 6, + LocalPort: 8081, + ExternalPort: 80, + ExternalSwIfIndex: vpp2306.NoInterface, + VrfID: 1, + Tag: "DNAT 2", + Flags: nat_types.NAT_IS_SELF_TWICE_NAT, + }, + &vpp_nat_ed.Nat44StaticMappingDetails{ + LocalIPAddress: ipTo4Address("10.10.11.140"), + ExternalIPAddress: ipTo4Address("10.36.40.20"), + Protocol: 6, + LocalPort: 8081, + ExternalPort: 80, + ExternalSwIfIndex: vpp2306.NoInterface, + VrfID: 1, + Tag: "DNAT 2", + Flags: nat_types.NAT_IS_SELF_TWICE_NAT, + }, + ) + + ctx.MockVpp.MockReply(&memclnt.ControlPingReply{}) + + // LB static mappings + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44LbStaticMappingDetails{ + ExternalAddr: ipTo4Address("10.36.20.60"), + ExternalPort: 53, + Protocol: 17, + Flags: nat_types.NAT_IS_OUT2IN_ONLY, + Tag: "DNAT 2", + LocalNum: 2, + Locals: []vpp_nat_ed.Nat44LbAddrPort{ + { + Addr: ipTo4Address("10.10.11.161"), + Port: 53, + Probability: 1, + VrfID: 0, + }, + { + Addr: ipTo4Address("10.10.11.162"), + Port: 153, + Probability: 2, + VrfID: 0, + }, + }, + }) + + ctx.MockVpp.MockReply(&memclnt.ControlPingReply{}) + + // identity mappings + ctx.MockVpp.MockReply( + &vpp_nat_ed.Nat44IdentityMappingDetails{ + Flags: nat_types.NAT_IS_ADDR_ONLY, + Protocol: 17, + IPAddress: ipTo4Address("10.10.11.200"), + SwIfIndex: vpp2306.NoInterface, + VrfID: 1, + Tag: "DNAT 3", + }, + &vpp_nat_ed.Nat44IdentityMappingDetails{ + Flags: nat_types.NAT_IS_ADDR_ONLY, + Protocol: 17, + SwIfIndex: 2, + VrfID: 1, + Tag: "DNAT 3", + }, + // auto-generated mappings with interface replaced by all assigned IP addresses + &vpp_nat_ed.Nat44IdentityMappingDetails{ + Flags: nat_types.NAT_IS_ADDR_ONLY, + Protocol: 17, + IPAddress: ipTo4Address("10.36.40.10"), + SwIfIndex: vpp2306.NoInterface, + VrfID: 1, + Tag: "DNAT 3", + }, + &vpp_nat_ed.Nat44IdentityMappingDetails{ + Flags: nat_types.NAT_IS_ADDR_ONLY, + Protocol: 17, + IPAddress: ipTo4Address("10.36.40.20"), + SwIfIndex: vpp2306.NoInterface, + VrfID: 1, + Tag: "DNAT 3", + }, + ) + + ctx.MockVpp.MockReply(&memclnt.ControlPingReply{}) + + // interfaces and their IP addresses + swIfIndexes.Put("if0", &ifaceidx.IfaceMetadata{SwIfIndex: 1, IPAddresses: []string{"10.36.20.30", "10.36.20.31"}}) + swIfIndexes.Put("if1", &ifaceidx.IfaceMetadata{SwIfIndex: 2, IPAddresses: []string{"10.36.40.10"}}) + dhcpIndexes.Put("if1", &ifs.DHCPLease{InterfaceName: "if0", HostIpAddress: "10.36.40.20"}) + + dnats, err := natHandler.DNat44Dump() + Expect(err).To(Succeed()) + + Expect(dnats).To(HaveLen(3)) + + dnat := dnats[0] + Expect(dnat.Label).To(Equal("DNAT 1")) + Expect(dnat.IdMappings).To(HaveLen(0)) + Expect(dnat.StMappings).To(HaveLen(2)) + // 1st mapping + Expect(dnat.StMappings[0].TwiceNat).To(Equal(nat.DNat44_StaticMapping_ENABLED)) + Expect(dnat.StMappings[0].Protocol).To(Equal(nat.DNat44_TCP)) + Expect(dnat.StMappings[0].ExternalInterface).To(BeEmpty()) + Expect(dnat.StMappings[0].ExternalIp).To(Equal("10.36.20.20")) + Expect(dnat.StMappings[0].ExternalPort).To(BeEquivalentTo(80)) + Expect(dnat.StMappings[0].LocalIps).To(HaveLen(1)) + Expect(dnat.StMappings[0].LocalIps[0].VrfId).To(BeEquivalentTo(1)) + Expect(dnat.StMappings[0].LocalIps[0].LocalIp).To(Equal("10.10.11.120")) + Expect(dnat.StMappings[0].LocalIps[0].LocalPort).To(BeEquivalentTo(8080)) + Expect(dnat.StMappings[0].LocalIps[0].Probability).To(BeEquivalentTo(0)) + // 2nd mapping + Expect(dnat.StMappings[1].TwiceNat).To(Equal(nat.DNat44_StaticMapping_ENABLED)) + Expect(dnat.StMappings[1].Protocol).To(Equal(nat.DNat44_TCP)) + Expect(dnat.StMappings[1].ExternalInterface).To(BeEquivalentTo("if0")) + Expect(dnat.StMappings[1].ExternalIp).To(BeEquivalentTo("0.0.0.0")) + Expect(dnat.StMappings[1].ExternalPort).To(BeEquivalentTo(80)) + Expect(dnat.StMappings[1].LocalIps).To(HaveLen(1)) + Expect(dnat.StMappings[1].LocalIps[0].VrfId).To(BeEquivalentTo(1)) + Expect(dnat.StMappings[1].LocalIps[0].LocalIp).To(Equal("10.10.11.120")) + Expect(dnat.StMappings[1].LocalIps[0].LocalPort).To(BeEquivalentTo(8080)) + Expect(dnat.StMappings[1].LocalIps[0].Probability).To(BeEquivalentTo(0)) + + dnat = dnats[1] + // -> non-LB mapping + Expect(dnat.Label).To(Equal("DNAT 2")) + Expect(dnat.IdMappings).To(HaveLen(0)) + Expect(dnat.StMappings).To(HaveLen(2)) + Expect(dnat.StMappings[0].TwiceNat).To(Equal(nat.DNat44_StaticMapping_SELF)) + Expect(dnat.StMappings[0].Protocol).To(Equal(nat.DNat44_TCP)) + Expect(dnat.StMappings[0].ExternalInterface).To(Equal("if1")) + Expect(dnat.StMappings[0].ExternalIp).To(BeEquivalentTo("0.0.0.0")) + Expect(dnat.StMappings[0].ExternalPort).To(BeEquivalentTo(80)) + Expect(dnat.StMappings[0].LocalIps).To(HaveLen(1)) + Expect(dnat.StMappings[0].LocalIps[0].VrfId).To(BeEquivalentTo(1)) + Expect(dnat.StMappings[0].LocalIps[0].LocalIp).To(Equal("10.10.11.140")) + Expect(dnat.StMappings[0].LocalIps[0].LocalPort).To(BeEquivalentTo(8081)) + Expect(dnat.StMappings[0].LocalIps[0].Probability).To(BeEquivalentTo(0)) + // -> LB mapping + Expect(dnat.StMappings[1].TwiceNat).To(Equal(nat.DNat44_StaticMapping_DISABLED)) + Expect(dnat.StMappings[1].Protocol).To(Equal(nat.DNat44_UDP)) + Expect(dnat.StMappings[1].ExternalInterface).To(BeEmpty()) + Expect(dnat.StMappings[1].ExternalIp).To(Equal("10.36.20.60")) + Expect(dnat.StMappings[1].ExternalPort).To(BeEquivalentTo(53)) + Expect(dnat.StMappings[1].LocalIps).To(HaveLen(2)) + Expect(dnat.StMappings[1].LocalIps[0].VrfId).To(BeEquivalentTo(0)) + Expect(dnat.StMappings[1].LocalIps[0].LocalIp).To(Equal("10.10.11.161")) + Expect(dnat.StMappings[1].LocalIps[0].LocalPort).To(BeEquivalentTo(53)) + Expect(dnat.StMappings[1].LocalIps[0].Probability).To(BeEquivalentTo(1)) + Expect(dnat.StMappings[1].LocalIps[1].VrfId).To(BeEquivalentTo(0)) + Expect(dnat.StMappings[1].LocalIps[1].LocalIp).To(Equal("10.10.11.162")) + Expect(dnat.StMappings[1].LocalIps[1].LocalPort).To(BeEquivalentTo(153)) + Expect(dnat.StMappings[1].LocalIps[1].Probability).To(BeEquivalentTo(2)) + + dnat = dnats[2] + Expect(dnat.Label).To(Equal("DNAT 3")) + Expect(dnat.StMappings).To(HaveLen(0)) + Expect(dnat.IdMappings).To(HaveLen(2)) + // 1st mapping + Expect(dnat.IdMappings[0].VrfId).To(BeEquivalentTo(1)) + Expect(dnat.IdMappings[0].Protocol).To(Equal(nat.DNat44_UDP)) + Expect(dnat.IdMappings[0].Port).To(BeEquivalentTo(0)) + Expect(dnat.IdMappings[0].IpAddress).To(Equal("10.10.11.200")) + Expect(dnat.IdMappings[0].Interface).To(BeEmpty()) + // 2nd mapping + Expect(dnat.IdMappings[1].VrfId).To(BeEquivalentTo(1)) + Expect(dnat.IdMappings[1].Protocol).To(Equal(nat.DNat44_UDP)) + Expect(dnat.IdMappings[1].Port).To(BeEquivalentTo(0)) + Expect(dnat.IdMappings[1].IpAddress).To(BeEquivalentTo("0.0.0.0")) + Expect(dnat.IdMappings[1].Interface).To(BeEquivalentTo("if1")) +} + +func TestNat44VrfTableDump(t *testing.T) { + ctx, natHandler, _, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44EdPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: true}) + Expect(err).ShouldNot(HaveOccurred()) + + // vrf tables + ctx.MockVpp.MockReply( + &vpp_nat_ed.Nat44EdVrfTablesDetails{ + TableVrfID: 5, + VrfIds: []uint32{bits.ReverseBytes32(1), bits.ReverseBytes32(2)}, + NVrfIds: 1, + }, + &vpp_nat_ed.Nat44EdVrfTablesDetails{ + TableVrfID: 10, + VrfIds: []uint32{0}, + NVrfIds: 1, + }) + ctx.MockVpp.MockReply(&memclnt.ControlPingReply{}) + + tables, err := natHandler.Nat44VrfTablesDump() + Expect(err).To(Succeed()) + Expect(tables).To(HaveLen(2)) + + Expect(tables[0].DestVrfIds[0]).To(BeEquivalentTo(1)) + Expect(tables[0].DestVrfIds[1]).To(BeEquivalentTo(2)) + Expect(tables[0].SrcVrfId).To(BeEquivalentTo(5)) + + Expect(tables[1].DestVrfIds[0]).To(BeEquivalentTo(0)) + Expect(tables[1].SrcVrfId).To(BeEquivalentTo(10)) + +} + +func natTestSetup(t *testing.T) (*vppmock.TestCtx, vppcalls.NatVppAPI, ifaceidx.IfaceMetadataIndexRW, idxmap.NamedMappingRW) { + ctx := vppmock.SetupTestCtx(t) + log := logrus.NewLogger("test-log") + swIfIndexes := ifaceidx.NewIfaceIndex(logrus.DefaultLogger(), "test-sw_if_indexes") + dhcpIndexes := idxmap_mem.NewNamedMapping(logrus.DefaultLogger(), "test-dhcp_indexes", nil) + natHandler := vpp2306.NewNatVppHandler(ctx.MockVPPClient, swIfIndexes, dhcpIndexes, log) + return ctx, natHandler, swIfIndexes, dhcpIndexes +} + +func ipTo4Address(ipStr string) (addr ip_types.IP4Address) { + netIP := net.ParseIP(ipStr) + if ip4 := netIP.To4(); ip4 != nil { + copy(addr[:], ip4) + } + return +} diff --git a/plugins/vpp/natplugin/vppcalls/vpp2306/nat_vppcalls.go b/plugins/vpp/natplugin/vppcalls/vpp2306/nat_vppcalls.go new file mode 100644 index 0000000000..8924bb6573 --- /dev/null +++ b/plugins/vpp/natplugin/vppcalls/vpp2306/nat_vppcalls.go @@ -0,0 +1,837 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "fmt" + "net" + + "go.fd.io/govpp/api" + + "github.com/pkg/errors" + + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface_types" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip_types" + vpp_nat_ed "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/nat44_ed" + vpp_nat_ei "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/nat44_ei" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/nat_types" + "go.ligato.io/vpp-agent/v3/plugins/vpp/natplugin/vppcalls" + nat "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/nat" +) + +// Num protocol representation +const ( + ICMP uint8 = 1 + TCP uint8 = 6 + UDP uint8 = 17 +) + +const ( + // NoInterface is sw-if-idx which means 'no interface' + NoInterface = interface_types.InterfaceIndex(^uint32(0)) + // Maximal length of tag + maxTagLen = 64 +) + +// holds a list of NAT44 ED flags set +type nat44EdFlags struct { + isTwiceNat bool + isSelfTwiceNat bool + isOut2In bool + isAddrOnly bool + isOutside bool + isInside bool + isStatic bool + isExtHostValid bool +} + +// holds a list of NAT44 EI flags set +type nat44EiFlags struct { + eiStaticMappingOnly bool + eiConnectionTracking bool + eiOut2InDpo bool + eiAddrOnlyMapping bool + eiIfInside bool + eiIfOutside bool + eiStaticMapping bool +} + +func (h *NatVppHandler) enableNAT44EdPlugin(opts vppcalls.Nat44InitOpts) error { + var flags vpp_nat_ed.Nat44ConfigFlags + if opts.ConnectionTracking { + flags |= vpp_nat_ed.NAT44_IS_CONNECTION_TRACKING + } + if opts.StaticMappingOnly { + flags |= vpp_nat_ed.NAT44_IS_STATIC_MAPPING_ONLY + } + + req := &vpp_nat_ed.Nat44EdPluginEnableDisable{ + Enable: true, + Flags: flags, + } + reply := &vpp_nat_ed.Nat44EdPluginEnableDisableReply{} + + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + return nil +} + +func (h *NatVppHandler) enableNAT44EiPlugin(opts vppcalls.Nat44InitOpts) error { + var flags vpp_nat_ei.Nat44EiConfigFlags + if opts.ConnectionTracking { + flags |= vpp_nat_ei.NAT44_EI_CONNECTION_TRACKING + } + if opts.StaticMappingOnly { + flags |= vpp_nat_ei.NAT44_EI_STATIC_MAPPING_ONLY + } + if opts.OutToInDPO { + flags |= vpp_nat_ei.NAT44_EI_OUT2IN_DPO + } + + req := &vpp_nat_ei.Nat44EiPluginEnableDisable{ + Enable: true, + Flags: flags, + } + reply := &vpp_nat_ei.Nat44EiPluginEnableDisableReply{} + + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + return nil +} + +// EnableNAT44plugin and apply the given set of options. +func (h *NatVppHandler) EnableNAT44Plugin(opts vppcalls.Nat44InitOpts) error { + // VPP since version 21.06 supports running both NAT EI and NAT ED simultaneously, + // but not vpp-agent yet. + // TODO: separate vpp-agent natplugin into 2 separate plugins + // (for ED and EI NAT) or create a separate handlers inside one plugin, + // this have number of advantages and will probably also become necessary as VPP + // NAT plugins will differ more and more over time. + if opts.EndpointDependent { + h.ed = true + return h.enableNAT44EdPlugin(opts) + } else { + h.ed = false + return h.enableNAT44EiPlugin(opts) + } +} + +func (h *NatVppHandler) disableNAT44EdPlugin() error { + req := &vpp_nat_ed.Nat44EdPluginEnableDisable{ + Enable: false, + } + reply := &vpp_nat_ed.Nat44EdPluginEnableDisableReply{} + err := h.callsChannel.SendRequest(req).ReceiveReply(reply) + if err == api.VPPApiError(1) { + return nil + } else if err != nil { + return err + } + return nil +} + +func (h *NatVppHandler) disableNAT44EiPlugin() error { + req := &vpp_nat_ei.Nat44EiPluginEnableDisable{ + Enable: false, + } + reply := &vpp_nat_ei.Nat44EiPluginEnableDisableReply{} + err := h.callsChannel.SendRequest(req).ReceiveReply(reply) + if err == api.VPPApiError(1) { + return nil + } else if err != nil { + return err + } + return nil +} + +// DisableNAT44Plugin disables NAT44 plugin. +func (h *NatVppHandler) DisableNAT44Plugin() error { + if h.ed { + return h.disableNAT44EdPlugin() + } else { + return h.disableNAT44EiPlugin() + } +} + +func (h *NatVppHandler) setNat44EdForwarding(enableFwd bool) error { + req := &vpp_nat_ed.Nat44ForwardingEnableDisable{ + Enable: enableFwd, + } + reply := &vpp_nat_ed.Nat44ForwardingEnableDisableReply{} + + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + + return nil +} + +func (h *NatVppHandler) setNat44EiForwarding(enableFwd bool) error { + req := &vpp_nat_ei.Nat44EiForwardingEnableDisable{ + Enable: enableFwd, + } + reply := &vpp_nat_ei.Nat44EiForwardingEnableDisableReply{} + + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + + return nil +} + +// SetNat44Forwarding configures NAT44 forwarding. +func (h *NatVppHandler) SetNat44Forwarding(enableFwd bool) error { + if h.ed { + return h.setNat44EdForwarding(enableFwd) + } else { + return h.setNat44EiForwarding(enableFwd) + } +} + +// EnableNat44Interface enables NAT44 feature for provided interface. +func (h *NatVppHandler) EnableNat44Interface(iface string, isInside, isOutput bool) error { + if isOutput { + return h.handleNat44InterfaceOutputFeature(iface, isInside, true) + } + return h.handleNat44Interface(iface, isInside, true) +} + +// DisableNat44Interface disables NAT44 feature for provided interface. +func (h *NatVppHandler) DisableNat44Interface(iface string, isInside, isOutput bool) error { + if isOutput { + return h.handleNat44InterfaceOutputFeature(iface, isInside, false) + } + return h.handleNat44Interface(iface, isInside, false) +} + +// AddNat44AddressPool adds new IPV4 address pool into the NAT pools. +func (h *NatVppHandler) AddNat44AddressPool(vrf uint32, firstIP, lastIP string, twiceNat bool) error { + return h.handleNat44AddressPool(vrf, firstIP, lastIP, twiceNat, true) +} + +// DelNat44AddressPool removes existing IPv4 address pool from the NAT pools. +func (h *NatVppHandler) DelNat44AddressPool(vrf uint32, firstIP, lastIP string, twiceNat bool) error { + return h.handleNat44AddressPool(vrf, firstIP, lastIP, twiceNat, false) +} + +// SetVirtualReassemblyIPv4 configures NAT virtual reassembly for IPv4 packets. +func (h *NatVppHandler) SetVirtualReassemblyIPv4(vrCfg *nat.VirtualReassembly) error { + return h.handleNatVirtualReassembly(vrCfg, false) +} + +// SetVirtualReassemblyIPv6 configures NAT virtual reassembly for IPv6 packets. +func (h *NatVppHandler) SetVirtualReassemblyIPv6(vrCfg *nat.VirtualReassembly) error { + return h.handleNatVirtualReassembly(vrCfg, true) +} + +// AddNat44IdentityMapping adds new NAT44 identity mapping +func (h *NatVppHandler) AddNat44IdentityMapping(mapping *nat.DNat44_IdentityMapping, dnatLabel string) error { + return h.handleNat44IdentityMapping(mapping, dnatLabel, true) +} + +// DelNat44IdentityMapping removes existing NAT44 identity mapping +func (h *NatVppHandler) DelNat44IdentityMapping(mapping *nat.DNat44_IdentityMapping, dnatLabel string) error { + return h.handleNat44IdentityMapping(mapping, dnatLabel, false) +} + +// AddNat44StaticMapping creates new NAT44 static mapping entry. +func (h *NatVppHandler) AddNat44StaticMapping(mapping *nat.DNat44_StaticMapping, dnatLabel string) error { + if len(mapping.LocalIps) == 0 { + return errors.Errorf("cannot configure static mapping for DNAT %s: no local address provided", dnatLabel) + } + if len(mapping.LocalIps) == 1 { + return h.handleNat44StaticMapping(mapping, dnatLabel, true) + } + return h.handleNat44StaticMappingLb(mapping, dnatLabel, true) +} + +// DelNat44StaticMapping removes existing NAT44 static mapping entry. +func (h *NatVppHandler) DelNat44StaticMapping(mapping *nat.DNat44_StaticMapping, dnatLabel string) error { + if len(mapping.LocalIps) == 0 { + return errors.Errorf("cannot un-configure static mapping from DNAT %s: no local address provided", dnatLabel) + } + if len(mapping.LocalIps) == 1 { + return h.handleNat44StaticMapping(mapping, dnatLabel, false) + } + return h.handleNat44StaticMappingLb(mapping, dnatLabel, false) +} + +func (h *NatVppHandler) handleNatEd44Interface(iface string, isInside, isAdd bool) error { + // get interface metadata + ifaceMeta, found := h.ifIndexes.LookupByName(iface) + if !found { + return errors.New("failed to get interface metadata") + } + + req := &vpp_nat_ed.Nat44InterfaceAddDelFeature{ + SwIfIndex: interface_types.InterfaceIndex(ifaceMeta.SwIfIndex), + Flags: setNat44EdFlags(&nat44EdFlags{isInside: isInside}), + IsAdd: isAdd, + } + reply := &vpp_nat_ed.Nat44InterfaceAddDelFeatureReply{} + + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + + return nil +} + +func (h *NatVppHandler) handleNat44EiInterface(iface string, isInside, isAdd bool) error { + // get interface metadata + ifaceMeta, found := h.ifIndexes.LookupByName(iface) + if !found { + return errors.New("failed to get interface metadata") + } + + req := &vpp_nat_ei.Nat44EiInterfaceAddDelFeature{ + SwIfIndex: interface_types.InterfaceIndex(ifaceMeta.SwIfIndex), + Flags: setNat44EiFlags(&nat44EiFlags{eiIfInside: isInside}), + IsAdd: isAdd, + } + reply := &vpp_nat_ei.Nat44EiInterfaceAddDelFeatureReply{} + + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + + return nil +} + +// Calls VPP binary API to set/unset interface NAT44 feature. +func (h *NatVppHandler) handleNat44Interface(iface string, isInside, isAdd bool) error { + if h.ed { + return h.handleNatEd44Interface(iface, isInside, isAdd) + } else { + return h.handleNat44EiInterface(iface, isInside, isAdd) + } +} + +func (h *NatVppHandler) handleNat44EdInterfaceOutputFeature(iface string, isInside, isAdd bool) error { + // get interface metadata + ifaceMeta, found := h.ifIndexes.LookupByName(iface) + if !found { + return errors.New("failed to get interface metadata") + } + + req := &vpp_nat_ed.Nat44EdAddDelOutputInterface{ + SwIfIndex: interface_types.InterfaceIndex(ifaceMeta.SwIfIndex), + IsAdd: isAdd, + } + reply := &vpp_nat_ed.Nat44EdAddDelOutputInterfaceReply{} + + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + + return nil +} + +func (h *NatVppHandler) handleNat44EiInterfaceOutputFeature(iface string, isInside, isAdd bool) error { + // get interface metadata + ifaceMeta, found := h.ifIndexes.LookupByName(iface) + if !found { + return errors.New("failed to get interface metadata") + } + + req := &vpp_nat_ei.Nat44EiAddDelOutputInterface{ + SwIfIndex: interface_types.InterfaceIndex(ifaceMeta.SwIfIndex), + IsAdd: isAdd, + } + reply := &vpp_nat_ei.Nat44EiAddDelOutputInterfaceReply{} + + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + + return nil +} + +// Calls VPP binary API to set/unset interface NAT44 output feature +func (h *NatVppHandler) handleNat44InterfaceOutputFeature(iface string, isInside, isAdd bool) error { + if h.ed { + return h.handleNat44EdInterfaceOutputFeature(iface, isInside, isAdd) + } else { + return h.handleNat44EiInterfaceOutputFeature(iface, isInside, isAdd) + } +} + +func (h *NatVppHandler) handleNat44EdAddressPool(vrf uint32, firstIP, lastIP string, twiceNat, isAdd bool) error { + firstAddr, err := ipTo4Address(firstIP) + if err != nil { + return errors.Errorf("unable to parse address %s from the NAT pool: %v", firstIP, err) + } + lastAddr := firstAddr + if lastIP != "" { + lastAddr, err = ipTo4Address(lastIP) + if err != nil { + return errors.Errorf("unable to parse address %s from the NAT pool: %v", lastIP, err) + } + } + + req := &vpp_nat_ed.Nat44AddDelAddressRange{ + FirstIPAddress: firstAddr, + LastIPAddress: lastAddr, + VrfID: vrf, + Flags: setNat44EdFlags(&nat44EdFlags{isTwiceNat: twiceNat}), + IsAdd: isAdd, + } + reply := &vpp_nat_ed.Nat44AddDelAddressRangeReply{} + + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + + return nil +} + +func (h *NatVppHandler) handleNat44EiAddressPool(vrf uint32, firstIP, lastIP string, twiceNat, isAdd bool) error { + firstAddr, err := ipTo4Address(firstIP) + if err != nil { + return errors.Errorf("unable to parse address %s from the NAT pool: %v", firstIP, err) + } + lastAddr := firstAddr + if lastIP != "" { + lastAddr, err = ipTo4Address(lastIP) + if err != nil { + return errors.Errorf("unable to parse address %s from the NAT pool: %v", lastIP, err) + } + } + + req := &vpp_nat_ei.Nat44EiAddDelAddressRange{ + FirstIPAddress: firstAddr, + LastIPAddress: lastAddr, + VrfID: vrf, + IsAdd: isAdd, + } + reply := &vpp_nat_ei.Nat44EiAddDelAddressRangeReply{} + + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + + return nil +} + +// Calls VPP binary API to add/remove addresses to/from the NAT44 pool. +func (h *NatVppHandler) handleNat44AddressPool(vrf uint32, firstIP, lastIP string, twiceNat, isAdd bool) error { + if h.ed { + return h.handleNat44EdAddressPool(vrf, firstIP, lastIP, twiceNat, isAdd) + } else { + return h.handleNat44EiAddressPool(vrf, firstIP, lastIP, twiceNat, isAdd) + } +} + +// Calls VPP binary API to setup NAT virtual reassembly +func (h *NatVppHandler) handleNatVirtualReassembly(vrCfg *nat.VirtualReassembly, isIpv6 bool) error { + // Virtual Reassembly has been removed from NAT API in VPP (moved to IP API) + // TODO: define IPReassembly model in L3 plugin + return nil + /*req := &vpp_nat.NatSetReass{ + Timeout: vrCfg.Timeout, + MaxReass: uint16(vrCfg.MaxReassemblies), + MaxFrag: uint8(vrCfg.MaxFragments), + DropFrag: boolToUint(vrCfg.DropFragments), + IsIP6: isIpv6, + } + reply := &vpp_nat.NatSetReassReply{} + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + }*/ +} + +// Calls VPP binary API to add/remove NAT44 static mapping +func (h *NatVppHandler) handleNat44StaticMapping(mapping *nat.DNat44_StaticMapping, dnatLabel string, isAdd bool) error { + var ifIdx interface_types.InterfaceIndex // NOTE: This is a workaround, because NoInterface crashes VPP 22.10 + var exIPAddr ip_types.IP4Address + ifIdx = interface_types.InterfaceIndex(^uint32(0)) + + // check tag length limit + if err := checkTagLength(dnatLabel); err != nil { + return err + } + + // parse local endpoint + lcIPAddr, err := ipTo4Address(mapping.LocalIps[0].LocalIp) + if err != nil { + return errors.Errorf("cannot configure DNAT static mapping %s: unable to parse local IP %s: %v", + dnatLabel, mapping.LocalIps[0].LocalIp, err) + } + lcPort := uint16(mapping.LocalIps[0].LocalPort) + lcVrf := mapping.LocalIps[0].VrfId + + // Check external interface (prioritized over external IP) + if mapping.ExternalInterface != "" { + // Check external interface + ifMeta, found := h.ifIndexes.LookupByName(mapping.ExternalInterface) + if !found { + return errors.Errorf("cannot configure static mapping for DNAT %s: required external interface %s is missing", + dnatLabel, mapping.ExternalInterface) + } + ifIdx = interface_types.InterfaceIndex(ifMeta.SwIfIndex) + } else { + // Parse external IP address + exIPAddr, err = ipTo4Address(mapping.ExternalIp) + if err != nil { + return errors.Errorf("cannot configure static mapping for DNAT %s: unable to parse external IP %s: %v", + dnatLabel, mapping.ExternalIp, err) + } + } + + // Resolve mapping (address only or address and port) + var addrOnly bool + if lcPort == 0 || mapping.ExternalPort == 0 { + addrOnly = true + } + + if h.ed { + req := &vpp_nat_ed.Nat44AddDelStaticMappingV2{ + Tag: dnatLabel, + LocalIPAddress: lcIPAddr, + ExternalIPAddress: exIPAddr, + Protocol: h.protocolNBValueToNumber(mapping.Protocol), + ExternalSwIfIndex: ifIdx, + VrfID: lcVrf, + Flags: setNat44EdFlags(&nat44EdFlags{ + isTwiceNat: mapping.TwiceNat == nat.DNat44_StaticMapping_ENABLED, + isSelfTwiceNat: mapping.TwiceNat == nat.DNat44_StaticMapping_SELF, + isOut2In: true, + isAddrOnly: addrOnly, + }), + IsAdd: isAdd, + } + if !addrOnly { + req.LocalPort = lcPort + req.ExternalPort = uint16(mapping.ExternalPort) + } + + // Applying(if needed) the override of IP address picking from twice-NAT address pool + if mapping.TwiceNatPoolIp != "" { + req.MatchPool = true + req.PoolIPAddress, err = ipTo4Address(mapping.TwiceNatPoolIp) + if err != nil { + return errors.Errorf("cannot configure static mapping for DNAT %s: unable to parse "+ + "twice-NAT pool IP %s: %v", dnatLabel, mapping.TwiceNatPoolIp, err) + } + } + + reply := &vpp_nat_ed.Nat44AddDelStaticMappingV2Reply{} + + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + } else { + req := &vpp_nat_ei.Nat44EiAddDelStaticMapping{ + Tag: dnatLabel, + LocalIPAddress: lcIPAddr, + ExternalIPAddress: exIPAddr, + Protocol: h.protocolNBValueToNumber(mapping.Protocol), + ExternalSwIfIndex: ifIdx, + VrfID: lcVrf, + Flags: setNat44EiFlags(&nat44EiFlags{ + eiAddrOnlyMapping: addrOnly, + }), + IsAdd: isAdd, + } + + if !addrOnly { + req.LocalPort = lcPort + req.ExternalPort = uint16(mapping.ExternalPort) + } + + reply := &vpp_nat_ei.Nat44EiAddDelStaticMappingReply{} + + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + } + + return nil +} + +func (h *NatVppHandler) handleNat44EdStaticMappingLb(mapping *nat.DNat44_StaticMapping, dnatLabel string, isAdd bool) error { + // check tag length limit + if err := checkTagLength(dnatLabel); err != nil { + return err + } + + // parse external IP address + exIPAddrByte, err := ipTo4Address(mapping.ExternalIp) + if err != nil { + return errors.Errorf("cannot configure LB static mapping for DNAT %s: unable to parse external IP %s: %v", + dnatLabel, mapping.ExternalIp, err) + } + + // In this case, external port is required + if mapping.ExternalPort == 0 { + return errors.Errorf("cannot configure LB static mapping for DNAT %s: external port is not set", dnatLabel) + } + + // Transform local IP/Ports + var locals []vpp_nat_ed.Nat44LbAddrPort + for _, local := range mapping.LocalIps { + if local.LocalPort == 0 { + return errors.Errorf("cannot set local IP/Port for DNAT mapping %s: port is missing", + dnatLabel) + } + + localIP, err := ipTo4Address(local.LocalIp) + if err != nil { + return errors.Errorf("cannot set local IP/Port for DNAT mapping %s: unable to parse local IP %v: %v", + dnatLabel, local.LocalIp, err) + } + + locals = append(locals, vpp_nat_ed.Nat44LbAddrPort{ + Addr: localIP, + Port: uint16(local.LocalPort), + Probability: uint8(local.Probability), + VrfID: local.VrfId, + }) + } + + req := &vpp_nat_ed.Nat44AddDelLbStaticMapping{ + Tag: dnatLabel, + Locals: locals, + // LocalNum: uint32(len(locals)), // should not be needed (will be set by struc) + ExternalAddr: exIPAddrByte, + ExternalPort: uint16(mapping.ExternalPort), + Protocol: h.protocolNBValueToNumber(mapping.Protocol), + Flags: setNat44EdFlags(&nat44EdFlags{ + isTwiceNat: mapping.TwiceNat == nat.DNat44_StaticMapping_ENABLED, + isSelfTwiceNat: mapping.TwiceNat == nat.DNat44_StaticMapping_SELF, + isOut2In: true, + }), + IsAdd: isAdd, + Affinity: mapping.SessionAffinity, + } + + reply := &vpp_nat_ed.Nat44AddDelLbStaticMappingReply{} + + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + + return nil +} + +// Calls VPP binary API to add/remove NAT44 static mapping with load balancing. +func (h *NatVppHandler) handleNat44StaticMappingLb(mapping *nat.DNat44_StaticMapping, dnatLabel string, isAdd bool) error { + if h.ed { + return h.handleNat44EdStaticMappingLb(mapping, dnatLabel, isAdd) + } else { + // no static mapping with load balancing implemented for EI nat yet + return nil + } +} + +// Calls VPP binary API to add/remove NAT44 identity mapping. +func (h *NatVppHandler) handleNat44IdentityMapping(mapping *nat.DNat44_IdentityMapping, dnatLabel string, isAdd bool) (err error) { + var ifIdx = NoInterface + var ipAddr ip_types.IP4Address + + // check tag length limit + if err := checkTagLength(dnatLabel); err != nil { + return err + } + + // get interface index + if mapping.Interface != "" { + ifMeta, found := h.ifIndexes.LookupByName(mapping.Interface) + if !found { + return errors.Errorf("failed to configure identity mapping for DNAT %s: addressed interface %s does not exist", + dnatLabel, mapping.Interface) + } + ifIdx = interface_types.InterfaceIndex(ifMeta.SwIfIndex) + } + + if ifIdx == NoInterface { + // Case with IP (optionally port). Verify and parse input IP/port + ipAddr, err = ipTo4Address(mapping.IpAddress) + if err != nil { + return errors.Errorf("failed to configure identity mapping for DNAT %s: unable to parse IP address %s: %v", + dnatLabel, mapping.IpAddress, err) + } + } + + var addrOnly bool + if mapping.Port == 0 { + addrOnly = true + } + + if h.ed { + req := &vpp_nat_ed.Nat44AddDelIdentityMapping{ + Tag: dnatLabel, + Flags: setNat44EdFlags(&nat44EdFlags{isAddrOnly: addrOnly}), + IPAddress: ipAddr, + Port: uint16(mapping.Port), + Protocol: h.protocolNBValueToNumber(mapping.Protocol), + SwIfIndex: ifIdx, + VrfID: mapping.VrfId, + IsAdd: isAdd, + } + + reply := &vpp_nat_ed.Nat44AddDelIdentityMappingReply{} + + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + } else { + req := &vpp_nat_ei.Nat44EiAddDelIdentityMapping{ + Tag: dnatLabel, + Flags: setNat44EiFlags(&nat44EiFlags{eiAddrOnlyMapping: addrOnly}), + IPAddress: ipAddr, + Port: uint16(mapping.Port), + Protocol: h.protocolNBValueToNumber(mapping.Protocol), + SwIfIndex: ifIdx, + VrfID: mapping.VrfId, + IsAdd: isAdd, + } + + reply := &vpp_nat_ei.Nat44EiAddDelIdentityMappingReply{} + + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + } + + return nil +} + +func (h *NatVppHandler) handleNat44AddDelVrfTable(vrf uint32, isAdd bool) error { + + req := &vpp_nat_ed.Nat44EdAddDelVrfTable{ + TableVrfID: vrf, + IsAdd: isAdd, + } + + reply := &vpp_nat_ed.Nat44EdAddDelVrfTableReply{} + + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + + return nil +} + +func (h *NatVppHandler) AddNat44VrfTable(vrf uint32) error { + return h.handleNat44AddDelVrfTable(vrf, true) +} +func (h *NatVppHandler) DelNat44VrfTable(vrf uint32) error { + return h.handleNat44AddDelVrfTable(vrf, false) +} + +func (h *NatVppHandler) handleNat44AddDelVrfRoute(tableVrfId uint32, vrf uint32, isAdd bool) error { + req := &vpp_nat_ed.Nat44EdAddDelVrfRoute{ + TableVrfID: tableVrfId, + VrfID: vrf, + IsAdd: isAdd, + } + + reply := &vpp_nat_ed.Nat44EdAddDelVrfRouteReply{} + + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + return nil +} + +func (h *NatVppHandler) AddNat44VrfRoute(tableVrfId uint32, vrf uint32) error { + return h.handleNat44AddDelVrfRoute(tableVrfId, vrf, true) +} + +func (h *NatVppHandler) DelNat44VrfRoute(tableVrfId uint32, vrf uint32) error { + return h.handleNat44AddDelVrfRoute(tableVrfId, vrf, false) +} + +func setNat44EdFlags(flags *nat44EdFlags) nat_types.NatConfigFlags { + var flagsCfg nat_types.NatConfigFlags + if flags.isTwiceNat { + flagsCfg |= nat_types.NAT_IS_TWICE_NAT + } + if flags.isSelfTwiceNat { + flagsCfg |= nat_types.NAT_IS_SELF_TWICE_NAT + } + if flags.isOut2In { + flagsCfg |= nat_types.NAT_IS_OUT2IN_ONLY + } + if flags.isAddrOnly { + flagsCfg |= nat_types.NAT_IS_ADDR_ONLY + } + if flags.isOutside { + flagsCfg |= nat_types.NAT_IS_OUTSIDE + } + if flags.isInside { + flagsCfg |= nat_types.NAT_IS_INSIDE + } + if flags.isStatic { + flagsCfg |= nat_types.NAT_IS_STATIC + } + if flags.isExtHostValid { + flagsCfg |= nat_types.NAT_IS_EXT_HOST_VALID + } + return flagsCfg +} + +func setNat44EiFlags(flags *nat44EiFlags) vpp_nat_ei.Nat44EiConfigFlags { + var flagsCfg vpp_nat_ei.Nat44EiConfigFlags + if flags.eiStaticMappingOnly { + flagsCfg |= vpp_nat_ei.NAT44_EI_STATIC_MAPPING_ONLY + } + if flags.eiConnectionTracking { + flagsCfg |= vpp_nat_ei.NAT44_EI_CONNECTION_TRACKING + } + if flags.eiOut2InDpo { + flagsCfg |= vpp_nat_ei.NAT44_EI_OUT2IN_DPO + } + if flags.eiAddrOnlyMapping { + flagsCfg |= vpp_nat_ei.NAT44_EI_ADDR_ONLY_MAPPING + } + if flags.eiIfInside { + flagsCfg |= vpp_nat_ei.NAT44_EI_IF_INSIDE + } + if flags.eiIfOutside { + flagsCfg |= vpp_nat_ei.NAT44_EI_IF_OUTSIDE + } + if flags.eiStaticMapping { + flagsCfg |= vpp_nat_ei.NAT44_EI_STATIC_MAPPING + } + return flagsCfg +} + +func ipTo4Address(ipStr string) (addr ip_types.IP4Address, err error) { + netIP := net.ParseIP(ipStr) + if netIP == nil { + return ip_types.IP4Address{}, fmt.Errorf("invalid IP: %q", ipStr) + } + if ip4 := netIP.To4(); ip4 != nil { + var ip4Addr ip_types.IP4Address + copy(ip4Addr[:], netIP.To4()) + addr = ip4Addr + } else { + return ip_types.IP4Address{}, fmt.Errorf("required IPv4, provided: %q", ipStr) + } + return +} + +// checkTagLength serves as a validator for static/identity mapping tag length +func checkTagLength(tag string) error { + if len(tag) > maxTagLen { + return errors.Errorf("DNAT label '%s' has %d bytes, max allowed is %d", + tag, len(tag), maxTagLen) + } + return nil +} diff --git a/plugins/vpp/natplugin/vppcalls/vpp2306/nat_vppcalls_test.go b/plugins/vpp/natplugin/vppcalls/vpp2306/nat_vppcalls_test.go new file mode 100644 index 0000000000..3f409b69d5 --- /dev/null +++ b/plugins/vpp/natplugin/vppcalls/vpp2306/nat_vppcalls_test.go @@ -0,0 +1,2011 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306_test + +import ( + "bytes" + "net" + "testing" + + "go.ligato.io/vpp-agent/v3/plugins/vpp/natplugin/vppcalls" + "go.ligato.io/vpp-agent/v3/plugins/vpp/natplugin/vppcalls/vpp2306" + + . "github.com/onsi/gomega" + + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip_types" + vpp_nat_ed "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/nat44_ed" + vpp_nat_ei "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/nat44_ei" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/nat_types" + "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/ifaceidx" + nat "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/nat" +) + +func TestSetNat44EdForwarding(t *testing.T) { + ctx, natHandler, _, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44EdPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: true}) + Expect(err).ShouldNot(HaveOccurred()) + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44ForwardingEnableDisableReply{}) + err = natHandler.SetNat44Forwarding(true) + + Expect(err).ShouldNot(HaveOccurred()) + + t.Logf("Msg: %+v (%#v)", ctx.MockChannel.Msg, ctx.MockChannel.Msg) + msg, ok := ctx.MockChannel.Msg.(*vpp_nat_ed.Nat44ForwardingEnableDisable) + Expect(ok).To(BeTrue()) + Expect(msg).ToNot(BeNil()) + Expect(msg.Enable).To(BeTrue()) +} + +func TestSetNat44EiForwarding(t *testing.T) { + ctx, natHandler, _, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: false}) + Expect(err).ShouldNot(HaveOccurred()) + + ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiForwardingEnableDisableReply{}) + err = natHandler.SetNat44Forwarding(true) + + Expect(err).ShouldNot(HaveOccurred()) + + t.Logf("Msg: %+v (%#v)", ctx.MockChannel.Msg, ctx.MockChannel.Msg) + msg, ok := ctx.MockChannel.Msg.(*vpp_nat_ei.Nat44EiForwardingEnableDisable) + Expect(ok).To(BeTrue()) + Expect(msg).ToNot(BeNil()) + Expect(msg.Enable).To(BeTrue()) +} + +func TestUnsetNat44EdForwarding(t *testing.T) { + ctx, natHandler, _, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44EdPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: true}) + Expect(err).ShouldNot(HaveOccurred()) + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44ForwardingEnableDisableReply{}) + err = natHandler.SetNat44Forwarding(false) + + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok := ctx.MockChannel.Msg.(*vpp_nat_ed.Nat44ForwardingEnableDisable) + Expect(ok).To(BeTrue()) + Expect(msg).ToNot(BeNil()) + Expect(msg.Enable).To(BeFalse()) +} + +func TestUnsetNat44EiForwarding(t *testing.T) { + ctx, natHandler, _, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: false}) + Expect(err).ShouldNot(HaveOccurred()) + + ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiForwardingEnableDisableReply{}) + err = natHandler.SetNat44Forwarding(false) + + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok := ctx.MockChannel.Msg.(*vpp_nat_ei.Nat44EiForwardingEnableDisable) + Expect(ok).To(BeTrue()) + Expect(msg).ToNot(BeNil()) + Expect(msg.Enable).To(BeFalse()) +} + +func TestSetNat44EdForwardingError(t *testing.T) { + ctx, natHandler, _, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + // Incorrect reply object + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44AddDelStaticMappingV2Reply{}) + err := natHandler.SetNat44Forwarding(true) + + Expect(err).Should(HaveOccurred()) +} + +func TestSetNat44EiForwardingError(t *testing.T) { + ctx, natHandler, _, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + // Incorrect reply object + ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiAddDelStaticMappingReply{}) + err := natHandler.SetNat44Forwarding(true) + + Expect(err).Should(HaveOccurred()) +} + +func TestSetNat44EdForwardingRetval(t *testing.T) { + ctx, natHandler, _, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44EdPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: true}) + Expect(err).ShouldNot(HaveOccurred()) + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44ForwardingEnableDisableReply{ + Retval: 1, + }) + err = natHandler.SetNat44Forwarding(true) + + Expect(err).Should(HaveOccurred()) +} + +func TestSetNat44EiForwardingRetval(t *testing.T) { + ctx, natHandler, _, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: false}) + Expect(err).ShouldNot(HaveOccurred()) + + ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiForwardingEnableDisableReply{ + Retval: 1, + }) + err = natHandler.SetNat44Forwarding(true) + + Expect(err).Should(HaveOccurred()) +} + +func TestEnableNat44EdInterfaceAsInside(t *testing.T) { + ctx, natHandler, swIfIndexes, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44EdPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: true}) + Expect(err).ShouldNot(HaveOccurred()) + + swIfIndexes.Put("if0", &ifaceidx.IfaceMetadata{SwIfIndex: 1}) + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44InterfaceAddDelFeatureReply{}) + err = natHandler.EnableNat44Interface("if0", true, false) + + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok := ctx.MockChannel.Msg.(*vpp_nat_ed.Nat44InterfaceAddDelFeature) + Expect(ok).To(BeTrue()) + Expect(msg).ToNot(BeNil()) + Expect(msg.IsAdd).To(BeTrue()) + Expect(msg.Flags).To(BeEquivalentTo(nat_types.NAT_IS_INSIDE)) + Expect(msg.SwIfIndex).To(BeEquivalentTo(1)) +} + +func TestEnableNat44EiInterfaceAsInside(t *testing.T) { + ctx, natHandler, swIfIndexes, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: false}) + Expect(err).ShouldNot(HaveOccurred()) + + swIfIndexes.Put("if0", &ifaceidx.IfaceMetadata{SwIfIndex: 1}) + + ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiInterfaceAddDelFeatureReply{}) + err = natHandler.EnableNat44Interface("if0", true, false) + + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok := ctx.MockChannel.Msg.(*vpp_nat_ei.Nat44EiInterfaceAddDelFeature) + Expect(ok).To(BeTrue()) + Expect(msg).ToNot(BeNil()) + Expect(msg.IsAdd).To(BeTrue()) + Expect(msg.Flags).To(BeEquivalentTo(vpp_nat_ei.NAT44_EI_IF_INSIDE)) + Expect(msg.SwIfIndex).To(BeEquivalentTo(1)) +} + +func TestEnableNat44EdInterfaceAsOutside(t *testing.T) { + ctx, natHandler, swIfIndexes, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44EdPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: true}) + Expect(err).ShouldNot(HaveOccurred()) + + swIfIndexes.Put("if1", &ifaceidx.IfaceMetadata{SwIfIndex: 2}) + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44InterfaceAddDelFeatureReply{}) + err = natHandler.EnableNat44Interface("if1", false, false) + + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok := ctx.MockChannel.Msg.(*vpp_nat_ed.Nat44InterfaceAddDelFeature) + Expect(ok).To(BeTrue()) + Expect(msg).ToNot(BeNil()) + Expect(msg.IsAdd).To(BeTrue()) + Expect(msg.Flags).To(BeEquivalentTo(0)) + Expect(msg.SwIfIndex).To(BeEquivalentTo(2)) +} + +func TestEnableNat44EiInterfaceAsOutside(t *testing.T) { + ctx, natHandler, swIfIndexes, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: false}) + Expect(err).ShouldNot(HaveOccurred()) + + swIfIndexes.Put("if1", &ifaceidx.IfaceMetadata{SwIfIndex: 2}) + + ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiInterfaceAddDelFeatureReply{}) + err = natHandler.EnableNat44Interface("if1", false, false) + + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok := ctx.MockChannel.Msg.(*vpp_nat_ei.Nat44EiInterfaceAddDelFeature) + Expect(ok).To(BeTrue()) + Expect(msg).ToNot(BeNil()) + Expect(msg.IsAdd).To(BeTrue()) + Expect(msg.Flags).To(BeEquivalentTo(0)) + Expect(msg.SwIfIndex).To(BeEquivalentTo(2)) +} + +func TestEnableNat44EdInterfaceError(t *testing.T) { + ctx, natHandler, swIfIndexes, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44EdPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: true}) + Expect(err).ShouldNot(HaveOccurred()) + + swIfIndexes.Put("if1", &ifaceidx.IfaceMetadata{SwIfIndex: 2}) + + // Incorrect reply object + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44AddDelAddressRangeReply{}) + err = natHandler.EnableNat44Interface("if1", false, false) + + Expect(err).Should(HaveOccurred()) +} + +func TestEnableNat44EiInterfaceError(t *testing.T) { + ctx, natHandler, swIfIndexes, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: false}) + Expect(err).ShouldNot(HaveOccurred()) + + swIfIndexes.Put("if1", &ifaceidx.IfaceMetadata{SwIfIndex: 2}) + + // Incorrect reply object + ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiAddDelAddressRangeReply{}) + err = natHandler.EnableNat44Interface("if1", false, false) + + Expect(err).Should(HaveOccurred()) +} + +func TestEnableNat44EdInterfaceRetval(t *testing.T) { + ctx, natHandler, swIfIndexes, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44EdPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: true}) + Expect(err).ShouldNot(HaveOccurred()) + + swIfIndexes.Put("if1", &ifaceidx.IfaceMetadata{SwIfIndex: 2}) + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44InterfaceAddDelFeatureReply{ + Retval: 1, + }) + err = natHandler.EnableNat44Interface("if1", false, false) + + Expect(err).Should(HaveOccurred()) +} + +func TestEnableNat44EiInterfaceRetval(t *testing.T) { + ctx, natHandler, swIfIndexes, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: false}) + Expect(err).ShouldNot(HaveOccurred()) + + swIfIndexes.Put("if1", &ifaceidx.IfaceMetadata{SwIfIndex: 2}) + + ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiInterfaceAddDelFeatureReply{ + Retval: 1, + }) + err = natHandler.EnableNat44Interface("if1", false, false) + + Expect(err).Should(HaveOccurred()) +} + +func TestDisableNat44EdInterfaceAsInside(t *testing.T) { + ctx, natHandler, swIfIndexes, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44EdPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: true}) + Expect(err).ShouldNot(HaveOccurred()) + + swIfIndexes.Put("if0", &ifaceidx.IfaceMetadata{SwIfIndex: 1}) + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44InterfaceAddDelFeatureReply{}) + err = natHandler.DisableNat44Interface("if0", true, false) + + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok := ctx.MockChannel.Msg.(*vpp_nat_ed.Nat44InterfaceAddDelFeature) + Expect(ok).To(BeTrue()) + Expect(msg).ToNot(BeNil()) + Expect(msg.IsAdd).To(BeFalse()) + Expect(msg.Flags).To(BeEquivalentTo(nat_types.NAT_IS_INSIDE)) + Expect(msg.SwIfIndex).To(BeEquivalentTo(1)) +} + +func TestDisableNat44EiInterfaceAsInside(t *testing.T) { + ctx, natHandler, swIfIndexes, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: false}) + Expect(err).ShouldNot(HaveOccurred()) + + swIfIndexes.Put("if0", &ifaceidx.IfaceMetadata{SwIfIndex: 1}) + + ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiInterfaceAddDelFeatureReply{}) + err = natHandler.DisableNat44Interface("if0", true, false) + + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok := ctx.MockChannel.Msg.(*vpp_nat_ei.Nat44EiInterfaceAddDelFeature) + Expect(ok).To(BeTrue()) + Expect(msg).ToNot(BeNil()) + Expect(msg.IsAdd).To(BeFalse()) + Expect(msg.Flags).To(BeEquivalentTo(vpp_nat_ei.NAT44_EI_IF_INSIDE)) + Expect(msg.SwIfIndex).To(BeEquivalentTo(1)) +} + +func TestDisableNat44EdInterfaceAsOutside(t *testing.T) { + ctx, natHandler, swIfIndexes, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44EdPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: true}) + Expect(err).ShouldNot(HaveOccurred()) + + swIfIndexes.Put("if1", &ifaceidx.IfaceMetadata{SwIfIndex: 2}) + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44InterfaceAddDelFeatureReply{}) + err = natHandler.DisableNat44Interface("if1", false, false) + + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok := ctx.MockChannel.Msg.(*vpp_nat_ed.Nat44InterfaceAddDelFeature) + Expect(ok).To(BeTrue()) + Expect(msg).ToNot(BeNil()) + Expect(msg.IsAdd).To(BeFalse()) + Expect(msg.Flags).To(BeEquivalentTo(0)) + Expect(msg.SwIfIndex).To(BeEquivalentTo(2)) +} + +func TestDisableNat44EiInterfaceAsOutside(t *testing.T) { + ctx, natHandler, swIfIndexes, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: false}) + Expect(err).ShouldNot(HaveOccurred()) + + swIfIndexes.Put("if1", &ifaceidx.IfaceMetadata{SwIfIndex: 2}) + + ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiInterfaceAddDelFeatureReply{}) + err = natHandler.DisableNat44Interface("if1", false, false) + + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok := ctx.MockChannel.Msg.(*vpp_nat_ei.Nat44EiInterfaceAddDelFeature) + Expect(ok).To(BeTrue()) + Expect(msg).ToNot(BeNil()) + Expect(msg.IsAdd).To(BeFalse()) + Expect(msg.Flags).To(BeEquivalentTo(0)) + Expect(msg.SwIfIndex).To(BeEquivalentTo(2)) +} + +func TestEnableNat44EdInterfaceOutputError(t *testing.T) { + ctx, natHandler, swIfIndexes, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44EdPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: true}) + Expect(err).ShouldNot(HaveOccurred()) + + swIfIndexes.Put("if1", &ifaceidx.IfaceMetadata{SwIfIndex: 2}) + + // Incorrect reply object + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44AddDelStaticMappingV2Reply{}) + err = natHandler.EnableNat44Interface("if1", false, true) + + Expect(err).Should(HaveOccurred()) +} + +func TestEnableNat44InterfaceOutputError(t *testing.T) { + ctx, natHandler, swIfIndexes, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: false}) + Expect(err).ShouldNot(HaveOccurred()) + + swIfIndexes.Put("if1", &ifaceidx.IfaceMetadata{SwIfIndex: 2}) + + // Incorrect reply object + ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiAddDelStaticMappingReply{}) + err = natHandler.EnableNat44Interface("if1", false, true) + + Expect(err).Should(HaveOccurred()) +} + +func TestEnableNat44EdInterfaceOutputRetval(t *testing.T) { + ctx, natHandler, swIfIndexes, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44EdPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: true}) + Expect(err).ShouldNot(HaveOccurred()) + + swIfIndexes.Put("if1", &ifaceidx.IfaceMetadata{SwIfIndex: 2}) + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44InterfaceAddDelFeatureReply{ + Retval: 1, + }) + err = natHandler.EnableNat44Interface("if1", false, true) + + Expect(err).Should(HaveOccurred()) +} + +func TestEnableNat44EiInterfaceOutputRetval(t *testing.T) { + ctx, natHandler, swIfIndexes, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: false}) + Expect(err).ShouldNot(HaveOccurred()) + + swIfIndexes.Put("if1", &ifaceidx.IfaceMetadata{SwIfIndex: 2}) + + ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiAddDelOutputInterfaceReply{ + Retval: 1, + }) + err = natHandler.EnableNat44Interface("if1", false, true) + + Expect(err).Should(HaveOccurred()) +} + +// FIXME: The following commented out tests seem to no longer make sense in +// VPP >= 22.10. If that is indeed the case (verify it), then delete them, else +// adapt them to the new VPP. +// func TestEnableNat44EdInterfaceOutputAsInside(t *testing.T) { +// ctx, natHandler, swIfIndexes, _ := natTestSetup(t) +// defer ctx.TeardownTestCtx() + +// ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44EdPluginEnableDisableReply{}) +// err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: true}) +// Expect(err).ShouldNot(HaveOccurred()) + +// swIfIndexes.Put("if0", &ifaceidx.IfaceMetadata{SwIfIndex: 1}) + +// ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44InterfaceAddDelFeatureReply{}) +// err = natHandler.EnableNat44Interface("if0", true, true) + +// Expect(err).ShouldNot(HaveOccurred()) + +// msg, ok := ctx.MockChannel.Msg.(*vpp_nat_ed.Nat44InterfaceAddDelFeature) +// Expect(ok).To(BeTrue()) +// Expect(msg).ToNot(BeNil()) +// Expect(msg.IsAdd).To(BeTrue()) +// Expect(msg.Flags).To(BeEquivalentTo(nat_types.NAT_IS_INSIDE)) +// Expect(msg.SwIfIndex).To(BeEquivalentTo(1)) +// } + +// func TestEnableNat44EiInterfaceOutputAsInside(t *testing.T) { +// ctx, natHandler, swIfIndexes, _ := natTestSetup(t) +// defer ctx.TeardownTestCtx() + +// ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiPluginEnableDisableReply{}) +// err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: false}) +// Expect(err).ShouldNot(HaveOccurred()) + +// swIfIndexes.Put("if0", &ifaceidx.IfaceMetadata{SwIfIndex: 1}) + +// ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiInterfaceAddDelFeatureReply{}) +// err = natHandler.EnableNat44Interface("if0", true, true) + +// Expect(err).ShouldNot(HaveOccurred()) + +// msg, ok := ctx.MockChannel.Msg.(*vpp_nat_ei.Nat44EiInterfaceAddDelFeature) +// Expect(ok).To(BeTrue()) +// Expect(msg).ToNot(BeNil()) +// Expect(msg.IsAdd).To(BeTrue()) +// Expect(msg.Flags).To(BeEquivalentTo(vpp_nat_ei.NAT44_EI_IF_INSIDE)) +// Expect(msg.SwIfIndex).To(BeEquivalentTo(1)) +// } + +// func TestEnableNat44EdInterfaceOutputAsOutside(t *testing.T) { +// ctx, natHandler, swIfIndexes, _ := natTestSetup(t) +// defer ctx.TeardownTestCtx() + +// ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44EdPluginEnableDisableReply{}) +// err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: true}) +// Expect(err).ShouldNot(HaveOccurred()) + +// swIfIndexes.Put("if1", &ifaceidx.IfaceMetadata{SwIfIndex: 2}) + +// ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44InterfaceAddDelFeatureReply{}) +// err = natHandler.EnableNat44Interface("if1", false, true) + +// Expect(err).ShouldNot(HaveOccurred()) + +// msg, ok := ctx.MockChannel.Msg.(*vpp_nat_ed.Nat44InterfaceAddDelFeature) +// Expect(ok).To(BeTrue()) +// Expect(msg).ToNot(BeNil()) +// Expect(msg.IsAdd).To(BeTrue()) +// Expect(msg.Flags).To(BeEquivalentTo(0)) +// Expect(msg.SwIfIndex).To(BeEquivalentTo(2)) +// } + +// func TestEnableNat44EiInterfaceOutputAsOutside(t *testing.T) { +// ctx, natHandler, swIfIndexes, _ := natTestSetup(t) +// defer ctx.TeardownTestCtx() + +// ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiPluginEnableDisableReply{}) +// err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: false}) +// Expect(err).ShouldNot(HaveOccurred()) + +// swIfIndexes.Put("if1", &ifaceidx.IfaceMetadata{SwIfIndex: 2}) + +// ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiInterfaceAddDelFeatureReply{}) +// err = natHandler.EnableNat44Interface("if1", false, true) + +// Expect(err).ShouldNot(HaveOccurred()) + +// msg, ok := ctx.MockChannel.Msg.(*vpp_nat_ei.Nat44EiInterfaceAddDelFeature) +// Expect(ok).To(BeTrue()) +// Expect(msg).ToNot(BeNil()) +// Expect(msg.IsAdd).To(BeTrue()) +// Expect(msg.Flags).To(BeEquivalentTo(0)) +// Expect(msg.SwIfIndex).To(BeEquivalentTo(2)) +// } + +// func TestDisableNat44EdInterfaceOutputAsInside(t *testing.T) { +// ctx, natHandler, swIfIndexes, _ := natTestSetup(t) +// defer ctx.TeardownTestCtx() + +// ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44EdPluginEnableDisableReply{}) +// err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: true}) +// Expect(err).ShouldNot(HaveOccurred()) + +// swIfIndexes.Put("if0", &ifaceidx.IfaceMetadata{SwIfIndex: 1}) + +// ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44InterfaceAddDelFeatureReply{}) +// err = natHandler.DisableNat44Interface("if0", true, true) + +// Expect(err).ShouldNot(HaveOccurred()) + +// msg, ok := ctx.MockChannel.Msg.(*vpp_nat_ed.Nat44InterfaceAddDelFeature) +// Expect(ok).To(BeTrue()) +// Expect(msg).ToNot(BeNil()) +// Expect(msg.IsAdd).To(BeFalse()) +// Expect(msg.Flags).To(BeEquivalentTo(nat_types.NAT_IS_INSIDE)) +// Expect(msg.SwIfIndex).To(BeEquivalentTo(1)) +// } + +// func TestDisableNat44EiInterfaceOutputAsInside(t *testing.T) { +// ctx, natHandler, swIfIndexes, _ := natTestSetup(t) +// defer ctx.TeardownTestCtx() + +// ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiPluginEnableDisableReply{}) +// err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: false}) +// Expect(err).ShouldNot(HaveOccurred()) + +// swIfIndexes.Put("if0", &ifaceidx.IfaceMetadata{SwIfIndex: 1}) + +// ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiInterfaceAddDelFeatureReply{}) +// err = natHandler.DisableNat44Interface("if0", true, true) + +// Expect(err).ShouldNot(HaveOccurred()) + +// msg, ok := ctx.MockChannel.Msg.(*vpp_nat_ei.Nat44EiInterfaceAddDelFeature) +// Expect(ok).To(BeTrue()) +// Expect(msg).ToNot(BeNil()) +// Expect(msg.IsAdd).To(BeFalse()) +// Expect(msg.Flags).To(BeEquivalentTo(vpp_nat_ei.NAT44_EI_IF_INSIDE)) +// Expect(msg.SwIfIndex).To(BeEquivalentTo(1)) +// } + +// func TestDisableNat44EdInterfaceOutputAsOutside(t *testing.T) { +// ctx, natHandler, swIfIndexes, _ := natTestSetup(t) +// defer ctx.TeardownTestCtx() + +// ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44EdPluginEnableDisableReply{}) +// err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: true}) +// Expect(err).ShouldNot(HaveOccurred()) + +// swIfIndexes.Put("if1", &ifaceidx.IfaceMetadata{SwIfIndex: 2}) + +// ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44InterfaceAddDelFeatureReply{}) +// err = natHandler.DisableNat44Interface("if1", false, true) + +// Expect(err).ShouldNot(HaveOccurred()) + +// msg, ok := ctx.MockChannel.Msg.(*vpp_nat_ed.Nat44InterfaceAddDelFeature) +// Expect(ok).To(BeTrue()) +// Expect(msg).ToNot(BeNil()) +// Expect(msg.IsAdd).To(BeFalse()) +// Expect(msg.Flags).To(BeEquivalentTo(0)) +// Expect(msg.SwIfIndex).To(BeEquivalentTo(2)) +// } + +// func TestDisableNat44EiInterfaceOutputAsOutside(t *testing.T) { +// ctx, natHandler, swIfIndexes, _ := natTestSetup(t) +// defer ctx.TeardownTestCtx() + +// ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiPluginEnableDisableReply{}) +// err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: false}) +// Expect(err).ShouldNot(HaveOccurred()) + +// swIfIndexes.Put("if1", &ifaceidx.IfaceMetadata{SwIfIndex: 2}) + +// ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiInterfaceAddDelFeatureReply{}) +// err = natHandler.DisableNat44Interface("if1", false, true) + +// Expect(err).ShouldNot(HaveOccurred()) + +// msg, ok := ctx.MockChannel.Msg.(*vpp_nat_ei.Nat44EiInterfaceAddDelFeature) +// Expect(ok).To(BeTrue()) +// Expect(msg).ToNot(BeNil()) +// Expect(msg.IsAdd).To(BeFalse()) +// Expect(msg.Flags).To(BeEquivalentTo(0)) +// Expect(msg.SwIfIndex).To(BeEquivalentTo(2)) +// } + +func TestAddNat44EdAddressPool(t *testing.T) { + ctx, natHandler, _, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44EdPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: true}) + Expect(err).ShouldNot(HaveOccurred()) + + addr1 := net.ParseIP("10.0.0.1").To4() + addr2 := net.ParseIP("10.0.0.10").To4() + + // first IP only + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44AddDelAddressRangeReply{}) + err = natHandler.AddNat44AddressPool(0, addr1.String(), "", false) + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok := ctx.MockChannel.Msg.(*vpp_nat_ed.Nat44AddDelAddressRange) + Expect(ok).To(BeTrue()) + Expect(msg.IsAdd).To(BeTrue()) + Expect(addressTo4IP(msg.FirstIPAddress)).To(BeEquivalentTo(addr1.String())) + Expect(addressTo4IP(msg.LastIPAddress)).To(BeEquivalentTo(addr1.String())) + Expect(msg.VrfID).To(BeEquivalentTo(0)) + Expect(msg.Flags).To(BeEquivalentTo(0)) + + // first IP + last IP + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44AddDelAddressRangeReply{}) + err = natHandler.AddNat44AddressPool(0, addr1.String(), addr2.String(), false) + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok = ctx.MockChannel.Msg.(*vpp_nat_ed.Nat44AddDelAddressRange) + Expect(ok).To(BeTrue()) + Expect(msg.IsAdd).To(BeTrue()) + Expect(addressTo4IP(msg.FirstIPAddress)).To(BeEquivalentTo(addr1.String())) + Expect(addressTo4IP(msg.LastIPAddress)).To(BeEquivalentTo(addr2.String())) + Expect(msg.VrfID).To(BeEquivalentTo(0)) + Expect(msg.Flags).To(BeEquivalentTo(0)) +} + +func TestAddNat44EiAddressPool(t *testing.T) { + ctx, natHandler, _, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: false}) + Expect(err).ShouldNot(HaveOccurred()) + + addr1 := net.ParseIP("10.0.0.1").To4() + addr2 := net.ParseIP("10.0.0.10").To4() + + // first IP only + ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiAddDelAddressRangeReply{}) + err = natHandler.AddNat44AddressPool(0, addr1.String(), "", false) + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok := ctx.MockChannel.Msg.(*vpp_nat_ei.Nat44EiAddDelAddressRange) + Expect(ok).To(BeTrue()) + Expect(msg.IsAdd).To(BeTrue()) + Expect(addressTo4IP(msg.FirstIPAddress)).To(BeEquivalentTo(addr1.String())) + Expect(addressTo4IP(msg.LastIPAddress)).To(BeEquivalentTo(addr1.String())) + Expect(msg.VrfID).To(BeEquivalentTo(0)) + //Expect(msg.Flags).To(BeEquivalentTo(0)) + + // first IP + last IP + ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiAddDelAddressRangeReply{}) + err = natHandler.AddNat44AddressPool(0, addr1.String(), addr2.String(), false) + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok = ctx.MockChannel.Msg.(*vpp_nat_ei.Nat44EiAddDelAddressRange) + Expect(ok).To(BeTrue()) + Expect(msg.IsAdd).To(BeTrue()) + Expect(addressTo4IP(msg.FirstIPAddress)).To(BeEquivalentTo(addr1.String())) + Expect(addressTo4IP(msg.LastIPAddress)).To(BeEquivalentTo(addr2.String())) + Expect(msg.VrfID).To(BeEquivalentTo(0)) + //Expect(msg.Flags).To(BeEquivalentTo(0)) +} + +func TestAddNat44EdAddressPoolError(t *testing.T) { + ctx, natHandler, _, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44EdPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: true}) + Expect(err).ShouldNot(HaveOccurred()) + + addr := net.ParseIP("10.0.0.1").To4() + + // Incorrect reply object + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44AddDelIdentityMappingReply{}) + err = natHandler.AddNat44AddressPool(0, addr.String(), "", false) + + Expect(err).Should(HaveOccurred()) +} + +func TestAddNat44EiAddressPoolError(t *testing.T) { + ctx, natHandler, _, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: false}) + Expect(err).ShouldNot(HaveOccurred()) + + addr := net.ParseIP("10.0.0.1").To4() + + // Incorrect reply object + ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiAddDelIdentityMappingReply{}) + err = natHandler.AddNat44AddressPool(0, addr.String(), "", false) + + Expect(err).Should(HaveOccurred()) +} + +func TestAddNat44EdAddressPoolRetval(t *testing.T) { + ctx, natHandler, _, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44EdPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: true}) + Expect(err).ShouldNot(HaveOccurred()) + + addr := net.ParseIP("10.0.0.1").To4() + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44AddDelAddressRangeReply{ + Retval: 1, + }) + err = natHandler.AddNat44AddressPool(0, addr.String(), "", false) + + Expect(err).Should(HaveOccurred()) +} + +func TestAddNat44EiAddressPoolRetval(t *testing.T) { + ctx, natHandler, _, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: false}) + Expect(err).ShouldNot(HaveOccurred()) + + addr := net.ParseIP("10.0.0.1").To4() + + ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiAddDelAddressRangeReply{ + Retval: 1, + }) + err = natHandler.AddNat44AddressPool(0, addr.String(), "", false) + + Expect(err).Should(HaveOccurred()) +} + +func TestDelNat44EdAddressPool(t *testing.T) { + ctx, natHandler, _, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44EdPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: true}) + Expect(err).ShouldNot(HaveOccurred()) + + addr := net.ParseIP("10.0.0.1").To4() + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44AddDelAddressRangeReply{}) + err = natHandler.DelNat44AddressPool(0, addr.String(), "", false) + + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok := ctx.MockChannel.Msg.(*vpp_nat_ed.Nat44AddDelAddressRange) + Expect(ok).To(BeTrue()) + Expect(msg.IsAdd).To(BeFalse()) + Expect(addressTo4IP(msg.FirstIPAddress)).To(BeEquivalentTo(addr.String())) + Expect(addressTo4IP(msg.LastIPAddress)).To(BeEquivalentTo(addr.String())) + Expect(msg.VrfID).To(BeEquivalentTo(0)) + Expect(msg.Flags).To(BeEquivalentTo(0)) +} + +func TestDelNat44EiAddressPool(t *testing.T) { + ctx, natHandler, _, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: false}) + Expect(err).ShouldNot(HaveOccurred()) + + addr := net.ParseIP("10.0.0.1").To4() + + ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiAddDelAddressRangeReply{}) + err = natHandler.DelNat44AddressPool(0, addr.String(), "", false) + + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok := ctx.MockChannel.Msg.(*vpp_nat_ei.Nat44EiAddDelAddressRange) + Expect(ok).To(BeTrue()) + Expect(msg.IsAdd).To(BeFalse()) + Expect(addressTo4IP(msg.FirstIPAddress)).To(BeEquivalentTo(addr.String())) + Expect(addressTo4IP(msg.LastIPAddress)).To(BeEquivalentTo(addr.String())) + Expect(msg.VrfID).To(BeEquivalentTo(0)) +} + +/* DEPRECATED + +func TestSetNat44VirtualReassemblyIPv4(t *testing.T) { + ctx, natHandler, _, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ed.NatSetReassReply{}) + err := natHandler.SetVirtualReassemblyIPv4(&nat.VirtualReassembly{ + Timeout: 10, + MaxFragments: 20, + MaxReassemblies: 30, + DropFragments: true, + }) + + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok := ctx.MockChannel.Msg.(*vpp_nat_ed.NatSetReass) + Expect(ok).To(BeTrue()) + Expect(msg.Timeout).To(BeEquivalentTo(10)) + Expect(msg.MaxFrag).To(BeEquivalentTo(20)) + Expect(msg.MaxReass).To(BeEquivalentTo(30)) + Expect(msg.DropFrag).To(BeEquivalentTo(1)) +} + +func TestSetNat44VirtualReassemblyIPv6(t *testing.T) { + ctx, natHandler, _, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ed.NatSetReassReply{}) + err := natHandler.SetVirtualReassemblyIPv6(&nat.VirtualReassembly{ + Timeout: 5, + MaxFragments: 10, + MaxReassemblies: 15, + DropFragments: true, + }) + + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok := ctx.MockChannel.Msg.(*vpp_nat_ed.NatSetReass) + Expect(ok).To(BeTrue()) + Expect(msg.Timeout).To(BeEquivalentTo(5)) + Expect(msg.MaxFrag).To(BeEquivalentTo(10)) + Expect(msg.MaxReass).To(BeEquivalentTo(15)) + Expect(msg.DropFrag).To(BeEquivalentTo(1)) +}*/ + +func TestAddNat44EdStaticMapping(t *testing.T) { + ctx, natHandler, swIfIndexes, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44EdPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: true}) + Expect(err).ShouldNot(HaveOccurred()) + + swIfIndexes.Put("if0", &ifaceidx.IfaceMetadata{SwIfIndex: 1}) + + localIP := net.ParseIP("10.0.0.1").To4() + externalIP := net.ParseIP("10.0.0.2").To4() + + // DataContext + mapping := &nat.DNat44_StaticMapping{ + ExternalIp: externalIP.String(), + ExternalPort: 8080, + ExternalInterface: "if0", // overrides external IP + Protocol: nat.DNat44_TCP, + TwiceNat: nat.DNat44_StaticMapping_ENABLED, + LocalIps: []*nat.DNat44_StaticMapping_LocalIP{ + { + LocalIp: localIP.String(), + VrfId: 1, + LocalPort: 24, + }, + }, + } + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44AddDelStaticMappingV2Reply{}) + err = natHandler.AddNat44StaticMapping(mapping, "DNAT 1") + + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok := ctx.MockChannel.Msg.(*vpp_nat_ed.Nat44AddDelStaticMappingV2) + Expect(ok).To(BeTrue()) + Expect(msg.Tag).To(BeEquivalentTo("DNAT 1")) + Expect(msg.VrfID).To(BeEquivalentTo(1)) + Expect(msg.IsAdd).To(BeTrue()) + Expect(msg.LocalPort).To(BeEquivalentTo(24)) + Expect(msg.ExternalPort).To(BeEquivalentTo(8080)) + Expect(msg.Protocol).To(BeEquivalentTo(6)) + Expect(addressTo4IP(msg.ExternalIPAddress)).To(BeEquivalentTo("0.0.0.0")) + Expect(msg.ExternalSwIfIndex).To(BeEquivalentTo(1)) + Expect(addressTo4IP(msg.LocalIPAddress)).To(BeEquivalentTo(localIP.String())) + Expect(msg.Flags).To(BeEquivalentTo(nat_types.NAT_IS_TWICE_NAT + nat_types.NAT_IS_OUT2IN_ONLY)) +} + +func TestAddNat44EdStaticMappingMinimalConfig(t *testing.T) { + ctx, natHandler, swIfIndexes, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44EdPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: true}) + Expect(err).ShouldNot(HaveOccurred()) + + swIfIndexes.Put("if0", &ifaceidx.IfaceMetadata{SwIfIndex: 1}) + + localIP := net.ParseIP("10.0.0.1").To4() + externalIP := net.ParseIP("10.0.0.2").To4() + + // DataContext + mapping := &nat.DNat44_StaticMapping{ + ExternalIp: externalIP.String(), + Protocol: nat.DNat44_TCP, + LocalIps: []*nat.DNat44_StaticMapping_LocalIP{ + { + LocalIp: localIP.String(), + }, + }, + } + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44AddDelStaticMappingV2Reply{}) + err = natHandler.AddNat44StaticMapping(mapping, "DNAT 1") + + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok := ctx.MockChannel.Msg.(*vpp_nat_ed.Nat44AddDelStaticMappingV2) + Expect(ok).To(BeTrue()) + Expect(msg.Tag).To(BeEquivalentTo("DNAT 1")) + Expect(msg.VrfID).To(BeEquivalentTo(0)) + Expect(msg.IsAdd).To(BeTrue()) + Expect(msg.Protocol).To(BeEquivalentTo(6)) + Expect(addressTo4IP(msg.ExternalIPAddress)).To(BeEquivalentTo("10.0.0.2")) + Expect(addressTo4IP(msg.LocalIPAddress)).To(BeEquivalentTo(localIP.String())) +} + +func TestAddNat44EiStaticMapping(t *testing.T) { + ctx, natHandler, swIfIndexes, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: false}) + Expect(err).ShouldNot(HaveOccurred()) + + swIfIndexes.Put("if0", &ifaceidx.IfaceMetadata{SwIfIndex: 1}) + + localIP := net.ParseIP("10.0.0.1").To4() + externalIP := net.ParseIP("10.0.0.2").To4() + + // DataContext + mapping := &nat.DNat44_StaticMapping{ + ExternalIp: externalIP.String(), + ExternalPort: 8080, + ExternalInterface: "if0", // overrides external IP + Protocol: nat.DNat44_TCP, + //TwiceNat: nat.DNat44_StaticMapping_ENABLED, + LocalIps: []*nat.DNat44_StaticMapping_LocalIP{ + { + LocalIp: localIP.String(), + VrfId: 1, + LocalPort: 24, + }, + }, + } + + ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiAddDelStaticMappingReply{}) + err = natHandler.AddNat44StaticMapping(mapping, "DNAT 1") + + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok := ctx.MockChannel.Msg.(*vpp_nat_ei.Nat44EiAddDelStaticMapping) + Expect(ok).To(BeTrue()) + Expect(msg.Tag).To(BeEquivalentTo("DNAT 1")) + Expect(msg.VrfID).To(BeEquivalentTo(1)) + Expect(msg.IsAdd).To(BeTrue()) + Expect(msg.LocalPort).To(BeEquivalentTo(24)) + Expect(msg.ExternalPort).To(BeEquivalentTo(8080)) + Expect(msg.Protocol).To(BeEquivalentTo(6)) + Expect(addressTo4IP(msg.ExternalIPAddress)).To(BeEquivalentTo("0.0.0.0")) + Expect(msg.ExternalSwIfIndex).To(BeEquivalentTo(1)) + Expect(addressTo4IP(msg.LocalIPAddress)).To(BeEquivalentTo(localIP.String())) +} + +func TestAddNat44EdIdentityMappingWithInterface(t *testing.T) { + ctx, natHandler, _, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44EdPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: true}) + Expect(err).ShouldNot(HaveOccurred()) + + localIP := net.ParseIP("10.0.0.1").To4() + externalIP := net.ParseIP("10.0.0.2").To4() + + // DataContext + mapping := &nat.DNat44_StaticMapping{ + ExternalIp: externalIP.String(), + Protocol: nat.DNat44_TCP, + LocalIps: []*nat.DNat44_StaticMapping_LocalIP{ + { + LocalIp: localIP.String(), + }, + }, + } + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44AddDelStaticMappingV2Reply{}) + err = natHandler.AddNat44StaticMapping(mapping, "DNAT 1") + + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok := ctx.MockChannel.Msg.(*vpp_nat_ed.Nat44AddDelStaticMappingV2) + Expect(ok).To(BeTrue()) + Expect(msg.Tag).To(BeEquivalentTo("DNAT 1")) + Expect(msg.IsAdd).To(BeTrue()) + Expect(addressTo4IP(msg.ExternalIPAddress)).To(BeEquivalentTo(externalIP.String())) + Expect(addressTo4IP(msg.LocalIPAddress)).To(BeEquivalentTo(localIP.String())) + Expect(msg.Flags).To(BeEquivalentTo(nat_types.NAT_IS_OUT2IN_ONLY + nat_types.NAT_IS_ADDR_ONLY)) +} + +func TestAddNat44EiIdentityMappingWithInterface(t *testing.T) { + ctx, natHandler, _, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: false}) + Expect(err).ShouldNot(HaveOccurred()) + + localIP := net.ParseIP("10.0.0.1").To4() + externalIP := net.ParseIP("10.0.0.2").To4() + + // DataContext + mapping := &nat.DNat44_StaticMapping{ + ExternalIp: externalIP.String(), + Protocol: nat.DNat44_TCP, + LocalIps: []*nat.DNat44_StaticMapping_LocalIP{ + { + LocalIp: localIP.String(), + }, + }, + } + + ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiAddDelStaticMappingReply{}) + err = natHandler.AddNat44StaticMapping(mapping, "DNAT 1") + + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok := ctx.MockChannel.Msg.(*vpp_nat_ei.Nat44EiAddDelStaticMapping) + Expect(ok).To(BeTrue()) + Expect(msg.Tag).To(BeEquivalentTo("DNAT 1")) + Expect(msg.IsAdd).To(BeTrue()) + Expect(addressTo4IP(msg.ExternalIPAddress)).To(BeEquivalentTo(externalIP.String())) + Expect(addressTo4IP(msg.LocalIPAddress)).To(BeEquivalentTo(localIP.String())) + //Expect(msg.Flags).To(BeEquivalentTo(nat_types.NAT_IS_OUT2IN_ONLY + nat_types.NAT_IS_ADDR_ONLY)) + Expect(msg.Flags).To(BeEquivalentTo(vpp_nat_ei.NAT44_EI_ADDR_ONLY_MAPPING)) +} + +func TestAddNat44EdStaticMappingError(t *testing.T) { + ctx, natHandler, _, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44EdPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: true}) + Expect(err).ShouldNot(HaveOccurred()) + + // Incorrect reply object + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44AddDelLbStaticMappingReply{}) + err = natHandler.AddNat44StaticMapping(&nat.DNat44_StaticMapping{}, "") + + Expect(err).Should(HaveOccurred()) +} + +func TestAddNat44EiStaticMappingError(t *testing.T) { + ctx, natHandler, _, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: false}) + Expect(err).ShouldNot(HaveOccurred()) + + // Incorrect reply object + ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiAddDelOutputInterfaceReply{}) + err = natHandler.AddNat44StaticMapping(&nat.DNat44_StaticMapping{}, "") + + Expect(err).Should(HaveOccurred()) +} + +func TestAddNat44EdStaticMappingRetval(t *testing.T) { + ctx, natHandler, _, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44EdPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: true}) + Expect(err).ShouldNot(HaveOccurred()) + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44AddDelStaticMappingV2Reply{ + Retval: 1, + }) + err = natHandler.AddNat44StaticMapping(&nat.DNat44_StaticMapping{}, "") + + Expect(err).Should(HaveOccurred()) +} + +func TestAddNat44EiStaticMappingRetval(t *testing.T) { + ctx, natHandler, _, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: false}) + Expect(err).ShouldNot(HaveOccurred()) + + ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiAddDelStaticMappingReply{ + Retval: 1, + }) + err = natHandler.AddNat44StaticMapping(&nat.DNat44_StaticMapping{}, "") + + Expect(err).Should(HaveOccurred()) +} + +func TestDelNat44EdStaticMapping(t *testing.T) { + ctx, natHandler, swIfIndexes, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44EdPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: true}) + Expect(err).ShouldNot(HaveOccurred()) + + swIfIndexes.Put("if0", &ifaceidx.IfaceMetadata{SwIfIndex: 1}) + + localIP := net.ParseIP("10.0.0.1").To4() + externalIP := net.ParseIP("10.0.0.2").To4() + + mapping := &nat.DNat44_StaticMapping{ + ExternalIp: externalIP.String(), + ExternalPort: 8080, + ExternalInterface: "if0", // overrides external IP + Protocol: nat.DNat44_TCP, + TwiceNat: nat.DNat44_StaticMapping_ENABLED, + LocalIps: []*nat.DNat44_StaticMapping_LocalIP{ + { + LocalIp: localIP.String(), + VrfId: 1, + LocalPort: 24, + }, + }, + } + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44AddDelStaticMappingV2Reply{}) + err = natHandler.DelNat44StaticMapping(mapping, "DNAT 1") + + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok := ctx.MockChannel.Msg.(*vpp_nat_ed.Nat44AddDelStaticMappingV2) + Expect(ok).To(BeTrue()) + Expect(msg.Tag).To(BeEquivalentTo("DNAT 1")) + Expect(msg.VrfID).To(BeEquivalentTo(1)) + Expect(msg.IsAdd).To(BeFalse()) + Expect(msg.LocalPort).To(BeEquivalentTo(24)) + Expect(msg.ExternalPort).To(BeEquivalentTo(8080)) + Expect(msg.Protocol).To(BeEquivalentTo(6)) + Expect(addressTo4IP(msg.ExternalIPAddress)).To(BeEquivalentTo("0.0.0.0")) + Expect(msg.ExternalSwIfIndex).To(BeEquivalentTo(1)) + Expect(addressTo4IP(msg.LocalIPAddress)).To(BeEquivalentTo(localIP.String())) + Expect(msg.Flags).To(BeEquivalentTo(nat_types.NAT_IS_TWICE_NAT + nat_types.NAT_IS_OUT2IN_ONLY)) +} + +func TestDelNat44EiStaticMapping(t *testing.T) { + ctx, natHandler, swIfIndexes, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: false}) + Expect(err).ShouldNot(HaveOccurred()) + + swIfIndexes.Put("if0", &ifaceidx.IfaceMetadata{SwIfIndex: 1}) + + localIP := net.ParseIP("10.0.0.1").To4() + externalIP := net.ParseIP("10.0.0.2").To4() + + mapping := &nat.DNat44_StaticMapping{ + ExternalIp: externalIP.String(), + ExternalPort: 8080, + ExternalInterface: "if0", // overrides external IP + Protocol: nat.DNat44_TCP, + LocalIps: []*nat.DNat44_StaticMapping_LocalIP{ + { + LocalIp: localIP.String(), + VrfId: 1, + LocalPort: 24, + }, + }, + } + + ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiAddDelStaticMappingReply{}) + err = natHandler.DelNat44StaticMapping(mapping, "DNAT 1") + + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok := ctx.MockChannel.Msg.(*vpp_nat_ei.Nat44EiAddDelStaticMapping) + Expect(ok).To(BeTrue()) + Expect(msg.Tag).To(BeEquivalentTo("DNAT 1")) + Expect(msg.VrfID).To(BeEquivalentTo(1)) + Expect(msg.IsAdd).To(BeFalse()) + Expect(msg.LocalPort).To(BeEquivalentTo(24)) + Expect(msg.ExternalPort).To(BeEquivalentTo(8080)) + Expect(msg.Protocol).To(BeEquivalentTo(6)) + Expect(addressTo4IP(msg.ExternalIPAddress)).To(BeEquivalentTo("0.0.0.0")) + Expect(msg.ExternalSwIfIndex).To(BeEquivalentTo(1)) + Expect(addressTo4IP(msg.LocalIPAddress)).To(BeEquivalentTo(localIP.String())) +} +func TestDelNat44EdStaticMappingAddrOnly(t *testing.T) { + ctx, natHandler, _, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44EdPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: true}) + Expect(err).ShouldNot(HaveOccurred()) + + localIP := net.ParseIP("10.0.0.1").To4() + externalIP := net.ParseIP("10.0.0.2").To4() + + mapping := &nat.DNat44_StaticMapping{ + ExternalIp: externalIP.String(), + Protocol: nat.DNat44_TCP, + LocalIps: []*nat.DNat44_StaticMapping_LocalIP{ + { + LocalIp: localIP.String(), + }, + }, + } + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44AddDelStaticMappingV2Reply{}) + err = natHandler.DelNat44StaticMapping(mapping, "DNAT 1") + + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok := ctx.MockChannel.Msg.(*vpp_nat_ed.Nat44AddDelStaticMappingV2) + Expect(ok).To(BeTrue()) + Expect(msg.Tag).To(BeEquivalentTo("DNAT 1")) + Expect(msg.IsAdd).To(BeFalse()) + Expect(addressTo4IP(msg.ExternalIPAddress)).To(BeEquivalentTo(externalIP.String())) + Expect(addressTo4IP(msg.LocalIPAddress)).To(BeEquivalentTo(localIP.String())) +} + +func TestDelNat44EiStaticMappingAddrOnly(t *testing.T) { + ctx, natHandler, _, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: false}) + Expect(err).ShouldNot(HaveOccurred()) + + localIP := net.ParseIP("10.0.0.1").To4() + externalIP := net.ParseIP("10.0.0.2").To4() + + mapping := &nat.DNat44_StaticMapping{ + ExternalIp: externalIP.String(), + Protocol: nat.DNat44_TCP, + LocalIps: []*nat.DNat44_StaticMapping_LocalIP{ + { + LocalIp: localIP.String(), + }, + }, + } + + ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiAddDelStaticMappingReply{}) + err = natHandler.DelNat44StaticMapping(mapping, "DNAT 1") + + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok := ctx.MockChannel.Msg.(*vpp_nat_ei.Nat44EiAddDelStaticMapping) + Expect(ok).To(BeTrue()) + Expect(msg.Tag).To(BeEquivalentTo("DNAT 1")) + Expect(msg.IsAdd).To(BeFalse()) + Expect(addressTo4IP(msg.ExternalIPAddress)).To(BeEquivalentTo(externalIP.String())) + Expect(addressTo4IP(msg.LocalIPAddress)).To(BeEquivalentTo(localIP.String())) +} + +func TestAddNat44EdStaticMappingLb(t *testing.T) { + ctx, natHandler, _, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44EdPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: true}) + Expect(err).ShouldNot(HaveOccurred()) + + externalIP := net.ParseIP("10.0.0.1").To4() + localIP1 := net.ParseIP("10.0.0.2").To4() + localIP2 := net.ParseIP("10.0.0.3").To4() + + mapping := &nat.DNat44_StaticMapping{ + ExternalIp: externalIP.String(), + ExternalPort: 8080, + ExternalInterface: "if0", + Protocol: nat.DNat44_TCP, + TwiceNat: nat.DNat44_StaticMapping_ENABLED, + LocalIps: localIPs(localIP1, localIP2), + } + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44AddDelLbStaticMappingReply{}) + err = natHandler.AddNat44StaticMapping(mapping, "DNAT 1") + + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok := ctx.MockChannel.Msg.(*vpp_nat_ed.Nat44AddDelLbStaticMapping) + Expect(ok).To(BeTrue()) + Expect(msg.Tag).To(BeEquivalentTo("DNAT 1")) + Expect(msg.IsAdd).To(BeTrue()) + Expect(addressTo4IP(msg.ExternalAddr)).To(BeEquivalentTo(externalIP.String())) + Expect(msg.ExternalPort).To(BeEquivalentTo(8080)) + Expect(msg.Protocol).To(BeEquivalentTo(6)) + Expect(msg.Flags).To(BeEquivalentTo(nat_types.NAT_IS_TWICE_NAT + nat_types.NAT_IS_OUT2IN_ONLY)) + + // Local IPs + Expect(msg.Locals).To(HaveLen(2)) + expectedCount := 0 + for _, local := range msg.Locals { + localAddr := make(net.IP, net.IPv4len) + copy(localAddr, local.Addr[:]) + if bytes.Equal(localAddr, localIP1) && local.Port == 8080 && local.Probability == 35 { + expectedCount++ + } + copy(localAddr, local.Addr[:]) + if bytes.Equal(localAddr, localIP2) && local.Port == 8181 && local.Probability == 65 { + expectedCount++ + } + } + Expect(expectedCount).To(BeEquivalentTo(2)) +} + +func TestDelNat44EdStaticMappingLb(t *testing.T) { + ctx, natHandler, _, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44EdPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: true}) + Expect(err).ShouldNot(HaveOccurred()) + + externalIP := net.ParseIP("10.0.0.1").To4() + localIP1 := net.ParseIP("10.0.0.2").To4() + localIP2 := net.ParseIP("10.0.0.3").To4() + + mapping := &nat.DNat44_StaticMapping{ + ExternalIp: externalIP.String(), + ExternalPort: 8080, + ExternalInterface: "if0", + Protocol: nat.DNat44_TCP, + TwiceNat: nat.DNat44_StaticMapping_ENABLED, + LocalIps: localIPs(localIP1, localIP2), + } + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44AddDelLbStaticMappingReply{}) + err = natHandler.DelNat44StaticMapping(mapping, "DNAT 1") + + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok := ctx.MockChannel.Msg.(*vpp_nat_ed.Nat44AddDelLbStaticMapping) + Expect(ok).To(BeTrue()) + Expect(msg.Tag).To(BeEquivalentTo("DNAT 1")) + Expect(msg.IsAdd).To(BeFalse()) + Expect(addressTo4IP(msg.ExternalAddr)).To(BeEquivalentTo(externalIP.String())) + Expect(msg.ExternalPort).To(BeEquivalentTo(8080)) + Expect(msg.Protocol).To(BeEquivalentTo(6)) + Expect(msg.Flags).To(BeEquivalentTo(nat_types.NAT_IS_TWICE_NAT + nat_types.NAT_IS_OUT2IN_ONLY)) + + // Local IPs + Expect(msg.Locals).To(HaveLen(2)) + expectedCount := 0 + for _, local := range msg.Locals { + localAddr := make(net.IP, net.IPv4len) + copy(localAddr, local.Addr[:]) + if bytes.Equal(localAddr, localIP1) && local.Port == 8080 && local.Probability == 35 { + expectedCount++ + } + copy(localAddr, local.Addr[:]) + if bytes.Equal(localAddr, localIP2) && local.Port == 8181 && local.Probability == 65 { + expectedCount++ + } + } + Expect(expectedCount).To(BeEquivalentTo(2)) +} + +func TestAddNat44EdIdentityMapping(t *testing.T) { + ctx, natHandler, swIfIndexes, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44EdPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: true}) + Expect(err).ShouldNot(HaveOccurred()) + + swIfIndexes.Put("if0", &ifaceidx.IfaceMetadata{SwIfIndex: 1}) + + address := net.ParseIP("10.0.0.1").To4() + + mapping := &nat.DNat44_IdentityMapping{ + VrfId: 1, + Interface: "if0", // overrides IP address + IpAddress: address.String(), + Port: 9000, + Protocol: nat.DNat44_UDP, + } + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44AddDelIdentityMappingReply{}) + err = natHandler.AddNat44IdentityMapping(mapping, "DNAT 1") + + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok := ctx.MockChannel.Msg.(*vpp_nat_ed.Nat44AddDelIdentityMapping) + Expect(ok).To(BeTrue()) + Expect(msg.Tag).To(BeEquivalentTo("DNAT 1")) + Expect(msg.VrfID).To(BeEquivalentTo(1)) + Expect(addressTo4IP(msg.IPAddress)).To(BeEquivalentTo("0.0.0.0")) + Expect(msg.IsAdd).To(BeTrue()) + Expect(msg.SwIfIndex).To(BeEquivalentTo(1)) + Expect(msg.Protocol).To(BeEquivalentTo(17)) + Expect(msg.Port).To(BeEquivalentTo(9000)) + Expect(msg.Flags).To(BeEquivalentTo(0)) +} + +func TestAddNat44EiIdentityMapping(t *testing.T) { + ctx, natHandler, swIfIndexes, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: false}) + Expect(err).ShouldNot(HaveOccurred()) + + swIfIndexes.Put("if0", &ifaceidx.IfaceMetadata{SwIfIndex: 1}) + + address := net.ParseIP("10.0.0.1").To4() + + mapping := &nat.DNat44_IdentityMapping{ + VrfId: 1, + Interface: "if0", // overrides IP address + IpAddress: address.String(), + Port: 9000, + Protocol: nat.DNat44_UDP, + } + + ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiAddDelIdentityMappingReply{}) + err = natHandler.AddNat44IdentityMapping(mapping, "DNAT 1") + + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok := ctx.MockChannel.Msg.(*vpp_nat_ei.Nat44EiAddDelIdentityMapping) + Expect(ok).To(BeTrue()) + Expect(msg.Tag).To(BeEquivalentTo("DNAT 1")) + Expect(msg.VrfID).To(BeEquivalentTo(1)) + Expect(addressTo4IP(msg.IPAddress)).To(BeEquivalentTo("0.0.0.0")) + Expect(msg.IsAdd).To(BeTrue()) + Expect(msg.SwIfIndex).To(BeEquivalentTo(1)) + Expect(msg.Protocol).To(BeEquivalentTo(17)) + Expect(msg.Port).To(BeEquivalentTo(9000)) + Expect(msg.Flags).To(BeEquivalentTo(0)) +} +func TestAddNat44EdIdentityMappingAddrOnly(t *testing.T) { + ctx, natHandler, swIfIndexes, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44EdPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: true}) + Expect(err).ShouldNot(HaveOccurred()) + + swIfIndexes.Put("if0", &ifaceidx.IfaceMetadata{SwIfIndex: 1}) + + // IPAddress == nil and Port == 0 means it's address only + mapping := &nat.DNat44_IdentityMapping{ + VrfId: 1, + Interface: "if0", // overrides IP address + Protocol: nat.DNat44_UDP, + } + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44AddDelIdentityMappingReply{}) + err = natHandler.AddNat44IdentityMapping(mapping, "DNAT 1") + + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok := ctx.MockChannel.Msg.(*vpp_nat_ed.Nat44AddDelIdentityMapping) + Expect(ok).To(BeTrue()) + Expect(msg.Tag).To(BeEquivalentTo("DNAT 1")) + Expect(addressTo4IP(msg.IPAddress)).To(BeEquivalentTo("0.0.0.0")) + Expect(msg.IsAdd).To(BeTrue()) + Expect(msg.Protocol).To(BeEquivalentTo(17)) + Expect(msg.SwIfIndex).To(BeEquivalentTo(1)) + Expect(msg.Flags).To(BeEquivalentTo(nat_types.NAT_IS_ADDR_ONLY)) +} + +func TestAddNat44EiIdentityMappingAddrOnly(t *testing.T) { + ctx, natHandler, swIfIndexes, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: false}) + Expect(err).ShouldNot(HaveOccurred()) + + swIfIndexes.Put("if0", &ifaceidx.IfaceMetadata{SwIfIndex: 1}) + + // IPAddress == nil and Port == 0 means it's address only + mapping := &nat.DNat44_IdentityMapping{ + VrfId: 1, + Interface: "if0", // overrides IP address + Protocol: nat.DNat44_UDP, + } + + ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiAddDelIdentityMappingReply{}) + err = natHandler.AddNat44IdentityMapping(mapping, "DNAT 1") + + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok := ctx.MockChannel.Msg.(*vpp_nat_ei.Nat44EiAddDelIdentityMapping) + Expect(ok).To(BeTrue()) + Expect(msg.Tag).To(BeEquivalentTo("DNAT 1")) + Expect(addressTo4IP(msg.IPAddress)).To(BeEquivalentTo("0.0.0.0")) + Expect(msg.IsAdd).To(BeTrue()) + Expect(msg.Protocol).To(BeEquivalentTo(17)) + Expect(msg.SwIfIndex).To(BeEquivalentTo(1)) + Expect(msg.Flags).To(BeEquivalentTo(nat_types.NAT_IS_ADDR_ONLY)) +} + +func TestAddNat44EdIdentityMappingNoInterface(t *testing.T) { + ctx, natHandler, _, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44EdPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: true}) + Expect(err).ShouldNot(HaveOccurred()) + + address := net.ParseIP("10.0.0.1").To4() + + mapping := &nat.DNat44_IdentityMapping{ + VrfId: 1, + Protocol: nat.DNat44_UDP, + IpAddress: address.String(), + Port: 8989, + } + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44AddDelIdentityMappingReply{}) + err = natHandler.AddNat44IdentityMapping(mapping, "DNAT 2") + + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok := ctx.MockChannel.Msg.(*vpp_nat_ed.Nat44AddDelIdentityMapping) + Expect(ok).To(BeTrue()) + Expect(msg.Tag).To(BeEquivalentTo("DNAT 2")) + Expect(addressTo4IP(msg.IPAddress)).To(BeEquivalentTo(address.String())) + Expect(msg.Port).To(BeEquivalentTo(8989)) + Expect(msg.SwIfIndex).To(BeEquivalentTo(vpp2306.NoInterface)) + Expect(msg.Flags).To(BeEquivalentTo(0)) +} + +func TestAddNat44EiIdentityMappingNoInterface(t *testing.T) { + ctx, natHandler, _, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: false}) + Expect(err).ShouldNot(HaveOccurred()) + + address := net.ParseIP("10.0.0.1").To4() + + mapping := &nat.DNat44_IdentityMapping{ + VrfId: 1, + Protocol: nat.DNat44_UDP, + IpAddress: address.String(), + Port: 8989, + } + + ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiAddDelIdentityMappingReply{}) + err = natHandler.AddNat44IdentityMapping(mapping, "DNAT 2") + + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok := ctx.MockChannel.Msg.(*vpp_nat_ei.Nat44EiAddDelIdentityMapping) + Expect(ok).To(BeTrue()) + Expect(msg.Tag).To(BeEquivalentTo("DNAT 2")) + Expect(addressTo4IP(msg.IPAddress)).To(BeEquivalentTo(address.String())) + Expect(msg.Port).To(BeEquivalentTo(8989)) + Expect(msg.SwIfIndex).To(BeEquivalentTo(vpp2306.NoInterface)) + Expect(msg.Flags).To(BeEquivalentTo(0)) +} + +func TestAddNat44EdIdentityMappingError(t *testing.T) { + ctx, natHandler, _, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44EdPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: true}) + Expect(err).ShouldNot(HaveOccurred()) + + // Incorrect reply object + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44AddDelStaticMappingV2Reply{}) + err = natHandler.AddNat44IdentityMapping(&nat.DNat44_IdentityMapping{}, "") + + Expect(err).Should(HaveOccurred()) +} + +func TestAddNat44EiIdentityMappingError(t *testing.T) { + ctx, natHandler, _, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: false}) + Expect(err).ShouldNot(HaveOccurred()) + + // Incorrect reply object + ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiAddDelStaticMappingReply{}) + err = natHandler.AddNat44IdentityMapping(&nat.DNat44_IdentityMapping{}, "") + + Expect(err).Should(HaveOccurred()) +} + +func TestAddNat44EdIdentityMappingRetval(t *testing.T) { + ctx, natHandler, _, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44EdPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: true}) + Expect(err).ShouldNot(HaveOccurred()) + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44AddDelIdentityMappingReply{ + Retval: 1, + }) + err = natHandler.AddNat44IdentityMapping(&nat.DNat44_IdentityMapping{}, "") + + Expect(err).Should(HaveOccurred()) +} + +func TestAddNat44IdentityMappingRetval(t *testing.T) { + ctx, natHandler, _, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: false}) + Expect(err).ShouldNot(HaveOccurred()) + + ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiAddDelIdentityMappingReply{ + Retval: 1, + }) + err = natHandler.AddNat44IdentityMapping(&nat.DNat44_IdentityMapping{}, "") + + Expect(err).Should(HaveOccurred()) +} + +func TestDelNat44EdIdentityMapping(t *testing.T) { + ctx, natHandler, swIfIndexes, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44EdPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: true}) + Expect(err).ShouldNot(HaveOccurred()) + + swIfIndexes.Put("if0", &ifaceidx.IfaceMetadata{SwIfIndex: 1}) + + address := net.ParseIP("10.0.0.1").To4() + + mapping := &nat.DNat44_IdentityMapping{ + Interface: "if0", + IpAddress: address.String(), + Protocol: nat.DNat44_TCP, + VrfId: 1, + } + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44AddDelIdentityMappingReply{}) + err = natHandler.DelNat44IdentityMapping(mapping, "DNAT 2") + + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok := ctx.MockChannel.Msg.(*vpp_nat_ed.Nat44AddDelIdentityMapping) + Expect(ok).To(BeTrue()) + Expect(msg.Tag).To(BeEquivalentTo("DNAT 2")) + Expect(msg.VrfID).To(BeEquivalentTo(1)) + Expect(addressTo4IP(msg.IPAddress)).To(BeEquivalentTo("0.0.0.0")) + Expect(msg.IsAdd).To(BeFalse()) + Expect(msg.SwIfIndex).To(BeEquivalentTo(1)) + Expect(msg.Protocol).To(BeEquivalentTo(6)) + Expect(msg.Flags).To(BeEquivalentTo(nat_types.NAT_IS_ADDR_ONLY)) +} + +func TestDelNat44EiIdentityMapping(t *testing.T) { + ctx, natHandler, swIfIndexes, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: false}) + Expect(err).ShouldNot(HaveOccurred()) + + swIfIndexes.Put("if0", &ifaceidx.IfaceMetadata{SwIfIndex: 1}) + + address := net.ParseIP("10.0.0.1").To4() + + mapping := &nat.DNat44_IdentityMapping{ + Interface: "if0", + IpAddress: address.String(), + Protocol: nat.DNat44_TCP, + VrfId: 1, + } + + ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiAddDelIdentityMappingReply{}) + err = natHandler.DelNat44IdentityMapping(mapping, "DNAT 2") + + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok := ctx.MockChannel.Msg.(*vpp_nat_ei.Nat44EiAddDelIdentityMapping) + Expect(ok).To(BeTrue()) + Expect(msg.Tag).To(BeEquivalentTo("DNAT 2")) + Expect(msg.VrfID).To(BeEquivalentTo(1)) + Expect(addressTo4IP(msg.IPAddress)).To(BeEquivalentTo("0.0.0.0")) + Expect(msg.IsAdd).To(BeFalse()) + Expect(msg.SwIfIndex).To(BeEquivalentTo(1)) + Expect(msg.Protocol).To(BeEquivalentTo(6)) + Expect(msg.Flags).To(BeEquivalentTo(nat_types.NAT_IS_ADDR_ONLY)) +} + +func TestNat44EdMappingLongTag(t *testing.T) { + ctx, natHandler, swIfIndexes, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44EdPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: true}) + Expect(err).ShouldNot(HaveOccurred()) + + swIfIndexes.Put("if0", &ifaceidx.IfaceMetadata{SwIfIndex: 1}) + + normalTag := "normalTag" + longTag := "some-weird-tag-which-is-much-longer-than-allowed-sixty-four-bytes" + + localIP1 := net.ParseIP("10.0.0.1").To4() + localIP2 := net.ParseIP("20.0.0.1").To4() + externalIP := net.ParseIP("10.0.0.2").To4() + + mapping := &nat.DNat44_StaticMapping{ + LocalIps: []*nat.DNat44_StaticMapping_LocalIP{ + { + LocalIp: localIP1.String(), + }, + }, + ExternalIp: externalIP.String(), + } + lbMapping := &nat.DNat44_StaticMapping{ + LocalIps: localIPs(localIP1, localIP2), + ExternalIp: externalIP.String(), + ExternalPort: 8080, + Protocol: nat.DNat44_TCP, + TwiceNat: nat.DNat44_StaticMapping_ENABLED, + } + idMapping := &nat.DNat44_IdentityMapping{ + IpAddress: localIP1.String(), + Protocol: nat.DNat44_UDP, + VrfId: 1, + Interface: "if0", + } + + // 1. test + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44AddDelStaticMappingV2Reply{}) + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44AddDelLbStaticMappingReply{}) + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44AddDelIdentityMappingReply{}) + // 2. test + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44AddDelStaticMappingV2Reply{}) + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44AddDelLbStaticMappingReply{}) + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44AddDelIdentityMappingReply{}) + + // Successful scenario (to ensure there is no other error) + err = natHandler.AddNat44StaticMapping(mapping, normalTag) + Expect(err).To(BeNil()) + err = natHandler.AddNat44StaticMapping(lbMapping, normalTag) + Expect(err).To(BeNil()) + err = natHandler.AddNat44IdentityMapping(idMapping, normalTag) + Expect(err).To(BeNil()) + + // Replace tags and test again + err = natHandler.AddNat44StaticMapping(mapping, longTag) + Expect(err).ToNot(BeNil()) + err = natHandler.AddNat44StaticMapping(lbMapping, longTag) + Expect(err).ToNot(BeNil()) + err = natHandler.AddNat44IdentityMapping(idMapping, longTag) + Expect(err).ToNot(BeNil()) +} + +func TestNat44EiMappingLongTag(t *testing.T) { + ctx, natHandler, swIfIndexes, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: false}) + Expect(err).ShouldNot(HaveOccurred()) + + swIfIndexes.Put("if0", &ifaceidx.IfaceMetadata{SwIfIndex: 1}) + + normalTag := "normalTag" + longTag := "some-weird-tag-which-is-much-longer-than-allowed-sixty-four-bytes" + + localIP1 := net.ParseIP("10.0.0.1").To4() + externalIP := net.ParseIP("10.0.0.2").To4() + + mapping := &nat.DNat44_StaticMapping{ + LocalIps: []*nat.DNat44_StaticMapping_LocalIP{ + { + LocalIp: localIP1.String(), + }, + }, + ExternalIp: externalIP.String(), + } + idMapping := &nat.DNat44_IdentityMapping{ + IpAddress: localIP1.String(), + Protocol: nat.DNat44_UDP, + VrfId: 1, + Interface: "if0", + } + + // 1. test + ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiAddDelStaticMappingReply{}) + ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiAddDelIdentityMappingReply{}) + // 2. test + ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiAddDelStaticMappingReply{}) + ctx.MockVpp.MockReply(&vpp_nat_ei.Nat44EiAddDelIdentityMappingReply{}) + + // Successful scenario (to ensure there is no other error) + err = natHandler.AddNat44StaticMapping(mapping, normalTag) + Expect(err).To(BeNil()) + err = natHandler.AddNat44IdentityMapping(idMapping, normalTag) + Expect(err).To(BeNil()) + + // Replace tags and test again + err = natHandler.AddNat44StaticMapping(mapping, longTag) + Expect(err).ToNot(BeNil()) + err = natHandler.AddNat44IdentityMapping(idMapping, longTag) + Expect(err).ToNot(BeNil()) +} + +func TestAddNat44VrfTable(t *testing.T) { + ctx, natHandler, swIfIndexes, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44EdPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: true}) + Expect(err).ShouldNot(HaveOccurred()) + + swIfIndexes.Put("if0", &ifaceidx.IfaceMetadata{SwIfIndex: 1}) + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44EdAddDelVrfTableReply{}) + err = natHandler.AddNat44VrfTable(0) + + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok := ctx.MockChannel.Msg.(*vpp_nat_ed.Nat44EdAddDelVrfTable) + Expect(ok).To(BeTrue()) + Expect(msg.TableVrfID).To(BeEquivalentTo(0)) + Expect(msg.IsAdd).To(BeTrue()) +} + +func TestDelNat44Table(t *testing.T) { + ctx, natHandler, swIfIndexes, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44EdPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: true}) + Expect(err).ShouldNot(HaveOccurred()) + + swIfIndexes.Put("if0", &ifaceidx.IfaceMetadata{SwIfIndex: 1}) + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44EdAddDelVrfTableReply{}) + err = natHandler.DelNat44VrfTable(0) + + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok := ctx.MockChannel.Msg.(*vpp_nat_ed.Nat44EdAddDelVrfTable) + Expect(ok).To(BeTrue()) + Expect(msg.TableVrfID).To(BeEquivalentTo(0)) + Expect(msg.IsAdd).To(BeFalse()) +} + +func TestAddNat44VrfRoute(t *testing.T) { + ctx, natHandler, swIfIndexes, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44EdPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: true}) + Expect(err).ShouldNot(HaveOccurred()) + + swIfIndexes.Put("if0", &ifaceidx.IfaceMetadata{SwIfIndex: 1}) + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44EdAddDelVrfRouteReply{}) + vrfRoute := nat.Nat44VrfRoute{ + SrcVrfId: 0, + DestVrfId: 1, + } + err = natHandler.AddNat44VrfRoute(vrfRoute.SrcVrfId, vrfRoute.DestVrfId) + + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok := ctx.MockChannel.Msg.(*vpp_nat_ed.Nat44EdAddDelVrfRoute) + Expect(ok).To(BeTrue()) + Expect(msg.VrfID).To(BeEquivalentTo(1)) + Expect(msg.TableVrfID).To(BeEquivalentTo(0)) + Expect(msg.IsAdd).To(BeTrue()) +} + +func TestDelNat44VrfRoute(t *testing.T) { + ctx, natHandler, swIfIndexes, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44EdPluginEnableDisableReply{}) + err := natHandler.EnableNAT44Plugin(vppcalls.Nat44InitOpts{EndpointDependent: true}) + Expect(err).ShouldNot(HaveOccurred()) + + swIfIndexes.Put("if0", &ifaceidx.IfaceMetadata{SwIfIndex: 1}) + + ctx.MockVpp.MockReply(&vpp_nat_ed.Nat44EdAddDelVrfRouteReply{}) + vrfRoute := nat.Nat44VrfRoute{ + SrcVrfId: 0, + DestVrfId: 1, + } + err = natHandler.DelNat44VrfRoute(vrfRoute.SrcVrfId, vrfRoute.DestVrfId) + + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok := ctx.MockChannel.Msg.(*vpp_nat_ed.Nat44EdAddDelVrfRoute) + Expect(ok).To(BeTrue()) + Expect(msg.TableVrfID).To(BeEquivalentTo(0)) + Expect(msg.IsAdd).To(BeFalse()) +} + +func localIPs(addr1, addr2 net.IP) []*nat.DNat44_StaticMapping_LocalIP { + return []*nat.DNat44_StaticMapping_LocalIP{ + { + LocalIp: addr1.String(), + LocalPort: 8080, + Probability: 35, + }, + { + LocalIp: addr2.String(), + LocalPort: 8181, + Probability: 65, + }, + } +} + +func addressTo4IP(address ip_types.IP4Address) string { + ipAddr := make(net.IP, net.IPv4len) + copy(ipAddr[:], address[:]) + if ipAddr.To4() == nil { + return "" + } + return ipAddr.To4().String() +} diff --git a/plugins/vpp/natplugin/vppcalls/vpp2306/vppcalls_handler.go b/plugins/vpp/natplugin/vppcalls/vpp2306/vppcalls_handler.go new file mode 100644 index 0000000000..00ced41f93 --- /dev/null +++ b/plugins/vpp/natplugin/vppcalls/vpp2306/vppcalls_handler.go @@ -0,0 +1,63 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + govppapi "go.fd.io/govpp/api" + "go.ligato.io/cn-infra/v2/idxmap" + "go.ligato.io/cn-infra/v2/logging" + + "go.ligato.io/vpp-agent/v3/plugins/vpp" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306" + vpp_nat_ed "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/nat44_ed" + vpp_nat_ei "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/nat44_ei" + "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/ifaceidx" + "go.ligato.io/vpp-agent/v3/plugins/vpp/natplugin/vppcalls" +) + +func init() { + var msgs []govppapi.Message + msgs = append(msgs, vpp_nat_ed.AllMessages()...) + msgs = append(msgs, vpp_nat_ei.AllMessages()...) + + vppcalls.AddNatHandlerVersion(vpp2306.Version, msgs, NewNatVppHandler) +} + +// NatVppHandler is accessor for NAT-related vppcalls methods. +type NatVppHandler struct { + callsChannel govppapi.Channel + natEd vpp_nat_ed.RPCService + natEi vpp_nat_ei.RPCService + ifIndexes ifaceidx.IfaceMetadataIndex + dhcpIndex idxmap.NamedMapping + log logging.Logger + ed bool +} + +// NewNatVppHandler creates new instance of NAT vppcalls handler. +func NewNatVppHandler(c vpp.Client, + ifIndexes ifaceidx.IfaceMetadataIndex, dhcpIndex idxmap.NamedMapping, log logging.Logger, +) vppcalls.NatVppAPI { + callsChan, _ := c.NewAPIChannel() + return &NatVppHandler{ + callsChannel: callsChan, + natEd: vpp_nat_ed.NewServiceClient(c), + natEi: vpp_nat_ei.NewServiceClient(c), + ifIndexes: ifIndexes, + dhcpIndex: dhcpIndex, + log: log, + ed: true, + } +} diff --git a/plugins/vpp/puntplugin/puntplugin.go b/plugins/vpp/puntplugin/puntplugin.go index 6ed006428e..c31cf34dcc 100644 --- a/plugins/vpp/puntplugin/puntplugin.go +++ b/plugins/vpp/puntplugin/puntplugin.go @@ -37,6 +37,7 @@ import ( _ "go.ligato.io/vpp-agent/v3/plugins/vpp/puntplugin/vppcalls/vpp2202" _ "go.ligato.io/vpp-agent/v3/plugins/vpp/puntplugin/vppcalls/vpp2210" + _ "go.ligato.io/vpp-agent/v3/plugins/vpp/puntplugin/vppcalls/vpp2306" ) // PuntPlugin configures VPP punt to host or unix domain socket entries and IP redirect entries using GoVPP. diff --git a/plugins/vpp/puntplugin/vppcalls/vpp2306/dump_vppcalls.go b/plugins/vpp/puntplugin/vppcalls/vpp2306/dump_vppcalls.go new file mode 100644 index 0000000000..d3a9d00afd --- /dev/null +++ b/plugins/vpp/puntplugin/vppcalls/vpp2306/dump_vppcalls.go @@ -0,0 +1,247 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "net" + "strings" + + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface_types" + vpp_ip "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip_types" + vpp_punt "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/punt" + "go.ligato.io/vpp-agent/v3/plugins/vpp/puntplugin/vppcalls" + punt "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/punt" +) + +// DumpPuntRedirect dumps ip redirect punts +func (h *PuntVppHandler) DumpPuntRedirect() (punts []*punt.IPRedirect, err error) { + punt4, err := h.dumpPuntRedirect(false) + if err != nil { + return nil, err + } + punts = append(punts, punt4...) + + punt6, err := h.dumpPuntRedirect(true) + if err != nil { + return nil, err + } + punts = append(punts, punt6...) + + return punts, nil +} + +func (h *PuntVppHandler) dumpPuntRedirect(ipv6 bool) (punts []*punt.IPRedirect, err error) { + req := h.callsChannel.SendMultiRequest(&vpp_ip.IPPuntRedirectDump{ + SwIfIndex: ^interface_types.InterfaceIndex(0), + IsIPv6: ipv6, + }) + for { + d := &vpp_ip.IPPuntRedirectDetails{} + stop, err := req.ReceiveReply(d) + if stop { + break + } + if err != nil { + return nil, err + } + + rxIface, _, exists := h.ifIndexes.LookupBySwIfIndex(uint32(d.Punt.RxSwIfIndex)) + if !exists { + h.log.Warnf("RX interface (%v) not found", d.Punt.RxSwIfIndex) + continue + } + txIface, _, exists := h.ifIndexes.LookupBySwIfIndex(uint32(d.Punt.TxSwIfIndex)) + if !exists { + h.log.Warnf("TX interface (%v) not found", d.Punt.TxSwIfIndex) + continue + } + + var l3proto punt.L3Protocol + var nextHop string + + if d.Punt.Nh.Af == ip_types.ADDRESS_IP4 { + l3proto = punt.L3Protocol_IPV4 + addr := d.Punt.Nh.Un.GetIP4() + nextHop = net.IP(addr[:]).To4().String() + } else if d.Punt.Nh.Af == ip_types.ADDRESS_IP6 { + l3proto = punt.L3Protocol_IPV6 + addr := d.Punt.Nh.Un.GetIP6() + nextHop = net.IP(addr[:]).To16().String() + } else { + h.log.Warnf("invalid address family (%v)", d.Punt.Nh.Af) + continue + } + + punts = append(punts, &punt.IPRedirect{ + L3Protocol: l3proto, + RxInterface: rxIface, + TxInterface: txIface, + NextHop: nextHop, + }) + } + + return punts, nil +} + +// DumpExceptions returns dump of registered punt exceptions. +func (h *PuntVppHandler) DumpExceptions() (punts []*vppcalls.ExceptionDetails, err error) { + reasons, err := h.dumpPuntReasons() + if err != nil { + return nil, err + } + reasonMap := make(map[uint32]string, len(reasons)) + for _, r := range reasons { + reasonMap[r.ID] = r.Reason.Name + } + + if punts, err = h.dumpPuntExceptions(reasonMap); err != nil { + return nil, err + } + + return punts, nil +} + +func (h *PuntVppHandler) dumpPuntExceptions(reasons map[uint32]string) (punts []*vppcalls.ExceptionDetails, err error) { + req := h.callsChannel.SendMultiRequest(&vpp_punt.PuntSocketDump{ + Type: vpp_punt.PUNT_API_TYPE_EXCEPTION, + }) + for { + d := &vpp_punt.PuntSocketDetails{} + stop, err := req.ReceiveReply(d) + if stop { + break + } + if err != nil { + return nil, err + } + + if d.Punt.Type != vpp_punt.PUNT_API_TYPE_EXCEPTION { + h.log.Warnf("VPP returned invalid punt type in exception punt dump: %v", d.Punt.Type) + continue + } + + puntData := d.Punt.Punt.GetException() + reason := reasons[puntData.ID] + socketPath := strings.Trim(d.Pathname, "\x00") + + punts = append(punts, &vppcalls.ExceptionDetails{ + Exception: &punt.Exception{ + Reason: reason, + SocketPath: vppConfigSocketPath, + }, + SocketPath: socketPath, + }) + } + + return punts, nil +} + +// DumpRegisteredPuntSockets returns punt to host via registered socket entries +func (h *PuntVppHandler) DumpRegisteredPuntSockets() (punts []*vppcalls.PuntDetails, err error) { + if punts, err = h.dumpPuntL4(); err != nil { + return nil, err + } + + return punts, nil +} + +func (h *PuntVppHandler) dumpPuntL4() (punts []*vppcalls.PuntDetails, err error) { + req := h.callsChannel.SendMultiRequest(&vpp_punt.PuntSocketDump{ + Type: vpp_punt.PUNT_API_TYPE_L4, + }) + for { + d := &vpp_punt.PuntSocketDetails{} + stop, err := req.ReceiveReply(d) + if stop { + break + } + if err != nil { + return nil, err + } + + if d.Punt.Type != vpp_punt.PUNT_API_TYPE_L4 { + h.log.Warnf("VPP returned invalid punt type in L4 punt dump: %v", d.Punt.Type) + continue + } + + puntData := d.Punt.Punt.GetL4() + socketPath := strings.Trim(d.Pathname, "\x00") + + punts = append(punts, &vppcalls.PuntDetails{ + PuntData: &punt.ToHost{ + Port: uint32(puntData.Port), + L3Protocol: parseL3Proto(puntData.Af), + L4Protocol: parseL4Proto(puntData.Protocol), + SocketPath: vppConfigSocketPath, + }, + SocketPath: socketPath, + }) + } + + return punts, nil +} + +// DumpPuntReasons returns all known punt reasons from VPP +func (h *PuntVppHandler) DumpPuntReasons() (reasons []*vppcalls.ReasonDetails, err error) { + if reasons, err = h.dumpPuntReasons(); err != nil { + return nil, err + } + + return reasons, nil +} + +func (h *PuntVppHandler) dumpPuntReasons() (reasons []*vppcalls.ReasonDetails, err error) { + req := h.callsChannel.SendMultiRequest(&vpp_punt.PuntReasonDump{}) + for { + d := &vpp_punt.PuntReasonDetails{} + stop, err := req.ReceiveReply(d) + if stop { + break + } + if err != nil { + return nil, err + } + + reasons = append(reasons, &vppcalls.ReasonDetails{ + Reason: &punt.Reason{ + Name: d.Reason.Name, + }, + ID: d.Reason.ID, + }) + } + + return reasons, nil +} + +func parseL3Proto(p ip_types.AddressFamily) punt.L3Protocol { + switch p { + case ip_types.ADDRESS_IP4: + return punt.L3Protocol_IPV4 + case ip_types.ADDRESS_IP6: + return punt.L3Protocol_IPV6 + } + return punt.L3Protocol_UNDEFINED_L3 +} + +func parseL4Proto(p ip_types.IPProto) punt.L4Protocol { + switch p { + case ip_types.IP_API_PROTO_TCP: + return punt.L4Protocol_TCP + case ip_types.IP_API_PROTO_UDP: + return punt.L4Protocol_UDP + } + return punt.L4Protocol_UNDEFINED_L4 +} diff --git a/plugins/vpp/puntplugin/vppcalls/vpp2306/punt_vppcalls.go b/plugins/vpp/puntplugin/vppcalls/vpp2306/punt_vppcalls.go new file mode 100644 index 0000000000..724b57e347 --- /dev/null +++ b/plugins/vpp/puntplugin/vppcalls/vpp2306/punt_vppcalls.go @@ -0,0 +1,287 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "fmt" + "strings" + + "github.com/pkg/errors" + + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface_types" + vpp_ip "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip_types" + vpp_punt "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/punt" + punt "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/punt" +) + +const PuntSocketHeaderVersion = 1 + +// Socket path from the VPP startup config file, returned when a punt socket +// is retrieved. Limited to single entry as supported in the VPP. +var vppConfigSocketPath string + +// AddPunt configures new punt entry +func (h *PuntVppHandler) AddPunt(p *punt.ToHost) error { + return errors.Errorf("passive punt add is currently not available") +} + +// DeletePunt removes punt entry +func (h *PuntVppHandler) DeletePunt(p *punt.ToHost) error { + return errors.Errorf("passive punt del is currently not available") +} + +// AddPuntException adds new punt exception entry +func (h *PuntVppHandler) AddPuntException(p *punt.Exception) (string, error) { + return h.addDelPuntException(p, true) +} + +// DeletePuntException removes punt exception entry +func (h *PuntVppHandler) DeletePuntException(p *punt.Exception) error { + _, err := h.addDelPuntException(p, false) + return err +} + +func (h *PuntVppHandler) addDelPuntException(p *punt.Exception, isAdd bool) (pathName string, err error) { + reasons, err := h.dumpPuntReasons() + if err != nil { + return "", fmt.Errorf("dumping punt reasons failed: %v", err) + } + + h.log.Debugf("dumped %d punt reasons: %+v", len(reasons), reasons) + + var reasonID *uint32 + for _, r := range reasons { + if r.Reason.Name == p.Reason { + id := r.ID + reasonID = &id + break + } + } + if reasonID == nil { + return "", fmt.Errorf("punt reason %q not found", p.Reason) + } + + baPunt := getPuntExceptionConfig(*reasonID) + + if isAdd { + h.log.Debugf("adding punt exception: %+v", p) + pathName, err = h.handleRegisterPuntSocket(baPunt, p.SocketPath) + if err != nil { + return "", err + } + } else { + err = h.handleDeregisterPuntSocket(baPunt) + if err != nil { + return "", err + } + } + + return pathName, nil +} + +// RegisterPuntSocket registers new punt to unix domain socket entry +func (h *PuntVppHandler) RegisterPuntSocket(p *punt.ToHost) (pathName string, err error) { + ipProto := resolveL4Proto(p.L4Protocol) + + if p.L3Protocol == punt.L3Protocol_IPV4 || p.L3Protocol == punt.L3Protocol_ALL { + baPunt := getPuntL4Config(ip_types.ADDRESS_IP4, ipProto, uint16(p.Port)) + if pathName, err = h.handleRegisterPuntSocket(baPunt, p.SocketPath); err != nil { + return "", err + } + } + if p.L3Protocol == punt.L3Protocol_IPV6 || p.L3Protocol == punt.L3Protocol_ALL { + baPunt := getPuntL4Config(ip_types.ADDRESS_IP6, ipProto, uint16(p.Port)) + if pathName, err = h.handleRegisterPuntSocket(baPunt, p.SocketPath); err != nil { + return "", err + } + } + + return pathName, nil +} + +// DeregisterPuntSocket removes existing punt to socket registration +func (h *PuntVppHandler) DeregisterPuntSocket(p *punt.ToHost) error { + ipProto := resolveL4Proto(p.L4Protocol) + + if p.L3Protocol == punt.L3Protocol_IPV4 || p.L3Protocol == punt.L3Protocol_ALL { + baPunt := getPuntL4Config(ip_types.ADDRESS_IP4, ipProto, uint16(p.Port)) + if err := h.handleDeregisterPuntSocket(baPunt); err != nil { + return err + } + } + if p.L3Protocol == punt.L3Protocol_IPV6 || p.L3Protocol == punt.L3Protocol_ALL { + baPunt := getPuntL4Config(ip_types.ADDRESS_IP6, ipProto, uint16(p.Port)) + if err := h.handleDeregisterPuntSocket(baPunt); err != nil { + return err + } + } + + return nil +} + +func (h *PuntVppHandler) handleRegisterPuntSocket(punt vpp_punt.Punt, path string) (string, error) { + req := &vpp_punt.PuntSocketRegister{ + HeaderVersion: PuntSocketHeaderVersion, + Punt: punt, + Pathname: path, + } + reply := &vpp_punt.PuntSocketRegisterReply{} + + h.log.Debugf("registering punt socket: %+v (pathname: %s)", req.Punt, req.Pathname) + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return "", err + } + + // socket pathname from VPP config + pathName := strings.SplitN(string(reply.Pathname), "\x00", 2)[0] + + // VPP startup config socket path name is always the same + if vppConfigSocketPath != pathName { + h.log.Debugf("setting vpp punt socket path to: %q (%s)", pathName, vppConfigSocketPath) + vppConfigSocketPath = pathName + } + + return pathName, nil +} + +// DeregisterPuntSocket removes existing punt to socket registration +func (h *PuntVppHandler) handleDeregisterPuntSocket(punt vpp_punt.Punt) error { + req := &vpp_punt.PuntSocketDeregister{ + Punt: punt, + } + reply := &vpp_punt.PuntSocketDeregisterReply{} + + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + + return nil +} + +func getPuntExceptionConfig(reasonID uint32) vpp_punt.Punt { + p := vpp_punt.PuntException{ + ID: reasonID, + } + return vpp_punt.Punt{ + Type: vpp_punt.PUNT_API_TYPE_EXCEPTION, + Punt: vpp_punt.PuntUnionException(p), + } +} + +func getPuntL4Config(ipv ip_types.AddressFamily, ipProto ip_types.IPProto, port uint16) vpp_punt.Punt { + puntL4 := vpp_punt.PuntL4{ + Af: ipv, + Protocol: ipProto, + Port: port, + } + return vpp_punt.Punt{ + Type: vpp_punt.PUNT_API_TYPE_L4, + Punt: vpp_punt.PuntUnionL4(puntL4), + } +} + +// AddPuntRedirect adds new redirect entry +func (h *PuntVppHandler) AddPuntRedirect(puntCfg *punt.IPRedirect) error { + if puntCfg.L3Protocol == punt.L3Protocol_IPV4 || puntCfg.L3Protocol == punt.L3Protocol_ALL { + if err := h.handlePuntRedirectIPv4(puntCfg, true); err != nil { + return err + } + } + if puntCfg.L3Protocol == punt.L3Protocol_IPV6 || puntCfg.L3Protocol == punt.L3Protocol_ALL { + if err := h.handlePuntRedirectIPv6(puntCfg, true); err != nil { + return err + } + } + return nil +} + +// DeletePuntRedirect removes existing redirect entry +func (h *PuntVppHandler) DeletePuntRedirect(puntCfg *punt.IPRedirect) error { + if puntCfg.L3Protocol == punt.L3Protocol_IPV4 || puntCfg.L3Protocol == punt.L3Protocol_ALL { + if err := h.handlePuntRedirectIPv4(puntCfg, false); err != nil { + return err + } + } + if puntCfg.L3Protocol == punt.L3Protocol_IPV6 || puntCfg.L3Protocol == punt.L3Protocol_ALL { + if err := h.handlePuntRedirectIPv6(puntCfg, false); err != nil { + return err + } + } + return nil +} + +func (h *PuntVppHandler) handlePuntRedirectIPv4(punt *punt.IPRedirect, isAdd bool) error { + return h.handlePuntRedirect(punt, true, isAdd) +} + +func (h *PuntVppHandler) handlePuntRedirectIPv6(punt *punt.IPRedirect, isAdd bool) error { + return h.handlePuntRedirect(punt, false, isAdd) +} + +func (h *PuntVppHandler) handlePuntRedirect(punt *punt.IPRedirect, isIPv4, isAdd bool) error { + // rx interface + var rxIfIdx uint32 + if punt.RxInterface == "" { + rxIfIdx = ^uint32(0) + } else { + rxMetadata, exists := h.ifIndexes.LookupByName(punt.RxInterface) + if !exists { + return errors.Errorf("index not found for interface %s", punt.RxInterface) + } + rxIfIdx = rxMetadata.SwIfIndex + } + + // tx interface + txMetadata, exists := h.ifIndexes.LookupByName(punt.TxInterface) + if !exists { + return errors.Errorf("index not found for interface %s", punt.TxInterface) + } + + // next hop address + // - remove mask from IP address if necessary + nextHopStr := punt.NextHop + ipParts := strings.Split(punt.NextHop, "/") + if len(ipParts) > 1 { + h.log.Debugf("IP punt redirect next hop IP address %s is defined with mask, removing it") + nextHopStr = ipParts[0] + } + nextHop, err := ipToAddress(nextHopStr) + if err != nil { + return err + } + + req := &vpp_ip.IPPuntRedirect{ + IsAdd: isAdd, + Punt: vpp_ip.PuntRedirect{ + RxSwIfIndex: interface_types.InterfaceIndex(rxIfIdx), + TxSwIfIndex: interface_types.InterfaceIndex(txMetadata.SwIfIndex), + Nh: nextHop, + }, + } + reply := &vpp_ip.IPPuntRedirectReply{} + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + + return nil +} + +func resolveL4Proto(protocol punt.L4Protocol) ip_types.IPProto { + if protocol == punt.L4Protocol_UDP { + return ip_types.IP_API_PROTO_UDP + } + return ip_types.IP_API_PROTO_TCP +} diff --git a/plugins/vpp/puntplugin/vppcalls/vpp2306/punt_vppcalls_test.go b/plugins/vpp/puntplugin/vppcalls/vpp2306/punt_vppcalls_test.go new file mode 100644 index 0000000000..132dba42d2 --- /dev/null +++ b/plugins/vpp/puntplugin/vppcalls/vpp2306/punt_vppcalls_test.go @@ -0,0 +1,224 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306_test + +import ( + "testing" + + . "github.com/onsi/gomega" + "go.ligato.io/cn-infra/v2/logging/logrus" + + vpp_ip "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip_types" + vpp_punt "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/punt" + "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/ifaceidx" + "go.ligato.io/vpp-agent/v3/plugins/vpp/puntplugin/vppcalls" + vpp2306 "go.ligato.io/vpp-agent/v3/plugins/vpp/puntplugin/vppcalls/vpp2306" + "go.ligato.io/vpp-agent/v3/plugins/vpp/vppmock" + punt "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/punt" +) + +// TODO test below temporary disabled (re-enable with set_punt) +/* +func TestAddPunt(t *testing.T) { + ctx, puntHandler, _ := puntTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&ba_punt.SetPuntReply{}) + + err := puntHandler.AddPunt(&punt.ToHost{ + L3Protocol: punt.L3Protocol_IPv4, + L4Protocol: punt.L4Protocol_UDP, + Port: 9000, + }) + + Expect(err).To(BeNil()) + vppMsg, ok := ctx.MockChannel.Msg.(*ba_punt.SetPunt) + Expect(ok).To(BeTrue()) + Expect(vppMsg.IsAdd).To(Equal(uint8(1))) + Expect(vppMsg.Punt.IPv).To(Equal(uint8(4))) + Expect(vppMsg.Punt.L4Protocol).To(Equal(uint8(17))) + Expect(vppMsg.Punt.L4Port).To(Equal(uint16(9000))) +} + +func TestDeletePunt(t *testing.T) { + ctx, puntHandler, _ := puntTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&ba_punt.SetPuntReply{}) + + err := puntHandler.DeletePunt(&punt.ToHost{ + L3Protocol: punt.L3Protocol_IPv4, + L4Protocol: punt.L4Protocol_UDP, + Port: 9000, + }) + + Expect(err).To(BeNil()) + vppMsg, ok := ctx.MockChannel.Msg.(*ba_punt.SetPunt) + Expect(ok).To(BeTrue()) + Expect(vppMsg.IsAdd).To(Equal(uint8(0))) + Expect(vppMsg.Punt.IPv).To(Equal(uint8(4))) + Expect(vppMsg.Punt.L4Protocol).To(Equal(uint8(17))) + Expect(vppMsg.Punt.L4Port).To(Equal(uint16(9000))) +} +*/ + +func TestRegisterPuntSocket(t *testing.T) { + ctx, puntHandler, _ := puntTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_punt.PuntSocketRegisterReply{ + Pathname: "/othersock", + }) + + path, err := puntHandler.RegisterPuntSocket(&punt.ToHost{ + L3Protocol: punt.L3Protocol_IPV4, + L4Protocol: punt.L4Protocol_UDP, + Port: 9000, + SocketPath: "/test/path/socket", + }) + + Expect(err).To(BeNil()) + vppMsg, ok := ctx.MockChannel.Msg.(*vpp_punt.PuntSocketRegister) + Expect(ok).To(BeTrue()) + Expect(vppMsg.HeaderVersion).To(Equal(uint32(1))) + Expect(vppMsg.Punt.Punt.GetL4().Af).To(Equal(ip_types.ADDRESS_IP4)) + Expect(vppMsg.Punt.Punt.GetL4().Protocol).To(Equal(ip_types.IP_API_PROTO_UDP)) + Expect(vppMsg.Punt.Punt.GetL4().Port).To(Equal(uint16(9000))) + Expect(path).To(Equal("/othersock")) +} + +func TestRegisterPuntSocketAllIPv4(t *testing.T) { + ctx, puntHandler, _ := puntTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_punt.PuntSocketRegisterReply{ + Pathname: "/othersock", + }) + ctx.MockVpp.MockReply(&vpp_punt.PuntSocketRegisterReply{ + Pathname: "/othersock", + }) + + path, err := puntHandler.RegisterPuntSocket(&punt.ToHost{ + L3Protocol: punt.L3Protocol_ALL, + L4Protocol: punt.L4Protocol_UDP, + Port: 9000, + SocketPath: "/test/path/socket", + }) + + Expect(err).To(BeNil()) + for _, msg := range ctx.MockChannel.Msgs { + vppMsg, ok := msg.(*vpp_punt.PuntSocketRegister) + Expect(ok).To(BeTrue()) + + if vppMsg.Punt.Punt.GetL4().Af == ip_types.ADDRESS_IP4 { + Expect(vppMsg.HeaderVersion).To(Equal(uint32(1))) + Expect(vppMsg.Punt.Punt.GetL4().Protocol).To(Equal(ip_types.IP_API_PROTO_UDP)) + Expect(vppMsg.Punt.Punt.GetL4().Port).To(Equal(uint16(9000))) + Expect(path).To(Equal("/othersock")) + } + if vppMsg.Punt.Punt.GetL4().Af == ip_types.ADDRESS_IP6 { + Expect(vppMsg.HeaderVersion).To(Equal(uint32(1))) + Expect(vppMsg.Punt.Punt.GetL4().Protocol).To(Equal(ip_types.IP_API_PROTO_UDP)) + Expect(vppMsg.Punt.Punt.GetL4().Port).To(Equal(uint16(9000))) + Expect(path).To(Equal("/othersock")) + } + } +} + +func TestAddIPRedirect(t *testing.T) { + ctx, puntHandler, ifIndexes := puntTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ip.IPPuntRedirectReply{}) + + ifIndexes.Put("if1", &ifaceidx.IfaceMetadata{SwIfIndex: 1}) + ifIndexes.Put("if2", &ifaceidx.IfaceMetadata{SwIfIndex: 2}) + + err := puntHandler.AddPuntRedirect(&punt.IPRedirect{ + L3Protocol: punt.L3Protocol_IPV4, + RxInterface: "if1", + TxInterface: "if2", + NextHop: "10.0.0.1", + }) + + Expect(err).To(BeNil()) + vppMsg, ok := ctx.MockChannel.Msg.(*vpp_ip.IPPuntRedirect) + Expect(ok).To(BeTrue()) + Expect(vppMsg.IsAdd).To(BeTrue()) + Expect(vppMsg.Punt.Nh.Af).To(Equal(ip_types.ADDRESS_IP4)) + Expect(vppMsg.Punt.RxSwIfIndex).To(BeEquivalentTo(uint32(1))) + Expect(vppMsg.Punt.TxSwIfIndex).To(BeEquivalentTo(uint32(2))) + //Expect(vppMsg.Nh).To(Equal([]uint8(net.ParseIP("10.0.0.1").To4()))) +} + +func TestAddIPRedirectAll(t *testing.T) { + ctx, puntHandler, ifIndexes := puntTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ip.IPPuntRedirectReply{}) + + ifIndexes.Put("if1", &ifaceidx.IfaceMetadata{SwIfIndex: 1}) + + err := puntHandler.AddPuntRedirect(&punt.IPRedirect{ + L3Protocol: punt.L3Protocol_IPV4, + TxInterface: "if1", + NextHop: "30.0.0.1", + }) + + Expect(err).To(BeNil()) + vppMsg, ok := ctx.MockChannel.Msg.(*vpp_ip.IPPuntRedirect) + Expect(ok).To(BeTrue()) + Expect(vppMsg.IsAdd).To(BeTrue()) + Expect(vppMsg.Punt.Nh.Af).To(Equal(ip_types.ADDRESS_IP4)) + Expect(vppMsg.Punt.RxSwIfIndex).To(BeEquivalentTo(^uint32(0))) + Expect(vppMsg.Punt.TxSwIfIndex).To(BeEquivalentTo(uint32(1))) + //Expect(vppMsg.Nh).To(Equal([]uint8(net.ParseIP("30.0.0.1").To4()))) +} + +func TestDeleteIPRedirect(t *testing.T) { + ctx, puntHandler, ifIndexes := puntTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_ip.IPPuntRedirectReply{}) + + ifIndexes.Put("if1", &ifaceidx.IfaceMetadata{SwIfIndex: 1}) + ifIndexes.Put("if2", &ifaceidx.IfaceMetadata{SwIfIndex: 2}) + + err := puntHandler.DeletePuntRedirect(&punt.IPRedirect{ + L3Protocol: punt.L3Protocol_IPV4, + RxInterface: "if1", + TxInterface: "if2", + NextHop: "10.0.0.1", + }) + + Expect(err).To(BeNil()) + vppMsg, ok := ctx.MockChannel.Msg.(*vpp_ip.IPPuntRedirect) + Expect(ok).To(BeTrue()) + Expect(vppMsg.IsAdd).To(BeFalse()) + //Expect(vppMsg.IsIP6).To(Equal(uint8(0))) + Expect(vppMsg.Punt.Nh.Af).To(Equal(ip_types.ADDRESS_IP4)) + Expect(vppMsg.Punt.RxSwIfIndex).To(BeEquivalentTo(uint32(1))) + Expect(vppMsg.Punt.TxSwIfIndex).To(BeEquivalentTo(uint32(2))) + //Expect(vppMsg.Nh).To(Equal([]uint8(net.ParseIP("10.0.0.1").To4()))) +} + +func puntTestSetup(t *testing.T) (*vppmock.TestCtx, vppcalls.PuntVppAPI, ifaceidx.IfaceMetadataIndexRW) { + ctx := vppmock.SetupTestCtx(t) + logger := logrus.NewLogger("test-log") + ifIndexes := ifaceidx.NewIfaceIndex(logger, "punt-if-idx") + puntHandler := vpp2306.NewPuntVppHandler(ctx.MockChannel, ifIndexes, logrus.DefaultLogger()) + return ctx, puntHandler, ifIndexes +} diff --git a/plugins/vpp/puntplugin/vppcalls/vpp2306/vppcalls_handler.go b/plugins/vpp/puntplugin/vppcalls/vpp2306/vppcalls_handler.go new file mode 100644 index 0000000000..b990a2d083 --- /dev/null +++ b/plugins/vpp/puntplugin/vppcalls/vpp2306/vppcalls_handler.go @@ -0,0 +1,75 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "fmt" + "net" + + govppapi "go.fd.io/govpp/api" + "go.ligato.io/cn-infra/v2/logging" + + vpp2306 "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306" + vpp_ip "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip_types" + vpp_punt "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/punt" + "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/ifaceidx" + "go.ligato.io/vpp-agent/v3/plugins/vpp/puntplugin/vppcalls" +) + +func init() { + var msgs []govppapi.Message + msgs = append(msgs, vpp_ip.AllMessages()...) + msgs = append(msgs, vpp_punt.AllMessages()...) + + vppcalls.AddHandlerVersion(vpp2306.Version, msgs, NewPuntVppHandler) +} + +// PuntVppHandler is accessor for punt-related vppcalls methods. +type PuntVppHandler struct { + callsChannel govppapi.Channel + ifIndexes ifaceidx.IfaceMetadataIndex + log logging.Logger +} + +// NewPuntVppHandler creates new instance of punt vppcalls handler +func NewPuntVppHandler( + callsChan govppapi.Channel, ifIndexes ifaceidx.IfaceMetadataIndex, log logging.Logger, +) vppcalls.PuntVppAPI { + return &PuntVppHandler{ + callsChannel: callsChan, + ifIndexes: ifIndexes, + log: log, + } +} + +func ipToAddress(ipstr string) (addr ip_types.Address, err error) { + netIP := net.ParseIP(ipstr) + if netIP == nil { + return ip_types.Address{}, fmt.Errorf("invalid IP: %q", ipstr) + } + if ip4 := netIP.To4(); ip4 == nil { + addr.Af = ip_types.ADDRESS_IP6 + var ip6addr ip_types.IP6Address + copy(ip6addr[:], netIP.To16()) + addr.Un.SetIP6(ip6addr) + } else { + addr.Af = ip_types.ADDRESS_IP4 + var ip4addr ip_types.IP4Address + copy(ip4addr[:], ip4) + addr.Un.SetIP4(ip4addr) + } + return +} diff --git a/plugins/vpp/srplugin/srplugin.go b/plugins/vpp/srplugin/srplugin.go index 7d2079d5dc..600bbde79c 100644 --- a/plugins/vpp/srplugin/srplugin.go +++ b/plugins/vpp/srplugin/srplugin.go @@ -31,6 +31,7 @@ import ( _ "go.ligato.io/vpp-agent/v3/plugins/vpp/srplugin/vppcalls/vpp2202" _ "go.ligato.io/vpp-agent/v3/plugins/vpp/srplugin/vppcalls/vpp2210" + _ "go.ligato.io/vpp-agent/v3/plugins/vpp/srplugin/vppcalls/vpp2306" ) // SRPlugin configures segment routing. diff --git a/plugins/vpp/srplugin/vppcalls/vpp2306/srv6.go b/plugins/vpp/srplugin/vppcalls/vpp2306/srv6.go new file mode 100644 index 0000000000..ce0abc4a02 --- /dev/null +++ b/plugins/vpp/srplugin/vppcalls/vpp2306/srv6.go @@ -0,0 +1,651 @@ +// Copyright (c) 2022 Bell Canada, Pantheon Technologies and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package vpp2306 contains wrappers over VPP binary APIs to simplify their usage +package vpp2306 + +import ( + "context" + "fmt" + "net" + "regexp" + "strconv" + "strings" + + "go.ligato.io/cn-infra/v2/logging" + + vpp_ifs "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface_types" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip_types" + vpp_sr "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/sr" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/sr_types" + vpp2306 "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/vppcalls/vpp2306" + ifs "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/interfaces" + srv6 "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/srv6" +) + +// Constants for behavior function hardcoded into VPP (there can be also custom behavior functions implemented as VPP plugins) +// Constants are taken from VPP's vnet/srv6/sr.h (names are modified to Golang from original C form in VPP code) +const ( + BehaviorEnd = iota + 1 // Behavior of simple endpoint + BehaviorX // Behavior of endpoint with Layer-3 cross-connect + BehaviorT // Behavior of endpoint with specific IPv6 table lookup + BehaviorDfirst // Unused. Separator in between regular and D + BehaviorDX2 // Behavior of endpoint with decapulation and Layer-2 cross-connect (or DX2 with egress VLAN rewrite when VLAN notzero - not supported this variant yet) + BehaviorDX6 // Behavior of endpoint with decapsulation and IPv6 cross-connect + BehaviorDX4 // Behavior of endpoint with decapsulation and IPv4 cross-connect + BehaviorDT6 // Behavior of endpoint with decapsulation and specific IPv6 table lookup + BehaviorDT4 // Behavior of endpoint with decapsulation and specific IPv4 table lookup + BehaviorLast // seems unused, note in VPP: "Must always be the last one" +) + +// Constants for steering type +// Constants are taken from VPP's vnet/srv6/sr.h (names are modified to Golang from original C form in VPP code) +const ( + SteerTypeL2 = 2 + SteerTypeIPv4 = 4 + SteerTypeIPv6 = 6 +) + +// Constants for operation of SR policy modify binary API method +const ( + AddSRList = iota + 1 // Add SR List to an existing SR policy + DeleteSRList // Delete SR List from an existing SR policy + ModifyWeightOfSRList // Modify the weight of an existing SR List +) + +// AddLocalSid adds local sid into VPP +func (h *SRv6VppHandler) AddLocalSid(localSID *srv6.LocalSID) error { + return h.addDelLocalSid(false, localSID) +} + +// DeleteLocalSid deletes local sid in VPP +func (h *SRv6VppHandler) DeleteLocalSid(localSID *srv6.LocalSID) error { + return h.addDelLocalSid(true, localSID) +} + +func (h *SRv6VppHandler) addDelLocalSid(deletion bool, localSID *srv6.LocalSID) error { + h.log.WithFields(logging.Fields{"localSID": localSID.GetSid(), "delete": deletion, "installationVrfID": h.installationVrfID(localSID), "end function": h.endFunction(localSID)}). + Debug("Adding/deleting Local SID", localSID.GetSid()) + sidAddr, err := parseIPv6(localSID.GetSid()) // parsing to get some standard sid form + if err != nil { + return fmt.Errorf("sid address %s is not IPv6 address: %v", localSID.GetSid(), err) // calls from descriptor are already validated + } + if !deletion && localSID.GetEndFunctionAd() != nil { + return h.addSRProxy(sidAddr, localSID) + } + var localsid ip_types.IP6Address + copy(localsid[:], sidAddr.To16()) + + req := &vpp_sr.SrLocalsidAddDel{ + IsDel: deletion, + Localsid: localsid, + } + req.FibTable = localSID.InstallationVrfId // where to install localsid entry/from where to remove installed localsid entry + if !deletion { + if err := h.writeEndFunction(req, localSID); err != nil { + return err + } + } + reply := &vpp_sr.SrLocalsidAddDelReply{} + + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + if reply.Retval != 0 { + return fmt.Errorf("vpp call %q returned: %d", reply.GetMessageName(), reply.Retval) + } + + h.log.WithFields(logging.Fields{"localSID": sidAddr, "delete": deletion, "installationVrfID": h.installationVrfID(localSID), "end function": h.endFunction(localSID)}). + Debug("Added/deleted Local SID ", sidAddr) + + return nil +} + +// addSRProxy adds local sid with SR-proxy end function (End.AD). This functionality has no binary API in VPP, therefore +// CLI commands are used (VPE binary API that calls VPP's CLI). +func (h *SRv6VppHandler) addSRProxy(sidAddr net.IP, localSID *srv6.LocalSID) error { + // get VPP-internal names of IN and OUT interfaces + names, err := h.interfaceNameMapping() + if err != nil { + return fmt.Errorf("can't convert interface names from etcd to VPP-internal interface names:%v", err) + } + outInterface, found := names[localSID.GetEndFunctionAd().OutgoingInterface] + if !found { + return fmt.Errorf("can't find VPP-internal name for interface %v (name in etcd)", localSID.GetEndFunctionAd().OutgoingInterface) + } + inInterface, found := names[localSID.GetEndFunctionAd().IncomingInterface] + if !found { + return fmt.Errorf("can't find VPP-internal name for interface %v (name in etcd)", localSID.GetEndFunctionAd().IncomingInterface) + } + + // add SR-proxy using VPP CLI + var cmd string + if strings.TrimSpace(localSID.GetEndFunctionAd().L3ServiceAddress) == "" { // L2 service + cmd = fmt.Sprintf("sr localsid address %v fib-table %v behavior end.ad oif %v iif %v", sidAddr, localSID.InstallationVrfId, outInterface, inInterface) + } else { // L3 service + cmd = fmt.Sprintf("sr localsid address %v fib-table %v behavior end.ad nh %v oif %v iif %v", sidAddr, localSID.InstallationVrfId, localSID.GetEndFunctionAd().L3ServiceAddress, outInterface, inInterface) + } + data, err := h.RunCli(context.TODO(), cmd) + if err != nil { + return err + } + if len(strings.TrimSpace(string(data))) > 0 { + return fmt.Errorf("addition of dynamic segment routing proxy failed by returning nonblank space text in CLI: %v", string(data)) + } + return nil +} + +// interfaceNameMapping dumps from VPP internal names of interfaces and uses them to produce mapping from ligato interface names to vpp internal names. +func (h *SRv6VppHandler) interfaceNameMapping() (map[string]string, error) { + mapping := make(map[string]string) + reqCtx := h.callsChannel.SendMultiRequest(&vpp_ifs.SwInterfaceDump{}) + + for { + // get next interface info + ifDetails := &vpp_ifs.SwInterfaceDetails{} + stop, err := reqCtx.ReceiveReply(ifDetails) + if stop { + break // Break from the loop. + } + if err != nil { + return nil, fmt.Errorf("failed to dump interface: %v", err) + } + + // extract and compute names + ligatoName := strings.TrimRight(ifDetails.Tag, "\x00") + vppInternalName := strings.TrimRight(ifDetails.InterfaceName, "\x00") + if ifDetails.SupSwIfIndex == uint32(ifDetails.SwIfIndex) && // no subinterface (subinterface are not DPDK) + guessInterfaceType(strings.TrimRight(ifDetails.InterfaceName, "\x00")) == ifs.Interface_DPDK { + // fill name for physical interfaces (they are mostly without tag) + ligatoName = vppInternalName + } + + mapping[ligatoName] = vppInternalName + } + return mapping, nil +} + +func (h *SRv6VppHandler) installationVrfID(localSID *srv6.LocalSID) string { + if localSID != nil { + return fmt.Sprint(localSID.InstallationVrfId) + } + return "" +} + +func (h *SRv6VppHandler) endFunction(localSID *srv6.LocalSID) string { + switch ef := localSID.GetEndFunction().(type) { + case *srv6.LocalSID_BaseEndFunction: + return fmt.Sprintf("End{psp: %v}", ef.BaseEndFunction.Psp) + case *srv6.LocalSID_EndFunctionX: + return fmt.Sprintf("X{psp: %v, OutgoingInterface: %v, NextHop: %v}", ef.EndFunctionX.Psp, ef.EndFunctionX.OutgoingInterface, ef.EndFunctionX.NextHop) + case *srv6.LocalSID_EndFunctionT: + return fmt.Sprintf("T{psp: %v, vrf: %v}", ef.EndFunctionT.Psp, ef.EndFunctionT.VrfId) + case *srv6.LocalSID_EndFunctionDx2: + return fmt.Sprintf("DX2{VlanTag: %v, OutgoingInterface: %v}", ef.EndFunctionDx2.VlanTag, ef.EndFunctionDx2.OutgoingInterface) + case *srv6.LocalSID_EndFunctionDx4: + return fmt.Sprintf("DX4{OutgoingInterface: %v, NextHop: %v}", ef.EndFunctionDx4.OutgoingInterface, ef.EndFunctionDx4.NextHop) + case *srv6.LocalSID_EndFunctionDx6: + return fmt.Sprintf("DX6{OutgoingInterface: %v, NextHop: %v}", ef.EndFunctionDx6.OutgoingInterface, ef.EndFunctionDx6.NextHop) + case *srv6.LocalSID_EndFunctionDt4: + return fmt.Sprintf("DT4{vrf: %v}", ef.EndFunctionDt4.VrfId) + case *srv6.LocalSID_EndFunctionDt6: + return fmt.Sprintf("DT6{vrf: %v}", ef.EndFunctionDt6.VrfId) + case *srv6.LocalSID_EndFunctionAd: + return fmt.Sprintf("AD{L3ServiceAddress: %v, OutgoingInterface: %v, IncomingInterface: %v}", ef.EndFunctionAd.L3ServiceAddress, ef.EndFunctionAd.OutgoingInterface, ef.EndFunctionAd.IncomingInterface) + case nil: + return "" + default: + return "unknown end function" + } +} + +func (h *SRv6VppHandler) writeEndFunction(req *vpp_sr.SrLocalsidAddDel, localSID *srv6.LocalSID) error { + switch ef := localSID.EndFunction.(type) { + case *srv6.LocalSID_BaseEndFunction: + req.Behavior = BehaviorEnd + req.EndPsp = ef.BaseEndFunction.Psp + case *srv6.LocalSID_EndFunctionX: + req.Behavior = BehaviorX + req.EndPsp = ef.EndFunctionX.Psp + ifMeta, exists := h.ifIndexes.LookupByName(ef.EndFunctionX.OutgoingInterface) + if !exists { + return fmt.Errorf("for interface %v doesn't exist sw index", ef.EndFunctionX.OutgoingInterface) + } + req.SwIfIndex = interface_types.InterfaceIndex(ifMeta.SwIfIndex) + + nhIP, err := parseIPv6(ef.EndFunctionX.NextHop) // parses also ipv4 addresses but into ipv6 address form + if err != nil { + return err + } + nhAddr, err := IPToAddress(nhIP.String()) + if err != nil { + return err + } + req.NhAddr = nhAddr // ipv4 address in ipv6 address form? + case *srv6.LocalSID_EndFunctionT: + req.Behavior = BehaviorT + req.EndPsp = ef.EndFunctionT.Psp + req.SwIfIndex = interface_types.InterfaceIndex(ef.EndFunctionT.VrfId) + case *srv6.LocalSID_EndFunctionDx2: + req.Behavior = BehaviorDX2 + req.VlanIndex = ef.EndFunctionDx2.VlanTag + ifMeta, exists := h.ifIndexes.LookupByName(ef.EndFunctionDx2.OutgoingInterface) + if !exists { + return fmt.Errorf("for interface %v doesn't exist sw index", ef.EndFunctionDx2.OutgoingInterface) + } + req.SwIfIndex = interface_types.InterfaceIndex(ifMeta.SwIfIndex) + case *srv6.LocalSID_EndFunctionDx4: + req.Behavior = BehaviorDX4 + ifMeta, exists := h.ifIndexes.LookupByName(ef.EndFunctionDx4.OutgoingInterface) + if !exists { + return fmt.Errorf("for interface %v doesn't exist sw index", ef.EndFunctionDx4.OutgoingInterface) + } + req.SwIfIndex = interface_types.InterfaceIndex(ifMeta.SwIfIndex) + nhAddr, err := parseIPv6(ef.EndFunctionDx4.NextHop) // parses also IPv4 + if err != nil { + return err + } + nhAddr4 := nhAddr.To4() + if nhAddr4 == nil { + return fmt.Errorf("next hop of DX4 end function (%v) is not valid IPv4 address", ef.EndFunctionDx4.NextHop) + } + var addr ip_types.IP4Address + copy(addr[:], nhAddr4) + req.NhAddr.Af = ip_types.ADDRESS_IP4 + req.NhAddr.Un.SetIP4(addr) + case *srv6.LocalSID_EndFunctionDx6: + req.Behavior = BehaviorDX6 + ifMeta, exists := h.ifIndexes.LookupByName(ef.EndFunctionDx6.OutgoingInterface) + if !exists { + return fmt.Errorf("for interface %v doesn't exist sw index", ef.EndFunctionDx6.OutgoingInterface) + } + req.SwIfIndex = interface_types.InterfaceIndex(ifMeta.SwIfIndex) + nhAddr6, err := parseIPv6(ef.EndFunctionDx6.NextHop) + if err != nil { + return err + } + var addr ip_types.IP6Address + copy(addr[:], nhAddr6) + req.NhAddr.Af = ip_types.ADDRESS_IP6 + req.NhAddr.Un.SetIP6(addr) + case *srv6.LocalSID_EndFunctionDt4: + req.Behavior = BehaviorDT4 + req.SwIfIndex = interface_types.InterfaceIndex(ef.EndFunctionDt4.VrfId) + case *srv6.LocalSID_EndFunctionDt6: + req.Behavior = BehaviorDT6 + req.SwIfIndex = interface_types.InterfaceIndex(ef.EndFunctionDt6.VrfId) + case nil: + return fmt.Errorf("End function not set. Please configure end function for local SID %v ", localSID.GetSid()) + default: + return fmt.Errorf("unknown end function (model link type %T)", ef) // EndFunction_AD is handled elsewhere + } + + return nil +} + +// SetEncapsSourceAddress sets for SRv6 in VPP the source address used for encapsulated packet +func (h *SRv6VppHandler) SetEncapsSourceAddress(address string) error { + h.log.Debugf("Configuring encapsulation source address to address %v", address) + ipAddress, err := parseIPv6(address) + if err != nil { + return err + } + var encapSrc ip_types.IP6Address + copy(encapSrc[:], ipAddress.To16()) + req := &vpp_sr.SrSetEncapSource{ + EncapsSource: encapSrc, + } + reply := &vpp_sr.SrSetEncapSourceReply{} + + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + if reply.Retval != 0 { + return fmt.Errorf("vpp call %q returned: %d", reply.GetMessageName(), reply.Retval) + } + + h.log.WithFields(logging.Fields{"Encapsulation source address": address}). + Debug("Encapsulation source address configured.") + + return nil +} + +// AddPolicy adds SRv6 policy into VPP (including all policy's segment lists). +func (h *SRv6VppHandler) AddPolicy(policy *srv6.Policy) error { + if err := h.addBasePolicyWithFirstSegmentList(policy); err != nil { + return fmt.Errorf("can't create Policy with first segment list (Policy: %+v): %v", policy, err) + } + if err := h.addOtherSegmentLists(policy); err != nil { + return fmt.Errorf("can't add all segment lists to created policy %+v: %v", policy, err) + } + return nil +} + +func (h *SRv6VppHandler) addBasePolicyWithFirstSegmentList(policy *srv6.Policy) error { + h.log.Debugf("Adding SR policy %+v", policy) + bindingSid, err := parseIPv6(policy.GetBsid()) // already validated + if err != nil { + return fmt.Errorf("binding sid address %s is not IPv6 address: %v", policy.GetBsid(), err) // calls from descriptor are already validated + } + if len(policy.SegmentLists) == 0 { + return fmt.Errorf("policy must have defined at least one segment list (Policy: %+v)", policy) // calls from descriptor are already validated + } + sids, err := h.convertPolicySegment(policy.SegmentLists[0]) + if err != nil { + return err + } + var BsidAddr ip_types.IP6Address + copy(BsidAddr[:], bindingSid.To16()) + // Note: Weight in sr.SrPolicyAdd is leftover from API changes that moved weight into sr.Srv6SidList (it is weight of sid list not of the whole policy) + req := &vpp_sr.SrPolicyAdd{ + BsidAddr: BsidAddr, + Sids: *sids, + IsEncap: policy.SrhEncapsulation, + IsSpray: policy.SprayBehaviour, + FibTable: policy.InstallationVrfId, + } + reply := &vpp_sr.SrPolicyAddReply{} + + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + if reply.Retval != 0 { + return fmt.Errorf("vpp call %q returned: %d", reply.GetMessageName(), reply.Retval) + } + + h.log.WithFields(logging.Fields{"binding SID": bindingSid, "list of next SIDs": policy.SegmentLists[0].Segments}). + Debug("base SR policy (policy with just one segment list) added") + + return nil +} + +func (h *SRv6VppHandler) addOtherSegmentLists(policy *srv6.Policy) error { + for _, sl := range policy.SegmentLists[1:] { + if err := h.AddPolicySegmentList(sl, policy); err != nil { + return fmt.Errorf("failed to add policy segment %+v: %v", sl, err) + } + } + return nil +} + +// DeletePolicy deletes SRv6 policy given by binding SID +func (h *SRv6VppHandler) DeletePolicy(bindingSid net.IP) error { + h.log.Debugf("Deleting SR policy with binding SID %v ", bindingSid) + var BsidAddr ip_types.IP6Address + copy(BsidAddr[:], bindingSid.To16()) + req := &vpp_sr.SrPolicyDel{ + BsidAddr: BsidAddr, // TODO add ability to define policy also by index (SrPolicyIndex) + } + reply := &vpp_sr.SrPolicyDelReply{} + + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + if reply.Retval != 0 { + return fmt.Errorf("vpp call %q returned: %d", reply.GetMessageName(), reply.Retval) + } + + h.log.WithFields(logging.Fields{"binding SID": bindingSid}).Debug("SR policy deleted") + + return nil +} + +// AddPolicySegmentList adds segment list to SRv6 policy in VPP +func (h *SRv6VppHandler) AddPolicySegmentList(segmentList *srv6.Policy_SegmentList, policy *srv6.Policy) error { + h.log.Debugf("Adding segment %+v to SR policy %+v", segmentList, policy) + err := h.modPolicy(AddSRList, policy, segmentList, 0) + if err == nil { + h.log.WithFields(logging.Fields{"binding SID": policy.Bsid, "list of next SIDs": segmentList.Segments}). + Debug("SR policy modified(added another segment list)") + } + return err +} + +// DeletePolicySegmentList removes segment list (with VPP-internal index ) from SRv6 policy in VPP +func (h *SRv6VppHandler) DeletePolicySegmentList(segmentList *srv6.Policy_SegmentList, segmentVPPIndex uint32, policy *srv6.Policy) error { + h.log.Debugf("Removing segment %+v (vpp-internal index %v) from SR policy %+v", segmentList, segmentVPPIndex, policy) + err := h.modPolicy(DeleteSRList, policy, segmentList, segmentVPPIndex) + if err == nil { + h.log.WithFields(logging.Fields{"binding SID": policy.Bsid, "list of next SIDs": segmentList.Segments, "segmentListIndex": segmentVPPIndex}). + Debug("SR policy modified(removed segment list)") + } + return err +} + +func (h *SRv6VppHandler) modPolicy(operation sr_types.SrPolicyOp, policy *srv6.Policy, segmentList *srv6.Policy_SegmentList, segmentListIndex uint32) error { + bindingSid, err := parseIPv6(policy.GetBsid()) + if err != nil { + return fmt.Errorf("binding sid address %s is not IPv6 address: %v", policy.GetBsid(), err) // calls from descriptor are already validated + } + sids, err := h.convertPolicySegment(segmentList) + if err != nil { + return err + } + + var BsidAddr ip_types.IP6Address + copy(BsidAddr[:], bindingSid.To16()) + // Note: Weight in sr.SrPolicyMod is leftover from API changes that moved weight into sr.Srv6SidList (it is weight of sid list not of the whole policy) + req := &vpp_sr.SrPolicyMod{ + BsidAddr: BsidAddr, // TODO add ability to define policy also by index (SrPolicyIndex) + Operation: operation, + Sids: *sids, + FibTable: policy.InstallationVrfId, + } + if operation == DeleteSRList || operation == ModifyWeightOfSRList { + req.SlIndex = segmentListIndex + } + + reply := &vpp_sr.SrPolicyModReply{} + + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + if reply.Retval != 0 { + return fmt.Errorf("vpp call %q returned: %d", reply.GetMessageName(), reply.Retval) + } + return nil +} + +func (h *SRv6VppHandler) convertPolicySegment(segmentList *srv6.Policy_SegmentList) (*vpp_sr.Srv6SidList, error) { + var segments []ip_types.IP6Address + for _, sid := range segmentList.Segments { + // parse to IPv6 address + parserSid, err := parseIPv6(sid) + if err != nil { + return nil, err + } + // add sid to segment list + var ipv6Segment ip_types.IP6Address + copy(ipv6Segment[:], parserSid) + segments = append(segments, ipv6Segment) + } + sidList := &vpp_sr.Srv6SidList{ + NumSids: uint8(len(segments)), + Weight: segmentList.Weight, + } + copy(sidList.Sids[:], segments) + return sidList, nil +} + +// RetrievePolicyIndexInfo retrieves index of policy and its segment lists +func (h *SRv6VppHandler) RetrievePolicyIndexInfo(policy *srv6.Policy) (policyIndex uint32, segmentListIndexes map[*srv6.Policy_SegmentList]uint32, err error) { + // dump sr policies using VPP CLI + data, err := h.RunCli(context.TODO(), "sh sr policies") + if err != nil { + return ^uint32(0), nil, fmt.Errorf("can't dump index data from VPP: %v", err) + } + + // do necessary parsing to extract index of segment list + dumpStr := strings.ToLower(string(data)) + segmentListIndexes = make(map[*srv6.Policy_SegmentList]uint32) + + for _, policyStr := range strings.Split(dumpStr, "-----------") { + policyHeader := regexp.MustCompile(fmt.Sprintf("\\[(\\d+)\\]\\.-\\s+bsid:\\s*%s", strings.ToLower(strings.TrimSpace(policy.GetBsid())))) + if policyMatch := policyHeader.FindStringSubmatch(policyStr); policyMatch != nil { + parsed, err := strconv.ParseUint(policyMatch[1], 10, 32) + if err != nil { + return ^uint32(0), nil, fmt.Errorf("can't parse policy index %q (dump: %s)", policyMatch[1], dumpStr) + } + policyIndex = uint32(parsed) + + for _, sl := range policy.SegmentLists { + slRE := regexp.MustCompile(fmt.Sprintf("\\[(\\d+)\\].- < %s,[^:>]*> weight: %d", strings.ToLower(strings.Join(sl.Segments, ", ")), sl.Weight)) + if slMatch := slRE.FindStringSubmatch(policyStr); slMatch != nil { + parsed, err := strconv.ParseUint(slMatch[1], 10, 32) + if err != nil { + return ^uint32(0), nil, fmt.Errorf("can't parse segment policy index %q (dump: %s)", slMatch[1], dumpStr) + } + segmentListIndexes[sl] = uint32(parsed) + continue + } + return ^uint32(0), nil, fmt.Errorf("can't find index for segment list %+v (policy bsid %v) in dump %q", sl, policy.GetBsid(), dumpStr) + } + return policyIndex, segmentListIndexes, nil + } + } + return ^uint32(0), nil, fmt.Errorf("can't find index for policy with bsid %v in dump %q", policy.GetBsid(), dumpStr) +} + +// AddSteering sets in VPP steering into SRv6 policy. +func (h *SRv6VppHandler) AddSteering(steering *srv6.Steering) error { + return h.addDelSteering(false, steering) +} + +// RemoveSteering removes in VPP steering into SRv6 policy. +func (h *SRv6VppHandler) RemoveSteering(steering *srv6.Steering) error { + return h.addDelSteering(true, steering) +} + +func (h *SRv6VppHandler) addDelSteering(delete bool, steering *srv6.Steering) error { + // defining operation strings for logging + operationProgressing, operationFinished := "Adding", "Added" + if delete { + operationProgressing, operationFinished = "Removing", "Removed" + } + + // logging info about operation with steering + switch t := steering.Traffic.(type) { + case *srv6.Steering_L3Traffic_: + h.log.Debugf("%v steering for l3 traffic with destination %v to SR policy (binding SID %v, policy index %v)", + operationProgressing, t.L3Traffic.PrefixAddress, steering.GetPolicyBsid(), steering.GetPolicyIndex()) + case *srv6.Steering_L2Traffic_: + h.log.Debugf("%v steering for l2 traffic from interface %v to SR policy (binding SID %v, policy index %v)", + operationProgressing, t.L2Traffic.InterfaceName, steering.GetPolicyBsid(), steering.GetPolicyIndex()) + } + + // converting policy reference + var policyBSIDAddr ip_types.IP6Address // undefined reference + var policyIndex = uint32(0) // undefined reference + switch ref := steering.PolicyRef.(type) { + case *srv6.Steering_PolicyBsid: + bsid, err := parseIPv6(ref.PolicyBsid) + if err != nil { + return fmt.Errorf("can't parse binding SID %q to IP address: %v ", ref.PolicyBsid, err) + } + copy(policyBSIDAddr[:], bsid.To16()) + case *srv6.Steering_PolicyIndex: + policyIndex = ref.PolicyIndex + case nil: + return fmt.Errorf("policy reference must be provided") + default: + return fmt.Errorf("unknown policy reference type (link type %+v)", ref) + } + + // converting target traffic info + var prefix ip_types.Prefix + steerType := sr_types.SrSteer(SteerTypeIPv6) + tableID := uint32(0) + intIndex := uint32(0) + switch t := steering.Traffic.(type) { + case *srv6.Steering_L3Traffic_: + ip, ipnet, err := net.ParseCIDR(t.L3Traffic.PrefixAddress) + if err != nil { + return fmt.Errorf("can't parse ip prefix %q: %v", t.L3Traffic.PrefixAddress, err) + } + if ip.To4() != nil { // IPv4 address + steerType = SteerTypeIPv4 + } + tableID = t.L3Traffic.InstallationVrfId + if ip.To16() != nil { + prefix.Address.Af = ip_types.ADDRESS_IP6 + } + prefix.Address, _ = vpp2306.IPToAddress(ip.String()) + maskWidth, _ := ipnet.Mask.Size() + prefix.Len = uint8(maskWidth) + case *srv6.Steering_L2Traffic_: + steerType = SteerTypeL2 + ifMeta, exists := h.ifIndexes.LookupByName(t.L2Traffic.InterfaceName) + if !exists { + return fmt.Errorf("for interface %v doesn't exist sw index", t.L2Traffic.InterfaceName) + } + intIndex = ifMeta.SwIfIndex + case nil: + return fmt.Errorf("traffic type must be provided") + default: + return fmt.Errorf("unknown traffic type (link type %+v)", t) + } + req := &vpp_sr.SrSteeringAddDel{ + IsDel: delete, + TableID: tableID, + BsidAddr: policyBSIDAddr, // policy (to which we want to steer routing into) identified by policy binding sid (alternativelly it can be used policy index) + SrPolicyIndex: policyIndex, // policy (to which we want to steer routing into) identified by policy index (alternativelly it can be used policy binding sid) + TrafficType: steerType, // type of traffic to steer + Prefix: prefix, // destination prefix address (L3 traffic type only) + SwIfIndex: interface_types.InterfaceIndex(intIndex), // incoming interface (L2 traffic type only) + } + reply := &vpp_sr.SrSteeringAddDelReply{} + + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + if reply.Retval != 0 { + return fmt.Errorf("vpp call %q returned: %d", reply.GetMessageName(), reply.Retval) + } + + h.log.WithFields(logging.Fields{ + "steer type": steerType, + "L3 prefix": prefix, + "L2 interface index": intIndex, + "policy binding SID": policyBSIDAddr, + "policy index": policyIndex, + }).Debugf("%v steering to SR policy ", operationFinished) + + return nil +} + +// guessInterfaceType attempts to guess the correct interface type from its internal name (as given by VPP). +// This is required mainly for those interface types, that do not provide dump binary API, +// such as loopback of af_packet. +func guessInterfaceType(ifName string) ifs.Interface_Type { + switch { + case strings.HasPrefix(ifName, "loop"), + strings.HasPrefix(ifName, "local"): + return ifs.Interface_SOFTWARE_LOOPBACK + case strings.HasPrefix(ifName, "memif"): + return ifs.Interface_MEMIF + case strings.HasPrefix(ifName, "tap"): + return ifs.Interface_TAP + case strings.HasPrefix(ifName, "host"): + return ifs.Interface_AF_PACKET + case strings.HasPrefix(ifName, "vxlan"): + return ifs.Interface_VXLAN_TUNNEL + case strings.HasPrefix(ifName, "ipsec"): + return ifs.Interface_IPSEC_TUNNEL + case strings.HasPrefix(ifName, "vmxnet3"): + return ifs.Interface_VMXNET3_INTERFACE + default: + return ifs.Interface_DPDK + } +} diff --git a/plugins/vpp/srplugin/vppcalls/vpp2306/srv6_dump.go b/plugins/vpp/srplugin/vppcalls/vpp2306/srv6_dump.go new file mode 100644 index 0000000000..59e7517693 --- /dev/null +++ b/plugins/vpp/srplugin/vppcalls/vpp2306/srv6_dump.go @@ -0,0 +1,165 @@ +// Copyright (c) 2022 Pantheon.tech +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "net" + + "github.com/go-errors/errors" + + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip_types" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/sr" + srv6 "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/srv6" +) + +// DumpLocalSids retrieves all localsids +func (h *SRv6VppHandler) DumpLocalSids() (localsids []*srv6.LocalSID, err error) { + h.log.Debug("Dumping LocalSIDs") + + reqCtx := h.callsChannel.SendMultiRequest(&sr.SrLocalsidsDump{}) + for { + // retrieve dump for another localsid + dumpReply := &sr.SrLocalsidsDetails{} + stop, err := reqCtx.ReceiveReply(dumpReply) + if stop { + break + } + if err != nil { + return nil, errors.Errorf("error while retrieving localsid details:%v", err) + } + + // convert dumped localsid details into modeled Localsid struct + sid, error := h.convertDumpedSid(&dumpReply.Addr) + if error != nil { + return localsids, errors.Errorf("can't properly handle sid address "+ + "of dumped localsid %v due to: %v", dumpReply, err) + } + localsid := &srv6.LocalSID{ + InstallationVrfId: dumpReply.FibTable, + Sid: sid.String(), + } + if err := h.fillEndFunction(localsid, dumpReply); err != nil { + return localsids, errors.Errorf("can't properly handle end function "+ + "of dumped localsid %v due to: %v", dumpReply, err) + } + + // collect all dumped localsids + localsids = append(localsids, localsid) + } + + return localsids, nil +} + +// convertDumpedSid extract from dumped structure SID value and converts it to IPv6 (net.IP) +func (h *SRv6VppHandler) convertDumpedSid(srv6Sid *ip_types.IP6Address) (net.IP, error) { + if srv6Sid == nil { + return nil, errors.New("can't convert sid from nil dumped address (or nil srv6sid)") + } + sid := net.IP(srv6Sid[:]).To16() + if sid == nil { + return nil, errors.Errorf("can't convert dumped SID bytes(%v) to net.IP", srv6Sid) + } + return sid, nil +} + +// fillEndFunction create end function part of NB-modeled localsid from SB-dumped structure +func (h *SRv6VppHandler) fillEndFunction(localSID *srv6.LocalSID, dumpReply *sr.SrLocalsidsDetails) error { + switch uint8(dumpReply.Behavior) { + case BehaviorEnd: + localSID.EndFunction = &srv6.LocalSID_BaseEndFunction{ + BaseEndFunction: &srv6.LocalSID_End{ + Psp: dumpReply.EndPsp, + }, + } + case BehaviorX: + ifName, _, exists := h.ifIndexes.LookupBySwIfIndex(dumpReply.XconnectIfaceOrVrfTable) + if !exists { + return errors.Errorf("there is no interface with sw index %v", dumpReply.XconnectIfaceOrVrfTable) + } + localSID.EndFunction = &srv6.LocalSID_EndFunctionX{ + EndFunctionX: &srv6.LocalSID_EndX{ + Psp: dumpReply.EndPsp, + OutgoingInterface: ifName, + NextHop: h.nextHop(dumpReply), + }, + } + case BehaviorT: + localSID.EndFunction = &srv6.LocalSID_EndFunctionT{ + EndFunctionT: &srv6.LocalSID_EndT{ + Psp: dumpReply.EndPsp, + VrfId: dumpReply.XconnectIfaceOrVrfTable, + }, + } + case BehaviorDX2: + ifName, _, exists := h.ifIndexes.LookupBySwIfIndex(dumpReply.XconnectIfaceOrVrfTable) + if !exists { + return errors.Errorf("there is no interface with sw index %v", dumpReply.XconnectIfaceOrVrfTable) + } + localSID.EndFunction = &srv6.LocalSID_EndFunctionDx2{ + EndFunctionDx2: &srv6.LocalSID_EndDX2{ + VlanTag: dumpReply.VlanIndex, + OutgoingInterface: ifName, + }, + } + case BehaviorDX4: + ifName, _, exists := h.ifIndexes.LookupBySwIfIndex(dumpReply.XconnectIfaceOrVrfTable) + if !exists { + return errors.Errorf("there is no interface with sw index %v", dumpReply.XconnectIfaceOrVrfTable) + } + localSID.EndFunction = &srv6.LocalSID_EndFunctionDx4{ + EndFunctionDx4: &srv6.LocalSID_EndDX4{ + OutgoingInterface: ifName, + NextHop: h.nextHop(dumpReply), + }, + } + case BehaviorDX6: + ifName, _, exists := h.ifIndexes.LookupBySwIfIndex(dumpReply.XconnectIfaceOrVrfTable) + if !exists { + return errors.Errorf("there is no interface with sw index %v", dumpReply.XconnectIfaceOrVrfTable) + } + localSID.EndFunction = &srv6.LocalSID_EndFunctionDx6{ + EndFunctionDx6: &srv6.LocalSID_EndDX6{ + OutgoingInterface: ifName, + NextHop: h.nextHop(dumpReply), + }, + } + case BehaviorDT4: + localSID.EndFunction = &srv6.LocalSID_EndFunctionDt4{ + EndFunctionDt4: &srv6.LocalSID_EndDT4{ + VrfId: dumpReply.XconnectIfaceOrVrfTable, + }, + } + case BehaviorDT6: + localSID.EndFunction = &srv6.LocalSID_EndFunctionDt6{ + EndFunctionDt6: &srv6.LocalSID_EndDT6{ + VrfId: dumpReply.XconnectIfaceOrVrfTable, + }, + } + default: + return errors.Errorf("localsid with unknown or unsupported behavior (%v)", dumpReply.Behavior) + } + + return nil +} + +// nextHop transforms SB dump data about next hop to NB modeled structure +func (h *SRv6VppHandler) nextHop(dumpReply *sr.SrLocalsidsDetails) string { + ip := addressToIP(dumpReply.XconnectNhAddr) + if ip.IsUnspecified() { + // default is no next hop address (i.e. L2 xconnect) + return "" + } + return ip.String() +} diff --git a/plugins/vpp/srplugin/vppcalls/vpp2306/srv6_test.go b/plugins/vpp/srplugin/vppcalls/vpp2306/srv6_test.go new file mode 100644 index 0000000000..7e05fd34e3 --- /dev/null +++ b/plugins/vpp/srplugin/vppcalls/vpp2306/srv6_test.go @@ -0,0 +1,1525 @@ +// Copyright (c) 2022 Bell Canada, Pantheon Technologies and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306_test + +import ( + "fmt" + "net" + "testing" + + . "github.com/onsi/gomega" + govppapi "go.fd.io/govpp/api" + "go.ligato.io/cn-infra/v2/logging/logrus" + + vpp_ifs "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface_types" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip_types" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/memclnt" + vpp_sr "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/sr" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/vlib" + "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/ifaceidx" + "go.ligato.io/vpp-agent/v3/plugins/vpp/srplugin/vppcalls" + vpp2306 "go.ligato.io/vpp-agent/v3/plugins/vpp/srplugin/vppcalls/vpp2306" + "go.ligato.io/vpp-agent/v3/plugins/vpp/vppmock" + srv6 "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/srv6" +) + +const ( + ifaceA = "A" + ifaceB = "B" + ifaceBOutOfidxs = "B" + swIndexA = 1 + invalidIPAddress = "XYZ" + memif1 = "memif1/1" + memif2 = "memif2/2" +) + +var ( + sidA = sid("A::") + sidB = sid("B::") + sidC = sid("C::") + nextHop = net.ParseIP("B::").To16() + nextHopIPv4 = net.ParseIP("1.2.3.4").To4() +) + +// TODO add tests for new nhAddr4 field in end behaviours +// TestAddLocalSID tests all cases for method AddLocalSID +func TestAddLocalSID(t *testing.T) { + // Prepare different cases + cases := []struct { + Name string + FailInVPP bool + FailInVPPDump bool + ExpectFailure bool + cliMode bool // sr-proxy can be se only using CLI -> using VPE binary API to send VPP CLI commands + MockInterfaceDump []govppapi.Message + Input *srv6.LocalSID + Expected govppapi.Message + }{ + { + Name: "addition with end behaviour", + Input: &srv6.LocalSID{ + Sid: sidToStr(sidA), + InstallationVrfId: 10, + EndFunction: &srv6.LocalSID_BaseEndFunction{ + BaseEndFunction: &srv6.LocalSID_End{ + Psp: true, + }, + }, + }, + Expected: &vpp_sr.SrLocalsidAddDel{ + IsDel: false, + Localsid: sidA, + Behavior: vpp2306.BehaviorEnd, + FibTable: 10, // installationVrfId + EndPsp: true, + }, + }, + { + Name: "addition with endX behaviour (ipv6 next hop address)", + Input: &srv6.LocalSID{ + Sid: sidToStr(sidA), + InstallationVrfId: 10, + EndFunction: &srv6.LocalSID_EndFunctionX{ + EndFunctionX: &srv6.LocalSID_EndX{ + Psp: true, + NextHop: nextHop.String(), + OutgoingInterface: ifaceA, + }, + }, + }, + Expected: &vpp_sr.SrLocalsidAddDel{ + IsDel: false, + Localsid: sidA, + Behavior: vpp2306.BehaviorX, + FibTable: 10, // installationVrfId + EndPsp: true, + SwIfIndex: interface_types.InterfaceIndex(swIndexA), + NhAddr: toAddress(nextHop.String()), + }, + }, + { + Name: "addition with endX behaviour (ipv4 next hop address)", + Input: &srv6.LocalSID{ + Sid: sidToStr(sidA), + InstallationVrfId: 10, + EndFunction: &srv6.LocalSID_EndFunctionX{ + EndFunctionX: &srv6.LocalSID_EndX{ + Psp: true, + NextHop: nextHopIPv4.String(), + OutgoingInterface: ifaceA, + }, + }, + }, + Expected: &vpp_sr.SrLocalsidAddDel{ + IsDel: false, + Localsid: sidA, + Behavior: vpp2306.BehaviorX, + FibTable: 10, // installationVrfId + EndPsp: true, + SwIfIndex: swIndexA, + NhAddr: toAddress(nextHopIPv4.String()), + }, + }, + { + Name: "addition with endT behaviour", + Input: &srv6.LocalSID{ + Sid: sidToStr(sidA), + InstallationVrfId: 10, + EndFunction: &srv6.LocalSID_EndFunctionT{ + EndFunctionT: &srv6.LocalSID_EndT{ + Psp: true, + VrfId: 11, + }, + }, + }, + Expected: &vpp_sr.SrLocalsidAddDel{ + IsDel: false, + Localsid: sidA, + Behavior: vpp2306.BehaviorT, + FibTable: 10, // installationVrfId + SwIfIndex: 11, + EndPsp: true, + }, + }, + { + Name: "addition with endDX2 behaviour", + Input: &srv6.LocalSID{ + Sid: sidToStr(sidA), + InstallationVrfId: 10, + EndFunction: &srv6.LocalSID_EndFunctionDx2{ + EndFunctionDx2: &srv6.LocalSID_EndDX2{ + VlanTag: 1, + OutgoingInterface: ifaceA, + }, + }, + }, + Expected: &vpp_sr.SrLocalsidAddDel{ + IsDel: false, + Localsid: sidA, + Behavior: vpp2306.BehaviorDX2, + FibTable: 10, // installationVrfId + EndPsp: false, + VlanIndex: 1, + SwIfIndex: swIndexA, + }, + }, + { + Name: "addition with endDX4 behaviour", + Input: &srv6.LocalSID{ + Sid: sidToStr(sidA), + InstallationVrfId: 10, + EndFunction: &srv6.LocalSID_EndFunctionDx4{ + EndFunctionDx4: &srv6.LocalSID_EndDX4{ + NextHop: nextHopIPv4.String(), + OutgoingInterface: ifaceA, + }, + }, + }, + Expected: &vpp_sr.SrLocalsidAddDel{ + IsDel: false, + Localsid: sidA, + Behavior: vpp2306.BehaviorDX4, + FibTable: 10, // installationVrfId + EndPsp: false, + SwIfIndex: swIndexA, + NhAddr: toAddress(nextHopIPv4.String()), + }, + }, + { + Name: "addition with endDX6 behaviour", + Input: &srv6.LocalSID{ + Sid: sidToStr(sidA), + InstallationVrfId: 10, + EndFunction: &srv6.LocalSID_EndFunctionDx6{ + EndFunctionDx6: &srv6.LocalSID_EndDX6{ + NextHop: nextHop.String(), + OutgoingInterface: ifaceA, + }, + }, + }, + Expected: &vpp_sr.SrLocalsidAddDel{ + IsDel: false, + Localsid: sidA, + Behavior: vpp2306.BehaviorDX6, + FibTable: 10, // installationVrfId + EndPsp: false, + SwIfIndex: swIndexA, + NhAddr: toAddress(nextHop.String()), + }, + }, + { + Name: "addition with endDT4 behaviour", + Input: &srv6.LocalSID{ + Sid: sidToStr(sidA), + InstallationVrfId: 10, + EndFunction: &srv6.LocalSID_EndFunctionDt4{ + EndFunctionDt4: &srv6.LocalSID_EndDT4{ + VrfId: 5, + }, + }, + }, + Expected: &vpp_sr.SrLocalsidAddDel{ + IsDel: false, + Localsid: sidA, + Behavior: vpp2306.BehaviorDT4, + FibTable: 10, // installationVrfId + SwIfIndex: 5, + EndPsp: false, + }, + }, + { + Name: "addition with endDT6 behaviour", + Input: &srv6.LocalSID{ + Sid: sidToStr(sidA), + InstallationVrfId: 10, + EndFunction: &srv6.LocalSID_EndFunctionDt6{ + EndFunctionDt6: &srv6.LocalSID_EndDT6{ + VrfId: 5, + }, + }, + }, + Expected: &vpp_sr.SrLocalsidAddDel{ + IsDel: false, + Localsid: sidA, + Behavior: vpp2306.BehaviorDT6, + FibTable: 10, // installationVrfId + SwIfIndex: 5, + EndPsp: false, + }, + }, + { + Name: "addition with endAD behaviour (+ memif interface name translation)", + cliMode: true, + MockInterfaceDump: []govppapi.Message{ + &vpp_ifs.SwInterfaceDetails{Tag: ifaceA, InterfaceName: memif1}, + &vpp_ifs.SwInterfaceDetails{Tag: ifaceB, InterfaceName: memif2}, + }, + Input: &srv6.LocalSID{ + Sid: sidToStr(sidA), + InstallationVrfId: 10, + EndFunction: &srv6.LocalSID_EndFunctionAd{ + EndFunctionAd: &srv6.LocalSID_EndAD{ + L3ServiceAddress: nextHopIPv4.String(), + OutgoingInterface: ifaceA, + IncomingInterface: ifaceB, + }, + }, + }, + Expected: &vlib.CliInband{ + Cmd: fmt.Sprintf("sr localsid address %v fib-table 10 behavior end.ad nh %v oif %v iif %v", sidToStr(sidA), nextHopIPv4.String(), memif1, memif2), + }, + }, + { + Name: "addition with endAD behaviour for L2 sr-unaware service", + cliMode: true, + MockInterfaceDump: []govppapi.Message{ + &vpp_ifs.SwInterfaceDetails{Tag: ifaceA, InterfaceName: memif1}, + &vpp_ifs.SwInterfaceDetails{Tag: ifaceB, InterfaceName: memif2}, + }, + Input: &srv6.LocalSID{ + Sid: sidToStr(sidA), + InstallationVrfId: 10, + EndFunction: &srv6.LocalSID_EndFunctionAd{ + EndFunctionAd: &srv6.LocalSID_EndAD{ // missing L3ServiceAddress means it is L2 service + OutgoingInterface: ifaceA, + IncomingInterface: ifaceB, + }, + }, + }, + Expected: &vlib.CliInband{ + Cmd: fmt.Sprintf("sr localsid address %v fib-table 10 behavior end.ad oif %v iif %v", sidToStr(sidA), memif1, memif2), + }, + }, + { + Name: "etcd-to-vpp-internal interface name translation for endAD behaviour (local and tap kind of interfaces)", + cliMode: true, + MockInterfaceDump: []govppapi.Message{ + &vpp_ifs.SwInterfaceDetails{Tag: ifaceA, InterfaceName: "local0"}, + &vpp_ifs.SwInterfaceDetails{Tag: ifaceB, InterfaceName: "tap0"}, + }, + Input: &srv6.LocalSID{ + Sid: sidToStr(sidA), + InstallationVrfId: 10, + EndFunction: &srv6.LocalSID_EndFunctionAd{ + EndFunctionAd: &srv6.LocalSID_EndAD{ + L3ServiceAddress: nextHopIPv4.String(), + OutgoingInterface: ifaceA, + IncomingInterface: ifaceB, + }, + }, + }, + Expected: &vlib.CliInband{ + Cmd: fmt.Sprintf("sr localsid address %v fib-table 10 behavior end.ad nh %v oif %v iif %v", sidToStr(sidA), nextHopIPv4.String(), "local0", "tap0"), + }, + }, + { + Name: "etcd-to-vpp-internal interface name translation for endAD behaviour (host and vxlan kind of interfaces)", + cliMode: true, + MockInterfaceDump: []govppapi.Message{ + &vpp_ifs.SwInterfaceDetails{Tag: ifaceA, InterfaceName: "host0"}, + &vpp_ifs.SwInterfaceDetails{Tag: ifaceB, InterfaceName: "vxlan0"}, + }, + Input: &srv6.LocalSID{ + Sid: sidToStr(sidA), + InstallationVrfId: 10, + EndFunction: &srv6.LocalSID_EndFunctionAd{ + EndFunctionAd: &srv6.LocalSID_EndAD{ + L3ServiceAddress: nextHopIPv4.String(), + OutgoingInterface: ifaceA, + IncomingInterface: ifaceB, + }, + }, + }, + Expected: &vlib.CliInband{ + Cmd: fmt.Sprintf("sr localsid address %v fib-table 10 behavior end.ad nh %v oif %v iif %v", sidToStr(sidA), nextHopIPv4.String(), "host0", "vxlan0"), + }, + }, + { + Name: "etcd-to-vpp-internal interface name translation for endAD behaviour (ipsec and vmxnet3 kind of interfaces)", + cliMode: true, + MockInterfaceDump: []govppapi.Message{ + &vpp_ifs.SwInterfaceDetails{Tag: ifaceA, InterfaceName: "ipsec0"}, + &vpp_ifs.SwInterfaceDetails{Tag: ifaceB, InterfaceName: "vmxnet3-0"}, + }, + Input: &srv6.LocalSID{ + Sid: sidToStr(sidA), + InstallationVrfId: 10, + EndFunction: &srv6.LocalSID_EndFunctionAd{ + EndFunctionAd: &srv6.LocalSID_EndAD{ + L3ServiceAddress: nextHopIPv4.String(), + OutgoingInterface: ifaceA, + IncomingInterface: ifaceB, + }, + }, + }, + Expected: &vlib.CliInband{ + Cmd: fmt.Sprintf("sr localsid address %v fib-table 10 behavior end.ad nh %v oif %v iif %v", sidToStr(sidA), nextHopIPv4.String(), "ipsec0", "vmxnet3-0"), + }, + }, + { + Name: "etcd-to-vpp-internal interface name translation for endAD behaviour (loop and unknown kind of interfaces)", + cliMode: true, + MockInterfaceDump: []govppapi.Message{ + &vpp_ifs.SwInterfaceDetails{Tag: ifaceA, InterfaceName: "loop0"}, + &vpp_ifs.SwInterfaceDetails{Tag: ifaceB, InterfaceName: "unknown0"}, + }, + Input: &srv6.LocalSID{ + Sid: sidToStr(sidA), + InstallationVrfId: 10, + EndFunction: &srv6.LocalSID_EndFunctionAd{ + EndFunctionAd: &srv6.LocalSID_EndAD{ + L3ServiceAddress: nextHopIPv4.String(), + OutgoingInterface: ifaceA, + IncomingInterface: "unknown0", // interface name is taken from vpp internal name + }, + }, + }, + Expected: &vlib.CliInband{ + Cmd: fmt.Sprintf("sr localsid address %v fib-table 10 behavior end.ad nh %v oif %v iif %v", sidToStr(sidA), nextHopIPv4.String(), "loop0", "unknown0"), + }, + }, + { + Name: "fail due to missing end function", + ExpectFailure: true, + Input: &srv6.LocalSID{ + Sid: sidToStr(sidA), + InstallationVrfId: 0, + }, + }, + { + Name: "failure propagation from VPP (doing main VPP call)", + FailInVPP: true, + ExpectFailure: true, + Input: &srv6.LocalSID{ + Sid: sidToStr(sidA), + InstallationVrfId: 0, + EndFunction: &srv6.LocalSID_BaseEndFunction{ + BaseEndFunction: &srv6.LocalSID_End{ + Psp: true, + }, + }, + }, + }, + { + Name: "failure propagation from VPP (doing main VPP call) for SR-proxy (CLI using VPE binary API)", + FailInVPP: true, + ExpectFailure: true, + cliMode: true, + MockInterfaceDump: []govppapi.Message{ + &vpp_ifs.SwInterfaceDetails{Tag: ifaceA, InterfaceName: memif1}, + &vpp_ifs.SwInterfaceDetails{Tag: ifaceB, InterfaceName: memif2}, + }, + Input: &srv6.LocalSID{ + Sid: sidToStr(sidA), + InstallationVrfId: 10, + EndFunction: &srv6.LocalSID_EndFunctionAd{ + EndFunctionAd: &srv6.LocalSID_EndAD{ + L3ServiceAddress: nextHopIPv4.String(), + OutgoingInterface: ifaceA, + IncomingInterface: ifaceB, + }, + }, + }, + }, + { + Name: "failure propagation from VPP Dump call", + FailInVPPDump: true, + ExpectFailure: true, + cliMode: true, + Input: &srv6.LocalSID{ + Sid: sidToStr(sidA), + InstallationVrfId: 10, + EndFunction: &srv6.LocalSID_EndFunctionAd{ + EndFunctionAd: &srv6.LocalSID_EndAD{ + L3ServiceAddress: nextHopIPv4.String(), + OutgoingInterface: ifaceA, + IncomingInterface: ifaceB, + }, + }, + }, + }, + { + Name: "missing SR-proxy outgoing interface in VPP interface dump", + ExpectFailure: true, + cliMode: true, + MockInterfaceDump: []govppapi.Message{ + &vpp_ifs.SwInterfaceDetails{Tag: ifaceB, InterfaceName: memif2}, + }, + Input: &srv6.LocalSID{ + Sid: sidToStr(sidA), + InstallationVrfId: 10, + EndFunction: &srv6.LocalSID_EndFunctionAd{ + EndFunctionAd: &srv6.LocalSID_EndAD{ + L3ServiceAddress: nextHopIPv4.String(), + OutgoingInterface: ifaceA, + IncomingInterface: ifaceB, + }, + }, + }, + }, + { + Name: "missing SR-proxy incoming interface in VPP interface dump", + ExpectFailure: true, + cliMode: true, + MockInterfaceDump: []govppapi.Message{ + &vpp_ifs.SwInterfaceDetails{Tag: ifaceA, InterfaceName: memif1}, + }, + Input: &srv6.LocalSID{ + Sid: sidToStr(sidA), + InstallationVrfId: 10, + EndFunction: &srv6.LocalSID_EndFunctionAd{ + EndFunctionAd: &srv6.LocalSID_EndAD{ + L3ServiceAddress: nextHopIPv4.String(), + OutgoingInterface: ifaceA, + IncomingInterface: ifaceB, + }, + }, + }, + }, + { + Name: "missing interface in swIndexes (addition with endX behaviour)", + ExpectFailure: true, + Input: &srv6.LocalSID{ + Sid: sidToStr(sidA), + InstallationVrfId: 10, + EndFunction: &srv6.LocalSID_EndFunctionX{ + EndFunctionX: &srv6.LocalSID_EndX{ + Psp: true, + NextHop: nextHop.String(), + OutgoingInterface: ifaceBOutOfidxs, + }, + }, + }, + }, + { + Name: "invalid IP address (addition with endX behaviour)", + ExpectFailure: true, + Input: &srv6.LocalSID{ + InstallationVrfId: 10, + EndFunction: &srv6.LocalSID_EndFunctionX{ + EndFunctionX: &srv6.LocalSID_EndX{ + Psp: true, + NextHop: invalidIPAddress, + OutgoingInterface: ifaceA, + }, + }, + }, + }, + { + Name: "missing interface in swIndexes (addition with endDX2 behaviour)", + ExpectFailure: true, + Input: &srv6.LocalSID{ + Sid: sidToStr(sidA), + InstallationVrfId: 10, + EndFunction: &srv6.LocalSID_EndFunctionDx2{ + EndFunctionDx2: &srv6.LocalSID_EndDX2{ + VlanTag: 1, + OutgoingInterface: ifaceBOutOfidxs, + }, + }, + }, + }, + { + Name: "missing interface in swIndexes (addition with endDX4 behaviour)", + ExpectFailure: true, + Input: &srv6.LocalSID{ + Sid: sidToStr(sidA), + InstallationVrfId: 10, + EndFunction: &srv6.LocalSID_EndFunctionDx4{ + EndFunctionDx4: &srv6.LocalSID_EndDX4{ + NextHop: nextHopIPv4.String(), + OutgoingInterface: ifaceBOutOfidxs, + }, + }, + }, + }, + { + Name: "invalid IP address (addition with endDX4 behaviour)", + ExpectFailure: true, + Input: &srv6.LocalSID{ + Sid: sidToStr(sidA), + InstallationVrfId: 10, + EndFunction: &srv6.LocalSID_EndFunctionDx4{ + EndFunctionDx4: &srv6.LocalSID_EndDX4{ + NextHop: invalidIPAddress, + OutgoingInterface: ifaceA, + }, + }, + }, + }, + { + Name: "rejection of IPv6 addresses (addition with endDX4 behaviour)", + ExpectFailure: true, + Input: &srv6.LocalSID{ + Sid: sidToStr(sidA), + InstallationVrfId: 10, + EndFunction: &srv6.LocalSID_EndFunctionDx4{ + EndFunctionDx4: &srv6.LocalSID_EndDX4{ + NextHop: nextHop.String(), + OutgoingInterface: ifaceA, + }, + }, + }, + }, + { + Name: "missing interface in swIndexes (addition with endDX6 behaviour)", + ExpectFailure: true, + Input: &srv6.LocalSID{ + Sid: sidToStr(sidA), + InstallationVrfId: 10, + EndFunction: &srv6.LocalSID_EndFunctionDx6{ + EndFunctionDx6: &srv6.LocalSID_EndDX6{ + NextHop: nextHop.String(), + OutgoingInterface: ifaceBOutOfidxs, + }, + }, + }, + }, + { + Name: "invalid IP address (addition with endDX6 behaviour)", + ExpectFailure: true, + Input: &srv6.LocalSID{ + Sid: sidToStr(sidA), + InstallationVrfId: 10, + EndFunction: &srv6.LocalSID_EndFunctionDx6{ + EndFunctionDx6: &srv6.LocalSID_EndDX6{ + NextHop: invalidIPAddress, + OutgoingInterface: ifaceA, + }, + }, + }, + }, + } + + // Run all cases + for _, td := range cases { + t.Run(td.Name, func(t *testing.T) { + ctx, vppCalls := setup(t) + defer teardown(ctx) + // prepare reply + if td.MockInterfaceDump != nil { + if td.FailInVPPDump { + ctx.MockVpp.MockReply(&vpp_sr.SrPolicyDelReply{}) // unexpected type of message creates error (swInterfaceDetail doesn't have way how to indicate failure) + } else { + ctx.MockVpp.MockReply(td.MockInterfaceDump...) + ctx.MockVpp.MockReply(&memclnt.ControlPingReply{}) + } + } + if td.cliMode && !td.FailInVPPDump { // SR-proxy can be set only using VPP CLI (-> using VPE binary API to deliver command to VPP) + if td.FailInVPP { + ctx.MockVpp.MockReply(&vlib.CliInbandReply{Retval: 1}) + } else { + ctx.MockVpp.MockReply(&vlib.CliInbandReply{}) + } + } else { // normal SR binary API + if td.FailInVPP { + ctx.MockVpp.MockReply(&vpp_sr.SrLocalsidAddDelReply{Retval: 1}) + } else { + ctx.MockVpp.MockReply(&vpp_sr.SrLocalsidAddDelReply{}) + } + } + // make the call + err := vppCalls.AddLocalSid(td.Input) + // verify result + if td.ExpectFailure { + Expect(err).Should(HaveOccurred()) + } else { + Expect(err).ShouldNot(HaveOccurred()) + Expect(ctx.MockChannel.Msg).To(Equal(td.Expected)) + } + }) + } +} + +// TestDeleteLocalSID tests all cases for method DeleteLocalSID +func TestDeleteLocalSID(t *testing.T) { + // Prepare different cases + cases := []struct { + Name string + Fail bool + Input *srv6.LocalSID + MockReply govppapi.Message + Verify func(error, govppapi.Message) + }{ + { + Name: "simple delete of local sid (using vrf table with id 0)", + Input: &srv6.LocalSID{ + Sid: sidToStr(sidA), + InstallationVrfId: 0, + EndFunction: &srv6.LocalSID_BaseEndFunction{ + BaseEndFunction: &srv6.LocalSID_End{ + Psp: true, + }, + }, + }, + MockReply: &vpp_sr.SrLocalsidAddDelReply{}, + Verify: func(err error, catchedMsg govppapi.Message) { + Expect(err).ShouldNot(HaveOccurred()) + Expect(catchedMsg).To(Equal(&vpp_sr.SrLocalsidAddDel{ + IsDel: true, + Localsid: sidA, + FibTable: 0, + })) + }, + }, + { + Name: "simple delete of local sid (using vrf table with nonzero id)", + Input: &srv6.LocalSID{ + Sid: sidToStr(sidA), + InstallationVrfId: 10, + EndFunction: &srv6.LocalSID_BaseEndFunction{ + BaseEndFunction: &srv6.LocalSID_End{ + Psp: true, + }, + }, + }, + MockReply: &vpp_sr.SrLocalsidAddDelReply{}, + Verify: func(err error, catchedMsg govppapi.Message) { + Expect(err).ShouldNot(HaveOccurred()) + Expect(catchedMsg).To(Equal(&vpp_sr.SrLocalsidAddDel{ + IsDel: true, + Localsid: sidA, + FibTable: 10, + })) + }, + }, + { + Name: "failure propagation from VPP", + Input: &srv6.LocalSID{ + Sid: sidToStr(sidA), + InstallationVrfId: 0, + EndFunction: &srv6.LocalSID_BaseEndFunction{ + BaseEndFunction: &srv6.LocalSID_End{ + Psp: true, + }, + }, + }, + MockReply: &vpp_sr.SrLocalsidAddDelReply{Retval: 1}, + Verify: func(err error, msg govppapi.Message) { + Expect(err).Should(HaveOccurred()) + }, + }, + } + + // Run all cases + for _, td := range cases { + t.Run(td.Name, func(t *testing.T) { + ctx, vppCalls := setup(t) + defer teardown(ctx) + // prepare for case + ctx.MockVpp.MockReply(td.MockReply) + // make the call and verify + err := vppCalls.DeleteLocalSid(td.Input) + td.Verify(err, ctx.MockChannel.Msg) + }) + } +} + +// TestSetEncapsSourceAddress tests all cases for method SetEncapsSourceAddress +func TestSetEncapsSourceAddress(t *testing.T) { + // Prepare different cases + cases := []struct { + Name string + Fail bool + Address string + MockReply govppapi.Message + Verify func(error, govppapi.Message) + }{ + { + Name: "simple SetEncapsSourceAddress", + Address: nextHop.String(), + MockReply: &vpp_sr.SrSetEncapSourceReply{}, + Verify: func(err error, catchedMsg govppapi.Message) { + Expect(err).ShouldNot(HaveOccurred()) + Expect(catchedMsg).To(Equal(&vpp_sr.SrSetEncapSource{ + EncapsSource: sid(nextHop.String()), + })) + }, + }, + { + Name: "invalid IP address", + Address: invalidIPAddress, + MockReply: &vpp_sr.SrSetEncapSourceReply{}, + Verify: func(err error, catchedMsg govppapi.Message) { + Expect(err).Should(HaveOccurred()) + }, + }, + { + Name: "failure propagation from VPP", + Address: nextHop.String(), + MockReply: &vpp_sr.SrSetEncapSourceReply{Retval: 1}, + Verify: func(err error, msg govppapi.Message) { + Expect(err).Should(HaveOccurred()) + }, + }, + } + + // Run all cases + for _, td := range cases { + t.Run(td.Name, func(t *testing.T) { + ctx, vppCalls := setup(t) + defer teardown(ctx) + + ctx.MockVpp.MockReply(td.MockReply) + err := vppCalls.SetEncapsSourceAddress(td.Address) + td.Verify(err, ctx.MockChannel.Msg) + }) + } +} + +// TestAddPolicy tests all cases for method AddPolicy +func TestAddPolicy(t *testing.T) { + // Prepare different cases + cases := []struct { + Name string + Fail bool + Policy *srv6.Policy + MockReplies []govppapi.Message + Verify func(error, []govppapi.Message) + }{ + { + Name: "simple SetAddPolicy", + Policy: policy(sidA[:], 10, false, true, policySegmentList(1, sidA[:], sidB[:], sidC[:])), + MockReplies: []govppapi.Message{&vpp_sr.SrPolicyAddReply{}}, + Verify: func(err error, catchedMsgs []govppapi.Message) { + Expect(err).ShouldNot(HaveOccurred()) + Expect(catchedMsgs).To(HaveLen(1)) + Expect(catchedMsgs[0]).To(Equal(&vpp_sr.SrPolicyAdd{ + BsidAddr: sidA, + FibTable: 10, // installationVrfId + IsSpray: false, + IsEncap: true, + Sids: vpp_sr.Srv6SidList{ + Weight: 1, + NumSids: 3, + Sids: [16]ip_types.IP6Address{sidA, sidB, sidC}, + }, + })) + }, + }, + { + Name: "adding policy with multiple segment lists", + Policy: policy(sidA[:], 10, false, true, + policySegmentList(1, sidA[:], sidB[:], sidC[:]), policySegmentList(1, sidB[:], sidC[:], sidA[:])), + MockReplies: []govppapi.Message{&vpp_sr.SrPolicyAddReply{}, &vpp_sr.SrPolicyModReply{}}, + Verify: func(err error, catchedMsgs []govppapi.Message) { + Expect(err).ShouldNot(HaveOccurred()) + Expect(catchedMsgs).To(HaveLen(2)) + Expect(catchedMsgs[0]).To(Equal(&vpp_sr.SrPolicyAdd{ + BsidAddr: sidA, + FibTable: 10, // installationVrfId + IsSpray: false, + IsEncap: true, + Sids: vpp_sr.Srv6SidList{ + Weight: 1, + NumSids: 3, + Sids: [16]ip_types.IP6Address{ + sidA, sidB, sidC, + }, + }, + })) + Expect(catchedMsgs[1]).To(Equal(&vpp_sr.SrPolicyMod{ + BsidAddr: sidA, + Operation: vpp2306.AddSRList, + FibTable: 10, // installationVrfId + Sids: vpp_sr.Srv6SidList{ + Weight: 1, + NumSids: 3, + Sids: [16]ip_types.IP6Address{sidB, sidC, sidA}, + }, + })) + }, + }, + { + Name: "failing when adding policy with empty segment lists", + Policy: policy(sidA[:], 10, false, true), + MockReplies: []govppapi.Message{&vpp_sr.SrPolicyAddReply{}}, + Verify: func(err error, catchedMsgs []govppapi.Message) { + Expect(err).Should(HaveOccurred()) + }, + }, + { + Name: "invalid binding SID in policy", + Policy: &srv6.Policy{ + Bsid: invalidIPAddress, + InstallationVrfId: 10, + SprayBehaviour: false, + SrhEncapsulation: true, + SegmentLists: []*srv6.Policy_SegmentList{ + { + Weight: 1, + Segments: []string{sidToStr(sidA), invalidIPAddress, sidToStr(sidC)}, + }, + }, + }, + MockReplies: []govppapi.Message{&vpp_sr.SrPolicyAddReply{}}, + Verify: func(err error, catchedMsgs []govppapi.Message) { + Expect(err).Should(HaveOccurred()) + }, + }, + { + Name: "invalid SID (not IP address) in first segment list", + Policy: policy(sidA[:], 10, false, true, + &srv6.Policy_SegmentList{ + Weight: 1, + Segments: []string{sidToStr(sidA), invalidIPAddress, sidToStr(sidC)}, + }), + MockReplies: []govppapi.Message{&vpp_sr.SrPolicyAddReply{}}, + Verify: func(err error, catchedMsgs []govppapi.Message) { + Expect(err).Should(HaveOccurred()) + }, + }, + { + Name: "invalid SID (not IP address) in non-first segment list", + Policy: policy(sidA[:], 10, false, true, + policySegmentList(1, sidA[:], sidB[:], sidC[:]), + &srv6.Policy_SegmentList{ + Weight: 1, + Segments: []string{sidToStr(sidA), invalidIPAddress, sidToStr(sidC)}, + }), + MockReplies: []govppapi.Message{&vpp_sr.SrPolicyAddReply{}, &vpp_sr.SrPolicyModReply{}}, + Verify: func(err error, catchedMsgs []govppapi.Message) { + Expect(err).Should(HaveOccurred()) + }, + }, + { + Name: "failure propagation from VPP", + Policy: policy(sidA[:], 0, true, true, policySegmentList(1, sidA[:], sidB[:], sidC[:])), + MockReplies: []govppapi.Message{&vpp_sr.SrPolicyAddReply{Retval: 1}}, + Verify: func(err error, msgs []govppapi.Message) { + Expect(err).Should(HaveOccurred()) + }, + }, + } + + // Run all cases + for _, td := range cases { + t.Run(td.Name, func(t *testing.T) { + ctx, vppCalls := setup(t) + defer teardown(ctx) + // prepare reply, make call and verify + for _, reply := range td.MockReplies { + ctx.MockVpp.MockReply(reply) + } + err := vppCalls.AddPolicy(td.Policy) + td.Verify(err, ctx.MockChannel.Msgs) + }) + } +} + +// TestDeletePolicy tests all cases for method DeletePolicy +func TestDeletePolicy(t *testing.T) { + // Prepare different cases + cases := []struct { + Name string + BSID net.IP + MockReply govppapi.Message + Verify func(error, govppapi.Message) + }{ + { + Name: "simple delete of policy", + BSID: sidA[:], + MockReply: &vpp_sr.SrPolicyDelReply{}, + Verify: func(err error, catchedMsg govppapi.Message) { + Expect(err).ShouldNot(HaveOccurred()) + Expect(catchedMsg).To(Equal(&vpp_sr.SrPolicyDel{ + BsidAddr: sidA, + })) + }, + }, + { + Name: "failure propagation from VPP", + BSID: sidA[:], + MockReply: &vpp_sr.SrPolicyDelReply{Retval: 1}, + Verify: func(err error, msg govppapi.Message) { + Expect(err).Should(HaveOccurred()) + }, + }, + } + + // Run all cases + for _, td := range cases { + t.Run(td.Name, func(t *testing.T) { + ctx, vppCalls := setup(t) + defer teardown(ctx) + // data and prepare case + policy := policy(td.BSID, 0, true, true, policySegmentList(1, sidA[:], sidB[:], sidC[:])) + vppCalls.AddPolicy(policy) + ctx.MockVpp.MockReply(td.MockReply) + // make the call and verify + err := vppCalls.DeletePolicy(td.BSID) + td.Verify(err, ctx.MockChannel.Msg) + }) + } +} + +// TestAddPolicySegmentList tests all cases for method AddPolicySegment +func TestAddPolicySegmentList(t *testing.T) { + // Prepare different cases + cases := []struct { + Name string + Policy *srv6.Policy + PolicySegmentList *srv6.Policy_SegmentList + MockReply govppapi.Message + Verify func(error, govppapi.Message) + }{ + { + Name: "simple addition of policy segment", + Policy: policy(sidA[:], 10, false, true), + PolicySegmentList: policySegmentList(1, sidA[:], sidB[:], sidC[:]), + MockReply: &vpp_sr.SrPolicyModReply{}, + Verify: func(err error, catchedMsg govppapi.Message) { + Expect(err).ShouldNot(HaveOccurred()) + Expect(catchedMsg).To(Equal(&vpp_sr.SrPolicyMod{ + BsidAddr: sidA, + Operation: vpp2306.AddSRList, + FibTable: 10, // installationVrfId + Sids: vpp_sr.Srv6SidList{ + Weight: 1, + NumSids: 3, + Sids: [16]ip_types.IP6Address{sidA, sidB, sidC}, + }, + })) + }, + }, + { + Name: "invalid SID (not IP address) in segment list", + Policy: policy(sidA[:], 10, false, true), + PolicySegmentList: &srv6.Policy_SegmentList{ + Weight: 1, + Segments: []string{sidToStr(sidA), invalidIPAddress, sidToStr(sidC)}, + }, + MockReply: &vpp_sr.SrPolicyModReply{}, + Verify: func(err error, catchedMsg govppapi.Message) { + Expect(err).Should(HaveOccurred()) + }, + }, + { + Name: "invalid binding SID (not IP address) in policy", + Policy: &srv6.Policy{ + Bsid: invalidIPAddress, + InstallationVrfId: 10, + SprayBehaviour: false, + SrhEncapsulation: true, + }, + PolicySegmentList: policySegmentList(1, sidA[:], sidB[:], sidC[:]), + MockReply: &vpp_sr.SrPolicyModReply{}, + Verify: func(err error, catchedMsg govppapi.Message) { + Expect(err).Should(HaveOccurred()) + }, + }, + { + Name: "failure propagation from VPP", + Policy: policy(sidA[:], 0, true, true), + PolicySegmentList: policySegmentList(1, sidA[:], sidB[:], sidC[:]), + MockReply: &vpp_sr.SrPolicyModReply{Retval: 1}, + Verify: func(err error, msg govppapi.Message) { + Expect(err).Should(HaveOccurred()) + }, + }, + } + + // Run all cases + for _, td := range cases { + t.Run(td.Name, func(t *testing.T) { + ctx, vppCalls := setup(t) + defer teardown(ctx) + // prepare reply, make call and verify + ctx.MockVpp.MockReply(td.MockReply) + err := vppCalls.AddPolicySegmentList(td.PolicySegmentList, td.Policy) + td.Verify(err, ctx.MockChannel.Msg) + }) + } +} + +// TestDeletePolicySegmentList tests all cases for method DeletePolicySegment +func TestDeletePolicySegmentList(t *testing.T) { + // Prepare different cases + cases := []struct { + Name string + Policy *srv6.Policy + PolicySegmentList *srv6.Policy_SegmentList + SegmentIndex uint32 + MockReply govppapi.Message + Verify func(error, govppapi.Message) + }{ + { + Name: "simple deletion of policy segment", + Policy: policy(sidA[:], 10, false, true, policySegmentList(1, sidA[:], sidB[:], sidC[:])), + PolicySegmentList: policySegmentList(1, sidA[:], sidB[:], sidC[:]), + SegmentIndex: 111, + MockReply: &vpp_sr.SrPolicyModReply{}, + Verify: func(err error, catchedMsg govppapi.Message) { + Expect(err).ShouldNot(HaveOccurred()) + Expect(catchedMsg).To(Equal(&vpp_sr.SrPolicyMod{ + BsidAddr: sidA, + Operation: vpp2306.DeleteSRList, + SlIndex: 111, + FibTable: 10, // installationVrfId + Sids: vpp_sr.Srv6SidList{ + Weight: 1, + NumSids: 3, + Sids: [16]ip_types.IP6Address{sidA, sidB, sidC}, + }, + })) + }, + }, + { + Name: "invalid SID (not IP address) in segment list", + Policy: policy(sidA[:], 10, false, true, + &srv6.Policy_SegmentList{ + Weight: 1, + Segments: []string{sidToStr(sidA), invalidIPAddress, sidToStr(sidC)}, + }), + PolicySegmentList: &srv6.Policy_SegmentList{ + Weight: 1, + Segments: []string{sidToStr(sidA), invalidIPAddress, sidToStr(sidC)}, + }, + SegmentIndex: 111, + MockReply: &vpp_sr.SrPolicyModReply{}, + Verify: func(err error, catchedMsg govppapi.Message) { + Expect(err).Should(HaveOccurred()) + }, + }, + { + Name: "failure propagation from VPP", + Policy: policy(sidA[:], 0, true, true, policySegmentList(1, sidA[:], sidB[:], sidC[:])), + PolicySegmentList: policySegmentList(1, sidA[:], sidB[:], sidC[:]), + SegmentIndex: 111, + MockReply: &vpp_sr.SrPolicyModReply{Retval: 1}, + Verify: func(err error, msg govppapi.Message) { + Expect(err).Should(HaveOccurred()) + }, + }, + } + + // Run all cases + for _, td := range cases { + t.Run(td.Name, func(t *testing.T) { + ctx, vppCalls := setup(t) + defer teardown(ctx) + // prepare reply, make call and verify + ctx.MockVpp.MockReply(td.MockReply) + err := vppCalls.DeletePolicySegmentList(td.PolicySegmentList, td.SegmentIndex, td.Policy) + td.Verify(err, ctx.MockChannel.Msg) + }) + } +} + +// TestAddSteering tests all cases for method AddSteering +func TestAddSteering(t *testing.T) { + testAddRemoveSteering(t, false) +} + +// TestRemoveSteering tests all cases for method RemoveSteering +func TestRemoveSteering(t *testing.T) { + testAddRemoveSteering(t, true) +} + +func testAddRemoveSteering(t *testing.T, removal bool) { + action := "addition" + if removal { + action = "removal" + } + // Prepare different cases + cases := []struct { + Name string + Steering *srv6.Steering + MockReply govppapi.Message + Verify func(error, govppapi.Message) + }{ + { + Name: action + " of IPv6 L3 steering", + Steering: &srv6.Steering{ + PolicyRef: &srv6.Steering_PolicyBsid{ + PolicyBsid: sidToStr(sidA), + }, + Traffic: &srv6.Steering_L3Traffic_{ + L3Traffic: &srv6.Steering_L3Traffic{ + InstallationVrfId: 10, + PrefixAddress: "1::/64", + }, + }, + }, + MockReply: &vpp_sr.SrSteeringAddDelReply{}, + Verify: func(err error, catchedMsg govppapi.Message) { + Expect(err).ShouldNot(HaveOccurred()) + Expect(catchedMsg).To(Equal(&vpp_sr.SrSteeringAddDel{ + IsDel: removal, + BsidAddr: sidA, + SrPolicyIndex: uint32(0), + TableID: 10, + TrafficType: vpp2306.SteerTypeIPv6, + Prefix: ip_types.Prefix{Address: toAddress("1::"), Len: 64}, + })) + }, + }, + { + Name: action + " of IPv4 L3 steering", + Steering: &srv6.Steering{ + PolicyRef: &srv6.Steering_PolicyBsid{ + PolicyBsid: sidToStr(sidA), + }, + Traffic: &srv6.Steering_L3Traffic_{ + L3Traffic: &srv6.Steering_L3Traffic{ + InstallationVrfId: 10, + PrefixAddress: "1.2.3.4/24", + }, + }, + }, + MockReply: &vpp_sr.SrSteeringAddDelReply{}, + Verify: func(err error, catchedMsg govppapi.Message) { + Expect(err).ShouldNot(HaveOccurred()) + Expect(catchedMsg).To(Equal(&vpp_sr.SrSteeringAddDel{ + IsDel: removal, + BsidAddr: sidA, + SrPolicyIndex: uint32(0), + TableID: 10, + TrafficType: vpp2306.SteerTypeIPv4, + Prefix: ip_types.Prefix{Address: toAddress("1.2.3.4"), Len: 24}, + })) + }, + }, + { + Name: action + " of L2 steering", + Steering: &srv6.Steering{ + PolicyRef: &srv6.Steering_PolicyBsid{ + PolicyBsid: sidToStr(sidA), + }, + Traffic: &srv6.Steering_L2Traffic_{ + L2Traffic: &srv6.Steering_L2Traffic{ + InterfaceName: ifaceA, + }, + }, + }, + MockReply: &vpp_sr.SrSteeringAddDelReply{}, + Verify: func(err error, catchedMsg govppapi.Message) { + Expect(err).ShouldNot(HaveOccurred()) + Expect(catchedMsg).To(Equal(&vpp_sr.SrSteeringAddDel{ + IsDel: removal, + BsidAddr: sidA, + SrPolicyIndex: uint32(0), + TrafficType: vpp2306.SteerTypeL2, + SwIfIndex: swIndexA, + })) + }, + }, + { + Name: action + " of IPv6 L3 steering with Policy referencing by index", + Steering: &srv6.Steering{ + PolicyRef: &srv6.Steering_PolicyIndex{ + PolicyIndex: 20, + }, + Traffic: &srv6.Steering_L3Traffic_{ + L3Traffic: &srv6.Steering_L3Traffic{ + InstallationVrfId: 10, + PrefixAddress: "1::/64", + }, + }, + }, + MockReply: &vpp_sr.SrSteeringAddDelReply{}, + Verify: func(err error, catchedMsg govppapi.Message) { + Expect(err).ShouldNot(HaveOccurred()) + Expect(catchedMsg).To(Equal(&vpp_sr.SrSteeringAddDel{ + IsDel: removal, + SrPolicyIndex: uint32(20), + TableID: 10, + TrafficType: vpp2306.SteerTypeIPv6, + Prefix: ip_types.Prefix{Address: toAddress("1::"), Len: 64}, + })) + }, + }, + { + Name: "missing policy reference ( " + action + " of IPv6 L3 steering)", + Steering: &srv6.Steering{ + Traffic: &srv6.Steering_L3Traffic_{ + L3Traffic: &srv6.Steering_L3Traffic{ + InstallationVrfId: 10, + PrefixAddress: "1::/64", + }, + }, + }, + MockReply: &vpp_sr.SrSteeringAddDelReply{}, + Verify: func(err error, catchedMsg govppapi.Message) { + Expect(err).Should(HaveOccurred()) + }, + }, + { + Name: "missing traffic ( " + action + " of IPv6 L3 steering)", + Steering: &srv6.Steering{ + PolicyRef: &srv6.Steering_PolicyBsid{ + PolicyBsid: sidToStr(sidA), + }, + }, + MockReply: &vpp_sr.SrSteeringAddDelReply{}, + Verify: func(err error, catchedMsg govppapi.Message) { + Expect(err).Should(HaveOccurred()) + }, + }, + { + Name: "invalid prefix (" + action + " of IPv4 L3 steering)", + Steering: &srv6.Steering{ + PolicyRef: &srv6.Steering_PolicyBsid{ + PolicyBsid: sidToStr(sidA), + }, + Traffic: &srv6.Steering_L3Traffic_{ + L3Traffic: &srv6.Steering_L3Traffic{ + InstallationVrfId: 10, + PrefixAddress: invalidIPAddress, + }, + }, + }, + MockReply: &vpp_sr.SrSteeringAddDelReply{}, + Verify: func(err error, catchedMsg govppapi.Message) { + Expect(err).Should(HaveOccurred()) + }, + }, + { + Name: "interface without index (" + action + " of L2 steering)", + Steering: &srv6.Steering{ + PolicyRef: &srv6.Steering_PolicyBsid{ + PolicyBsid: sidToStr(sidA), + }, + Traffic: &srv6.Steering_L2Traffic_{ + L2Traffic: &srv6.Steering_L2Traffic{ + InterfaceName: ifaceBOutOfidxs, + }, + }, + }, + MockReply: &vpp_sr.SrSteeringAddDelReply{}, + Verify: func(err error, catchedMsg govppapi.Message) { + Expect(err).Should(HaveOccurred()) + }, + }, + { + Name: "invalid BSID (not IP address) as policy reference", + Steering: &srv6.Steering{ + PolicyRef: &srv6.Steering_PolicyBsid{ + PolicyBsid: invalidIPAddress, + }, + Traffic: &srv6.Steering_L3Traffic_{ + L3Traffic: &srv6.Steering_L3Traffic{ + InstallationVrfId: 10, + PrefixAddress: "1::/64", + }, + }, + }, + MockReply: &vpp_sr.SrSteeringAddDelReply{}, + Verify: func(err error, catchedMsg govppapi.Message) { + Expect(err).Should(HaveOccurred()) + }, + }, + { + Name: "failure propagation from VPP", + Steering: &srv6.Steering{ + PolicyRef: &srv6.Steering_PolicyBsid{ + PolicyBsid: sidToStr(sidA), + }, + Traffic: &srv6.Steering_L3Traffic_{ + L3Traffic: &srv6.Steering_L3Traffic{ + InstallationVrfId: 10, + PrefixAddress: "1::/64", + }, + }, + }, + MockReply: &vpp_sr.SrSteeringAddDelReply{Retval: 1}, + Verify: func(err error, msg govppapi.Message) { + Expect(err).Should(HaveOccurred()) + }, + }, + } + + // Run all cases + for _, td := range cases { + t.Run(td.Name, func(t *testing.T) { + ctx, vppCalls := setup(t) + defer teardown(ctx) + // prepare reply, make call and verify + ctx.MockVpp.MockReply(td.MockReply) + var err error + if removal { + err = vppCalls.RemoveSteering(td.Steering) + } else { + err = vppCalls.AddSteering(td.Steering) + } + td.Verify(err, ctx.MockChannel.Msg) + }) + } +} + +// RetrievePolicyIndexInfo tests all cases for method RetrievePolicyIndexInfo +func TestRetrievePolicyIndexInfo(t *testing.T) { + correctCLIOutput := ` +[4].- BSID: a:: + + Behavior: SRH insertion + + Type: Spray + + FIB table: 0 + + Segment Lists: + + [2].- < a::, b::, c::,  > weight: 1 + [3].- < b::, b::, c::,  > weight: 1 + [4].- < c::, b::, c::,  > weight: 1 + +----------- +` + correctPolicyIndex := uint32(4) + segmentListABC := policySegmentList(1, sidA[:], sidB[:], sidC[:]) + segmentListBBC := policySegmentList(1, sidB[:], sidB[:], sidC[:]) + notExistingSegmentListCCC := policySegmentList(1, sidC[:], sidC[:], sidC[:]) + + // Prepare different cases + cases := []struct { + Name string + Policy *srv6.Policy + MockReply govppapi.Message + ExpectedPolicyIndex uint32 + ExpectedSegmentListIndexes map[*srv6.Policy_SegmentList]uint32 + ExpectingFailure bool + }{ + { + Name: "basic successful index retrieval", + Policy: policy(sidA[:], 10, false, true, segmentListABC, segmentListBBC), + MockReply: &vlib.CliInbandReply{ + Reply: correctCLIOutput, + Retval: 0, + }, + ExpectedPolicyIndex: correctPolicyIndex, + ExpectedSegmentListIndexes: map[*srv6.Policy_SegmentList]uint32{segmentListABC: uint32(2), segmentListBBC: uint32(3)}, + }, + { + Name: "failure propagation from VPP", + Policy: policy(sidA[:], 10, false, true, segmentListABC, segmentListBBC), + MockReply: &vlib.CliInbandReply{Retval: 1}, + ExpectingFailure: true, + }, + { + Name: "searching for not existing policy ", + Policy: policy(sidC[:], 10, false, true, segmentListABC, segmentListBBC), + MockReply: &vlib.CliInbandReply{ + Reply: correctCLIOutput, + Retval: 0, + }, + ExpectingFailure: true, + }, + { + Name: "searching for not existing policy segment list", + Policy: policy(sidA[:], 10, false, true, notExistingSegmentListCCC), + MockReply: &vlib.CliInbandReply{ + Reply: correctCLIOutput, + Retval: 0, + }, + ExpectingFailure: true, + }, + } + // Run all cases + for _, td := range cases { + t.Run(td.Name, func(t *testing.T) { + ctx, vppCalls := setup(t) + defer teardown(ctx) + // prepare reply, make call and verify + ctx.MockVpp.MockReply(td.MockReply) + resultPolicyIndex, resultSlIndexes, err := vppCalls.RetrievePolicyIndexInfo(td.Policy) + Expect(ctx.MockChannel.Msg).To(Equal(&vlib.CliInband{ + Cmd: "sh sr policies", + })) + if td.ExpectingFailure { + Expect(err).Should(HaveOccurred()) + } else { + Expect(err).ShouldNot(HaveOccurred()) + Expect(resultPolicyIndex).To(Equal(td.ExpectedPolicyIndex)) + Expect(resultSlIndexes).To(Equal(td.ExpectedSegmentListIndexes)) + } + }) + } +} + +func setup(t *testing.T) (*vppmock.TestCtx, vppcalls.SRv6VppAPI) { + ctx := vppmock.SetupTestCtx(t) + log := logrus.NewLogger("test") + swIfIndex := ifaceidx.NewIfaceIndex(log, "test") + swIfIndex.Put(ifaceA, &ifaceidx.IfaceMetadata{SwIfIndex: swIndexA}) + vppCalls := vpp2306.NewSRv6VppHandler(ctx.MockVPPClient, swIfIndex, log) + return ctx, vppCalls +} + +func teardown(ctx *vppmock.TestCtx) { + ctx.TeardownTestCtx() +} + +func sid(str string) ip_types.IP6Address { + bsid, err := parseIPv6(str) + if err != nil { + panic(fmt.Sprintf("can't parse %q into SRv6 BSID (IPv6 address)", str)) + } + var ip ip_types.IP6Address + copy(ip[:], bsid) + return ip +} + +// parseIPv6 parses string to IPv6 address (including IPv4 address converted to IPv6 address) +func parseIPv6(str string) (net.IP, error) { + ip := net.ParseIP(str) + if ip == nil { + return nil, fmt.Errorf(" %q is not ip address", str) + } + ipv6 := ip.To16() + if ipv6 == nil { + return nil, fmt.Errorf(" %q is not ipv6 address", str) + } + return ipv6, nil +} + +func policy(bsid srv6.SID, installationVrfId uint32, sprayBehaviour bool, srhEncapsulation bool, segmentLists ...*srv6.Policy_SegmentList) *srv6.Policy { + return &srv6.Policy{ + Bsid: bsid.String(), + InstallationVrfId: installationVrfId, + SprayBehaviour: sprayBehaviour, + SrhEncapsulation: srhEncapsulation, + SegmentLists: segmentLists, + } +} + +func policySegmentList(weight uint32, sids ...srv6.SID) *srv6.Policy_SegmentList { + segments := make([]string, len(sids)) + for i, sid := range sids { + segments[i] = sid.String() + } + + return &srv6.Policy_SegmentList{ + Weight: weight, + Segments: segments, + } +} + +func sidToStr(sid ip_types.IP6Address) string { + return srv6.SID(sid[:]).String() +} + +func toAddress(ip interface{}) (addr ip_types.Address) { + switch ip := ip.(type) { + case string: + addr, _ = vpp2306.IPToAddress(ip) + case net.IP: + addr, _ = vpp2306.IPToAddress(ip.String()) + default: + panic(fmt.Sprintf("cannot convert to ip_types.Address from type %T", ip)) + } + return +} diff --git a/plugins/vpp/srplugin/vppcalls/vpp2306/vppcalls_handlers.go b/plugins/vpp/srplugin/vppcalls/vpp2306/vppcalls_handlers.go new file mode 100644 index 0000000000..8921881570 --- /dev/null +++ b/plugins/vpp/srplugin/vppcalls/vpp2306/vppcalls_handlers.go @@ -0,0 +1,106 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "fmt" + "net" + + govppapi "go.fd.io/govpp/api" + "go.ligato.io/cn-infra/v2/logging" + + core_vppcalls "go.ligato.io/vpp-agent/v3/plugins/govppmux/vppcalls" + core_vpp2306 "go.ligato.io/vpp-agent/v3/plugins/govppmux/vppcalls/vpp2306" + "go.ligato.io/vpp-agent/v3/plugins/vpp" + vpp2306 "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip_types" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/sr" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/vpe" + "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/ifaceidx" + "go.ligato.io/vpp-agent/v3/plugins/vpp/srplugin/vppcalls" +) + +func init() { + msgs := vpp.Messages( + sr.AllMessages, + vpe.AllMessages, // using also vpe -> need to have correct vpp version also for vpe + ) + vppcalls.AddHandlerVersion(vpp2306.Version, msgs.AllMessages(), NewSRv6VppHandler) +} + +// SRv6VppHandler is accessor for SRv6-related vppcalls methods +type SRv6VppHandler struct { + core_vppcalls.VppCoreAPI + + log logging.Logger + callsChannel govppapi.Channel + ifIndexes ifaceidx.IfaceMetadataIndex +} + +// NewSRv6VppHandler creates new instance of SRv6 vppcalls handler +func NewSRv6VppHandler(c vpp.Client, ifIdx ifaceidx.IfaceMetadataIndex, log logging.Logger) vppcalls.SRv6VppAPI { + vppChan, err := c.NewAPIChannel() + if err != nil { + logging.Warnf("failed to create API channel") + return nil + } + return &SRv6VppHandler{ + callsChannel: vppChan, + ifIndexes: ifIdx, + log: log, + VppCoreAPI: core_vpp2306.NewVpeHandler(c), + } +} + +func addressToIP(address ip_types.Address) net.IP { + if address.Af == ip_types.ADDRESS_IP6 { + ipAddr := address.Un.GetIP6() + return net.IP(ipAddr[:]).To16() + } + ipAddr := address.Un.GetIP4() + return net.IP(ipAddr[:]).To4() +} + +// parseIPv6 parses string to IPv6 address (including IPv4 address converted to IPv6 address) +func parseIPv6(str string) (net.IP, error) { + ip := net.ParseIP(str) + if ip == nil { + return nil, fmt.Errorf(" %q is not ip address", str) + } + ipv6 := ip.To16() + if ipv6 == nil { + return nil, fmt.Errorf(" %q is not ipv6 address", str) + } + return ipv6, nil +} + +func IPToAddress(ipstr string) (addr ip_types.Address, err error) { + netIP := net.ParseIP(ipstr) + if netIP == nil { + return ip_types.Address{}, fmt.Errorf("invalid IP: %q", ipstr) + } + if ip4 := netIP.To4(); ip4 == nil { + addr.Af = ip_types.ADDRESS_IP6 + var ip6addr ip_types.IP6Address + copy(ip6addr[:], netIP.To16()) + addr.Un.SetIP6(ip6addr) + } else { + addr.Af = ip_types.ADDRESS_IP4 + var ip4addr ip_types.IP4Address + copy(ip4addr[:], ip4.To4()) + addr.Un.SetIP4(ip4addr) + } + return +} diff --git a/plugins/vpp/stnplugin/stnplugin.go b/plugins/vpp/stnplugin/stnplugin.go index 3d457f7742..36d4f2e06d 100644 --- a/plugins/vpp/stnplugin/stnplugin.go +++ b/plugins/vpp/stnplugin/stnplugin.go @@ -29,6 +29,7 @@ import ( _ "go.ligato.io/vpp-agent/v3/plugins/vpp/stnplugin/vppcalls/vpp2202" _ "go.ligato.io/vpp-agent/v3/plugins/vpp/stnplugin/vppcalls/vpp2210" + _ "go.ligato.io/vpp-agent/v3/plugins/vpp/stnplugin/vppcalls/vpp2306" ) // STNPlugin configures VPP STN rules using GoVPP. diff --git a/plugins/vpp/stnplugin/vppcalls/vpp2306/dump_stn_vppcalls.go b/plugins/vpp/stnplugin/vppcalls/vpp2306/dump_stn_vppcalls.go new file mode 100644 index 0000000000..0628acda8d --- /dev/null +++ b/plugins/vpp/stnplugin/vppcalls/vpp2306/dump_stn_vppcalls.go @@ -0,0 +1,73 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "net" + + "github.com/pkg/errors" + + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip_types" + "go.ligato.io/vpp-agent/v3/plugins/vpp/stnplugin/vppcalls" + + vpp_stn "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/stn" + stn "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/stn" +) + +// DumpSTNRules implements STN handler, it returns all STN rules present on the VPP +func (h *StnVppHandler) DumpSTNRules() ([]*vppcalls.StnDetails, error) { + var stnDetails []*vppcalls.StnDetails + + req := &vpp_stn.StnRulesDump{} + reqCtx := h.callsChannel.SendMultiRequest(req) + for { + msg := &vpp_stn.StnRulesDetails{} + stop, err := reqCtx.ReceiveReply(msg) + if stop { + break + } + if err != nil { + return nil, errors.Errorf("error reading STN rules from the VPP: %v", err) + } + ifName, _, found := h.ifIndexes.LookupBySwIfIndex(uint32(msg.SwIfIndex)) + if !found { + h.log.Warnf("STN dump: interface name not found for index %d", msg.SwIfIndex) + } + + var stnIP net.IP + if msg.IPAddress.Af == ip_types.ADDRESS_IP4 { + stnAddr := msg.IPAddress.Un.GetIP4() + stnIP = net.IP(stnAddr[:]) + } else { + stnAddr := msg.IPAddress.Un.GetIP6() + stnIP = net.IP(stnAddr[:]) + } + + stnRule := &stn.Rule{ + IpAddress: stnIP.String(), + Interface: ifName, + } + stnMeta := &vppcalls.StnMeta{ + IfIdx: uint32(msg.SwIfIndex), + } + + stnDetails = append(stnDetails, &vppcalls.StnDetails{ + Rule: stnRule, + Meta: stnMeta, + }) + } + + return stnDetails, nil +} diff --git a/plugins/vpp/stnplugin/vppcalls/vpp2306/stn_vppcalls.go b/plugins/vpp/stnplugin/vppcalls/vpp2306/stn_vppcalls.go new file mode 100644 index 0000000000..fee56bae4a --- /dev/null +++ b/plugins/vpp/stnplugin/vppcalls/vpp2306/stn_vppcalls.go @@ -0,0 +1,95 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "fmt" + "net" + "strings" + + "github.com/pkg/errors" + + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface_types" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip_types" + vpp_stn "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/stn" + stn "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/stn" +) + +// AddSTNRule implements STN handler, adds a new STN rule to the VPP. +func (h *StnVppHandler) AddSTNRule(stnRule *stn.Rule) error { + return h.addDelStnRule(stnRule, true) +} + +// DeleteSTNRule implements STN handler, removes the provided STN rule from the VPP. +func (h *StnVppHandler) DeleteSTNRule(stnRule *stn.Rule) error { + return h.addDelStnRule(stnRule, false) +} + +func (h *StnVppHandler) addDelStnRule(stnRule *stn.Rule, isAdd bool) error { + // get interface index + ifaceMeta, found := h.ifIndexes.LookupByName(stnRule.Interface) + if !found { + return errors.New("failed to get interface metadata") + } + swIfIndex := ifaceMeta.GetIndex() + + // remove mask from IP address if necessary + ipAddr := stnRule.IpAddress + ipParts := strings.Split(ipAddr, "/") + if len(ipParts) > 1 { + h.log.Debugf("STN IP address %s is defined with mask, removing it") + ipAddr = ipParts[0] + } + + // parse IP address + ip, err := ipToAddress(ipAddr) + if err != nil { + return err + } + + // add STN rule + req := &vpp_stn.StnAddDelRule{ + IPAddress: ip, + SwIfIndex: interface_types.InterfaceIndex(swIfIndex), + IsAdd: isAdd, + } + reply := &vpp_stn.StnAddDelRuleReply{} + + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + + return nil + +} + +func ipToAddress(address string) (dhcpAddr ip_types.Address, err error) { + netIP := net.ParseIP(address) + if netIP == nil { + return ip_types.Address{}, fmt.Errorf("invalid IP: %q", address) + } + if ip4 := netIP.To4(); ip4 == nil { + dhcpAddr.Af = ip_types.ADDRESS_IP6 + var ip6addr ip_types.IP6Address + copy(ip6addr[:], netIP.To16()) + dhcpAddr.Un.SetIP6(ip6addr) + } else { + dhcpAddr.Af = ip_types.ADDRESS_IP4 + var ip4addr ip_types.IP4Address + copy(ip4addr[:], ip4) + dhcpAddr.Un.SetIP4(ip4addr) + } + return +} diff --git a/plugins/vpp/stnplugin/vppcalls/vpp2306/stn_vppcalls_test.go b/plugins/vpp/stnplugin/vppcalls/vpp2306/stn_vppcalls_test.go new file mode 100644 index 0000000000..83c8c69c1c --- /dev/null +++ b/plugins/vpp/stnplugin/vppcalls/vpp2306/stn_vppcalls_test.go @@ -0,0 +1,151 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306_test + +import ( + "testing" + + . "github.com/onsi/gomega" + "go.ligato.io/cn-infra/v2/logging/logrus" + + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip_types" + vpp_stn "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/stn" + "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/ifaceidx" + "go.ligato.io/vpp-agent/v3/plugins/vpp/stnplugin/vppcalls" + vpp2306 "go.ligato.io/vpp-agent/v3/plugins/vpp/stnplugin/vppcalls/vpp2306" + "go.ligato.io/vpp-agent/v3/plugins/vpp/vppmock" + stn "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/stn" +) + +func TestAddStnRule(t *testing.T) { + ctx, stnHandler, ifIndexes := stnTestSetup(t) + defer ctx.TeardownTestCtx() + + ifIndexes.Put("if1", &ifaceidx.IfaceMetadata{SwIfIndex: 1}) + + ctx.MockVpp.MockReply(&vpp_stn.StnAddDelRuleReply{}) + + err := stnHandler.AddSTNRule(&stn.Rule{ + Interface: "if1", + IpAddress: "10.0.0.1", + }) + + Expect(err).To(BeNil()) + vppMsg, ok := ctx.MockChannel.Msg.(*vpp_stn.StnAddDelRule) + Expect(ok).To(BeTrue()) + Expect(vppMsg.SwIfIndex).To(BeEquivalentTo(1)) + Expect(vppMsg.IPAddress.Un.GetIP4()).To(BeEquivalentTo(ip_types.IP4Address{10, 0, 0, 1})) + Expect(vppMsg.IPAddress.Af).To(Equal(ip_types.ADDRESS_IP4)) + Expect(vppMsg.IsAdd).To(BeTrue()) +} + +func TestAddStnRuleIPv6(t *testing.T) { + ctx, stnHandler, ifIndexes := stnTestSetup(t) + defer ctx.TeardownTestCtx() + + ifIndexes.Put("if1", &ifaceidx.IfaceMetadata{SwIfIndex: 1}) + + ctx.MockVpp.MockReply(&vpp_stn.StnAddDelRuleReply{}) + + err := stnHandler.AddSTNRule(&stn.Rule{ + Interface: "if1", + IpAddress: "2001:db8:0:1:1:1:1:1", + }) + + Expect(err).To(BeNil()) + vppMsg, ok := ctx.MockChannel.Msg.(*vpp_stn.StnAddDelRule) + Expect(ok).To(BeTrue()) + Expect(vppMsg.SwIfIndex).To(BeEquivalentTo(1)) + Expect(vppMsg.IPAddress.Un.GetIP6()).To(BeEquivalentTo(ip_types.IP6Address{32, 1, 13, 184, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1})) + Expect(vppMsg.IPAddress.Af).To(Equal(ip_types.ADDRESS_IP6)) + Expect(vppMsg.IsAdd).To(BeTrue()) +} + +func TestAddStnRuleInvalidIP(t *testing.T) { + ctx, stnHandler, ifIndexes := stnTestSetup(t) + defer ctx.TeardownTestCtx() + + ifIndexes.Put("if1", &ifaceidx.IfaceMetadata{SwIfIndex: 1}) + + ctx.MockVpp.MockReply(&vpp_stn.StnAddDelRuleReply{}) + + err := stnHandler.AddSTNRule(&stn.Rule{ + Interface: "if1", + IpAddress: "invalid-ip", + }) + + Expect(err).ToNot(BeNil()) +} + +func TestAddStnRuleError(t *testing.T) { + ctx, stnHandler, ifIndexes := stnTestSetup(t) + defer ctx.TeardownTestCtx() + + ifIndexes.Put("if1", &ifaceidx.IfaceMetadata{SwIfIndex: 1}) + + ctx.MockVpp.MockReply(&vpp_stn.StnAddDelRule{}) + + err := stnHandler.AddSTNRule(&stn.Rule{ + Interface: "if1", + IpAddress: "10.0.0.1", + }) + + Expect(err).ToNot(BeNil()) +} + +func TestAddStnRuleRetval(t *testing.T) { + ctx, stnHandler, ifIndexes := stnTestSetup(t) + defer ctx.TeardownTestCtx() + + ifIndexes.Put("if1", &ifaceidx.IfaceMetadata{SwIfIndex: 1}) + + ctx.MockVpp.MockReply(&vpp_stn.StnAddDelRuleReply{ + Retval: 1, + }) + + err := stnHandler.AddSTNRule(&stn.Rule{ + Interface: "if1", + IpAddress: "10.0.0.1", + }) + + Expect(err).ToNot(BeNil()) +} + +func TestDelStnRule(t *testing.T) { + ctx, stnHandler, ifIndexes := stnTestSetup(t) + defer ctx.TeardownTestCtx() + + ifIndexes.Put("if1", &ifaceidx.IfaceMetadata{SwIfIndex: 1}) + + ctx.MockVpp.MockReply(&vpp_stn.StnAddDelRuleReply{}) + + err := stnHandler.DeleteSTNRule(&stn.Rule{ + Interface: "if1", + IpAddress: "10.0.0.1", + }) + + Expect(err).To(BeNil()) + vppMsg, ok := ctx.MockChannel.Msg.(*vpp_stn.StnAddDelRule) + Expect(ok).To(BeTrue()) + Expect(vppMsg.IsAdd).To(BeFalse()) +} + +func stnTestSetup(t *testing.T) (*vppmock.TestCtx, vppcalls.StnVppAPI, ifaceidx.IfaceMetadataIndexRW) { + ctx := vppmock.SetupTestCtx(t) + logger := logrus.NewLogger("test-log") + ifIndexes := ifaceidx.NewIfaceIndex(logger, "stn-if-idx") + stnHandler := vpp2306.NewStnVppHandler(ctx.MockChannel, ifIndexes, logrus.DefaultLogger()) + return ctx, stnHandler, ifIndexes +} diff --git a/plugins/vpp/stnplugin/vppcalls/vpp2306/vppcalls_handler.go b/plugins/vpp/stnplugin/vppcalls/vpp2306/vppcalls_handler.go new file mode 100644 index 0000000000..858008082b --- /dev/null +++ b/plugins/vpp/stnplugin/vppcalls/vpp2306/vppcalls_handler.go @@ -0,0 +1,50 @@ +// Copyright (c) 2022 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + govppapi "go.fd.io/govpp/api" + "go.ligato.io/cn-infra/v2/logging" + + vpp2306 "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306" + vpp_stn "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/stn" + "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/ifaceidx" + "go.ligato.io/vpp-agent/v3/plugins/vpp/stnplugin/vppcalls" +) + +func init() { + var msgs []govppapi.Message + msgs = append(msgs, vpp_stn.AllMessages()...) + + vppcalls.AddStnHandlerVersion(vpp2306.Version, msgs, NewStnVppHandler) +} + +// StnVppHandler is accessor for STN-related vppcalls methods +type StnVppHandler struct { + callsChannel govppapi.Channel + ifIndexes ifaceidx.IfaceMetadataIndex + log logging.Logger +} + +// NewStnVppHandler creates new instance of STN vppcalls handler +func NewStnVppHandler( + callsChan govppapi.Channel, ifIndexes ifaceidx.IfaceMetadataIndex, log logging.Logger, +) vppcalls.StnVppAPI { + return &StnVppHandler{ + callsChannel: callsChan, + ifIndexes: ifIndexes, + log: log, + } +} diff --git a/plugins/vpp/wireguardplugin/vppcalls/vpp2306/dump_vppcalls.go b/plugins/vpp/wireguardplugin/vppcalls/vpp2306/dump_vppcalls.go new file mode 100644 index 0000000000..e8b93c1d38 --- /dev/null +++ b/plugins/vpp/wireguardplugin/vppcalls/vpp2306/dump_vppcalls.go @@ -0,0 +1,73 @@ +// Copyright (c) 2022 Doc.ai and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "encoding/base64" + + vpp_wg "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/wireguard" + wg "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/wireguard" +) + +// DumpWgPeers implements wg handler. +func (h *WgVppHandler) DumpWgPeers() (peerList []*wg.Peer, err error) { + // index of ^uint32(0) dumps all peers + req := &vpp_wg.WireguardPeersDump{PeerIndex: ^uint32(0)} + requestCtx := h.callsChannel.SendMultiRequest(req) + + var vppPeerList []*vpp_wg.WireguardPeersDetails + for { + vppPeerDetails := &vpp_wg.WireguardPeersDetails{} + stop, err := requestCtx.ReceiveReply(vppPeerDetails) + if stop { + break + } + if err != nil { + return nil, err + } + vppPeerList = append(vppPeerList, vppPeerDetails) + } + + for _, vppPeerDetails := range vppPeerList { + peerDetails := &wg.Peer{ + Port: uint32(vppPeerDetails.Peer.Port), + PersistentKeepalive: uint32(vppPeerDetails.Peer.PersistentKeepalive), + Flags: uint32(vppPeerDetails.Peer.Flags), + } + + peerDetails.PublicKey = base64.StdEncoding.EncodeToString(vppPeerDetails.Peer.PublicKey) + + for _, prefix := range vppPeerDetails.Peer.AllowedIps { + peerDetails.AllowedIps = append(peerDetails.AllowedIps, prefix.String()) + } + + ifName, _, exists := h.ifIndexes.LookupBySwIfIndex(uint32(vppPeerDetails.Peer.SwIfIndex)) + if !exists { + h.log.Warnf("Wireguard peers dump: interface name for index %d not found", vppPeerDetails.Peer.SwIfIndex) + continue + } + + peerDetails.WgIfName = ifName + + endpointAddr := vppPeerDetails.Peer.Endpoint.ToIP() + if !endpointAddr.IsUnspecified() { + peerDetails.Endpoint = endpointAddr.String() + } + + peerList = append(peerList, peerDetails) + } + + return +} diff --git a/plugins/vpp/wireguardplugin/vppcalls/vpp2306/vppcalls_handlers.go b/plugins/vpp/wireguardplugin/vppcalls/vpp2306/vppcalls_handlers.go new file mode 100644 index 0000000000..ff39e194c4 --- /dev/null +++ b/plugins/vpp/wireguardplugin/vppcalls/vpp2306/vppcalls_handlers.go @@ -0,0 +1,42 @@ +// Copyright (c) 2022 Doc.ai and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + govppapi "go.fd.io/govpp/api" + "go.ligato.io/cn-infra/v2/logging" + + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306" + vpp_wg "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/wireguard" + "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/ifaceidx" + "go.ligato.io/vpp-agent/v3/plugins/vpp/wireguardplugin/vppcalls" +) + +func init() { + var msgs []govppapi.Message + msgs = append(msgs, vpp_wg.AllMessages()...) + + vppcalls.AddHandlerVersion(vpp2306.Version, msgs, NewWgVppHandler) +} + +type WgVppHandler struct { + callsChannel govppapi.Channel + ifIndexes ifaceidx.IfaceMetadataIndex + log logging.Logger +} + +func NewWgVppHandler(ch govppapi.Channel, ifIdx ifaceidx.IfaceMetadataIndex, log logging.Logger) vppcalls.WgVppAPI { + return &WgVppHandler{ch, ifIdx, log} +} diff --git a/plugins/vpp/wireguardplugin/vppcalls/vpp2306/wireguard_vppcalls.go b/plugins/vpp/wireguardplugin/vppcalls/vpp2306/wireguard_vppcalls.go new file mode 100644 index 0000000000..ad3cfe2f61 --- /dev/null +++ b/plugins/vpp/wireguardplugin/vppcalls/vpp2306/wireguard_vppcalls.go @@ -0,0 +1,86 @@ +// Copyright (c) 2022 Doc.ai and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306 + +import ( + "encoding/base64" + "fmt" + + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/interface_types" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip_types" + vpp_wg "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/wireguard" + wg "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/wireguard" +) + +func (h *WgVppHandler) AddPeer(peer *wg.Peer) (uint32, error) { + invalidIdx := ^uint32(0) + + peer_vpp := vpp_wg.WireguardPeer{ + Port: uint16(peer.Port), + PersistentKeepalive: uint16(peer.PersistentKeepalive), + } + + publicKeyBin, err := base64.StdEncoding.DecodeString(peer.PublicKey) + if err != nil { + return invalidIdx, err + } + peer_vpp.PublicKey = publicKeyBin + + ifaceMeta, found := h.ifIndexes.LookupByName(peer.WgIfName) + if !found { + return invalidIdx, fmt.Errorf("failed to get interface metadata") + } + peer_vpp.SwIfIndex = interface_types.InterfaceIndex(ifaceMeta.SwIfIndex) + peer_vpp.TableID = ifaceMeta.Vrf + + peer_vpp.Endpoint, err = ip_types.ParseAddress(peer.Endpoint) + if err != nil { + return invalidIdx, err + } + + for _, allowedIp := range peer.AllowedIps { + prefix, err := ip_types.ParsePrefix(allowedIp) + if err != nil { + return invalidIdx, err + } + peer_vpp.AllowedIps = append(peer_vpp.AllowedIps, prefix) + } + + request := &vpp_wg.WireguardPeerAdd{ + Peer: peer_vpp, + } + // prepare reply + reply := &vpp_wg.WireguardPeerAddReply{} + // send request and obtain reply + if err := h.callsChannel.SendRequest(request).ReceiveReply(reply); err != nil { + return invalidIdx, err + } + return reply.PeerIndex, nil +} + +func (h *WgVppHandler) RemovePeer(peer_idx uint32) error { + // prepare request + request := &vpp_wg.WireguardPeerRemove{ + PeerIndex: peer_idx, + } + // prepare reply + reply := &vpp_wg.WireguardPeerRemoveReply{} + + // send request and obtain reply + if err := h.callsChannel.SendRequest(request).ReceiveReply(reply); err != nil { + return err + } + return nil +} diff --git a/plugins/vpp/wireguardplugin/vppcalls/vpp2306/wireguard_vppcalls_test.go b/plugins/vpp/wireguardplugin/vppcalls/vpp2306/wireguard_vppcalls_test.go new file mode 100644 index 0000000000..bb29562e00 --- /dev/null +++ b/plugins/vpp/wireguardplugin/vppcalls/vpp2306/wireguard_vppcalls_test.go @@ -0,0 +1,93 @@ +// Copyright (c) 2022 Doc.ai and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vpp2306_test + +import ( + "encoding/base64" + "testing" + + . "github.com/onsi/gomega" + + "go.ligato.io/cn-infra/v2/logging/logrus" + "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/ip_types" + vpp_wg "go.ligato.io/vpp-agent/v3/plugins/vpp/binapi/vpp2306/wireguard" + "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/ifaceidx" + "go.ligato.io/vpp-agent/v3/plugins/vpp/vppmock" + "go.ligato.io/vpp-agent/v3/plugins/vpp/wireguardplugin/vppcalls" + "go.ligato.io/vpp-agent/v3/plugins/vpp/wireguardplugin/vppcalls/vpp2306" + wg "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/wireguard" +) + +func TestVppAddPeer(t *testing.T) { + ctx, wgHandler, ifIndex := wgTestSetup(t) + defer ctx.TeardownTestCtx() + + ifIndex.Put("wg1", &ifaceidx.IfaceMetadata{SwIfIndex: 2}) + + ctx.MockVpp.MockReply(&vpp_wg.WireguardPeerAddReply{ + PeerIndex: 0, + }) + + peer := &wg.Peer{ + PublicKey: "dIjXzrQfIFf80d0O8Hd2KhcfkKLRncc+8C70OjotIW8=", + WgIfName: "wg1", + Port: 12314, + PersistentKeepalive: 10, + Endpoint: "10.10.2.1", + Flags: 0, + AllowedIps: []string{"10.10.0.0/24"}, + } + + index, err := wgHandler.AddPeer(peer) + Expect(err).To(BeNil()) + Expect(index).To(Equal(uint32(0))) + + vppMsg, ok := ctx.MockChannel.Msg.(*vpp_wg.WireguardPeerAdd) + Expect(ok).To(BeTrue()) + + pubKeyBin, _ := base64.StdEncoding.DecodeString("dIjXzrQfIFf80d0O8Hd2KhcfkKLRncc+8C70OjotIW8=") + Expect(vppMsg.Peer.PublicKey).To(BeEquivalentTo(pubKeyBin)) + Expect(vppMsg.Peer.SwIfIndex).To(BeEquivalentTo(2)) + Expect(vppMsg.Peer.Port).To(BeEquivalentTo(12314)) + Expect(vppMsg.Peer.PersistentKeepalive).To(BeEquivalentTo(10)) + Expect(vppMsg.Peer.TableID).To(BeEquivalentTo(0)) + Expect(vppMsg.Peer.Endpoint.Un.GetIP4()).To(BeEquivalentTo(ip_types.IP4Address{10, 10, 2, 1})) + Expect(vppMsg.Peer.Flags).To(BeEquivalentTo(0)) + Expect(vppMsg.Peer.AllowedIps).To(BeEquivalentTo([]ip_types.Prefix{{ + Address: ip_types.Address{ + Af: ip_types.ADDRESS_IP4, + Un: ip_types.AddressUnionIP4(ip_types.IP4Address{10, 10, 0, 0}), + }, + Len: 24, + }})) +} + +func TestVppRemovePeer(t *testing.T) { + ctx, wgHandler, _ := wgTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply(&vpp_wg.WireguardPeerRemoveReply{}) + err := wgHandler.RemovePeer(0) + + Expect(err).ShouldNot(HaveOccurred()) +} + +func wgTestSetup(t *testing.T) (*vppmock.TestCtx, vppcalls.WgVppAPI, ifaceidx.IfaceMetadataIndexRW) { + ctx := vppmock.SetupTestCtx(t) + log := logrus.NewLogger("test-log") + ifIndex := ifaceidx.NewIfaceIndex(log, "wg-test-ifidx") + wgHandler := vpp2306.NewWgVppHandler(ctx.MockChannel, ifIndex, log) + return ctx, wgHandler, ifIndex +} diff --git a/plugins/vpp/wireguardplugin/wireguardplugin.go b/plugins/vpp/wireguardplugin/wireguardplugin.go index 86ff66ca6a..7be5336531 100644 --- a/plugins/vpp/wireguardplugin/wireguardplugin.go +++ b/plugins/vpp/wireguardplugin/wireguardplugin.go @@ -30,6 +30,7 @@ import ( _ "go.ligato.io/vpp-agent/v3/plugins/vpp/wireguardplugin/vppcalls/vpp2202" _ "go.ligato.io/vpp-agent/v3/plugins/vpp/wireguardplugin/vppcalls/vpp2210" + _ "go.ligato.io/vpp-agent/v3/plugins/vpp/wireguardplugin/vppcalls/vpp2306" ) type WgPlugin struct { diff --git a/tests/e2e/010_interfaces_test.go b/tests/e2e/010_interfaces_test.go index 078149186e..9574c28569 100644 --- a/tests/e2e/010_interfaces_test.go +++ b/tests/e2e/010_interfaces_test.go @@ -668,9 +668,11 @@ func TestInterfaceConnAfPacket(t *testing.T) { ctx.Expect(ctx.AgentInSync()).To(BeTrue()) ctx.StartMicroservice(msName) + ctx.Eventually(ctx.IsMicroserviceRunning(msName)).Should(Equal(true)) + + ctx.Eventually(ctx.GetValueState(veth1)).Should(Equal(kvscheduler.ValueState_CONFIGURED)) + ctx.Eventually(ctx.GetValueState(veth2)).Should(Equal(kvscheduler.ValueState_CONFIGURED)) ctx.Eventually(ctx.GetValueStateClb(afPacket)).Should(Equal(kvscheduler.ValueState_CONFIGURED)) - ctx.Expect(ctx.GetValueState(veth1)).To(Equal(kvscheduler.ValueState_CONFIGURED)) - ctx.Expect(ctx.GetValueState(veth2)).To(Equal(kvscheduler.ValueState_CONFIGURED)) ctx.Expect(ctx.PingFromVPP(veth2IP)).To(Succeed()) ctx.Expect(ctx.PingFromMs(msName, afPacketIP)).To(Succeed()) ctx.Expect(ctx.AgentInSync()).To(BeTrue()) @@ -691,7 +693,6 @@ func TestInterfaceConnAfPacket(t *testing.T) { afPacket, ).Send(context.Background()) ctx.Expect(err).ToNot(HaveOccurred()) - ctx.Eventually(ctx.PingFromVPPClb(veth2IP)).Should(Succeed()) ctx.Expect(ctx.PingFromMs(msName, afPacketIP)).To(Succeed()) ctx.Expect(ctx.AgentInSync()).To(BeTrue()) diff --git a/tests/e2e/120_dns_test.go b/tests/e2e/120_dns_test.go index 23a1fb7a72..f2ad0aed42 100644 --- a/tests/e2e/120_dns_test.go +++ b/tests/e2e/120_dns_test.go @@ -54,8 +54,9 @@ func TestDnsCache(t *testing.T) { PublicUpstreamDNSServer: net.ParseIP("8.8.8.8"), QueryDomainName: "www.google.com", UnreachabilityVerificationDomainName: "www.sme.sk", - SkipAAAARecordCheck: true, // TODO remove skipping when VPP bug resolved - SkipReason: "VPP bug https://jira.fd.io/browse/VPP-1963", + //SkipAAAARecordCheck: true, // TODO remove skipping when VPP bug resolved + SkipAll: true, // TODO remove skipping when VPP bug resolved + SkipReason: "VPP bug https://jira.fd.io/browse/VPP-1963 & https://jira.fd.io/browse/VPP-2079", }, { Name: "Test VPP DNS Cache with cloudflare DNS as upstream DNS server", PublicUpstreamDNSServer: net.ParseIP("1.1.1.1"), diff --git a/tests/e2e/e2etest/e2e.go b/tests/e2e/e2etest/e2e.go index 2e4f85ddf3..38a634d41d 100644 --- a/tests/e2e/e2etest/e2e.go +++ b/tests/e2e/e2etest/e2e.go @@ -386,6 +386,24 @@ func (test *TestCtx) ExecVppctl(action string, args ...string) (string, error) { return test.Agent.ExecVppctl(action, args...) } +func (test *TestCtx) IsMicroserviceRunning(name string) bool { + cli, err := docker.NewClientFromEnv() + if err != nil { + test.Logger.Fatal(err) + } + + containers, err := cli.ListContainers(docker.ListContainersOptions{All: false}) + if err != nil { + test.Logger.Fatal(err) + } + for _, container := range containers { + if 0 == strings.Compare(container.Names[0], "/"+MsNamePrefix+name) { + return true + } + } + return false +} + // StartMicroservice starts microservice according to given options func (test *TestCtx) StartMicroservice(name string, optMods ...MicroserviceOptModifier) *Microservice { test.t.Helper() diff --git a/tests/integration/vpp/150_nat_test.go b/tests/integration/vpp/150_nat_test.go index 1a01dea95e..5e8a8d6112 100644 --- a/tests/integration/vpp/150_nat_test.go +++ b/tests/integration/vpp/150_nat_test.go @@ -15,9 +15,6 @@ package vpp import ( - "net" - "testing" - . "github.com/onsi/gomega" idxmap_mem "go.ligato.io/cn-infra/v2/idxmap/mem" "go.ligato.io/cn-infra/v2/logging/logrus" @@ -26,6 +23,8 @@ import ( nat_vppcalls "go.ligato.io/vpp-agent/v3/plugins/vpp/natplugin/vppcalls" nat "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/nat" "google.golang.org/protobuf/proto" + "net" + "testing" ) const ( @@ -35,6 +34,7 @@ const ( vpp2106 = "21.06" vpp2202 = "22.02" vpp2210 = "22.10" + vpp2306 = "23.06" ) func TestNat44Global(t *testing.T) { @@ -184,7 +184,6 @@ func TestNat44StaticMapping(t *testing.T) { // Create Expect(test).ShouldNot(BeNil()) Expect(natHandler.AddNat44StaticMapping(test.input, dnatLabel)).Should(Succeed()) - // Read dnatDump, err := natHandler.DNat44Dump() t.Logf("received this dnat from dump: %v", dnatDump) diff --git a/vpp.env b/vpp.env index e60cc05677..6866a7f1da 100644 --- a/vpp.env +++ b/vpp.env @@ -1,5 +1,5 @@ VPP_REPO_URL=https://github.com/FDio/vpp.git -VPP_DEFAULT=2210 +VPP_DEFAULT=2306 # VPP 22.02 VPP_2202_IMAGE=ligato/vpp-base:22.02 @@ -10,3 +10,8 @@ VPP_2202_BRANCH=stable/2202 VPP_2210_IMAGE=ligato/vpp-base:22.10 VPP_2210_BINAPI=plugins/vpp/binapi/vpp2210 VPP_2210_BRANCH=stable/2210 + +# VPP 23.06 +VPP_2306_IMAGE=ligato/vpp-base:23.06 +VPP_2306_BINAPI=plugins/vpp/binapi/vpp2306 +VPP_2306_BRANCH=stable/2306