Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Added support for NAT44 static mapping twice-NAT pool IP address reference #1728

Merged
merged 7 commits into from
Sep 22, 2020
16 changes: 16 additions & 0 deletions plugins/vpp/natplugin/descriptor/nat44_address_pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ func NewNAT44AddressPoolDescriptor(nat44GlobalDesc *NAT44GlobalDescriptor,
Delete: ctx.Delete,
Retrieve: ctx.Retrieve,
Dependencies: ctx.Dependencies,
DerivedValues: ctx.DerivedValues,
// retrieve global NAT config first (required for deprecated global NAT interface & address API)
RetrieveDependencies: []string{NAT44GlobalDescriptorName},
}
Expand Down Expand Up @@ -151,6 +152,21 @@ func (d *NAT44AddressPoolDescriptor) Dependencies(key string, natAddr *nat.Nat44
}
}

// DerivedValues derives:
// - for twiceNAT address pool the pool itself with exposed IP addresses and VRF in derived key
func (d *NAT44AddressPoolDescriptor) DerivedValues(key string, addrPool *nat.Nat44AddressPool) (derValues []kvs.KeyValuePair) {
if addrPool.TwiceNat {
// this derived value may seem as copy of nat44-pool, but nat44-pool key can have 2 forms and in form
// where nat44-pool key is only pool name, there can't be made dependency based on IP address and
// twiceNAT bool => this derived key is needed
derValues = append(derValues, kvs.KeyValuePair{
Key: nat.DerivedTwiceNATAddressPoolKey(addrPool.FirstIp, addrPool.LastIp, addrPool.VrfId),
Value: addrPool,
})
}
return derValues
}

// equalNamelessPool determine equality between 2 Nat44AddressPools ignoring Name field
func (d *NAT44AddressPoolDescriptor) equalNamelessPool(pool1, pool2 *nat.Nat44AddressPool) bool {
return pool1.VrfId == pool2.VrfId &&
Expand Down
81 changes: 75 additions & 6 deletions plugins/vpp/natplugin/descriptor/nat44_dnat.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@
package descriptor

import (
"bytes"
"net"
"strconv"

"github.com/golang/protobuf/proto"
"github.com/pkg/errors"
"go.ligato.io/cn-infra/v2/logging"

"strconv"

kvs "go.ligato.io/vpp-agent/v3/plugins/kvscheduler/api"
vpp_ifdescriptor "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/descriptor"
"go.ligato.io/vpp-agent/v3/plugins/vpp/natplugin/descriptor/adapter"
Expand All @@ -40,15 +41,28 @@ const (
untaggedDNAT = "UNTAGGED-DNAT"

// dependency labels
mappingInterfaceDep = "interface-exists"
mappingVrfDep = "vrf-table-exists"
mappingInterfaceDep = "interface-exists"
mappingVrfDep = "vrf-table-exists"
refTwiceNATPoolIPDep = "reference-to-twiceNATPoolIP"
)

// A list of non-retriable errors:
var (
// ErrDNAT44WithEmptyLabel is returned when NAT44 DNAT configuration is defined
// with empty label
ErrDNAT44WithEmptyLabel = errors.New("NAT44 DNAT configuration defined with empty label")

// ErrDNAT44TwiceNATPoolIPNeedsTwiceNAT is returned when NAT44 DNAT static configuration is defined
// with non-empty twiceNAT pool IP, but twiceNAT is not enabled for given static mapping.
ErrDNAT44TwiceNATPoolIPNeedsTwiceNAT = errors.New("NAT44 DNAT static mapping configuration with " +
"non-empty twiceNAT pool IP have to have also enabled twiceNAT (use enabled, not self-twiceNAT)")

// ErrDNAT44TwiceNATPoolIPIsNotSupportedForLBStMappings is returned when twiceNAT pool IP is used with
// loadbalanced version of static mapping. This combination is not supported by VPP.
ErrDNAT44TwiceNATPoolIPIsNotSupportedForLBStMappings = errors.New("NAT44 DNAT static mapping's " +
"twiceNAT pool IP feature is not supported(by VPP) when the loadbalanced version of static mapping " +
"is used. Use non-loadbalanced version of static mappings(<=>len(local IP)<=1) or don't use twiceNAT " +
"pool IP feature.")
)

// DNAT44Descriptor teaches KVScheduler how to configure Destination NAT44 in VPP.
Expand Down Expand Up @@ -106,6 +120,27 @@ func (d *DNAT44Descriptor) Validate(key string, dnat *nat.DNat44) error {
if dnat.Label == "" {
return kvs.NewInvalidValueError(ErrDNAT44WithEmptyLabel, "label")
}

// Static Mapping validation
for _, stMapping := range dnat.StMappings {
// Twice-NAT validation
if stMapping.TwiceNatPoolIp != "" {
if stMapping.TwiceNat != nat.DNat44_StaticMapping_ENABLED {
return kvs.NewInvalidValueError(ErrDNAT44TwiceNATPoolIPNeedsTwiceNAT,
"st_mappings.twice_nat_pool_ip")
}
if len(stMapping.LocalIps) > 1 {
kvs.NewInvalidValueError(ErrDNAT44TwiceNATPoolIPIsNotSupportedForLBStMappings,
"st_mappings.twice_nat_pool_ip")
}
if _, err := ParseIPv4(stMapping.TwiceNatPoolIp); err != nil {
return kvs.NewInvalidValueError(errors.Errorf("NAT44 DNAT static mapping configuration "+
"has unparsable non-empty twice-NAT pool IPv4 address %s: %v", stMapping.TwiceNatPoolIp, err),
"st_mappings.twice_nat_pool_ip")
}
}
}

return nil
}

Expand Down Expand Up @@ -170,6 +205,11 @@ func (d *DNAT44Descriptor) Update(key string, oldDNAT, newDNAT *nat.DNat44, oldM
func (d *DNAT44Descriptor) Retrieve(correlate []adapter.DNAT44KVWithMetadata) (
retrieved []adapter.DNAT44KVWithMetadata, err error,
) {
// TODO when added to dump then implement value retrieval for these new values
// vpp_nat.Nat44AddDelStaticMappingV2.MatchPool
// vpp_nat.Nat44AddDelStaticMappingV2.PoolIPAddress
// (=functionality modeled in NB proto model as DNat44.StaticMapping.twice_nat_pool_ip)

// collect DNATs which are expected to be empty
corrEmptyDNATs := make(map[string]*nat.DNat44)
for _, kv := range correlate {
Expand Down Expand Up @@ -253,6 +293,34 @@ func (d *DNAT44Descriptor) Dependencies(key string, dnat *nat.DNat44) (dependenc
})
}

// for every twiceNAT pool address reference add one dependency
for _, stMapping := range dnat.StMappings {
if stMapping.TwiceNat == nat.DNat44_StaticMapping_ENABLED && stMapping.TwiceNatPoolIp != "" {
dependencies = append(dependencies, kvs.Dependency{
Label: refTwiceNATPoolIPDep,
AnyOf: kvs.AnyOfDependency{
KeyPrefixes: []string{nat.TwiceNATDerivedKeyPrefix},
KeySelector: func(key string) bool {
firstIP, lastIP, _, isValid := nat.ParseDerivedTwiceNATAddressPoolKey(key)
if isValid {
if lastIP == "" { // single IP address pool
return equivalentTrimmedLowered(firstIP, stMapping.TwiceNatPoolIp)
}
// multiple IP addresses in address pool
fIP := net.ParseIP(firstIP)
lIP := net.ParseIP(lastIP)
tnpIP := net.ParseIP(stMapping.TwiceNatPoolIp)
if fIP != nil && lIP != nil && tnpIP != nil {
return bytes.Compare(fIP, tnpIP) <= 0 && bytes.Compare(tnpIP, lIP) <= 0
}
}
return false
},
},
})
}
}

return dependencies
}

Expand Down Expand Up @@ -323,7 +391,8 @@ func equivalentStaticMappings(stMapping1, stMapping2 *nat.DNat44_StaticMapping)
// attributes compared as usually
if stMapping1.Protocol != stMapping2.Protocol || stMapping1.ExternalPort != stMapping2.ExternalPort ||
stMapping1.ExternalIp != stMapping2.ExternalIp || stMapping1.ExternalInterface != stMapping2.ExternalInterface ||
stMapping1.TwiceNat != stMapping2.TwiceNat || stMapping1.SessionAffinity != stMapping1.SessionAffinity {
stMapping1.TwiceNat != stMapping2.TwiceNat || stMapping1.SessionAffinity != stMapping1.SessionAffinity ||
!equivalentIPv4(stMapping1.TwiceNatPoolIp, stMapping2.TwiceNatPoolIp) {
return false
}

Expand Down
5 changes: 5 additions & 0 deletions plugins/vpp/natplugin/vppcalls/vpp1904/nat_vppcalls.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,11 @@ func (h *NatVppHandler) handleNat44StaticMapping(mapping *nat.DNat44_StaticMappi
var ifIdx = NoInterface
var exIPAddr net.IP

if mapping.TwiceNatPoolIp != "" {
h.log.Debug("DNAT44 static mapping's twiceNAT pool IP feature " +
"is unsupported in this version of VPP (use 20.09 and newer)")
}

// check tag length limit
if err := checkTagLength(dnatLabel); err != nil {
return err
Expand Down
5 changes: 5 additions & 0 deletions plugins/vpp/natplugin/vppcalls/vpp1908/nat_vppcalls.go
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,11 @@ func (h *NatVppHandler) handleNat44StaticMapping(mapping *nat.DNat44_StaticMappi
var ifIdx = NoInterface
var exIPAddr natba.IP4Address

if mapping.TwiceNatPoolIp != "" {
h.log.Debug("DNAT44 static mapping's twiceNAT pool IP feature " +
"is unsupported in this version of VPP (use 20.09 and newer)")
}

// check tag length limit
if err := checkTagLength(dnatLabel); err != nil {
return err
Expand Down
5 changes: 5 additions & 0 deletions plugins/vpp/natplugin/vppcalls/vpp2001/nat_vppcalls.go
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,11 @@ func (h *NatVppHandler) handleNat44StaticMapping(mapping *nat.DNat44_StaticMappi
var ifIdx = NoInterface
var exIPAddr vpp_nat.IP4Address

if mapping.TwiceNatPoolIp != "" {
h.log.Debug("DNAT44 static mapping's twiceNAT pool IP feature " +
"is unsupported in this version of VPP (use 20.09 and newer)")
}

// check tag length limit
if err := checkTagLength(dnatLabel); err != nil {
return err
Expand Down
5 changes: 5 additions & 0 deletions plugins/vpp/natplugin/vppcalls/vpp2005/nat_vppcalls.go
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,11 @@ func (h *NatVppHandler) handleNat44StaticMapping(mapping *nat.DNat44_StaticMappi
var ifIdx = NoInterface
var exIPAddr vpp_nat.IP4Address

if mapping.TwiceNatPoolIp != "" {
h.log.Debug("DNAT44 static mapping's twiceNAT pool IP feature " +
"is unsupported in this version of VPP (use 20.09 and newer)")
}

// check tag length limit
if err := checkTagLength(dnatLabel); err != nil {
return err
Expand Down
14 changes: 12 additions & 2 deletions plugins/vpp/natplugin/vppcalls/vpp2009/nat_vppcalls.go
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ func (h *NatVppHandler) handleNat44StaticMapping(mapping *nat.DNat44_StaticMappi
addrOnly = true
}

req := &vpp_nat.Nat44AddDelStaticMapping{
req := &vpp_nat.Nat44AddDelStaticMappingV2{
Tag: dnatLabel,
LocalIPAddress: lcIPAddr,
ExternalIPAddress: exIPAddr,
Expand All @@ -291,7 +291,17 @@ func (h *NatVppHandler) handleNat44StaticMapping(mapping *nat.DNat44_StaticMappi
req.ExternalPort = uint16(mapping.ExternalPort)
}

reply := &vpp_nat.Nat44AddDelStaticMappingReply{}
// Applying(if needed) the override of IP address picking from twice-NAT address pool
if mapping.TwiceNatPoolIp != "" {
req.MatchPool = true
req.PoolIPAddress, err = ipTo4Address(mapping.TwiceNatPoolIp)
if err != nil {
return errors.Errorf("cannot configure static mapping for DNAT %s: unable to parse " +
"twice-NAT pool IP %s: %v", dnatLabel, mapping.TwiceNatPoolIp, err)
}
}

reply := &vpp_nat.Nat44AddDelStaticMappingV2Reply{}

if err := h.callsChannel.SendRequest(req).ReceiveReply(reply); err != nil {
return err
Expand Down
28 changes: 14 additions & 14 deletions plugins/vpp/natplugin/vppcalls/vpp2009/nat_vppcalls_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func TestSetNat44ForwardingError(t *testing.T) {
defer ctx.TeardownTestCtx()

// Incorrect reply object
ctx.MockVpp.MockReply(&vpp_nat.Nat44AddDelStaticMappingReply{})
ctx.MockVpp.MockReply(&vpp_nat.Nat44AddDelStaticMappingV2Reply{})
err := natHandler.SetNat44Forwarding(true)

Expect(err).Should(HaveOccurred())
Expand Down Expand Up @@ -231,7 +231,7 @@ func TestEnableNat44InterfaceOutputError(t *testing.T) {
swIfIndexes.Put("if1", &ifaceidx.IfaceMetadata{SwIfIndex: 2})

// Incorrect reply object
ctx.MockVpp.MockReply(&vpp_nat.Nat44AddDelStaticMappingReply{})
ctx.MockVpp.MockReply(&vpp_nat.Nat44AddDelStaticMappingV2Reply{})
err := natHandler.EnableNat44Interface("if1", false, true)

Expect(err).Should(HaveOccurred())
Expand Down Expand Up @@ -441,12 +441,12 @@ func TestAddNat44StaticMapping(t *testing.T) {
},
}

ctx.MockVpp.MockReply(&vpp_nat.Nat44AddDelStaticMappingReply{})
ctx.MockVpp.MockReply(&vpp_nat.Nat44AddDelStaticMappingV2Reply{})
err := natHandler.AddNat44StaticMapping(mapping, "DNAT 1")

Expect(err).ShouldNot(HaveOccurred())

msg, ok := ctx.MockChannel.Msg.(*vpp_nat.Nat44AddDelStaticMapping)
msg, ok := ctx.MockChannel.Msg.(*vpp_nat.Nat44AddDelStaticMappingV2)
Expect(ok).To(BeTrue())
Expect(msg.Tag).To(BeEquivalentTo("DNAT 1"))
Expect(msg.VrfID).To(BeEquivalentTo(1))
Expand Down Expand Up @@ -478,12 +478,12 @@ func TestAddNat44IdentityMappingWithInterface(t *testing.T) {
},
}

ctx.MockVpp.MockReply(&vpp_nat.Nat44AddDelStaticMappingReply{})
ctx.MockVpp.MockReply(&vpp_nat.Nat44AddDelStaticMappingV2Reply{})
err := natHandler.AddNat44StaticMapping(mapping, "DNAT 1")

Expect(err).ShouldNot(HaveOccurred())

msg, ok := ctx.MockChannel.Msg.(*vpp_nat.Nat44AddDelStaticMapping)
msg, ok := ctx.MockChannel.Msg.(*vpp_nat.Nat44AddDelStaticMappingV2)
Expect(ok).To(BeTrue())
Expect(msg.Tag).To(BeEquivalentTo("DNAT 1"))
Expect(msg.IsAdd).To(BeTrue())
Expand All @@ -507,7 +507,7 @@ func TestAddNat44StaticMappingRetval(t *testing.T) {
ctx, natHandler, _, _ := natTestSetup(t)
defer ctx.TeardownTestCtx()

ctx.MockVpp.MockReply(&vpp_nat.Nat44AddDelStaticMappingReply{
ctx.MockVpp.MockReply(&vpp_nat.Nat44AddDelStaticMappingV2Reply{
Retval: 1,
})
err := natHandler.AddNat44StaticMapping(&nat.DNat44_StaticMapping{}, "")
Expand Down Expand Up @@ -539,12 +539,12 @@ func TestDelNat44StaticMapping(t *testing.T) {
},
}

ctx.MockVpp.MockReply(&vpp_nat.Nat44AddDelStaticMappingReply{})
ctx.MockVpp.MockReply(&vpp_nat.Nat44AddDelStaticMappingV2Reply{})
err := natHandler.DelNat44StaticMapping(mapping, "DNAT 1")

Expect(err).ShouldNot(HaveOccurred())

msg, ok := ctx.MockChannel.Msg.(*vpp_nat.Nat44AddDelStaticMapping)
msg, ok := ctx.MockChannel.Msg.(*vpp_nat.Nat44AddDelStaticMappingV2)
Expect(ok).To(BeTrue())
Expect(msg.Tag).To(BeEquivalentTo("DNAT 1"))
Expect(msg.VrfID).To(BeEquivalentTo(1))
Expand Down Expand Up @@ -575,12 +575,12 @@ func TestDelNat44StaticMappingAddrOnly(t *testing.T) {
},
}

ctx.MockVpp.MockReply(&vpp_nat.Nat44AddDelStaticMappingReply{})
ctx.MockVpp.MockReply(&vpp_nat.Nat44AddDelStaticMappingV2Reply{})
err := natHandler.DelNat44StaticMapping(mapping, "DNAT 1")

Expect(err).ShouldNot(HaveOccurred())

msg, ok := ctx.MockChannel.Msg.(*vpp_nat.Nat44AddDelStaticMapping)
msg, ok := ctx.MockChannel.Msg.(*vpp_nat.Nat44AddDelStaticMappingV2)
Expect(ok).To(BeTrue())
Expect(msg.Tag).To(BeEquivalentTo("DNAT 1"))
Expect(msg.IsAdd).To(BeFalse())
Expand Down Expand Up @@ -777,7 +777,7 @@ func TestAddNat44IdentityMappingError(t *testing.T) {
defer ctx.TeardownTestCtx()

// Incorrect reply object
ctx.MockVpp.MockReply(&vpp_nat.Nat44AddDelStaticMappingReply{})
ctx.MockVpp.MockReply(&vpp_nat.Nat44AddDelStaticMappingV2Reply{})
err := natHandler.AddNat44IdentityMapping(&nat.DNat44_IdentityMapping{}, "")

Expect(err).Should(HaveOccurred())
Expand Down Expand Up @@ -862,11 +862,11 @@ func TestNat44MappingLongTag(t *testing.T) {
}

// 1. test
ctx.MockVpp.MockReply(&vpp_nat.Nat44AddDelStaticMappingReply{})
ctx.MockVpp.MockReply(&vpp_nat.Nat44AddDelStaticMappingV2Reply{})
ctx.MockVpp.MockReply(&vpp_nat.Nat44AddDelLbStaticMappingReply{})
ctx.MockVpp.MockReply(&vpp_nat.Nat44AddDelIdentityMappingReply{})
// 2. test
ctx.MockVpp.MockReply(&vpp_nat.Nat44AddDelStaticMappingReply{})
ctx.MockVpp.MockReply(&vpp_nat.Nat44AddDelStaticMappingV2Reply{})
ctx.MockVpp.MockReply(&vpp_nat.Nat44AddDelLbStaticMappingReply{})
ctx.MockVpp.MockReply(&vpp_nat.Nat44AddDelIdentityMappingReply{})

Expand Down
Loading