diff --git a/.golangci.yml b/.golangci.yml index d3e8b4ad..f254f2c2 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -178,7 +178,7 @@ issues: - path: internal/vppinit/vppinit.go linters: - funlen - text: "Function 'LinkToAfPacket'" + text: "Function 'LinkToSocket'" - path: internal/tests/suite_combinatronics_test.go linters: - funlen diff --git a/Dockerfile b/Dockerfile index 0b849c2f..44e77c20 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,10 +12,14 @@ ADD https://github.com/spiffe/spire/releases/download/v1.2.2/spire-1.2.2-linux-x RUN tar xzvf spire-1.2.2-linux-x86_64-glibc.tar.gz -C /bin --strip=2 spire-1.2.2/bin/spire-server spire-1.2.2/bin/spire-agent FROM go as build +RUN apt update +RUN apt install -f -y libbpf-dev clang WORKDIR /build COPY go.mod go.sum ./ COPY ./local ./local COPY ./internal/imports ./internal/imports +COPY ./internal/afxdp/afxdp.c ./internal/afxdp/ +RUN clang -O3 -g -Wextra -Wall -target bpf -I/usr/include/x86_64-linux-gnu -I/usr/include -c -o /bin/afxdp.o ./internal/afxdp/afxdp.c RUN go build ./internal/imports COPY . . RUN go build -o /bin/forwarder . @@ -28,6 +32,9 @@ WORKDIR /build/internal/tests/ CMD dlv -l :40000 --headless=true --api-version=2 test -test.v . FROM ghcr.io/edwarnicke/govpp/vpp:${VPP_VERSION} as runtime +RUN apt-get update +RUN apt install -f -y libbpf-dev COPY --from=build /bin/forwarder /bin/forwarder +COPY --from=build /bin/afxdp.o /bin/afxdp.o COPY --from=build /bin/grpc-health-probe /bin/grpc-health-probe ENTRYPOINT [ "/bin/forwarder" ] diff --git a/go.mod b/go.mod index 1d39e6a0..e0b64455 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/edwarnicke/govpp v0.0.0-20230130211138-14ef5d20b1d0 github.com/edwarnicke/grpcfd v1.1.2 github.com/edwarnicke/vpphelper v0.0.0-20210512223648-f914b171f679 + github.com/go-ping/ping v1.0.0 github.com/golang/protobuf v1.5.2 github.com/google/uuid v1.2.0 github.com/kelseyhightower/envconfig v1.4.0 diff --git a/go.sum b/go.sum index b5cde2a1..4723bbaf 100644 --- a/go.sum +++ b/go.sum @@ -127,6 +127,8 @@ github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbV github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-ping/ping v1.0.0 h1:34GZiqLDqqIHEeL5NZIz7jSnMluK7/p0qDB436yO6H0= +github.com/go-ping/ping v1.0.0/go.mod h1:35JbSyV/BYqHwwRA6Zr1uVDm1637YlNOU61wI797NPI= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= @@ -409,6 +411,7 @@ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= diff --git a/internal/afxdp/afxdp.c b/internal/afxdp/afxdp.c new file mode 100644 index 00000000..49a4a903 --- /dev/null +++ b/internal/afxdp/afxdp.c @@ -0,0 +1,90 @@ +// Copyright (c) 2023 Cisco and/or its affiliates. +// +// SPDX-License-Identifier: Apache-2.0 +// +// 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. + +#include +#include +#include +#include +#include +#include +#include + +#define ntohs(x) __constant_ntohs(x) +#define MAX_NR_PORTS 65535 + +// nsm_xdp_pinhole contains NSM UDP ports. It is filled by afxdppinhole chain element +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, MAX_NR_PORTS); + __type(key, unsigned short int); + __type(value, unsigned short int); + __uint(pinning, LIBBPF_PIN_BY_NAME); +} nsm_xdp_pinhole SEC(".maps"); + +// xsks_map redirects raw XDP frames to AF_XDP sockets (XSKs). It is filled by VPP plugin +// https://docs.kernel.org/bpf/map_xskmap.html +struct { + __uint(type, BPF_MAP_TYPE_XSKMAP); + __uint(max_entries, 64); + __type(key, int); + __type(value, int); +} xsks_map SEC(".maps"); + +SEC("xdp_sock") +int xdp_sock_prog(struct xdp_md *ctx) { + const void *data = (void *)(long)ctx->data; + const void *data_end = (void *)(long)ctx->data_end; + + if (data + sizeof(struct ethhdr) > data_end) { + // the packet is too small + return XDP_PASS; + } + + const struct ethhdr *eth = data; + // Here we check if a packet belongs to NSM. We do this with a UDP port map, which is filled on NSM request. + // It's a kind of pinhole + if (eth->h_proto == ntohs(ETH_P_IP)) { + // IPv4 case + // Check the packet size + if (data + sizeof(struct ethhdr) + sizeof(struct iphdr) + sizeof(struct udphdr) > data_end) { + return XDP_PASS; + } + const struct iphdr *ip = (void *)(eth + 1); + if (ip->protocol == IPPROTO_UDP) { + const struct udphdr *udp = (void *)(ip + 1); + const unsigned int port = ntohs(udp->dest); + if (bpf_map_lookup_elem(&nsm_xdp_pinhole, &port)) { + return bpf_redirect_map(&xsks_map, ctx->rx_queue_index, XDP_PASS); + } + } + } else if (eth->h_proto == ntohs(ETH_P_IPV6)) { + // IPv6 case + // Check the packet size + if (data + sizeof(struct ethhdr) + sizeof(struct ipv6hdr) + sizeof(struct udphdr) > data_end) { + return XDP_PASS; + } + const struct ipv6hdr *ip = (void *)(eth + 1); + if (ip->nexthdr == IPPROTO_UDP) { + const struct udphdr *udp = (void *)(ip + 1); + const unsigned int port = ntohs(udp->dest); + if (bpf_map_lookup_elem(&nsm_xdp_pinhole, &port)) { + return bpf_redirect_map(&xsks_map, ctx->rx_queue_index, XDP_PASS); + } + } + } + return XDP_PASS; +} + diff --git a/internal/config/config.go b/internal/config/config.go index fd224824..b7ffa1bc 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -1,6 +1,6 @@ -// Copyright (c) 2020-2022 Cisco and/or its affiliates. +// Copyright (c) 2020-2023 Cisco and/or its affiliates. // -// Copyright (c) 2021-2022 Doc.ai and/or its affiliates. +// Copyright (c) 2021-2023 Doc.ai and/or its affiliates. // // SPDX-License-Identifier: Apache-2.0 // @@ -48,7 +48,7 @@ type Config struct { TunnelIP net.IP `desc:"IP to use for tunnels" split_words:"true"` VxlanPort uint16 `default:"0" desc:"VXLAN port to use" split_words:"true"` VppAPISocket string `default:"/var/run/vpp/external/vpp-api.sock" desc:"filename of socket to connect to existing VPP instance. If empty a VPP instance is run in forwarder" split_words:"true"` - VppInit vppinit.Func `default:"NONE" desc:"type of VPP initialization. Must be NONE or AF_PACKET" split_words:"true"` + VppInit vppinit.Func `default:"AF_PACKET" desc:"type of VPP initialization. Must be AF_XDP, AF_PACKET or NONE" split_words:"true"` ResourcePollTimeout time.Duration `default:"30s" desc:"device plugin polling timeout" split_words:"true"` DevicePluginPath string `default:"/var/lib/kubelet/device-plugins/" desc:"path to the device plugin directory" split_words:"true"` diff --git a/internal/imports/imports_linux.go b/internal/imports/imports_linux.go index 7f4a29fc..7e7f904a 100644 --- a/internal/imports/imports_linux.go +++ b/internal/imports/imports_linux.go @@ -14,15 +14,17 @@ import ( _ "github.com/edwarnicke/govpp/binapi/acl" _ "github.com/edwarnicke/govpp/binapi/acl_types" _ "github.com/edwarnicke/govpp/binapi/af_packet" + _ "github.com/edwarnicke/govpp/binapi/af_xdp" _ "github.com/edwarnicke/govpp/binapi/fib_types" _ "github.com/edwarnicke/govpp/binapi/interface" _ "github.com/edwarnicke/govpp/binapi/interface_types" _ "github.com/edwarnicke/govpp/binapi/ip" + _ "github.com/edwarnicke/govpp/binapi/ip6_nd" _ "github.com/edwarnicke/govpp/binapi/ip_neighbor" _ "github.com/edwarnicke/govpp/binapi/ip_types" - _ "github.com/edwarnicke/govpp/binapi/vlib" _ "github.com/edwarnicke/grpcfd" _ "github.com/edwarnicke/vpphelper" + _ "github.com/go-ping/ping" _ "github.com/golang/protobuf/ptypes/empty" _ "github.com/google/uuid" _ "github.com/kelseyhightower/envconfig" @@ -115,6 +117,7 @@ import ( _ "path" _ "path/filepath" _ "runtime" + _ "strconv" _ "strings" _ "syscall" _ "testing" diff --git a/internal/tests/suite_setup_test.go b/internal/tests/suite_setup_test.go index 31372bef..c63b17c0 100644 --- a/internal/tests/suite_setup_test.go +++ b/internal/tests/suite_setup_test.go @@ -67,6 +67,7 @@ func (f *ForwarderTestSuite) SetupSuite() { log.FromContext(f.ctx).Infof("Getting Config from Env (time since start: %s)", time.Since(starttime)) // ******************************************************************************** _ = os.Setenv("NSM_TUNNEL_IP", forwarderIP) + _ = os.Setenv("NSM_VPP_INIT", "AF_XDP") f.Require().NoError(f.config.Process()) // ******************************************************************************** @@ -78,14 +79,14 @@ func (f *ForwarderTestSuite) SetupSuite() { log.FromContext(f.ctx).Infof("Creating test vpp Server (time since start: %s)", time.Since(starttime)) // ******************************************************************************** f.vppServerConn, f.vppServerRoot, f.vppServerErrCh = f.createVpp(f.ctx, "vpp-server") - _, err := vppinit.LinkToAfPacket(f.ctx, f.vppServerConn, net.ParseIP(serverIP)) + _, err := vppinit.LinkToSocket(f.ctx, f.vppServerConn, net.ParseIP(serverIP), vppinit.AfPacket) f.Require().NoError(err) // ******************************************************************************** log.FromContext(f.ctx).Infof("Creating test vpp Client (time since start: %s)", time.Since(starttime)) // ******************************************************************************** f.vppClientConn, f.vppClientRoot, f.vppClientErrCh = f.createVpp(f.ctx, "vpp-client") - _, err = vppinit.LinkToAfPacket(f.ctx, f.vppClientConn, net.ParseIP(clientIP)) + _, err = vppinit.LinkToSocket(f.ctx, f.vppClientConn, net.ParseIP(clientIP), vppinit.AfPacket) f.Require().NoError(err) // ******************************************************************************** diff --git a/internal/vppinit/vppinit.go b/internal/vppinit/vppinit.go index 14d1b2d9..d8350dd6 100644 --- a/internal/vppinit/vppinit.go +++ b/internal/vppinit/vppinit.go @@ -21,16 +21,22 @@ import ( "context" "fmt" "net" + "strconv" + "strings" + "syscall" "time" + "github.com/go-ping/ping" + "git.fd.io/govpp.git/api" "github.com/edwarnicke/govpp/binapi/af_packet" + "github.com/edwarnicke/govpp/binapi/af_xdp" "github.com/edwarnicke/govpp/binapi/fib_types" interfaces "github.com/edwarnicke/govpp/binapi/interface" "github.com/edwarnicke/govpp/binapi/interface_types" "github.com/edwarnicke/govpp/binapi/ip" + "github.com/edwarnicke/govpp/binapi/ip6_nd" "github.com/edwarnicke/govpp/binapi/ip_neighbor" - "github.com/edwarnicke/govpp/binapi/vlib" "github.com/pkg/errors" "github.com/vishvananda/netlink" @@ -38,6 +44,22 @@ import ( "github.com/networkservicemesh/sdk/pkg/tools/log" ) +// AfType represents socket address family +type AfType uint32 + +const ( + // AfPacket - AF_PACKET + AfPacket AfType = 0 + // AfXDP - AF_XDP + AfXDP AfType = 1 +) + +// Minimum required kernel version for AF_XDP +const ( + afXdpMajorVer = 5 + afXdpMinorVer = 4 +) + // Func - vpp initialization function type Func struct { f func(ctx context.Context, vppConn api.Connection, tunnelIP net.IP) (net.IP, error) @@ -51,8 +73,23 @@ func (f *Func) Execute(ctx context.Context, vppConn api.Connection, tunnelIP net // Decode for envconfig to select correct vpp initialization function func (f *Func) Decode(value string) error { switch value { + case "AF_XDP": + ver, err := getKernelVer() + if err != nil { + return err + } + if ver[0] > afXdpMajorVer || (ver[0] == afXdpMajorVer && ver[1] >= afXdpMinorVer) { + f.f = func(ctx context.Context, vppConn api.Connection, tunnelIP net.IP) (net.IP, error) { + return LinkToSocket(ctx, vppConn, tunnelIP, AfXDP) + } + return nil + } + log.FromContext(context.Background()).Warn("AF_XDP is not supported by this linux kernel version. AF_PACKET will be used") + fallthrough case "AF_PACKET": - f.f = LinkToAfPacket + f.f = func(ctx context.Context, vppConn api.Connection, tunnelIP net.IP) (net.IP, error) { + return LinkToSocket(ctx, vppConn, tunnelIP, AfPacket) + } case "NONE": f.f = None default: @@ -74,10 +111,38 @@ func None(ctx context.Context, vppConn api.Connection, tunnelIP net.IP) (net.IP, return tunnelIP, nil } -// LinkToAfPacket - will link vpp via af_packet to the interface having the tunnelIP +// Get Linux kernel version +// Example: 5.11.0-25-generic -> [5,11] +func getKernelVer() ([2]int, error) { + var uname syscall.Utsname + err := syscall.Uname(&uname) + if err != nil { + return [2]int{}, err + } + + b := make([]byte, 0, len(uname.Release)) + for _, v := range uname.Release { + if v == 0x00 { + break + } + b = append(b, byte(v)) + } + ver := strings.Split(string(b), ".") + maj, err := strconv.Atoi(ver[0]) + if err != nil { + return [2]int{}, err + } + min, err := strconv.Atoi(ver[1]) + if err != nil { + return [2]int{}, err + } + return [2]int{maj, min}, nil +} + +// LinkToSocket - will link vpp via af_packet or af_xdp to the interface having the tunnelIP // if tunnelIP is nil, it will find the interface for the default route and use that instead. // It returns the resulting tunnelIP -func LinkToAfPacket(ctx context.Context, vppConn api.Connection, tunnelIP net.IP) (net.IP, error) { +func LinkToSocket(ctx context.Context, vppConn api.Connection, tunnelIP net.IP, family AfType) (net.IP, error) { link, addrs, routes, err := linkAddrsRoutes(ctx, tunnelIP) if err != nil { return nil, err @@ -86,7 +151,12 @@ func LinkToAfPacket(ctx context.Context, vppConn api.Connection, tunnelIP net.IP return tunnelIP, nil } - swIfIndex, err := createAfPacket(ctx, vppConn, link) + afFunc := createAfPacket + if family == AfXDP { + afFunc = createAfXDP + } + + swIfIndex, err := afFunc(ctx, vppConn, link) if err != nil { return nil, err } @@ -101,7 +171,7 @@ func LinkToAfPacket(ctx context.Context, vppConn api.Connection, tunnelIP net.IP // Disable Router Advertisement on IPv6 tunnels if tunnelIP.To4() == nil { - err = disableIPv6RA(ctx, vppConn, link) + err = disableIPv6RA(ctx, vppConn, swIfIndex) if err != nil { return nil, err } @@ -124,6 +194,10 @@ func LinkToAfPacket(ctx context.Context, vppConn api.Connection, tunnelIP net.IP if err != nil { return nil, err } + err = addHostLinksAsNeighbours(ctx, vppConn, link, swIfIndex) + if err != nil { + return nil, err + } for _, addr := range addrs { if addr.IPNet != nil && addr.IPNet.IP.IsGlobalUnicast() && tunnelIP == nil { @@ -231,6 +305,91 @@ func createAfPacket(ctx context.Context, vppConn api.Connection, link netlink.Li return afPacketCreateRsp.SwIfIndex, nil } +func createAfXDP(ctx context.Context, vppConn api.Connection, link netlink.Link) (interface_types.InterfaceIndex, error) { + // Get all gateways for a given interface and send ping requests. + // This will allow us to resolve the neighbors. + rl, _ := netlink.RouteList(link, netlink.FAMILY_ALL) + for i := 0; i < len(rl); i++ { + if rl[i].Gw != nil { + continue + } + pi := ping.New(rl[i].Gw.String()) + pi.Count = 1 + pi.Timeout = time.Millisecond * 100 + pi.SetPrivileged(true) + err := pi.Run() + if err == nil { + log.FromContext(ctx).Infof("Gateway %v was resolved", rl[0].Gw.String()) + } + } + + // Based on VPP guidelines + err := netlink.SetPromiscOn(link) + if err != nil { + return 0, err + } + + // /sys/fs/bpf - the default dir of BPF filesystem + err = syscall.Mount("bpffs", "/sys/fs/bpf", "bpf", 0, "") + if err != nil { + panic(err) + } + afXDPCreate := &af_xdp.AfXdpCreate{ + HostIf: link.Attrs().Name, + RxqSize: 8192, + TxqSize: 8192, + Mode: af_xdp.AF_XDP_API_MODE_AUTO, + Prog: "/bin/afxdp.o", + } + now := time.Now() + afXDPCreateRsp, err := af_xdp.NewServiceClient(vppConn).AfXdpCreate(ctx, afXDPCreate) + if err != nil { + log.FromContext(ctx). + WithField("hostIfName", afXDPCreate.HostIf). + WithField("duration", time.Since(now)). + WithField("vppapi", "AfXdpCreate").Error(err) + return 0, err + } + log.FromContext(ctx). + WithField("swIfIndex", afXDPCreateRsp.SwIfIndex). + WithField("hostIfName", afXDPCreate.HostIf). + WithField("duration", time.Since(now)). + WithField("vppapi", "AfXdpCreate").Debug("completed") + + now = time.Now() + if _, err = interfaces.NewServiceClient(vppConn).SwInterfaceSetRxMode(ctx, &interfaces.SwInterfaceSetRxMode{ + SwIfIndex: afXDPCreateRsp.SwIfIndex, + Mode: interface_types.RX_MODE_API_ADAPTIVE, + }); err != nil { + return 0, errors.Wrap(err, "vppapi SwInterfaceSetRxMode returned error") + } + log.FromContext(ctx). + WithField("swIfIndex", afXDPCreateRsp.SwIfIndex). + WithField("mode", interface_types.RX_MODE_API_ADAPTIVE). + WithField("duration", time.Since(now)). + WithField("vppapi", "SwInterfaceSetRxMode").Debug("completed") + + now = time.Now() + _, err = interfaces.NewServiceClient(vppConn).SwInterfaceSetMacAddress(ctx, &interfaces.SwInterfaceSetMacAddress{ + SwIfIndex: afXDPCreateRsp.SwIfIndex, + MacAddress: types.ToVppMacAddress(&link.Attrs().HardwareAddr), + }) + if err != nil { + log.FromContext(ctx). + WithField("swIfIndex", afXDPCreateRsp.SwIfIndex). + WithField("duration", time.Since(now)). + WithField("vppapi", "SwInterfaceSetMacAddress").Error(err) + return 0, err + } + log.FromContext(ctx). + WithField("swIfIndex", afXDPCreateRsp.SwIfIndex). + WithField("hwaddr", types.ToVppMacAddress(&link.Attrs().HardwareAddr)). + WithField("duration", time.Since(now)). + WithField("vppapi", "SwInterfaceSetMacAddress").Debug("completed") + + return afXDPCreateRsp.SwIfIndex, nil +} + func addIPNeighbor(ctx context.Context, vppConn api.Connection, swIfIndex interface_types.InterfaceIndex, linkIdx int) error { neighList, err := netlink.NeighList(linkIdx, netlink.FAMILY_ALL) if err != nil { @@ -353,33 +512,79 @@ func linkByIP(ctx context.Context, ipaddress net.IP) (netlink.Link, error) { return nil, nil } -func disableIPv6RA(ctx context.Context, vppConn api.Connection, link netlink.Link) error { - cmds := []string{ - fmt.Sprintf("enable ip6 interface host-%s", link.Attrs().Name), - fmt.Sprintf("ip6 nd host-%s ra-cease ra-suppress", link.Attrs().Name), +func disableIPv6RA(ctx context.Context, vppConn api.Connection, swIfIndex interface_types.InterfaceIndex) error { + now := time.Now() + _, err := ip.NewServiceClient(vppConn).SwInterfaceIP6EnableDisable(ctx, + &ip.SwInterfaceIP6EnableDisable{ + SwIfIndex: swIfIndex, + Enable: true, + }, + ) + if err != nil { + log.FromContext(ctx). + WithField("duration", time.Since(now)). + WithField("swIfIndex", swIfIndex). + WithField("vppapi", "SwInterfaceIP6Enable").Error(err) + return err } - for _, cmd := range cmds { - now := time.Now() - var reply, err = vlib.NewServiceClient(vppConn).CliInband(ctx, &vlib.CliInband{ - Cmd: cmd, - }) - - if err != nil { - log.FromContext(ctx). - WithField("interface", fmt.Sprintf("host-%s", link.Attrs().Name)). - WithField("duration", time.Since(now)). - WithField("cmd", cmd). - WithField("vppapi", "CliInband").Error(err) - return err - } + log.FromContext(ctx). + WithField("duration", time.Since(now)). + WithField("swIfIndex", swIfIndex). + WithField("vppapi", "SwInterfaceIP6Enable").Debug("completed") + now = time.Now() + _, err = ip6_nd.NewServiceClient(vppConn).SwInterfaceIP6ndRaConfig(ctx, &ip6_nd.SwInterfaceIP6ndRaConfig{ + SwIfIndex: swIfIndex, + Suppress: 1, + Cease: 1, + }) + if err != nil { log.FromContext(ctx). - WithField("interface", fmt.Sprintf("host-%s", link.Attrs().Name)). - WithField("cmd", cmd). - WithField("reply", reply.Reply). WithField("duration", time.Since(now)). - WithField("vppapi", "CliInband").Debug("completed") + WithField("swIfIndex", swIfIndex). + WithField("vppapi", "SwInterfaceIP6ndRaConfig").Error(err) + return err } + log.FromContext(ctx). + WithField("duration", time.Since(now)). + WithField("swIfIndex", swIfIndex). + WithField("vppapi", "SwInterfaceIP6ndRaConfig").Debug("completed") return nil } + +func addHostLinksAsNeighbours(ctx context.Context, vppConn api.Connection, link netlink.Link, swIfIndex interface_types.InterfaceIndex) error { + // Add host links as neighbors. We have no other way to make an ARP request + hostLinks, err := netlink.LinkList() + if err != nil { + return err + } + + for _, hl := range hostLinks { + if link.Attrs().Index == hl.Attrs().Index || len(hl.Attrs().HardwareAddr) == 0 { + continue + } + var ips []netlink.Addr + ips, err = netlink.AddrList(hl, netlink.FAMILY_ALL) + if err != nil { + return err + } + for _, hlIP := range ips { + ipNeighborAddDel := &ip_neighbor.IPNeighborAddDel{ + IsAdd: true, + Neighbor: ip_neighbor.IPNeighbor{ + SwIfIndex: swIfIndex, + Flags: ip_neighbor.IP_API_NEIGHBOR_FLAG_STATIC, + MacAddress: types.ToVppMacAddress(&hl.Attrs().HardwareAddr), + IPAddress: types.ToVppAddress(hlIP.IP), + }, + } + _, err = ip_neighbor.NewServiceClient(vppConn).IPNeighborAddDel(ctx, ipNeighborAddDel) + if err != nil { + return err + } + log.FromContext(ctx).Infof("host link was added as a neighbor: IP: %v, MAC: %v", hlIP.IP.String(), hl.Attrs().HardwareAddr.String()) + } + } + return err +} diff --git a/main.go b/main.go index 752f3653..ead3090e 100644 --- a/main.go +++ b/main.go @@ -169,12 +169,12 @@ func main() { dir, _ := path.Split(cfg.VppAPISocket) statsOpts = append(statsOpts, stats.WithSocket(path.Join(dir, "stats.sock"))) cleanupOpts = append(cleanupOpts, cleanup.WithDoneChan(cleanupDoneCh)) - log.FromContext(ctx).Info("external vpp is being used") - } else { // If we don't have a VPPAPISocket, start VPP and use that - if err = cfg.VppInit.Decode("AF_PACKET"); err != nil { + + if err = cfg.VppInit.Decode("NONE"); err != nil { log.FromContext(ctx).Fatalf("VppInit.Decode error: %v", err) } + } else { // If we don't have a VPPAPISocket, start VPP and use that vppConn, vppErrCh = vpphelper.StartAndDialContext(ctx) exitOnErrCh(ctx, cancel, vppErrCh) close(cleanupDoneCh)