Skip to content

Commit

Permalink
feat: Added support for NAT44 static mapping twice-NAT pool IP addres…
Browse files Browse the repository at this point in the history
…s reference (#1728)
  • Loading branch information
fgschwan authored Sep 22, 2020
1 parent cd7cd02 commit 1290bd5
Show file tree
Hide file tree
Showing 13 changed files with 434 additions and 85 deletions.
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

0 comments on commit 1290bd5

Please sign in to comment.