{"record":{"id":"04bdfad1b9670361","repo":"cilium/cilium","slug":"route-to-destination-s-contains-gateway-s-must","errorCode":null,"errorMessage":"route to destination %s contains gateway %s, must be directly reachable. Add `direct-routing-skip-unreachable` to skip unreachable routes","messagePattern":"route to destination (.+?) contains gateway (.+?), must be directly reachable\\. Add `direct-routing-skip-unreachable` to skip unreachable routes","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/datapath/linux/node.go","lineNumber":191,"sourceCode":"\troutes, err = netlink.RouteGet(nodeIP)\n\tif err != nil {\n\t\terr = fmt.Errorf(\"unable to lookup route for node %s: %w\", nodeIP, err)\n\t\treturn\n\t}\n\n\tif len(routes) == 0 {\n\t\terr = fmt.Errorf(\"no route found to destination %s\", nodeIP.String())\n\t\treturn\n\t}\n\n\tif routes[0].Gw != nil && !routes[0].Gw.IsUnspecified() && !routes[0].Gw.Equal(nodeIP) {\n\t\tif skipUnreachable {\n\t\t\tlog.Debug(\"route to destination contains gateway, skipping route as not directly reachable\",\n\t\t\t\tlogfields.NodeIP, nodeIP,\n\t\t\t\tlogfields.GatewayIP, routes[0].Gw)\n\t\t\taddRoute = false\n\t\t} else {\n\t\t\terr = fmt.Errorf(\"route to destination %s contains gateway %s, must be directly reachable. Add `direct-routing-skip-unreachable` to skip unreachable routes\",\n\t\t\t\tnodeIP, routes[0].Gw.String())\n\t\t}\n\t\treturn\n\t}\n\n\tlinkIndex := routes[0].LinkIndex\n\n\t// Special treatment if the route points to the loopback, lookup the\n\t// local route and use that ifindex\n\tif linkIndex == 1 {\n\t\tfamily := netlink.FAMILY_V4\n\t\tdst := &net.IPNet{IP: nodeIP, Mask: net.CIDRMask(32, 32)}\n\t\tif nodeIP.To4() == nil {\n\t\t\tfamily = netlink.FAMILY_V6\n\t\t\tdst.Mask = net.CIDRMask(128, 128)\n\t\t}\n\n\t\tfilter := &netlink.Route{","sourceCodeStart":173,"sourceCodeEnd":209,"githubUrl":"https://github.com/cilium/cilium/blob/ac7b90affa4baf0642e6685319d56907b3a73a6d/pkg/datapath/linux/node.go#L173-L209","documentation":"When building a direct route, createDirectRouteSpec requires the kernel route to the peer node IP to be directly connected (no gateway), because Cilium would otherwise install a route spec pointing at the wrong next-hop. If routes[0].Gw is set (non-nil, non-unspecified, and different from the node IP itself) and skip-unreachable is not enabled, it returns this error telling the operator the route is gatewayed and how to skip such routes.","triggerScenarios":"installDirectRoute -> createDirectRouteSpec with native/direct routing where `ip route get <nodeIP>` resolves via a gateway (e.g. 'via 10.0.0.1'), and the agent was NOT started with --direct-routing-skip-unreachable=true.","commonSituations":"Nodes on different L2 segments or subnets while autoDirectNodeRoutes/direct routing expects L2 adjacency; cloud environments where inter-node traffic traverses a router (VPC gateway) rather than being directly connected; VPN/WireGuard or SDN setups that present a gatewayed route to peer nodes; migrating from overlay mode to native routing without checking topology.","solutions":["Add --direct-routing-skip-unreachable=true (helm: --set directRoutingSkipUnreachable=true) so gatewayed (non-directly-reachable) node routes are skipped instead of failing","Make peer nodes directly reachable at L2: put nodes on the same subnet/VLAN, or set up direct routes without a gateway","Switch back to encapsulation (tunnel mode, e.g. vxlan/geneve) if nodes are genuinely routed through a gateway — direct routing is not the right mode for this topology","Verify the host route with `ip route get <nodeIP>`; if a gateway is expected, skip-unreachable is the correct configuration"],"exampleFix":"// before: direct routing across a gatewayed route fails\n# cilium-agent --direct-routing=true --auto-direct-node-routes=true\n// after: skip gatewayed routes\n# cilium-agent --direct-routing=true --auto-direct-node-routes=true --direct-routing-skip-unreachable=true","handlingStrategy":"validation","validationCode":"// detect gatewayed routes to peer nodes before enabling direct routing\nconst { execSync } = require('child_process');\nfunction routeIsDirect(ip) {\n  const out = execSync(`ip route get ${ip}`).toString();\n  return !/\\bvia\\s+/.test(out); // a 'via X' means gatewayed, not directly connected\n}\nif (!routeIsDirect('10.0.2.15')) {\n  // enable --direct-routing-skip-unreachable or switch to tunnel mode\n  console.warn('peer node reachable only via gateway; direct routing will fail');\n}","typeGuard":"function isDirectlyConnected(routeGetOutput) {\n  return typeof routeGetOutput === 'string' && !/\\bvia\\s+\\S+/.test(routeGetOutput);\n}","tryCatchPattern":"try {\n  await installDirectRoute(nodeIP, linkIndex);\n} catch (err) {\n  if (/contains gateway/.test(err.message)) {\n    log.warn('gatewayed node route skipped; enable direct-routing-skip-unreachable or use tunneling', { nodeIP });\n    return skipRoute(nodeIP);\n  }\n  throw err;\n}","preventionTips":["Only enable direct routing when all nodes share L2 adjacency (same subnet/VLAN)","Set --direct-routing-skip-unreachable=true when some node pairs are gateway-separated","Prefer tunnel mode (vxlan/geneve) for routed topologies (cloud VPCs, VPNs)","Document the topology requirement for operators deploying with autoDirectNodeRoutes","Test `ip route get <peerIP>` on each node pair during cluster bring-up"],"tags":["network","routing","gateway","cilium","datapath"],"backgroundTag":"gateway-route-unreachable","analyzedSha":"ac7b90affa4baf0642e6685319d56907b3a73a6d","analyzedAt":"2026-08-31T18:27:15.868Z","schemaVersion":2},"datasetVersion":"2026-08-31T22:30:34.772Z"}