/
iptables.go
333 lines (306 loc) · 10.1 KB
/
iptables.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
//go:build !windows
// +build !windows
// Copyright 2020 Antrea Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package iptables
import (
"bytes"
"fmt"
"os/exec"
"strconv"
"time"
"github.com/blang/semver"
"github.com/coreos/go-iptables/iptables"
"k8s.io/klog/v2"
)
const (
NATTable = "nat"
FilterTable = "filter"
MangleTable = "mangle"
RawTable = "raw"
AcceptTarget = "ACCEPT"
MasqueradeTarget = "MASQUERADE"
MarkTarget = "MARK"
ReturnTarget = "RETURN"
ConnTrackTarget = "CT"
NoTrackTarget = "NOTRACK"
SNATTarget = "SNAT"
DNATTarget = "DNAT"
PreRoutingChain = "PREROUTING"
ForwardChain = "FORWARD"
PostRoutingChain = "POSTROUTING"
OutputChain = "OUTPUT"
waitSeconds = 10
waitIntervalMicroSeconds = 200000
)
type Protocol byte
var protocolStrMap = map[Protocol]string{
ProtocolIPv4: "IPv4",
ProtocolIPv6: "IPv6",
}
func (p Protocol) String() string {
return protocolStrMap[p]
}
const (
ProtocolDual Protocol = iota
ProtocolIPv4
ProtocolIPv6
)
// https://netfilter.org/projects/iptables/files/changes-iptables-1.6.2.txt:
// iptables-restore: support acquiring the lock.
var restoreWaitSupportedMinVersion = semver.Version{Major: 1, Minor: 6, Patch: 2}
type Client struct {
ipts map[Protocol]*iptables.IPTables
// restoreWaitSupported indicates whether iptables-restore (or ip6tables-restore) supports --wait flag.
restoreWaitSupported bool
}
func New(enableIPV4, enableIPV6 bool) (*Client, error) {
ipts := make(map[Protocol]*iptables.IPTables)
var restoreWaitSupported bool
if enableIPV4 {
ipt, err := iptables.New()
if err != nil {
return nil, fmt.Errorf("error creating IPTables instance: %v", err)
}
ipts[ProtocolIPv4] = ipt
restoreWaitSupported = isRestoreWaitSupported(ipt)
}
if enableIPV6 {
ip6t, err := iptables.NewWithProtocol(iptables.ProtocolIPv6)
if err != nil {
return nil, fmt.Errorf("error creating IPTables instance for IPv6: %v", err)
}
ipts[ProtocolIPv6] = ip6t
if !restoreWaitSupported {
restoreWaitSupported = isRestoreWaitSupported(ip6t)
}
}
return &Client{ipts: ipts, restoreWaitSupported: restoreWaitSupported}, nil
}
func isRestoreWaitSupported(ipt *iptables.IPTables) bool {
major, minor, patch := ipt.GetIptablesVersion()
version := semver.Version{Major: uint64(major), Minor: uint64(minor), Patch: uint64(patch)}
return version.GE(restoreWaitSupportedMinVersion)
}
// EnsureChain checks if target chain already exists, creates it if not.
func (c *Client) EnsureChain(protocol Protocol, table string, chain string) error {
for p := range c.ipts {
ipt := c.ipts[p]
if !matchProtocol(ipt, protocol) {
continue
}
exists, err := ipt.ChainExists(table, chain)
if err != nil {
return fmt.Errorf("error checking if chain %s exists in table %s: %v", chain, table, err)
}
if exists {
continue
}
if err := ipt.NewChain(table, chain); err != nil {
return fmt.Errorf("error creating chain %s in table %s: %v", chain, table, err)
}
klog.V(2).InfoS("Created a chain", "chain", chain, "table", table, "protocol", p)
}
return nil
}
// ChainExists checks if target chain already exists in a table
func (c *Client) ChainExists(protocol Protocol, table string, chain string) (bool, error) {
for p := range c.ipts {
ipt := c.ipts[p]
if !matchProtocol(ipt, protocol) {
continue
}
exists, err := ipt.ChainExists(table, chain)
if err != nil {
return false, fmt.Errorf("error checking if chain %s exists in table %s: %v", chain, table, err)
}
if !exists {
return false, nil
}
klog.V(2).InfoS("A chain exists", "chain", chain, "table", table, "protocol", p)
}
return true, nil
}
// AppendRule checks if target rule already exists with the protocol, appends it if not.
func (c *Client) AppendRule(protocol Protocol, table string, chain string, ruleSpec []string) error {
for p := range c.ipts {
ipt := c.ipts[p]
if !matchProtocol(ipt, protocol) {
continue
}
exist, err := ipt.Exists(table, chain, ruleSpec...)
if err != nil {
return fmt.Errorf("error checking if rule %v exists in table %s chain %s: %v", ruleSpec, table, chain, err)
}
if exist {
continue
}
if err := ipt.Append(table, chain, ruleSpec...); err != nil {
return fmt.Errorf("error appending rule %v to table %s chain %s: %v", ruleSpec, table, chain, err)
}
klog.V(2).InfoS("Appended a rule", "rule", ruleSpec, "table", table, "chain", chain, "protocol", p)
}
return nil
}
// InsertRule checks if target rule already exists, inserts it if not.
func (c *Client) InsertRule(protocol Protocol, table string, chain string, ruleSpec []string) error {
for p := range c.ipts {
ipt := c.ipts[p]
if !matchProtocol(ipt, protocol) {
continue
}
exist, err := ipt.Exists(table, chain, ruleSpec...)
if err != nil {
return fmt.Errorf("error checking if rule %v exists in table %s chain %s: %v", ruleSpec, table, chain, err)
}
if exist {
continue
}
if err := ipt.Insert(table, chain, 1, ruleSpec...); err != nil {
return fmt.Errorf("error inserting rule %v to table %s chain %s: %v", ruleSpec, table, chain, err)
}
klog.V(2).InfoS("Inserted a rule", "rule", ruleSpec, "table", table, "chain", chain)
}
return nil
}
func matchProtocol(ipt *iptables.IPTables, protocol Protocol) bool {
switch protocol {
case ProtocolDual:
return true
case ProtocolIPv4:
return ipt.Proto() == iptables.ProtocolIPv4
case ProtocolIPv6:
return ipt.Proto() == iptables.ProtocolIPv6
}
return false
}
// DeleteRule checks if target rule already exists, deletes the rule if found.
func (c *Client) DeleteRule(protocol Protocol, table string, chain string, ruleSpec []string) error {
for p := range c.ipts {
ipt := c.ipts[p]
if !matchProtocol(ipt, protocol) {
continue
}
exist, err := ipt.Exists(table, chain, ruleSpec...)
if err != nil {
return fmt.Errorf("error checking if rule %v exists in table %s chain %s: %v", ruleSpec, table, chain, err)
}
if !exist {
continue
}
if err := ipt.Delete(table, chain, ruleSpec...); err != nil {
return fmt.Errorf("error deleting rule %v from table %s chain %s: %v", ruleSpec, table, chain, err)
}
klog.V(2).InfoS("Deleted a rule", "rule", ruleSpec, "table", table, "chain", chain, "protocol", p)
}
return nil
}
// DeleteChain deletes all rules from a chain in a table and then delete the chain.
func (c *Client) DeleteChain(protocol Protocol, table string, chain string) error {
for p := range c.ipts {
ipt := c.ipts[p]
if !matchProtocol(ipt, protocol) {
continue
}
exists, err := ipt.ChainExists(table, chain)
if err != nil {
return fmt.Errorf("error checking if chain %s exists in table %s: %v", chain, table, err)
}
if !exists {
continue
}
if err = ipt.ClearChain(table, chain); err != nil {
return fmt.Errorf("error clearing rules from table %s chain %s: %v", table, chain, err)
}
if err = ipt.DeleteChain(table, chain); err != nil {
return fmt.Errorf("error deleting chain %s from table %s: %v", chain, table, err)
}
klog.V(2).InfoS("Deleted a chain", "chain", chain, "table", table, "protocol", p)
}
return nil
}
// ListRules lists all rules from a chain in a table.
func (c *Client) ListRules(table string, chain string) ([]string, error) {
var allRules []string
for p := range c.ipts {
rules, err := c.ipts[p].List(table, chain)
if err != nil {
return rules, fmt.Errorf("error getting rules from table %s chain %s protocol %s: %v", table, chain, p, err)
}
allRules = append(allRules, rules...)
}
return allRules, nil
}
// Restore calls iptable-restore to restore iptables with the provided content.
// If flush is true, all previous contents of the respective tables will be flushed.
// Otherwise only involved chains will be flushed. Restore supports "ip6tables-restore" for IPv6.
func (c *Client) Restore(data []byte, flush bool, useIPv6 bool) error {
var args []string
if !flush {
args = append(args, "--noflush")
}
iptablesCmd := "iptables-restore"
if useIPv6 {
iptablesCmd = "ip6tables-restore"
}
cmd := exec.Command(iptablesCmd, args...)
cmd.Stdin = bytes.NewBuffer(data)
stderr := &bytes.Buffer{}
cmd.Stderr = stderr
// We acquire xtables lock for iptables-restore to prevent it from conflicting
// with iptables/iptables-restore which might being called by kube-proxy.
// iptables supports "--wait" option and go-iptables has enabled it.
// iptables-restore doesn't support the option until 1.6.2. We use "-w" if the
// detected version is greater than or equal to 1.6.2, otherwise we acquire the
// file lock explicitly.
// Note that we cannot just acquire the file lock explicitly for all cases because
// iptables-restore will try acquiring the lock with or without "-w" provided since 1.6.2.
if c.restoreWaitSupported {
cmd.Args = append(cmd.Args, "-w", strconv.Itoa(waitSeconds), "-W", strconv.Itoa(waitIntervalMicroSeconds))
} else {
unlockFunc, err := Lock(XtablesLockFilePath, waitSeconds*time.Second)
if err != nil {
return err
}
defer unlockFunc()
}
if err := cmd.Run(); err != nil {
klog.ErrorS(err, "Failed to execute iptables command", "iptablesCmd", iptablesCmd, "stdin", data, "stderr", stderr)
return fmt.Errorf("error executing %s: %v", iptablesCmd, err)
}
return nil
}
// Save calls iptables-saves to dump chains and tables in iptables.
func (c *Client) Save() ([]byte, error) {
var output []byte
for p := range c.ipts {
var cmd string
ipt := c.ipts[p]
switch ipt.Proto() {
case iptables.ProtocolIPv6:
cmd = "ip6tables-save"
default:
cmd = "iptables-save"
}
data, err := exec.Command(cmd, "-c").CombinedOutput()
if err != nil {
return nil, err
}
output = append(output, data...)
}
return output, nil
}
func MakeChainLine(chain string) string {
return fmt.Sprintf(":%s - [0:0]", chain)
}