|
|
|
|
@ -6,8 +6,10 @@ |
|
|
|
|
package linuxfw |
|
|
|
|
|
|
|
|
|
import ( |
|
|
|
|
"bytes" |
|
|
|
|
"fmt" |
|
|
|
|
"net/netip" |
|
|
|
|
"os" |
|
|
|
|
"os/exec" |
|
|
|
|
"strconv" |
|
|
|
|
"strings" |
|
|
|
|
@ -59,6 +61,7 @@ func newIPTablesRunner(logf logger.Logf) (*iptablesRunner, error) { |
|
|
|
|
supportsV6, supportsV6NAT := 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) |
|
|
|
|
@ -66,20 +69,54 @@ func newIPTablesRunner(logf logger.Logf) (*iptablesRunner, error) { |
|
|
|
|
logf("disabling tunneled IPv6 due to missing ip6tables: %v", ip6terr) |
|
|
|
|
default: |
|
|
|
|
supportsV6 = true |
|
|
|
|
supportsV6NAT = supportsV6 && checkSupportsV6NAT() |
|
|
|
|
logf("v6nat = %v", supportsV6NAT) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var ipt6 *iptables.IPTables |
|
|
|
|
if supportsV6 { |
|
|
|
|
ipt6, err = iptables.NewWithProtocol(iptables.ProtocolIPv6) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
supportsV6NAT = checkSupportsV6NAT(ipt6, logf) |
|
|
|
|
logf("v6nat = %v", supportsV6NAT) |
|
|
|
|
} |
|
|
|
|
return &iptablesRunner{ipt4, ipt6, supportsV6, supportsV6NAT}, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 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 |
|
|
|
|
|