From f44ade1ee0e3395870b18256aa2730734da7a59d Mon Sep 17 00:00:00 2001 From: mhalaj1 Date: Mon, 5 Dec 2022 13:34:47 +0100 Subject: [PATCH] Update bfd and nat64 plugin (#73) Signed-off-by: Matus Halaj Signed-off-by: Matus Halaj Co-authored-by: Ondrej Fabry --- plugins/bfd/bfdplugin.go | 2 + plugins/bfd/vppcalls/vpp2202/bfd_vppcalls.go | 246 +++++++++++ .../bfd/vppcalls/vpp2202/vppcalls_handlers.go | 51 +++ plugins/bfd/vppcalls/vpp2210/bfd_vppcalls.go | 246 +++++++++++ .../bfd/vppcalls/vpp2210/vppcalls_handlers.go | 51 +++ plugins/nat64/nat64plugin.go | 2 + .../vppcalls/vpp2202/dump_nat64_vppcalls.go | 209 ++++++++++ .../vpp2202/dump_nat64_vppcalls_test.go | 247 +++++++++++ .../nat64/vppcalls/vpp2202/nat64_vppcalls.go | 191 +++++++++ .../vppcalls/vpp2202/nat64_vppcalls_test.go | 387 ++++++++++++++++++ .../vppcalls/vpp2202/vppcalls_handler.go | 53 +++ .../vppcalls/vpp2210/dump_nat64_vppcalls.go | 209 ++++++++++ .../vpp2210/dump_nat64_vppcalls_test.go | 247 +++++++++++ .../nat64/vppcalls/vpp2210/nat64_vppcalls.go | 191 +++++++++ .../vppcalls/vpp2210/nat64_vppcalls_test.go | 387 ++++++++++++++++++ .../vppcalls/vpp2210/vppcalls_handler.go | 53 +++ 16 files changed, 2772 insertions(+) create mode 100644 plugins/bfd/vppcalls/vpp2202/bfd_vppcalls.go create mode 100644 plugins/bfd/vppcalls/vpp2202/vppcalls_handlers.go create mode 100644 plugins/bfd/vppcalls/vpp2210/bfd_vppcalls.go create mode 100644 plugins/bfd/vppcalls/vpp2210/vppcalls_handlers.go create mode 100644 plugins/nat64/vppcalls/vpp2202/dump_nat64_vppcalls.go create mode 100644 plugins/nat64/vppcalls/vpp2202/dump_nat64_vppcalls_test.go create mode 100644 plugins/nat64/vppcalls/vpp2202/nat64_vppcalls.go create mode 100644 plugins/nat64/vppcalls/vpp2202/nat64_vppcalls_test.go create mode 100644 plugins/nat64/vppcalls/vpp2202/vppcalls_handler.go create mode 100644 plugins/nat64/vppcalls/vpp2210/dump_nat64_vppcalls.go create mode 100644 plugins/nat64/vppcalls/vpp2210/dump_nat64_vppcalls_test.go create mode 100644 plugins/nat64/vppcalls/vpp2210/nat64_vppcalls.go create mode 100644 plugins/nat64/vppcalls/vpp2210/nat64_vppcalls_test.go create mode 100644 plugins/nat64/vppcalls/vpp2210/vppcalls_handler.go diff --git a/plugins/bfd/bfdplugin.go b/plugins/bfd/bfdplugin.go index f1e11f59..739457c9 100644 --- a/plugins/bfd/bfdplugin.go +++ b/plugins/bfd/bfdplugin.go @@ -38,6 +38,8 @@ import ( "go.pantheon.tech/stonework/proto/bfd" _ "go.pantheon.tech/stonework/plugins/bfd/vppcalls/vpp2106" + _ "go.pantheon.tech/stonework/plugins/bfd/vppcalls/vpp2202" + _ "go.pantheon.tech/stonework/plugins/bfd/vppcalls/vpp2210" ) // BfdPlugin groups required BFD dependencies and descriptors diff --git a/plugins/bfd/vppcalls/vpp2202/bfd_vppcalls.go b/plugins/bfd/vppcalls/vpp2202/bfd_vppcalls.go new file mode 100644 index 00000000..0f2c5e74 --- /dev/null +++ b/plugins/bfd/vppcalls/vpp2202/bfd_vppcalls.go @@ -0,0 +1,246 @@ +// SPDX-License-Identifier: Apache-2.0 + +// Copyright 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 vpp2202 + +import ( + "context" + "errors" + "fmt" + "os" + "time" + + govppapi "go.fd.io/govpp/api" + + "go.pantheon.tech/stonework/plugins/bfd/vppcalls" + binapi "go.pantheon.tech/stonework/plugins/binapi/vpp2202/bfd" + "go.pantheon.tech/stonework/plugins/binapi/vpp2202/interface_types" + "go.pantheon.tech/stonework/plugins/binapi/vpp2202/ip_types" + "go.pantheon.tech/stonework/proto/bfd" +) + +var ( + // EventDeliverTimeout defines maximum time to deliver event upstream. + EventDeliverTimeout = time.Second + + // NotificationChanBufferSize defines size of notification channel buffer. + NotificationChanBufferSize = 10 +) + +// AddBfd creates BFD session attached to the defined interface with given configuration ID. +func (h *BfdVppHandler) AddBfd(confID uint32, bfdEntry *bfd.BFD) error { + // interface + ifMeta, exists := h.ifIndexes.LookupByName(bfdEntry.Interface) + if !exists { + return fmt.Errorf("cannot configure BFD: interface %s is missing", bfdEntry.Interface) + } + + localAddr, err := ip_types.ParseAddress(bfdEntry.GetLocalIp()) + if err != nil { + return err + } + peerAddr, err := ip_types.ParseAddress(bfdEntry.GetPeerIp()) + if err != nil { + return err + } + if localAddr.Af != peerAddr.Af { + return fmt.Errorf("both IP addresses must be the same IP version") + } + req := &binapi.BfdUDPAdd{ + SwIfIndex: interface_types.InterfaceIndex(ifMeta.SwIfIndex), + DesiredMinTx: bfdEntry.GetMinTxInterval(), + RequiredMinRx: bfdEntry.GetMinRxInterval(), + LocalAddr: localAddr, + PeerAddr: peerAddr, + DetectMult: uint8(bfdEntry.GetDetectMultiplier()), + BfdKeyID: uint8(confID), + ConfKeyID: confID, + } + + resp := &binapi.BfdUDPAddReply{} + return h.callsChannel.SendRequest(req).ReceiveReply(resp) +} + +// DeletebfdEntry removes existing BFD session. +func (h *BfdVppHandler) DeleteBfd(bfdEntry *bfd.BFD) error { + ifMeta, exists := h.ifIndexes.LookupByName(bfdEntry.Interface) + if !exists { + return fmt.Errorf("cannot remove BFD: interface %s is missing", bfdEntry.Interface) + } + + localAddr, err := ip_types.ParseAddress(bfdEntry.GetLocalIp()) + if err != nil { + return err + } + peerAddr, err := ip_types.ParseAddress(bfdEntry.GetPeerIp()) + if err != nil { + return err + } + if localAddr.Af != peerAddr.Af { + return fmt.Errorf("both IP addresses must be the same IP version") + } + req := &binapi.BfdUDPDel{ + SwIfIndex: interface_types.InterfaceIndex(ifMeta.SwIfIndex), + LocalAddr: localAddr, + PeerAddr: peerAddr, + } + + resp := &binapi.BfdUDPDelReply{} + return h.callsChannel.SendRequest(req).ReceiveReply(resp) +} + +// DumpBfd returns retrieved BFD data together with BFD state. +func (h *BfdVppHandler) DumpBfd() ([]*vppcalls.BfdDetails, error) { + var bfdList []*vppcalls.BfdDetails + reqCtx := h.callsChannel.SendMultiRequest(&binapi.BfdUDPSessionDump{}) + for { + bfdEntryDetails := &binapi.BfdUDPSessionDetails{} + if stop, err := reqCtx.ReceiveReply(bfdEntryDetails); err != nil { + h.log.Error(err) + return nil, err + } else if stop { + break + } + ifName, _, exists := h.ifIndexes.LookupBySwIfIndex(uint32(bfdEntryDetails.SwIfIndex)) + if !exists { + return nil, fmt.Errorf("BFD interface with index %d is missing", bfdEntryDetails.SwIfIndex) + } + config := &bfd.BFD{ + Interface: ifName, + LocalIp: bfdEntryDetails.LocalAddr.String(), + PeerIp: bfdEntryDetails.PeerAddr.String(), + MinTxInterval: bfdEntryDetails.DesiredMinTx, + MinRxInterval: bfdEntryDetails.RequiredMinRx, + DetectMultiplier: uint32(bfdEntryDetails.DetectMult), + } + + bfdList = append(bfdList, &vppcalls.BfdDetails{ + Config: config, + State: stateToProto(bfdEntryDetails.State), + ConfKey: bfdEntryDetails.ConfKeyID, + BfdKey: bfdEntryDetails.BfdKeyID, + IsAuthenticated: bfdEntryDetails.IsAuthenticated, + }) + } + + return bfdList, nil +} + +// WatchBfdEvents starts BFD event watcher. +func (h *BfdVppHandler) WatchBfdEvents(ctx context.Context, eventChan chan<- *bfd.BFDEvent) error { + notificationChan := make(chan govppapi.Message, NotificationChanBufferSize) + + // subscribe to BFD notifications + sub, err := h.callsChannel.SubscribeNotification(notificationChan, &binapi.BfdUDPSessionDetails{}) + if err != nil { + return fmt.Errorf("subscribing to VPP notification (bfd_session_event) failed: %v", err) + } + unsubscribe := func() { + if err := sub.Unsubscribe(); err != nil { + h.log.Warnf("unsubscribing VPP notification (bfd_session_event) failed: %v", err) + } + } + + go func() { + h.log.Debugf("start watching BFD events") + defer h.log.Debugf("done watching BFD events (%v)", ctx.Err()) + + for { + select { + case e, open := <-notificationChan: + if !open { + h.log.Debugf("BFD events channel was closed") + unsubscribe() + return + } + + bfdEvent, ok := e.(*binapi.BfdUDPSessionDetails) + if !ok { + h.log.Debugf("unexpected notification type: %#v", bfdEvent) + continue + } + + event, err := h.toBfdEvent(bfdEvent) + if err != nil { + h.log.Warn(err) + continue + } + + select { + case eventChan <- event: + // ok + case <-ctx.Done(): + unsubscribe() + return + default: + // in case the channel is full + go func() { + select { + case eventChan <- event: + // sent ok + case <-time.After(EventDeliverTimeout): + h.log.Warnf("BFD (conf-ID: %d) event dropped, cannot deliver", bfdEvent.ConfKeyID) + } + }() + } + case <-ctx.Done(): + unsubscribe() + return + } + } + }() + + // enable BFD events from VPP + req := &binapi.WantBfdEvents{ + PID: uint32(os.Getpid()), + EnableDisable: true, + } + resp := &binapi.WantBfdEventsReply{} + err = h.callsChannel.SendRequest(req).ReceiveReply(resp) + // do not return error on repeated subscribe attempt + if errors.Is(err, govppapi.VPPApiError(govppapi.INVALID_REGISTRATION)) { + h.log.Debugf("already subscribed to BFD events: %v", err) + return nil + } + return err +} + +func (h *BfdVppHandler) toBfdEvent(bfdEvent *binapi.BfdUDPSessionDetails) (*bfd.BFDEvent, error) { + ifName, _, exists := h.ifIndexes.LookupBySwIfIndex(uint32(bfdEvent.SwIfIndex)) + if !exists { + return nil, fmt.Errorf("BFD event for unknown interface (sw_if_index: %d)", + bfdEvent.SwIfIndex) + } + event := &bfd.BFDEvent{ + Interface: ifName, + LocalIp: bfdEvent.LocalAddr.String(), + PeerIp: bfdEvent.PeerAddr.String(), + SessionState: stateToProto(bfdEvent.State), + } + return event, nil +} + +func stateToProto(state binapi.BfdState) bfd.BFDEvent_SessionState { + switch state { + case 1: + return bfd.BFDEvent_Down + case 2: + return bfd.BFDEvent_Init + case 3: + return bfd.BFDEvent_Up + } + return bfd.BFDEvent_Unknown +} diff --git a/plugins/bfd/vppcalls/vpp2202/vppcalls_handlers.go b/plugins/bfd/vppcalls/vpp2202/vppcalls_handlers.go new file mode 100644 index 00000000..ab9d8b8c --- /dev/null +++ b/plugins/bfd/vppcalls/vpp2202/vppcalls_handlers.go @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: Apache-2.0 + +// Copyright 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 vpp2202 + +import ( + govppapi "go.fd.io/govpp/api" + "go.ligato.io/cn-infra/v2/logging" + + "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/ifaceidx" + + "go.pantheon.tech/stonework/plugins/bfd/vppcalls" + binapi "go.pantheon.tech/stonework/plugins/binapi/vpp2202" + "go.pantheon.tech/stonework/plugins/binapi/vpp2202/bfd" +) + +func init() { + var msgs []govppapi.Message + msgs = append(msgs, bfd.AllMessages()...) + + vppcalls.AddBfdHandlerVersion(binapi.Version, msgs, NewBfdVppHandler) +} + +// BfdVppHandler is accessor for BFD-related vppcalls methods +type BfdVppHandler struct { + callsChannel govppapi.Channel + ifIndexes ifaceidx.IfaceMetadataIndex + log logging.Logger +} + +// NewBfdVppHandler creates new instance of BFD vppcalls handler +func NewBfdVppHandler(calls govppapi.Channel, ifIndexes ifaceidx.IfaceMetadataIndex, log logging.Logger) vppcalls.BfdVppAPI { + return &BfdVppHandler{ + callsChannel: calls, + ifIndexes: ifIndexes, + log: log, + } +} diff --git a/plugins/bfd/vppcalls/vpp2210/bfd_vppcalls.go b/plugins/bfd/vppcalls/vpp2210/bfd_vppcalls.go new file mode 100644 index 00000000..e473ec91 --- /dev/null +++ b/plugins/bfd/vppcalls/vpp2210/bfd_vppcalls.go @@ -0,0 +1,246 @@ +// SPDX-License-Identifier: Apache-2.0 + +// Copyright 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 vpp2210 + +import ( + "context" + "errors" + "fmt" + "os" + "time" + + govppapi "go.fd.io/govpp/api" + + "go.pantheon.tech/stonework/plugins/bfd/vppcalls" + binapi "go.pantheon.tech/stonework/plugins/binapi/vpp2210/bfd" + "go.pantheon.tech/stonework/plugins/binapi/vpp2210/interface_types" + "go.pantheon.tech/stonework/plugins/binapi/vpp2210/ip_types" + "go.pantheon.tech/stonework/proto/bfd" +) + +var ( + // EventDeliverTimeout defines maximum time to deliver event upstream. + EventDeliverTimeout = time.Second + + // NotificationChanBufferSize defines size of notification channel buffer. + NotificationChanBufferSize = 10 +) + +// AddBfd creates BFD session attached to the defined interface with given configuration ID. +func (h *BfdVppHandler) AddBfd(confID uint32, bfdEntry *bfd.BFD) error { + // interface + ifMeta, exists := h.ifIndexes.LookupByName(bfdEntry.Interface) + if !exists { + return fmt.Errorf("cannot configure BFD: interface %s is missing", bfdEntry.Interface) + } + + localAddr, err := ip_types.ParseAddress(bfdEntry.GetLocalIp()) + if err != nil { + return err + } + peerAddr, err := ip_types.ParseAddress(bfdEntry.GetPeerIp()) + if err != nil { + return err + } + if localAddr.Af != peerAddr.Af { + return fmt.Errorf("both IP addresses must be the same IP version") + } + req := &binapi.BfdUDPAdd{ + SwIfIndex: interface_types.InterfaceIndex(ifMeta.SwIfIndex), + DesiredMinTx: bfdEntry.GetMinTxInterval(), + RequiredMinRx: bfdEntry.GetMinRxInterval(), + LocalAddr: localAddr, + PeerAddr: peerAddr, + DetectMult: uint8(bfdEntry.GetDetectMultiplier()), + BfdKeyID: uint8(confID), + ConfKeyID: confID, + } + + resp := &binapi.BfdUDPAddReply{} + return h.callsChannel.SendRequest(req).ReceiveReply(resp) +} + +// DeletebfdEntry removes existing BFD session. +func (h *BfdVppHandler) DeleteBfd(bfdEntry *bfd.BFD) error { + ifMeta, exists := h.ifIndexes.LookupByName(bfdEntry.Interface) + if !exists { + return fmt.Errorf("cannot remove BFD: interface %s is missing", bfdEntry.Interface) + } + + localAddr, err := ip_types.ParseAddress(bfdEntry.GetLocalIp()) + if err != nil { + return err + } + peerAddr, err := ip_types.ParseAddress(bfdEntry.GetPeerIp()) + if err != nil { + return err + } + if localAddr.Af != peerAddr.Af { + return fmt.Errorf("both IP addresses must be the same IP version") + } + req := &binapi.BfdUDPDel{ + SwIfIndex: interface_types.InterfaceIndex(ifMeta.SwIfIndex), + LocalAddr: localAddr, + PeerAddr: peerAddr, + } + + resp := &binapi.BfdUDPDelReply{} + return h.callsChannel.SendRequest(req).ReceiveReply(resp) +} + +// DumpBfd returns retrieved BFD data together with BFD state. +func (h *BfdVppHandler) DumpBfd() ([]*vppcalls.BfdDetails, error) { + var bfdList []*vppcalls.BfdDetails + reqCtx := h.callsChannel.SendMultiRequest(&binapi.BfdUDPSessionDump{}) + for { + bfdEntryDetails := &binapi.BfdUDPSessionDetails{} + if stop, err := reqCtx.ReceiveReply(bfdEntryDetails); err != nil { + h.log.Error(err) + return nil, err + } else if stop { + break + } + ifName, _, exists := h.ifIndexes.LookupBySwIfIndex(uint32(bfdEntryDetails.SwIfIndex)) + if !exists { + return nil, fmt.Errorf("BFD interface with index %d is missing", bfdEntryDetails.SwIfIndex) + } + config := &bfd.BFD{ + Interface: ifName, + LocalIp: bfdEntryDetails.LocalAddr.String(), + PeerIp: bfdEntryDetails.PeerAddr.String(), + MinTxInterval: bfdEntryDetails.DesiredMinTx, + MinRxInterval: bfdEntryDetails.RequiredMinRx, + DetectMultiplier: uint32(bfdEntryDetails.DetectMult), + } + + bfdList = append(bfdList, &vppcalls.BfdDetails{ + Config: config, + State: stateToProto(bfdEntryDetails.State), + ConfKey: bfdEntryDetails.ConfKeyID, + BfdKey: bfdEntryDetails.BfdKeyID, + IsAuthenticated: bfdEntryDetails.IsAuthenticated, + }) + } + + return bfdList, nil +} + +// WatchBfdEvents starts BFD event watcher. +func (h *BfdVppHandler) WatchBfdEvents(ctx context.Context, eventChan chan<- *bfd.BFDEvent) error { + notificationChan := make(chan govppapi.Message, NotificationChanBufferSize) + + // subscribe to BFD notifications + sub, err := h.callsChannel.SubscribeNotification(notificationChan, &binapi.BfdUDPSessionDetails{}) + if err != nil { + return fmt.Errorf("subscribing to VPP notification (bfd_session_event) failed: %v", err) + } + unsubscribe := func() { + if err := sub.Unsubscribe(); err != nil { + h.log.Warnf("unsubscribing VPP notification (bfd_session_event) failed: %v", err) + } + } + + go func() { + h.log.Debugf("start watching BFD events") + defer h.log.Debugf("done watching BFD events (%v)", ctx.Err()) + + for { + select { + case e, open := <-notificationChan: + if !open { + h.log.Debugf("BFD events channel was closed") + unsubscribe() + return + } + + bfdEvent, ok := e.(*binapi.BfdUDPSessionDetails) + if !ok { + h.log.Debugf("unexpected notification type: %#v", bfdEvent) + continue + } + + event, err := h.toBfdEvent(bfdEvent) + if err != nil { + h.log.Warn(err) + continue + } + + select { + case eventChan <- event: + // ok + case <-ctx.Done(): + unsubscribe() + return + default: + // in case the channel is full + go func() { + select { + case eventChan <- event: + // sent ok + case <-time.After(EventDeliverTimeout): + h.log.Warnf("BFD (conf-ID: %d) event dropped, cannot deliver", bfdEvent.ConfKeyID) + } + }() + } + case <-ctx.Done(): + unsubscribe() + return + } + } + }() + + // enable BFD events from VPP + req := &binapi.WantBfdEvents{ + PID: uint32(os.Getpid()), + EnableDisable: true, + } + resp := &binapi.WantBfdEventsReply{} + err = h.callsChannel.SendRequest(req).ReceiveReply(resp) + // do not return error on repeated subscribe attempt + if errors.Is(err, govppapi.VPPApiError(govppapi.INVALID_REGISTRATION)) { + h.log.Debugf("already subscribed to BFD events: %v", err) + return nil + } + return err +} + +func (h *BfdVppHandler) toBfdEvent(bfdEvent *binapi.BfdUDPSessionDetails) (*bfd.BFDEvent, error) { + ifName, _, exists := h.ifIndexes.LookupBySwIfIndex(uint32(bfdEvent.SwIfIndex)) + if !exists { + return nil, fmt.Errorf("BFD event for unknown interface (sw_if_index: %d)", + bfdEvent.SwIfIndex) + } + event := &bfd.BFDEvent{ + Interface: ifName, + LocalIp: bfdEvent.LocalAddr.String(), + PeerIp: bfdEvent.PeerAddr.String(), + SessionState: stateToProto(bfdEvent.State), + } + return event, nil +} + +func stateToProto(state binapi.BfdState) bfd.BFDEvent_SessionState { + switch state { + case 1: + return bfd.BFDEvent_Down + case 2: + return bfd.BFDEvent_Init + case 3: + return bfd.BFDEvent_Up + } + return bfd.BFDEvent_Unknown +} diff --git a/plugins/bfd/vppcalls/vpp2210/vppcalls_handlers.go b/plugins/bfd/vppcalls/vpp2210/vppcalls_handlers.go new file mode 100644 index 00000000..a2befce7 --- /dev/null +++ b/plugins/bfd/vppcalls/vpp2210/vppcalls_handlers.go @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: Apache-2.0 + +// Copyright 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 vpp2210 + +import ( + govppapi "go.fd.io/govpp/api" + "go.ligato.io/cn-infra/v2/logging" + + "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/ifaceidx" + + "go.pantheon.tech/stonework/plugins/bfd/vppcalls" + binapi "go.pantheon.tech/stonework/plugins/binapi/vpp2210" + "go.pantheon.tech/stonework/plugins/binapi/vpp2210/bfd" +) + +func init() { + var msgs []govppapi.Message + msgs = append(msgs, bfd.AllMessages()...) + + vppcalls.AddBfdHandlerVersion(binapi.Version, msgs, NewBfdVppHandler) +} + +// BfdVppHandler is accessor for BFD-related vppcalls methods +type BfdVppHandler struct { + callsChannel govppapi.Channel + ifIndexes ifaceidx.IfaceMetadataIndex + log logging.Logger +} + +// NewBfdVppHandler creates new instance of BFD vppcalls handler +func NewBfdVppHandler(calls govppapi.Channel, ifIndexes ifaceidx.IfaceMetadataIndex, log logging.Logger) vppcalls.BfdVppAPI { + return &BfdVppHandler{ + callsChannel: calls, + ifIndexes: ifIndexes, + log: log, + } +} diff --git a/plugins/nat64/nat64plugin.go b/plugins/nat64/nat64plugin.go index 294748e1..a5727ce8 100644 --- a/plugins/nat64/nat64plugin.go +++ b/plugins/nat64/nat64plugin.go @@ -34,6 +34,8 @@ import ( "go.pantheon.tech/stonework/plugins/nat64/vppcalls" _ "go.pantheon.tech/stonework/plugins/nat64/vppcalls/vpp2106" + _ "go.pantheon.tech/stonework/plugins/nat64/vppcalls/vpp2202" + _ "go.pantheon.tech/stonework/plugins/nat64/vppcalls/vpp2210" ) // NAT64Plugin configures VPP NAT. diff --git a/plugins/nat64/vppcalls/vpp2202/dump_nat64_vppcalls.go b/plugins/nat64/vppcalls/vpp2202/dump_nat64_vppcalls.go new file mode 100644 index 00000000..917d416a --- /dev/null +++ b/plugins/nat64/vppcalls/vpp2202/dump_nat64_vppcalls.go @@ -0,0 +1,209 @@ +// SPDX-License-Identifier: Apache-2.0 + +// Copyright 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 vpp2202 + +import ( + "encoding/binary" + "fmt" + "net" + + natba "go.pantheon.tech/stonework/plugins/binapi/vpp2202/nat64" + "go.pantheon.tech/stonework/plugins/binapi/vpp2202/nat_types" + nat "go.pantheon.tech/stonework/proto/nat64" +) + +// Num protocol representation +const ( + ICMP uint8 = 1 + TCP uint8 = 6 + UDP uint8 = 17 +) + +// Nat64IPv6PrefixDump dumps all IPv6 prefixes configured for NAT64. +func (h *Nat64VppHandler) Nat64IPv6PrefixDump() (prefixes []*nat.Nat64IPv6Prefix, err error) { + req := &natba.Nat64PrefixDump{} + reqContext := h.callsChannel.SendMultiRequest(req) + + for { + msg := &natba.Nat64PrefixDetails{} + stop, err := reqContext.ReceiveReply(msg) + if err != nil { + return nil, fmt.Errorf("failed to dump NAT64 IPv6 prefixes: %v", err) + } + if stop { + break + } + prefixes = append(prefixes, &nat.Nat64IPv6Prefix{ + VrfId: msg.VrfID, + Prefix: msg.Prefix.ToIPNet().String(), + }) + } + + return prefixes, nil +} + +// Nat64InterfacesDump dumps NAT64 config of all NAT64-enabled interfaces. +func (h *Nat64VppHandler) Nat64InterfacesDump() (interfaces []*nat.Nat64Interface, err error) { + req := &natba.Nat64InterfaceDump{} + reqContext := h.callsChannel.SendMultiRequest(req) + + for { + msg := &natba.Nat64InterfaceDetails{} + stop, err := reqContext.ReceiveReply(msg) + if err != nil { + return nil, fmt.Errorf("failed to dump NAT64 interfaces: %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 + } + ifType := nat.Nat64Interface_IPV6_INSIDE + if msg.Flags&nat_types.NAT_IS_OUTSIDE != 0 { + ifType = nat.Nat64Interface_IPV4_OUTSIDE + } + interfaces = append(interfaces, &nat.Nat64Interface{ + Name: ifName, + Type: ifType, + }) + } + return interfaces, nil +} + +// Nat64AddressPoolsDump dumps all configured NAT64 address pools. +// Note that VPP returns configured addresses one-by-one, loosing information about address pools +// configured with multiple addresses through IP ranges. Provide expected configuration to group +// addresses from the same range. +func (h *Nat64VppHandler) Nat64AddressPoolsDump(correlateWith []*nat.Nat64AddressPool) (pools []*nat.Nat64AddressPool, err error) { + req := &natba.Nat64PoolAddrDump{} + reqContext := h.callsChannel.SendMultiRequest(req) + + for { + msg := &natba.Nat64PoolAddrDetails{} + stop, err := reqContext.ReceiveReply(msg) + if err != nil { + return nil, fmt.Errorf("failed to dump NAT64 address pools: %v", err) + } + if stop { + break + } + address := net.IP(msg.Address[:]).String() + pools = append(pools, &nat.Nat64AddressPool{ + VrfId: msg.VrfID, + FirstIp: address, + LastIp: address, + }) + } + return correlateAddressPools(pools, correlateWith), nil +} + +// Nat64StaticBIBsDump dumps NAT64 static bindings. +func (h *Nat64VppHandler) Nat64StaticBIBsDump() (bibs []*nat.Nat64StaticBIB, err error) { + req := &natba.Nat64BibDump{ + Proto: ^uint8(0), // ALL + } + reqContext := h.callsChannel.SendMultiRequest(req) + + for { + msg := &natba.Nat64BibDetails{} + stop, err := reqContext.ReceiveReply(msg) + if err != nil { + return nil, fmt.Errorf("failed to dump NAT64 static BIBs: %v", err) + } + if stop { + break + } + if msg.Flags&nat_types.NAT_IS_STATIC == 0 { + // dynamic entry + continue + } + var proto nat.Nat64StaticBIB_Protocol + switch msg.Proto { + case TCP: + proto = nat.Nat64StaticBIB_TCP + case UDP: + proto = nat.Nat64StaticBIB_UDP + case ICMP: + proto = nat.Nat64StaticBIB_ICMP + } + bibs = append(bibs, &nat.Nat64StaticBIB{ + VrfId: msg.VrfID, + Protocol: proto, + InsideIpv6Address: net.IP(msg.IAddr[:]).String(), + InsidePort: uint32(msg.IPort), + OutsideIpv4Address: net.IP(msg.OAddr[:]).String(), + OutsidePort: uint32(msg.OPort), + }) + } + return bibs, nil +} + +func correlateAddressPools(dumped, correlateWith []*nat.Nat64AddressPool) (correlated []*nat.Nat64AddressPool) { + if len(correlateWith) == 0 { + return dumped + } + dumpedMap := make(map[uint32]map[uint32]*nat.Nat64AddressPool) // VRF -> address as int -> pool + for _, pool := range dumped { + if _, initialized := dumpedMap[pool.VrfId]; !initialized { + dumpedMap[pool.VrfId] = make(map[uint32]*nat.Nat64AddressPool) + } + dumpedMap[pool.VrfId][ip2int(net.ParseIP(pool.FirstIp))] = pool + } + for _, correlate := range correlateWith { + var firstIP, lastIP net.IP + firstIP = net.ParseIP(correlate.FirstIp) + if correlate.LastIp != "" { + lastIP = net.ParseIP(correlate.LastIp) + } else { + lastIP = firstIP + } + if firstIP == nil || lastIP == nil { + continue + } + first := ip2int(firstIP) + last := ip2int(lastIP) + if first >= last { + continue + } + allDumped := true + for i := first; i <= last; i++ { + if _, isDumped := dumpedMap[correlate.VrfId][i]; !isDumped { + allDumped = false + break + } + } + if allDumped { + for i := first; i <= last; i++ { + delete(dumpedMap[correlate.VrfId], i) + } + correlated = append(correlated, correlate) + } + } + for vrf := range dumpedMap { + for _, pool := range dumpedMap[vrf] { + correlated = append(correlated, pool) + } + } + return correlated +} + +func ip2int(ip net.IP) uint32 { + return binary.BigEndian.Uint32(ip.To4()) +} diff --git a/plugins/nat64/vppcalls/vpp2202/dump_nat64_vppcalls_test.go b/plugins/nat64/vppcalls/vpp2202/dump_nat64_vppcalls_test.go new file mode 100644 index 00000000..d5262606 --- /dev/null +++ b/plugins/nat64/vppcalls/vpp2202/dump_nat64_vppcalls_test.go @@ -0,0 +1,247 @@ +// SPDX-License-Identifier: Apache-2.0 + +// Copyright 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 vpp2202_test + +import ( + "net" + "sort" + "testing" + + . "github.com/onsi/gomega" + + "go.ligato.io/cn-infra/v2/logging/logrus" + + "go.pantheon.tech/stonework/plugins/binapi/vpp2202/ip_types" + "go.pantheon.tech/stonework/plugins/binapi/vpp2202/memclnt" + natba "go.pantheon.tech/stonework/plugins/binapi/vpp2202/nat64" + "go.pantheon.tech/stonework/plugins/binapi/vpp2202/nat_types" + + "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/ifaceidx" + "go.ligato.io/vpp-agent/v3/plugins/vpp/vppmock" + + "go.pantheon.tech/stonework/plugins/nat64/vppcalls" + "go.pantheon.tech/stonework/plugins/nat64/vppcalls/vpp2202" + "go.pantheon.tech/stonework/proto/nat64" +) + +func TestNat64IPv6PrefixDump(t *testing.T) { + ctx, natHandler, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply( + &natba.Nat64PrefixDetails{ + VrfID: 5, + Prefix: ipTo6Prefix("64:ff9b::/96"), + }, + &natba.Nat64PrefixDetails{ + VrfID: 3, + Prefix: ipTo6Prefix("2004::/32"), + }) + ctx.MockVpp.MockReply(&memclnt.ControlPingReply{}) + + prefixes, err := natHandler.Nat64IPv6PrefixDump() + Expect(err).To(Succeed()) + + Expect(prefixes).To(HaveLen(2)) + + Expect(prefixes[0].VrfId).To(BeEquivalentTo(5)) + Expect(prefixes[0].Prefix).To(Equal("64:ff9b::/96")) + + Expect(prefixes[1].VrfId).To(BeEquivalentTo(3)) + Expect(prefixes[1].Prefix).To(Equal("2004::/32")) +} + +func TestNat64InterfacesDump(t *testing.T) { + ctx, natHandler, swIfIndexes := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply( + &natba.Nat64InterfaceDetails{ + SwIfIndex: 1, + Flags: nat_types.NAT_IS_OUTSIDE, + }, + &natba.Nat64InterfaceDetails{ + SwIfIndex: 2, + Flags: nat_types.NAT_IS_INSIDE, + }) + ctx.MockVpp.MockReply(&memclnt.ControlPingReply{}) + + swIfIndexes.Put("if0", &ifaceidx.IfaceMetadata{SwIfIndex: 1}) + swIfIndexes.Put("if1", &ifaceidx.IfaceMetadata{SwIfIndex: 2}) + + interfaces, err := natHandler.Nat64InterfacesDump() + Expect(err).To(Succeed()) + + Expect(interfaces).To(HaveLen(2)) + + Expect(interfaces[0].Name).To(Equal("if0")) + Expect(interfaces[0].Type).To(Equal(nat64.Nat64Interface_IPV4_OUTSIDE)) + + Expect(interfaces[1].Name).To(Equal("if1")) + Expect(interfaces[1].Type).To(Equal(nat64.Nat64Interface_IPV6_INSIDE)) +} + +func TestNat64AddressPoolsDump(t *testing.T) { + ctx, natHandler, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + // address pool + ctx.MockVpp.MockReply( + &natba.Nat64PoolAddrDetails{ + Address: ipTo4Address("10.10.1.1"), + VrfID: 1, + }, + &natba.Nat64PoolAddrDetails{ + Address: ipTo4Address("80.80.80.2"), + VrfID: 2, + }, + &natba.Nat64PoolAddrDetails{ + Address: ipTo4Address("192.168.10.3"), + VrfID: 2, + }, + &natba.Nat64PoolAddrDetails{ + Address: ipTo4Address("192.168.10.4"), + VrfID: 2, + }, + &natba.Nat64PoolAddrDetails{ + Address: ipTo4Address("192.168.10.5"), + VrfID: 2, + }) + ctx.MockVpp.MockReply(&memclnt.ControlPingReply{}) + + pools, err := natHandler.Nat64AddressPoolsDump([]*nat64.Nat64AddressPool{ + { + VrfId: 2, + FirstIp: "192.168.10.3", + LastIp: "192.168.10.5", + }, + }) + Expect(err).To(Succeed()) + + Expect(pools).To(HaveLen(3)) + + sort.Slice(pools, func(i, j int) bool { + return pools[i].FirstIp < pools[j].FirstIp + }) + + Expect(pools[0].FirstIp).To(Equal("10.10.1.1")) + Expect(pools[0].LastIp).To(Equal("10.10.1.1")) + Expect(pools[0].VrfId).To(BeEquivalentTo(1)) + + Expect(pools[1].FirstIp).To(Equal("192.168.10.3")) + Expect(pools[1].LastIp).To(Equal("192.168.10.5")) + Expect(pools[1].VrfId).To(BeEquivalentTo(2)) + + Expect(pools[2].FirstIp).To(Equal("80.80.80.2")) + Expect(pools[2].LastIp).To(Equal("80.80.80.2")) + Expect(pools[2].VrfId).To(BeEquivalentTo(2)) +} + +func TestNat64StaticBIBsDump(t *testing.T) { + ctx, natHandler, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply( + &natba.Nat64BibDetails{ + IAddr: ipTo6Address("2000::3"), + IPort: 8080, + OAddr: ipTo4Address("172.16.2.3"), + OPort: 80, + VrfID: 5, + Proto: 6, // TCP + Flags: nat_types.NAT_IS_STATIC, + }, + &natba.Nat64BibDetails{ + IAddr: ipTo6Address("2000::8"), + IPort: 9090, + OAddr: ipTo4Address("10.10.5.5"), + OPort: 90, + VrfID: 0, + Proto: 17, // UDP + Flags: nat_types.NAT_IS_STATIC, + }, + &natba.Nat64BibDetails{ + IAddr: ipTo6Address("2000::10"), + OAddr: ipTo4Address("80.80.80.1"), + VrfID: 4, + Proto: 1, // ICMP + Flags: nat_types.NAT_IS_STATIC, + }) + ctx.MockVpp.MockReply(&memclnt.ControlPingReply{}) + + bibs, err := natHandler.Nat64StaticBIBsDump() + Expect(err).To(Succeed()) + + Expect(bibs).To(HaveLen(3)) + + Expect(bibs[0].InsideIpv6Address).To(Equal("2000::3")) + Expect(bibs[0].InsidePort).To(BeEquivalentTo(8080)) + Expect(bibs[0].OutsideIpv4Address).To(Equal("172.16.2.3")) + Expect(bibs[0].OutsidePort).To(BeEquivalentTo(80)) + Expect(bibs[0].VrfId).To(BeEquivalentTo(5)) + Expect(bibs[0].Protocol).To(Equal(nat64.Nat64StaticBIB_TCP)) + + Expect(bibs[1].InsideIpv6Address).To(Equal("2000::8")) + Expect(bibs[1].InsidePort).To(BeEquivalentTo(9090)) + Expect(bibs[1].OutsideIpv4Address).To(Equal("10.10.5.5")) + Expect(bibs[1].OutsidePort).To(BeEquivalentTo(90)) + Expect(bibs[1].VrfId).To(BeEquivalentTo(0)) + Expect(bibs[1].Protocol).To(Equal(nat64.Nat64StaticBIB_UDP)) + + Expect(bibs[2].InsideIpv6Address).To(Equal("2000::10")) + Expect(bibs[2].InsidePort).To(BeEquivalentTo(0)) + Expect(bibs[2].OutsideIpv4Address).To(Equal("80.80.80.1")) + Expect(bibs[2].OutsidePort).To(BeEquivalentTo(0)) + Expect(bibs[2].VrfId).To(BeEquivalentTo(4)) + Expect(bibs[2].Protocol).To(Equal(nat64.Nat64StaticBIB_ICMP)) +} + +func ipTo6Address(ipStr string) (addr ip_types.IP6Address) { + netIP := net.ParseIP(ipStr) + Expect(netIP).ToNot(BeNil()) + ip4 := netIP.To4() + Expect(ip4).To(BeNil()) + copy(addr[:], netIP.To16()) + return +} + +func ipTo6Prefix(ipStr string) (prefix ip_types.IP6Prefix) { + _, netIP, err := net.ParseCIDR(ipStr) + Expect(err).To(Succeed()) + ip4 := netIP.IP.To4() + Expect(ip4).To(BeNil()) + copy(prefix.Address[:], netIP.IP.To16()) + prefixLen, _ := netIP.Mask.Size() + prefix.Len = uint8(prefixLen) + return +} + +func ipTo4Address(ipStr string) (addr ip_types.IP4Address) { + netIP := net.ParseIP(ipStr) + if ip4 := netIP.To4(); ip4 != nil { + copy(addr[:], ip4) + } + return +} + +func natTestSetup(t *testing.T) (*vppmock.TestCtx, vppcalls.Nat64VppAPI, ifaceidx.IfaceMetadataIndexRW) { + ctx := vppmock.SetupTestCtx(t) + log := logrus.NewLogger("test-log") + swIfIndexes := ifaceidx.NewIfaceIndex(logrus.DefaultLogger(), "test-sw_if_indexes") + natHandler := vpp2202.NewNat64VppHandler(ctx.MockChannel, swIfIndexes, log) + return ctx, natHandler, swIfIndexes +} diff --git a/plugins/nat64/vppcalls/vpp2202/nat64_vppcalls.go b/plugins/nat64/vppcalls/vpp2202/nat64_vppcalls.go new file mode 100644 index 00000000..cc198a1c --- /dev/null +++ b/plugins/nat64/vppcalls/vpp2202/nat64_vppcalls.go @@ -0,0 +1,191 @@ +// SPDX-License-Identifier: Apache-2.0 + +// Copyright 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 vpp2202 + +import ( + "fmt" + "net" + + "go.pantheon.tech/stonework/plugins/binapi/vpp2202/interface_types" + "go.pantheon.tech/stonework/plugins/binapi/vpp2202/ip_types" + natba "go.pantheon.tech/stonework/plugins/binapi/vpp2202/nat64" + "go.pantheon.tech/stonework/plugins/binapi/vpp2202/nat_types" + + "go.pantheon.tech/stonework/proto/nat64" +) + +// AddNat64IPv6Prefix adds IPv6 prefix for NAT64 (used to embed IPv4 address). +func (h *Nat64VppHandler) AddNat64IPv6Prefix(vrf uint32, prefix string) error { + return h.handleNat64IPv6Prefix(vrf, prefix, true) +} + +// DelNat64IPv6Prefix removes existing IPv6 prefix previously configured for NAT64. +func (h *Nat64VppHandler) DelNat64IPv6Prefix(vrf uint32, prefix string) error { + return h.handleNat64IPv6Prefix(vrf, prefix, false) +} + +// EnableNat64Interface enables NAT64 for provided interface. +func (h *Nat64VppHandler) EnableNat64Interface(iface string, natIfaceType nat64.Nat64Interface_Type) error { + return h.handleNat64Interface(iface, natIfaceType, true) +} + +// DisableNat64Interface disables NAT64 for provided interface. +func (h *Nat64VppHandler) DisableNat64Interface(iface string, natIfaceType nat64.Nat64Interface_Type) error { + return h.handleNat64Interface(iface, natIfaceType, false) +} + +// AddNat64AddressPool adds new IPV4 address pool into the NAT64 pools. +func (h *Nat64VppHandler) AddNat64AddressPool(vrf uint32, firstIP, lastIP string) error { + return h.handleNat64AddressPool(vrf, firstIP, lastIP, true) +} + +// DelNat64AddressPool removes existing IPv4 address pool from the NAT64 pools. +func (h *Nat64VppHandler) DelNat64AddressPool(vrf uint32, firstIP, lastIP string) error { + return h.handleNat64AddressPool(vrf, firstIP, lastIP, false) +} + +// AddNat64StaticBIB creates new NAT64 static binding. +func (h *Nat64VppHandler) AddNat64StaticBIB(bib *nat64.Nat64StaticBIB) error { + return h.handleNat64StaticBIB(bib, true) +} + +// DelNat64StaticBIB removes existing NAT64 static binding. +func (h *Nat64VppHandler) DelNat64StaticBIB(bib *nat64.Nat64StaticBIB) error { + return h.handleNat64StaticBIB(bib, false) +} + +// Calls VPP binary API to set/unset NAT64 IPv6 prefix. +func (h *Nat64VppHandler) handleNat64IPv6Prefix(vrf uint32, prefix string, isAdd bool) error { + ipv6Prefix, err := ipTo6Prefix(prefix) + if err != nil { + return fmt.Errorf("unable to parse IPv6 prefix %s: %v", prefix, err) + } + req := &natba.Nat64AddDelPrefix{ + VrfID: vrf, + Prefix: ipv6Prefix, + IsAdd: isAdd, + } + reply := &natba.Nat64AddDelPrefixReply{} + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + return nil +} + +// Calls VPP binary API to set/unset interface NAT64 feature. +func (h *Nat64VppHandler) handleNat64Interface(iface string, natIfaceType nat64.Nat64Interface_Type, isAdd bool) error { + // get interface metadata + ifaceMeta, found := h.ifIndexes.LookupByName(iface) + if !found { + return fmt.Errorf("failed to get metadata for interface: %s", iface) + } + var flags nat_types.NatConfigFlags + switch natIfaceType { + case nat64.Nat64Interface_IPV6_INSIDE: + flags = nat_types.NAT_IS_INSIDE + case nat64.Nat64Interface_IPV4_OUTSIDE: + flags = nat_types.NAT_IS_OUTSIDE + } + req := &natba.Nat64AddDelInterface{ + SwIfIndex: interface_types.InterfaceIndex(ifaceMeta.SwIfIndex), + Flags: flags, + IsAdd: isAdd, + } + reply := &natba.Nat64AddDelInterfaceReply{} + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + return nil +} + +// Calls VPP binary API to add/del NAT64 address pool. +func (h *Nat64VppHandler) handleNat64AddressPool(vrf uint32, firstIP, lastIP string, isAdd bool) error { + startAddr, err := ip_types.ParseIP4Address(firstIP) + if err != nil { + return fmt.Errorf("unable to parse address %s from the NAT64 pool: %v", firstIP, err) + } + endAddr := startAddr + if lastIP != "" { + endAddr, err = ip_types.ParseIP4Address(lastIP) + if err != nil { + return fmt.Errorf("unable to parse address %s from the NAT64 pool: %v", lastIP, err) + } + } + req := &natba.Nat64AddDelPoolAddrRange{ + StartAddr: startAddr, + EndAddr: endAddr, + VrfID: vrf, + IsAdd: isAdd, + } + reply := &natba.Nat64AddDelPoolAddrRangeReply{} + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + return nil +} + +// Calls VPP binary API to add/del NAT64 static binding. +func (h *Nat64VppHandler) handleNat64StaticBIB(bib *nat64.Nat64StaticBIB, isAdd bool) error { + inAddr, err := ip_types.ParseIP6Address(bib.GetInsideIpv6Address()) + if err != nil { + return fmt.Errorf("unable to parse inside IPv6 address (%s): %v", bib.GetInsideIpv6Address(), err) + } + outAddr, err := ip_types.ParseIP4Address(bib.GetOutsideIpv4Address()) + if err != nil { + return fmt.Errorf("unable to parse outside IPv4 address (%s): %v", bib.GetOutsideIpv4Address(), err) + } + var proto uint8 + switch bib.GetProtocol() { + case nat64.Nat64StaticBIB_TCP: + proto = TCP + case nat64.Nat64StaticBIB_UDP: + proto = UDP + case nat64.Nat64StaticBIB_ICMP: + proto = ICMP + default: + h.log.Warnf("Unknown protocol %v, defaulting to TCP", bib.GetProtocol()) + proto = TCP + } + req := &natba.Nat64AddDelStaticBib{ + IAddr: inAddr, + OAddr: outAddr, + IPort: uint16(bib.GetInsidePort()), + OPort: uint16(bib.GetOutsidePort()), + Proto: proto, + VrfID: bib.GetVrfId(), + IsAdd: isAdd, + } + reply := &natba.Nat64AddDelStaticBibReply{} + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + return nil +} + +func ipTo6Prefix(ipStr string) (prefix ip_types.IP6Prefix, err error) { + _, netIP, err := net.ParseCIDR(ipStr) + if err != nil { + return prefix, fmt.Errorf("invalid IP (%q): %v", ipStr, err) + } + if ip4 := netIP.IP.To4(); ip4 != nil { + return prefix, fmt.Errorf("required IPv6, provided IPv4 prefix: %q", ipStr) + } + copy(prefix.Address[:], netIP.IP.To16()) + prefixLen, _ := netIP.Mask.Size() + prefix.Len = uint8(prefixLen) + return +} diff --git a/plugins/nat64/vppcalls/vpp2202/nat64_vppcalls_test.go b/plugins/nat64/vppcalls/vpp2202/nat64_vppcalls_test.go new file mode 100644 index 00000000..687d1d66 --- /dev/null +++ b/plugins/nat64/vppcalls/vpp2202/nat64_vppcalls_test.go @@ -0,0 +1,387 @@ +// SPDX-License-Identifier: Apache-2.0 + +// Copyright 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 vpp2202_test + +import ( + "net" + "testing" + + . "github.com/onsi/gomega" + + "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/ifaceidx" + + "go.pantheon.tech/stonework/plugins/binapi/vpp2202/ip_types" + natba "go.pantheon.tech/stonework/plugins/binapi/vpp2202/nat64" + "go.pantheon.tech/stonework/plugins/binapi/vpp2202/nat_types" + "go.pantheon.tech/stonework/proto/nat64" +) + +func TestAddNat64IPv6Prefix(t *testing.T) { + ctx, natHandler, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + _, prefix1, _ := net.ParseCIDR("64:ff9b::/96") + _, prefix2, _ := net.ParseCIDR("2004::/32") + + ctx.MockVpp.MockReply(&natba.Nat64AddDelPrefixReply{}) + err := natHandler.AddNat64IPv6Prefix(0, prefix1.String()) + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok := ctx.MockChannel.Msg.(*natba.Nat64AddDelPrefix) + Expect(ok).To(BeTrue()) + Expect(msg.IsAdd).To(BeTrue()) + Expect(ip6PrefixToIPNet(msg.Prefix)).To(BeEquivalentTo(prefix1.String())) + Expect(msg.VrfID).To(BeEquivalentTo(0)) + + ctx.MockVpp.MockReply(&natba.Nat64AddDelPrefixReply{}) + err = natHandler.AddNat64IPv6Prefix(5, prefix2.String()) + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok = ctx.MockChannel.Msg.(*natba.Nat64AddDelPrefix) + Expect(ok).To(BeTrue()) + Expect(msg.IsAdd).To(BeTrue()) + Expect(ip6PrefixToIPNet(msg.Prefix)).To(BeEquivalentTo(prefix2.String())) + Expect(msg.VrfID).To(BeEquivalentTo(5)) + + err = natHandler.AddNat64IPv6Prefix(1, "invalid prefix") + Expect(err).Should(HaveOccurred()) + err = natHandler.AddNat64IPv6Prefix(1, "192.168.1.0/24") + Expect(err).Should(HaveOccurred()) +} + +func TestDelNat64IPv6Prefix(t *testing.T) { + ctx, natHandler, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + _, prefix1, _ := net.ParseCIDR("64:ff9b::/96") + _, prefix2, _ := net.ParseCIDR("2004::/32") + + ctx.MockVpp.MockReply(&natba.Nat64AddDelPrefixReply{}) + err := natHandler.DelNat64IPv6Prefix(0, prefix1.String()) + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok := ctx.MockChannel.Msg.(*natba.Nat64AddDelPrefix) + Expect(ok).To(BeTrue()) + Expect(msg.IsAdd).To(BeFalse()) + Expect(ip6PrefixToIPNet(msg.Prefix)).To(BeEquivalentTo(prefix1.String())) + Expect(msg.VrfID).To(BeEquivalentTo(0)) + + ctx.MockVpp.MockReply(&natba.Nat64AddDelPrefixReply{}) + err = natHandler.DelNat64IPv6Prefix(5, prefix2.String()) + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok = ctx.MockChannel.Msg.(*natba.Nat64AddDelPrefix) + Expect(ok).To(BeTrue()) + Expect(msg.IsAdd).To(BeFalse()) + Expect(ip6PrefixToIPNet(msg.Prefix)).To(BeEquivalentTo(prefix2.String())) + Expect(msg.VrfID).To(BeEquivalentTo(5)) + + err = natHandler.DelNat64IPv6Prefix(1, "invalid prefix") + Expect(err).Should(HaveOccurred()) + err = natHandler.DelNat64IPv6Prefix(1, "192.168.1.0/24") + Expect(err).Should(HaveOccurred()) +} + +func TestEnableNat64Interface(t *testing.T) { + ctx, natHandler, swIfIndexes := natTestSetup(t) + defer ctx.TeardownTestCtx() + + swIfIndexes.Put("if1", &ifaceidx.IfaceMetadata{SwIfIndex: 2}) + + ctx.MockVpp.MockReply(&natba.Nat64AddDelInterfaceReply{}) + err := natHandler.EnableNat64Interface("if1", nat64.Nat64Interface_IPV4_OUTSIDE) + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok := ctx.MockChannel.Msg.(*natba.Nat64AddDelInterface) + Expect(ok).To(BeTrue()) + Expect(msg.IsAdd).To(BeTrue()) + Expect(msg.SwIfIndex).To(BeEquivalentTo(2)) + Expect(msg.Flags).To(BeEquivalentTo(nat_types.NAT_IS_OUTSIDE)) + + ctx.MockVpp.MockReply(&natba.Nat64AddDelInterfaceReply{}) + err = natHandler.EnableNat64Interface("if1", nat64.Nat64Interface_IPV6_INSIDE) + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok = ctx.MockChannel.Msg.(*natba.Nat64AddDelInterface) + Expect(ok).To(BeTrue()) + Expect(msg.IsAdd).To(BeTrue()) + Expect(msg.SwIfIndex).To(BeEquivalentTo(2)) + Expect(msg.Flags).To(BeEquivalentTo(nat_types.NAT_IS_INSIDE)) + + err = natHandler.EnableNat64Interface("non-existent interface", nat64.Nat64Interface_IPV4_OUTSIDE) + Expect(err).Should(HaveOccurred()) +} + +func TestDisableNat64Interface(t *testing.T) { + ctx, natHandler, swIfIndexes := natTestSetup(t) + defer ctx.TeardownTestCtx() + + swIfIndexes.Put("if1", &ifaceidx.IfaceMetadata{SwIfIndex: 2}) + + ctx.MockVpp.MockReply(&natba.Nat64AddDelInterfaceReply{}) + err := natHandler.DisableNat64Interface("if1", nat64.Nat64Interface_IPV4_OUTSIDE) + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok := ctx.MockChannel.Msg.(*natba.Nat64AddDelInterface) + Expect(ok).To(BeTrue()) + Expect(msg.IsAdd).To(BeFalse()) + Expect(msg.SwIfIndex).To(BeEquivalentTo(2)) + Expect(msg.Flags).To(BeEquivalentTo(nat_types.NAT_IS_OUTSIDE)) + + ctx.MockVpp.MockReply(&natba.Nat64AddDelInterfaceReply{}) + err = natHandler.DisableNat64Interface("if1", nat64.Nat64Interface_IPV6_INSIDE) + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok = ctx.MockChannel.Msg.(*natba.Nat64AddDelInterface) + Expect(ok).To(BeTrue()) + Expect(msg.IsAdd).To(BeFalse()) + Expect(msg.SwIfIndex).To(BeEquivalentTo(2)) + Expect(msg.Flags).To(BeEquivalentTo(nat_types.NAT_IS_INSIDE)) + + err = natHandler.DisableNat64Interface("non-existent interface", nat64.Nat64Interface_IPV4_OUTSIDE) + Expect(err).Should(HaveOccurred()) +} + +func TestAddNat64AddressPool(t *testing.T) { + ctx, natHandler, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + addr1 := net.ParseIP("10.0.0.1").To4() + addr2 := net.ParseIP("10.0.0.10").To4() + + // first IP only + ctx.MockVpp.MockReply(&natba.Nat64AddDelPoolAddrRangeReply{}) + err := natHandler.AddNat64AddressPool(0, addr1.String(), "") + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok := ctx.MockChannel.Msg.(*natba.Nat64AddDelPoolAddrRange) + Expect(ok).To(BeTrue()) + Expect(msg.IsAdd).To(BeTrue()) + Expect(msg.StartAddr.String()).To(BeEquivalentTo(addr1.String())) + Expect(msg.EndAddr.String()).To(BeEquivalentTo(addr1.String())) + Expect(msg.VrfID).To(BeEquivalentTo(0)) + + // first IP + last IP + ctx.MockVpp.MockReply(&natba.Nat64AddDelPoolAddrRangeReply{}) + err = natHandler.AddNat64AddressPool(5, addr1.String(), addr2.String()) + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok = ctx.MockChannel.Msg.(*natba.Nat64AddDelPoolAddrRange) + Expect(ok).To(BeTrue()) + Expect(msg.IsAdd).To(BeTrue()) + Expect(msg.StartAddr.String()).To(BeEquivalentTo(addr1.String())) + Expect(msg.EndAddr.String()).To(BeEquivalentTo(addr2.String())) + Expect(msg.VrfID).To(BeEquivalentTo(5)) + + err = natHandler.AddNat64AddressPool(0, "invalid address", "") + Expect(err).Should(HaveOccurred()) + err = natHandler.AddNat64AddressPool(0, "invalid address", addr2.String()) + Expect(err).Should(HaveOccurred()) + err = natHandler.AddNat64AddressPool(0, addr1.String(), "invalid address") + Expect(err).Should(HaveOccurred()) +} + +func TestDelNat64AddressPool(t *testing.T) { + ctx, natHandler, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + addr1 := net.ParseIP("10.0.0.1").To4() + addr2 := net.ParseIP("10.0.0.10").To4() + + // first IP only + ctx.MockVpp.MockReply(&natba.Nat64AddDelPoolAddrRangeReply{}) + err := natHandler.DelNat64AddressPool(0, addr1.String(), "") + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok := ctx.MockChannel.Msg.(*natba.Nat64AddDelPoolAddrRange) + Expect(ok).To(BeTrue()) + Expect(msg.IsAdd).To(BeFalse()) + Expect(msg.StartAddr.String()).To(BeEquivalentTo(addr1.String())) + Expect(msg.EndAddr.String()).To(BeEquivalentTo(addr1.String())) + Expect(msg.VrfID).To(BeEquivalentTo(0)) + + // first IP + last IP + ctx.MockVpp.MockReply(&natba.Nat64AddDelPoolAddrRangeReply{}) + err = natHandler.DelNat64AddressPool(5, addr1.String(), addr2.String()) + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok = ctx.MockChannel.Msg.(*natba.Nat64AddDelPoolAddrRange) + Expect(ok).To(BeTrue()) + Expect(msg.IsAdd).To(BeFalse()) + Expect(msg.StartAddr.String()).To(BeEquivalentTo(addr1.String())) + Expect(msg.EndAddr.String()).To(BeEquivalentTo(addr2.String())) + Expect(msg.VrfID).To(BeEquivalentTo(5)) + + err = natHandler.DelNat64AddressPool(0, "invalid address", "") + Expect(err).Should(HaveOccurred()) + err = natHandler.DelNat64AddressPool(0, "invalid address", addr2.String()) + Expect(err).Should(HaveOccurred()) + err = natHandler.DelNat64AddressPool(0, addr1.String(), "invalid address") + Expect(err).Should(HaveOccurred()) +} + +func TestAddNat64StaticBIB(t *testing.T) { + ctx, natHandler, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + inAddr := net.ParseIP("2000::3").To16() + outAddr := net.ParseIP("172.16.2.3").To4() + + // TCP + ctx.MockVpp.MockReply(&natba.Nat64AddDelStaticBibReply{}) + err := natHandler.AddNat64StaticBIB(&nat64.Nat64StaticBIB{ + VrfId: 10, + InsideIpv6Address: inAddr.String(), + InsidePort: 8000, + OutsideIpv4Address: outAddr.String(), + OutsidePort: 80, + Protocol: nat64.Nat64StaticBIB_TCP, + }) + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok := ctx.MockChannel.Msg.(*natba.Nat64AddDelStaticBib) + Expect(ok).To(BeTrue()) + Expect(msg.IsAdd).To(BeTrue()) + Expect(msg.IAddr.String()).To(BeEquivalentTo(inAddr.String())) + Expect(msg.IPort).To(BeEquivalentTo(8000)) + Expect(msg.OAddr.String()).To(BeEquivalentTo(outAddr.String())) + Expect(msg.OPort).To(BeEquivalentTo(80)) + Expect(msg.VrfID).To(BeEquivalentTo(10)) + Expect(msg.Proto).To(BeEquivalentTo(6)) + + // UDP + ctx.MockVpp.MockReply(&natba.Nat64AddDelStaticBibReply{}) + err = natHandler.AddNat64StaticBIB(&nat64.Nat64StaticBIB{ + VrfId: 0, + InsideIpv6Address: inAddr.String(), + InsidePort: 9000, + OutsideIpv4Address: outAddr.String(), + OutsidePort: 90, + Protocol: nat64.Nat64StaticBIB_UDP, + }) + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok = ctx.MockChannel.Msg.(*natba.Nat64AddDelStaticBib) + Expect(ok).To(BeTrue()) + Expect(msg.IsAdd).To(BeTrue()) + Expect(msg.IAddr.String()).To(BeEquivalentTo(inAddr.String())) + Expect(msg.IPort).To(BeEquivalentTo(9000)) + Expect(msg.OAddr.String()).To(BeEquivalentTo(outAddr.String())) + Expect(msg.OPort).To(BeEquivalentTo(90)) + Expect(msg.VrfID).To(BeEquivalentTo(0)) + Expect(msg.Proto).To(BeEquivalentTo(17)) + + // Invalid data + err = natHandler.AddNat64StaticBIB(&nat64.Nat64StaticBIB{ + VrfId: 0, + InsideIpv6Address: "192.168.1.1", // expecting IPv6 + InsidePort: 9000, + OutsideIpv4Address: outAddr.String(), + OutsidePort: 90, + Protocol: nat64.Nat64StaticBIB_UDP, + }) + Expect(err).Should(HaveOccurred()) + err = natHandler.AddNat64StaticBIB(&nat64.Nat64StaticBIB{ + VrfId: 0, + InsideIpv6Address: inAddr.String(), + InsidePort: 9000, + OutsideIpv4Address: "invalid IP address", + OutsidePort: 90, + Protocol: nat64.Nat64StaticBIB_UDP, + }) + Expect(err).Should(HaveOccurred()) +} + +func TestDelNat64StaticBIB(t *testing.T) { + ctx, natHandler, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + inAddr := net.ParseIP("2000::3").To16() + outAddr := net.ParseIP("172.16.2.3").To4() + + // TCP + ctx.MockVpp.MockReply(&natba.Nat64AddDelStaticBibReply{}) + err := natHandler.DelNat64StaticBIB(&nat64.Nat64StaticBIB{ + VrfId: 10, + InsideIpv6Address: inAddr.String(), + InsidePort: 8000, + OutsideIpv4Address: outAddr.String(), + OutsidePort: 80, + Protocol: nat64.Nat64StaticBIB_TCP, + }) + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok := ctx.MockChannel.Msg.(*natba.Nat64AddDelStaticBib) + Expect(ok).To(BeTrue()) + Expect(msg.IsAdd).To(BeFalse()) + Expect(msg.IAddr.String()).To(BeEquivalentTo(inAddr.String())) + Expect(msg.IPort).To(BeEquivalentTo(8000)) + Expect(msg.OAddr.String()).To(BeEquivalentTo(outAddr.String())) + Expect(msg.OPort).To(BeEquivalentTo(80)) + Expect(msg.VrfID).To(BeEquivalentTo(10)) + Expect(msg.Proto).To(BeEquivalentTo(6)) + + // UDP + ctx.MockVpp.MockReply(&natba.Nat64AddDelStaticBibReply{}) + err = natHandler.DelNat64StaticBIB(&nat64.Nat64StaticBIB{ + VrfId: 0, + InsideIpv6Address: inAddr.String(), + InsidePort: 9000, + OutsideIpv4Address: outAddr.String(), + OutsidePort: 90, + Protocol: nat64.Nat64StaticBIB_UDP, + }) + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok = ctx.MockChannel.Msg.(*natba.Nat64AddDelStaticBib) + Expect(ok).To(BeTrue()) + Expect(msg.IsAdd).To(BeFalse()) + Expect(msg.IAddr.String()).To(BeEquivalentTo(inAddr.String())) + Expect(msg.IPort).To(BeEquivalentTo(9000)) + Expect(msg.OAddr.String()).To(BeEquivalentTo(outAddr.String())) + Expect(msg.OPort).To(BeEquivalentTo(90)) + Expect(msg.VrfID).To(BeEquivalentTo(0)) + Expect(msg.Proto).To(BeEquivalentTo(17)) + + // Invalid data + err = natHandler.DelNat64StaticBIB(&nat64.Nat64StaticBIB{ + VrfId: 0, + InsideIpv6Address: "192.168.1.1", // expecting IPv6 + InsidePort: 9000, + OutsideIpv4Address: outAddr.String(), + OutsidePort: 90, + Protocol: nat64.Nat64StaticBIB_UDP, + }) + Expect(err).Should(HaveOccurred()) + err = natHandler.DelNat64StaticBIB(&nat64.Nat64StaticBIB{ + VrfId: 0, + InsideIpv6Address: inAddr.String(), + InsidePort: 9000, + OutsideIpv4Address: "invalid IP address", + OutsidePort: 90, + Protocol: nat64.Nat64StaticBIB_UDP, + }) + Expect(err).Should(HaveOccurred()) +} + +func ip6PrefixToIPNet(prefix ip_types.IP6Prefix) string { + ipNet := &net.IPNet{} + ipNet.IP = make(net.IP, net.IPv6len) + copy(ipNet.IP[:], prefix.Address[:]) + ipNet.Mask = net.CIDRMask(int(prefix.Len), 8*net.IPv6len) + return ipNet.String() +} diff --git a/plugins/nat64/vppcalls/vpp2202/vppcalls_handler.go b/plugins/nat64/vppcalls/vpp2202/vppcalls_handler.go new file mode 100644 index 00000000..e3ee864d --- /dev/null +++ b/plugins/nat64/vppcalls/vpp2202/vppcalls_handler.go @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: Apache-2.0 + +// Copyright 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 vpp2202 + +import ( + govppapi "go.fd.io/govpp/api" + "go.ligato.io/cn-infra/v2/logging" + + "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/ifaceidx" + + binapi "go.pantheon.tech/stonework/plugins/binapi/vpp2202" + natba "go.pantheon.tech/stonework/plugins/binapi/vpp2202/nat64" + "go.pantheon.tech/stonework/plugins/nat64/vppcalls" +) + +func init() { + var msgs []govppapi.Message + msgs = append(msgs, natba.AllMessages()...) + + vppcalls.AddNat64HandlerVersion(binapi.Version, msgs, NewNat64VppHandler) +} + +// Nat64VppHandler is accessor for NAT64-related vppcalls methods. +type Nat64VppHandler struct { + callsChannel govppapi.Channel + ifIndexes ifaceidx.IfaceMetadataIndex + log logging.Logger +} + +// NewNat64VppHandler creates new instance of NAT64 vppcalls handler. +func NewNat64VppHandler(callsChan govppapi.Channel, + ifIndexes ifaceidx.IfaceMetadataIndex, log logging.Logger, +) vppcalls.Nat64VppAPI { + return &Nat64VppHandler{ + callsChannel: callsChan, + ifIndexes: ifIndexes, + log: log, + } +} diff --git a/plugins/nat64/vppcalls/vpp2210/dump_nat64_vppcalls.go b/plugins/nat64/vppcalls/vpp2210/dump_nat64_vppcalls.go new file mode 100644 index 00000000..c280a006 --- /dev/null +++ b/plugins/nat64/vppcalls/vpp2210/dump_nat64_vppcalls.go @@ -0,0 +1,209 @@ +// SPDX-License-Identifier: Apache-2.0 + +// Copyright 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 vpp2210 + +import ( + "encoding/binary" + "fmt" + "net" + + natba "go.pantheon.tech/stonework/plugins/binapi/vpp2210/nat64" + "go.pantheon.tech/stonework/plugins/binapi/vpp2210/nat_types" + nat "go.pantheon.tech/stonework/proto/nat64" +) + +// Num protocol representation +const ( + ICMP uint8 = 1 + TCP uint8 = 6 + UDP uint8 = 17 +) + +// Nat64IPv6PrefixDump dumps all IPv6 prefixes configured for NAT64. +func (h *Nat64VppHandler) Nat64IPv6PrefixDump() (prefixes []*nat.Nat64IPv6Prefix, err error) { + req := &natba.Nat64PrefixDump{} + reqContext := h.callsChannel.SendMultiRequest(req) + + for { + msg := &natba.Nat64PrefixDetails{} + stop, err := reqContext.ReceiveReply(msg) + if err != nil { + return nil, fmt.Errorf("failed to dump NAT64 IPv6 prefixes: %v", err) + } + if stop { + break + } + prefixes = append(prefixes, &nat.Nat64IPv6Prefix{ + VrfId: msg.VrfID, + Prefix: msg.Prefix.ToIPNet().String(), + }) + } + + return prefixes, nil +} + +// Nat64InterfacesDump dumps NAT64 config of all NAT64-enabled interfaces. +func (h *Nat64VppHandler) Nat64InterfacesDump() (interfaces []*nat.Nat64Interface, err error) { + req := &natba.Nat64InterfaceDump{} + reqContext := h.callsChannel.SendMultiRequest(req) + + for { + msg := &natba.Nat64InterfaceDetails{} + stop, err := reqContext.ReceiveReply(msg) + if err != nil { + return nil, fmt.Errorf("failed to dump NAT64 interfaces: %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 + } + ifType := nat.Nat64Interface_IPV6_INSIDE + if msg.Flags&nat_types.NAT_IS_OUTSIDE != 0 { + ifType = nat.Nat64Interface_IPV4_OUTSIDE + } + interfaces = append(interfaces, &nat.Nat64Interface{ + Name: ifName, + Type: ifType, + }) + } + return interfaces, nil +} + +// Nat64AddressPoolsDump dumps all configured NAT64 address pools. +// Note that VPP returns configured addresses one-by-one, loosing information about address pools +// configured with multiple addresses through IP ranges. Provide expected configuration to group +// addresses from the same range. +func (h *Nat64VppHandler) Nat64AddressPoolsDump(correlateWith []*nat.Nat64AddressPool) (pools []*nat.Nat64AddressPool, err error) { + req := &natba.Nat64PoolAddrDump{} + reqContext := h.callsChannel.SendMultiRequest(req) + + for { + msg := &natba.Nat64PoolAddrDetails{} + stop, err := reqContext.ReceiveReply(msg) + if err != nil { + return nil, fmt.Errorf("failed to dump NAT64 address pools: %v", err) + } + if stop { + break + } + address := net.IP(msg.Address[:]).String() + pools = append(pools, &nat.Nat64AddressPool{ + VrfId: msg.VrfID, + FirstIp: address, + LastIp: address, + }) + } + return correlateAddressPools(pools, correlateWith), nil +} + +// Nat64StaticBIBsDump dumps NAT64 static bindings. +func (h *Nat64VppHandler) Nat64StaticBIBsDump() (bibs []*nat.Nat64StaticBIB, err error) { + req := &natba.Nat64BibDump{ + Proto: ^uint8(0), // ALL + } + reqContext := h.callsChannel.SendMultiRequest(req) + + for { + msg := &natba.Nat64BibDetails{} + stop, err := reqContext.ReceiveReply(msg) + if err != nil { + return nil, fmt.Errorf("failed to dump NAT64 static BIBs: %v", err) + } + if stop { + break + } + if msg.Flags&nat_types.NAT_IS_STATIC == 0 { + // dynamic entry + continue + } + var proto nat.Nat64StaticBIB_Protocol + switch msg.Proto { + case TCP: + proto = nat.Nat64StaticBIB_TCP + case UDP: + proto = nat.Nat64StaticBIB_UDP + case ICMP: + proto = nat.Nat64StaticBIB_ICMP + } + bibs = append(bibs, &nat.Nat64StaticBIB{ + VrfId: msg.VrfID, + Protocol: proto, + InsideIpv6Address: net.IP(msg.IAddr[:]).String(), + InsidePort: uint32(msg.IPort), + OutsideIpv4Address: net.IP(msg.OAddr[:]).String(), + OutsidePort: uint32(msg.OPort), + }) + } + return bibs, nil +} + +func correlateAddressPools(dumped, correlateWith []*nat.Nat64AddressPool) (correlated []*nat.Nat64AddressPool) { + if len(correlateWith) == 0 { + return dumped + } + dumpedMap := make(map[uint32]map[uint32]*nat.Nat64AddressPool) // VRF -> address as int -> pool + for _, pool := range dumped { + if _, initialized := dumpedMap[pool.VrfId]; !initialized { + dumpedMap[pool.VrfId] = make(map[uint32]*nat.Nat64AddressPool) + } + dumpedMap[pool.VrfId][ip2int(net.ParseIP(pool.FirstIp))] = pool + } + for _, correlate := range correlateWith { + var firstIP, lastIP net.IP + firstIP = net.ParseIP(correlate.FirstIp) + if correlate.LastIp != "" { + lastIP = net.ParseIP(correlate.LastIp) + } else { + lastIP = firstIP + } + if firstIP == nil || lastIP == nil { + continue + } + first := ip2int(firstIP) + last := ip2int(lastIP) + if first >= last { + continue + } + allDumped := true + for i := first; i <= last; i++ { + if _, isDumped := dumpedMap[correlate.VrfId][i]; !isDumped { + allDumped = false + break + } + } + if allDumped { + for i := first; i <= last; i++ { + delete(dumpedMap[correlate.VrfId], i) + } + correlated = append(correlated, correlate) + } + } + for vrf := range dumpedMap { + for _, pool := range dumpedMap[vrf] { + correlated = append(correlated, pool) + } + } + return correlated +} + +func ip2int(ip net.IP) uint32 { + return binary.BigEndian.Uint32(ip.To4()) +} diff --git a/plugins/nat64/vppcalls/vpp2210/dump_nat64_vppcalls_test.go b/plugins/nat64/vppcalls/vpp2210/dump_nat64_vppcalls_test.go new file mode 100644 index 00000000..07fd012f --- /dev/null +++ b/plugins/nat64/vppcalls/vpp2210/dump_nat64_vppcalls_test.go @@ -0,0 +1,247 @@ +// SPDX-License-Identifier: Apache-2.0 + +// Copyright 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 vpp2210_test + +import ( + "net" + "sort" + "testing" + + . "github.com/onsi/gomega" + + "go.ligato.io/cn-infra/v2/logging/logrus" + + "go.pantheon.tech/stonework/plugins/binapi/vpp2210/ip_types" + "go.pantheon.tech/stonework/plugins/binapi/vpp2210/memclnt" + natba "go.pantheon.tech/stonework/plugins/binapi/vpp2210/nat64" + "go.pantheon.tech/stonework/plugins/binapi/vpp2210/nat_types" + + "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/ifaceidx" + "go.ligato.io/vpp-agent/v3/plugins/vpp/vppmock" + + "go.pantheon.tech/stonework/plugins/nat64/vppcalls" + "go.pantheon.tech/stonework/plugins/nat64/vppcalls/vpp2210" + "go.pantheon.tech/stonework/proto/nat64" +) + +func TestNat64IPv6PrefixDump(t *testing.T) { + ctx, natHandler, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply( + &natba.Nat64PrefixDetails{ + VrfID: 5, + Prefix: ipTo6Prefix("64:ff9b::/96"), + }, + &natba.Nat64PrefixDetails{ + VrfID: 3, + Prefix: ipTo6Prefix("2004::/32"), + }) + ctx.MockVpp.MockReply(&memclnt.ControlPingReply{}) + + prefixes, err := natHandler.Nat64IPv6PrefixDump() + Expect(err).To(Succeed()) + + Expect(prefixes).To(HaveLen(2)) + + Expect(prefixes[0].VrfId).To(BeEquivalentTo(5)) + Expect(prefixes[0].Prefix).To(Equal("64:ff9b::/96")) + + Expect(prefixes[1].VrfId).To(BeEquivalentTo(3)) + Expect(prefixes[1].Prefix).To(Equal("2004::/32")) +} + +func TestNat64InterfacesDump(t *testing.T) { + ctx, natHandler, swIfIndexes := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply( + &natba.Nat64InterfaceDetails{ + SwIfIndex: 1, + Flags: nat_types.NAT_IS_OUTSIDE, + }, + &natba.Nat64InterfaceDetails{ + SwIfIndex: 2, + Flags: nat_types.NAT_IS_INSIDE, + }) + ctx.MockVpp.MockReply(&memclnt.ControlPingReply{}) + + swIfIndexes.Put("if0", &ifaceidx.IfaceMetadata{SwIfIndex: 1}) + swIfIndexes.Put("if1", &ifaceidx.IfaceMetadata{SwIfIndex: 2}) + + interfaces, err := natHandler.Nat64InterfacesDump() + Expect(err).To(Succeed()) + + Expect(interfaces).To(HaveLen(2)) + + Expect(interfaces[0].Name).To(Equal("if0")) + Expect(interfaces[0].Type).To(Equal(nat64.Nat64Interface_IPV4_OUTSIDE)) + + Expect(interfaces[1].Name).To(Equal("if1")) + Expect(interfaces[1].Type).To(Equal(nat64.Nat64Interface_IPV6_INSIDE)) +} + +func TestNat64AddressPoolsDump(t *testing.T) { + ctx, natHandler, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + // address pool + ctx.MockVpp.MockReply( + &natba.Nat64PoolAddrDetails{ + Address: ipTo4Address("10.10.1.1"), + VrfID: 1, + }, + &natba.Nat64PoolAddrDetails{ + Address: ipTo4Address("80.80.80.2"), + VrfID: 2, + }, + &natba.Nat64PoolAddrDetails{ + Address: ipTo4Address("192.168.10.3"), + VrfID: 2, + }, + &natba.Nat64PoolAddrDetails{ + Address: ipTo4Address("192.168.10.4"), + VrfID: 2, + }, + &natba.Nat64PoolAddrDetails{ + Address: ipTo4Address("192.168.10.5"), + VrfID: 2, + }) + ctx.MockVpp.MockReply(&memclnt.ControlPingReply{}) + + pools, err := natHandler.Nat64AddressPoolsDump([]*nat64.Nat64AddressPool{ + { + VrfId: 2, + FirstIp: "192.168.10.3", + LastIp: "192.168.10.5", + }, + }) + Expect(err).To(Succeed()) + + Expect(pools).To(HaveLen(3)) + + sort.Slice(pools, func(i, j int) bool { + return pools[i].FirstIp < pools[j].FirstIp + }) + + Expect(pools[0].FirstIp).To(Equal("10.10.1.1")) + Expect(pools[0].LastIp).To(Equal("10.10.1.1")) + Expect(pools[0].VrfId).To(BeEquivalentTo(1)) + + Expect(pools[1].FirstIp).To(Equal("192.168.10.3")) + Expect(pools[1].LastIp).To(Equal("192.168.10.5")) + Expect(pools[1].VrfId).To(BeEquivalentTo(2)) + + Expect(pools[2].FirstIp).To(Equal("80.80.80.2")) + Expect(pools[2].LastIp).To(Equal("80.80.80.2")) + Expect(pools[2].VrfId).To(BeEquivalentTo(2)) +} + +func TestNat64StaticBIBsDump(t *testing.T) { + ctx, natHandler, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + ctx.MockVpp.MockReply( + &natba.Nat64BibDetails{ + IAddr: ipTo6Address("2000::3"), + IPort: 8080, + OAddr: ipTo4Address("172.16.2.3"), + OPort: 80, + VrfID: 5, + Proto: 6, // TCP + Flags: nat_types.NAT_IS_STATIC, + }, + &natba.Nat64BibDetails{ + IAddr: ipTo6Address("2000::8"), + IPort: 9090, + OAddr: ipTo4Address("10.10.5.5"), + OPort: 90, + VrfID: 0, + Proto: 17, // UDP + Flags: nat_types.NAT_IS_STATIC, + }, + &natba.Nat64BibDetails{ + IAddr: ipTo6Address("2000::10"), + OAddr: ipTo4Address("80.80.80.1"), + VrfID: 4, + Proto: 1, // ICMP + Flags: nat_types.NAT_IS_STATIC, + }) + ctx.MockVpp.MockReply(&memclnt.ControlPingReply{}) + + bibs, err := natHandler.Nat64StaticBIBsDump() + Expect(err).To(Succeed()) + + Expect(bibs).To(HaveLen(3)) + + Expect(bibs[0].InsideIpv6Address).To(Equal("2000::3")) + Expect(bibs[0].InsidePort).To(BeEquivalentTo(8080)) + Expect(bibs[0].OutsideIpv4Address).To(Equal("172.16.2.3")) + Expect(bibs[0].OutsidePort).To(BeEquivalentTo(80)) + Expect(bibs[0].VrfId).To(BeEquivalentTo(5)) + Expect(bibs[0].Protocol).To(Equal(nat64.Nat64StaticBIB_TCP)) + + Expect(bibs[1].InsideIpv6Address).To(Equal("2000::8")) + Expect(bibs[1].InsidePort).To(BeEquivalentTo(9090)) + Expect(bibs[1].OutsideIpv4Address).To(Equal("10.10.5.5")) + Expect(bibs[1].OutsidePort).To(BeEquivalentTo(90)) + Expect(bibs[1].VrfId).To(BeEquivalentTo(0)) + Expect(bibs[1].Protocol).To(Equal(nat64.Nat64StaticBIB_UDP)) + + Expect(bibs[2].InsideIpv6Address).To(Equal("2000::10")) + Expect(bibs[2].InsidePort).To(BeEquivalentTo(0)) + Expect(bibs[2].OutsideIpv4Address).To(Equal("80.80.80.1")) + Expect(bibs[2].OutsidePort).To(BeEquivalentTo(0)) + Expect(bibs[2].VrfId).To(BeEquivalentTo(4)) + Expect(bibs[2].Protocol).To(Equal(nat64.Nat64StaticBIB_ICMP)) +} + +func ipTo6Address(ipStr string) (addr ip_types.IP6Address) { + netIP := net.ParseIP(ipStr) + Expect(netIP).ToNot(BeNil()) + ip4 := netIP.To4() + Expect(ip4).To(BeNil()) + copy(addr[:], netIP.To16()) + return +} + +func ipTo6Prefix(ipStr string) (prefix ip_types.IP6Prefix) { + _, netIP, err := net.ParseCIDR(ipStr) + Expect(err).To(Succeed()) + ip4 := netIP.IP.To4() + Expect(ip4).To(BeNil()) + copy(prefix.Address[:], netIP.IP.To16()) + prefixLen, _ := netIP.Mask.Size() + prefix.Len = uint8(prefixLen) + return +} + +func ipTo4Address(ipStr string) (addr ip_types.IP4Address) { + netIP := net.ParseIP(ipStr) + if ip4 := netIP.To4(); ip4 != nil { + copy(addr[:], ip4) + } + return +} + +func natTestSetup(t *testing.T) (*vppmock.TestCtx, vppcalls.Nat64VppAPI, ifaceidx.IfaceMetadataIndexRW) { + ctx := vppmock.SetupTestCtx(t) + log := logrus.NewLogger("test-log") + swIfIndexes := ifaceidx.NewIfaceIndex(logrus.DefaultLogger(), "test-sw_if_indexes") + natHandler := vpp2210.NewNat64VppHandler(ctx.MockChannel, swIfIndexes, log) + return ctx, natHandler, swIfIndexes +} diff --git a/plugins/nat64/vppcalls/vpp2210/nat64_vppcalls.go b/plugins/nat64/vppcalls/vpp2210/nat64_vppcalls.go new file mode 100644 index 00000000..5e138807 --- /dev/null +++ b/plugins/nat64/vppcalls/vpp2210/nat64_vppcalls.go @@ -0,0 +1,191 @@ +// SPDX-License-Identifier: Apache-2.0 + +// Copyright 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 vpp2210 + +import ( + "fmt" + "net" + + "go.pantheon.tech/stonework/plugins/binapi/vpp2210/interface_types" + "go.pantheon.tech/stonework/plugins/binapi/vpp2210/ip_types" + natba "go.pantheon.tech/stonework/plugins/binapi/vpp2210/nat64" + "go.pantheon.tech/stonework/plugins/binapi/vpp2210/nat_types" + + "go.pantheon.tech/stonework/proto/nat64" +) + +// AddNat64IPv6Prefix adds IPv6 prefix for NAT64 (used to embed IPv4 address). +func (h *Nat64VppHandler) AddNat64IPv6Prefix(vrf uint32, prefix string) error { + return h.handleNat64IPv6Prefix(vrf, prefix, true) +} + +// DelNat64IPv6Prefix removes existing IPv6 prefix previously configured for NAT64. +func (h *Nat64VppHandler) DelNat64IPv6Prefix(vrf uint32, prefix string) error { + return h.handleNat64IPv6Prefix(vrf, prefix, false) +} + +// EnableNat64Interface enables NAT64 for provided interface. +func (h *Nat64VppHandler) EnableNat64Interface(iface string, natIfaceType nat64.Nat64Interface_Type) error { + return h.handleNat64Interface(iface, natIfaceType, true) +} + +// DisableNat64Interface disables NAT64 for provided interface. +func (h *Nat64VppHandler) DisableNat64Interface(iface string, natIfaceType nat64.Nat64Interface_Type) error { + return h.handleNat64Interface(iface, natIfaceType, false) +} + +// AddNat64AddressPool adds new IPV4 address pool into the NAT64 pools. +func (h *Nat64VppHandler) AddNat64AddressPool(vrf uint32, firstIP, lastIP string) error { + return h.handleNat64AddressPool(vrf, firstIP, lastIP, true) +} + +// DelNat64AddressPool removes existing IPv4 address pool from the NAT64 pools. +func (h *Nat64VppHandler) DelNat64AddressPool(vrf uint32, firstIP, lastIP string) error { + return h.handleNat64AddressPool(vrf, firstIP, lastIP, false) +} + +// AddNat64StaticBIB creates new NAT64 static binding. +func (h *Nat64VppHandler) AddNat64StaticBIB(bib *nat64.Nat64StaticBIB) error { + return h.handleNat64StaticBIB(bib, true) +} + +// DelNat64StaticBIB removes existing NAT64 static binding. +func (h *Nat64VppHandler) DelNat64StaticBIB(bib *nat64.Nat64StaticBIB) error { + return h.handleNat64StaticBIB(bib, false) +} + +// Calls VPP binary API to set/unset NAT64 IPv6 prefix. +func (h *Nat64VppHandler) handleNat64IPv6Prefix(vrf uint32, prefix string, isAdd bool) error { + ipv6Prefix, err := ipTo6Prefix(prefix) + if err != nil { + return fmt.Errorf("unable to parse IPv6 prefix %s: %v", prefix, err) + } + req := &natba.Nat64AddDelPrefix{ + VrfID: vrf, + Prefix: ipv6Prefix, + IsAdd: isAdd, + } + reply := &natba.Nat64AddDelPrefixReply{} + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + return nil +} + +// Calls VPP binary API to set/unset interface NAT64 feature. +func (h *Nat64VppHandler) handleNat64Interface(iface string, natIfaceType nat64.Nat64Interface_Type, isAdd bool) error { + // get interface metadata + ifaceMeta, found := h.ifIndexes.LookupByName(iface) + if !found { + return fmt.Errorf("failed to get metadata for interface: %s", iface) + } + var flags nat_types.NatConfigFlags + switch natIfaceType { + case nat64.Nat64Interface_IPV6_INSIDE: + flags = nat_types.NAT_IS_INSIDE + case nat64.Nat64Interface_IPV4_OUTSIDE: + flags = nat_types.NAT_IS_OUTSIDE + } + req := &natba.Nat64AddDelInterface{ + SwIfIndex: interface_types.InterfaceIndex(ifaceMeta.SwIfIndex), + Flags: flags, + IsAdd: isAdd, + } + reply := &natba.Nat64AddDelInterfaceReply{} + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + return nil +} + +// Calls VPP binary API to add/del NAT64 address pool. +func (h *Nat64VppHandler) handleNat64AddressPool(vrf uint32, firstIP, lastIP string, isAdd bool) error { + startAddr, err := ip_types.ParseIP4Address(firstIP) + if err != nil { + return fmt.Errorf("unable to parse address %s from the NAT64 pool: %v", firstIP, err) + } + endAddr := startAddr + if lastIP != "" { + endAddr, err = ip_types.ParseIP4Address(lastIP) + if err != nil { + return fmt.Errorf("unable to parse address %s from the NAT64 pool: %v", lastIP, err) + } + } + req := &natba.Nat64AddDelPoolAddrRange{ + StartAddr: startAddr, + EndAddr: endAddr, + VrfID: vrf, + IsAdd: isAdd, + } + reply := &natba.Nat64AddDelPoolAddrRangeReply{} + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + return nil +} + +// Calls VPP binary API to add/del NAT64 static binding. +func (h *Nat64VppHandler) handleNat64StaticBIB(bib *nat64.Nat64StaticBIB, isAdd bool) error { + inAddr, err := ip_types.ParseIP6Address(bib.GetInsideIpv6Address()) + if err != nil { + return fmt.Errorf("unable to parse inside IPv6 address (%s): %v", bib.GetInsideIpv6Address(), err) + } + outAddr, err := ip_types.ParseIP4Address(bib.GetOutsideIpv4Address()) + if err != nil { + return fmt.Errorf("unable to parse outside IPv4 address (%s): %v", bib.GetOutsideIpv4Address(), err) + } + var proto uint8 + switch bib.GetProtocol() { + case nat64.Nat64StaticBIB_TCP: + proto = TCP + case nat64.Nat64StaticBIB_UDP: + proto = UDP + case nat64.Nat64StaticBIB_ICMP: + proto = ICMP + default: + h.log.Warnf("Unknown protocol %v, defaulting to TCP", bib.GetProtocol()) + proto = TCP + } + req := &natba.Nat64AddDelStaticBib{ + IAddr: inAddr, + OAddr: outAddr, + IPort: uint16(bib.GetInsidePort()), + OPort: uint16(bib.GetOutsidePort()), + Proto: proto, + VrfID: bib.GetVrfId(), + IsAdd: isAdd, + } + reply := &natba.Nat64AddDelStaticBibReply{} + if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil { + return err + } + return nil +} + +func ipTo6Prefix(ipStr string) (prefix ip_types.IP6Prefix, err error) { + _, netIP, err := net.ParseCIDR(ipStr) + if err != nil { + return prefix, fmt.Errorf("invalid IP (%q): %v", ipStr, err) + } + if ip4 := netIP.IP.To4(); ip4 != nil { + return prefix, fmt.Errorf("required IPv6, provided IPv4 prefix: %q", ipStr) + } + copy(prefix.Address[:], netIP.IP.To16()) + prefixLen, _ := netIP.Mask.Size() + prefix.Len = uint8(prefixLen) + return +} diff --git a/plugins/nat64/vppcalls/vpp2210/nat64_vppcalls_test.go b/plugins/nat64/vppcalls/vpp2210/nat64_vppcalls_test.go new file mode 100644 index 00000000..88fe1dc0 --- /dev/null +++ b/plugins/nat64/vppcalls/vpp2210/nat64_vppcalls_test.go @@ -0,0 +1,387 @@ +// SPDX-License-Identifier: Apache-2.0 + +// Copyright 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 vpp2210_test + +import ( + "net" + "testing" + + . "github.com/onsi/gomega" + + "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/ifaceidx" + + "go.pantheon.tech/stonework/plugins/binapi/vpp2210/ip_types" + natba "go.pantheon.tech/stonework/plugins/binapi/vpp2210/nat64" + "go.pantheon.tech/stonework/plugins/binapi/vpp2210/nat_types" + "go.pantheon.tech/stonework/proto/nat64" +) + +func TestAddNat64IPv6Prefix(t *testing.T) { + ctx, natHandler, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + _, prefix1, _ := net.ParseCIDR("64:ff9b::/96") + _, prefix2, _ := net.ParseCIDR("2004::/32") + + ctx.MockVpp.MockReply(&natba.Nat64AddDelPrefixReply{}) + err := natHandler.AddNat64IPv6Prefix(0, prefix1.String()) + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok := ctx.MockChannel.Msg.(*natba.Nat64AddDelPrefix) + Expect(ok).To(BeTrue()) + Expect(msg.IsAdd).To(BeTrue()) + Expect(ip6PrefixToIPNet(msg.Prefix)).To(BeEquivalentTo(prefix1.String())) + Expect(msg.VrfID).To(BeEquivalentTo(0)) + + ctx.MockVpp.MockReply(&natba.Nat64AddDelPrefixReply{}) + err = natHandler.AddNat64IPv6Prefix(5, prefix2.String()) + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok = ctx.MockChannel.Msg.(*natba.Nat64AddDelPrefix) + Expect(ok).To(BeTrue()) + Expect(msg.IsAdd).To(BeTrue()) + Expect(ip6PrefixToIPNet(msg.Prefix)).To(BeEquivalentTo(prefix2.String())) + Expect(msg.VrfID).To(BeEquivalentTo(5)) + + err = natHandler.AddNat64IPv6Prefix(1, "invalid prefix") + Expect(err).Should(HaveOccurred()) + err = natHandler.AddNat64IPv6Prefix(1, "192.168.1.0/24") + Expect(err).Should(HaveOccurred()) +} + +func TestDelNat64IPv6Prefix(t *testing.T) { + ctx, natHandler, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + _, prefix1, _ := net.ParseCIDR("64:ff9b::/96") + _, prefix2, _ := net.ParseCIDR("2004::/32") + + ctx.MockVpp.MockReply(&natba.Nat64AddDelPrefixReply{}) + err := natHandler.DelNat64IPv6Prefix(0, prefix1.String()) + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok := ctx.MockChannel.Msg.(*natba.Nat64AddDelPrefix) + Expect(ok).To(BeTrue()) + Expect(msg.IsAdd).To(BeFalse()) + Expect(ip6PrefixToIPNet(msg.Prefix)).To(BeEquivalentTo(prefix1.String())) + Expect(msg.VrfID).To(BeEquivalentTo(0)) + + ctx.MockVpp.MockReply(&natba.Nat64AddDelPrefixReply{}) + err = natHandler.DelNat64IPv6Prefix(5, prefix2.String()) + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok = ctx.MockChannel.Msg.(*natba.Nat64AddDelPrefix) + Expect(ok).To(BeTrue()) + Expect(msg.IsAdd).To(BeFalse()) + Expect(ip6PrefixToIPNet(msg.Prefix)).To(BeEquivalentTo(prefix2.String())) + Expect(msg.VrfID).To(BeEquivalentTo(5)) + + err = natHandler.DelNat64IPv6Prefix(1, "invalid prefix") + Expect(err).Should(HaveOccurred()) + err = natHandler.DelNat64IPv6Prefix(1, "192.168.1.0/24") + Expect(err).Should(HaveOccurred()) +} + +func TestEnableNat64Interface(t *testing.T) { + ctx, natHandler, swIfIndexes := natTestSetup(t) + defer ctx.TeardownTestCtx() + + swIfIndexes.Put("if1", &ifaceidx.IfaceMetadata{SwIfIndex: 2}) + + ctx.MockVpp.MockReply(&natba.Nat64AddDelInterfaceReply{}) + err := natHandler.EnableNat64Interface("if1", nat64.Nat64Interface_IPV4_OUTSIDE) + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok := ctx.MockChannel.Msg.(*natba.Nat64AddDelInterface) + Expect(ok).To(BeTrue()) + Expect(msg.IsAdd).To(BeTrue()) + Expect(msg.SwIfIndex).To(BeEquivalentTo(2)) + Expect(msg.Flags).To(BeEquivalentTo(nat_types.NAT_IS_OUTSIDE)) + + ctx.MockVpp.MockReply(&natba.Nat64AddDelInterfaceReply{}) + err = natHandler.EnableNat64Interface("if1", nat64.Nat64Interface_IPV6_INSIDE) + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok = ctx.MockChannel.Msg.(*natba.Nat64AddDelInterface) + Expect(ok).To(BeTrue()) + Expect(msg.IsAdd).To(BeTrue()) + Expect(msg.SwIfIndex).To(BeEquivalentTo(2)) + Expect(msg.Flags).To(BeEquivalentTo(nat_types.NAT_IS_INSIDE)) + + err = natHandler.EnableNat64Interface("non-existent interface", nat64.Nat64Interface_IPV4_OUTSIDE) + Expect(err).Should(HaveOccurred()) +} + +func TestDisableNat64Interface(t *testing.T) { + ctx, natHandler, swIfIndexes := natTestSetup(t) + defer ctx.TeardownTestCtx() + + swIfIndexes.Put("if1", &ifaceidx.IfaceMetadata{SwIfIndex: 2}) + + ctx.MockVpp.MockReply(&natba.Nat64AddDelInterfaceReply{}) + err := natHandler.DisableNat64Interface("if1", nat64.Nat64Interface_IPV4_OUTSIDE) + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok := ctx.MockChannel.Msg.(*natba.Nat64AddDelInterface) + Expect(ok).To(BeTrue()) + Expect(msg.IsAdd).To(BeFalse()) + Expect(msg.SwIfIndex).To(BeEquivalentTo(2)) + Expect(msg.Flags).To(BeEquivalentTo(nat_types.NAT_IS_OUTSIDE)) + + ctx.MockVpp.MockReply(&natba.Nat64AddDelInterfaceReply{}) + err = natHandler.DisableNat64Interface("if1", nat64.Nat64Interface_IPV6_INSIDE) + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok = ctx.MockChannel.Msg.(*natba.Nat64AddDelInterface) + Expect(ok).To(BeTrue()) + Expect(msg.IsAdd).To(BeFalse()) + Expect(msg.SwIfIndex).To(BeEquivalentTo(2)) + Expect(msg.Flags).To(BeEquivalentTo(nat_types.NAT_IS_INSIDE)) + + err = natHandler.DisableNat64Interface("non-existent interface", nat64.Nat64Interface_IPV4_OUTSIDE) + Expect(err).Should(HaveOccurred()) +} + +func TestAddNat64AddressPool(t *testing.T) { + ctx, natHandler, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + addr1 := net.ParseIP("10.0.0.1").To4() + addr2 := net.ParseIP("10.0.0.10").To4() + + // first IP only + ctx.MockVpp.MockReply(&natba.Nat64AddDelPoolAddrRangeReply{}) + err := natHandler.AddNat64AddressPool(0, addr1.String(), "") + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok := ctx.MockChannel.Msg.(*natba.Nat64AddDelPoolAddrRange) + Expect(ok).To(BeTrue()) + Expect(msg.IsAdd).To(BeTrue()) + Expect(msg.StartAddr.String()).To(BeEquivalentTo(addr1.String())) + Expect(msg.EndAddr.String()).To(BeEquivalentTo(addr1.String())) + Expect(msg.VrfID).To(BeEquivalentTo(0)) + + // first IP + last IP + ctx.MockVpp.MockReply(&natba.Nat64AddDelPoolAddrRangeReply{}) + err = natHandler.AddNat64AddressPool(5, addr1.String(), addr2.String()) + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok = ctx.MockChannel.Msg.(*natba.Nat64AddDelPoolAddrRange) + Expect(ok).To(BeTrue()) + Expect(msg.IsAdd).To(BeTrue()) + Expect(msg.StartAddr.String()).To(BeEquivalentTo(addr1.String())) + Expect(msg.EndAddr.String()).To(BeEquivalentTo(addr2.String())) + Expect(msg.VrfID).To(BeEquivalentTo(5)) + + err = natHandler.AddNat64AddressPool(0, "invalid address", "") + Expect(err).Should(HaveOccurred()) + err = natHandler.AddNat64AddressPool(0, "invalid address", addr2.String()) + Expect(err).Should(HaveOccurred()) + err = natHandler.AddNat64AddressPool(0, addr1.String(), "invalid address") + Expect(err).Should(HaveOccurred()) +} + +func TestDelNat64AddressPool(t *testing.T) { + ctx, natHandler, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + addr1 := net.ParseIP("10.0.0.1").To4() + addr2 := net.ParseIP("10.0.0.10").To4() + + // first IP only + ctx.MockVpp.MockReply(&natba.Nat64AddDelPoolAddrRangeReply{}) + err := natHandler.DelNat64AddressPool(0, addr1.String(), "") + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok := ctx.MockChannel.Msg.(*natba.Nat64AddDelPoolAddrRange) + Expect(ok).To(BeTrue()) + Expect(msg.IsAdd).To(BeFalse()) + Expect(msg.StartAddr.String()).To(BeEquivalentTo(addr1.String())) + Expect(msg.EndAddr.String()).To(BeEquivalentTo(addr1.String())) + Expect(msg.VrfID).To(BeEquivalentTo(0)) + + // first IP + last IP + ctx.MockVpp.MockReply(&natba.Nat64AddDelPoolAddrRangeReply{}) + err = natHandler.DelNat64AddressPool(5, addr1.String(), addr2.String()) + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok = ctx.MockChannel.Msg.(*natba.Nat64AddDelPoolAddrRange) + Expect(ok).To(BeTrue()) + Expect(msg.IsAdd).To(BeFalse()) + Expect(msg.StartAddr.String()).To(BeEquivalentTo(addr1.String())) + Expect(msg.EndAddr.String()).To(BeEquivalentTo(addr2.String())) + Expect(msg.VrfID).To(BeEquivalentTo(5)) + + err = natHandler.DelNat64AddressPool(0, "invalid address", "") + Expect(err).Should(HaveOccurred()) + err = natHandler.DelNat64AddressPool(0, "invalid address", addr2.String()) + Expect(err).Should(HaveOccurred()) + err = natHandler.DelNat64AddressPool(0, addr1.String(), "invalid address") + Expect(err).Should(HaveOccurred()) +} + +func TestAddNat64StaticBIB(t *testing.T) { + ctx, natHandler, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + inAddr := net.ParseIP("2000::3").To16() + outAddr := net.ParseIP("172.16.2.3").To4() + + // TCP + ctx.MockVpp.MockReply(&natba.Nat64AddDelStaticBibReply{}) + err := natHandler.AddNat64StaticBIB(&nat64.Nat64StaticBIB{ + VrfId: 10, + InsideIpv6Address: inAddr.String(), + InsidePort: 8000, + OutsideIpv4Address: outAddr.String(), + OutsidePort: 80, + Protocol: nat64.Nat64StaticBIB_TCP, + }) + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok := ctx.MockChannel.Msg.(*natba.Nat64AddDelStaticBib) + Expect(ok).To(BeTrue()) + Expect(msg.IsAdd).To(BeTrue()) + Expect(msg.IAddr.String()).To(BeEquivalentTo(inAddr.String())) + Expect(msg.IPort).To(BeEquivalentTo(8000)) + Expect(msg.OAddr.String()).To(BeEquivalentTo(outAddr.String())) + Expect(msg.OPort).To(BeEquivalentTo(80)) + Expect(msg.VrfID).To(BeEquivalentTo(10)) + Expect(msg.Proto).To(BeEquivalentTo(6)) + + // UDP + ctx.MockVpp.MockReply(&natba.Nat64AddDelStaticBibReply{}) + err = natHandler.AddNat64StaticBIB(&nat64.Nat64StaticBIB{ + VrfId: 0, + InsideIpv6Address: inAddr.String(), + InsidePort: 9000, + OutsideIpv4Address: outAddr.String(), + OutsidePort: 90, + Protocol: nat64.Nat64StaticBIB_UDP, + }) + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok = ctx.MockChannel.Msg.(*natba.Nat64AddDelStaticBib) + Expect(ok).To(BeTrue()) + Expect(msg.IsAdd).To(BeTrue()) + Expect(msg.IAddr.String()).To(BeEquivalentTo(inAddr.String())) + Expect(msg.IPort).To(BeEquivalentTo(9000)) + Expect(msg.OAddr.String()).To(BeEquivalentTo(outAddr.String())) + Expect(msg.OPort).To(BeEquivalentTo(90)) + Expect(msg.VrfID).To(BeEquivalentTo(0)) + Expect(msg.Proto).To(BeEquivalentTo(17)) + + // Invalid data + err = natHandler.AddNat64StaticBIB(&nat64.Nat64StaticBIB{ + VrfId: 0, + InsideIpv6Address: "192.168.1.1", // expecting IPv6 + InsidePort: 9000, + OutsideIpv4Address: outAddr.String(), + OutsidePort: 90, + Protocol: nat64.Nat64StaticBIB_UDP, + }) + Expect(err).Should(HaveOccurred()) + err = natHandler.AddNat64StaticBIB(&nat64.Nat64StaticBIB{ + VrfId: 0, + InsideIpv6Address: inAddr.String(), + InsidePort: 9000, + OutsideIpv4Address: "invalid IP address", + OutsidePort: 90, + Protocol: nat64.Nat64StaticBIB_UDP, + }) + Expect(err).Should(HaveOccurred()) +} + +func TestDelNat64StaticBIB(t *testing.T) { + ctx, natHandler, _ := natTestSetup(t) + defer ctx.TeardownTestCtx() + + inAddr := net.ParseIP("2000::3").To16() + outAddr := net.ParseIP("172.16.2.3").To4() + + // TCP + ctx.MockVpp.MockReply(&natba.Nat64AddDelStaticBibReply{}) + err := natHandler.DelNat64StaticBIB(&nat64.Nat64StaticBIB{ + VrfId: 10, + InsideIpv6Address: inAddr.String(), + InsidePort: 8000, + OutsideIpv4Address: outAddr.String(), + OutsidePort: 80, + Protocol: nat64.Nat64StaticBIB_TCP, + }) + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok := ctx.MockChannel.Msg.(*natba.Nat64AddDelStaticBib) + Expect(ok).To(BeTrue()) + Expect(msg.IsAdd).To(BeFalse()) + Expect(msg.IAddr.String()).To(BeEquivalentTo(inAddr.String())) + Expect(msg.IPort).To(BeEquivalentTo(8000)) + Expect(msg.OAddr.String()).To(BeEquivalentTo(outAddr.String())) + Expect(msg.OPort).To(BeEquivalentTo(80)) + Expect(msg.VrfID).To(BeEquivalentTo(10)) + Expect(msg.Proto).To(BeEquivalentTo(6)) + + // UDP + ctx.MockVpp.MockReply(&natba.Nat64AddDelStaticBibReply{}) + err = natHandler.DelNat64StaticBIB(&nat64.Nat64StaticBIB{ + VrfId: 0, + InsideIpv6Address: inAddr.String(), + InsidePort: 9000, + OutsideIpv4Address: outAddr.String(), + OutsidePort: 90, + Protocol: nat64.Nat64StaticBIB_UDP, + }) + Expect(err).ShouldNot(HaveOccurred()) + + msg, ok = ctx.MockChannel.Msg.(*natba.Nat64AddDelStaticBib) + Expect(ok).To(BeTrue()) + Expect(msg.IsAdd).To(BeFalse()) + Expect(msg.IAddr.String()).To(BeEquivalentTo(inAddr.String())) + Expect(msg.IPort).To(BeEquivalentTo(9000)) + Expect(msg.OAddr.String()).To(BeEquivalentTo(outAddr.String())) + Expect(msg.OPort).To(BeEquivalentTo(90)) + Expect(msg.VrfID).To(BeEquivalentTo(0)) + Expect(msg.Proto).To(BeEquivalentTo(17)) + + // Invalid data + err = natHandler.DelNat64StaticBIB(&nat64.Nat64StaticBIB{ + VrfId: 0, + InsideIpv6Address: "192.168.1.1", // expecting IPv6 + InsidePort: 9000, + OutsideIpv4Address: outAddr.String(), + OutsidePort: 90, + Protocol: nat64.Nat64StaticBIB_UDP, + }) + Expect(err).Should(HaveOccurred()) + err = natHandler.DelNat64StaticBIB(&nat64.Nat64StaticBIB{ + VrfId: 0, + InsideIpv6Address: inAddr.String(), + InsidePort: 9000, + OutsideIpv4Address: "invalid IP address", + OutsidePort: 90, + Protocol: nat64.Nat64StaticBIB_UDP, + }) + Expect(err).Should(HaveOccurred()) +} + +func ip6PrefixToIPNet(prefix ip_types.IP6Prefix) string { + ipNet := &net.IPNet{} + ipNet.IP = make(net.IP, net.IPv6len) + copy(ipNet.IP[:], prefix.Address[:]) + ipNet.Mask = net.CIDRMask(int(prefix.Len), 8*net.IPv6len) + return ipNet.String() +} diff --git a/plugins/nat64/vppcalls/vpp2210/vppcalls_handler.go b/plugins/nat64/vppcalls/vpp2210/vppcalls_handler.go new file mode 100644 index 00000000..e6c52898 --- /dev/null +++ b/plugins/nat64/vppcalls/vpp2210/vppcalls_handler.go @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: Apache-2.0 + +// Copyright 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 vpp2210 + +import ( + govppapi "go.fd.io/govpp/api" + "go.ligato.io/cn-infra/v2/logging" + + "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/ifaceidx" + + binapi "go.pantheon.tech/stonework/plugins/binapi/vpp2210" + natba "go.pantheon.tech/stonework/plugins/binapi/vpp2210/nat64" + "go.pantheon.tech/stonework/plugins/nat64/vppcalls" +) + +func init() { + var msgs []govppapi.Message + msgs = append(msgs, natba.AllMessages()...) + + vppcalls.AddNat64HandlerVersion(binapi.Version, msgs, NewNat64VppHandler) +} + +// Nat64VppHandler is accessor for NAT64-related vppcalls methods. +type Nat64VppHandler struct { + callsChannel govppapi.Channel + ifIndexes ifaceidx.IfaceMetadataIndex + log logging.Logger +} + +// NewNat64VppHandler creates new instance of NAT64 vppcalls handler. +func NewNat64VppHandler(callsChan govppapi.Channel, + ifIndexes ifaceidx.IfaceMetadataIndex, log logging.Logger, +) vppcalls.Nat64VppAPI { + return &Nat64VppHandler{ + callsChannel: callsChan, + ifIndexes: ifIndexes, + log: log, + } +}