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 }