Skip to content

Commit 8c98dee

Browse files
committed
Add utiliptables.NewDualStack
Basically all callers want dual-stack-if-possible, so simplify that. Also, tweak the startup-time checking in kubelet to treat "no iptables support" as interesting but not an error.
1 parent b031258 commit 8c98dee

File tree

6 files changed

+181
-45
lines changed

6 files changed

+181
-45
lines changed

cmd/kube-proxy/app/server_linux.go

Lines changed: 10 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ import (
5050
"k8s.io/kubernetes/pkg/proxy/nftables"
5151
proxyutil "k8s.io/kubernetes/pkg/proxy/util"
5252
utiliptables "k8s.io/kubernetes/pkg/util/iptables"
53-
"k8s.io/utils/exec"
5453
)
5554

5655
// timeoutForNodePodCIDR is the time to wait for allocators to assign a PodCIDR to the
@@ -105,35 +104,15 @@ func isIPTablesBased(mode proxyconfigapi.ProxyMode) bool {
105104
return mode == proxyconfigapi.ProxyModeIPTables || mode == proxyconfigapi.ProxyModeIPVS
106105
}
107106

108-
// getIPTables returns an array of [IPv4, IPv6] utiliptables.Interfaces. If primaryFamily
109-
// is not v1.IPFamilyUnknown then it will also separately return the interface for just
110-
// that family.
111-
func getIPTables(primaryFamily v1.IPFamily) ([2]utiliptables.Interface, utiliptables.Interface) {
112-
// Create iptables handlers for both families. Always ordered as IPv4, IPv6
113-
ipt := [2]utiliptables.Interface{
114-
utiliptables.New(utiliptables.ProtocolIPv4),
115-
utiliptables.New(utiliptables.ProtocolIPv6),
116-
}
117-
118-
var iptInterface utiliptables.Interface
119-
if primaryFamily == v1.IPv4Protocol {
120-
iptInterface = ipt[0]
121-
} else if primaryFamily == v1.IPv6Protocol {
122-
iptInterface = ipt[1]
123-
}
124-
125-
return ipt, iptInterface
126-
}
127-
128107
// platformCheckSupported is called immediately before creating the Proxier, to check
129108
// what IP families are supported (and whether the configuration is usable at all).
130109
func (s *ProxyServer) platformCheckSupported(ctx context.Context) (ipv4Supported, ipv6Supported, dualStackSupported bool, err error) {
131110
logger := klog.FromContext(ctx)
132111

133112
if isIPTablesBased(s.Config.Mode) {
134-
ipt, _ := getIPTables(v1.IPFamilyUnknown)
135-
ipv4Supported = ipt[0].Present()
136-
ipv6Supported = ipt[1].Present()
113+
ipts := utiliptables.NewDualStack()
114+
ipv4Supported = ipts[v1.IPv4Protocol] != nil
115+
ipv6Supported = ipts[v1.IPv6Protocol] != nil
137116

138117
if !ipv4Supported && !ipv6Supported {
139118
err = fmt.Errorf("iptables is not available on this host")
@@ -164,14 +143,13 @@ func (s *ProxyServer) createProxier(ctx context.Context, config *proxyconfigapi.
164143

165144
if config.Mode == proxyconfigapi.ProxyModeIPTables {
166145
logger.Info("Using iptables Proxier")
146+
ipts := utiliptables.NewDualStack()
167147

168148
if dualStack {
169-
ipt, _ := getIPTables(s.PrimaryIPFamily)
170-
171149
// TODO this has side effects that should only happen when Run() is invoked.
172150
proxier, err = iptables.NewDualStackProxier(
173151
ctx,
174-
ipt,
152+
ipts,
175153
utilsysctl.New(),
176154
config.SyncPeriod.Duration,
177155
config.MinSyncPeriod.Duration,
@@ -188,13 +166,12 @@ func (s *ProxyServer) createProxier(ctx context.Context, config *proxyconfigapi.
188166
)
189167
} else {
190168
// Create a single-stack proxier if and only if the node does not support dual-stack (i.e, no iptables support).
191-
_, iptInterface := getIPTables(s.PrimaryIPFamily)
192169

193170
// TODO this has side effects that should only happen when Run() is invoked.
194171
proxier, err = iptables.NewProxier(
195172
ctx,
196173
s.PrimaryIPFamily,
197-
iptInterface,
174+
ipts[s.PrimaryIPFamily],
198175
utilsysctl.New(),
199176
config.SyncPeriod.Duration,
200177
config.MinSyncPeriod.Duration,
@@ -220,13 +197,13 @@ func (s *ProxyServer) createProxier(ctx context.Context, config *proxyconfigapi.
220197
if err := ipvs.CanUseIPVSProxier(ctx, ipvsInterface, ipsetInterface, config.IPVS.Scheduler); err != nil {
221198
return nil, fmt.Errorf("can't use the IPVS proxier: %v", err)
222199
}
200+
ipts := utiliptables.NewDualStack()
223201

224202
logger.Info("Using ipvs Proxier")
225203
if dualStack {
226-
ipt, _ := getIPTables(s.PrimaryIPFamily)
227204
proxier, err = ipvs.NewDualStackProxier(
228205
ctx,
229-
ipt,
206+
ipts,
230207
ipvsInterface,
231208
ipsetInterface,
232209
utilsysctl.New(),
@@ -249,11 +226,10 @@ func (s *ProxyServer) createProxier(ctx context.Context, config *proxyconfigapi.
249226
initOnly,
250227
)
251228
} else {
252-
_, iptInterface := getIPTables(s.PrimaryIPFamily)
253229
proxier, err = ipvs.NewProxier(
254230
ctx,
255231
s.PrimaryIPFamily,
256-
iptInterface,
232+
ipts[s.PrimaryIPFamily],
257233
ipvsInterface,
258234
ipsetInterface,
259235
utilsysctl.New(),
@@ -507,7 +483,7 @@ func platformCleanup(ctx context.Context, mode proxyconfigapi.ProxyMode, cleanup
507483

508484
// Clean up iptables and ipvs rules if switching to nftables, or if cleanupAndExit
509485
if !isIPTablesBased(mode) || cleanupAndExit {
510-
ipts, _ := getIPTables(v1.IPFamilyUnknown)
486+
ipts := utiliptables.NewDualStack()
511487
ipsetInterface := utilipset.New()
512488
ipvsInterface := utilipvs.New()
513489

pkg/kubelet/kubelet_network_linux.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,14 @@ const (
3737
)
3838

3939
func (kl *Kubelet) initNetworkUtil() {
40-
iptClients := []utiliptables.Interface{
41-
utiliptables.New(utiliptables.ProtocolIPv4),
42-
utiliptables.New(utiliptables.ProtocolIPv6),
40+
iptClients := utiliptables.NewDualStack()
41+
if len(iptClients) == 0 {
42+
klog.InfoS("No iptables support on this system; not creating the KUBE-IPTABLES-HINT chain")
43+
return
4344
}
4445

45-
for i := range iptClients {
46-
iptClient := iptClients[i]
46+
for family := range iptClients {
47+
iptClient := iptClients[family]
4748
if kl.syncIPTablesRules(iptClient) {
4849
klog.InfoS("Initialized iptables rules.", "protocol", iptClient.Protocol())
4950
go iptClient.Monitor(

pkg/proxy/iptables/proxier.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ const sysctlNFConntrackTCPBeLiberal = "net/netfilter/nf_conntrack_tcp_be_liberal
9494
// NewDualStackProxier creates a MetaProxier instance, with IPv4 and IPv6 proxies.
9595
func NewDualStackProxier(
9696
ctx context.Context,
97-
ipt [2]utiliptables.Interface,
97+
ipts map[v1.IPFamily]utiliptables.Interface,
9898
sysctl utilsysctl.Interface,
9999
syncPeriod time.Duration,
100100
minSyncPeriod time.Duration,
@@ -110,15 +110,15 @@ func NewDualStackProxier(
110110
initOnly bool,
111111
) (proxy.Provider, error) {
112112
// Create an ipv4 instance of the single-stack proxier
113-
ipv4Proxier, err := NewProxier(ctx, v1.IPv4Protocol, ipt[0], sysctl,
113+
ipv4Proxier, err := NewProxier(ctx, v1.IPv4Protocol, ipts[v1.IPv4Protocol], sysctl,
114114
syncPeriod, minSyncPeriod, masqueradeAll, localhostNodePorts, masqueradeBit,
115115
localDetectors[v1.IPv4Protocol], hostname, nodeIPs[v1.IPv4Protocol],
116116
recorder, healthzServer, nodePortAddresses, initOnly)
117117
if err != nil {
118118
return nil, fmt.Errorf("unable to create ipv4 proxier: %v", err)
119119
}
120120

121-
ipv6Proxier, err := NewProxier(ctx, v1.IPv6Protocol, ipt[1], sysctl,
121+
ipv6Proxier, err := NewProxier(ctx, v1.IPv6Protocol, ipts[v1.IPv6Protocol], sysctl,
122122
syncPeriod, minSyncPeriod, masqueradeAll, false, masqueradeBit,
123123
localDetectors[v1.IPv6Protocol], hostname, nodeIPs[v1.IPv6Protocol],
124124
recorder, healthzServer, nodePortAddresses, initOnly)

pkg/proxy/ipvs/proxier.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ const (
110110
// NewDualStackProxier returns a new Proxier for dual-stack operation
111111
func NewDualStackProxier(
112112
ctx context.Context,
113-
ipt [2]utiliptables.Interface,
113+
ipts map[v1.IPFamily]utiliptables.Interface,
114114
ipvs utilipvs.Interface,
115115
ipset utilipset.Interface,
116116
sysctl utilsysctl.Interface,
@@ -133,7 +133,7 @@ func NewDualStackProxier(
133133
initOnly bool,
134134
) (proxy.Provider, error) {
135135
// Create an ipv4 instance of the single-stack proxier
136-
ipv4Proxier, err := NewProxier(ctx, v1.IPv4Protocol, ipt[0], ipvs, ipset, sysctl,
136+
ipv4Proxier, err := NewProxier(ctx, v1.IPv4Protocol, ipts[v1.IPv4Protocol], ipvs, ipset, sysctl,
137137
syncPeriod, minSyncPeriod, filterCIDRs(false, excludeCIDRs), strictARP,
138138
tcpTimeout, tcpFinTimeout, udpTimeout, masqueradeAll, masqueradeBit,
139139
localDetectors[v1.IPv4Protocol], hostname, nodeIPs[v1.IPv4Protocol], recorder,
@@ -142,7 +142,7 @@ func NewDualStackProxier(
142142
return nil, fmt.Errorf("unable to create ipv4 proxier: %v", err)
143143
}
144144

145-
ipv6Proxier, err := NewProxier(ctx, v1.IPv6Protocol, ipt[1], ipvs, ipset, sysctl,
145+
ipv6Proxier, err := NewProxier(ctx, v1.IPv6Protocol, ipts[v1.IPv6Protocol], ipvs, ipset, sysctl,
146146
syncPeriod, minSyncPeriod, filterCIDRs(true, excludeCIDRs), strictARP,
147147
tcpTimeout, tcpFinTimeout, udpTimeout, masqueradeAll, masqueradeBit,
148148
localDetectors[v1.IPv6Protocol], hostname, nodeIPs[v1.IPv6Protocol], recorder,

pkg/util/iptables/iptables.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030
"sync"
3131
"time"
3232

33+
"k8s.io/api/core/v1"
3334
"k8s.io/apimachinery/pkg/util/sets"
3435
utilversion "k8s.io/apimachinery/pkg/util/version"
3536
utilwait "k8s.io/apimachinery/pkg/util/wait"
@@ -253,6 +254,28 @@ func New(protocol Protocol) Interface {
253254
return newInternal(utilexec.New(), protocol, "", "")
254255
}
255256

257+
func newDualStackInternal(exec utilexec.Interface) map[v1.IPFamily]Interface {
258+
interfaces := map[v1.IPFamily]Interface{}
259+
260+
iptv4 := newInternal(exec, ProtocolIPv4, "", "")
261+
if iptv4.Present() {
262+
interfaces[v1.IPv4Protocol] = iptv4
263+
}
264+
iptv6 := newInternal(exec, ProtocolIPv6, "", "")
265+
if iptv6.Present() {
266+
interfaces[v1.IPv6Protocol] = iptv6
267+
}
268+
269+
return interfaces
270+
}
271+
272+
// NewDualStack returns a map containing an IPv4 Interface (if IPv4 iptables is supported)
273+
// and an IPv6 Interface (if IPv6 iptables is supported). If either family is not
274+
// supported, no Interface will be returned for that family.
275+
func NewDualStack() map[v1.IPFamily]Interface {
276+
return newDualStackInternal(utilexec.New())
277+
}
278+
256279
// EnsureChain is part of Interface.
257280
func (runner *runner) EnsureChain(table Table, chain Chain) (bool, error) {
258281
fullArgs := makeFullArgs(table, chain)

pkg/util/iptables/iptables_test.go

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
"testing"
3030
"time"
3131

32+
v1 "k8s.io/api/core/v1"
3233
"k8s.io/apimachinery/pkg/util/sets"
3334
utilversion "k8s.io/apimachinery/pkg/util/version"
3435
"k8s.io/apimachinery/pkg/util/wait"
@@ -208,6 +209,141 @@ func TestNew(t *testing.T) {
208209
}
209210
}
210211

212+
func TestNewDualStack(t *testing.T) {
213+
testCases := []struct {
214+
name string
215+
commands []testCommand
216+
ipv4 bool
217+
ipv6 bool
218+
}{
219+
{
220+
name: "both available",
221+
commands: []testCommand{
222+
{
223+
// ipv4 creation
224+
command: "iptables --version",
225+
action: func() ([]byte, []byte, error) { return []byte("iptables v1.8.0"), nil, nil },
226+
},
227+
{
228+
// ipv4 Present()
229+
command: "iptables -w 5 -W 100000 -S POSTROUTING -t nat",
230+
action: func() ([]byte, []byte, error) { return nil, nil, nil },
231+
},
232+
{
233+
// ipv6 creation
234+
command: "ip6tables --version",
235+
action: func() ([]byte, []byte, error) { return []byte("iptables v1.8.0"), nil, nil },
236+
},
237+
{
238+
// ipv6 Present()
239+
command: "ip6tables -w 5 -W 100000 -S POSTROUTING -t nat",
240+
action: func() ([]byte, []byte, error) { return nil, nil, nil },
241+
},
242+
},
243+
ipv4: true,
244+
ipv6: true,
245+
},
246+
{
247+
name: "ipv4 available, ipv6 not installed",
248+
commands: []testCommand{
249+
{
250+
// ipv4 creation
251+
command: "iptables --version",
252+
action: func() ([]byte, []byte, error) { return []byte("iptables v1.8.0"), nil, nil },
253+
},
254+
{
255+
// ipv4 Present()
256+
command: "iptables -w 5 -W 100000 -S POSTROUTING -t nat",
257+
action: func() ([]byte, []byte, error) { return nil, nil, nil },
258+
},
259+
{
260+
// ipv6 creation
261+
command: "ip6tables --version",
262+
action: func() ([]byte, []byte, error) { return nil, nil, fmt.Errorf("no such file or directory") },
263+
},
264+
{
265+
// ipv6 Present()
266+
command: "ip6tables -S POSTROUTING -t nat",
267+
action: func() ([]byte, []byte, error) { return nil, nil, fmt.Errorf("no such file or directory") },
268+
},
269+
},
270+
ipv4: true,
271+
ipv6: false,
272+
},
273+
{
274+
name: "ipv4 available, ipv6 disabled",
275+
commands: []testCommand{
276+
{
277+
// ipv4 creation
278+
command: "iptables --version",
279+
action: func() ([]byte, []byte, error) { return []byte("iptables v1.8.0"), nil, nil },
280+
},
281+
{
282+
// ipv4 Present()
283+
command: "iptables -w 5 -W 100000 -S POSTROUTING -t nat",
284+
action: func() ([]byte, []byte, error) { return nil, nil, nil },
285+
},
286+
{
287+
// ipv6 creation
288+
command: "ip6tables --version",
289+
action: func() ([]byte, []byte, error) { return []byte("iptables v1.8.0"), nil, nil },
290+
},
291+
{
292+
// ipv6 Present()
293+
command: "ip6tables -w 5 -W 100000 -S POSTROUTING -t nat",
294+
action: func() ([]byte, []byte, error) { return nil, nil, fmt.Errorf("ipv6 is broken") },
295+
},
296+
},
297+
ipv4: true,
298+
ipv6: false,
299+
},
300+
{
301+
name: "no iptables support",
302+
commands: []testCommand{
303+
{
304+
// ipv4 creation
305+
command: "iptables --version",
306+
action: func() ([]byte, []byte, error) { return nil, nil, fmt.Errorf("no such file or directory") },
307+
},
308+
{
309+
// ipv4 Present()
310+
command: "iptables -S POSTROUTING -t nat",
311+
action: func() ([]byte, []byte, error) { return nil, nil, fmt.Errorf("no such file or directory") },
312+
},
313+
{
314+
// ipv6 creation
315+
command: "ip6tables --version",
316+
action: func() ([]byte, []byte, error) { return nil, nil, fmt.Errorf("no such file or directory") },
317+
},
318+
{
319+
// ipv6 Present()
320+
command: "ip6tables -S POSTROUTING -t nat",
321+
action: func() ([]byte, []byte, error) { return nil, nil, fmt.Errorf("no such file or directory") },
322+
},
323+
},
324+
ipv4: false,
325+
ipv6: false,
326+
},
327+
}
328+
329+
for _, tc := range testCases {
330+
t.Run(tc.name, func(t *testing.T) {
331+
fexec := fakeExecForCommands(tc.commands)
332+
runners := newDualStackInternal(fexec)
333+
334+
if tc.ipv4 && runners[v1.IPv4Protocol] == nil {
335+
t.Errorf("Expected ipv4 runner, got nil")
336+
} else if !tc.ipv4 && runners[v1.IPv4Protocol] != nil {
337+
t.Errorf("Expected no ipv4 runner, got one")
338+
}
339+
if tc.ipv6 && runners[v1.IPv6Protocol] == nil {
340+
t.Errorf("Expected ipv6 runner, got nil")
341+
} else if !tc.ipv6 && runners[v1.IPv6Protocol] != nil {
342+
t.Errorf("Expected no ipv6 runner, got one")
343+
}
344+
})
345+
}
346+
}
211347
func testEnsureChain(t *testing.T, protocol Protocol) {
212348
fcmd := fakeexec.FakeCmd{
213349
CombinedOutputScript: []fakeexec.FakeAction{

0 commit comments

Comments
 (0)