Skip to content

Commit 61d0786

Browse files
danwinshipsqueed
authored andcommitted
Add nftables implementation of ipmasq
Signed-off-by: Dan Winship <[email protected]>
1 parent 729dd23 commit 61d0786

8 files changed

+777
-130
lines changed

Diff for: pkg/ip/ipmasq_iptables_linux.go

+161
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
// Copyright 2015 CNI authors
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 ip
16+
17+
import (
18+
"fmt"
19+
"net"
20+
21+
"github.com/coreos/go-iptables/iptables"
22+
23+
"github.com/containernetworking/cni/pkg/types"
24+
"github.com/containernetworking/plugins/pkg/utils"
25+
)
26+
27+
// setupIPMasqIPTables is the iptables-based implementation of SetupIPMasqForNetwork
28+
func setupIPMasqIPTables(ipn *net.IPNet, network, _, containerID string) error {
29+
// Note: for historical reasons, the iptables implementation ignores ifname.
30+
chain := utils.FormatChainName(network, containerID)
31+
comment := utils.FormatComment(network, containerID)
32+
return SetupIPMasq(ipn, chain, comment)
33+
}
34+
35+
// SetupIPMasq installs iptables rules to masquerade traffic
36+
// coming from ip of ipn and going outside of ipn.
37+
// Deprecated: This function only supports iptables. Use SetupIPMasqForNetwork, which
38+
// supports both iptables and nftables.
39+
func SetupIPMasq(ipn *net.IPNet, chain string, comment string) error {
40+
isV6 := ipn.IP.To4() == nil
41+
42+
var ipt *iptables.IPTables
43+
var err error
44+
var multicastNet string
45+
46+
if isV6 {
47+
ipt, err = iptables.NewWithProtocol(iptables.ProtocolIPv6)
48+
multicastNet = "ff00::/8"
49+
} else {
50+
ipt, err = iptables.NewWithProtocol(iptables.ProtocolIPv4)
51+
multicastNet = "224.0.0.0/4"
52+
}
53+
if err != nil {
54+
return fmt.Errorf("failed to locate iptables: %v", err)
55+
}
56+
57+
// Create chain if doesn't exist
58+
exists := false
59+
chains, err := ipt.ListChains("nat")
60+
if err != nil {
61+
return fmt.Errorf("failed to list chains: %v", err)
62+
}
63+
for _, ch := range chains {
64+
if ch == chain {
65+
exists = true
66+
break
67+
}
68+
}
69+
if !exists {
70+
if err = ipt.NewChain("nat", chain); err != nil {
71+
return err
72+
}
73+
}
74+
75+
// Packets to this network should not be touched
76+
if err := ipt.AppendUnique("nat", chain, "-d", ipn.String(), "-j", "ACCEPT", "-m", "comment", "--comment", comment); err != nil {
77+
return err
78+
}
79+
80+
// Don't masquerade multicast - pods should be able to talk to other pods
81+
// on the local network via multicast.
82+
if err := ipt.AppendUnique("nat", chain, "!", "-d", multicastNet, "-j", "MASQUERADE", "-m", "comment", "--comment", comment); err != nil {
83+
return err
84+
}
85+
86+
// Packets from the specific IP of this network will hit the chain
87+
return ipt.AppendUnique("nat", "POSTROUTING", "-s", ipn.IP.String(), "-j", chain, "-m", "comment", "--comment", comment)
88+
}
89+
90+
// teardownIPMasqIPTables is the iptables-based implementation of TeardownIPMasqForNetwork
91+
func teardownIPMasqIPTables(ipn *net.IPNet, network, _, containerID string) error {
92+
// Note: for historical reasons, the iptables implementation ignores ifname.
93+
chain := utils.FormatChainName(network, containerID)
94+
comment := utils.FormatComment(network, containerID)
95+
return TeardownIPMasq(ipn, chain, comment)
96+
}
97+
98+
// TeardownIPMasq undoes the effects of SetupIPMasq.
99+
// Deprecated: This function only supports iptables. Use TeardownIPMasqForNetwork, which
100+
// supports both iptables and nftables.
101+
func TeardownIPMasq(ipn *net.IPNet, chain string, comment string) error {
102+
isV6 := ipn.IP.To4() == nil
103+
104+
var ipt *iptables.IPTables
105+
var err error
106+
107+
if isV6 {
108+
ipt, err = iptables.NewWithProtocol(iptables.ProtocolIPv6)
109+
} else {
110+
ipt, err = iptables.NewWithProtocol(iptables.ProtocolIPv4)
111+
}
112+
if err != nil {
113+
return fmt.Errorf("failed to locate iptables: %v", err)
114+
}
115+
116+
err = ipt.Delete("nat", "POSTROUTING", "-s", ipn.IP.String(), "-j", chain, "-m", "comment", "--comment", comment)
117+
if err != nil && !isNotExist(err) {
118+
return err
119+
}
120+
121+
// for downward compatibility
122+
err = ipt.Delete("nat", "POSTROUTING", "-s", ipn.String(), "-j", chain, "-m", "comment", "--comment", comment)
123+
if err != nil && !isNotExist(err) {
124+
return err
125+
}
126+
127+
err = ipt.ClearChain("nat", chain)
128+
if err != nil && !isNotExist(err) {
129+
return err
130+
}
131+
132+
err = ipt.DeleteChain("nat", chain)
133+
if err != nil && !isNotExist(err) {
134+
return err
135+
}
136+
137+
return nil
138+
}
139+
140+
// gcIPMasqIPTables is the iptables-based implementation of GCIPMasqForNetwork
141+
func gcIPMasqIPTables(_ string, _ []types.GCAttachment) error {
142+
// FIXME: The iptables implementation does not support GC.
143+
//
144+
// (In theory, it _could_ backward-compatibly support it, by adding a no-op rule
145+
// with a comment indicating the network to each chain it creates, so that it
146+
// could later figure out which chains corresponded to which networks; older
147+
// implementations would ignore the extra rule but would still correctly delete
148+
// the chain on teardown (because they ClearChain() before doing DeleteChain()).
149+
150+
return nil
151+
}
152+
153+
// isNotExist returnst true if the error is from iptables indicating
154+
// that the target does not exist.
155+
func isNotExist(err error) bool {
156+
e, ok := err.(*iptables.Error)
157+
if !ok {
158+
return false
159+
}
160+
return e.IsNotExist()
161+
}

Diff for: pkg/ip/ipmasq_linux.go

+50-83
Original file line numberDiff line numberDiff line change
@@ -15,111 +15,78 @@
1515
package ip
1616

1717
import (
18+
"errors"
1819
"fmt"
1920
"net"
21+
"strings"
2022

21-
"github.com/coreos/go-iptables/iptables"
23+
"github.com/containernetworking/cni/pkg/types"
24+
"github.com/containernetworking/plugins/pkg/utils"
2225
)
2326

24-
// SetupIPMasq installs iptables rules to masquerade traffic
25-
// coming from ip of ipn and going outside of ipn
26-
func SetupIPMasq(ipn *net.IPNet, chain string, comment string) error {
27-
isV6 := ipn.IP.To4() == nil
28-
29-
var ipt *iptables.IPTables
30-
var err error
31-
var multicastNet string
32-
33-
if isV6 {
34-
ipt, err = iptables.NewWithProtocol(iptables.ProtocolIPv6)
35-
multicastNet = "ff00::/8"
36-
} else {
37-
ipt, err = iptables.NewWithProtocol(iptables.ProtocolIPv4)
38-
multicastNet = "224.0.0.0/4"
39-
}
40-
if err != nil {
41-
return fmt.Errorf("failed to locate iptables: %v", err)
42-
}
43-
44-
// Create chain if doesn't exist
45-
exists := false
46-
chains, err := ipt.ListChains("nat")
47-
if err != nil {
48-
return fmt.Errorf("failed to list chains: %v", err)
49-
}
50-
for _, ch := range chains {
51-
if ch == chain {
52-
exists = true
53-
break
54-
}
55-
}
56-
if !exists {
57-
if err = ipt.NewChain("nat", chain); err != nil {
58-
return err
27+
// SetupIPMasqForNetwork installs rules to masquerade traffic coming from ip of ipn and
28+
// going outside of ipn, using a chain name based on network, ifname, and containerID. The
29+
// backend can be either "iptables" or "nftables"; if it is nil, then a suitable default
30+
// implementation will be used.
31+
func SetupIPMasqForNetwork(backend *string, ipn *net.IPNet, network, ifname, containerID string) error {
32+
if backend == nil {
33+
// Prefer iptables, unless only nftables is available
34+
defaultBackend := "iptables"
35+
if !utils.SupportsIPTables() && utils.SupportsNFTables() {
36+
defaultBackend = "nftables"
5937
}
38+
backend = &defaultBackend
6039
}
6140

62-
// Packets to this network should not be touched
63-
if err := ipt.AppendUnique("nat", chain, "-d", ipn.String(), "-j", "ACCEPT", "-m", "comment", "--comment", comment); err != nil {
64-
return err
41+
switch *backend {
42+
case "iptables":
43+
return setupIPMasqIPTables(ipn, network, ifname, containerID)
44+
case "nftables":
45+
return setupIPMasqNFTables(ipn, network, ifname, containerID)
46+
default:
47+
return fmt.Errorf("unknown ipmasq backend %q", *backend)
6548
}
66-
67-
// Don't masquerade multicast - pods should be able to talk to other pods
68-
// on the local network via multicast.
69-
if err := ipt.AppendUnique("nat", chain, "!", "-d", multicastNet, "-j", "MASQUERADE", "-m", "comment", "--comment", comment); err != nil {
70-
return err
71-
}
72-
73-
// Packets from the specific IP of this network will hit the chain
74-
return ipt.AppendUnique("nat", "POSTROUTING", "-s", ipn.IP.String(), "-j", chain, "-m", "comment", "--comment", comment)
7549
}
7650

77-
// TeardownIPMasq undoes the effects of SetupIPMasq
78-
func TeardownIPMasq(ipn *net.IPNet, chain string, comment string) error {
79-
isV6 := ipn.IP.To4() == nil
51+
// TeardownIPMasqForNetwork undoes the effects of SetupIPMasqForNetwork
52+
func TeardownIPMasqForNetwork(ipn *net.IPNet, network, ifname, containerID string) error {
53+
var errs []string
8054

81-
var ipt *iptables.IPTables
82-
var err error
55+
// Do both the iptables and the nftables cleanup, since the pod may have been
56+
// created with a different version of this plugin or a different configuration.
8357

84-
if isV6 {
85-
ipt, err = iptables.NewWithProtocol(iptables.ProtocolIPv6)
86-
} else {
87-
ipt, err = iptables.NewWithProtocol(iptables.ProtocolIPv4)
88-
}
89-
if err != nil {
90-
return fmt.Errorf("failed to locate iptables: %v", err)
58+
err := teardownIPMasqIPTables(ipn, network, ifname, containerID)
59+
if err != nil && utils.SupportsIPTables() {
60+
errs = append(errs, err.Error())
9161
}
9262

93-
err = ipt.Delete("nat", "POSTROUTING", "-s", ipn.IP.String(), "-j", chain, "-m", "comment", "--comment", comment)
94-
if err != nil && !isNotExist(err) {
95-
return err
63+
err = teardownIPMasqNFTables(ipn, network, ifname, containerID)
64+
if err != nil && utils.SupportsNFTables() {
65+
errs = append(errs, err.Error())
9666
}
9767

98-
// for downward compatibility
99-
err = ipt.Delete("nat", "POSTROUTING", "-s", ipn.String(), "-j", chain, "-m", "comment", "--comment", comment)
100-
if err != nil && !isNotExist(err) {
101-
return err
68+
if errs == nil {
69+
return nil
10270
}
71+
return errors.New(strings.Join(errs, "\n"))
72+
}
10373

104-
err = ipt.ClearChain("nat", chain)
105-
if err != nil && !isNotExist(err) {
106-
return err
107-
}
74+
// GCIPMasqForNetwork garbage collects stale IPMasq entries for network
75+
func GCIPMasqForNetwork(network string, attachments []types.GCAttachment) error {
76+
var errs []string
10877

109-
err = ipt.DeleteChain("nat", chain)
110-
if err != nil && !isNotExist(err) {
111-
return err
78+
err := gcIPMasqIPTables(network, attachments)
79+
if err != nil && utils.SupportsIPTables() {
80+
errs = append(errs, err.Error())
11281
}
11382

114-
return nil
115-
}
83+
err = gcIPMasqNFTables(network, attachments)
84+
if err != nil && utils.SupportsNFTables() {
85+
errs = append(errs, err.Error())
86+
}
11687

117-
// isNotExist returnst true if the error is from iptables indicating
118-
// that the target does not exist.
119-
func isNotExist(err error) bool {
120-
e, ok := err.(*iptables.Error)
121-
if !ok {
122-
return false
88+
if errs == nil {
89+
return nil
12390
}
124-
return e.IsNotExist()
91+
return errors.New(strings.Join(errs, "\n"))
12592
}

0 commit comments

Comments
 (0)