feat(firewall): use all default routes

- Accept output traffic from all default routes through VPN interface
- Accept output from all default routes to outbound subnets
- Accept all input traffic on ports for all default routes
- Add IP rules for all default routes
This commit is contained in:
Quentin McGaw
2022-03-13 13:26:09 +00:00
parent 0795008c23
commit f99d5e8656
11 changed files with 212 additions and 154 deletions

View File

@@ -13,56 +13,60 @@ var (
)
type DefaultRouteGetter interface {
DefaultRoute() (defaultInterface string, defaultGateway net.IP, err error)
DefaultRoutes() (defaultRoutes []DefaultRoute, err error)
}
func (r *Routing) DefaultRoute() (defaultInterface string, defaultGateway net.IP, err error) {
routes, err := r.netLinker.RouteList(nil, netlink.FAMILY_ALL)
if err != nil {
return "", nil, fmt.Errorf("cannot list routes: %w", err)
}
for _, route := range routes {
if route.Dst == nil {
defaultGateway = route.Gw
linkIndex := route.LinkIndex
link, err := r.netLinker.LinkByIndex(linkIndex)
if err != nil {
return "", nil, fmt.Errorf("cannot obtain link by index: for default route at index %d: %w", linkIndex, err)
}
attributes := link.Attrs()
defaultInterface = attributes.Name
r.logger.Info("default route found: interface " + defaultInterface +
", gateway " + defaultGateway.String())
return defaultInterface, defaultGateway, nil
}
}
return "", nil, fmt.Errorf("%w: in %d route(s)", ErrRouteDefaultNotFound, len(routes))
type DefaultRoute struct {
NetInterface string
Gateway net.IP
AssignedIP net.IP
}
type DefaultIPGetter interface {
DefaultIP() (defaultIP net.IP, err error)
func (d DefaultRoute) String() string {
return fmt.Sprintf("interface %s, gateway %s and assigned IP %s",
d.NetInterface, d.Gateway, d.AssignedIP)
}
func (r *Routing) DefaultIP() (ip net.IP, err error) {
func (r *Routing) DefaultRoutes() (defaultRoutes []DefaultRoute, err error) {
routes, err := r.netLinker.RouteList(nil, netlink.FAMILY_ALL)
if err != nil {
return nil, fmt.Errorf("cannot list routes: %w", err)
}
defaultLinkName := ""
for _, route := range routes {
if route.Dst == nil {
defaultRoute := DefaultRoute{
Gateway: route.Gw,
}
linkIndex := route.LinkIndex
link, err := r.netLinker.LinkByIndex(linkIndex)
if err != nil {
return nil, fmt.Errorf("cannot find link by index: for default route at index %d: %w", linkIndex, err)
return nil, fmt.Errorf("cannot obtain link by index: for default route at index %d: %w", linkIndex, err)
}
defaultLinkName = link.Attrs().Name
attributes := link.Attrs()
defaultRoute.NetInterface = attributes.Name
defaultRoute.AssignedIP, err = r.assignedIP(defaultRoute.NetInterface)
if err != nil {
return nil, fmt.Errorf("cannot get assigned IP of %s: %w", defaultRoute.NetInterface, err)
}
r.logger.Info("default route found: " + defaultRoute.String())
defaultRoutes = append(defaultRoutes, defaultRoute)
}
}
if defaultLinkName == "" {
return nil, fmt.Errorf("%w: in %d route(s)", ErrLinkDefaultNotFound, len(routes))
if len(defaultRoutes) == 0 {
return nil, fmt.Errorf("%w: in %d route(s)", ErrRouteDefaultNotFound, len(routes))
}
return r.assignedIP(defaultLinkName)
return defaultRoutes, nil
}
func DefaultRoutesInterfaces(defaultRoutes []DefaultRoute) (interfaces []string) {
interfaces = make([]string, len(defaultRoutes))
for i := range defaultRoutes {
interfaces[i] = defaultRoutes[i].NetInterface
}
return interfaces
}

View File

@@ -9,9 +9,9 @@ type Setuper interface {
}
func (r *Routing) Setup() (err error) {
defaultInterfaceName, defaultGateway, err := r.DefaultRoute()
defaultRoutes, err := r.DefaultRoutes()
if err != nil {
return fmt.Errorf("cannot get default route: %w", err)
return fmt.Errorf("cannot get default routes: %w", err)
}
touched := false
@@ -25,7 +25,7 @@ func (r *Routing) Setup() (err error) {
touched = true
err = r.routeInboundFromDefault(defaultGateway, defaultInterfaceName)
err = r.routeInboundFromDefault(defaultRoutes)
if err != nil {
return fmt.Errorf("cannot add routes for inbound traffic from default IP: %w", err)
}
@@ -33,7 +33,7 @@ func (r *Routing) Setup() (err error) {
r.stateMutex.RLock()
outboundSubnets := r.outboundSubnets
r.stateMutex.RUnlock()
if err := r.setOutboundRoutes(outboundSubnets, defaultInterfaceName, defaultGateway); err != nil {
if err := r.setOutboundRoutes(outboundSubnets, defaultRoutes); err != nil {
return fmt.Errorf("cannot set outbound subnets routes: %w", err)
}
@@ -45,17 +45,17 @@ type TearDowner interface {
}
func (r *Routing) TearDown() error {
defaultInterfaceName, defaultGateway, err := r.DefaultRoute()
defaultRoutes, err := r.DefaultRoutes()
if err != nil {
return fmt.Errorf("cannot get default route: %w", err)
}
err = r.unrouteInboundFromDefault(defaultGateway, defaultInterfaceName)
err = r.unrouteInboundFromDefault(defaultRoutes)
if err != nil {
return fmt.Errorf("cannot remove routes for inbound traffic from default IP: %w", err)
}
if err := r.setOutboundRoutes(nil, defaultInterfaceName, defaultGateway); err != nil {
if err := r.setOutboundRoutes(nil, defaultRoutes); err != nil {
return fmt.Errorf("cannot set outbound subnets routes: %w", err)
}

View File

@@ -12,61 +12,62 @@ const (
inboundPriority = 100
)
func (r *Routing) routeInboundFromDefault(defaultGateway net.IP,
defaultInterface string) (err error) {
if err := r.addRuleInboundFromDefault(inboundTable); err != nil {
func (r *Routing) routeInboundFromDefault(defaultRoutes []DefaultRoute) (err error) {
if err := r.addRuleInboundFromDefault(inboundTable, defaultRoutes); err != nil {
return fmt.Errorf("cannot add rule: %w", err)
}
defaultDestination := net.IPNet{IP: net.IPv4(0, 0, 0, 0), Mask: net.IPv4Mask(0, 0, 0, 0)}
if err := r.addRouteVia(defaultDestination, defaultGateway, defaultInterface, inboundTable); err != nil {
return fmt.Errorf("cannot add route: %w", err)
// TODO IPv6
for _, defaultRoute := range defaultRoutes {
err := r.addRouteVia(defaultDestination, defaultRoute.Gateway, defaultRoute.NetInterface, inboundTable)
if err != nil {
return fmt.Errorf("cannot add route: %w", err)
}
}
return nil
}
func (r *Routing) unrouteInboundFromDefault(defaultGateway net.IP,
defaultInterface string) (err error) {
func (r *Routing) unrouteInboundFromDefault(defaultRoutes []DefaultRoute) (err error) {
defaultDestination := net.IPNet{IP: net.IPv4(0, 0, 0, 0), Mask: net.IPv4Mask(0, 0, 0, 0)}
if err := r.deleteRouteVia(defaultDestination, defaultGateway, defaultInterface, inboundTable); err != nil {
return fmt.Errorf("cannot delete route: %w", err)
for _, defaultRoute := range defaultRoutes {
err := r.deleteRouteVia(defaultDestination, defaultRoute.Gateway, defaultRoute.NetInterface, inboundTable)
if err != nil {
return fmt.Errorf("cannot delete route: %w", err)
}
}
if err := r.delRuleInboundFromDefault(inboundTable); err != nil {
if err := r.delRuleInboundFromDefault(inboundTable, defaultRoutes); err != nil {
return fmt.Errorf("cannot delete rule: %w", err)
}
return nil
}
func (r *Routing) addRuleInboundFromDefault(table int) (err error) {
defaultIP, err := r.DefaultIP()
if err != nil {
return fmt.Errorf("cannot find default IP: %w", err)
}
defaultIPMasked32 := netlink.NewIPNet(defaultIP)
ruleDstNet := (*net.IPNet)(nil)
err = r.addIPRule(defaultIPMasked32, ruleDstNet, table, inboundPriority)
if err != nil {
return fmt.Errorf("cannot add rule: %w", err)
func (r *Routing) addRuleInboundFromDefault(table int, defaultRoutes []DefaultRoute) (err error) {
for _, defaultRoute := range defaultRoutes {
defaultIPMasked32 := netlink.NewIPNet(defaultRoute.AssignedIP)
ruleDstNet := (*net.IPNet)(nil)
err = r.addIPRule(defaultIPMasked32, ruleDstNet, table, inboundPriority)
if err != nil {
return fmt.Errorf("cannot add rule for default route %s: %w", defaultRoute, err)
}
}
return nil
}
func (r *Routing) delRuleInboundFromDefault(table int) (err error) {
defaultIP, err := r.DefaultIP()
if err != nil {
return fmt.Errorf("cannot find default IP: %w", err)
}
defaultIPMasked32 := netlink.NewIPNet(defaultIP)
ruleDstNet := (*net.IPNet)(nil)
err = r.deleteIPRule(defaultIPMasked32, ruleDstNet, table, inboundPriority)
if err != nil {
return fmt.Errorf("cannot delete rule: %w", err)
func (r *Routing) delRuleInboundFromDefault(table int, defaultRoutes []DefaultRoute) (err error) {
for _, defaultRoute := range defaultRoutes {
defaultIPMasked32 := netlink.NewIPNet(defaultRoute.AssignedIP)
ruleDstNet := (*net.IPNet)(nil)
err = r.deleteIPRule(defaultIPMasked32, ruleDstNet, table, inboundPriority)
if err != nil {
return fmt.Errorf("cannot delete rule for default route %s: %w", defaultRoute, err)
}
}
return nil

View File

@@ -17,15 +17,15 @@ type OutboundRoutesSetter interface {
}
func (r *Routing) SetOutboundRoutes(outboundSubnets []net.IPNet) error {
defaultInterface, defaultGateway, err := r.DefaultRoute()
defaultRoutes, err := r.DefaultRoutes()
if err != nil {
return err
}
return r.setOutboundRoutes(outboundSubnets, defaultInterface, defaultGateway)
return r.setOutboundRoutes(outboundSubnets, defaultRoutes)
}
func (r *Routing) setOutboundRoutes(outboundSubnets []net.IPNet,
defaultInterfaceName string, defaultGateway net.IP) (err error) {
defaultRoutes []DefaultRoute) (err error) {
r.stateMutex.Lock()
defer r.stateMutex.Unlock()
@@ -36,12 +36,12 @@ func (r *Routing) setOutboundRoutes(outboundSubnets []net.IPNet,
return nil
}
warnings := r.removeOutboundSubnets(subnetsToRemove, defaultInterfaceName, defaultGateway)
warnings := r.removeOutboundSubnets(subnetsToRemove, defaultRoutes)
for _, warning := range warnings {
r.logger.Warn("cannot remove outdated outbound subnet from routing: " + warning)
}
err = r.addOutboundSubnets(subnetsToAdd, defaultInterfaceName, defaultGateway)
err = r.addOutboundSubnets(subnetsToAdd, defaultRoutes)
if err != nil {
return fmt.Errorf("cannot add outbound subnet to routes: %w", err)
}
@@ -50,17 +50,19 @@ func (r *Routing) setOutboundRoutes(outboundSubnets []net.IPNet,
}
func (r *Routing) removeOutboundSubnets(subnets []net.IPNet,
defaultInterfaceName string, defaultGateway net.IP) (warnings []string) {
defaultRoutes []DefaultRoute) (warnings []string) {
for i, subNet := range subnets {
err := r.deleteRouteVia(subNet, defaultGateway, defaultInterfaceName, outboundTable)
if err != nil {
warnings = append(warnings, err.Error())
continue
for _, defaultRoute := range defaultRoutes {
err := r.deleteRouteVia(subNet, defaultRoute.Gateway, defaultRoute.NetInterface, outboundTable)
if err != nil {
warnings = append(warnings, err.Error())
continue
}
}
ruleSrcNet := (*net.IPNet)(nil)
ruleDstNet := &subnets[i]
err = r.deleteIPRule(ruleSrcNet, ruleDstNet, outboundTable, outboundPriority)
err := r.deleteIPRule(ruleSrcNet, ruleDstNet, outboundTable, outboundPriority)
if err != nil {
warnings = append(warnings,
"cannot delete rule: for subnet "+subNet.String()+": "+err.Error())
@@ -74,11 +76,13 @@ func (r *Routing) removeOutboundSubnets(subnets []net.IPNet,
}
func (r *Routing) addOutboundSubnets(subnets []net.IPNet,
defaultInterfaceName string, defaultGateway net.IP) error {
defaultRoutes []DefaultRoute) (err error) {
for i, subnet := range subnets {
err := r.addRouteVia(subnet, defaultGateway, defaultInterfaceName, outboundTable)
if err != nil {
return fmt.Errorf("cannot add route for subnet %s: %w", subnet, err)
for _, defaultRoute := range defaultRoutes {
err = r.addRouteVia(subnet, defaultRoute.Gateway, defaultRoute.NetInterface, outboundTable)
if err != nil {
return fmt.Errorf("cannot add route for subnet %s: %w", subnet, err)
}
}
ruleSrcNet := (*net.IPNet)(nil)

View File

@@ -15,7 +15,6 @@ type ReadWriter interface {
type Reader interface {
DefaultRouteGetter
DefaultIPGetter
LocalSubnetGetter
LocalNetworksGetter
VPNGetter