diff --git a/internal/firewall/enable.go b/internal/firewall/enable.go index 37487174..5e006ad3 100644 --- a/internal/firewall/enable.go +++ b/internal/firewall/enable.go @@ -101,6 +101,9 @@ func (c *Config) enable(ctx context.Context) (err error) { if err := c.acceptOutputFromIPToSubnet(ctx, network.InterfaceName, network.IP, *network.IPNet, remove); err != nil { return err } + if err = c.acceptIpv6MulticastOutput(ctx, network.InterfaceName, remove); err != nil { + return err + } } if err = c.allowOutboundSubnets(ctx); err != nil { diff --git a/internal/firewall/iptables.go b/internal/firewall/iptables.go index 666568d0..44da2eb1 100644 --- a/internal/firewall/iptables.go +++ b/internal/firewall/iptables.go @@ -179,6 +179,18 @@ func (c *Config) acceptOutputFromIPToSubnet(ctx context.Context, return c.runIP6tablesInstruction(ctx, instruction) } +// NDP uses multicast address (theres no broadcast in IPv6 like ARP uses in IPv4). +func (c *Config) acceptIpv6MulticastOutput(ctx context.Context, + intf string, remove bool) error { + interfaceFlag := "-o " + intf + if intf == "*" { // all interfaces + interfaceFlag = "" + } + instruction := fmt.Sprintf("%s OUTPUT %s -d ff02::1:ff/104 -j ACCEPT", + appendOrDelete(remove), interfaceFlag) + return c.runIP6tablesInstruction(ctx, instruction) +} + // Used for port forwarding, with intf set to tun. func (c *Config) acceptInputToPort(ctx context.Context, intf string, port uint16, remove bool) error { interfaceFlag := "-i " + intf diff --git a/internal/routing/default.go b/internal/routing/default.go index 334eab5e..5cc3aca5 100644 --- a/internal/routing/default.go +++ b/internal/routing/default.go @@ -43,8 +43,11 @@ func (r *Routing) DefaultRoutes() (defaultRoutes []DefaultRoute, err error) { } attributes := link.Attrs() defaultRoute.NetInterface = attributes.Name - - defaultRoute.AssignedIP, err = r.assignedIP(defaultRoute.NetInterface) + family := netlink.FAMILY_V6 + if route.Gw.To4() != nil { + family = netlink.FAMILY_V4 + } + defaultRoute.AssignedIP, err = r.assignedIP(defaultRoute.NetInterface, family) if err != nil { return nil, fmt.Errorf("cannot get assigned IP of %s: %w", defaultRoute.NetInterface, err) } diff --git a/internal/routing/ip.go b/internal/routing/ip.go index 217d30d8..3bff7636 100644 --- a/internal/routing/ip.go +++ b/internal/routing/ip.go @@ -4,6 +4,8 @@ import ( "errors" "fmt" "net" + + "github.com/qdm12/gluetun/internal/netlink" ) func IPIsPrivate(ip net.IP) bool { @@ -15,7 +17,12 @@ var ( errInterfaceIPNotFound = errors.New("IP address not found for interface") ) -func (r *Routing) assignedIP(interfaceName string) (ip net.IP, err error) { +func ipMatchesFamily(ip net.IP, family int) bool { + return (family == netlink.FAMILY_V6 && ip.To4() == nil) || + (family == netlink.FAMILY_V4 && ip.To4() != nil) +} + +func (r *Routing) assignedIP(interfaceName string, family int) (ip net.IP, err error) { iface, err := net.InterfaceByName(interfaceName) if err != nil { return nil, fmt.Errorf("network interface %s not found: %w", interfaceName, err) @@ -27,9 +34,13 @@ func (r *Routing) assignedIP(interfaceName string) (ip net.IP, err error) { for _, address := range addresses { switch value := address.(type) { case *net.IPAddr: - return value.IP, nil + if ipMatchesFamily(value.IP, family) { + return value.IP, nil + } case *net.IPNet: - return value.IP, nil + if ipMatchesFamily(value.IP, family) { + return value.IP, nil + } } } return nil, fmt.Errorf("%w: interface %s in %d addresses", diff --git a/internal/routing/local.go b/internal/routing/local.go index b5d9e81e..be8afa9e 100644 --- a/internal/routing/local.go +++ b/internal/routing/local.go @@ -41,7 +41,7 @@ func (r *Routing) LocalNetworks() (localNetworks []LocalNetwork, err error) { return localNetworks, fmt.Errorf("%w: in %d links", ErrLinkLocalNotFound, len(links)) } - routes, err := r.netLinker.RouteList(nil, netlink.FAMILY_V4) + routes, err := r.netLinker.RouteList(nil, netlink.FAMILY_ALL) if err != nil { return localNetworks, fmt.Errorf("cannot list routes: %w", err) } @@ -65,7 +65,11 @@ func (r *Routing) LocalNetworks() (localNetworks []LocalNetwork, err error) { localNet.InterfaceName = link.Attrs().Name - ip, err := r.assignedIP(localNet.InterfaceName) + family := netlink.FAMILY_V6 + if localNet.IPNet.IP.To4() != nil { + family = netlink.FAMILY_V4 + } + ip, err := r.assignedIP(localNet.InterfaceName, family) if err != nil { return localNetworks, err } diff --git a/internal/wireguard/netlink_integration_test.go b/internal/wireguard/netlink_integration_test.go index 9604e623..c06114c4 100644 --- a/internal/wireguard/netlink_integration_test.go +++ b/internal/wireguard/netlink_integration_test.go @@ -10,6 +10,7 @@ import ( "github.com/qdm12/gluetun/internal/netlink" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "golang.org/x/sys/unix" ) func Test_netlink_Wireguard_addAddresses(t *testing.T) { @@ -75,15 +76,17 @@ func Test_netlink_Wireguard_addRule(t *testing.T) { rulePriority := 10000 const firewallMark = 999 + const family = unix.AF_INET // ipv4 - cleanup, err := wg.addRule(rulePriority, firewallMark) + cleanup, err := wg.addRule(rulePriority, + firewallMark, family) require.NoError(t, err) defer func() { err := cleanup() assert.NoError(t, err) }() - rules, err := netlinker.RuleList(netlink.FAMILY_ALL) + rules, err := netlinker.RuleList(netlink.FAMILY_V4) require.NoError(t, err) var rule netlink.Rule var ruleFound bool @@ -108,7 +111,8 @@ func Test_netlink_Wireguard_addRule(t *testing.T) { assert.Equal(t, expectedRule, rule) // Existing rule cannot be added - nilCleanup, err := wg.addRule(rulePriority, firewallMark) + nilCleanup, err := wg.addRule(rulePriority, + firewallMark, family) if nilCleanup != nil { _ = nilCleanup() // in case it succeeds } diff --git a/internal/wireguard/rule.go b/internal/wireguard/rule.go index 9e2f41f8..e4c52bb9 100644 --- a/internal/wireguard/rule.go +++ b/internal/wireguard/rule.go @@ -6,13 +6,14 @@ import ( "github.com/qdm12/gluetun/internal/netlink" ) -func (w *Wireguard) addRule(rulePriority, firewallMark int) ( +func (w *Wireguard) addRule(rulePriority, firewallMark, family int) ( cleanup func() error, err error) { rule := netlink.NewRule() rule.Invert = true rule.Priority = rulePriority rule.Mark = firewallMark rule.Table = firewallMark + rule.Family = family if err := w.netlink.RuleAdd(rule); err != nil { return nil, fmt.Errorf("cannot add rule %s: %w", rule, err) } diff --git a/internal/wireguard/rule_test.go b/internal/wireguard/rule_test.go index ed408df1..9a438ca3 100644 --- a/internal/wireguard/rule_test.go +++ b/internal/wireguard/rule_test.go @@ -8,6 +8,7 @@ import ( "github.com/qdm12/gluetun/internal/netlink" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "golang.org/x/sys/unix" ) func Test_Wireguard_addRule(t *testing.T) { @@ -15,6 +16,7 @@ func Test_Wireguard_addRule(t *testing.T) { const rulePriority = 987 const firewallMark = 456 + const family = unix.AF_INET errDummy := errors.New("dummy") @@ -36,6 +38,7 @@ func Test_Wireguard_addRule(t *testing.T) { Flow: -1, SuppressIfgroup: -1, SuppressPrefixlen: -1, + Family: family, }, }, "rule add error": { @@ -49,6 +52,7 @@ func Test_Wireguard_addRule(t *testing.T) { Flow: -1, SuppressIfgroup: -1, SuppressPrefixlen: -1, + Family: family, }, ruleAddErr: errDummy, err: errors.New("cannot add rule ip rule 987: from all to all table 456: dummy"), @@ -64,6 +68,7 @@ func Test_Wireguard_addRule(t *testing.T) { Flow: -1, SuppressIfgroup: -1, SuppressPrefixlen: -1, + Family: family, }, ruleDelErr: errDummy, cleanupErr: errors.New("cannot delete rule ip rule 987: from all to all table 456: dummy"), @@ -83,7 +88,7 @@ func Test_Wireguard_addRule(t *testing.T) { netLinker.EXPECT().RuleAdd(testCase.expectedRule). Return(testCase.ruleAddErr) - cleanup, err := wg.addRule(rulePriority, firewallMark) + cleanup, err := wg.addRule(rulePriority, firewallMark, family) if testCase.err != nil { require.Error(t, err) assert.Equal(t, testCase.err.Error(), err.Error()) diff --git a/internal/wireguard/run.go b/internal/wireguard/run.go index 4743ded2..8060f835 100644 --- a/internal/wireguard/run.go +++ b/internal/wireguard/run.go @@ -8,6 +8,7 @@ import ( "strings" "github.com/qdm12/gluetun/internal/netlink" + "golang.org/x/sys/unix" "golang.zx2c4.com/wireguard/conn" "golang.zx2c4.com/wireguard/device" "golang.zx2c4.com/wireguard/ipc" @@ -29,7 +30,6 @@ var ( ErrDeviceInfo = errors.New("cannot get wireguard device information") ErrIfaceUp = errors.New("cannot set the interface to UP") ErrRouteAdd = errors.New("cannot add route for interface") - ErrRuleAdd = errors.New("cannot add rule for interface") ErrDeviceWaited = errors.New("device waited for") ) @@ -96,34 +96,52 @@ func (w *Wireguard) Run(ctx context.Context, waitError chan<- error, ready chan< if *w.settings.IPv6 { // requires net.ipv6.conf.all.disable_ipv6=0 - err = w.addRoute(link, allIPv6(), w.settings.FirewallMark) + err = w.setupIPv6(link, &closers) if err != nil { - if strings.Contains(err.Error(), "permission denied") { - w.logger.Errorf("cannot add route for IPv6 due to a permission denial. "+ - "Ignoring and continuing execution; "+ - "Please report to https://github.com/qdm12/gluetun/issues/998 if you find a fix. "+ - "Full error string: %s", err) - } else { - waitError <- fmt.Errorf("%w: %s", ErrRouteAdd, err) - return - } + waitError <- fmt.Errorf("setting up IPv6: %w", err) + return } } - ruleCleanup, err := w.addRule( - w.settings.RulePriority, w.settings.FirewallMark) + ruleCleanup, err := w.addRule(w.settings.RulePriority, + w.settings.FirewallMark, unix.AF_INET) if err != nil { - waitError <- fmt.Errorf("%w: %s", ErrRuleAdd, err) + waitError <- fmt.Errorf("adding IPv4 rule: %w", err) return } - closers.add("removing rule", stepOne, ruleCleanup) + closers.add("removing IPv4 rule", stepOne, ruleCleanup) w.logger.Info("Wireguard is up") ready <- struct{}{} waitError <- waitAndCleanup() } +func (w *Wireguard) setupIPv6(link netlink.Link, closers *closers) (err error) { + // requires net.ipv6.conf.all.disable_ipv6=0 + err = w.addRoute(link, allIPv6(), w.settings.FirewallMark) + if err != nil { + if strings.Contains(err.Error(), "permission denied") { + w.logger.Errorf("cannot add route for IPv6 due to a permission denial. "+ + "Ignoring and continuing execution; "+ + "Please report to https://github.com/qdm12/gluetun/issues/998 if you find a fix. "+ + "Full error string: %s", err) + return nil + } + return fmt.Errorf("%w: %s", ErrRouteAdd, err) + } + + ruleCleanup6, ruleErr := w.addRule( + w.settings.RulePriority, w.settings.FirewallMark, + unix.AF_INET6) + if ruleErr != nil { + return fmt.Errorf("adding IPv6 rule: %w", err) + } + + closers.add("removing IPv6 rule", stepOne, ruleCleanup6) + return nil +} + type waitAndCleanupFunc func() error func setupKernelSpace(ctx context.Context,