| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183 |
- // Copyright (c) Tailscale Inc & contributors
- // SPDX-License-Identifier: BSD-3-Clause
- //go:build linux
- // Package linuxfw returns the kind of firewall being used by the kernel.
- package linuxfw
- import (
- "errors"
- "fmt"
- "os"
- "strconv"
- "strings"
- "github.com/tailscale/netlink"
- "tailscale.com/feature"
- "tailscale.com/tsconst"
- "tailscale.com/types/logger"
- )
- // MatchDecision is the decision made by the firewall for a packet matched by a rule.
- // It is used to decide whether to accept or masquerade a packet in addMatchSubnetRouteMarkRule.
- type MatchDecision int
- const (
- Accept MatchDecision = iota
- Masq
- )
- type FWModeNotSupportedError struct {
- Mode FirewallMode
- Err error
- }
- func (e FWModeNotSupportedError) Error() string {
- return fmt.Sprintf("firewall mode %q not supported: %v", e.Mode, e.Err)
- }
- func (e FWModeNotSupportedError) Is(target error) bool {
- _, ok := target.(FWModeNotSupportedError)
- return ok
- }
- func (e FWModeNotSupportedError) Unwrap() error {
- return e.Err
- }
- type FirewallMode string
- const (
- FirewallModeIPTables FirewallMode = "iptables"
- FirewallModeNfTables FirewallMode = "nftables"
- )
- // The following bits are added to packet marks for Tailscale use.
- //
- // We tried to pick bits sufficiently out of the way that it's
- // unlikely to collide with existing uses. We have 4 bytes of mark
- // bits to play with. We leave the lower byte alone on the assumption
- // that sysadmins would use those. Kubernetes uses a few bits in the
- // second byte, so we steer clear of that too.
- //
- // Empirically, most of the documentation on packet marks on the
- // internet gives the impression that the marks are 16 bits
- // wide. Based on this, we theorize that the upper two bytes are
- // relatively unused in the wild, and so we consume bits 16:23 (the
- // third byte).
- //
- // The constants are in the iptables/iproute2 string format for
- // matching and setting the bits, so they can be directly embedded in
- // commands.
- const (
- fwmarkMask = tsconst.LinuxFwmarkMask
- fwmarkMaskNum = tsconst.LinuxFwmarkMaskNum
- subnetRouteMark = tsconst.LinuxSubnetRouteMark
- subnetRouteMarkNum = tsconst.LinuxSubnetRouteMarkNum
- bypassMark = tsconst.LinuxBypassMark
- bypassMarkNum = tsconst.LinuxBypassMarkNum
- )
- // getTailscaleFwmarkMaskNeg returns the negation of TailscaleFwmarkMask in bytes.
- func getTailscaleFwmarkMaskNeg() []byte {
- return []byte{0xff, 0x00, 0xff, 0xff}
- }
- // getTailscaleFwmarkMask returns the TailscaleFwmarkMask in bytes.
- func getTailscaleFwmarkMask() []byte {
- return []byte{0x00, 0xff, 0x00, 0x00}
- }
- // getTailscaleSubnetRouteMark returns the TailscaleSubnetRouteMark in bytes.
- func getTailscaleSubnetRouteMark() []byte {
- return []byte{0x00, 0x04, 0x00, 0x00}
- }
- // checkIPv6ForTest can be set in tests.
- var checkIPv6ForTest func(logger.Logf) error
- // checkIPv6 checks whether the system appears to have a working IPv6
- // network stack. It returns an error explaining what looks wrong or
- // missing. It does not check that IPv6 is currently functional or
- // that there's a global address, just that the system would support
- // IPv6 if it were on an IPv6 network.
- func CheckIPv6(logf logger.Logf) error {
- if f := checkIPv6ForTest; f != nil {
- return f(logf)
- }
- _, err := os.Stat("/proc/sys/net/ipv6")
- if os.IsNotExist(err) {
- return err
- }
- bs, err := os.ReadFile("/proc/sys/net/ipv6/conf/all/disable_ipv6")
- if err != nil {
- // Be conservative if we can't find the IPv6 configuration knob.
- return err
- }
- disabled, err := strconv.ParseBool(strings.TrimSpace(string(bs)))
- if err != nil {
- return errors.New("disable_ipv6 has invalid bool")
- }
- if disabled {
- return errors.New("disable_ipv6 is set")
- }
- // Older kernels don't support IPv6 policy routing. Some kernels
- // support policy routing but don't have this knob, so absence of
- // the knob is not fatal.
- bs, err = os.ReadFile("/proc/sys/net/ipv6/conf/all/disable_policy")
- if err == nil {
- disabled, err = strconv.ParseBool(strings.TrimSpace(string(bs)))
- if err != nil {
- return errors.New("disable_policy has invalid bool")
- }
- if disabled {
- return errors.New("disable_policy is set")
- }
- }
- if err := CheckIPRuleSupportsV6(logf); err != nil {
- return fmt.Errorf("kernel doesn't support IPv6 policy routing: %w", err)
- }
- return nil
- }
- func CheckIPRuleSupportsV6(logf logger.Logf) error {
- // First try just a read-only operation to ideally avoid
- // having to modify any state.
- if rules, err := netlink.RuleList(netlink.FAMILY_V6); err != nil {
- return fmt.Errorf("querying IPv6 policy routing rules: %w", err)
- } else {
- if len(rules) > 0 {
- logf("[v1] kernel supports IPv6 policy routing (found %d rules)", len(rules))
- return nil
- }
- }
- // Try to actually create & delete one as a test.
- rule := netlink.NewRule()
- rule.Priority = 1234
- rule.Mark = bypassMarkNum
- rule.Table = 52
- rule.Family = netlink.FAMILY_V6
- // First delete the rule unconditionally, and don't check for
- // errors. This is just cleaning up anything that might be already
- // there.
- netlink.RuleDel(rule)
- // And clean up on exit.
- defer netlink.RuleDel(rule)
- return netlink.RuleAdd(rule)
- }
- var hookIPTablesCleanup feature.Hook[func(logger.Logf)]
- // IPTablesCleanUp removes all Tailscale added iptables rules.
- // Any errors that occur are logged to the provided logf.
- func IPTablesCleanUp(logf logger.Logf) {
- if f, ok := hookIPTablesCleanup.GetOk(); ok {
- f(logf)
- }
- }
|