From 810ff62c2646bd8cb1327d640ed9f03e0c5e450a Mon Sep 17 00:00:00 2001 From: Quentin McGaw Date: Mon, 10 May 2021 17:33:31 +0000 Subject: [PATCH] Maintenance: improve error codes in IP routing --- cmd/gluetun/main.go | 9 +++- internal/routing/enable.go | 37 +++++++++------- internal/routing/mutate.go | 31 +++++++++---- internal/routing/outboundsubnets.go | 11 +++-- internal/routing/reader.go | 68 +++++++++++++++++++---------- 5 files changed, 103 insertions(+), 53 deletions(-) diff --git a/cmd/gluetun/main.go b/cmd/gluetun/main.go index d868cea2..6f14549a 100644 --- a/cmd/gluetun/main.go +++ b/cmd/gluetun/main.go @@ -2,6 +2,7 @@ package main import ( "context" + "errors" "fmt" "net" "net/http" @@ -45,6 +46,10 @@ var ( buildDate = "an unknown date" ) +var ( + errSetupRouting = errors.New("cannot setup routing") +) + func main() { buildInfo := models.BuildInformation{ Version: version, @@ -204,12 +209,12 @@ func _main(ctx context.Context, buildInfo models.BuildInformation, firewallConf.SetNetworkInformation(defaultInterface, defaultGateway, localNetworks, defaultIP) if err := routingConf.Setup(); err != nil { - return err + return fmt.Errorf("%w: %s", errSetupRouting, err) } defer func() { routingConf.SetVerbose(false) if err := routingConf.TearDown(); err != nil { - logger.Error(err) + logger.Error("cannot teardown routing: " + err.Error()) } }() diff --git a/internal/routing/enable.go b/internal/routing/enable.go index b116d52b..ad3d1ab9 100644 --- a/internal/routing/enable.go +++ b/internal/routing/enable.go @@ -1,28 +1,33 @@ package routing import ( + "errors" "fmt" "net" ) -var ( - ErrSetup = fmt.Errorf("cannot setup routing") - ErrTeardown = fmt.Errorf("cannot teardown routing") -) - const ( table = 200 priority = 100 ) +var ( + ErrDefaultIP = errors.New("cannot get default IP address") + ErrDefaultRoute = errors.New("cannot get default route") + ErrIPRuleAdd = errors.New("cannot add IP rule") + ErrIPRuleDelete = errors.New("cannot delete IP rule") + ErrRouteAdd = errors.New("cannot add route") + ErrSubnetsOutboundSet = errors.New("cannot set outbound subnets routes") +) + func (r *routing) Setup() (err error) { defaultIP, err := r.DefaultIP() if err != nil { - return fmt.Errorf("%s: %w", ErrSetup, err) + return fmt.Errorf("%w: %s", ErrDefaultIP, err) } defaultInterfaceName, defaultGateway, err := r.DefaultRoute() if err != nil { - return fmt.Errorf("%s: %w", ErrSetup, err) + return fmt.Errorf("%w: %s", ErrDefaultRoute, err) } defer func() { @@ -30,22 +35,22 @@ func (r *routing) Setup() (err error) { return } if err := r.TearDown(); err != nil { - r.logger.Error(err) + r.logger.Error("cannot reverse routing changes: " + err.Error()) } }() if err := r.addIPRule(defaultIP, table, priority); err != nil { - return fmt.Errorf("%s: %w", ErrSetup, err) + return fmt.Errorf("%w: %s", ErrIPRuleAdd, err) } defaultDestination := net.IPNet{IP: net.IPv4(0, 0, 0, 0), Mask: net.IPv4Mask(0, 0, 0, 0)} if err := r.addRouteVia(defaultDestination, defaultGateway, defaultInterfaceName, table); err != nil { - return fmt.Errorf("%s: %w", ErrSetup, err) + return fmt.Errorf("%w: %s", ErrRouteAdd, err) } r.stateMutex.RLock() outboundSubnets := r.outboundSubnets r.stateMutex.RUnlock() if err := r.setOutboundRoutes(outboundSubnets, defaultInterfaceName, defaultGateway); err != nil { - return fmt.Errorf("%s: %w", ErrSetup, err) + return fmt.Errorf("%w: %s", ErrSubnetsOutboundSet, err) } return nil @@ -54,23 +59,23 @@ func (r *routing) Setup() (err error) { func (r *routing) TearDown() error { defaultIP, err := r.DefaultIP() if err != nil { - return fmt.Errorf("%s: %w", ErrTeardown, err) + return fmt.Errorf("%w: %s", ErrDefaultIP, err) } defaultInterfaceName, defaultGateway, err := r.DefaultRoute() if err != nil { - return fmt.Errorf("%s: %w", ErrTeardown, err) + return fmt.Errorf("%w: %s", ErrDefaultRoute, err) } defaultNet := net.IPNet{IP: net.IPv4(0, 0, 0, 0), Mask: net.IPv4Mask(0, 0, 0, 0)} if err := r.deleteRouteVia(defaultNet, defaultGateway, defaultInterfaceName, table); err != nil { - return fmt.Errorf("%s: %w", ErrTeardown, err) + return fmt.Errorf("%w: %s", ErrRouteDelete, err) } if err := r.deleteIPRule(defaultIP, table, priority); err != nil { - return fmt.Errorf("%s: %w", ErrTeardown, err) + return fmt.Errorf("%w: %s", ErrIPRuleDelete, err) } if err := r.setOutboundRoutes(nil, defaultInterfaceName, defaultGateway); err != nil { - return fmt.Errorf("%s: %w", ErrSetup, err) + return fmt.Errorf("%w: %s", ErrSubnetsOutboundSet, err) } return nil diff --git a/internal/routing/mutate.go b/internal/routing/mutate.go index 67fa1c48..08db4ac9 100644 --- a/internal/routing/mutate.go +++ b/internal/routing/mutate.go @@ -2,12 +2,20 @@ package routing import ( "bytes" + "errors" "fmt" "net" "github.com/vishvananda/netlink" ) +var ( + ErrRouteReplace = errors.New("cannot replace route") + ErrRouteDelete = errors.New("cannot delete route") + ErrRuleAdd = errors.New("cannot add routing rule") + ErrRuleDel = errors.New("cannot delete routing rule") +) + func (r *routing) addRouteVia(destination net.IPNet, gateway net.IP, iface string, table int) error { destinationStr := destination.String() if r.verbose { @@ -19,7 +27,7 @@ func (r *routing) addRouteVia(destination net.IPNet, gateway net.IP, iface strin link, err := netlink.LinkByName(iface) if err != nil { - return fmt.Errorf("cannot add route for %s: %w", destinationStr, err) + return fmt.Errorf("%w: interface %s: %s", ErrLinkByName, iface, err) } route := netlink.Route{ Dst: &destination, @@ -28,7 +36,8 @@ func (r *routing) addRouteVia(destination net.IPNet, gateway net.IP, iface strin Table: table, } if err := netlink.RouteReplace(&route); err != nil { - return fmt.Errorf("cannot add route for %s: %w", destinationStr, err) + return fmt.Errorf("%w: for subnet %s at interface %s: %s", + ErrRouteReplace, destinationStr, iface, err) } return nil } @@ -44,7 +53,7 @@ func (r *routing) deleteRouteVia(destination net.IPNet, gateway net.IP, iface st link, err := netlink.LinkByName(iface) if err != nil { - return fmt.Errorf("cannot delete route for %s: %w", destinationStr, err) + return fmt.Errorf("%w: for interface %s: %s", ErrLinkByName, iface, err) } route := netlink.Route{ Dst: &destination, @@ -53,7 +62,8 @@ func (r *routing) deleteRouteVia(destination net.IPNet, gateway net.IP, iface st Table: table, } if err := netlink.RouteDel(&route); err != nil { - return fmt.Errorf("cannot delete route for %s: %w", destinationStr, err) + return fmt.Errorf("%w: for subnet %s at interface %s: %s", + ErrRouteDelete, destinationStr, iface, err) } return nil } @@ -71,7 +81,7 @@ func (r *routing) addIPRule(src net.IP, table, priority int) error { rules, err := netlink.RuleList(netlink.FAMILY_ALL) if err != nil { - return fmt.Errorf("cannot add ip rule: %w", err) + return fmt.Errorf("%w: %s", ErrRulesList, err) } for _, existingRule := range rules { if existingRule.Src != nil && @@ -83,7 +93,10 @@ func (r *routing) addIPRule(src net.IP, table, priority int) error { } } - return netlink.RuleAdd(rule) + if err := netlink.RuleAdd(rule); err != nil { + return fmt.Errorf("%w: for rule %q: %s", ErrRuleAdd, rule, err) + } + return nil } func (r *routing) deleteIPRule(src net.IP, table, priority int) error { @@ -99,7 +112,7 @@ func (r *routing) deleteIPRule(src net.IP, table, priority int) error { rules, err := netlink.RuleList(netlink.FAMILY_ALL) if err != nil { - return fmt.Errorf("cannot add ip rule: %w", err) + return fmt.Errorf("%w: %s", ErrRulesList, err) } for _, existingRule := range rules { if existingRule.Src != nil && @@ -107,7 +120,9 @@ func (r *routing) deleteIPRule(src net.IP, table, priority int) error { bytes.Equal(existingRule.Src.Mask, rule.Src.Mask) && existingRule.Priority == rule.Priority && existingRule.Table == rule.Table { - return netlink.RuleDel(rule) + if err := netlink.RuleDel(rule); err != nil { + return fmt.Errorf("%w: for rule %q: %s", ErrRuleDel, rule, err) + } } } return nil diff --git a/internal/routing/outboundsubnets.go b/internal/routing/outboundsubnets.go index 3ce30756..19795e48 100644 --- a/internal/routing/outboundsubnets.go +++ b/internal/routing/outboundsubnets.go @@ -1,14 +1,19 @@ package routing import ( + "errors" "fmt" "net" ) +var ( + ErrAddOutboundSubnet = errors.New("cannot add outbound subnet to routes") +) + func (r *routing) SetOutboundRoutes(outboundSubnets []net.IPNet) error { defaultInterface, defaultGateway, err := r.DefaultRoute() if err != nil { - return fmt.Errorf("cannot set oubtound subnets in routing: %w", err) + return err } return r.setOutboundRoutes(outboundSubnets, defaultInterface, defaultGateway) } @@ -27,7 +32,7 @@ func (r *routing) setOutboundRoutes(outboundSubnets []net.IPNet, r.removeOutboundSubnets(subnetsToRemove, defaultInterfaceName, defaultGateway) if err := r.addOutboundSubnets(subnetsToAdd, defaultInterfaceName, defaultGateway); err != nil { - return fmt.Errorf("cannot set outbound subnets in routing: %w", err) + return err } return nil @@ -50,7 +55,7 @@ func (r *routing) addOutboundSubnets(subnets []net.IPNet, for _, subnet := range subnets { const table = 0 if err := r.addRouteVia(subnet, defaultGateway, defaultInterfaceName, table); err != nil { - return fmt.Errorf("cannot add outbound subnet %s to routing: %w", subnet, err) + return fmt.Errorf("%w: %s: %s", ErrAddOutboundSubnet, subnet, err) } r.outboundSubnets = append(r.outboundSubnets, subnet) } diff --git a/internal/routing/reader.go b/internal/routing/reader.go index 60d80226..18b6062b 100644 --- a/internal/routing/reader.go +++ b/internal/routing/reader.go @@ -2,6 +2,7 @@ package routing import ( "bytes" + "errors" "fmt" "net" @@ -15,10 +16,28 @@ type LocalNetwork struct { IP net.IP } +var ( + ErrInterfaceIPNotFound = errors.New("IP address not found for interface") + ErrInterfaceListAddr = errors.New("cannot list interface addresses") + ErrInterfaceNotFound = errors.New("network interface not found") + ErrLinkByIndex = errors.New("cannot obtain link by index") + ErrLinkByName = errors.New("cannot obtain link by name") + ErrLinkDefaultNotFound = errors.New("default link not found") + ErrLinkList = errors.New("cannot list links") + ErrLinkLocalNotFound = errors.New("local link not found") + ErrRouteDefaultNotFound = errors.New("default route not found") + ErrRoutesList = errors.New("cannot list routes") + ErrRulesList = errors.New("cannot list rules") + ErrSubnetDefaultNotFound = errors.New("default subnet not found") + ErrSubnetLocalNotFound = errors.New("local subnet not found") + ErrVPNDestinationIPNotFound = errors.New("VPN destination IP address not found") + ErrVPNLocalGatewayIPNotFound = errors.New("VPN local gateway IP address not found") +) + func (r *routing) DefaultRoute() (defaultInterface string, defaultGateway net.IP, err error) { routes, err := netlink.RouteList(nil, netlink.FAMILY_ALL) if err != nil { - return "", nil, fmt.Errorf("cannot list routes: %w", err) + return "", nil, fmt.Errorf("%w: %s", ErrRoutesList, err) } for _, route := range routes { if route.Dst == nil { @@ -26,7 +45,7 @@ func (r *routing) DefaultRoute() (defaultInterface string, defaultGateway net.IP linkIndex := route.LinkIndex link, err := netlink.LinkByIndex(linkIndex) if err != nil { - return "", nil, fmt.Errorf("cannot obtain link with index %d for default route: %w", linkIndex, err) + return "", nil, fmt.Errorf("%w: for default route at index %d: %s", ErrLinkByIndex, linkIndex, err) } attributes := link.Attrs() defaultInterface = attributes.Name @@ -36,13 +55,13 @@ func (r *routing) DefaultRoute() (defaultInterface string, defaultGateway net.IP return defaultInterface, defaultGateway, nil } } - return "", nil, fmt.Errorf("cannot find default route in %d routes", len(routes)) + return "", nil, fmt.Errorf("%w: in %d route(s)", ErrRouteDefaultNotFound, len(routes)) } func (r *routing) DefaultIP() (ip net.IP, err error) { routes, err := netlink.RouteList(nil, netlink.FAMILY_ALL) if err != nil { - return nil, fmt.Errorf("cannot get default IP address: %w", err) + return nil, fmt.Errorf("%w: %s", ErrRoutesList, err) } defaultLinkName := "" @@ -51,13 +70,13 @@ func (r *routing) DefaultIP() (ip net.IP, err error) { linkIndex := route.LinkIndex link, err := netlink.LinkByIndex(linkIndex) if err != nil { - return nil, fmt.Errorf("cannot get default IP address: %w", err) + return nil, fmt.Errorf("%w: for default route at index %d: %s", ErrLinkByIndex, linkIndex, err) } defaultLinkName = link.Attrs().Name } } if len(defaultLinkName) == 0 { - return nil, fmt.Errorf("cannot find default link name in %d routes", len(routes)) + return nil, fmt.Errorf("%w: in %d route(s)", ErrLinkDefaultNotFound, len(routes)) } return r.assignedIP(defaultLinkName) @@ -66,7 +85,7 @@ func (r *routing) DefaultIP() (ip net.IP, err error) { func (r *routing) LocalSubnet() (defaultSubnet net.IPNet, err error) { routes, err := netlink.RouteList(nil, netlink.FAMILY_ALL) if err != nil { - return defaultSubnet, fmt.Errorf("cannot find local subnet: %w", err) + return defaultSubnet, fmt.Errorf("%w: %s", ErrRoutesList, err) } defaultLinkIndex := -1 @@ -77,7 +96,7 @@ func (r *routing) LocalSubnet() (defaultSubnet net.IPNet, err error) { } } if defaultLinkIndex == -1 { - return defaultSubnet, fmt.Errorf("cannot find local subnet: cannot find default link") + return defaultSubnet, fmt.Errorf("%w: in %d route(s)", ErrLinkDefaultNotFound, len(routes)) } for _, route := range routes { @@ -91,13 +110,13 @@ func (r *routing) LocalSubnet() (defaultSubnet net.IPNet, err error) { return defaultSubnet, nil } - return defaultSubnet, fmt.Errorf("cannot find default subnet in %d routes", len(routes)) + return defaultSubnet, fmt.Errorf("%w: in %d routes", ErrSubnetDefaultNotFound, len(routes)) } func (r *routing) LocalNetworks() (localNetworks []LocalNetwork, err error) { links, err := netlink.LinkList() if err != nil { - return localNetworks, fmt.Errorf("cannot find local subnet: %w", err) + return localNetworks, fmt.Errorf("%w: %s", ErrLinkList, err) } localLinks := make(map[int]struct{}) @@ -114,12 +133,12 @@ func (r *routing) LocalNetworks() (localNetworks []LocalNetwork, err error) { } if len(localLinks) == 0 { - return localNetworks, fmt.Errorf("cannot find any local interfaces") + return localNetworks, fmt.Errorf("%w: in %d links", ErrLinkLocalNotFound, len(links)) } routes, err := netlink.RouteList(nil, netlink.FAMILY_V4) if err != nil { - return localNetworks, fmt.Errorf("cannot list local routes: %w", err) + return localNetworks, fmt.Errorf("%w: %s", ErrRoutesList, err) } for _, route := range routes { @@ -138,14 +157,14 @@ func (r *routing) LocalNetworks() (localNetworks []LocalNetwork, err error) { link, err := netlink.LinkByIndex(route.LinkIndex) if err != nil { - return localNetworks, fmt.Errorf("cannot get link by index: %w", err) + return localNetworks, fmt.Errorf("%w: at index %d: %s", ErrLinkByIndex, route.LinkIndex, err) } localNet.InterfaceName = link.Attrs().Name ip, err := r.assignedIP(localNet.InterfaceName) if err != nil { - return localNetworks, fmt.Errorf("cannot get IP assigned to link: %w", err) + return localNetworks, err } localNet.IP = ip @@ -154,7 +173,7 @@ func (r *routing) LocalNetworks() (localNetworks []LocalNetwork, err error) { } if len(localNetworks) == 0 { - return localNetworks, fmt.Errorf("cannot find any local networks across %d routes", len(routes)) + return localNetworks, fmt.Errorf("%w: in %d routes", ErrSubnetLocalNotFound, len(routes)) } return localNetworks, nil @@ -163,11 +182,11 @@ func (r *routing) LocalNetworks() (localNetworks []LocalNetwork, err error) { func (r *routing) assignedIP(interfaceName string) (ip net.IP, err error) { iface, err := net.InterfaceByName(interfaceName) if err != nil { - return nil, err + return nil, fmt.Errorf("%w: %s: %s", ErrInterfaceNotFound, interfaceName, err) } addresses, err := iface.Addrs() if err != nil { - return nil, err + return nil, fmt.Errorf("%w: %s: %s", ErrInterfaceListAddr, interfaceName, err) } for _, address := range addresses { switch value := address.(type) { @@ -177,13 +196,14 @@ func (r *routing) assignedIP(interfaceName string) (ip net.IP, err error) { return value.IP, nil } } - return nil, fmt.Errorf("IP address not found in addresses of interface %s", interfaceName) + return nil, fmt.Errorf("%w: interface %s in %d addresses", + ErrInterfaceIPNotFound, interfaceName, len(addresses)) } func (r *routing) VPNDestinationIP() (ip net.IP, err error) { routes, err := netlink.RouteList(nil, netlink.FAMILY_ALL) if err != nil { - return nil, fmt.Errorf("cannot find VPN destination IP: %w", err) + return nil, fmt.Errorf("%w: %s", ErrRoutesList, err) } defaultLinkIndex := -1 @@ -194,7 +214,7 @@ func (r *routing) VPNDestinationIP() (ip net.IP, err error) { } } if defaultLinkIndex == -1 { - return nil, fmt.Errorf("cannot find VPN destination IP: cannot find default link") + return nil, fmt.Errorf("%w: in %d route(s)", ErrLinkDefaultNotFound, len(routes)) } for _, route := range routes { @@ -205,18 +225,18 @@ func (r *routing) VPNDestinationIP() (ip net.IP, err error) { return route.Dst.IP, nil } } - return nil, fmt.Errorf("cannot find VPN destination IP address from ip routes") + return nil, fmt.Errorf("%w: in %d routes", ErrVPNDestinationIPNotFound, len(routes)) } func (r *routing) VPNLocalGatewayIP() (ip net.IP, err error) { routes, err := netlink.RouteList(nil, netlink.FAMILY_ALL) if err != nil { - return nil, fmt.Errorf("cannot find VPN local gateway IP: %w", err) + return nil, fmt.Errorf("%w: %s", ErrRoutesList, err) } for _, route := range routes { link, err := netlink.LinkByIndex(route.LinkIndex) if err != nil { - return nil, fmt.Errorf("cannot find VPN local gateway IP: %w", err) + return nil, fmt.Errorf("%w: %s", ErrLinkByIndex, err) } interfaceName := link.Attrs().Name if interfaceName == string(constants.TUN) && @@ -225,7 +245,7 @@ func (r *routing) VPNLocalGatewayIP() (ip net.IP, err error) { return route.Gw, nil } } - return nil, fmt.Errorf("cannot find VPN local gateway IP address from ip routes") + return nil, fmt.Errorf("%w: in %d routes", ErrVPNLocalGatewayIPNotFound, len(routes)) } func IPIsPrivate(ip net.IP) bool {