Skip to content

Commit 09d142a

Browse files
authored
Merge pull request kubernetes#80854 from aojea/hostportv6
Add IPv6 support to kubenet hostport
2 parents d362b42 + cc7257b commit 09d142a

File tree

8 files changed

+531
-42
lines changed

8 files changed

+531
-42
lines changed

pkg/kubelet/dockershim/network/hostport/fake_iptables.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ type fakeTable struct {
4040
type fakeIPTables struct {
4141
tables map[string]*fakeTable
4242
builtinChains map[string]sets.String
43+
ipv6 bool
4344
}
4445

4546
func NewFakeIPTables() *fakeIPTables {
@@ -50,6 +51,7 @@ func NewFakeIPTables() *fakeIPTables {
5051
string(utiliptables.TableNAT): sets.NewString("PREROUTING", "INPUT", "OUTPUT", "POSTROUTING"),
5152
string(utiliptables.TableMangle): sets.NewString("PREROUTING", "INPUT", "FORWARD", "OUTPUT", "POSTROUTING"),
5253
},
54+
ipv6: false,
5355
}
5456
}
5557

@@ -222,7 +224,7 @@ func (f *fakeIPTables) DeleteRule(tableName utiliptables.Table, chainName utilip
222224
}
223225

224226
func (f *fakeIPTables) IsIpv6() bool {
225-
return false
227+
return f.ipv6
226228
}
227229

228230
func saveChain(chain *fakeChain, data *bytes.Buffer) {

pkg/kubelet/dockershim/network/hostport/hostport.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import (
2323

2424
"k8s.io/klog"
2525

26-
"k8s.io/api/core/v1"
26+
v1 "k8s.io/api/core/v1"
2727
utiliptables "k8s.io/kubernetes/pkg/util/iptables"
2828
)
2929

@@ -136,7 +136,11 @@ func ensureKubeHostportChains(iptables utiliptables.Interface, natInterfaceName
136136
}
137137
if natInterfaceName != "" && natInterfaceName != "lo" {
138138
// Need to SNAT traffic from localhost
139-
args = []string{"-m", "comment", "--comment", "SNAT for localhost access to hostports", "-o", natInterfaceName, "-s", "127.0.0.0/8", "-j", "MASQUERADE"}
139+
localhost := "127.0.0.0/8"
140+
if iptables.IsIpv6() {
141+
localhost = "::1/128"
142+
}
143+
args = []string{"-m", "comment", "--comment", "SNAT for localhost access to hostports", "-o", natInterfaceName, "-s", localhost, "-j", "MASQUERADE"}
140144
if _, err := iptables.EnsureRule(utiliptables.Append, utiliptables.TableNAT, utiliptables.ChainPostrouting, args...); err != nil {
141145
return fmt.Errorf("failed to ensure that %s chain %s jumps to MASQUERADE: %v", utiliptables.TableNAT, utiliptables.ChainPostrouting, err)
142146
}

pkg/kubelet/dockershim/network/hostport/hostport_manager.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,12 @@ import (
2121
"crypto/sha256"
2222
"encoding/base32"
2323
"fmt"
24+
"net"
2425
"strconv"
2526
"strings"
2627
"sync"
2728

28-
"k8s.io/api/core/v1"
29+
v1 "k8s.io/api/core/v1"
2930
utilerrors "k8s.io/apimachinery/pkg/util/errors"
3031
"k8s.io/klog"
3132
iptablesproxy "k8s.io/kubernetes/pkg/proxy/iptables"
@@ -82,10 +83,16 @@ func (hm *hostportManager) Add(id string, podPortMapping *PodPortMapping, natInt
8283
return nil
8384
}
8485

85-
if podPortMapping.IP.To4() == nil {
86+
// IP.To16() returns nil if IP is not a valid IPv4 or IPv6 address
87+
if podPortMapping.IP.To16() == nil {
8688
return fmt.Errorf("invalid or missing IP of pod %s", podFullName)
8789
}
8890
podIP := podPortMapping.IP.String()
91+
isIpv6 := utilnet.IsIPv6(podPortMapping.IP)
92+
93+
if isIpv6 != hm.iptables.IsIpv6() {
94+
return fmt.Errorf("HostPortManager IP family mismatch: %v, isIPv6 - %v", podIP, isIpv6)
95+
}
8996

9097
if err = ensureKubeHostportChains(hm.iptables, natInterfaceName); err != nil {
9198
return err
@@ -142,10 +149,11 @@ func (hm *hostportManager) Add(id string, podPortMapping *PodPortMapping, natInt
142149
"-j", string(iptablesproxy.KubeMarkMasqChain))
143150

144151
// DNAT to the podIP:containerPort
152+
hostPortBinding := net.JoinHostPort(podIP, strconv.Itoa(int(pm.ContainerPort)))
145153
writeLine(natRules, "-A", string(chain),
146154
"-m", "comment", "--comment", fmt.Sprintf(`"%s hostport %d"`, podFullName, pm.HostPort),
147155
"-m", protocol, "-p", protocol,
148-
"-j", "DNAT", fmt.Sprintf("--to-destination=%s:%d", podIP, pm.ContainerPort))
156+
"-j", "DNAT", fmt.Sprintf("--to-destination=%s", hostPortBinding))
149157
}
150158

151159
// getHostportChain should be able to provide unique hostport chain name using hash
@@ -166,7 +174,6 @@ func (hm *hostportManager) Add(id string, podPortMapping *PodPortMapping, natInt
166174
// clean up opened host port if encounter any error
167175
return utilerrors.NewAggregate([]error{err, hm.closeHostports(hostportMappings)})
168176
}
169-
isIpv6 := utilnet.IsIPv6(podPortMapping.IP)
170177

171178
// Remove conntrack entries just after adding the new iptables rules. If the conntrack entry is removed along with
172179
// the IP tables rule, it can be the case that the packets received by the node after iptables rule removal will

pkg/kubelet/dockershim/network/hostport/hostport_manager_test.go

Lines changed: 213 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import (
2323
"testing"
2424

2525
"github.com/stretchr/testify/assert"
26-
"k8s.io/api/core/v1"
26+
v1 "k8s.io/api/core/v1"
2727
utiliptables "k8s.io/kubernetes/pkg/util/iptables"
2828
"k8s.io/utils/exec"
2929
)
@@ -253,6 +253,22 @@ func TestHostportManager(t *testing.T) {
253253
},
254254
expectError: false,
255255
},
256+
{
257+
mapping: &PodPortMapping{
258+
Name: "pod3",
259+
Namespace: "ns1",
260+
IP: net.ParseIP("2001:beef::2"),
261+
HostNetwork: false,
262+
PortMappings: []*PortMapping{
263+
{
264+
HostPort: 8443,
265+
ContainerPort: 443,
266+
Protocol: v1.ProtocolTCP,
267+
},
268+
},
269+
},
270+
expectError: true,
271+
},
256272
}
257273

258274
// Add Hostports
@@ -362,3 +378,199 @@ func TestGetHostportChain(t *testing.T) {
362378
t.Fatal(m)
363379
}
364380
}
381+
382+
func TestHostportManagerIPv6(t *testing.T) {
383+
iptables := NewFakeIPTables()
384+
iptables.ipv6 = true
385+
portOpener := NewFakeSocketManager()
386+
manager := &hostportManager{
387+
hostPortMap: make(map[hostport]closeable),
388+
iptables: iptables,
389+
portOpener: portOpener.openFakeSocket,
390+
execer: exec.New(),
391+
}
392+
393+
testCases := []struct {
394+
mapping *PodPortMapping
395+
expectError bool
396+
}{
397+
{
398+
mapping: &PodPortMapping{
399+
Name: "pod1",
400+
Namespace: "ns1",
401+
IP: net.ParseIP("2001:beef::2"),
402+
HostNetwork: false,
403+
PortMappings: []*PortMapping{
404+
{
405+
HostPort: 8080,
406+
ContainerPort: 80,
407+
Protocol: v1.ProtocolTCP,
408+
},
409+
{
410+
HostPort: 8081,
411+
ContainerPort: 81,
412+
Protocol: v1.ProtocolUDP,
413+
},
414+
{
415+
HostPort: 8083,
416+
ContainerPort: 83,
417+
Protocol: v1.ProtocolSCTP,
418+
},
419+
},
420+
},
421+
expectError: false,
422+
},
423+
{
424+
mapping: &PodPortMapping{
425+
Name: "pod2",
426+
Namespace: "ns1",
427+
IP: net.ParseIP("2001:beef::3"),
428+
HostNetwork: false,
429+
PortMappings: []*PortMapping{
430+
{
431+
HostPort: 8082,
432+
ContainerPort: 80,
433+
Protocol: v1.ProtocolTCP,
434+
},
435+
{
436+
HostPort: 8081,
437+
ContainerPort: 81,
438+
Protocol: v1.ProtocolUDP,
439+
},
440+
{
441+
HostPort: 8083,
442+
ContainerPort: 83,
443+
Protocol: v1.ProtocolSCTP,
444+
},
445+
},
446+
},
447+
expectError: true,
448+
},
449+
{
450+
mapping: &PodPortMapping{
451+
Name: "pod3",
452+
Namespace: "ns1",
453+
IP: net.ParseIP("2001:beef::4"),
454+
HostNetwork: false,
455+
PortMappings: []*PortMapping{
456+
{
457+
HostPort: 8443,
458+
ContainerPort: 443,
459+
Protocol: v1.ProtocolTCP,
460+
},
461+
},
462+
},
463+
expectError: false,
464+
},
465+
{
466+
mapping: &PodPortMapping{
467+
Name: "pod4",
468+
Namespace: "ns2",
469+
IP: net.ParseIP("192.168.2.2"),
470+
HostNetwork: false,
471+
PortMappings: []*PortMapping{
472+
{
473+
HostPort: 8443,
474+
ContainerPort: 443,
475+
Protocol: v1.ProtocolTCP,
476+
},
477+
},
478+
},
479+
expectError: true,
480+
},
481+
}
482+
483+
// Add Hostports
484+
for _, tc := range testCases {
485+
err := manager.Add("id", tc.mapping, "cbr0")
486+
if tc.expectError {
487+
assert.Error(t, err)
488+
continue
489+
}
490+
assert.NoError(t, err)
491+
}
492+
493+
// Check port opened
494+
expectedPorts := []hostport{{8080, "tcp"}, {8081, "udp"}, {8443, "tcp"}}
495+
openedPorts := make(map[hostport]bool)
496+
for hp, port := range portOpener.mem {
497+
if !port.closed {
498+
openedPorts[hp] = true
499+
}
500+
}
501+
assert.EqualValues(t, len(openedPorts), len(expectedPorts))
502+
for _, hp := range expectedPorts {
503+
_, ok := openedPorts[hp]
504+
assert.EqualValues(t, true, ok)
505+
}
506+
507+
// Check Iptables-save result after adding hostports
508+
raw := bytes.NewBuffer(nil)
509+
err := iptables.SaveInto(utiliptables.TableNAT, raw)
510+
assert.NoError(t, err)
511+
512+
lines := strings.Split(string(raw.Bytes()), "\n")
513+
expectedLines := map[string]bool{
514+
`*nat`: true,
515+
`:KUBE-HOSTPORTS - [0:0]`: true,
516+
`:OUTPUT - [0:0]`: true,
517+
`:PREROUTING - [0:0]`: true,
518+
`:POSTROUTING - [0:0]`: true,
519+
`:KUBE-HP-IJHALPHTORMHHPPK - [0:0]`: true,
520+
`:KUBE-HP-63UPIDJXVRSZGSUZ - [0:0]`: true,
521+
`:KUBE-HP-WFBOALXEP42XEMJK - [0:0]`: true,
522+
`:KUBE-HP-XU6AWMMJYOZOFTFZ - [0:0]`: true,
523+
"-A KUBE-HOSTPORTS -m comment --comment \"pod3_ns1 hostport 8443\" -m tcp -p tcp --dport 8443 -j KUBE-HP-WFBOALXEP42XEMJK": true,
524+
"-A KUBE-HOSTPORTS -m comment --comment \"pod1_ns1 hostport 8081\" -m udp -p udp --dport 8081 -j KUBE-HP-63UPIDJXVRSZGSUZ": true,
525+
"-A KUBE-HOSTPORTS -m comment --comment \"pod1_ns1 hostport 8080\" -m tcp -p tcp --dport 8080 -j KUBE-HP-IJHALPHTORMHHPPK": true,
526+
"-A KUBE-HOSTPORTS -m comment --comment \"pod1_ns1 hostport 8083\" -m sctp -p sctp --dport 8083 -j KUBE-HP-XU6AWMMJYOZOFTFZ": true,
527+
"-A OUTPUT -m comment --comment \"kube hostport portals\" -m addrtype --dst-type LOCAL -j KUBE-HOSTPORTS": true,
528+
"-A PREROUTING -m comment --comment \"kube hostport portals\" -m addrtype --dst-type LOCAL -j KUBE-HOSTPORTS": true,
529+
"-A POSTROUTING -m comment --comment \"SNAT for localhost access to hostports\" -o cbr0 -s ::1/128 -j MASQUERADE": true,
530+
"-A KUBE-HP-IJHALPHTORMHHPPK -m comment --comment \"pod1_ns1 hostport 8080\" -s 2001:beef::2/32 -j KUBE-MARK-MASQ": true,
531+
"-A KUBE-HP-IJHALPHTORMHHPPK -m comment --comment \"pod1_ns1 hostport 8080\" -m tcp -p tcp -j DNAT --to-destination [2001:beef::2]:80": true,
532+
"-A KUBE-HP-63UPIDJXVRSZGSUZ -m comment --comment \"pod1_ns1 hostport 8081\" -s 2001:beef::2/32 -j KUBE-MARK-MASQ": true,
533+
"-A KUBE-HP-63UPIDJXVRSZGSUZ -m comment --comment \"pod1_ns1 hostport 8081\" -m udp -p udp -j DNAT --to-destination [2001:beef::2]:81": true,
534+
"-A KUBE-HP-XU6AWMMJYOZOFTFZ -m comment --comment \"pod1_ns1 hostport 8083\" -s 2001:beef::2/32 -j KUBE-MARK-MASQ": true,
535+
"-A KUBE-HP-XU6AWMMJYOZOFTFZ -m comment --comment \"pod1_ns1 hostport 8083\" -m sctp -p sctp -j DNAT --to-destination [2001:beef::2]:83": true,
536+
"-A KUBE-HP-WFBOALXEP42XEMJK -m comment --comment \"pod3_ns1 hostport 8443\" -s 2001:beef::4/32 -j KUBE-MARK-MASQ": true,
537+
"-A KUBE-HP-WFBOALXEP42XEMJK -m comment --comment \"pod3_ns1 hostport 8443\" -m tcp -p tcp -j DNAT --to-destination [2001:beef::4]:443": true,
538+
`COMMIT`: true,
539+
}
540+
for _, line := range lines {
541+
if len(strings.TrimSpace(line)) > 0 {
542+
_, ok := expectedLines[strings.TrimSpace(line)]
543+
assert.EqualValues(t, true, ok)
544+
}
545+
}
546+
547+
// Remove all added hostports
548+
for _, tc := range testCases {
549+
if !tc.expectError {
550+
err := manager.Remove("id", tc.mapping)
551+
assert.NoError(t, err)
552+
}
553+
}
554+
555+
// Check Iptables-save result after deleting hostports
556+
raw.Reset()
557+
err = iptables.SaveInto(utiliptables.TableNAT, raw)
558+
assert.NoError(t, err)
559+
lines = strings.Split(string(raw.Bytes()), "\n")
560+
remainingChains := make(map[string]bool)
561+
for _, line := range lines {
562+
if strings.HasPrefix(line, ":") {
563+
remainingChains[strings.TrimSpace(line)] = true
564+
}
565+
}
566+
expectDeletedChains := []string{"KUBE-HP-4YVONL46AKYWSKS3", "KUBE-HP-7THKRFSEH4GIIXK7", "KUBE-HP-5N7UH5JAXCVP5UJR"}
567+
for _, chain := range expectDeletedChains {
568+
_, ok := remainingChains[chain]
569+
assert.EqualValues(t, false, ok)
570+
}
571+
572+
// check if all ports are closed
573+
for _, port := range portOpener.mem {
574+
assert.EqualValues(t, true, port.closed)
575+
}
576+
}

0 commit comments

Comments
 (0)