package routing import ( "fmt" "net/netip" "github.com/qdm12/gluetun/internal/netlink" "github.com/qdm12/gluetun/internal/subnet" ) const ( outboundTable = 199 outboundPriority = 99 ) func (r *Routing) SetOutboundRoutes(outboundSubnets []netip.Prefix) error { defaultRoutes, err := r.DefaultRoutes() if err != nil { return err } return r.setOutboundRoutes(outboundSubnets, defaultRoutes) } func (r *Routing) setOutboundRoutes(outboundSubnets []netip.Prefix, defaultRoutes []DefaultRoute, ) (err error) { r.stateMutex.Lock() defer r.stateMutex.Unlock() subnetsToAdd, subnetsToRemove := subnet.FindSubnetsToChange( r.outboundSubnets, outboundSubnets) if len(subnetsToAdd) == 0 && len(subnetsToRemove) == 0 { return nil } warnings := r.removeOutboundSubnets(subnetsToRemove, defaultRoutes) for _, warning := range warnings { r.logger.Warn("cannot remove outdated outbound subnet from routing: " + warning) } err = r.addOutboundSubnets(subnetsToAdd, defaultRoutes) if err != nil { return fmt.Errorf("adding outbound subnet to routes: %w", err) } return nil } func (r *Routing) removeOutboundSubnets(subnets []netip.Prefix, defaultRoutes []DefaultRoute, ) (warnings []string) { for i, subNet := range subnets { for _, defaultRoute := range defaultRoutes { err := r.deleteRouteVia(subNet, defaultRoute.Gateway, defaultRoute.NetInterface, outboundTable) if err != nil { warnings = append(warnings, err.Error()) continue } } ruleSrcNet := netip.Prefix{} ruleDstNet := subnets[i] err := r.deleteIPRule(ruleSrcNet, ruleDstNet, outboundTable, outboundPriority) if err != nil { warnings = append(warnings, "cannot delete rule: for subnet "+subNet.String()+": "+err.Error()) continue } r.outboundSubnets = subnet.RemoveSubnetFromSubnets(r.outboundSubnets, subNet) } return warnings } func (r *Routing) addOutboundSubnets(subnets []netip.Prefix, defaultRoutes []DefaultRoute, ) (err error) { for i, subnet := range subnets { subnetIsIPv6 := subnet.Addr().Is6() subnetRouteAdded := false for _, defaultRoute := range defaultRoutes { defaultRouteIsIPv6 := defaultRoute.Family == netlink.FamilyV6 ipFamilyMatch := subnetIsIPv6 == defaultRouteIsIPv6 if !ipFamilyMatch { continue } err = r.addRouteVia(subnet, defaultRoute.Gateway, defaultRoute.NetInterface, outboundTable) if err != nil { return fmt.Errorf("adding route for subnet %s: %w", subnet, err) } subnetRouteAdded = true } if !subnetRouteAdded { continue } ruleSrcNet := netip.Prefix{} ruleDstNet := subnets[i] err = r.addIPRule(ruleSrcNet, ruleDstNet, outboundTable, outboundPriority) if err != nil { return fmt.Errorf("adding rule: for subnet %s: %w", subnet, err) } r.outboundSubnets = append(r.outboundSubnets, subnet) } return nil }