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 }