Understanding FreeBSD's Route Metric
Understanding FreeBSD’s Route Metric
Route metrics are a fundamental networking concept that determine which path traffic takes when multiple routes to the same destination exist. FreeBSD CURRENT now has full metric support in the routing stack.
Let me walk you through how it works with a practical example.
What is a Metric?
A metric is a numeric value assigned to a route that influences route selection.
When you have multiple routes to the same destination, the kernel prefers the route with the lowest metric.
Think of it as a “cost”, lower cost = preferred path.
Default Metric Behavior
When you create a route without specifying a metric, FreeBSD assigns a default metric of 1.
Wildcard Metric Behavior
We don’t allow you to create a route with metric of 0, That one is used for wildcard. You can delete all of the nexthops without specifiying route metric.
Its netlink behavior is also consistent.
Either you don’t have RTA_PRIORITY or you have one with metric of zero.
If you’re creating a route, it means you’re not specifying its metric, which means default metric (1). If you’re deleting a route, it means you don’t care about its metric, you want to delete all of them.
# Create a route without specifying metric
route -6 add -net 3fff:a::/64 -gateway 3fff::1
# The route automatically gets metric 1
route -n6 get 3fff:a::/64
netstat -rn6W
This is useful because it means all “equal” routes start with the same priority, and you can selectively add routes with higher metrics as fallbacks.
Scenario: Building a Tiered Routing
Let’s say you’re a network operator managing traffic to network 3fff:a::/64. You want:
- Primary paths (metric 10): Two equal-cost gateways for load balancing
- Secondary path (metric 20): A backup gateway that’s only used if primaries fail
- Fallback path (metric 30): A better gateway that becomes available later
Step 1: Add Primary ECMP Routes
You start by creating an Equal-Cost Multi-Path (ECMP) setup with two gateways at metric 10:
For the sake of this example, I want this ECMP route to be unbalanced.
% route -6 add -net 3fff:a::/64 -gateway 3fff::2 -metric 10
add net 3fff:a::/64: gateway 3fff::2 fib 0
% route -6 add -net 3fff:a::/64 -gateway 3fff::3 -weight 2 -metric 10
% route -on6 get 3fff:a::/64
route to: 3fff:a::
destination: 3fff:a::
mask: ffff:ffff:ffff:ffff::
fib: 0
flags: <UP,GATEWAY,DONE,STATIC>
nhops: 2
via gw 3fff::2 iface vtnet1 metric 10 weight 1 mtu 1500 table inet6.0
via gw 3fff::3 iface vtnet1 metric 10 weight 2 mtu 1500
The -weight 2 parameter indicates these are NOT equally balanced within the ECMP group.
Traffic to 3fff:a::/64 now load-balances between the two gateways.
Verify unbalanced ECMP
See the value of Slots column:
% netstat -On6W
Nexthop groups data
Internet6:
GrpIdx NhIdx Weight Slots Gateway Netif Refcnt
14 ------- ------- ------- --------------------------- ----- 2
12 1 1 3fff::2 vtnet1
13 2 2 3fff::3 vtnet1
Current best route: 3fff::2 or 3fff::3 (metric 10) Backup: (none yet)
Step 2: Add a Backup Route
Next, you add a backup gateway with a higher metric (20), so it’s only used if the primary routes fail:
% route -6 add -net 3fff:a::/64 -gateway 3fff::1 -metric 20
add net 3fff:a::/64: gateway 3fff::1 fib 0
% # Verify the results
% route -on6 get 3fff:a::/64
route to: 3fff:a::
destination: 3fff:a::
mask: ffff:ffff:ffff:ffff::
fib: 0
flags: <UP,GATEWAY,DONE,STATIC>
nhops: 3
via gw 3fff::2 iface vtnet1 metric 10 weight 1 mtu 1500 table inet6.0
via gw 3fff::3 iface vtnet1 metric 10 weight 2 mtu 1500
via gw 3fff::1 iface vtnet1 metric 20 weight 1 mtu 1500
% netstat -On6W
Nexthop groups data
Internet6:
GrpIdx NhIdx Weight Slots Gateway Netif Refcnt
16 ------- ------- ------- --------------------------- ----- 2
12 1 0 3fff::2 vtnet1
13 2 2 3fff::3 vtnet1
15 1 0 3fff::1 vtnet1
The system now has three routes, but only uses the metric 10 paths:
Current best route: 3fff::2 or 3fff::3 (metric 10) Backup: 3fff::1 (metric 20), waiting in the RIB
Step 3: Attempting Duplicates (What Fails)
If you try to add the same gateway with the same metric again, the system rejects it:
% route -6 add -net 3fff:a::/64 -gateway 3fff::1 -metric 20
route: message indicates error: File exists
add net 3fff:a::/64: gateway 3fff::1 fib 0: route already in table
This prevents accidental duplicates.
Step 4: Promoting a Better Gateway
Now your new, faster gateway 3fff::1 becomes available. You can immediately promote it to metric 5, better than the current metric 20 routes:
% route -6 add -net 3fff:a::/64 -gateway 3fff::1 -metric 5
add net 3fff:a::/64: gateway 3fff::1 fib 0
% route -on6 get 3fff:a::/64
route to: 3fff:a::
destination: 3fff:a::
mask: ffff:ffff:ffff:ffff::
fib: 0
flags: <UP,GATEWAY,DONE,STATIC>
nhops: 4
via gw 3fff::2 iface vtnet1 metric 10 weight 1 mtu 1500 table inet6.0
via gw 3fff::3 iface vtnet1 metric 10 weight 2 mtu 1500
via gw 3fff::1 iface vtnet1 metric 5 weight 1 mtu 1500
via gw 3fff::1 iface vtnet1 metric 20 weight 1 mtu 1500
% netstat -On6W
Nexthop groups data
Internet6:
GrpIdx NhIdx Weight Slots Gateway Netif Refcnt
17 ------- ------- ------- -------------------------- ----- 2
12 1 0 3fff::2 vtnet1
13 2 0 3fff::3 vtnet1
14 1 1 3fff::1 vtnet1
15 1 0 3fff::1 vtnet1
The routing table now has four routes, but the best one changes:
Current best route: 3fff::1 (metric 5, NEW PRIMARY) Alternate: 3fff::2, 3fff::3 (metric 10) Backup: 3fff::1 (metric 20, This one is shadowed now)
Advanced: Selective Route Deletion
The metric system also enables fine-grained route deletion. You can delete routes by:
- Gateway + Metric: Remove a specific route
- Gateway only: Remove all routes to that gateway
- Metric only: Remove all routes with that metric
Example: Clean Up by Metric
After the gateway 3fff::1 at metric 5 has proven stable, you can remove the old metric 20 routes without affecting your ECMP setup:
% route -n6 delete -net 3fff:a::/64 -metric 20
delete net 3fff:a::/64 fib 0
% route -on6 get 3fff:a::/64
route to: 3fff:a::
destination: 3fff:a::
mask: ffff:ffff:ffff:ffff::
fib: 0
flags: <UP,GATEWAY,DONE,STATIC>
nhops: 3
via gw 3fff::2 iface vtnet1 metric 10 weight 1 mtu 1500 table inet6.0
via gw 3fff::3 iface vtnet1 metric 10 weight 2 mtu 1500
via gw 3fff::1 iface vtnet1 metric 5 weight 1 mtu 1500
Now you have:
- 3fff::1 (metric 5) - best
- 3fff::2, 3fff::3 (metric 10) - secondaries
Or, remove a specific gateway from ECMP without touching other metrics:
% route -n6 delete -net 3fff:a::/64 -gateway 3fff::3 -metric 10
delete net 3fff:a::/64: gateway 3fff::3 fib 0
% route -on6 get 3fff:a::/64
route to: 3fff:a::
destination: 3fff:a::
mask: ffff:ffff:ffff:ffff::
fib: 0
flags: <UP,GATEWAY,DONE,STATIC>
nhops: 2
via gw 3fff::2 iface vtnet1 metric 10 weight 1 mtu 1500 table inet6.0
via gw 3fff::1 iface vtnet1 metric 5 weight 1 mtu 1500
% netstat -On6W
Nexthop groups data
Internet6:
GrpIdx NhIdx Weight Slots Gateway Netif Refcnt
15 ------- ------- ------- -------------------------- ----- 2
12 1 0 3fff::2 vtnet1
14 1 1 3fff::1 vtnet1
Viewing Metrics in netstat
You can now see metric values in netstat output:
netstat -rn6 -W # Wide output includes metric
netstat -rn6 --libxo json # Structured output with metric field
metric filtering using netstat
You can find the metric attribute in libxo output.
% netstat -rn6 --libxo json | jq '.statistics."route-information"."route-table"."rt-family".[]."rt-entry".[] | select(.destination | contains("3fff:a::/64"))'
{
"destination": "3fff:a::/64",
"gateway": "3fff::2",
"flags": "UGS",
"flags_pretty": [
"up",
"gateway",
"static"
],
"weight": 1,
"metric": 10,
"nhg-kidx": 15,
"interface-name": "vtnet1"
}
{
"destination": "3fff:a::/64",
"gateway": "3fff::1",
"flags": "UGS",
"flags_pretty": [
"up",
"gateway",
"static"
],
"weight": 1,
"metric": 5,
"nhg-kidx": 15,
"interface-name": "vtnet1"
}
Viewing all nexthops of a route
You can now see all the nexthops of a route in route output:
% route -on 3fff:a::/64
route to: 3fff:a::
destination: 3fff:a::
mask: ffff:ffff:ffff:ffff::
fib: 0
flags: <UP,GATEWAY,DONE,STATIC>
nhops: 3
via gw 3fff::2 iface vtnet1 metric 10 weight 1 mtu 1500 table inet6.0
via gw 3fff::3 iface vtnet1 metric 10 weight 2 mtu 1500
via gw 3fff::1 iface vtnet1 metric 5 weight 1 mtu 1500
Bird
By default bird uses metric value of 32 during route installation, you can overwrite it by setting metric keyword in kernel section:
protocol kernel kernel6 {
metric 1;
ipv6 {
import all;
export all;
};
learn all;
merge paths;
}