github.com/timstclair/heapster@v0.20.0-alpha1/Godeps/_workspace/src/k8s.io/kubernetes/pkg/util/iptables/iptables.go (about) 1 /* 2 Copyright 2014 The Kubernetes Authors All rights reserved. 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 "fmt" 21 "io/ioutil" 22 "os" 23 "regexp" 24 "strings" 25 "sync" 26 27 "github.com/coreos/go-semver/semver" 28 godbus "github.com/godbus/dbus" 29 "github.com/golang/glog" 30 utildbus "k8s.io/kubernetes/pkg/util/dbus" 31 utilexec "k8s.io/kubernetes/pkg/util/exec" 32 "k8s.io/kubernetes/pkg/util/sets" 33 ) 34 35 type RulePosition string 36 37 const ( 38 Prepend RulePosition = "-I" 39 Append RulePosition = "-A" 40 ) 41 42 // An injectable interface for running iptables commands. Implementations must be goroutine-safe. 43 type Interface interface { 44 // GetVersion returns the "X.Y.Z" semver string for iptables. 45 GetVersion() (string, error) 46 // EnsureChain checks if the specified chain exists and, if not, creates it. If the chain existed, return true. 47 EnsureChain(table Table, chain Chain) (bool, error) 48 // FlushChain clears the specified chain. If the chain did not exist, return error. 49 FlushChain(table Table, chain Chain) error 50 // DeleteChain deletes the specified chain. If the chain did not exist, return error. 51 DeleteChain(table Table, chain Chain) error 52 // EnsureRule checks if the specified rule is present and, if not, creates it. If the rule existed, return true. 53 EnsureRule(position RulePosition, table Table, chain Chain, args ...string) (bool, error) 54 // DeleteRule checks if the specified rule is present and, if so, deletes it. 55 DeleteRule(table Table, chain Chain, args ...string) error 56 // IsIpv6 returns true if this is managing ipv6 tables 57 IsIpv6() bool 58 // TODO: (BenTheElder) Unit-Test Save/SaveAll, Restore/RestoreAll 59 // Save calls `iptables-save` for table. 60 Save(table Table) ([]byte, error) 61 // SaveAll calls `iptables-save`. 62 SaveAll() ([]byte, error) 63 // Restore runs `iptables-restore` passing data through a temporary file. 64 // table is the Table to restore 65 // data should be formatted like the output of Save() 66 // flush sets the presence of the "--noflush" flag. see: FlushFlag 67 // counters sets the "--counters" flag. see: RestoreCountersFlag 68 Restore(table Table, data []byte, flush FlushFlag, counters RestoreCountersFlag) error 69 // RestoreAll is the same as Restore except that no table is specified. 70 RestoreAll(data []byte, flush FlushFlag, counters RestoreCountersFlag) error 71 // AddReloadFunc adds a function to call on iptables reload 72 AddReloadFunc(reloadFunc func()) 73 // Destroy cleans up resources used by the Interface 74 Destroy() 75 } 76 77 type Protocol byte 78 79 const ( 80 ProtocolIpv4 Protocol = iota + 1 81 ProtocolIpv6 82 ) 83 84 type Table string 85 86 const ( 87 TableNAT Table = "nat" 88 ) 89 90 type Chain string 91 92 const ( 93 ChainPostrouting Chain = "POSTROUTING" 94 ChainPrerouting Chain = "PREROUTING" 95 ChainOutput Chain = "OUTPUT" 96 ) 97 98 const ( 99 cmdIptablesSave string = "iptables-save" 100 cmdIptablesRestore string = "iptables-restore" 101 cmdIptables string = "iptables" 102 cmdIp6tables string = "ip6tables" 103 ) 104 105 // Option flag for Restore 106 type RestoreCountersFlag bool 107 108 const RestoreCounters RestoreCountersFlag = true 109 const NoRestoreCounters RestoreCountersFlag = false 110 111 // Option flag for Flush 112 type FlushFlag bool 113 114 const FlushTables FlushFlag = true 115 const NoFlushTables FlushFlag = false 116 117 // Versions of iptables less than this do not support the -C / --check flag 118 // (test whether a rule exists). 119 const MinCheckVersion = "1.4.11" 120 121 // Minimum iptables versions supporting the -w and -w2 flags 122 const MinWaitVersion = "1.4.20" 123 const MinWait2Version = "1.4.22" 124 125 // runner implements Interface in terms of exec("iptables"). 126 type runner struct { 127 mu sync.Mutex 128 exec utilexec.Interface 129 dbus utildbus.Interface 130 protocol Protocol 131 hasCheck bool 132 waitFlag []string 133 134 reloadFuncs []func() 135 signal chan *godbus.Signal 136 } 137 138 // New returns a new Interface which will exec iptables. 139 func New(exec utilexec.Interface, dbus utildbus.Interface, protocol Protocol) Interface { 140 vstring, err := getIptablesVersionString(exec) 141 if err != nil { 142 glog.Warningf("Error checking iptables version, assuming version at least %s: %v", MinCheckVersion, err) 143 vstring = MinCheckVersion 144 } 145 runner := &runner{ 146 exec: exec, 147 dbus: dbus, 148 protocol: protocol, 149 hasCheck: getIptablesHasCheckCommand(vstring), 150 waitFlag: getIptablesWaitFlag(vstring), 151 } 152 runner.connectToFirewallD() 153 return runner 154 } 155 156 // Destroy is part of Interface. 157 func (runner *runner) Destroy() { 158 if runner.signal != nil { 159 runner.signal <- nil 160 } 161 } 162 163 const ( 164 firewalldName = "org.fedoraproject.FirewallD1" 165 firewalldPath = "/org/fedoraproject/FirewallD1" 166 firewalldInterface = "org.fedoraproject.FirewallD1" 167 ) 168 169 // Connects to D-Bus and listens for FirewallD start/restart. (On non-FirewallD-using 170 // systems, this is effectively a no-op; we listen for the signals, but they will never be 171 // emitted, so reload() will never be called.) 172 func (runner *runner) connectToFirewallD() { 173 bus, err := runner.dbus.SystemBus() 174 if err != nil { 175 glog.V(1).Infof("Could not connect to D-Bus system bus: %s", err) 176 return 177 } 178 179 rule := fmt.Sprintf("type='signal',sender='%s',path='%s',interface='%s',member='Reloaded'", firewalldName, firewalldPath, firewalldInterface) 180 bus.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, rule) 181 182 rule = fmt.Sprintf("type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',path='/org/freedesktop/DBus',sender='org.freedesktop.DBus',arg0='%s'", firewalldName) 183 bus.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, rule) 184 185 runner.signal = make(chan *godbus.Signal, 10) 186 bus.Signal(runner.signal) 187 188 go runner.dbusSignalHandler(bus) 189 } 190 191 // GetVersion returns the version string. 192 func (runner *runner) GetVersion() (string, error) { 193 return getIptablesVersionString(runner.exec) 194 } 195 196 // EnsureChain is part of Interface. 197 func (runner *runner) EnsureChain(table Table, chain Chain) (bool, error) { 198 fullArgs := makeFullArgs(table, chain) 199 200 runner.mu.Lock() 201 defer runner.mu.Unlock() 202 203 out, err := runner.run(opCreateChain, fullArgs) 204 if err != nil { 205 if ee, ok := err.(utilexec.ExitError); ok { 206 if ee.Exited() && ee.ExitStatus() == 1 { 207 return true, nil 208 } 209 } 210 return false, fmt.Errorf("error creating chain %q: %v: %s", chain, err, out) 211 } 212 return false, nil 213 } 214 215 // FlushChain is part of Interface. 216 func (runner *runner) FlushChain(table Table, chain Chain) error { 217 fullArgs := makeFullArgs(table, chain) 218 219 runner.mu.Lock() 220 defer runner.mu.Unlock() 221 222 out, err := runner.run(opFlushChain, fullArgs) 223 if err != nil { 224 return fmt.Errorf("error flushing chain %q: %v: %s", chain, err, out) 225 } 226 return nil 227 } 228 229 // DeleteChain is part of Interface. 230 func (runner *runner) DeleteChain(table Table, chain Chain) error { 231 fullArgs := makeFullArgs(table, chain) 232 233 runner.mu.Lock() 234 defer runner.mu.Unlock() 235 236 // TODO: we could call iptables -S first, ignore the output and check for non-zero return (more like DeleteRule) 237 out, err := runner.run(opDeleteChain, fullArgs) 238 if err != nil { 239 return fmt.Errorf("error deleting chain %q: %v: %s", chain, err, out) 240 } 241 return nil 242 } 243 244 // EnsureRule is part of Interface. 245 func (runner *runner) EnsureRule(position RulePosition, table Table, chain Chain, args ...string) (bool, error) { 246 fullArgs := makeFullArgs(table, chain, args...) 247 248 runner.mu.Lock() 249 defer runner.mu.Unlock() 250 251 exists, err := runner.checkRule(table, chain, args...) 252 if err != nil { 253 return false, err 254 } 255 if exists { 256 return true, nil 257 } 258 out, err := runner.run(operation(position), fullArgs) 259 if err != nil { 260 return false, fmt.Errorf("error appending rule: %v: %s", err, out) 261 } 262 return false, nil 263 } 264 265 // DeleteRule is part of Interface. 266 func (runner *runner) DeleteRule(table Table, chain Chain, args ...string) error { 267 fullArgs := makeFullArgs(table, chain, args...) 268 269 runner.mu.Lock() 270 defer runner.mu.Unlock() 271 272 exists, err := runner.checkRule(table, chain, args...) 273 if err != nil { 274 return err 275 } 276 if !exists { 277 return nil 278 } 279 out, err := runner.run(opDeleteRule, fullArgs) 280 if err != nil { 281 return fmt.Errorf("error deleting rule: %v: %s", err, out) 282 } 283 return nil 284 } 285 286 func (runner *runner) IsIpv6() bool { 287 return runner.protocol == ProtocolIpv6 288 } 289 290 // Save is part of Interface. 291 func (runner *runner) Save(table Table) ([]byte, error) { 292 runner.mu.Lock() 293 defer runner.mu.Unlock() 294 295 // run and return 296 args := []string{"-t", string(table)} 297 return runner.exec.Command(cmdIptablesSave, args...).CombinedOutput() 298 } 299 300 // SaveAll is part of Interface. 301 func (runner *runner) SaveAll() ([]byte, error) { 302 runner.mu.Lock() 303 defer runner.mu.Unlock() 304 305 // run and return 306 return runner.exec.Command(cmdIptablesSave, []string{}...).CombinedOutput() 307 } 308 309 // Restore is part of Interface. 310 func (runner *runner) Restore(table Table, data []byte, flush FlushFlag, counters RestoreCountersFlag) error { 311 // setup args 312 args := []string{"-T", string(table)} 313 return runner.restoreInternal(args, data, flush, counters) 314 } 315 316 // RestoreAll is part of Interface. 317 func (runner *runner) RestoreAll(data []byte, flush FlushFlag, counters RestoreCountersFlag) error { 318 // setup args 319 args := make([]string, 0) 320 return runner.restoreInternal(args, data, flush, counters) 321 } 322 323 // restoreInternal is the shared part of Restore/RestoreAll 324 func (runner *runner) restoreInternal(args []string, data []byte, flush FlushFlag, counters RestoreCountersFlag) error { 325 runner.mu.Lock() 326 defer runner.mu.Unlock() 327 328 if !flush { 329 args = append(args, "--noflush") 330 } 331 if counters { 332 args = append(args, "--counters") 333 } 334 // create temp file through which to pass data 335 temp, err := ioutil.TempFile("", "kube-temp-iptables-restore-") 336 if err != nil { 337 return err 338 } 339 // make sure we delete the temp file 340 defer os.Remove(temp.Name()) 341 // Put the filename at the end of args. 342 // NOTE: the filename must be at the end. 343 // See: https://git.netfilter.org/iptables/commit/iptables-restore.c?id=e6869a8f59d779ff4d5a0984c86d80db70784962 344 args = append(args, temp.Name()) 345 if err != nil { 346 return err 347 } 348 // write data to the file 349 _, err = temp.Write(data) 350 temp.Close() 351 if err != nil { 352 return err 353 } 354 // run the command and return the output or an error including the output and error 355 b, err := runner.exec.Command(cmdIptablesRestore, args...).CombinedOutput() 356 if err != nil { 357 return fmt.Errorf("%v (%s)", err, b) 358 } 359 return nil 360 } 361 362 func (runner *runner) iptablesCommand() string { 363 if runner.IsIpv6() { 364 return cmdIp6tables 365 } else { 366 return cmdIptables 367 } 368 } 369 370 func (runner *runner) run(op operation, args []string) ([]byte, error) { 371 iptablesCmd := runner.iptablesCommand() 372 373 fullArgs := append(runner.waitFlag, string(op)) 374 fullArgs = append(fullArgs, args...) 375 glog.V(4).Infof("running iptables %s %v", string(op), args) 376 return runner.exec.Command(iptablesCmd, fullArgs...).CombinedOutput() 377 // Don't log err here - callers might not think it is an error. 378 } 379 380 // Returns (bool, nil) if it was able to check the existence of the rule, or 381 // (<undefined>, error) if the process of checking failed. 382 func (runner *runner) checkRule(table Table, chain Chain, args ...string) (bool, error) { 383 if runner.hasCheck { 384 return runner.checkRuleUsingCheck(makeFullArgs(table, chain, args...)) 385 } else { 386 return runner.checkRuleWithoutCheck(table, chain, args...) 387 } 388 } 389 390 // Executes the rule check without using the "-C" flag, instead parsing iptables-save. 391 // Present for compatibility with <1.4.11 versions of iptables. This is full 392 // of hack and half-measures. We should nix this ASAP. 393 func (runner *runner) checkRuleWithoutCheck(table Table, chain Chain, args ...string) (bool, error) { 394 glog.V(1).Infof("running iptables-save -t %s", string(table)) 395 out, err := runner.exec.Command(cmdIptablesSave, "-t", string(table)).CombinedOutput() 396 if err != nil { 397 return false, fmt.Errorf("error checking rule: %v", err) 398 } 399 400 // Sadly, iptables has inconsistent quoting rules for comments. Just remove all quotes. 401 // Also, quoted multi-word comments (which are counted as a single arg) 402 // will be unpacked into multiple args, 403 // in order to compare against iptables-save output (which will be split at whitespace boundary) 404 // e.g. a single arg('"this must be before the NodePort rules"') will be unquoted and unpacked into 7 args. 405 var argsCopy []string 406 for i := range args { 407 tmpField := strings.Trim(args[i], "\"") 408 argsCopy = append(argsCopy, strings.Fields(tmpField)...) 409 } 410 argset := sets.NewString(argsCopy...) 411 412 for _, line := range strings.Split(string(out), "\n") { 413 var fields = strings.Fields(line) 414 415 // Check that this is a rule for the correct chain, and that it has 416 // the correct number of argument (+2 for "-A <chain name>") 417 if !strings.HasPrefix(line, fmt.Sprintf("-A %s", string(chain))) || len(fields) != len(argsCopy)+2 { 418 continue 419 } 420 421 // Sadly, iptables has inconsistent quoting rules for comments. 422 // Just remove all quotes. 423 for i := range fields { 424 fields[i] = strings.Trim(fields[i], "\"") 425 } 426 427 // TODO: This misses reorderings e.g. "-x foo ! -y bar" will match "! -x foo -y bar" 428 if sets.NewString(fields...).IsSuperset(argset) { 429 return true, nil 430 } 431 glog.V(5).Infof("DBG: fields is not a superset of args: fields=%v args=%v", fields, args) 432 } 433 434 return false, nil 435 } 436 437 // Executes the rule check using the "-C" flag 438 func (runner *runner) checkRuleUsingCheck(args []string) (bool, error) { 439 out, err := runner.run(opCheckRule, args) 440 if err == nil { 441 return true, nil 442 } 443 if ee, ok := err.(utilexec.ExitError); ok { 444 // iptables uses exit(1) to indicate a failure of the operation, 445 // as compared to a malformed commandline, for example. 446 if ee.Exited() && ee.ExitStatus() == 1 { 447 return false, nil 448 } 449 } 450 return false, fmt.Errorf("error checking rule: %v: %s", err, out) 451 } 452 453 type operation string 454 455 const ( 456 opCreateChain operation = "-N" 457 opFlushChain operation = "-F" 458 opDeleteChain operation = "-X" 459 opAppendRule operation = "-A" 460 opCheckRule operation = "-C" 461 opDeleteRule operation = "-D" 462 ) 463 464 func makeFullArgs(table Table, chain Chain, args ...string) []string { 465 return append([]string{string(chain), "-t", string(table)}, args...) 466 } 467 468 // Checks if iptables has the "-C" flag 469 func getIptablesHasCheckCommand(vstring string) bool { 470 minVersion, err := semver.NewVersion(MinCheckVersion) 471 if err != nil { 472 glog.Errorf("MinCheckVersion (%s) is not a valid version string: %v", MinCheckVersion, err) 473 return true 474 } 475 version, err := semver.NewVersion(vstring) 476 if err != nil { 477 glog.Errorf("vstring (%s) is not a valid version string: %v", vstring, err) 478 return true 479 } 480 if version.LessThan(*minVersion) { 481 return false 482 } 483 return true 484 } 485 486 // Checks if iptables version has a "wait" flag 487 func getIptablesWaitFlag(vstring string) []string { 488 version, err := semver.NewVersion(vstring) 489 if err != nil { 490 glog.Errorf("vstring (%s) is not a valid version string: %v", vstring, err) 491 return nil 492 } 493 494 minVersion, err := semver.NewVersion(MinWaitVersion) 495 if err != nil { 496 glog.Errorf("MinWaitVersion (%s) is not a valid version string: %v", MinWaitVersion, err) 497 return nil 498 } 499 if version.LessThan(*minVersion) { 500 return nil 501 } 502 503 minVersion, err = semver.NewVersion(MinWait2Version) 504 if err != nil { 505 glog.Errorf("MinWait2Version (%s) is not a valid version string: %v", MinWait2Version, err) 506 return nil 507 } 508 if version.LessThan(*minVersion) { 509 return []string{"-w"} 510 } else { 511 return []string{"-w2"} 512 } 513 } 514 515 // getIptablesVersionString runs "iptables --version" to get the version string 516 // in the form "X.X.X" 517 func getIptablesVersionString(exec utilexec.Interface) (string, error) { 518 // this doesn't access mutable state so we don't need to use the interface / runner 519 bytes, err := exec.Command(cmdIptables, "--version").CombinedOutput() 520 if err != nil { 521 return "", err 522 } 523 versionMatcher := regexp.MustCompile("v([0-9]+\\.[0-9]+\\.[0-9]+)") 524 match := versionMatcher.FindStringSubmatch(string(bytes)) 525 if match == nil { 526 return "", fmt.Errorf("no iptables version found in string: %s", bytes) 527 } 528 return match[1], nil 529 } 530 531 // goroutine to listen for D-Bus signals 532 func (runner *runner) dbusSignalHandler(bus utildbus.Connection) { 533 firewalld := bus.Object(firewalldName, firewalldPath) 534 535 for s := range runner.signal { 536 if s == nil { 537 // Unregister 538 bus.Signal(runner.signal) 539 return 540 } 541 542 switch s.Name { 543 case "org.freedesktop.DBus.NameOwnerChanged": 544 name := s.Body[0].(string) 545 new_owner := s.Body[2].(string) 546 547 if name != firewalldName || len(new_owner) == 0 { 548 continue 549 } 550 551 // FirewallD startup (specifically the part where it deletes 552 // all existing iptables rules) may not yet be complete when 553 // we get this signal, so make a dummy request to it to 554 // synchronize. 555 firewalld.Call(firewalldInterface+".getDefaultZone", 0) 556 557 runner.reload() 558 case firewalldInterface + ".Reloaded": 559 runner.reload() 560 } 561 } 562 } 563 564 // AddReloadFunc is part of Interface 565 func (runner *runner) AddReloadFunc(reloadFunc func()) { 566 runner.reloadFuncs = append(runner.reloadFuncs, reloadFunc) 567 } 568 569 // runs all reload funcs to re-sync iptables rules 570 func (runner *runner) reload() { 571 glog.V(1).Infof("reloading iptables rules") 572 573 for _, f := range runner.reloadFuncs { 574 f() 575 } 576 }