|
|
@@ -6,31 +6,22 @@
|
|
|
package linuxfw
|
|
|
|
|
|
import (
|
|
|
- "bytes"
|
|
|
- "errors"
|
|
|
"fmt"
|
|
|
"log"
|
|
|
"net/netip"
|
|
|
- "os"
|
|
|
"os/exec"
|
|
|
"slices"
|
|
|
"strconv"
|
|
|
"strings"
|
|
|
|
|
|
- "github.com/coreos/go-iptables/iptables"
|
|
|
"tailscale.com/net/tsaddr"
|
|
|
"tailscale.com/types/logger"
|
|
|
- "tailscale.com/util/multierr"
|
|
|
- "tailscale.com/version/distro"
|
|
|
)
|
|
|
|
|
|
// isNotExistError needs to be overridden in tests that rely on distinguishing
|
|
|
// this error, because we don't have a good way how to create a new
|
|
|
// iptables.Error of that type.
|
|
|
-var isNotExistError = func(err error) bool {
|
|
|
- var e *iptables.Error
|
|
|
- return errors.As(err, &e) && e.IsNotExist()
|
|
|
-}
|
|
|
+var isNotExistError = func(err error) bool { return false }
|
|
|
|
|
|
type iptablesInterface interface {
|
|
|
// Adding this interface for testing purposes so we can mock out
|
|
|
@@ -62,98 +53,6 @@ func checkIP6TablesExists() error {
|
|
|
return 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
|
|
|
-}
|
|
|
-
|
|
|
// HasIPV6 reports true if the system supports IPv6.
|
|
|
func (i *iptablesRunner) HasIPV6() bool {
|
|
|
return i.v6Available
|
|
|
@@ -685,26 +584,6 @@ func (i *iptablesRunner) DelMagicsockPortRule(port uint16, network string) error
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
-// 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)
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
// delTSHook deletes hook in a chain that jumps to a ts-chain. If the hook does not
|
|
|
// exist, it's a no-op since the desired state is already achieved but we log the
|
|
|
// error because error code from the iptables module resists unwrapping.
|
|
|
@@ -733,40 +612,6 @@ func delChain(ipt iptablesInterface, table, chain string) error {
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
-// 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...)
|
|
|
-}
|
|
|
-
|
|
|
// argsFromPostRoutingRule accepts a rule as returned by iptables.List and, if it is a rule from POSTROUTING chain,
|
|
|
// returns the args part, else returns the original rule.
|
|
|
func argsFromPostRoutingRule(r string) string {
|