Skip to content

Commit dc9e8e8

Browse files
committed
Enable twice NAT for external IPs in services
1 parent 3423afc commit dc9e8e8

File tree

6 files changed

+75
-68
lines changed

6 files changed

+75
-68
lines changed

mock/natplugin/natplugin_mock.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -255,8 +255,8 @@ func (mnt *MockNatPlugin) dnatToStaticMappings(dnat *nat.Nat44DNat_DNatConfig) (
255255
sm := &StaticMapping{}
256256

257257
// fields set to a constant value
258-
if staticMapping.ExternalPort != 0 && staticMapping.TwiceNat != nat.TwiceNatMode_SELF {
259-
return nil, errors.New("self-twice-NAT not enabled for static mapping")
258+
if staticMapping.ExternalPort != 0 && staticMapping.TwiceNat == nat.TwiceNatMode_DISABLED {
259+
return nil, errors.New("self-twice-NAT/twice-NAT not enabled for static mapping")
260260
}
261261
if staticMapping.ExternalInterface != "" {
262262
return nil, errors.New("static mapping with external interface is not expected")
@@ -285,6 +285,11 @@ func (mnt *MockNatPlugin) dnatToStaticMappings(dnat *nat.Nat44DNat_DNatConfig) (
285285
}
286286
sm.ExternalPort = uint16(staticMapping.ExternalPort)
287287

288+
// twice NAT
289+
if staticMapping.TwiceNat == nat.TwiceNatMode_ENABLED {
290+
sm.TwiceNAT = true
291+
}
292+
288293
// locals
289294
for _, local := range staticMapping.LocalIps {
290295
localIP := net.ParseIP(local.LocalIp)

mock/natplugin/static_mappings.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ type StaticMapping struct {
3434
ExternalPort uint16
3535
Protocol renderer.ProtocolType
3636
Locals []*Local
37+
TwiceNAT bool
3738
}
3839

3940
// Local represents a single backend for VPP NAT mapping.
@@ -125,8 +126,8 @@ func (sm *StaticMapping) String() string {
125126
locals += ", "
126127
}
127128
}
128-
return fmt.Sprintf("StaticMapping <ExternalIP:%s ExternalPort:%d Protocol:%s, Locals:[%s]>",
129-
sm.ExternalIP.String(), sm.ExternalPort, sm.Protocol.String(), locals)
129+
return fmt.Sprintf("StaticMapping <ExternalIP:%s ExternalPort:%d Protocol:%s, Locals:[%s] TwiceNAT:[%t]>",
130+
sm.ExternalIP.String(), sm.ExternalPort, sm.Protocol.String(), locals, sm.TwiceNAT)
130131
}
131132

132133
// String converts local into a human-readable string.
@@ -141,6 +142,7 @@ func (sm *StaticMapping) Copy() *StaticMapping {
141142
ExternalIP: dupIP(sm.ExternalIP),
142143
ExternalPort: sm.ExternalPort,
143144
Protocol: sm.Protocol,
145+
TwiceNAT: sm.TwiceNAT,
144146
}
145147
for _, local := range sm.Locals {
146148
smCopy.Locals = append(smCopy.Locals, &Local{
@@ -157,7 +159,8 @@ func (sm *StaticMapping) Copy() *StaticMapping {
157159
func (sm *StaticMapping) Equals(sm2 *StaticMapping) bool {
158160
if !sm.ExternalIP.Equal(sm2.ExternalIP) ||
159161
sm.ExternalPort != sm2.ExternalPort ||
160-
sm.Protocol != sm2.Protocol {
162+
sm.Protocol != sm2.Protocol ||
163+
sm.TwiceNAT != sm2.TwiceNAT {
161164
return false
162165
}
163166
if len(sm.Locals) != len(sm2.Locals) {

plugins/service/nat44_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,7 @@ func TestResyncAndSingleService(t *testing.T) {
363363
Expect(natPlugin.HasStaticMapping(staticMapping1)).To(BeTrue())
364364
staticMapping2 := staticMapping1.Copy()
365365
staticMapping2.ExternalIP = net.ParseIP("20.20.20.20")
366+
staticMapping2.TwiceNAT = true
366367
Expect(natPlugin.HasStaticMapping(staticMapping2)).To(BeTrue())
367368

368369
// Change port number for pod2.
@@ -799,8 +800,10 @@ func TestMultipleServicesWithMultiplePortsAndResync(t *testing.T) {
799800
Expect(natPlugin.HasStaticMapping(staticMappingHTTPS)).To(BeTrue())
800801
staticMappingHTTP2 := staticMappingHTTP.Copy()
801802
staticMappingHTTP2.ExternalIP = net.ParseIP("20.20.20.20")
803+
staticMappingHTTP2.TwiceNAT = true
802804
staticMappingHTTPS2 := staticMappingHTTPS.Copy()
803805
staticMappingHTTPS2.ExternalIP = net.ParseIP("20.20.20.20")
806+
staticMappingHTTPS2.TwiceNAT = true
804807
Expect(natPlugin.HasStaticMapping(staticMappingHTTP2)).To(BeTrue())
805808
Expect(natPlugin.HasStaticMapping(staticMappingHTTPS2)).To(BeTrue())
806809

@@ -1592,6 +1595,7 @@ func TestServiceUpdates(t *testing.T) {
15921595
Expect(natPlugin.HasStaticMapping(staticMappingHTTP)).To(BeTrue())
15931596
staticMappingHTTP2 := staticMappingHTTP.Copy()
15941597
staticMappingHTTP2.ExternalIP = net.ParseIP("20.20.20.20")
1598+
staticMappingHTTP2.TwiceNAT = true
15951599
Expect(natPlugin.HasStaticMapping(staticMappingHTTP2)).To(BeTrue())
15961600
Expect(natPlugin.NumOfStaticMappings()).To(Equal(2))
15971601

@@ -1718,6 +1722,7 @@ func TestServiceUpdates(t *testing.T) {
17181722
Expect(natPlugin.HasStaticMapping(staticMappingHTTP)).To(BeTrue())
17191723
staticMappingHTTP2 = staticMappingHTTP.Copy()
17201724
staticMappingHTTP2.ExternalIP = net.ParseIP("20.20.20.20")
1725+
staticMappingHTTP2.TwiceNAT = true
17211726
Expect(natPlugin.HasStaticMapping(staticMappingHTTP2)).To(BeTrue())
17221727
Expect(natPlugin.NumOfStaticMappings()).To(Equal(2))
17231728
Expect(natPlugin.NumOfIdentityMappings()).To(Equal(3))
@@ -1789,6 +1794,7 @@ func TestServiceUpdates(t *testing.T) {
17891794
Expect(natPlugin.HasStaticMapping(staticMappingHTTPS)).To(BeTrue())
17901795
staticMappingHTTPS2 := staticMappingHTTPS.Copy()
17911796
staticMappingHTTPS2.ExternalIP = net.ParseIP("20.20.20.20")
1797+
staticMappingHTTPS2.TwiceNAT = true
17921798
Expect(natPlugin.HasStaticMapping(staticMappingHTTP2)).To(BeTrue())
17931799
Expect(natPlugin.HasStaticMapping(staticMappingHTTPS2)).To(BeTrue())
17941800
Expect(natPlugin.NumOfStaticMappings()).To(Equal(4))

plugins/service/processor/service.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ func (s *Service) Refresh() {
9999
if s.meta.ClusterIp != "" && s.meta.ClusterIp != "None" {
100100
clusterIP := net.ParseIP(s.meta.ClusterIp)
101101
if clusterIP != nil {
102-
s.contivSvc.ExternalIPs.Add(clusterIP)
102+
s.contivSvc.ClusterIPs.Add(clusterIP)
103103
} else {
104104
s.sp.Log.WithFields(logging.Fields{
105105
"service": s.contivSvc.ID,

plugins/service/renderer/api.go

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -117,10 +117,14 @@ type ContivService struct {
117117
// TrafficPolicy decides if traffic is routed cluster-wide or node-local only.
118118
TrafficPolicy TrafficPolicyType
119119

120-
// ExternalIPs is a set of all IP addresses on which the service
121-
// should be exposed on this node (aside from node IPs for NodePorts, which
120+
// ClusterIPs is a set of all IP addresses on which the service
121+
// should be exposed within the cluster (aside from node IPs for NodePorts, which
122122
// are provided separately via the ServiceRendererAPI.UpdateNodePortServices()
123123
// method).
124+
ClusterIPs *IPAddresses
125+
126+
// ExternalIPs is a set of all IP addresses on which the service
127+
// should be exposed outside of the cluster (e.g. external load-balancer IPs).
124128
ExternalIPs *IPAddresses
125129

126130
// Ports is a map of all ports exposed for this service.
@@ -144,6 +148,7 @@ const (
144148
// NewContivService is a constructor for ContivService.
145149
func NewContivService() *ContivService {
146150
return &ContivService{
151+
ClusterIPs: NewIPAddresses(),
147152
ExternalIPs: NewIPAddresses(),
148153
Ports: make(map[string]*ServicePort),
149154
Backends: make(map[string][]*ServiceBackend),
@@ -152,6 +157,13 @@ func NewContivService() *ContivService {
152157

153158
// String converts ContivService into a human-readable string.
154159
func (cs ContivService) String() string {
160+
clusterIPs := ""
161+
for idx, ip := range cs.ClusterIPs.list {
162+
clusterIPs += ip.String()
163+
if idx < len(cs.ClusterIPs.list)-1 {
164+
clusterIPs += ", "
165+
}
166+
}
155167
externalIPs := ""
156168
for idx, ip := range cs.ExternalIPs.list {
157169
externalIPs += ip.String()
@@ -175,8 +187,8 @@ func (cs ContivService) String() string {
175187
}
176188
idx++
177189
}
178-
return fmt.Sprintf("ContivService %s <Traffic-Policy:%s ExternalIPs:[%s] Backends:{%s}>",
179-
cs.ID.String(), cs.TrafficPolicy.String(), externalIPs, allBackends)
190+
return fmt.Sprintf("ContivService %s <Traffic-Policy:%s ClusterIPs:[%s] ExternalIPs:[%s] Backends:{%s}>",
191+
cs.ID.String(), cs.TrafficPolicy.String(), clusterIPs, externalIPs, allBackends)
180192
}
181193

182194
// String converts TrafficPolicyType into a human-readable string.

plugins/service/renderer/nat44/nat44_renderer.go

Lines changed: 39 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,15 @@ const (
5353
defaultIdleOtherTimeout = 5 * time.Minute // inactive timeout for other NAT sessions
5454
)
5555

56+
// serviceIPType represents type of IP address where the service is accessible: node / cluster / external
57+
type serviceIPType int
58+
59+
const (
60+
nodeIP serviceIPType = iota
61+
clusterIP
62+
externalIP
63+
)
64+
5665
var (
5766
tcpNatSessionCount uint64
5867
otherNatSessionCount uint64
@@ -490,70 +499,43 @@ func (rndr *Renderer) exportDNATMappings(service *renderer.ContivService) []*nat
490499

491500
// Export NAT mappings for NodePort services.
492501
if service.HasNodePort() {
493-
for _, nodeIP := range rndr.nodeIPs.List() {
494-
if nodeIP.To4() != nil {
495-
nodeIP = nodeIP.To4()
496-
}
497-
// Add one mapping for each port.
498-
for portName, port := range service.Ports {
499-
if port.NodePort == 0 {
500-
continue
501-
}
502-
mapping := &nat.Nat44DNat_DNatConfig_StaticMapping{}
503-
mapping.TwiceNat = nat.TwiceNatMode_SELF
504-
mapping.ExternalIp = nodeIP.String()
505-
mapping.ExternalPort = uint32(port.NodePort)
506-
switch port.Protocol {
507-
case renderer.TCP:
508-
mapping.Protocol = nat.Protocol_TCP
509-
case renderer.UDP:
510-
mapping.Protocol = nat.Protocol_UDP
511-
}
512-
for _, backend := range service.Backends[portName] {
513-
if service.TrafficPolicy != renderer.ClusterWide && !backend.Local {
514-
// Do not NAT+LB remote backends.
515-
continue
516-
}
517-
local := &nat.Nat44DNat_DNatConfig_StaticMapping_LocalIP{
518-
LocalIp: backend.IP.String(),
519-
LocalPort: uint32(backend.Port),
520-
}
521-
if backend.Local {
522-
local.Probability = uint32(rndr.Contiv.GetServiceLocalEndpointWeight())
523-
} else {
524-
local.Probability = 1
525-
}
526-
if rndr.isNodeLocalIP(backend.IP) {
527-
local.VrfId = rndr.Contiv.GetMainVrfID()
528-
} else {
529-
local.VrfId = rndr.Contiv.GetPodVrfID()
530-
}
531-
mapping.LocalIps = append(mapping.LocalIps, local)
532-
}
533-
if len(mapping.LocalIps) == 0 {
534-
continue
535-
}
536-
if len(mapping.LocalIps) == 1 {
537-
// For single backend we use "0" to represent the probability
538-
// (not really configured).
539-
mapping.LocalIps[0].Probability = 0
540-
}
541-
mappings = append(mappings, mapping)
542-
}
543-
}
502+
mappings = rndr.exportServiceIPMappings(service, mappings, rndr.nodeIPs, nodeIP)
544503
}
545504

546-
// Export NAT mappings for external IPs.
547-
for _, externalIP := range service.ExternalIPs.List() {
505+
// Export NAT mappings for cluster & external IPs.
506+
mappings = rndr.exportServiceIPMappings(service, mappings, service.ClusterIPs, clusterIP)
507+
mappings = rndr.exportServiceIPMappings(service, mappings, service.ExternalIPs, externalIP)
508+
509+
return mappings
510+
}
511+
512+
// exportServiceIPMappings exports the corresponding list of D-NAT mappings from a list of service IPs of the given service.
513+
func (rndr *Renderer) exportServiceIPMappings(service *renderer.ContivService, mappings []*nat.Nat44DNat_DNatConfig_StaticMapping,
514+
serviceIPs *renderer.IPAddresses, ipType serviceIPType) []*nat.Nat44DNat_DNatConfig_StaticMapping {
515+
516+
for _, ip := range serviceIPs.List() {
517+
if ip.To4() != nil {
518+
ip = ip.To4()
519+
}
548520
// Add one mapping for each port.
549521
for portName, port := range service.Ports {
550-
if port.Port == 0 {
522+
if ipType == nodeIP && port.NodePort == 0 {
523+
continue
524+
} else if ipType != nodeIP && port.Port == 0 {
551525
continue
552526
}
553527
mapping := &nat.Nat44DNat_DNatConfig_StaticMapping{}
554-
mapping.TwiceNat = nat.TwiceNatMode_ENABLED
555-
mapping.ExternalIp = externalIP.String()
556-
mapping.ExternalPort = uint32(port.Port)
528+
if ipType == externalIP {
529+
mapping.TwiceNat = nat.TwiceNatMode_ENABLED
530+
} else {
531+
mapping.TwiceNat = nat.TwiceNatMode_SELF
532+
}
533+
mapping.ExternalIp = ip.String()
534+
if ipType == nodeIP {
535+
mapping.ExternalPort = uint32(port.NodePort)
536+
} else {
537+
mapping.ExternalPort = uint32(port.Port)
538+
}
557539
switch port.Protocol {
558540
case renderer.TCP:
559541
mapping.Protocol = nat.Protocol_TCP
@@ -592,7 +574,6 @@ func (rndr *Renderer) exportDNATMappings(service *renderer.ContivService) []*nat
592574
mappings = append(mappings, mapping)
593575
}
594576
}
595-
596577
return mappings
597578
}
598579

0 commit comments

Comments
 (0)