go.ligato.io/vpp-agent/v3@v3.5.0/plugins/linux/l3plugin/descriptor/route.go (about) 1 // Copyright (c) 2018 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 package descriptor 16 17 import ( 18 "bytes" 19 "net" 20 "strings" 21 22 "github.com/pkg/errors" 23 "github.com/vishvananda/netlink" 24 "go.ligato.io/cn-infra/v2/logging" 25 "google.golang.org/protobuf/proto" 26 prototypes "google.golang.org/protobuf/types/known/emptypb" 27 28 "go.ligato.io/vpp-agent/v3/pkg/models" 29 kvs "go.ligato.io/vpp-agent/v3/plugins/kvscheduler/api" 30 "go.ligato.io/vpp-agent/v3/plugins/linux/ifplugin" 31 ifdescriptor "go.ligato.io/vpp-agent/v3/plugins/linux/ifplugin/descriptor" 32 "go.ligato.io/vpp-agent/v3/plugins/linux/l3plugin/descriptor/adapter" 33 l3linuxcalls "go.ligato.io/vpp-agent/v3/plugins/linux/l3plugin/linuxcalls" 34 "go.ligato.io/vpp-agent/v3/plugins/linux/nsplugin" 35 nslinuxcalls "go.ligato.io/vpp-agent/v3/plugins/linux/nsplugin/linuxcalls" 36 "go.ligato.io/vpp-agent/v3/plugins/netalloc" 37 netalloc_descr "go.ligato.io/vpp-agent/v3/plugins/netalloc/descriptor" 38 ifmodel "go.ligato.io/vpp-agent/v3/proto/ligato/linux/interfaces" 39 linux_l3 "go.ligato.io/vpp-agent/v3/proto/ligato/linux/l3" 40 netalloc_api "go.ligato.io/vpp-agent/v3/proto/ligato/netalloc" 41 ) 42 43 const ( 44 // RouteDescriptorName is the name of the descriptor for Linux routes. 45 RouteDescriptorName = "linux-route" 46 47 // dependency labels 48 routeOutInterfaceDep = "outgoing-interface-is-up" 49 routeOutInterfaceIPAddrDep = "outgoing-interface-has-ip-address" 50 routeGwReachabilityDep = "gw-reachable" 51 allocatedAddrAttached = "allocated-addr-attached" 52 53 // default metric of the IPv6 route 54 ipv6DefaultMetric = 1024 55 ) 56 57 // A list of non-retriable errors: 58 var ( 59 // ErrRouteWithoutInterface is returned when Linux Route configuration is missing 60 // outgoing interface reference. 61 ErrRouteWithoutInterface = errors.New("Linux Route defined without outgoing interface reference") 62 63 // ErrRouteWithUndefinedScope is returned when Linux Route is configured without scope. 64 ErrRouteWithUndefinedScope = errors.New("Linux Route defined without scope") 65 66 // ErrRouteLinkWithGw is returned when link-local Linux route has gateway address 67 // specified - it shouldn't be since destination is already neighbour by definition. 68 ErrRouteLinkWithGw = errors.New("Link-local Linux Route was defined with non-empty GW address") 69 ) 70 71 // RouteDescriptor teaches KVScheduler how to configure Linux routes. 72 type RouteDescriptor struct { 73 log logging.Logger 74 l3Handler l3linuxcalls.NetlinkAPI 75 ifPlugin ifplugin.API 76 nsPlugin nsplugin.API 77 addrAlloc netalloc.AddressAllocator 78 scheduler kvs.KVScheduler 79 80 // parallelization of the Retrieve operation 81 goRoutinesCnt int 82 } 83 84 // NewRouteDescriptor creates a new instance of the Route descriptor. 85 func NewRouteDescriptor( 86 scheduler kvs.KVScheduler, ifPlugin ifplugin.API, nsPlugin nsplugin.API, addrAlloc netalloc.AddressAllocator, 87 l3Handler l3linuxcalls.NetlinkAPI, log logging.PluginLogger, goRoutinesCnt int) *kvs.KVDescriptor { 88 89 ctx := &RouteDescriptor{ 90 scheduler: scheduler, 91 l3Handler: l3Handler, 92 ifPlugin: ifPlugin, 93 nsPlugin: nsPlugin, 94 addrAlloc: addrAlloc, 95 goRoutinesCnt: goRoutinesCnt, 96 log: log.NewLogger("route-descriptor"), 97 } 98 typedDescr := &adapter.RouteDescriptor{ 99 Name: RouteDescriptorName, 100 NBKeyPrefix: linux_l3.ModelRoute.KeyPrefix(), 101 ValueTypeName: linux_l3.ModelRoute.ProtoName(), 102 KeySelector: linux_l3.ModelRoute.IsKeyValid, 103 KeyLabel: linux_l3.ModelRoute.StripKeyPrefix, 104 ValueComparator: ctx.EquivalentRoutes, 105 Validate: ctx.Validate, 106 Create: ctx.Create, 107 Delete: ctx.Delete, 108 Update: ctx.Update, 109 UpdateWithRecreate: ctx.UpdateWithRecreate, 110 Retrieve: ctx.Retrieve, 111 DerivedValues: ctx.DerivedValues, 112 Dependencies: ctx.Dependencies, 113 RetrieveDependencies: []string{ 114 netalloc_descr.IPAllocDescriptorName, 115 ifdescriptor.InterfaceDescriptorName}, 116 } 117 return adapter.NewRouteDescriptor(typedDescr) 118 } 119 120 // EquivalentRoutes is case-insensitive comparison function for l3.LinuxRoute. 121 func (d *RouteDescriptor) EquivalentRoutes(key string, oldRoute, newRoute *linux_l3.Route) bool { 122 // attributes compared as usually: 123 if oldRoute.OutgoingInterface != newRoute.OutgoingInterface { 124 return false 125 } 126 // compare scopes for IPv4 routes 127 if d.isIPv4Route(newRoute) && oldRoute.Scope != newRoute.Scope { 128 return false 129 } 130 // compare metrics 131 if !d.isRouteMetricEqual(oldRoute, newRoute) { 132 return false 133 } 134 135 // compare IP addresses converted to net.IP(Net) 136 if !equalNetworks(oldRoute.DstNetwork, newRoute.DstNetwork) { 137 return false 138 } 139 return equalAddrs(d.getGwAddr(oldRoute), d.getGwAddr(newRoute)) 140 } 141 142 // Validate validates static route configuration. 143 func (d *RouteDescriptor) Validate(key string, route *linux_l3.Route) (err error) { 144 if route.OutgoingInterface == "" { 145 return kvs.NewInvalidValueError(ErrRouteWithoutInterface, "outgoing_interface") 146 } 147 if route.Scope == linux_l3.Route_LINK && route.GwAddr != "" { 148 return kvs.NewInvalidValueError(ErrRouteLinkWithGw, "scope", "gw_addr") 149 } 150 err = d.addrAlloc.ValidateIPAddress(route.DstNetwork, "", "dst_network", 151 netalloc.GWRefAllowed) 152 if err != nil { 153 return err 154 } 155 return d.addrAlloc.ValidateIPAddress(d.getGwAddr(route), route.OutgoingInterface, 156 "gw_addr", netalloc.GWRefRequired) 157 } 158 159 // Create adds Linux route. 160 func (d *RouteDescriptor) Create(key string, route *linux_l3.Route) (metadata interface{}, err error) { 161 err = d.updateRoute(route, "add", d.l3Handler.AddRoute) 162 return nil, err 163 } 164 165 // Delete removes Linux route. 166 func (d *RouteDescriptor) Delete(key string, route *linux_l3.Route, metadata interface{}) error { 167 return d.updateRoute(route, "delete", d.l3Handler.DelRoute) 168 } 169 170 // Update is able to change route scope and GW address. 171 func (d *RouteDescriptor) Update(key string, oldRoute, newRoute *linux_l3.Route, oldMetadata interface{}) (newMetadata interface{}, err error) { 172 err = d.updateRoute(newRoute, "modify", d.l3Handler.ReplaceRoute) 173 return nil, err 174 } 175 176 // UpdateWithRecreate in case the metric was changed 177 func (d *RouteDescriptor) UpdateWithRecreate(_ string, oldRoute, newRoute *linux_l3.Route, _ interface{}) bool { 178 return !d.isRouteMetricEqual(oldRoute, newRoute) 179 } 180 181 // updateRoute adds, modifies or deletes a Linux route. 182 func (d *RouteDescriptor) updateRoute(route *linux_l3.Route, actionName string, actionClb func(route *netlink.Route) error) error { 183 var err error 184 185 // Prepare Netlink Route object 186 netlinkRoute := &netlink.Route{} 187 188 // Get interface metadata 189 ifMeta, found := d.ifPlugin.GetInterfaceIndex().LookupByName(route.OutgoingInterface) 190 if !found || ifMeta == nil { 191 err = errors.Errorf("failed to obtain metadata for interface %s", route.OutgoingInterface) 192 d.log.Error(err) 193 return err 194 } 195 196 // set link index 197 netlinkRoute.LinkIndex = ifMeta.LinuxIfIndex 198 199 // set routing table 200 if ifMeta.VrfMasterIf != "" { 201 // - route depends on interface having an IP address 202 // - IP address depends on the interface already being in the VRF 203 // - VRF assignment depends on the VRF device being configured 204 // => conclusion: VRF device is configured at this point 205 vrfMeta, found := d.ifPlugin.GetInterfaceIndex().LookupByName(ifMeta.VrfMasterIf) 206 if !found || vrfMeta == nil { 207 err = errors.Errorf("failed to obtain metadata for VRF device %s", ifMeta.VrfMasterIf) 208 d.log.Error(err) 209 return err 210 } 211 netlinkRoute.Table = int(vrfMeta.VrfDevRT) 212 } 213 214 // set destination network 215 dstNet, err := d.addrAlloc.GetOrParseIPAddress(route.DstNetwork, "", 216 netalloc_api.IPAddressForm_ADDR_NET) 217 if err != nil { 218 d.log.Error(err) 219 return err 220 } 221 netlinkRoute.Dst = dstNet 222 223 // set gateway address 224 if route.GwAddr != "" { 225 gwAddr, err := d.addrAlloc.GetOrParseIPAddress(route.GwAddr, route.OutgoingInterface, 226 netalloc_api.IPAddressForm_ADDR_ONLY) 227 if err != nil { 228 d.log.Error(err) 229 return err 230 } 231 netlinkRoute.Gw = gwAddr.IP 232 } 233 234 // set route scope for IPv4 235 if d.isIPv4Route(route) { 236 scope, err := rtScopeFromNBToNetlink(route.Scope) 237 if err != nil { 238 d.log.Error(err) 239 return err 240 } 241 netlinkRoute.Scope = scope 242 } 243 244 // set route metric 245 netlinkRoute.Priority = int(route.Metric) 246 247 // move to the namespace of the associated interface 248 nsCtx := nslinuxcalls.NewNamespaceMgmtCtx() 249 revertNs, err := d.nsPlugin.SwitchToNamespace(nsCtx, ifMeta.Namespace) 250 if err != nil { 251 err = errors.Errorf("failed to switch namespace: %v", err) 252 d.log.Error(err) 253 return err 254 } 255 defer revertNs() 256 257 // update route in the interface namespace 258 err = actionClb(netlinkRoute) 259 if err != nil { 260 err = errors.Errorf("failed to %s linux route: %v", actionName, err) 261 d.log.Error(err) 262 return err 263 } 264 265 return nil 266 } 267 268 // Dependencies lists dependencies for a Linux route. 269 func (d *RouteDescriptor) Dependencies(key string, route *linux_l3.Route) []kvs.Dependency { 270 var dependencies []kvs.Dependency 271 // the outgoing interface must exist and be UP 272 if route.OutgoingInterface != "" { 273 dependencies = append(dependencies, kvs.Dependency{ 274 Label: routeOutInterfaceDep, 275 Key: ifmodel.InterfaceStateKey(route.OutgoingInterface, true), 276 }) 277 } 278 // if destination network is netalloc reference, then the address must be allocated first 279 allocDep, hasAllocDep := d.addrAlloc.GetAddressAllocDep(route.DstNetwork, "", 280 "dst_network-") 281 if hasAllocDep { 282 dependencies = append(dependencies, allocDep) 283 } 284 // if GW is netalloc reference, then the address must be allocated first 285 allocDep, hasAllocDep = d.addrAlloc.GetAddressAllocDep(route.GwAddr, route.OutgoingInterface, 286 "gw_addr-") 287 if hasAllocDep { 288 dependencies = append(dependencies, allocDep) 289 } 290 // GW must be routable 291 network, iface, _, isRef, _ := d.addrAlloc.ParseAddressAllocRef(route.GwAddr, route.OutgoingInterface) 292 if isRef { 293 // GW is netalloc reference 294 dependencies = append(dependencies, kvs.Dependency{ 295 Label: routeGwReachabilityDep, 296 AnyOf: kvs.AnyOfDependency{ 297 KeyPrefixes: []string{ 298 netalloc_api.NeighGwKey(network, iface), 299 linux_l3.StaticLinkLocalRouteKey( 300 d.addrAlloc.CreateAddressAllocRef(network, iface, true), 301 route.OutgoingInterface), 302 }, 303 }, 304 }) 305 dependencies = append(dependencies, kvs.Dependency{ 306 Label: allocatedAddrAttached, 307 Key: ifmodel.InterfaceAddressKey( 308 route.OutgoingInterface, d.addrAlloc.CreateAddressAllocRef(network, "", false), 309 netalloc_api.IPAddressSource_ALLOC_REF), 310 }) 311 } else if gwAddr := net.ParseIP(d.getGwAddr(route)); gwAddr != nil && !gwAddr.IsUnspecified() { 312 // GW is not netalloc reference but an actual IP 313 dependencies = append(dependencies, kvs.Dependency{ 314 Label: routeGwReachabilityDep, 315 AnyOf: kvs.AnyOfDependency{ 316 KeyPrefixes: []string{ 317 ifmodel.InterfaceAddressPrefix(route.OutgoingInterface), 318 linux_l3.StaticLinkLocalRoutePrefix(route.OutgoingInterface), 319 }, 320 KeySelector: func(key string) bool { 321 dstAddr, ifName, isRouteKey := linux_l3.ParseStaticLinkLocalRouteKey(key) 322 if isRouteKey && ifName == route.OutgoingInterface { 323 if _, dstNet, err := net.ParseCIDR(dstAddr); err == nil && dstNet.Contains(gwAddr) { 324 // GW address is neighbour as told by another link-local route 325 return true 326 } 327 return false 328 } 329 _, address, source, _, isAddrKey := ifmodel.ParseInterfaceAddressKey(key) 330 if isAddrKey && source != netalloc_api.IPAddressSource_ALLOC_REF { 331 if _, network, err := net.ParseCIDR(address); err == nil && network.Contains(gwAddr) { 332 // GW address is inside the local network of the outgoing interface 333 // as given by the assigned IP address 334 return true 335 } 336 } 337 return false 338 }, 339 }, 340 }) 341 } 342 if route.OutgoingInterface != "" { 343 // route also requires the interface to be in the L3 mode (have at least one IP address assigned) 344 dependencies = append(dependencies, kvs.Dependency{ 345 Label: routeOutInterfaceIPAddrDep, 346 AnyOf: kvs.AnyOfDependency{ 347 KeyPrefixes: []string{ 348 ifmodel.InterfaceAddressPrefix(route.OutgoingInterface), 349 }, 350 }, 351 }) 352 } 353 return dependencies 354 } 355 356 // DerivedValues derives empty value under StaticLinkLocalRouteKey if route is link-local. 357 // It is used in dependencies for network reachability of a route gateway (see above). 358 func (d *RouteDescriptor) DerivedValues(key string, route *linux_l3.Route) (derValues []kvs.KeyValuePair) { 359 if route.Scope == linux_l3.Route_LINK { 360 derValues = append(derValues, kvs.KeyValuePair{ 361 Key: linux_l3.StaticLinkLocalRouteKey(route.DstNetwork, route.OutgoingInterface), 362 Value: &prototypes.Empty{}, 363 }) 364 } 365 return derValues 366 } 367 368 // Retrieve returns all routes associated with interfaces managed by this agent. 369 func (d *RouteDescriptor) Retrieve(correlate []adapter.RouteKVWithMetadata) ([]adapter.RouteKVWithMetadata, error) { 370 var values []adapter.RouteKVWithMetadata 371 372 // prepare expected configuration with de-referenced netalloc links 373 nbCfg := make(map[string]*linux_l3.Route) 374 expCfg := make(map[string]*linux_l3.Route) 375 for _, kv := range correlate { 376 dstNetwork := kv.Value.DstNetwork 377 parsed, err := d.addrAlloc.GetOrParseIPAddress(kv.Value.DstNetwork, 378 "", netalloc_api.IPAddressForm_ADDR_NET) 379 if err == nil { 380 dstNetwork = parsed.String() 381 } 382 gwAddr := kv.Value.GwAddr 383 parsed, err = d.addrAlloc.GetOrParseIPAddress(d.getGwAddr(kv.Value), 384 kv.Value.OutgoingInterface, netalloc_api.IPAddressForm_ADDR_ONLY) 385 if err == nil { 386 gwAddr = parsed.IP.String() 387 } 388 route := proto.Clone(kv.Value).(*linux_l3.Route) 389 route.DstNetwork = dstNetwork 390 route.GwAddr = gwAddr 391 key := models.Key(route) 392 expCfg[key] = route 393 nbCfg[key] = kv.Value 394 } 395 396 routeDetails, err := d.l3Handler.DumpRoutes() 397 if err != nil { 398 return nil, errors.Errorf("Failed to retrieve linux ARPs: %v", err) 399 } 400 401 // correlate with the expected configuration 402 for _, routeDetails := range routeDetails { 403 // convert to key-value object with metadata 404 // resolve scope for IPv4. Note that IPv6 route scope always returns zero value. 405 var scope linux_l3.Route_Scope 406 if d.isIPv4Route(routeDetails.Route) { 407 scope, err = rtScopeFromNetlinkToNB(routeDetails.Meta.NetlinkScope) 408 if err != nil { 409 // route not configured by the agent 410 continue 411 } 412 } 413 route := adapter.RouteKVWithMetadata{ 414 Key: linux_l3.RouteKey(routeDetails.Route.DstNetwork, routeDetails.Route.OutgoingInterface), 415 Value: &linux_l3.Route{ 416 OutgoingInterface: routeDetails.Route.OutgoingInterface, 417 Scope: scope, 418 DstNetwork: routeDetails.Route.DstNetwork, 419 GwAddr: routeDetails.Route.GwAddr, 420 Metric: routeDetails.Route.Metric, 421 }, 422 Origin: kvs.UnknownOrigin, // let the scheduler to determine the origin 423 } 424 425 key := linux_l3.RouteKey(routeDetails.Route.DstNetwork, routeDetails.Route.OutgoingInterface) 426 if expCfg, hasExpCfg := expCfg[key]; hasExpCfg { 427 if d.EquivalentRoutes(key, route.Value, expCfg) { 428 route.Value = nbCfg[key] 429 // recreate the key in case the dest. IP was replaced with netalloc link 430 route.Key = models.Key(route.Value) 431 } 432 } 433 values = append(values, route) 434 } 435 436 return values, nil 437 } 438 439 // compares route metrics. For IPv6, Metric 0 & 1024 are considered the same value 440 func (d *RouteDescriptor) isRouteMetricEqual(oldRoute, newRoute *linux_l3.Route) bool { 441 if oldRoute.Metric != newRoute.Metric { 442 if d.isIPv4Route(newRoute) { 443 return false 444 } 445 return (oldRoute.Metric == 0 && newRoute.Metric == ipv6DefaultMetric) || 446 (oldRoute.Metric == ipv6DefaultMetric && newRoute.Metric == 0) 447 } 448 return true 449 } 450 451 // checks the destination network to determine whether the route is an IPv4 route 452 func (d *RouteDescriptor) isIPv4Route(r *linux_l3.Route) bool { 453 addr, err := d.addrAlloc.GetOrParseIPAddress(r.DstNetwork, "", netalloc_api.IPAddressForm_ADDR_ONLY) 454 if err != nil { 455 d.log.Error(err) 456 } 457 return addr != nil && addr.IP != nil && addr.IP.To4() != nil 458 } 459 460 // getGwAddr returns the GW address chosen in the given route, handling the cases 461 // when it is left undefined. 462 func (d *RouteDescriptor) getGwAddr(route *linux_l3.Route) string { 463 if route.GwAddr == "" { 464 if d.isIPv4Route(route) { 465 return l3linuxcalls.IPv4AddrAny 466 } 467 return l3linuxcalls.IPv6AddrAny 468 } 469 return route.GwAddr 470 } 471 472 // rtScopeFromNBToNetlink convert Route scope from NB configuration 473 // to the corresponding Netlink constant. 474 func rtScopeFromNBToNetlink(scope linux_l3.Route_Scope) (netlink.Scope, error) { 475 switch scope { 476 case linux_l3.Route_GLOBAL: 477 return netlink.SCOPE_UNIVERSE, nil 478 case linux_l3.Route_HOST: 479 return netlink.SCOPE_HOST, nil 480 case linux_l3.Route_LINK: 481 return netlink.SCOPE_LINK, nil 482 case linux_l3.Route_SITE: 483 return netlink.SCOPE_SITE, nil 484 } 485 return 0, ErrRouteWithUndefinedScope 486 } 487 488 // rtScopeFromNetlinkToNB converts Route scope from Netlink constant 489 // to the corresponding NB constant. 490 func rtScopeFromNetlinkToNB(scope netlink.Scope) (linux_l3.Route_Scope, error) { 491 switch scope { 492 case netlink.SCOPE_UNIVERSE: 493 return linux_l3.Route_GLOBAL, nil 494 case netlink.SCOPE_HOST: 495 return linux_l3.Route_HOST, nil 496 case netlink.SCOPE_LINK: 497 return linux_l3.Route_LINK, nil 498 case netlink.SCOPE_SITE: 499 return linux_l3.Route_SITE, nil 500 } 501 return 0, ErrRouteWithUndefinedScope 502 } 503 504 // equalAddrs compares two IP addresses for equality. 505 func equalAddrs(addr1, addr2 string) bool { 506 if strings.HasPrefix(addr1, netalloc_api.AllocRefPrefix) { 507 return addr1 == addr2 508 } 509 a1 := net.ParseIP(addr1) 510 a2 := net.ParseIP(addr2) 511 if a1 == nil || a2 == nil { 512 // if parsing fails, compare as strings 513 return strings.EqualFold(addr1, addr2) 514 } 515 return a1.Equal(a2) 516 } 517 518 // equalNetworks compares two IP networks for equality. 519 func equalNetworks(net1, net2 string) bool { 520 if strings.HasPrefix(net1, netalloc_api.AllocRefPrefix) { 521 return net1 == net2 522 } 523 _, n1, err1 := net.ParseCIDR(net1) 524 _, n2, err2 := net.ParseCIDR(net2) 525 if err1 != nil || err2 != nil { 526 // if parsing fails, compare as strings 527 return strings.EqualFold(net1, net2) 528 } 529 return n1.IP.Equal(n2.IP) && bytes.Equal(n1.Mask, n2.Mask) 530 }