go.ligato.io/vpp-agent/v3@v3.5.0/plugins/linux/l3plugin/linuxcalls/dump_route_linuxcalls.go (about)

     1  // Copyright (c) 2019 Cisco and/or its affiliates.
     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  //go:build !windows && !darwin
    16  
    17  package linuxcalls
    18  
    19  import (
    20  	"github.com/pkg/errors"
    21  	"github.com/vishvananda/netlink"
    22  	"go.ligato.io/cn-infra/v2/logging"
    23  
    24  	"go.ligato.io/vpp-agent/v3/plugins/linux/nsplugin/linuxcalls"
    25  	linux_l3 "go.ligato.io/vpp-agent/v3/proto/ligato/linux/l3"
    26  )
    27  
    28  const (
    29  	// IP addresses matching any destination.
    30  	IPv4AddrAny = "0.0.0.0"
    31  	IPv6AddrAny = "::"
    32  
    33  	// minimum number of interfaces to be given to a single Go routine for processing
    34  	// in the Retrieve operation
    35  	minWorkForGoRoutine = 3
    36  )
    37  
    38  // retrievedRoutes is used as the return value sent via channel by retrieveRoutes().
    39  type retrievedRoutes struct {
    40  	routes []*RouteDetails
    41  	err    error
    42  }
    43  
    44  // GetRoutes reads all configured static routes with the given outgoing
    45  // interface.
    46  // <interfaceIdx> works as filter, if set to zero, all routes in the namespace
    47  // are returned.
    48  func (h *NetLinkHandler) GetRoutes(interfaceIdx, table int) (v4Routes, v6Routes []netlink.Route, err error) {
    49  	var routeFilter *netlink.Route
    50  	var filterMask uint64
    51  	if interfaceIdx != 0 || table != 0 {
    52  		routeFilter = &netlink.Route{
    53  			LinkIndex: interfaceIdx,
    54  			Table:     table,
    55  		}
    56  		if interfaceIdx != 0 {
    57  			filterMask |= netlink.RT_FILTER_OIF
    58  		}
    59  		if table != 0 {
    60  			filterMask |= netlink.RT_FILTER_TABLE
    61  		}
    62  	}
    63  	v4Routes, err = netlink.RouteListFiltered(netlink.FAMILY_V4, routeFilter, filterMask)
    64  	if err != nil {
    65  		return
    66  	}
    67  	v6Routes, err = netlink.RouteListFiltered(netlink.FAMILY_V6, routeFilter, filterMask)
    68  	return
    69  }
    70  
    71  // DumpRoutes reads all route entries and returns them as details
    72  // with proto-modeled route data and additional metadata
    73  func (h *NetLinkHandler) DumpRoutes() ([]*RouteDetails, error) {
    74  	interfaces := h.ifIndexes.ListAllInterfaces()
    75  	goRoutinesCnt := len(interfaces) / minWorkForGoRoutine
    76  	if goRoutinesCnt == 0 {
    77  		goRoutinesCnt = 1
    78  	}
    79  	if goRoutinesCnt > h.goRoutineCount {
    80  		goRoutinesCnt = h.goRoutineCount
    81  	}
    82  	ch := make(chan retrievedRoutes, goRoutinesCnt)
    83  
    84  	// invoke multiple go routines for more efficient parallel route retrieval
    85  	for idx := 0; idx < goRoutinesCnt; idx++ {
    86  		if goRoutinesCnt > 1 {
    87  			go h.retrieveRoutes(interfaces, idx, goRoutinesCnt, ch)
    88  		} else {
    89  			h.retrieveRoutes(interfaces, idx, goRoutinesCnt, ch)
    90  		}
    91  	}
    92  
    93  	// collect results from the go routines
    94  	var routeDetails []*RouteDetails
    95  	for idx := 0; idx < goRoutinesCnt; idx++ {
    96  		retrieved := <-ch
    97  		if retrieved.err != nil {
    98  			return nil, retrieved.err
    99  		}
   100  		// correlate with the expected configuration
   101  		routeDetails = append(routeDetails, retrieved.routes...)
   102  	}
   103  
   104  	return routeDetails, nil
   105  }
   106  
   107  // retrieveRoutes is run by a separate go routine to retrieve all routes entries
   108  // associated with every <goRoutineIdx>-th interface.
   109  func (h *NetLinkHandler) retrieveRoutes(interfaces []string, goRoutineIdx, goRoutinesCnt int, ch chan<- retrievedRoutes) {
   110  	var retrieved retrievedRoutes
   111  	nsCtx := linuxcalls.NewNamespaceMgmtCtx()
   112  
   113  	for i := goRoutineIdx; i < len(interfaces); i += goRoutinesCnt {
   114  		ifName := interfaces[i]
   115  		// get interface metadata
   116  		ifMeta, found := h.ifIndexes.LookupByName(ifName)
   117  		if !found || ifMeta == nil {
   118  			retrieved.err = errors.Errorf("failed to obtain metadata for interface %s", ifName)
   119  			h.log.Error(retrieved.err)
   120  			break
   121  		}
   122  
   123  		// obtain the associated routing table
   124  		var table int
   125  		if ifMeta.VrfMasterIf != "" {
   126  			vrfMeta, found := h.ifIndexes.LookupByName(ifMeta.VrfMasterIf)
   127  			if found {
   128  				table = int(vrfMeta.VrfDevRT)
   129  			}
   130  		}
   131  
   132  		// switch to the namespace of the interface
   133  		revertNs, err := h.nsPlugin.SwitchToNamespace(nsCtx, ifMeta.Namespace)
   134  		if err != nil {
   135  			// namespace and all the routes it had contained no longer exist
   136  			h.log.WithFields(logging.Fields{
   137  				"err":       err,
   138  				"namespace": ifMeta.Namespace,
   139  			}).Warn("Failed to retrieve routes from the namespace")
   140  			continue
   141  		}
   142  
   143  		// get routes assigned to this interface
   144  		v4Routes, v6Routes, err := h.GetRoutes(ifMeta.LinuxIfIndex, table)
   145  		revertNs()
   146  		if err != nil {
   147  			retrieved.err = err
   148  			h.log.Error(retrieved.err)
   149  			break
   150  		}
   151  
   152  		// convert each route from Netlink representation to the NB representation
   153  		for idx, route := range append(v4Routes, v6Routes...) {
   154  			var dstNet, gwAddr string
   155  			if route.Dst == nil {
   156  				if idx < len(v4Routes) {
   157  					dstNet = IPv4AddrAny + "/0"
   158  				} else {
   159  					dstNet = IPv6AddrAny + "/0"
   160  				}
   161  			} else {
   162  				if route.Dst.IP.To4() == nil && route.Dst.IP.IsLinkLocalUnicast() {
   163  					// skip link-local IPv6 destinations until there is a requirement to support them
   164  					continue
   165  				}
   166  				dstNet = route.Dst.String()
   167  			}
   168  			if len(route.Gw) != 0 {
   169  				gwAddr = route.Gw.String()
   170  			}
   171  			retrieved.routes = append(retrieved.routes, &RouteDetails{
   172  				Route: &linux_l3.Route{
   173  					OutgoingInterface: ifName,
   174  					DstNetwork:        dstNet,
   175  					GwAddr:            gwAddr,
   176  					Metric:            uint32(route.Priority),
   177  				},
   178  				Meta: &RouteMeta{
   179  					InterfaceIndex: uint32(route.LinkIndex),
   180  					NetlinkScope:   route.Scope,
   181  					Protocol:       uint32(route.Protocol),
   182  					MTU:            uint32(route.MTU),
   183  					Table:          uint32(route.Table),
   184  				},
   185  			})
   186  		}
   187  	}
   188  
   189  	ch <- retrieved
   190  }