mirror of
https://github.com/tailscale/tailscale.git
synced 2025-12-23 09:06:24 +00:00
util/linuxfw, feature/buildfeatures: add ts_omit_iptables to make IPTables optional
Updates #12614 Change-Id: Ic0eba982aa8468a55c63e1b763345f032a55b4e2 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
committed by
Brad Fitzpatrick
parent
7df7e01d0f
commit
dd615c8fdd
@@ -1,21 +1,34 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build linux && (arm64 || amd64) && !ts_omit_iptables
|
||||
|
||||
// TODO(#8502): add support for more architectures
|
||||
//go:build linux && (arm64 || amd64)
|
||||
|
||||
package linuxfw
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/coreos/go-iptables/iptables"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/util/multierr"
|
||||
"tailscale.com/version/distro"
|
||||
)
|
||||
|
||||
func init() {
|
||||
isNotExistError = func(err error) bool {
|
||||
var e *iptables.Error
|
||||
return errors.As(err, &e) && e.IsNotExist()
|
||||
}
|
||||
}
|
||||
|
||||
// DebugNetfilter prints debug information about iptables rules to the
|
||||
// provided log function.
|
||||
func DebugIptables(logf logger.Logf) error {
|
||||
@@ -71,3 +84,153 @@ func detectIptables() (int, error) {
|
||||
// return the count of non-default rules
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// newIPTablesRunner constructs a NetfilterRunner that programs iptables rules.
|
||||
// If the underlying iptables library fails to initialize, that error is
|
||||
// returned. The runner probes for IPv6 support once at initialization time and
|
||||
// if not found, no IPv6 rules will be modified for the lifetime of the runner.
|
||||
func newIPTablesRunner(logf logger.Logf) (*iptablesRunner, error) {
|
||||
ipt4, err := iptables.NewWithProtocol(iptables.ProtocolIPv4)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
supportsV6, supportsV6NAT, supportsV6Filter := false, false, false
|
||||
v6err := CheckIPv6(logf)
|
||||
ip6terr := checkIP6TablesExists()
|
||||
var ipt6 *iptables.IPTables
|
||||
switch {
|
||||
case v6err != nil:
|
||||
logf("disabling tunneled IPv6 due to system IPv6 config: %v", v6err)
|
||||
case ip6terr != nil:
|
||||
logf("disabling tunneled IPv6 due to missing ip6tables: %v", ip6terr)
|
||||
default:
|
||||
supportsV6 = true
|
||||
ipt6, err = iptables.NewWithProtocol(iptables.ProtocolIPv6)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
supportsV6Filter = checkSupportsV6Filter(ipt6, logf)
|
||||
supportsV6NAT = checkSupportsV6NAT(ipt6, logf)
|
||||
logf("netfilter running in iptables mode v6 = %v, v6filter = %v, v6nat = %v", supportsV6, supportsV6Filter, supportsV6NAT)
|
||||
}
|
||||
return &iptablesRunner{
|
||||
ipt4: ipt4,
|
||||
ipt6: ipt6,
|
||||
v6Available: supportsV6,
|
||||
v6NATAvailable: supportsV6NAT,
|
||||
v6FilterAvailable: supportsV6Filter}, nil
|
||||
}
|
||||
|
||||
// checkSupportsV6Filter returns whether the system has a "filter" table in the
|
||||
// IPv6 tables. Some container environments such as GitHub codespaces have
|
||||
// limited local IPv6 support, and containers containing ip6tables, but do not
|
||||
// have kernel support for IPv6 filtering.
|
||||
// We will not set ip6tables rules in these instances.
|
||||
func checkSupportsV6Filter(ipt *iptables.IPTables, logf logger.Logf) bool {
|
||||
if ipt == nil {
|
||||
return false
|
||||
}
|
||||
_, filterListErr := ipt.ListChains("filter")
|
||||
if filterListErr == nil {
|
||||
return true
|
||||
}
|
||||
logf("ip6tables filtering is not supported on this host: %v", filterListErr)
|
||||
return false
|
||||
}
|
||||
|
||||
// checkSupportsV6NAT returns whether the system has a "nat" table in the
|
||||
// IPv6 netfilter stack.
|
||||
//
|
||||
// The nat table was added after the initial release of ipv6
|
||||
// netfilter, so some older distros ship a kernel that can't NAT IPv6
|
||||
// traffic.
|
||||
// ipt must be initialized for IPv6.
|
||||
func checkSupportsV6NAT(ipt *iptables.IPTables, logf logger.Logf) bool {
|
||||
if ipt == nil || ipt.Proto() != iptables.ProtocolIPv6 {
|
||||
return false
|
||||
}
|
||||
_, natListErr := ipt.ListChains("nat")
|
||||
if natListErr == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
// TODO (irbekrm): the following two checks were added before the check
|
||||
// above that verifies that nat chains can be listed. It is a
|
||||
// container-friendly check (see
|
||||
// https://github.com/tailscale/tailscale/issues/11344), but also should
|
||||
// be good enough on its own in other environments. If we never observe
|
||||
// it falsely succeed, let's remove the other two checks.
|
||||
|
||||
bs, err := os.ReadFile("/proc/net/ip6_tables_names")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if bytes.Contains(bs, []byte("nat\n")) {
|
||||
logf("[unexpected] listing nat chains failed, but /proc/net/ip6_tables_name reports a nat table existing")
|
||||
return true
|
||||
}
|
||||
if exec.Command("modprobe", "ip6table_nat").Run() == nil {
|
||||
logf("[unexpected] listing nat chains failed, but modprobe ip6table_nat succeeded")
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func init() {
|
||||
hookIPTablesCleanup.Set(ipTablesCleanUp)
|
||||
}
|
||||
|
||||
// ipTablesCleanUp removes all Tailscale added iptables rules.
|
||||
// Any errors that occur are logged to the provided logf.
|
||||
func ipTablesCleanUp(logf logger.Logf) {
|
||||
switch distro.Get() {
|
||||
case distro.Gokrazy, distro.JetKVM:
|
||||
// These use nftables and don't have the "iptables" command.
|
||||
// Avoid log spam on cleanup. (#12277)
|
||||
return
|
||||
}
|
||||
err := clearRules(iptables.ProtocolIPv4, logf)
|
||||
if err != nil {
|
||||
logf("linuxfw: clear iptables: %v", err)
|
||||
}
|
||||
|
||||
err = clearRules(iptables.ProtocolIPv6, logf)
|
||||
if err != nil {
|
||||
logf("linuxfw: clear ip6tables: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// clearRules clears all the iptables rules created by Tailscale
|
||||
// for the given protocol. If error occurs, it's logged but not returned.
|
||||
func clearRules(proto iptables.Protocol, logf logger.Logf) error {
|
||||
ipt, err := iptables.NewWithProtocol(proto)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var errs []error
|
||||
|
||||
if err := delTSHook(ipt, "filter", "INPUT", logf); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
if err := delTSHook(ipt, "filter", "FORWARD", logf); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
if err := delTSHook(ipt, "nat", "POSTROUTING", logf); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
if err := delChain(ipt, "filter", "ts-input"); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
if err := delChain(ipt, "filter", "ts-forward"); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
if err := delChain(ipt, "nat", "ts-postrouting"); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
return multierr.New(errs...)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user