k8s.io/kubernetes@v1.29.3/pkg/util/iptables/iptables.go (about) 1 /* 2 Copyright 2014 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package iptables 18 19 import ( 20 "bufio" 21 "bytes" 22 "context" 23 "fmt" 24 "regexp" 25 "strconv" 26 "strings" 27 "sync" 28 "time" 29 30 "k8s.io/apimachinery/pkg/util/sets" 31 utilversion "k8s.io/apimachinery/pkg/util/version" 32 utilwait "k8s.io/apimachinery/pkg/util/wait" 33 "k8s.io/klog/v2" 34 utilexec "k8s.io/utils/exec" 35 utiltrace "k8s.io/utils/trace" 36 ) 37 38 // RulePosition holds the -I/-A flags for iptable 39 type RulePosition string 40 41 const ( 42 // Prepend is the insert flag for iptable 43 Prepend RulePosition = "-I" 44 // Append is the append flag for iptable 45 Append RulePosition = "-A" 46 ) 47 48 // Interface is an injectable interface for running iptables commands. Implementations must be goroutine-safe. 49 type Interface interface { 50 // EnsureChain checks if the specified chain exists and, if not, creates it. If the chain existed, return true. 51 EnsureChain(table Table, chain Chain) (bool, error) 52 // FlushChain clears the specified chain. If the chain did not exist, return error. 53 FlushChain(table Table, chain Chain) error 54 // DeleteChain deletes the specified chain. If the chain did not exist, return error. 55 DeleteChain(table Table, chain Chain) error 56 // ChainExists tests whether the specified chain exists, returning an error if it 57 // does not, or if it is unable to check. 58 ChainExists(table Table, chain Chain) (bool, error) 59 // EnsureRule checks if the specified rule is present and, if not, creates it. If the rule existed, return true. 60 EnsureRule(position RulePosition, table Table, chain Chain, args ...string) (bool, error) 61 // DeleteRule checks if the specified rule is present and, if so, deletes it. 62 DeleteRule(table Table, chain Chain, args ...string) error 63 // IsIPv6 returns true if this is managing ipv6 tables. 64 IsIPv6() bool 65 // Protocol returns the IP family this instance is managing, 66 Protocol() Protocol 67 // SaveInto calls `iptables-save` for table and stores result in a given buffer. 68 SaveInto(table Table, buffer *bytes.Buffer) error 69 // Restore runs `iptables-restore` passing data through []byte. 70 // table is the Table to restore 71 // data should be formatted like the output of SaveInto() 72 // flush sets the presence of the "--noflush" flag. see: FlushFlag 73 // counters sets the "--counters" flag. see: RestoreCountersFlag 74 Restore(table Table, data []byte, flush FlushFlag, counters RestoreCountersFlag) error 75 // RestoreAll is the same as Restore except that no table is specified. 76 RestoreAll(data []byte, flush FlushFlag, counters RestoreCountersFlag) error 77 // Monitor detects when the given iptables tables have been flushed by an external 78 // tool (e.g. a firewall reload) by creating canary chains and polling to see if 79 // they have been deleted. (Specifically, it polls tables[0] every interval until 80 // the canary has been deleted from there, then waits a short additional time for 81 // the canaries to be deleted from the remaining tables as well. You can optimize 82 // the polling by listing a relatively empty table in tables[0]). When a flush is 83 // detected, this calls the reloadFunc so the caller can reload their own iptables 84 // rules. If it is unable to create the canary chains (either initially or after 85 // a reload) it will log an error and stop monitoring. 86 // (This function should be called from a goroutine.) 87 Monitor(canary Chain, tables []Table, reloadFunc func(), interval time.Duration, stopCh <-chan struct{}) 88 // HasRandomFully reveals whether `-j MASQUERADE` takes the 89 // `--random-fully` option. This is helpful to work around a 90 // Linux kernel bug that sometimes causes multiple flows to get 91 // mapped to the same IP:PORT and consequently some suffer packet 92 // drops. 93 HasRandomFully() bool 94 95 // Present checks if the kernel supports the iptable interface 96 Present() bool 97 } 98 99 // Protocol defines the ip protocol either ipv4 or ipv6 100 type Protocol string 101 102 const ( 103 // ProtocolIPv4 represents ipv4 protocol in iptables 104 ProtocolIPv4 Protocol = "IPv4" 105 // ProtocolIPv6 represents ipv6 protocol in iptables 106 ProtocolIPv6 Protocol = "IPv6" 107 ) 108 109 // Table represents different iptable like filter,nat, mangle and raw 110 type Table string 111 112 const ( 113 // TableNAT represents the built-in nat table 114 TableNAT Table = "nat" 115 // TableFilter represents the built-in filter table 116 TableFilter Table = "filter" 117 // TableMangle represents the built-in mangle table 118 TableMangle Table = "mangle" 119 ) 120 121 // Chain represents the different rules 122 type Chain string 123 124 const ( 125 // ChainPostrouting used for source NAT in nat table 126 ChainPostrouting Chain = "POSTROUTING" 127 // ChainPrerouting used for DNAT (destination NAT) in nat table 128 ChainPrerouting Chain = "PREROUTING" 129 // ChainOutput used for the packets going out from local 130 ChainOutput Chain = "OUTPUT" 131 // ChainInput used for incoming packets 132 ChainInput Chain = "INPUT" 133 // ChainForward used for the packets for another NIC 134 ChainForward Chain = "FORWARD" 135 ) 136 137 const ( 138 cmdIPTablesSave string = "iptables-save" 139 cmdIPTablesRestore string = "iptables-restore" 140 cmdIPTables string = "iptables" 141 cmdIP6TablesRestore string = "ip6tables-restore" 142 cmdIP6TablesSave string = "ip6tables-save" 143 cmdIP6Tables string = "ip6tables" 144 ) 145 146 // RestoreCountersFlag is an option flag for Restore 147 type RestoreCountersFlag bool 148 149 // RestoreCounters a boolean true constant for the option flag RestoreCountersFlag 150 const RestoreCounters RestoreCountersFlag = true 151 152 // NoRestoreCounters a boolean false constant for the option flag RestoreCountersFlag 153 const NoRestoreCounters RestoreCountersFlag = false 154 155 // FlushFlag an option flag for Flush 156 type FlushFlag bool 157 158 // FlushTables a boolean true constant for option flag FlushFlag 159 const FlushTables FlushFlag = true 160 161 // NoFlushTables a boolean false constant for option flag FlushFlag 162 const NoFlushTables FlushFlag = false 163 164 // MinCheckVersion minimum version to be checked 165 // Versions of iptables less than this do not support the -C / --check flag 166 // (test whether a rule exists). 167 var MinCheckVersion = utilversion.MustParseGeneric("1.4.11") 168 169 // RandomFullyMinVersion is the minimum version from which the --random-fully flag is supported, 170 // used for port mapping to be fully randomized 171 var RandomFullyMinVersion = utilversion.MustParseGeneric("1.6.2") 172 173 // WaitMinVersion a minimum iptables versions supporting the -w and -w<seconds> flags 174 var WaitMinVersion = utilversion.MustParseGeneric("1.4.20") 175 176 // WaitIntervalMinVersion a minimum iptables versions supporting the wait interval useconds 177 var WaitIntervalMinVersion = utilversion.MustParseGeneric("1.6.1") 178 179 // WaitSecondsMinVersion a minimum iptables versions supporting the wait seconds 180 var WaitSecondsMinVersion = utilversion.MustParseGeneric("1.4.22") 181 182 // WaitRestoreMinVersion a minimum iptables versions supporting the wait restore seconds 183 var WaitRestoreMinVersion = utilversion.MustParseGeneric("1.6.2") 184 185 // WaitString a constant for specifying the wait flag 186 const WaitString = "-w" 187 188 // WaitSecondsValue a constant for specifying the default wait seconds 189 const WaitSecondsValue = "5" 190 191 // WaitIntervalString a constant for specifying the wait interval flag 192 const WaitIntervalString = "-W" 193 194 // WaitIntervalUsecondsValue a constant for specifying the default wait interval useconds 195 const WaitIntervalUsecondsValue = "100000" 196 197 // LockfilePath16x is the iptables 1.6.x lock file acquired by any process that's making any change in the iptable rule 198 const LockfilePath16x = "/run/xtables.lock" 199 200 // LockfilePath14x is the iptables 1.4.x lock file acquired by any process that's making any change in the iptable rule 201 const LockfilePath14x = "@xtables" 202 203 // runner implements Interface in terms of exec("iptables"). 204 type runner struct { 205 mu sync.Mutex 206 exec utilexec.Interface 207 protocol Protocol 208 hasCheck bool 209 hasRandomFully bool 210 waitFlag []string 211 restoreWaitFlag []string 212 lockfilePath14x string 213 lockfilePath16x string 214 } 215 216 // newInternal returns a new Interface which will exec iptables, and allows the 217 // caller to change the iptables-restore lockfile path 218 func newInternal(exec utilexec.Interface, protocol Protocol, lockfilePath14x, lockfilePath16x string) Interface { 219 version, err := getIPTablesVersion(exec, protocol) 220 if err != nil { 221 klog.InfoS("Error checking iptables version, assuming version at least", "version", MinCheckVersion, "err", err) 222 version = MinCheckVersion 223 } 224 225 if lockfilePath16x == "" { 226 lockfilePath16x = LockfilePath16x 227 } 228 if lockfilePath14x == "" { 229 lockfilePath14x = LockfilePath14x 230 } 231 232 runner := &runner{ 233 exec: exec, 234 protocol: protocol, 235 hasCheck: version.AtLeast(MinCheckVersion), 236 hasRandomFully: version.AtLeast(RandomFullyMinVersion), 237 waitFlag: getIPTablesWaitFlag(version), 238 restoreWaitFlag: getIPTablesRestoreWaitFlag(version, exec, protocol), 239 lockfilePath14x: lockfilePath14x, 240 lockfilePath16x: lockfilePath16x, 241 } 242 return runner 243 } 244 245 // New returns a new Interface which will exec iptables. 246 func New(exec utilexec.Interface, protocol Protocol) Interface { 247 return newInternal(exec, protocol, "", "") 248 } 249 250 // EnsureChain is part of Interface. 251 func (runner *runner) EnsureChain(table Table, chain Chain) (bool, error) { 252 fullArgs := makeFullArgs(table, chain) 253 254 runner.mu.Lock() 255 defer runner.mu.Unlock() 256 257 out, err := runner.run(opCreateChain, fullArgs) 258 if err != nil { 259 if ee, ok := err.(utilexec.ExitError); ok { 260 if ee.Exited() && ee.ExitStatus() == 1 { 261 return true, nil 262 } 263 } 264 return false, fmt.Errorf("error creating chain %q: %v: %s", chain, err, out) 265 } 266 return false, nil 267 } 268 269 // FlushChain is part of Interface. 270 func (runner *runner) FlushChain(table Table, chain Chain) error { 271 fullArgs := makeFullArgs(table, chain) 272 273 runner.mu.Lock() 274 defer runner.mu.Unlock() 275 276 out, err := runner.run(opFlushChain, fullArgs) 277 if err != nil { 278 return fmt.Errorf("error flushing chain %q: %v: %s", chain, err, out) 279 } 280 return nil 281 } 282 283 // DeleteChain is part of Interface. 284 func (runner *runner) DeleteChain(table Table, chain Chain) error { 285 fullArgs := makeFullArgs(table, chain) 286 287 runner.mu.Lock() 288 defer runner.mu.Unlock() 289 290 out, err := runner.run(opDeleteChain, fullArgs) 291 if err != nil { 292 return fmt.Errorf("error deleting chain %q: %v: %s", chain, err, out) 293 } 294 return nil 295 } 296 297 // EnsureRule is part of Interface. 298 func (runner *runner) EnsureRule(position RulePosition, table Table, chain Chain, args ...string) (bool, error) { 299 fullArgs := makeFullArgs(table, chain, args...) 300 301 runner.mu.Lock() 302 defer runner.mu.Unlock() 303 304 exists, err := runner.checkRule(table, chain, args...) 305 if err != nil { 306 return false, err 307 } 308 if exists { 309 return true, nil 310 } 311 out, err := runner.run(operation(position), fullArgs) 312 if err != nil { 313 return false, fmt.Errorf("error appending rule: %v: %s", err, out) 314 } 315 return false, nil 316 } 317 318 // DeleteRule is part of Interface. 319 func (runner *runner) DeleteRule(table Table, chain Chain, args ...string) error { 320 fullArgs := makeFullArgs(table, chain, args...) 321 322 runner.mu.Lock() 323 defer runner.mu.Unlock() 324 325 exists, err := runner.checkRule(table, chain, args...) 326 if err != nil { 327 return err 328 } 329 if !exists { 330 return nil 331 } 332 out, err := runner.run(opDeleteRule, fullArgs) 333 if err != nil { 334 return fmt.Errorf("error deleting rule: %v: %s", err, out) 335 } 336 return nil 337 } 338 339 func (runner *runner) IsIPv6() bool { 340 return runner.protocol == ProtocolIPv6 341 } 342 343 func (runner *runner) Protocol() Protocol { 344 return runner.protocol 345 } 346 347 // SaveInto is part of Interface. 348 func (runner *runner) SaveInto(table Table, buffer *bytes.Buffer) error { 349 runner.mu.Lock() 350 defer runner.mu.Unlock() 351 352 trace := utiltrace.New("iptables save") 353 defer trace.LogIfLong(2 * time.Second) 354 355 // run and return 356 iptablesSaveCmd := iptablesSaveCommand(runner.protocol) 357 args := []string{"-t", string(table)} 358 klog.V(4).InfoS("Running", "command", iptablesSaveCmd, "arguments", args) 359 cmd := runner.exec.Command(iptablesSaveCmd, args...) 360 cmd.SetStdout(buffer) 361 stderrBuffer := bytes.NewBuffer(nil) 362 cmd.SetStderr(stderrBuffer) 363 364 err := cmd.Run() 365 if err != nil { 366 stderrBuffer.WriteTo(buffer) // ignore error, since we need to return the original error 367 } 368 return err 369 } 370 371 // Restore is part of Interface. 372 func (runner *runner) Restore(table Table, data []byte, flush FlushFlag, counters RestoreCountersFlag) error { 373 // setup args 374 args := []string{"-T", string(table)} 375 return runner.restoreInternal(args, data, flush, counters) 376 } 377 378 // RestoreAll is part of Interface. 379 func (runner *runner) RestoreAll(data []byte, flush FlushFlag, counters RestoreCountersFlag) error { 380 // setup args 381 args := make([]string, 0) 382 return runner.restoreInternal(args, data, flush, counters) 383 } 384 385 type iptablesLocker interface { 386 Close() error 387 } 388 389 // restoreInternal is the shared part of Restore/RestoreAll 390 func (runner *runner) restoreInternal(args []string, data []byte, flush FlushFlag, counters RestoreCountersFlag) error { 391 runner.mu.Lock() 392 defer runner.mu.Unlock() 393 394 trace := utiltrace.New("iptables restore") 395 defer trace.LogIfLong(2 * time.Second) 396 397 if !flush { 398 args = append(args, "--noflush") 399 } 400 if counters { 401 args = append(args, "--counters") 402 } 403 404 // Grab the iptables lock to prevent iptables-restore and iptables 405 // from stepping on each other. iptables-restore 1.6.2 will have 406 // a --wait option like iptables itself, but that's not widely deployed. 407 if len(runner.restoreWaitFlag) == 0 { 408 locker, err := grabIptablesLocks(runner.lockfilePath14x, runner.lockfilePath16x) 409 if err != nil { 410 return err 411 } 412 trace.Step("Locks grabbed") 413 defer func(locker iptablesLocker) { 414 if err := locker.Close(); err != nil { 415 klog.ErrorS(err, "Failed to close iptables locks") 416 } 417 }(locker) 418 } 419 420 // run the command and return the output or an error including the output and error 421 fullArgs := append(runner.restoreWaitFlag, args...) 422 iptablesRestoreCmd := iptablesRestoreCommand(runner.protocol) 423 klog.V(4).InfoS("Running", "command", iptablesRestoreCmd, "arguments", fullArgs) 424 cmd := runner.exec.Command(iptablesRestoreCmd, fullArgs...) 425 cmd.SetStdin(bytes.NewBuffer(data)) 426 b, err := cmd.CombinedOutput() 427 if err != nil { 428 pErr, ok := parseRestoreError(string(b)) 429 if ok { 430 return pErr 431 } 432 return fmt.Errorf("%w: %s", err, b) 433 } 434 return nil 435 } 436 437 func iptablesSaveCommand(protocol Protocol) string { 438 if protocol == ProtocolIPv6 { 439 return cmdIP6TablesSave 440 } 441 return cmdIPTablesSave 442 } 443 444 func iptablesRestoreCommand(protocol Protocol) string { 445 if protocol == ProtocolIPv6 { 446 return cmdIP6TablesRestore 447 } 448 return cmdIPTablesRestore 449 450 } 451 452 func iptablesCommand(protocol Protocol) string { 453 if protocol == ProtocolIPv6 { 454 return cmdIP6Tables 455 } 456 return cmdIPTables 457 } 458 459 func (runner *runner) run(op operation, args []string) ([]byte, error) { 460 return runner.runContext(context.TODO(), op, args) 461 } 462 463 func (runner *runner) runContext(ctx context.Context, op operation, args []string) ([]byte, error) { 464 iptablesCmd := iptablesCommand(runner.protocol) 465 fullArgs := append(runner.waitFlag, string(op)) 466 fullArgs = append(fullArgs, args...) 467 klog.V(5).InfoS("Running", "command", iptablesCmd, "arguments", fullArgs) 468 if ctx == nil { 469 return runner.exec.Command(iptablesCmd, fullArgs...).CombinedOutput() 470 } 471 return runner.exec.CommandContext(ctx, iptablesCmd, fullArgs...).CombinedOutput() 472 // Don't log err here - callers might not think it is an error. 473 } 474 475 // Returns (bool, nil) if it was able to check the existence of the rule, or 476 // (<undefined>, error) if the process of checking failed. 477 func (runner *runner) checkRule(table Table, chain Chain, args ...string) (bool, error) { 478 if runner.hasCheck { 479 return runner.checkRuleUsingCheck(makeFullArgs(table, chain, args...)) 480 } 481 return runner.checkRuleWithoutCheck(table, chain, args...) 482 } 483 484 var hexnumRE = regexp.MustCompile("0x0+([0-9])") 485 486 func trimhex(s string) string { 487 return hexnumRE.ReplaceAllString(s, "0x$1") 488 } 489 490 // Executes the rule check without using the "-C" flag, instead parsing iptables-save. 491 // Present for compatibility with <1.4.11 versions of iptables. This is full 492 // of hack and half-measures. We should nix this ASAP. 493 func (runner *runner) checkRuleWithoutCheck(table Table, chain Chain, args ...string) (bool, error) { 494 iptablesSaveCmd := iptablesSaveCommand(runner.protocol) 495 klog.V(1).InfoS("Running", "command", iptablesSaveCmd, "table", string(table)) 496 out, err := runner.exec.Command(iptablesSaveCmd, "-t", string(table)).CombinedOutput() 497 if err != nil { 498 return false, fmt.Errorf("error checking rule: %v", err) 499 } 500 501 // Sadly, iptables has inconsistent quoting rules for comments. Just remove all quotes. 502 // Also, quoted multi-word comments (which are counted as a single arg) 503 // will be unpacked into multiple args, 504 // in order to compare against iptables-save output (which will be split at whitespace boundary) 505 // e.g. a single arg('"this must be before the NodePort rules"') will be unquoted and unpacked into 7 args. 506 var argsCopy []string 507 for i := range args { 508 tmpField := strings.Trim(args[i], "\"") 509 tmpField = trimhex(tmpField) 510 argsCopy = append(argsCopy, strings.Fields(tmpField)...) 511 } 512 argset := sets.NewString(argsCopy...) 513 514 for _, line := range strings.Split(string(out), "\n") { 515 var fields = strings.Fields(line) 516 517 // Check that this is a rule for the correct chain, and that it has 518 // the correct number of argument (+2 for "-A <chain name>") 519 if !strings.HasPrefix(line, fmt.Sprintf("-A %s", string(chain))) || len(fields) != len(argsCopy)+2 { 520 continue 521 } 522 523 // Sadly, iptables has inconsistent quoting rules for comments. 524 // Just remove all quotes. 525 for i := range fields { 526 fields[i] = strings.Trim(fields[i], "\"") 527 fields[i] = trimhex(fields[i]) 528 } 529 530 // TODO: This misses reorderings e.g. "-x foo ! -y bar" will match "! -x foo -y bar" 531 if sets.NewString(fields...).IsSuperset(argset) { 532 return true, nil 533 } 534 klog.V(5).InfoS("DBG: fields is not a superset of args", "fields", fields, "arguments", args) 535 } 536 537 return false, nil 538 } 539 540 // Executes the rule check using the "-C" flag 541 func (runner *runner) checkRuleUsingCheck(args []string) (bool, error) { 542 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) 543 defer cancel() 544 545 out, err := runner.runContext(ctx, opCheckRule, args) 546 if ctx.Err() == context.DeadlineExceeded { 547 return false, fmt.Errorf("timed out while checking rules") 548 } 549 if err == nil { 550 return true, nil 551 } 552 if ee, ok := err.(utilexec.ExitError); ok { 553 // iptables uses exit(1) to indicate a failure of the operation, 554 // as compared to a malformed commandline, for example. 555 if ee.Exited() && ee.ExitStatus() == 1 { 556 return false, nil 557 } 558 } 559 return false, fmt.Errorf("error checking rule: %v: %s", err, out) 560 } 561 562 const ( 563 // Max time we wait for an iptables flush to complete after we notice it has started 564 iptablesFlushTimeout = 5 * time.Second 565 // How often we poll while waiting for an iptables flush to complete 566 iptablesFlushPollTime = 100 * time.Millisecond 567 ) 568 569 // Monitor is part of Interface 570 func (runner *runner) Monitor(canary Chain, tables []Table, reloadFunc func(), interval time.Duration, stopCh <-chan struct{}) { 571 for { 572 _ = utilwait.PollImmediateUntil(interval, func() (bool, error) { 573 for _, table := range tables { 574 if _, err := runner.EnsureChain(table, canary); err != nil { 575 klog.ErrorS(err, "Could not set up iptables canary", "table", table, "chain", canary) 576 return false, nil 577 } 578 } 579 return true, nil 580 }, stopCh) 581 582 // Poll until stopCh is closed or iptables is flushed 583 err := utilwait.PollUntil(interval, func() (bool, error) { 584 if exists, err := runner.ChainExists(tables[0], canary); exists { 585 return false, nil 586 } else if isResourceError(err) { 587 klog.ErrorS(err, "Could not check for iptables canary", "table", tables[0], "chain", canary) 588 return false, nil 589 } 590 klog.V(2).InfoS("IPTables canary deleted", "table", tables[0], "chain", canary) 591 // Wait for the other canaries to be deleted too before returning 592 // so we don't start reloading too soon. 593 err := utilwait.PollImmediate(iptablesFlushPollTime, iptablesFlushTimeout, func() (bool, error) { 594 for i := 1; i < len(tables); i++ { 595 if exists, err := runner.ChainExists(tables[i], canary); exists || isResourceError(err) { 596 return false, nil 597 } 598 } 599 return true, nil 600 }) 601 if err != nil { 602 klog.InfoS("Inconsistent iptables state detected") 603 } 604 return true, nil 605 }, stopCh) 606 607 if err != nil { 608 // stopCh was closed 609 for _, table := range tables { 610 _ = runner.DeleteChain(table, canary) 611 } 612 return 613 } 614 615 klog.V(2).InfoS("Reloading after iptables flush") 616 reloadFunc() 617 } 618 } 619 620 // ChainExists is part of Interface 621 func (runner *runner) ChainExists(table Table, chain Chain) (bool, error) { 622 fullArgs := makeFullArgs(table, chain) 623 624 runner.mu.Lock() 625 defer runner.mu.Unlock() 626 627 trace := utiltrace.New("iptables ChainExists") 628 defer trace.LogIfLong(2 * time.Second) 629 630 _, err := runner.run(opListChain, fullArgs) 631 return err == nil, err 632 } 633 634 type operation string 635 636 const ( 637 opCreateChain operation = "-N" 638 opFlushChain operation = "-F" 639 opDeleteChain operation = "-X" 640 opListChain operation = "-S" 641 opCheckRule operation = "-C" 642 opDeleteRule operation = "-D" 643 ) 644 645 func makeFullArgs(table Table, chain Chain, args ...string) []string { 646 return append([]string{string(chain), "-t", string(table)}, args...) 647 } 648 649 const iptablesVersionPattern = `v([0-9]+(\.[0-9]+)+)` 650 651 // getIPTablesVersion runs "iptables --version" and parses the returned version 652 func getIPTablesVersion(exec utilexec.Interface, protocol Protocol) (*utilversion.Version, error) { 653 // this doesn't access mutable state so we don't need to use the interface / runner 654 iptablesCmd := iptablesCommand(protocol) 655 bytes, err := exec.Command(iptablesCmd, "--version").CombinedOutput() 656 if err != nil { 657 return nil, err 658 } 659 versionMatcher := regexp.MustCompile(iptablesVersionPattern) 660 match := versionMatcher.FindStringSubmatch(string(bytes)) 661 if match == nil { 662 return nil, fmt.Errorf("no iptables version found in string: %s", bytes) 663 } 664 version, err := utilversion.ParseGeneric(match[1]) 665 if err != nil { 666 return nil, fmt.Errorf("iptables version %q is not a valid version string: %v", match[1], err) 667 } 668 669 return version, nil 670 } 671 672 // Checks if iptables version has a "wait" flag 673 func getIPTablesWaitFlag(version *utilversion.Version) []string { 674 switch { 675 case version.AtLeast(WaitIntervalMinVersion): 676 return []string{WaitString, WaitSecondsValue, WaitIntervalString, WaitIntervalUsecondsValue} 677 case version.AtLeast(WaitSecondsMinVersion): 678 return []string{WaitString, WaitSecondsValue} 679 case version.AtLeast(WaitMinVersion): 680 return []string{WaitString} 681 default: 682 return nil 683 } 684 } 685 686 // Checks if iptables-restore has a "wait" flag 687 func getIPTablesRestoreWaitFlag(version *utilversion.Version, exec utilexec.Interface, protocol Protocol) []string { 688 if version.AtLeast(WaitRestoreMinVersion) { 689 return []string{WaitString, WaitSecondsValue, WaitIntervalString, WaitIntervalUsecondsValue} 690 } 691 692 // Older versions may have backported features; if iptables-restore supports 693 // --version, assume it also supports --wait 694 vstring, err := getIPTablesRestoreVersionString(exec, protocol) 695 if err != nil || vstring == "" { 696 klog.V(3).InfoS("Couldn't get iptables-restore version; assuming it doesn't support --wait") 697 return nil 698 } 699 if _, err := utilversion.ParseGeneric(vstring); err != nil { 700 klog.V(3).InfoS("Couldn't parse iptables-restore version; assuming it doesn't support --wait") 701 return nil 702 } 703 return []string{WaitString} 704 } 705 706 // getIPTablesRestoreVersionString runs "iptables-restore --version" to get the version string 707 // in the form "X.X.X" 708 func getIPTablesRestoreVersionString(exec utilexec.Interface, protocol Protocol) (string, error) { 709 // this doesn't access mutable state so we don't need to use the interface / runner 710 711 // iptables-restore hasn't always had --version, and worse complains 712 // about unrecognized commands but doesn't exit when it gets them. 713 // Work around that by setting stdin to nothing so it exits immediately. 714 iptablesRestoreCmd := iptablesRestoreCommand(protocol) 715 cmd := exec.Command(iptablesRestoreCmd, "--version") 716 cmd.SetStdin(bytes.NewReader([]byte{})) 717 bytes, err := cmd.CombinedOutput() 718 if err != nil { 719 return "", err 720 } 721 versionMatcher := regexp.MustCompile(iptablesVersionPattern) 722 match := versionMatcher.FindStringSubmatch(string(bytes)) 723 if match == nil { 724 return "", fmt.Errorf("no iptables version found in string: %s", bytes) 725 } 726 return match[1], nil 727 } 728 729 func (runner *runner) HasRandomFully() bool { 730 return runner.hasRandomFully 731 } 732 733 // Present tests if iptable is supported on current kernel by checking the existence 734 // of default table and chain 735 func (runner *runner) Present() bool { 736 if _, err := runner.ChainExists(TableNAT, ChainPostrouting); err != nil { 737 return false 738 } 739 740 return true 741 } 742 743 var iptablesNotFoundStrings = []string{ 744 // iptables-legacy [-A|-I] BAD-CHAIN [...] 745 // iptables-legacy [-C|-D] GOOD-CHAIN [...non-matching rule...] 746 // iptables-legacy [-X|-F|-Z] BAD-CHAIN 747 // iptables-nft -X BAD-CHAIN 748 // NB: iptables-nft [-F|-Z] BAD-CHAIN exits with no error 749 "No chain/target/match by that name", 750 751 // iptables-legacy [...] -j BAD-CHAIN 752 // iptables-nft-1.8.0 [-A|-I] BAD-CHAIN [...] 753 // iptables-nft-1.8.0 [-A|-I] GOOD-CHAIN -j BAD-CHAIN 754 // NB: also matches some other things like "-m BAD-MODULE" 755 "No such file or directory", 756 757 // iptables-legacy [-C|-D] BAD-CHAIN [...] 758 // iptables-nft [-C|-D] GOOD-CHAIN [...non-matching rule...] 759 "does a matching rule exist", 760 761 // iptables-nft-1.8.2 [-A|-C|-D|-I] BAD-CHAIN [...] 762 // iptables-nft-1.8.2 [...] -j BAD-CHAIN 763 "does not exist", 764 } 765 766 // IsNotFoundError returns true if the error indicates "not found". It parses 767 // the error string looking for known values, which is imperfect; beware using 768 // this function for anything beyond deciding between logging or ignoring an 769 // error. 770 func IsNotFoundError(err error) bool { 771 es := err.Error() 772 for _, str := range iptablesNotFoundStrings { 773 if strings.Contains(es, str) { 774 return true 775 } 776 } 777 return false 778 } 779 780 const iptablesStatusResourceProblem = 4 781 782 // isResourceError returns true if the error indicates that iptables ran into a "resource 783 // problem" and was unable to attempt the request. In particular, this will be true if it 784 // times out trying to get the iptables lock. 785 func isResourceError(err error) bool { 786 if ee, isExitError := err.(utilexec.ExitError); isExitError { 787 return ee.ExitStatus() == iptablesStatusResourceProblem 788 } 789 return false 790 } 791 792 // ParseError records the payload when iptables reports an error parsing its input. 793 type ParseError interface { 794 // Line returns the line number on which the parse error was reported. 795 // NOTE: First line is 1. 796 Line() int 797 // Error returns the error message of the parse error, including line number. 798 Error() string 799 } 800 801 type parseError struct { 802 cmd string 803 line int 804 } 805 806 func (e parseError) Line() int { 807 return e.line 808 } 809 810 func (e parseError) Error() string { 811 return fmt.Sprintf("%s: input error on line %d: ", e.cmd, e.line) 812 } 813 814 // LineData represents a single numbered line of data. 815 type LineData struct { 816 // Line holds the line number (the first line is 1). 817 Line int 818 // The data of the line. 819 Data string 820 } 821 822 var regexpParseError = regexp.MustCompile("line ([1-9][0-9]*) failed$") 823 824 // parseRestoreError extracts the line from the error, if it matches returns parseError 825 // for example: 826 // input: iptables-restore: line 51 failed 827 // output: parseError: cmd = iptables-restore, line = 51 828 // NOTE: parseRestoreError depends on the error format of iptables, if it ever changes 829 // we need to update this function 830 func parseRestoreError(str string) (ParseError, bool) { 831 errors := strings.Split(str, ":") 832 if len(errors) != 2 { 833 return nil, false 834 } 835 cmd := errors[0] 836 matches := regexpParseError.FindStringSubmatch(errors[1]) 837 if len(matches) != 2 { 838 return nil, false 839 } 840 line, errMsg := strconv.Atoi(matches[1]) 841 if errMsg != nil { 842 return nil, false 843 } 844 return parseError{cmd: cmd, line: line}, true 845 } 846 847 // ExtractLines extracts the -count and +count data from the lineNum row of lines and return 848 // NOTE: lines start from line 1 849 func ExtractLines(lines []byte, line, count int) []LineData { 850 // first line is line 1, so line can't be smaller than 1 851 if line < 1 { 852 return nil 853 } 854 start := line - count 855 if start <= 0 { 856 start = 1 857 } 858 end := line + count + 1 859 860 offset := 1 861 scanner := bufio.NewScanner(bytes.NewBuffer(lines)) 862 extractLines := make([]LineData, 0, count*2) 863 for scanner.Scan() { 864 if offset >= start && offset < end { 865 extractLines = append(extractLines, LineData{ 866 Line: offset, 867 Data: scanner.Text(), 868 }) 869 } 870 if offset == end { 871 break 872 } 873 offset++ 874 } 875 return extractLines 876 }