go.ligato.io/vpp-agent/v3@v3.5.0/plugins/vpp/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 "context" 20 "fmt" 21 "net" 22 "strings" 23 24 "github.com/pkg/errors" 25 "go.ligato.io/cn-infra/v2/logging" 26 "go.ligato.io/cn-infra/v2/utils/addrs" 27 "google.golang.org/protobuf/proto" 28 29 "go.ligato.io/vpp-agent/v3/pkg/models" 30 kvs "go.ligato.io/vpp-agent/v3/plugins/kvscheduler/api" 31 "go.ligato.io/vpp-agent/v3/plugins/netalloc" 32 netalloc_descr "go.ligato.io/vpp-agent/v3/plugins/netalloc/descriptor" 33 ifdescriptor "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/descriptor" 34 "go.ligato.io/vpp-agent/v3/plugins/vpp/l3plugin/descriptor/adapter" 35 "go.ligato.io/vpp-agent/v3/plugins/vpp/l3plugin/vppcalls" 36 netalloc_api "go.ligato.io/vpp-agent/v3/proto/ligato/netalloc" 37 interfaces "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/interfaces" 38 l3 "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/l3" 39 ) 40 41 const ( 42 // RouteDescriptorName is the name of the descriptor for static routes. 43 RouteDescriptorName = "vpp-route" 44 45 // dependency labels 46 routeOutInterfaceDep = "interface-exists" 47 vrfTableDep = "vrf-table-exists" 48 viaVrfTableDep = "via-vrf-table-exists" 49 50 // static route weight by default 51 defaultWeight = 1 52 ) 53 54 // RouteDescriptor teaches KVScheduler how to configure VPP routes. 55 type RouteDescriptor struct { 56 log logging.Logger 57 routeHandler vppcalls.RouteVppAPI 58 addrAlloc netalloc.AddressAllocator 59 } 60 61 // NewRouteDescriptor creates a new instance of the Route descriptor. 62 func NewRouteDescriptor( 63 routeHandler vppcalls.RouteVppAPI, addrAlloc netalloc.AddressAllocator, 64 log logging.PluginLogger) *kvs.KVDescriptor { 65 66 ctx := &RouteDescriptor{ 67 routeHandler: routeHandler, 68 addrAlloc: addrAlloc, 69 log: log.NewLogger("static-route-descriptor"), 70 } 71 72 typedDescr := &adapter.RouteDescriptor{ 73 Name: RouteDescriptorName, 74 NBKeyPrefix: l3.ModelRoute.KeyPrefix(), 75 ValueTypeName: l3.ModelRoute.ProtoName(), 76 KeySelector: l3.ModelRoute.IsKeyValid, 77 ValueComparator: ctx.EquivalentRoutes, 78 Validate: ctx.Validate, 79 Create: ctx.Create, 80 Delete: ctx.Delete, 81 Retrieve: ctx.Retrieve, 82 Dependencies: ctx.Dependencies, 83 RetrieveDependencies: []string{ 84 netalloc_descr.IPAllocDescriptorName, 85 ifdescriptor.InterfaceDescriptorName, 86 VrfTableDescriptorName}, 87 } 88 return adapter.NewRouteDescriptor(typedDescr) 89 } 90 91 // EquivalentRoutes is case-insensitive comparison function for l3.Route. 92 func (d *RouteDescriptor) EquivalentRoutes(key string, oldRoute, newRoute *l3.Route) bool { 93 if oldRoute.GetType() != newRoute.GetType() || 94 oldRoute.GetVrfId() != newRoute.GetVrfId() || 95 oldRoute.GetViaVrfId() != newRoute.GetViaVrfId() || 96 oldRoute.GetOutgoingInterface() != newRoute.GetOutgoingInterface() || 97 getWeight(oldRoute) != getWeight(newRoute) || 98 oldRoute.GetPreference() != newRoute.GetPreference() { 99 return false 100 } 101 102 // compare dst networks 103 if !equalNetworks(oldRoute.DstNetwork, newRoute.DstNetwork) { 104 return false 105 } 106 107 // compare gw addresses (next hop) 108 if !equalAddrs(getGwAddr(oldRoute), getGwAddr(newRoute)) { 109 return false 110 } 111 112 return true 113 } 114 115 // Validate validates VPP static route configuration. 116 func (d *RouteDescriptor) Validate(key string, route *l3.Route) (err error) { 117 // validate destination network 118 err = d.addrAlloc.ValidateIPAddress(route.DstNetwork, "", "dst_network", 119 netalloc.GWRefAllowed) 120 if err != nil { 121 return err 122 } 123 124 // validate next hop address (GW) 125 err = d.addrAlloc.ValidateIPAddress(getGwAddr(route), route.OutgoingInterface, 126 "gw_addr", netalloc.GWRefRequired) 127 if err != nil { 128 return err 129 } 130 131 // validate IP network implied by the IP and prefix length 132 if !strings.HasPrefix(route.DstNetwork, netalloc_api.AllocRefPrefix) { 133 _, ipNet, _ := net.ParseCIDR(route.DstNetwork) 134 if !strings.EqualFold(ipNet.String(), route.DstNetwork) { 135 e := fmt.Errorf("DstNetwork (%s) must represent IP network (%s)", 136 route.DstNetwork, ipNet.String()) 137 return kvs.NewInvalidValueError(e, "dst_network") 138 } 139 } 140 141 // TODO: validate mix of IP versions? 142 143 return nil 144 } 145 146 // Create adds VPP static route. 147 func (d *RouteDescriptor) Create(key string, route *l3.Route) (metadata interface{}, err error) { 148 err = d.routeHandler.VppAddRoute(context.TODO(), route) 149 if err != nil { 150 return nil, err 151 } 152 153 return nil, nil 154 } 155 156 // Delete removes VPP static route. 157 func (d *RouteDescriptor) Delete(key string, route *l3.Route, metadata interface{}) error { 158 err := d.routeHandler.VppDelRoute(context.TODO(), route) 159 if err != nil { 160 return err 161 } 162 163 return nil 164 } 165 166 // Retrieve returns all routes associated with interfaces managed by this agent. 167 func (d *RouteDescriptor) Retrieve(correlate []adapter.RouteKVWithMetadata) ( 168 retrieved []adapter.RouteKVWithMetadata, err error, 169 ) { 170 // prepare expected configuration with de-referenced netalloc links 171 nbCfg := make(map[string]*l3.Route) 172 expCfg := make(map[string]*l3.Route) 173 for _, kv := range correlate { 174 dstNetwork := kv.Value.DstNetwork 175 parsed, err := d.addrAlloc.GetOrParseIPAddress(kv.Value.DstNetwork, 176 "", netalloc_api.IPAddressForm_ADDR_NET) 177 if err == nil { 178 dstNetwork = parsed.String() 179 } 180 nextHop := kv.Value.NextHopAddr 181 parsed, err = d.addrAlloc.GetOrParseIPAddress(getGwAddr(kv.Value), 182 kv.Value.OutgoingInterface, netalloc_api.IPAddressForm_ADDR_ONLY) 183 if err == nil { 184 nextHop = parsed.IP.String() 185 } 186 route := proto.Clone(kv.Value).(*l3.Route) 187 route.DstNetwork = dstNetwork 188 route.NextHopAddr = nextHop 189 key := models.Key(route) 190 expCfg[key] = route 191 nbCfg[key] = kv.Value 192 } 193 194 // Retrieve VPP route configuration 195 routes, err := d.routeHandler.DumpRoutes() 196 if err != nil { 197 return nil, errors.Errorf("failed to dump VPP routes: %v", err) 198 } 199 200 for _, route := range routes { 201 key := models.Key(route.Route) 202 value := route.Route 203 origin := kvs.UnknownOrigin 204 205 // correlate with the expected configuration 206 if expCfg, hasExpCfg := expCfg[key]; hasExpCfg { 207 if d.EquivalentRoutes(key, value, expCfg) { 208 value = nbCfg[key] 209 // recreate the key in case the dest. IP or GW IP were replaced with netalloc link 210 key = models.Key(value) 211 origin = kvs.FromNB 212 } 213 } 214 215 retrieved = append(retrieved, adapter.RouteKVWithMetadata{ 216 Key: key, 217 Value: value, 218 Origin: origin, 219 }) 220 } 221 222 return retrieved, nil 223 } 224 225 // Dependencies lists dependencies for a VPP route. 226 func (d *RouteDescriptor) Dependencies(key string, route *l3.Route) []kvs.Dependency { 227 var dependencies []kvs.Dependency 228 // the outgoing interface must exist and be UP 229 if route.OutgoingInterface != "" { 230 dependencies = append(dependencies, kvs.Dependency{ 231 Label: routeOutInterfaceDep, 232 Key: interfaces.InterfaceKey(route.OutgoingInterface), 233 }) 234 } 235 236 // non-zero VRFs 237 var protocol l3.VrfTable_Protocol 238 _, isIPv6, _ := addrs.ParseIPWithPrefix(route.DstNetwork) 239 if isIPv6 { 240 protocol = l3.VrfTable_IPV6 241 } 242 if route.VrfId != 0 { 243 dependencies = append(dependencies, kvs.Dependency{ 244 Label: vrfTableDep, 245 Key: l3.VrfTableKey(route.VrfId, protocol), 246 }) 247 } 248 if route.Type == l3.Route_INTER_VRF && route.ViaVrfId != 0 { 249 dependencies = append(dependencies, kvs.Dependency{ 250 Label: viaVrfTableDep, 251 Key: l3.VrfTableKey(route.ViaVrfId, protocol), 252 }) 253 } 254 255 // if destination network is netalloc reference, then the address must be allocated first 256 allocDep, hasAllocDep := d.addrAlloc.GetAddressAllocDep(route.DstNetwork, 257 "", "dst_network-") 258 if hasAllocDep { 259 dependencies = append(dependencies, allocDep) 260 } 261 // if GW is netalloc reference, then the address must be allocated first 262 allocDep, hasAllocDep = d.addrAlloc.GetAddressAllocDep(route.NextHopAddr, 263 route.OutgoingInterface, "gw_addr-") 264 if hasAllocDep { 265 dependencies = append(dependencies, allocDep) 266 } 267 268 // TODO: perhaps check GW routability 269 return dependencies 270 } 271 272 // equalAddrs compares two IP addresses for equality. 273 func equalAddrs(addr1, addr2 string) bool { 274 if strings.HasPrefix(addr1, netalloc_api.AllocRefPrefix) || 275 strings.HasPrefix(addr2, netalloc_api.AllocRefPrefix) { 276 return addr1 == addr2 277 } 278 a1 := net.ParseIP(addr1) 279 a2 := net.ParseIP(addr2) 280 if a1 == nil || a2 == nil { 281 // if parsing fails, compare as strings 282 return strings.EqualFold(addr1, addr2) 283 } 284 return a1.Equal(a2) 285 } 286 287 // getGwAddr returns the GW address chosen in the given route, handling the cases 288 // when it is left undefined. 289 func getGwAddr(route *l3.Route) string { 290 if route.GetNextHopAddr() != "" { 291 return route.GetNextHopAddr() 292 } 293 // return zero address 294 // - with netalloc'd destination network, just assume it is for IPv4 295 if !strings.HasPrefix(route.GetDstNetwork(), netalloc_api.AllocRefPrefix) { 296 _, dstIPNet, err := net.ParseCIDR(route.GetDstNetwork()) 297 if err != nil { 298 return "" 299 } 300 if dstIPNet.IP.To4() == nil { 301 return net.IPv6zero.String() 302 } 303 } 304 return net.IPv4zero.String() 305 } 306 307 // getWeight returns static route weight, handling the cases when it is left undefined. 308 func getWeight(route *l3.Route) uint32 { 309 if route.Weight == 0 { 310 return defaultWeight 311 } 312 return route.Weight 313 } 314 315 // equalNetworks compares two IP networks for equality. 316 func equalNetworks(net1, net2 string) bool { 317 if strings.HasPrefix(net1, netalloc_api.AllocRefPrefix) || 318 strings.HasPrefix(net2, netalloc_api.AllocRefPrefix) { 319 return net1 == net2 320 } 321 _, n1, err1 := net.ParseCIDR(net1) 322 _, n2, err2 := net.ParseCIDR(net2) 323 if err1 != nil || err2 != nil { 324 // if parsing fails, compare as strings 325 return strings.EqualFold(net1, net2) 326 } 327 return n1.IP.Equal(n2.IP) && bytes.Equal(n1.Mask, n2.Mask) 328 }