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