mirror of
https://github.com/tailscale/tailscale.git
synced 2024-12-01 14:05:39 +00:00
a496cdc943
Instead of retrieving the list of chains, or the list of rules in a chain, just try deleting the ones we don't want and then adding the ones we do want. An error in flushing/deleting still means the rule doesn't exist anymore, so there was no need to check for it first. This avoids the need to parse iptables output, which avoids the need to ever call iptables -S, which fixes #403, among other things. It's also much more future proof in case the iptables command line changes. Unfortunately the iptables go module doesn't properly pass the iptables command exit code back up when doing .Delete(), so we can't correctly check the exit code there. (exit code 1 really means the rule didn't exist, rather than some other weird problem). Signed-off-by: Avery Pennarun <apenwarr@tailscale.com>
88 lines
1.9 KiB
Go
88 lines
1.9 KiB
Go
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package router
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os/exec"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// commandRunner abstracts helpers to run OS commands. It exists
|
|
// purely to swap out osCommandRunner (below) with a fake runner in
|
|
// tests.
|
|
type commandRunner interface {
|
|
run(...string) error
|
|
output(...string) ([]byte, error)
|
|
}
|
|
|
|
type osCommandRunner struct{}
|
|
|
|
func errCode(err error) int {
|
|
if err == nil {
|
|
return 0
|
|
}
|
|
var e *exec.ExitError
|
|
if ok := errors.As(err, &e); ok {
|
|
return e.ExitCode()
|
|
}
|
|
s := err.Error()
|
|
if strings.HasPrefix(s, "exitcode:") {
|
|
code, err := strconv.Atoi(s[9:])
|
|
if err == nil {
|
|
return code
|
|
}
|
|
}
|
|
return -42
|
|
}
|
|
|
|
func (o osCommandRunner) run(args ...string) error {
|
|
_, err := o.output(args...)
|
|
return err
|
|
}
|
|
|
|
func (o osCommandRunner) output(args ...string) ([]byte, error) {
|
|
if len(args) == 0 {
|
|
return nil, errors.New("cmd: no argv[0]")
|
|
}
|
|
|
|
out, err := exec.Command(args[0], args[1:]...).CombinedOutput()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("running %q failed: %w\n%s", strings.Join(args, " "), err, out)
|
|
}
|
|
|
|
return out, nil
|
|
}
|
|
|
|
type runGroup struct {
|
|
OkCode int // an error code that is acceptable, other than 0, if any
|
|
Runner commandRunner // the runner that actually runs our commands
|
|
ErrAcc error // first error encountered, if any
|
|
}
|
|
|
|
func newRunGroup(okCode int, runner commandRunner) *runGroup {
|
|
return &runGroup{
|
|
OkCode: okCode,
|
|
Runner: runner,
|
|
}
|
|
}
|
|
|
|
func (rg *runGroup) Output(args ...string) []byte {
|
|
b, err := rg.Runner.output(args...)
|
|
if rg.ErrAcc == nil && err != nil && errCode(err) != rg.OkCode {
|
|
rg.ErrAcc = err
|
|
}
|
|
return b
|
|
}
|
|
|
|
func (rg *runGroup) Run(args ...string) {
|
|
err := rg.Runner.run(args...)
|
|
if rg.ErrAcc == nil && err != nil && errCode(err) != rg.OkCode {
|
|
rg.ErrAcc = err
|
|
}
|
|
}
|