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 }