github.com/elfadel/cilium@v1.6.12/pkg/datapath/linux/route/route_linux.go (about)

     1  // Copyright 2016-2018 Authors of Cilium
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  //
    15  // +build linux
    16  
    17  package route
    18  
    19  import (
    20  	"fmt"
    21  	"net"
    22  	"time"
    23  
    24  	"github.com/vishvananda/netlink"
    25  )
    26  
    27  const (
    28  	// RouteReplaceMaxTries is the number of attempts the route will be
    29  	// attempted to be added or updated in case the kernel returns an error
    30  	RouteReplaceMaxTries = 10
    31  
    32  	// RouteReplaceRetryInterval is the interval in which
    33  	// RouteReplaceMaxTries attempts are attempted
    34  	RouteReplaceRetryInterval = 100 * time.Millisecond
    35  
    36  	// RTN_LOCAL is a route type used to indicate packet should be "routed"
    37  	// locally and passed up the stack. Is used by IPSec to force encrypted
    38  	// packets to pass through XFRM layer.
    39  	RTN_LOCAL = 0x2
    40  
    41  	// MainTable is Linux's default routing table
    42  	MainTable = 254
    43  
    44  	// EncryptRouteProtocol for Encryption specific routes
    45  	EncryptRouteProtocol = 192
    46  )
    47  
    48  // getNetlinkRoute returns the route configuration as netlink.Route
    49  func (r *Route) getNetlinkRoute() netlink.Route {
    50  	rt := netlink.Route{
    51  		Dst:      &r.Prefix,
    52  		Src:      r.Local,
    53  		MTU:      r.MTU,
    54  		Protocol: r.Proto,
    55  		Table:    r.Table,
    56  		Type:     r.Type,
    57  	}
    58  
    59  	if r.Nexthop != nil {
    60  		rt.Gw = *r.Nexthop
    61  	}
    62  
    63  	if r.Scope != netlink.SCOPE_UNIVERSE {
    64  		rt.Scope = r.Scope
    65  	} else if r.Scope == netlink.SCOPE_UNIVERSE && r.Type == RTN_LOCAL {
    66  		rt.Scope = netlink.SCOPE_HOST
    67  	}
    68  
    69  	return rt
    70  }
    71  
    72  // getNexthopAsIPNet returns the nexthop of the route as IPNet
    73  func (r *Route) getNexthopAsIPNet() *net.IPNet {
    74  	if r.Nexthop == nil {
    75  		return nil
    76  	}
    77  
    78  	if r.Nexthop.To4() != nil {
    79  		return &net.IPNet{IP: *r.Nexthop, Mask: net.CIDRMask(32, 32)}
    80  	}
    81  
    82  	return &net.IPNet{IP: *r.Nexthop, Mask: net.CIDRMask(128, 128)}
    83  }
    84  
    85  func ipFamily(ip net.IP) int {
    86  	if ip.To4() == nil {
    87  		return netlink.FAMILY_V6
    88  	}
    89  
    90  	return netlink.FAMILY_V4
    91  }
    92  
    93  // Lookup attempts to find the linux route based on the route specification.
    94  // If the route exists, the route is returned, otherwise an error is returned.
    95  func Lookup(route Route) (*Route, error) {
    96  	link, err := netlink.LinkByName(route.Device)
    97  	if err != nil {
    98  		return nil, fmt.Errorf("unable to find interface '%s' of route: %s", route.Device, err)
    99  	}
   100  
   101  	routeSpec := route.getNetlinkRoute()
   102  	routeSpec.LinkIndex = link.Attrs().Index
   103  
   104  	nlRoute := lookup(&routeSpec)
   105  	if nlRoute == nil {
   106  		return nil, nil
   107  	}
   108  
   109  	result := &Route{
   110  		Local:   nlRoute.Src,
   111  		Device:  link.Attrs().Name,
   112  		MTU:     nlRoute.MTU,
   113  		Scope:   nlRoute.Scope,
   114  		Nexthop: &nlRoute.Gw,
   115  	}
   116  
   117  	if nlRoute.Dst != nil {
   118  		result.Prefix = *nlRoute.Dst
   119  	}
   120  
   121  	return result, nil
   122  }
   123  
   124  // lookup finds a particular route as specified by the filter which points
   125  // to the specified device. The filter route can have the following fields set:
   126  //  - Dst
   127  //  - LinkIndex
   128  //  - Scope
   129  //  - Gw
   130  func lookup(route *netlink.Route) *netlink.Route {
   131  	var filter uint64
   132  	if route.Dst != nil {
   133  		filter |= netlink.RT_FILTER_DST
   134  	}
   135  	if route.Table != 0 {
   136  		filter |= netlink.RT_FILTER_TABLE
   137  	}
   138  	if route.Scope != 0 {
   139  		filter |= netlink.RT_FILTER_SCOPE
   140  	}
   141  	if route.Gw != nil {
   142  		filter |= netlink.RT_FILTER_GW
   143  	}
   144  	if route.LinkIndex != 0 {
   145  		filter |= netlink.RT_FILTER_OIF
   146  	}
   147  
   148  	routes, err := netlink.RouteListFiltered(ipFamily(route.Dst.IP), route, filter)
   149  	if err != nil {
   150  		return nil
   151  	}
   152  
   153  	for _, r := range routes {
   154  		if r.Dst != nil && route.Dst == nil {
   155  			continue
   156  		}
   157  
   158  		if route.Dst != nil && r.Dst == nil {
   159  			continue
   160  		}
   161  
   162  		if route.Table != 0 && route.Table != r.Table {
   163  			continue
   164  		}
   165  
   166  		aMaskLen, aMaskBits := r.Dst.Mask.Size()
   167  		bMaskLen, bMaskBits := route.Dst.Mask.Size()
   168  		if r.Scope == route.Scope &&
   169  			aMaskLen == bMaskLen && aMaskBits == bMaskBits &&
   170  			r.Dst.IP.Equal(route.Dst.IP) && r.Gw.Equal(route.Gw) {
   171  			return &r
   172  		}
   173  	}
   174  
   175  	return nil
   176  }
   177  
   178  func createNexthopRoute(route Route, link netlink.Link, routerNet *net.IPNet) *netlink.Route {
   179  	// This is the L2 route which makes router IP available behind the
   180  	// interface.
   181  	rt := &netlink.Route{
   182  		LinkIndex: link.Attrs().Index,
   183  		Dst:       routerNet,
   184  		Table:     route.Table,
   185  	}
   186  
   187  	// Known issue: scope for IPv6 routes is not propagated correctly. If
   188  	// we set the scope here, lookup() will be unable to identify the route
   189  	// again and we will continuously re-add the route
   190  	if routerNet.IP.To4() != nil {
   191  		rt.Scope = netlink.SCOPE_LINK
   192  	}
   193  
   194  	return rt
   195  }
   196  
   197  // replaceNexthopRoute verifies that the L2 route for the router IP which is
   198  // used as nexthop for all node routes is properly installed. If unavailable or
   199  // incorrect, it will be replaced with the proper L2 route.
   200  func replaceNexthopRoute(route Route, link netlink.Link, routerNet *net.IPNet) (bool, error) {
   201  	if err := netlink.RouteReplace(createNexthopRoute(route, link, routerNet)); err != nil {
   202  		return false, fmt.Errorf("unable to add L2 nexthop route: %s", err)
   203  	}
   204  
   205  	return true, nil
   206  }
   207  
   208  // deleteNexthopRoute deletes
   209  func deleteNexthopRoute(route Route, link netlink.Link, routerNet *net.IPNet) error {
   210  	if err := netlink.RouteDel(createNexthopRoute(route, link, routerNet)); err != nil {
   211  		return fmt.Errorf("unable to delete L2 nexthop route: %s", err)
   212  	}
   213  
   214  	return nil
   215  }
   216  
   217  // Upsert adds or updates a Linux kernel route. The route described can be in
   218  // the following two forms:
   219  //
   220  // direct:
   221  //   prefix dev foo
   222  //
   223  // nexthop:
   224  //   prefix via nexthop dev foo
   225  //
   226  // If a nexthop route is specified, this function will check whether a direct
   227  // route to the nexthop exists and add if required. This means that the
   228  // following two routes will exist afterwards:
   229  //
   230  //   nexthop dev foo
   231  //   prefix via nexthop dev foo
   232  //
   233  // Due to a bug in the Linux kernel, the prefix route is attempted to be
   234  // updated RouteReplaceMaxTries with an interval of RouteReplaceRetryInterval.
   235  // This is a workaround for a race condition in which the direct route to the
   236  // nexthop is not available immediately and the prefix route can fail with
   237  // EINVAL if the Netlink calls are issued in short order.
   238  //
   239  // An error is returned if the route can not be added or updated.
   240  func Upsert(route Route) (bool, error) {
   241  	var nexthopRouteCreated bool
   242  
   243  	link, err := netlink.LinkByName(route.Device)
   244  	if err != nil {
   245  		return false, fmt.Errorf("unable to lookup interface %s: %s", route.Device, err)
   246  	}
   247  
   248  	routerNet := route.getNexthopAsIPNet()
   249  	if routerNet != nil {
   250  		if _, err := replaceNexthopRoute(route, link, routerNet); err != nil {
   251  			return false, fmt.Errorf("unable to add nexthop route: %s", err)
   252  		}
   253  
   254  		nexthopRouteCreated = true
   255  	}
   256  
   257  	routeSpec := route.getNetlinkRoute()
   258  	routeSpec.LinkIndex = link.Attrs().Index
   259  
   260  	err = fmt.Errorf("routeReplace not called yet")
   261  
   262  	// Workaround: See description of this function
   263  	for i := 0; err != nil && i < RouteReplaceMaxTries; i++ {
   264  		err = netlink.RouteReplace(&routeSpec)
   265  		if err == nil {
   266  			break
   267  		}
   268  		time.Sleep(RouteReplaceRetryInterval)
   269  	}
   270  
   271  	if err != nil {
   272  		if nexthopRouteCreated {
   273  			deleteNexthopRoute(route, link, routerNet)
   274  		}
   275  		return false, err
   276  	}
   277  
   278  	return true, nil
   279  }
   280  
   281  // Delete deletes a Linux route. An error is returned if the route does not
   282  // exist or if the route could not be deleted.
   283  func Delete(route Route) error {
   284  	link, err := netlink.LinkByName(route.Device)
   285  	if err != nil {
   286  		return fmt.Errorf("unable to lookup interface %s: %s", route.Device, err)
   287  	}
   288  
   289  	// Deletion of routes with Nexthop or Local set fails for IPv6.
   290  	// Therefore do not use getNetlinkRoute().
   291  	routeSpec := netlink.Route{
   292  		Dst:       &route.Prefix,
   293  		LinkIndex: link.Attrs().Index,
   294  		Table:     route.Table,
   295  	}
   296  
   297  	// Scope can only be specified for IPv4
   298  	if route.Prefix.IP.To4() != nil {
   299  		routeSpec.Scope = route.Scope
   300  	}
   301  
   302  	if err := netlink.RouteDel(&routeSpec); err != nil {
   303  		return err
   304  	}
   305  
   306  	return nil
   307  }
   308  
   309  // Rule is the specification of an IP routing rule
   310  type Rule struct {
   311  	// Priority is the routing rule priority
   312  	Priority int
   313  
   314  	// Mark is the skb mark that needs to match
   315  	Mark int
   316  
   317  	// Mask is the mask to apply to the skb mark before matching the Mark
   318  	// field
   319  	Mask int
   320  
   321  	// From is the source address selector
   322  	From *net.IPNet
   323  
   324  	// To is the destination address selector
   325  	To *net.IPNet
   326  
   327  	// Table is the routing table to look up if the rule matches
   328  	Table int
   329  }
   330  
   331  func lookupRule(spec Rule, family int) (bool, error) {
   332  	rules, err := netlink.RuleList(family)
   333  	if err != nil {
   334  		return false, err
   335  	}
   336  	for _, r := range rules {
   337  		if spec.Priority != 0 && spec.Priority != r.Priority {
   338  			continue
   339  		}
   340  
   341  		if spec.From != nil && (r.Src == nil || r.Src.String() != spec.From.String()) {
   342  			continue
   343  		}
   344  
   345  		if spec.To != nil && (r.Dst == nil || r.Dst.String() != spec.To.String()) {
   346  			continue
   347  		}
   348  
   349  		if spec.Mark != 0 && r.Mark != spec.Mark {
   350  			continue
   351  		}
   352  
   353  		if r.Table == spec.Table {
   354  			return true, nil
   355  		}
   356  	}
   357  	return false, nil
   358  }
   359  
   360  // ReplaceRule add or replace rule in the routing table using a mark to indicate
   361  // table. Used with BPF datapath to set mark and direct packets to route table.
   362  func ReplaceRule(spec Rule) error {
   363  	exists, err := lookupRule(spec, netlink.FAMILY_V4)
   364  	if err != nil {
   365  		return err
   366  	}
   367  	if exists == true {
   368  		return nil
   369  	}
   370  	return replaceRule(spec, netlink.FAMILY_V4)
   371  }
   372  
   373  // ReplaceRuleIPv6 add or replace IPv6 rule in the routing table using a mark to
   374  // indicate table.
   375  func ReplaceRuleIPv6(spec Rule) error {
   376  	exists, err := lookupRule(spec, netlink.FAMILY_V6)
   377  	if err != nil {
   378  		return err
   379  	}
   380  	if exists == true {
   381  		return nil
   382  	}
   383  	return replaceRule(spec, netlink.FAMILY_V6)
   384  }
   385  
   386  func replaceRule(spec Rule, family int) error {
   387  	rule := netlink.NewRule()
   388  	rule.Mark = spec.Mark
   389  	rule.Mask = spec.Mask
   390  	rule.Table = spec.Table
   391  	rule.Family = family
   392  	rule.Priority = spec.Priority
   393  	rule.Src = spec.From
   394  	rule.Dst = spec.To
   395  	return netlink.RuleAdd(rule)
   396  }
   397  
   398  // DeleteRule delete a mark based rule from the routing table.
   399  func DeleteRule(spec Rule) error {
   400  	rule := netlink.NewRule()
   401  	rule.Mark = spec.Mark
   402  	rule.Mask = spec.Mask
   403  	rule.Table = spec.Table
   404  	rule.Priority = spec.Priority
   405  	rule.Src = spec.From
   406  	rule.Dst = spec.To
   407  	rule.Family = netlink.FAMILY_V4
   408  	return netlink.RuleDel(rule)
   409  }
   410  
   411  // DeleteRuleIPv6 delete a mark based IPv6 rule from the routing table.
   412  func DeleteRuleIPv6(spec Rule) error {
   413  	rule := netlink.NewRule()
   414  	rule.Mark = spec.Mark
   415  	rule.Mask = spec.Mask
   416  	rule.Table = spec.Table
   417  	rule.Priority = spec.Priority
   418  	rule.Src = spec.From
   419  	rule.Dst = spec.To
   420  	rule.Family = netlink.FAMILY_V6
   421  	return netlink.RuleDel(rule)
   422  }