go.ligato.io/vpp-agent/v3@v3.5.0/plugins/vpp/natplugin/descriptor/nat44_dnat.go (about)

     1  // Copyright (c) 2018 Cisco and/or its affiliates.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at:
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package descriptor
    16  
    17  import (
    18  	"bytes"
    19  	"net"
    20  	"strconv"
    21  
    22  	"github.com/pkg/errors"
    23  	"go.ligato.io/cn-infra/v2/logging"
    24  	"google.golang.org/protobuf/proto"
    25  
    26  	kvs "go.ligato.io/vpp-agent/v3/plugins/kvscheduler/api"
    27  	vpp_ifdescriptor "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/descriptor"
    28  	"go.ligato.io/vpp-agent/v3/plugins/vpp/natplugin/descriptor/adapter"
    29  	"go.ligato.io/vpp-agent/v3/plugins/vpp/natplugin/vppcalls"
    30  	interfaces "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/interfaces"
    31  	l3 "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/l3"
    32  	nat "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/nat"
    33  )
    34  
    35  const (
    36  	// DNAT44DescriptorName is the name of the descriptor for VPP NAT44
    37  	// Destination-NAT configurations.
    38  	DNAT44DescriptorName = "vpp-nat44-dnat"
    39  
    40  	// untaggedDNAT is used as a label for DNAT grouping all untagged static
    41  	// and identity mappings.
    42  	untaggedDNAT = "UNTAGGED-DNAT"
    43  
    44  	// dependency labels
    45  	mappingInterfaceDep  = "interface-exists"
    46  	mappingVrfDep        = "vrf-table-exists"
    47  	mappingEpModeDep     = "nat44-is-in-endpoint-dependent-mode"
    48  	refTwiceNATPoolIPDep = "reference-to-twiceNATPoolIP"
    49  )
    50  
    51  // A list of non-retriable errors:
    52  var (
    53  	// ErrDNAT44WithEmptyLabel is returned when NAT44 DNAT configuration is defined
    54  	// with empty label
    55  	ErrDNAT44WithEmptyLabel = errors.New("NAT44 DNAT configuration defined with empty label")
    56  
    57  	// ErrDNAT44TwiceNATPoolIPNeedsTwiceNAT is returned when NAT44 DNAT static configuration is defined
    58  	// with non-empty twiceNAT pool IP, but twiceNAT is not enabled for given static mapping.
    59  	ErrDNAT44TwiceNATPoolIPNeedsTwiceNAT = errors.New("NAT44 DNAT static mapping configuration with " +
    60  		"non-empty twiceNAT pool IP have to have also enabled twiceNAT (use enabled, not self-twiceNAT)")
    61  
    62  	// ErrDNAT44TwiceNATPoolIPIsNotSupportedForLBStMappings is returned when twiceNAT pool IP is used with
    63  	// loadbalanced version of static mapping. This combination is not supported by VPP.
    64  	ErrDNAT44TwiceNATPoolIPIsNotSupportedForLBStMappings = errors.New("NAT44 DNAT static mapping's " +
    65  		"twiceNAT pool IP feature is not supported(by VPP) when the loadbalanced version of static mapping " +
    66  		"is used. Use non-loadbalanced version of static mappings(<=>len(local IP)<=1) or don't use twiceNAT " +
    67  		"pool IP feature.")
    68  )
    69  
    70  // DNAT44Descriptor teaches KVScheduler how to configure Destination NAT44 in VPP.
    71  type DNAT44Descriptor struct {
    72  	log        logging.Logger
    73  	natHandler vppcalls.NatVppAPI
    74  }
    75  
    76  // NewDNAT44Descriptor creates a new instance of the DNAT44 descriptor.
    77  func NewDNAT44Descriptor(natHandler vppcalls.NatVppAPI, log logging.PluginLogger) *kvs.KVDescriptor {
    78  	ctx := &DNAT44Descriptor{
    79  		natHandler: natHandler,
    80  		log:        log.NewLogger("nat44-dnat-descriptor"),
    81  	}
    82  
    83  	typedDescr := &adapter.DNAT44Descriptor{
    84  		Name:            DNAT44DescriptorName,
    85  		NBKeyPrefix:     nat.ModelDNat44.KeyPrefix(),
    86  		ValueTypeName:   nat.ModelDNat44.ProtoName(),
    87  		KeySelector:     nat.ModelDNat44.IsKeyValid,
    88  		KeyLabel:        nat.ModelDNat44.StripKeyPrefix,
    89  		ValueComparator: ctx.EquivalentDNAT44,
    90  		Validate:        ctx.Validate,
    91  		Create:          ctx.Create,
    92  		Delete:          ctx.Delete,
    93  		Update:          ctx.Update,
    94  		Retrieve:        ctx.Retrieve,
    95  		Dependencies:    ctx.Dependencies,
    96  		// retrieve interfaces and allocated IP addresses first
    97  		RetrieveDependencies: []string{vpp_ifdescriptor.InterfaceDescriptorName, vpp_ifdescriptor.DHCPDescriptorName},
    98  	}
    99  	return adapter.NewDNAT44Descriptor(typedDescr)
   100  }
   101  
   102  // EquivalentDNAT44 compares two instances of DNAT44 for equality.
   103  func (d *DNAT44Descriptor) EquivalentDNAT44(key string, oldDNAT, newDNAT *nat.DNat44) bool {
   104  	// compare identity mappings
   105  	obsoleteIDMappings, newIDMappings := diffIdentityMappings(oldDNAT.IdMappings, newDNAT.IdMappings)
   106  	if len(obsoleteIDMappings) != 0 || len(newIDMappings) != 0 {
   107  		return false
   108  	}
   109  
   110  	// compare static mappings
   111  	obsoleteStMappings, newStMappings := diffStaticMappings(oldDNAT.StMappings, newDNAT.StMappings)
   112  	return len(obsoleteStMappings) == 0 && len(newStMappings) == 0
   113  }
   114  
   115  // IsRetriableFailure returns <false> for errors related to invalid configuration.
   116  func (d *DNAT44Descriptor) IsRetriableFailure(err error) bool {
   117  	return err != ErrDNAT44WithEmptyLabel
   118  }
   119  
   120  // Validate validates VPP destination-NAT44 configuration.
   121  func (d *DNAT44Descriptor) Validate(key string, dnat *nat.DNat44) error {
   122  	if dnat.Label == "" {
   123  		return kvs.NewInvalidValueError(ErrDNAT44WithEmptyLabel, "label")
   124  	}
   125  
   126  	// Static Mapping validation
   127  	for _, stMapping := range dnat.StMappings {
   128  		// Twice-NAT validation
   129  		if stMapping.TwiceNatPoolIp != "" {
   130  			if stMapping.TwiceNat != nat.DNat44_StaticMapping_ENABLED {
   131  				return kvs.NewInvalidValueError(ErrDNAT44TwiceNATPoolIPNeedsTwiceNAT,
   132  					"st_mappings.twice_nat_pool_ip")
   133  			}
   134  			if len(stMapping.LocalIps) > 1 {
   135  				return kvs.NewInvalidValueError(ErrDNAT44TwiceNATPoolIPIsNotSupportedForLBStMappings,
   136  					"st_mappings.twice_nat_pool_ip")
   137  			}
   138  			if _, err := ParseIPv4(stMapping.TwiceNatPoolIp); err != nil {
   139  				return kvs.NewInvalidValueError(errors.Errorf("NAT44 DNAT static mapping configuration "+
   140  					"has unparsable non-empty twice-NAT pool IPv4 address %s: %v", stMapping.TwiceNatPoolIp, err),
   141  					"st_mappings.twice_nat_pool_ip")
   142  			}
   143  		}
   144  	}
   145  
   146  	return nil
   147  }
   148  
   149  // Create adds new destination-NAT44 configuration.
   150  func (d *DNAT44Descriptor) Create(key string, dnat *nat.DNat44) (metadata interface{}, err error) {
   151  	// Add = Modify from empty DNAT
   152  	return d.Update(key, &nat.DNat44{Label: dnat.Label}, dnat, nil)
   153  }
   154  
   155  // Delete removes existing destination-NAT44 configuration.
   156  func (d *DNAT44Descriptor) Delete(key string, dnat *nat.DNat44, metadata interface{}) error {
   157  	// Delete = Modify into empty DNAT
   158  	_, err := d.Update(key, dnat, &nat.DNat44{Label: dnat.Label}, metadata)
   159  	return err
   160  }
   161  
   162  // Update updates destination-NAT44 configuration.
   163  func (d *DNAT44Descriptor) Update(key string, oldDNAT, newDNAT *nat.DNat44, oldMetadata interface{}) (newMetadata interface{}, err error) {
   164  	obsoleteIDMappings, newIDMappings := diffIdentityMappings(oldDNAT.IdMappings, newDNAT.IdMappings)
   165  	obsoleteStMappings, newStMappings := diffStaticMappings(oldDNAT.StMappings, newDNAT.StMappings)
   166  
   167  	// remove obsolete identity mappings
   168  	for _, oldMapping := range obsoleteIDMappings {
   169  		if err = d.natHandler.DelNat44IdentityMapping(oldMapping, oldDNAT.Label); err != nil {
   170  			err = errors.Errorf("failed to remove identity mapping from DNAT %s: %v", oldDNAT.Label, err)
   171  			d.log.Error(err)
   172  			return nil, err
   173  		}
   174  	}
   175  
   176  	// remove obsolete static mappings
   177  	for _, oldMapping := range obsoleteStMappings {
   178  		if err = d.natHandler.DelNat44StaticMapping(oldMapping, oldDNAT.Label); err != nil {
   179  			err = errors.Errorf("failed to remove static mapping from DNAT %s: %v", oldDNAT.Label, err)
   180  			d.log.Error(err)
   181  			return nil, err
   182  		}
   183  	}
   184  
   185  	// add new identity mappings
   186  	for _, newMapping := range newIDMappings {
   187  		if err = d.natHandler.AddNat44IdentityMapping(newMapping, newDNAT.Label); err != nil {
   188  			err = errors.Errorf("failed to add identity mapping for DNAT %s: %v", newDNAT.Label, err)
   189  			d.log.Error(err)
   190  			return nil, err
   191  		}
   192  	}
   193  
   194  	// add new static mappings
   195  	for _, newMapping := range newStMappings {
   196  		if err = d.natHandler.AddNat44StaticMapping(newMapping, newDNAT.Label); err != nil {
   197  			err = errors.Errorf("failed to add static mapping for DNAT %s: %v", newDNAT.Label, err)
   198  			d.log.Error(err)
   199  			return nil, err
   200  		}
   201  	}
   202  
   203  	return nil, nil
   204  }
   205  
   206  // Retrieve returns the current NAT44 global configuration.
   207  func (d *DNAT44Descriptor) Retrieve(correlate []adapter.DNAT44KVWithMetadata) (
   208  	retrieved []adapter.DNAT44KVWithMetadata, err error,
   209  ) {
   210  	// TODO when added to dump then implement value retrieval for these new values
   211  	//  vpp_nat.Nat44AddDelStaticMappingV2.MatchPool
   212  	//  vpp_nat.Nat44AddDelStaticMappingV2.PoolIPAddress
   213  	//  (=functionality modeled in NB proto model as DNat44.StaticMapping.twice_nat_pool_ip)
   214  
   215  	// collect DNATs which are expected to be empty
   216  	corrEmptyDNATs := make(map[string]*nat.DNat44)
   217  	for _, kv := range correlate {
   218  		if len(kv.Value.IdMappings) == 0 && len(kv.Value.StMappings) == 0 {
   219  			corrEmptyDNATs[kv.Value.Label] = kv.Value
   220  		}
   221  	}
   222  
   223  	// dump (non-empty) DNATs
   224  	dnatDump, err := d.natHandler.DNat44Dump()
   225  	if err != nil {
   226  		d.log.Error(err)
   227  		return retrieved, err
   228  	}
   229  
   230  	// process DNAT dump
   231  	for _, dnat := range dnatDump {
   232  		if dnat.Label == "" {
   233  			// all untagged mappings are grouped under one DNAT with label <untaggedDNAT>
   234  			// - they will get removed by resync (not configured by agent, or tagging has failed)
   235  			dnat.Label = untaggedDNAT
   236  		}
   237  		// a DNAT mapping which is expected to be empty, but actually is not
   238  		delete(corrEmptyDNATs, dnat.Label)
   239  		retrieved = append(retrieved, adapter.DNAT44KVWithMetadata{
   240  			Key:    nat.DNAT44Key(dnat.Label),
   241  			Value:  dnat,
   242  			Origin: kvs.FromNB,
   243  		})
   244  	}
   245  
   246  	// add empty DNATs (nothing from them is dumped)
   247  	for dnatLabel, dnat := range corrEmptyDNATs {
   248  		retrieved = append(retrieved, adapter.DNAT44KVWithMetadata{
   249  			Key:    nat.DNAT44Key(dnatLabel),
   250  			Value:  dnat,
   251  			Origin: kvs.FromNB,
   252  		})
   253  	}
   254  
   255  	return retrieved, nil
   256  }
   257  
   258  // Dependencies lists endpoint-dependent mode, external interfaces and non-zero VRFs from mappings as dependencies.
   259  func (d *DNAT44Descriptor) Dependencies(key string, dnat *nat.DNat44) (dependencies []kvs.Dependency) {
   260  	// collect referenced external interfaces and VRFs
   261  	externalIfaces := make(map[string]struct{})
   262  	vrfs := make(map[uint32]struct{})
   263  	for _, mapping := range dnat.StMappings {
   264  		if mapping.ExternalInterface != "" {
   265  			externalIfaces[mapping.ExternalInterface] = struct{}{}
   266  		}
   267  		for _, localIP := range mapping.LocalIps {
   268  			vrfs[localIP.VrfId] = struct{}{}
   269  		}
   270  	}
   271  	for _, mapping := range dnat.IdMappings {
   272  		if mapping.Interface != "" {
   273  			externalIfaces[mapping.Interface] = struct{}{}
   274  		}
   275  		vrfs[mapping.VrfId] = struct{}{}
   276  	}
   277  
   278  	// for every external interface add one dependency
   279  	for externalIface := range externalIfaces {
   280  		dependencies = append(dependencies, kvs.Dependency{
   281  			Label: mappingInterfaceDep + "-" + externalIface,
   282  			Key:   interfaces.InterfaceKey(externalIface),
   283  		})
   284  	}
   285  	// for every non-zero VRF add one dependency
   286  	for vrf := range vrfs {
   287  		if vrf == 0 {
   288  			continue
   289  		}
   290  		dependencies = append(dependencies, kvs.Dependency{
   291  			Label: mappingVrfDep + "-" + strconv.Itoa(int(vrf)),
   292  			Key:   l3.VrfTableKey(vrf, l3.VrfTable_IPV4),
   293  		})
   294  	}
   295  
   296  	// for every twiceNAT pool address reference add one dependency
   297  	for _, stMapping := range dnat.StMappings {
   298  		if stMapping.TwiceNat == nat.DNat44_StaticMapping_ENABLED && stMapping.TwiceNatPoolIp != "" {
   299  			dependencies = append(dependencies, kvs.Dependency{
   300  				Label: refTwiceNATPoolIPDep,
   301  				AnyOf: kvs.AnyOfDependency{
   302  					KeyPrefixes: []string{nat.TwiceNATDerivedKeyPrefix},
   303  					KeySelector: func(key string) bool {
   304  						firstIP, lastIP, _, isValid := nat.ParseDerivedTwiceNATAddressPoolKey(key)
   305  						if isValid {
   306  							if lastIP == "" { // single IP address pool
   307  								return equivalentTrimmedLowered(firstIP, stMapping.TwiceNatPoolIp)
   308  							}
   309  							// multiple IP addresses in address pool
   310  							fIP := net.ParseIP(firstIP)
   311  							lIP := net.ParseIP(lastIP)
   312  							tnpIP := net.ParseIP(stMapping.TwiceNatPoolIp)
   313  							if fIP != nil && lIP != nil && tnpIP != nil {
   314  								return bytes.Compare(fIP, tnpIP) <= 0 && bytes.Compare(tnpIP, lIP) <= 0
   315  							}
   316  						}
   317  						return false
   318  					},
   319  				},
   320  			})
   321  		}
   322  	}
   323  	// D-NAT mapping require the NAT plugin to be in the endpoint dependent mode
   324  	if !d.natHandler.WithLegacyStartupConf() {
   325  		dependencies = append(dependencies, kvs.Dependency{
   326  			Label: mappingEpModeDep,
   327  			Key:   nat.Nat44EndpointDepKey,
   328  		})
   329  	}
   330  	return dependencies
   331  }
   332  
   333  // diffIdentityMappings compares two *sets* of identity mappings.
   334  func diffIdentityMappings(
   335  	oldIDMappings, newIDMappings []*nat.DNat44_IdentityMapping) (obsoleteMappings, newMappings []*nat.DNat44_IdentityMapping) {
   336  
   337  	for _, oldMapping := range oldIDMappings {
   338  		found := false
   339  		for _, newMapping := range newIDMappings {
   340  			if proto.Equal(oldMapping, newMapping) {
   341  				found = true
   342  				break
   343  			}
   344  		}
   345  		if !found {
   346  			obsoleteMappings = append(obsoleteMappings, oldMapping)
   347  		}
   348  	}
   349  	for _, newMapping := range newIDMappings {
   350  		found := false
   351  		for _, oldMapping := range oldIDMappings {
   352  			if proto.Equal(oldMapping, newMapping) {
   353  				found = true
   354  				break
   355  			}
   356  		}
   357  		if !found {
   358  			newMappings = append(newMappings, newMapping)
   359  		}
   360  	}
   361  	return obsoleteMappings, newMappings
   362  }
   363  
   364  // diffStaticMappings compares two *sets* of static mappings.
   365  func diffStaticMappings(
   366  	oldStMappings, newStMappings []*nat.DNat44_StaticMapping) (obsoleteMappings, newMappings []*nat.DNat44_StaticMapping) {
   367  
   368  	for _, oldMapping := range oldStMappings {
   369  		found := false
   370  		for _, newMapping := range newStMappings {
   371  			if equivalentStaticMappings(oldMapping, newMapping) {
   372  				found = true
   373  				break
   374  			}
   375  		}
   376  		if !found {
   377  			obsoleteMappings = append(obsoleteMappings, oldMapping)
   378  		}
   379  	}
   380  	for _, newMapping := range newStMappings {
   381  		found := false
   382  		for _, oldMapping := range oldStMappings {
   383  			if equivalentStaticMappings(oldMapping, newMapping) {
   384  				found = true
   385  				break
   386  			}
   387  		}
   388  		if !found {
   389  			newMappings = append(newMappings, newMapping)
   390  		}
   391  	}
   392  	return obsoleteMappings, newMappings
   393  }
   394  
   395  // equivalentStaticMappings compares two static mappings for equality.
   396  func equivalentStaticMappings(stMapping1, stMapping2 *nat.DNat44_StaticMapping) bool {
   397  	// attributes compared as usually
   398  	if stMapping1.Protocol != stMapping2.Protocol || stMapping1.ExternalPort != stMapping2.ExternalPort ||
   399  		stMapping1.ExternalIp != stMapping2.ExternalIp || stMapping1.ExternalInterface != stMapping2.ExternalInterface ||
   400  		stMapping1.TwiceNat != stMapping2.TwiceNat || stMapping1.SessionAffinity != stMapping2.SessionAffinity ||
   401  		!equivalentIPv4(stMapping1.TwiceNatPoolIp, stMapping2.TwiceNatPoolIp) {
   402  		return false
   403  	}
   404  
   405  	// compare locals ignoring their order
   406  	for _, localIP1 := range stMapping1.LocalIps {
   407  		found := false
   408  		for _, localIP2 := range stMapping2.LocalIps {
   409  			if proto.Equal(localIP1, localIP2) {
   410  				found = true
   411  				break
   412  			}
   413  		}
   414  		if !found {
   415  			return false
   416  		}
   417  	}
   418  	for _, localIP2 := range stMapping2.LocalIps {
   419  		found := false
   420  		for _, localIP1 := range stMapping1.LocalIps {
   421  			if proto.Equal(localIP1, localIP2) {
   422  				found = true
   423  				break
   424  			}
   425  		}
   426  		if !found {
   427  			return false
   428  		}
   429  	}
   430  
   431  	return true
   432  }