Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge branch 'ipv6-no-rtnl-for-ipv6-routing-table'

Kuniyuki Iwashima says:

====================
ipv6: No RTNL for IPv6 routing table.

IPv6 routing tables are protected by each table's lock and work in
the interrupt context, which means we basically don't need RTNL to
modify an IPv6 routing table itself.

Currently, the control paths require RTNL because we may need to
perform device and nexthop lookups; we must prevent dev/nexthop from
going away from the netns.

This, however, can be achieved by RCU as well.

If we are in the RCU critical section while adding an IPv6 route,
synchronize_net() in __dev_change_net_namespace() and
unregister_netdevice_many_notify() guarantee that the dev will not be
moved to another netns or removed.

Also, nexthop is guaranteed not to be freed during the RCU grace period.

If we care about a race between nexthop removal and IPv6 route addition,
we can get rid of RTNL from the control paths.

Patch 1 moves a validation for RTA_MULTIPATH earlier.
Patch 2 removes RTNL for SIOCDELRT and RTM_DELROUTE.
Patch 3 ~ 11 moves validation and memory allocation earlier.
Patch 12 prevents a race between two requests for the same table.
Patch 13 & 14 prevents the nexthop race mentioned above.
Patch 15 removes RTNL for SIOCADDRT and RTM_NEWROUTE.

Test:

The script [0] lets each CPU-X create 100000 routes on table-X in a
batch.

On c7a.metal-48xl EC2 instance with 192 CPUs,

without this series:

$ sudo ./route_test.sh
start adding routes
added 19200000 routes (100000 routes * 192 tables).
total routes: 19200006
Time elapsed: 191577 milliseconds.

with this series:

$ sudo ./route_test.sh
start adding routes
added 19200000 routes (100000 routes * 192 tables).
total routes: 19200006
Time elapsed: 62854 milliseconds.

I changed the number of routes (1000 ~ 100000 per CPU/table) and
consistently saw it finish 3x faster with this series.

[0]

mkdir tmp

NS="test"
ip netns add $NS
ip -n $NS link add veth0 type veth peer veth1
ip -n $NS link set veth0 up
ip -n $NS link set veth1 up

TABLES=()
for i in $(seq $(nproc)); do
TABLES+=("$i")
done

ROUTES=()
for i in {1..100}; do
for j in {1..1000}; do
ROUTES+=("2001:$i:$j::/64")
done
done

for TABLE in "${TABLES[@]}"; do
(
FILE="./tmp/batch-table-$TABLE.txt"
> $FILE
for ROUTE in "${ROUTES[@]}"; do
echo "route add $ROUTE dev veth0 table $TABLE" >> $FILE
done
) &
done

wait

echo "start adding routes"

START_TIME=$(date +%s%3N)
for TABLE in "${TABLES[@]}"; do
ip -n $NS -6 -batch "./tmp/batch-table-$TABLE.txt" &
done

wait
END_TIME=$(date +%s%3N)
ELAPSED_TIME=$((END_TIME - START_TIME))

echo "added $((${#ROUTES[@]} * ${#TABLES[@]})) routes (${#ROUTES[@]} routes * ${#TABLES[@]} tables)."
echo "total routes: $(ip -n $NS -6 route show table all | wc -l)" # Just for debug
echo "Time elapsed: ${ELAPSED_TIME} milliseconds."

ip netns del $NS
rm -fr ./tmp/

v2: https://lore.kernel.org/netdev/20250409011243.26195-1-kuniyu@amazon.com/
v1: https://lore.kernel.org/netdev/20250321040131.21057-1-kuniyu@amazon.com/
====================

Link: https://patch.msgid.link/20250418000443.43734-1-kuniyu@amazon.com
Signed-off-by: Paolo Abeni <pabeni@redhat.com>

+456 -249
+1
include/net/ip6_fib.h
··· 198 198 fib6_destroying:1, 199 199 unused:4; 200 200 201 + struct list_head purge_link; 201 202 struct rcu_head rcu; 202 203 struct nexthop *nh; 203 204 struct fib6_nh fib6_nh[];
+1
include/net/netns/ipv6.h
··· 72 72 struct rt6_statistics *rt6_stats; 73 73 struct timer_list ip6_fib_timer; 74 74 struct hlist_head *fib_table_hash; 75 + spinlock_t fib_table_hash_lock; 75 76 struct fib6_table *fib6_main_tbl; 76 77 struct list_head fib6_walkers; 77 78 rwlock_t fib6_walker_lock;
+2
include/net/nexthop.h
··· 152 152 u8 protocol; /* app managing this nh */ 153 153 u8 nh_flags; 154 154 bool is_group; 155 + bool dead; 156 + spinlock_t lock; /* protect dead and f6i_list */ 155 157 156 158 refcount_t refcnt; 157 159 struct rcu_head rcu;
+6 -4
net/ipv4/fib_semantics.c
··· 617 617 { 618 618 int err; 619 619 620 - nhc->nhc_pcpu_rth_output = alloc_percpu_gfp(struct rtable __rcu *, 621 - gfp_flags); 622 - if (!nhc->nhc_pcpu_rth_output) 623 - return -ENOMEM; 620 + if (!nhc->nhc_pcpu_rth_output) { 621 + nhc->nhc_pcpu_rth_output = alloc_percpu_gfp(struct rtable __rcu *, 622 + gfp_flags); 623 + if (!nhc->nhc_pcpu_rth_output) 624 + return -ENOMEM; 625 + } 624 626 625 627 if (encap) { 626 628 struct lwtunnel_state *lwtstate;
+17 -5
net/ipv4/nexthop.c
··· 541 541 INIT_LIST_HEAD(&nh->f6i_list); 542 542 INIT_LIST_HEAD(&nh->grp_list); 543 543 INIT_LIST_HEAD(&nh->fdb_list); 544 + spin_lock_init(&nh->lock); 544 545 } 545 546 return nh; 546 547 } ··· 1556 1555 if (nh->is_group) { 1557 1556 struct nh_group *nhg; 1558 1557 1559 - nhg = rtnl_dereference(nh->nh_grp); 1558 + nhg = rcu_dereference_rtnl(nh->nh_grp); 1560 1559 if (nhg->has_v4) 1561 1560 goto no_v4_nh; 1562 1561 is_fdb_nh = nhg->fdb_nh; 1563 1562 } else { 1564 - nhi = rtnl_dereference(nh->nh_info); 1563 + nhi = rcu_dereference_rtnl(nh->nh_info); 1565 1564 if (nhi->family == AF_INET) 1566 1565 goto no_v4_nh; 1567 1566 is_fdb_nh = nhi->fdb_nh; ··· 2119 2118 /* not called for nexthop replace */ 2120 2119 static void __remove_nexthop_fib(struct net *net, struct nexthop *nh) 2121 2120 { 2122 - struct fib6_info *f6i, *tmp; 2121 + struct fib6_info *f6i; 2123 2122 bool do_flush = false; 2124 2123 struct fib_info *fi; 2125 2124 ··· 2130 2129 if (do_flush) 2131 2130 fib_flush(net); 2132 2131 2133 - /* ip6_del_rt removes the entry from this list hence the _safe */ 2134 - list_for_each_entry_safe(f6i, tmp, &nh->f6i_list, nh_list) { 2132 + spin_lock_bh(&nh->lock); 2133 + 2134 + nh->dead = true; 2135 + 2136 + while (!list_empty(&nh->f6i_list)) { 2137 + f6i = list_first_entry(&nh->f6i_list, typeof(*f6i), nh_list); 2138 + 2135 2139 /* __ip6_del_rt does a release, so do a hold here */ 2136 2140 fib6_info_hold(f6i); 2141 + 2142 + spin_unlock_bh(&nh->lock); 2137 2143 ipv6_stub->ip6_del_rt(net, f6i, 2138 2144 !READ_ONCE(net->ipv4.sysctl_nexthop_compat_mode)); 2145 + 2146 + spin_lock_bh(&nh->lock); 2139 2147 } 2148 + 2149 + spin_unlock_bh(&nh->lock); 2140 2150 } 2141 2151 2142 2152 static void __remove_nexthop(struct net *net, struct nexthop *nh,
+68 -16
net/ipv6/ip6_fib.c
··· 249 249 250 250 struct fib6_table *fib6_new_table(struct net *net, u32 id) 251 251 { 252 - struct fib6_table *tb; 252 + struct fib6_table *tb, *new_tb; 253 253 254 254 if (id == 0) 255 255 id = RT6_TABLE_MAIN; 256 + 256 257 tb = fib6_get_table(net, id); 257 258 if (tb) 258 259 return tb; 259 260 260 - tb = fib6_alloc_table(net, id); 261 - if (tb) 262 - fib6_link_table(net, tb); 261 + new_tb = fib6_alloc_table(net, id); 262 + if (!new_tb) 263 + return NULL; 263 264 264 - return tb; 265 + spin_lock_bh(&net->ipv6.fib_table_hash_lock); 266 + 267 + tb = fib6_get_table(net, id); 268 + if (unlikely(tb)) { 269 + spin_unlock_bh(&net->ipv6.fib_table_hash_lock); 270 + kfree(new_tb); 271 + return tb; 272 + } 273 + 274 + fib6_link_table(net, new_tb); 275 + 276 + spin_unlock_bh(&net->ipv6.fib_table_hash_lock); 277 + 278 + return new_tb; 265 279 } 266 280 EXPORT_SYMBOL_GPL(fib6_new_table); 267 281 ··· 1048 1034 rt6_flush_exceptions(rt); 1049 1035 fib6_drop_pcpu_from(rt, table); 1050 1036 1051 - if (rt->nh && !list_empty(&rt->nh_list)) 1052 - list_del_init(&rt->nh_list); 1037 + if (rt->nh) { 1038 + spin_lock(&rt->nh->lock); 1039 + 1040 + if (!list_empty(&rt->nh_list)) 1041 + list_del_init(&rt->nh_list); 1042 + 1043 + spin_unlock(&rt->nh->lock); 1044 + } 1053 1045 1054 1046 if (refcount_read(&rt->fib6_ref) != 1) { 1055 1047 /* This route is used as dummy address holder in some split ··· 1089 1069 */ 1090 1070 1091 1071 static int fib6_add_rt2node(struct fib6_node *fn, struct fib6_info *rt, 1092 - struct nl_info *info, 1093 - struct netlink_ext_ack *extack) 1072 + struct nl_info *info, struct netlink_ext_ack *extack, 1073 + struct list_head *purge_list) 1094 1074 { 1095 1075 struct fib6_info *leaf = rcu_dereference_protected(fn->leaf, 1096 1076 lockdep_is_held(&rt->fib6_table->tb6_lock)); ··· 1314 1294 } 1315 1295 nsiblings = iter->fib6_nsiblings; 1316 1296 iter->fib6_node = NULL; 1317 - fib6_purge_rt(iter, fn, info->nl_net); 1297 + list_add(&iter->purge_link, purge_list); 1318 1298 if (rcu_access_pointer(fn->rr_ptr) == iter) 1319 1299 fn->rr_ptr = NULL; 1320 - fib6_info_release(iter); 1321 1300 1322 1301 if (nsiblings) { 1323 1302 /* Replacing an ECMP route, remove all siblings */ ··· 1329 1310 if (rt6_qualify_for_ecmp(iter)) { 1330 1311 *ins = iter->fib6_next; 1331 1312 iter->fib6_node = NULL; 1332 - fib6_purge_rt(iter, fn, info->nl_net); 1313 + list_add(&iter->purge_link, purge_list); 1333 1314 if (rcu_access_pointer(fn->rr_ptr) == iter) 1334 1315 fn->rr_ptr = NULL; 1335 - fib6_info_release(iter); 1336 1316 nsiblings--; 1337 1317 info->nl_net->ipv6.rt6_stats->fib_rt_entries--; 1338 1318 } else { ··· 1345 1327 } 1346 1328 1347 1329 return 0; 1330 + } 1331 + 1332 + static int fib6_add_rt2node_nh(struct fib6_node *fn, struct fib6_info *rt, 1333 + struct nl_info *info, struct netlink_ext_ack *extack, 1334 + struct list_head *purge_list) 1335 + { 1336 + int err; 1337 + 1338 + spin_lock(&rt->nh->lock); 1339 + 1340 + if (rt->nh->dead) { 1341 + NL_SET_ERR_MSG(extack, "Nexthop has been deleted"); 1342 + err = -EINVAL; 1343 + } else { 1344 + err = fib6_add_rt2node(fn, rt, info, extack, purge_list); 1345 + if (!err) 1346 + list_add(&rt->nh_list, &rt->nh->f6i_list); 1347 + } 1348 + 1349 + spin_unlock(&rt->nh->lock); 1350 + 1351 + return err; 1348 1352 } 1349 1353 1350 1354 static void fib6_start_gc(struct net *net, struct fib6_info *rt) ··· 1423 1383 struct nl_info *info, struct netlink_ext_ack *extack) 1424 1384 { 1425 1385 struct fib6_table *table = rt->fib6_table; 1386 + LIST_HEAD(purge_list); 1426 1387 struct fib6_node *fn; 1427 1388 #ifdef CONFIG_IPV6_SUBTREES 1428 1389 struct fib6_node *pn = NULL; ··· 1526 1485 } 1527 1486 #endif 1528 1487 1529 - err = fib6_add_rt2node(fn, rt, info, extack); 1488 + if (rt->nh) 1489 + err = fib6_add_rt2node_nh(fn, rt, info, extack, &purge_list); 1490 + else 1491 + err = fib6_add_rt2node(fn, rt, info, extack, &purge_list); 1530 1492 if (!err) { 1531 - if (rt->nh) 1532 - list_add(&rt->nh_list, &rt->nh->f6i_list); 1493 + struct fib6_info *iter, *next; 1494 + 1495 + list_for_each_entry_safe(iter, next, &purge_list, purge_link) { 1496 + list_del(&iter->purge_link); 1497 + fib6_purge_rt(iter, fn, info->nl_net); 1498 + fib6_info_release(iter); 1499 + } 1500 + 1533 1501 __fib6_update_sernum_upto_root(rt, fib6_new_sernum(info->nl_net)); 1534 1502 1535 1503 if (rt->fib6_flags & RTF_EXPIRES) ··· 2472 2422 net->ipv6.fib_table_hash = kzalloc(size, GFP_KERNEL); 2473 2423 if (!net->ipv6.fib_table_hash) 2474 2424 goto out_rt6_stats; 2425 + 2426 + spin_lock_init(&net->ipv6.fib_table_hash_lock); 2475 2427 2476 2428 net->ipv6.fib6_main_tbl = kzalloc(sizeof(*net->ipv6.fib6_main_tbl), 2477 2429 GFP_KERNEL);
+361 -224
net/ipv6/route.c
··· 3665 3665 goto out; 3666 3666 3667 3667 pcpu_alloc: 3668 - fib6_nh->rt6i_pcpu = alloc_percpu_gfp(struct rt6_info *, gfp_flags); 3669 3668 if (!fib6_nh->rt6i_pcpu) { 3670 - err = -ENOMEM; 3671 - goto out; 3669 + fib6_nh->rt6i_pcpu = alloc_percpu_gfp(struct rt6_info *, gfp_flags); 3670 + if (!fib6_nh->rt6i_pcpu) { 3671 + err = -ENOMEM; 3672 + goto out; 3673 + } 3672 3674 } 3673 3675 3674 3676 fib6_nh->fib_nh_dev = dev; ··· 3730 3728 } 3731 3729 } 3732 3730 3731 + static int fib6_nh_prealloc_percpu(struct fib6_nh *fib6_nh, gfp_t gfp_flags) 3732 + { 3733 + struct fib_nh_common *nhc = &fib6_nh->nh_common; 3734 + 3735 + fib6_nh->rt6i_pcpu = alloc_percpu_gfp(struct rt6_info *, gfp_flags); 3736 + if (!fib6_nh->rt6i_pcpu) 3737 + return -ENOMEM; 3738 + 3739 + nhc->nhc_pcpu_rth_output = alloc_percpu_gfp(struct rtable __rcu *, 3740 + gfp_flags); 3741 + if (!nhc->nhc_pcpu_rth_output) { 3742 + free_percpu(fib6_nh->rt6i_pcpu); 3743 + return -ENOMEM; 3744 + } 3745 + 3746 + return 0; 3747 + } 3748 + 3733 3749 static struct fib6_info *ip6_route_info_create(struct fib6_config *cfg, 3734 - gfp_t gfp_flags, 3735 - struct netlink_ext_ack *extack) 3750 + gfp_t gfp_flags, 3751 + struct netlink_ext_ack *extack) 3736 3752 { 3737 3753 struct net *net = cfg->fc_nlinfo.nl_net; 3738 - struct fib6_info *rt = NULL; 3739 - struct nexthop *nh = NULL; 3740 3754 struct fib6_table *table; 3741 - struct fib6_nh *fib6_nh; 3742 - int err = -EINVAL; 3743 - int addr_type; 3755 + struct fib6_info *rt; 3756 + int err; 3744 3757 3745 - /* RTF_PCPU is an internal flag; can not be set by userspace */ 3746 - if (cfg->fc_flags & RTF_PCPU) { 3747 - NL_SET_ERR_MSG(extack, "Userspace can not set RTF_PCPU"); 3748 - goto out; 3749 - } 3750 - 3751 - /* RTF_CACHE is an internal flag; can not be set by userspace */ 3752 - if (cfg->fc_flags & RTF_CACHE) { 3753 - NL_SET_ERR_MSG(extack, "Userspace can not set RTF_CACHE"); 3754 - goto out; 3755 - } 3756 - 3757 - if (cfg->fc_type > RTN_MAX) { 3758 - NL_SET_ERR_MSG(extack, "Invalid route type"); 3759 - goto out; 3760 - } 3761 - 3762 - if (cfg->fc_dst_len > 128) { 3763 - NL_SET_ERR_MSG(extack, "Invalid prefix length"); 3764 - goto out; 3765 - } 3766 - if (cfg->fc_src_len > 128) { 3767 - NL_SET_ERR_MSG(extack, "Invalid source address length"); 3768 - goto out; 3769 - } 3770 - #ifndef CONFIG_IPV6_SUBTREES 3771 - if (cfg->fc_src_len) { 3772 - NL_SET_ERR_MSG(extack, 3773 - "Specifying source address requires IPV6_SUBTREES to be enabled"); 3774 - goto out; 3775 - } 3776 - #endif 3777 - if (cfg->fc_nh_id) { 3778 - nh = nexthop_find_by_id(net, cfg->fc_nh_id); 3779 - if (!nh) { 3780 - NL_SET_ERR_MSG(extack, "Nexthop id does not exist"); 3781 - goto out; 3782 - } 3783 - err = fib6_check_nexthop(nh, cfg, extack); 3784 - if (err) 3785 - goto out; 3786 - } 3787 - 3788 - err = -ENOBUFS; 3789 3758 if (cfg->fc_nlinfo.nlh && 3790 3759 !(cfg->fc_nlinfo.nlh->nlmsg_flags & NLM_F_CREATE)) { 3791 3760 table = fib6_get_table(net, cfg->fc_table); ··· 3767 3794 } else { 3768 3795 table = fib6_new_table(net, cfg->fc_table); 3769 3796 } 3797 + if (!table) { 3798 + err = -ENOBUFS; 3799 + goto err; 3800 + } 3770 3801 3771 - if (!table) 3772 - goto out; 3773 - 3774 - err = -ENOMEM; 3775 - rt = fib6_info_alloc(gfp_flags, !nh); 3776 - if (!rt) 3777 - goto out; 3802 + rt = fib6_info_alloc(gfp_flags, !cfg->fc_nh_id); 3803 + if (!rt) { 3804 + err = -ENOMEM; 3805 + goto err; 3806 + } 3778 3807 3779 3808 rt->fib6_metrics = ip_fib_metrics_init(cfg->fc_mx, cfg->fc_mx_len, 3780 3809 extack); 3781 3810 if (IS_ERR(rt->fib6_metrics)) { 3782 3811 err = PTR_ERR(rt->fib6_metrics); 3783 - /* Do not leave garbage there. */ 3784 - rt->fib6_metrics = (struct dst_metrics *)&dst_default_metrics; 3785 - goto out_free; 3812 + goto free; 3813 + } 3814 + 3815 + if (!cfg->fc_nh_id) { 3816 + err = fib6_nh_prealloc_percpu(&rt->fib6_nh[0], gfp_flags); 3817 + if (err) 3818 + goto free_metrics; 3786 3819 } 3787 3820 3788 3821 if (cfg->fc_flags & RTF_ADDRCONF) ··· 3796 3817 3797 3818 if (cfg->fc_flags & RTF_EXPIRES) 3798 3819 fib6_set_expires(rt, jiffies + 3799 - clock_t_to_jiffies(cfg->fc_expires)); 3820 + clock_t_to_jiffies(cfg->fc_expires)); 3800 3821 3801 3822 if (cfg->fc_protocol == RTPROT_UNSPEC) 3802 3823 cfg->fc_protocol = RTPROT_BOOT; 3803 - rt->fib6_protocol = cfg->fc_protocol; 3804 3824 3825 + rt->fib6_protocol = cfg->fc_protocol; 3805 3826 rt->fib6_table = table; 3806 3827 rt->fib6_metric = cfg->fc_metric; 3807 3828 rt->fib6_type = cfg->fc_type ? : RTN_UNICAST; ··· 3814 3835 ipv6_addr_prefix(&rt->fib6_src.addr, &cfg->fc_src, cfg->fc_src_len); 3815 3836 rt->fib6_src.plen = cfg->fc_src_len; 3816 3837 #endif 3817 - if (nh) { 3818 - if (rt->fib6_src.plen) { 3819 - NL_SET_ERR_MSG(extack, "Nexthops can not be used with source routing"); 3838 + return rt; 3839 + free_metrics: 3840 + ip_fib_metrics_put(rt->fib6_metrics); 3841 + free: 3842 + kfree(rt); 3843 + err: 3844 + return ERR_PTR(err); 3845 + } 3846 + 3847 + static int ip6_route_info_create_nh(struct fib6_info *rt, 3848 + struct fib6_config *cfg, 3849 + struct netlink_ext_ack *extack) 3850 + { 3851 + struct net *net = cfg->fc_nlinfo.nl_net; 3852 + struct fib6_nh *fib6_nh; 3853 + int err; 3854 + 3855 + if (cfg->fc_nh_id) { 3856 + struct nexthop *nh; 3857 + 3858 + nh = nexthop_find_by_id(net, cfg->fc_nh_id); 3859 + if (!nh) { 3820 3860 err = -EINVAL; 3861 + NL_SET_ERR_MSG(extack, "Nexthop id does not exist"); 3821 3862 goto out_free; 3822 3863 } 3864 + 3865 + err = fib6_check_nexthop(nh, cfg, extack); 3866 + if (err) 3867 + goto out_free; 3868 + 3823 3869 if (!nexthop_get(nh)) { 3824 3870 NL_SET_ERR_MSG(extack, "Nexthop has been deleted"); 3825 3871 err = -ENOENT; 3826 3872 goto out_free; 3827 3873 } 3874 + 3828 3875 rt->nh = nh; 3829 3876 fib6_nh = nexthop_fib6_nh(rt->nh); 3830 3877 } else { 3831 - err = fib6_nh_init(net, rt->fib6_nh, cfg, gfp_flags, extack); 3878 + int addr_type; 3879 + 3880 + err = fib6_nh_init(net, rt->fib6_nh, cfg, GFP_ATOMIC, extack); 3832 3881 if (err) 3833 - goto out; 3882 + goto out_release; 3834 3883 3835 3884 fib6_nh = rt->fib6_nh; 3836 3885 ··· 3877 3870 if (!ipv6_chk_addr(net, &cfg->fc_prefsrc, dev, 0)) { 3878 3871 NL_SET_ERR_MSG(extack, "Invalid source address"); 3879 3872 err = -EINVAL; 3880 - goto out; 3873 + goto out_release; 3881 3874 } 3882 3875 rt->fib6_prefsrc.addr = cfg->fc_prefsrc; 3883 3876 rt->fib6_prefsrc.plen = 128; 3884 - } else 3885 - rt->fib6_prefsrc.plen = 0; 3877 + } 3886 3878 3887 - return rt; 3888 - out: 3879 + return 0; 3880 + out_release: 3889 3881 fib6_info_release(rt); 3890 - return ERR_PTR(err); 3882 + return err; 3891 3883 out_free: 3892 3884 ip_fib_metrics_put(rt->fib6_metrics); 3893 3885 kfree(rt); 3894 - return ERR_PTR(err); 3886 + return err; 3895 3887 } 3896 3888 3897 3889 int ip6_route_add(struct fib6_config *cfg, gfp_t gfp_flags, ··· 3903 3897 if (IS_ERR(rt)) 3904 3898 return PTR_ERR(rt); 3905 3899 3900 + rcu_read_lock(); 3901 + 3902 + err = ip6_route_info_create_nh(rt, cfg, extack); 3903 + if (err) 3904 + goto unlock; 3905 + 3906 3906 err = __ip6_ins_rt(rt, &cfg->fc_nlinfo, extack); 3907 3907 fib6_info_release(rt); 3908 + unlock: 3909 + rcu_read_unlock(); 3908 3910 3909 3911 return err; 3910 3912 } ··· 4139 4125 if (rt->nh) { 4140 4126 if (!fib6_info_hold_safe(rt)) 4141 4127 continue; 4142 - rcu_read_unlock(); 4143 4128 4144 - return __ip6_del_rt(rt, &cfg->fc_nlinfo); 4129 + err = __ip6_del_rt(rt, &cfg->fc_nlinfo); 4130 + break; 4145 4131 } 4146 4132 if (cfg->fc_nh_id) 4147 4133 continue; ··· 4156 4142 continue; 4157 4143 if (!fib6_info_hold_safe(rt)) 4158 4144 continue; 4159 - rcu_read_unlock(); 4160 4145 4161 4146 /* if gateway was specified only delete the one hop */ 4162 4147 if (cfg->fc_flags & RTF_GATEWAY) 4163 - return __ip6_del_rt(rt, &cfg->fc_nlinfo); 4164 - 4165 - return __ip6_del_rt_siblings(rt, cfg); 4148 + err = __ip6_del_rt(rt, &cfg->fc_nlinfo); 4149 + else 4150 + err = __ip6_del_rt_siblings(rt, cfg); 4151 + break; 4166 4152 } 4167 4153 } 4168 4154 rcu_read_unlock(); ··· 4531 4517 4532 4518 rtmsg_to_fib6_config(net, rtmsg, &cfg); 4533 4519 4534 - rtnl_lock(); 4535 4520 switch (cmd) { 4536 4521 case SIOCADDRT: 4537 4522 /* Only do the default setting of fc_metric in route adding */ ··· 4542 4529 err = ip6_route_del(&cfg, NULL); 4543 4530 break; 4544 4531 } 4545 - rtnl_unlock(); 4532 + 4546 4533 return err; 4547 4534 } 4548 4535 ··· 4632 4619 .fc_ignore_dev_down = true, 4633 4620 }; 4634 4621 struct fib6_info *f6i; 4622 + int err; 4635 4623 4636 4624 if (anycast) { 4637 4625 cfg.fc_type = RTN_ANYCAST; ··· 4643 4629 } 4644 4630 4645 4631 f6i = ip6_route_info_create(&cfg, gfp_flags, extack); 4646 - if (!IS_ERR(f6i)) { 4647 - f6i->dst_nocount = true; 4632 + if (IS_ERR(f6i)) 4633 + return f6i; 4648 4634 4649 - if (!anycast && 4650 - (READ_ONCE(net->ipv6.devconf_all->disable_policy) || 4651 - READ_ONCE(idev->cnf.disable_policy))) 4652 - f6i->dst_nopolicy = true; 4653 - } 4635 + err = ip6_route_info_create_nh(f6i, &cfg, extack); 4636 + if (err) 4637 + return ERR_PTR(err); 4638 + 4639 + f6i->dst_nocount = true; 4640 + 4641 + if (!anycast && 4642 + (READ_ONCE(net->ipv6.devconf_all->disable_policy) || 4643 + READ_ONCE(idev->cnf.disable_policy))) 4644 + f6i->dst_nopolicy = true; 4654 4645 4655 4646 return f6i; 4656 4647 } ··· 5070 5051 [RTA_FLOWLABEL] = { .type = NLA_BE32 }, 5071 5052 }; 5072 5053 5054 + static int rtm_to_fib6_multipath_config(struct fib6_config *cfg, 5055 + struct netlink_ext_ack *extack, 5056 + bool newroute) 5057 + { 5058 + struct rtnexthop *rtnh; 5059 + int remaining; 5060 + 5061 + remaining = cfg->fc_mp_len; 5062 + rtnh = (struct rtnexthop *)cfg->fc_mp; 5063 + 5064 + if (!rtnh_ok(rtnh, remaining)) { 5065 + NL_SET_ERR_MSG(extack, "Invalid nexthop configuration - no valid nexthops"); 5066 + return -EINVAL; 5067 + } 5068 + 5069 + do { 5070 + bool has_gateway = cfg->fc_flags & RTF_GATEWAY; 5071 + int attrlen = rtnh_attrlen(rtnh); 5072 + 5073 + if (attrlen > 0) { 5074 + struct nlattr *nla, *attrs; 5075 + 5076 + attrs = rtnh_attrs(rtnh); 5077 + nla = nla_find(attrs, attrlen, RTA_GATEWAY); 5078 + if (nla) { 5079 + if (nla_len(nla) < sizeof(cfg->fc_gateway)) { 5080 + NL_SET_ERR_MSG(extack, 5081 + "Invalid IPv6 address in RTA_GATEWAY"); 5082 + return -EINVAL; 5083 + } 5084 + 5085 + has_gateway = true; 5086 + } 5087 + } 5088 + 5089 + if (newroute && (cfg->fc_nh_id || !has_gateway)) { 5090 + NL_SET_ERR_MSG(extack, 5091 + "Device only routes can not be added for IPv6 using the multipath API."); 5092 + return -EINVAL; 5093 + } 5094 + 5095 + rtnh = rtnh_next(rtnh, &remaining); 5096 + } while (rtnh_ok(rtnh, remaining)); 5097 + 5098 + return lwtunnel_valid_encap_type_attr(cfg->fc_mp, cfg->fc_mp_len, 5099 + extack, false); 5100 + } 5101 + 5073 5102 static int rtm_to_fib6_config(struct sk_buff *skb, struct nlmsghdr *nlh, 5074 5103 struct fib6_config *cfg, 5075 5104 struct netlink_ext_ack *extack) 5076 5105 { 5077 - struct rtmsg *rtm; 5106 + bool newroute = nlh->nlmsg_type == RTM_NEWROUTE; 5078 5107 struct nlattr *tb[RTA_MAX+1]; 5108 + struct rtmsg *rtm; 5079 5109 unsigned int pref; 5080 5110 int err; 5081 5111 ··· 5233 5165 cfg->fc_mp = nla_data(tb[RTA_MULTIPATH]); 5234 5166 cfg->fc_mp_len = nla_len(tb[RTA_MULTIPATH]); 5235 5167 5236 - err = lwtunnel_valid_encap_type_attr(cfg->fc_mp, 5237 - cfg->fc_mp_len, 5238 - extack, true); 5168 + err = rtm_to_fib6_multipath_config(cfg, extack, newroute); 5239 5169 if (err < 0) 5240 5170 goto errout; 5241 5171 } ··· 5253 5187 cfg->fc_encap_type = nla_get_u16(tb[RTA_ENCAP_TYPE]); 5254 5188 5255 5189 err = lwtunnel_valid_encap_type(cfg->fc_encap_type, 5256 - extack, true); 5190 + extack, false); 5257 5191 if (err < 0) 5258 5192 goto errout; 5259 5193 } ··· 5267 5201 } 5268 5202 } 5269 5203 5204 + if (newroute) { 5205 + /* RTF_PCPU is an internal flag; can not be set by userspace */ 5206 + if (cfg->fc_flags & RTF_PCPU) { 5207 + NL_SET_ERR_MSG(extack, "Userspace can not set RTF_PCPU"); 5208 + goto errout; 5209 + } 5210 + 5211 + /* RTF_CACHE is an internal flag; can not be set by userspace */ 5212 + if (cfg->fc_flags & RTF_CACHE) { 5213 + NL_SET_ERR_MSG(extack, "Userspace can not set RTF_CACHE"); 5214 + goto errout; 5215 + } 5216 + 5217 + if (cfg->fc_type > RTN_MAX) { 5218 + NL_SET_ERR_MSG(extack, "Invalid route type"); 5219 + goto errout; 5220 + } 5221 + 5222 + if (cfg->fc_dst_len > 128) { 5223 + NL_SET_ERR_MSG(extack, "Invalid prefix length"); 5224 + goto errout; 5225 + } 5226 + 5227 + #ifdef CONFIG_IPV6_SUBTREES 5228 + if (cfg->fc_src_len > 128) { 5229 + NL_SET_ERR_MSG(extack, "Invalid source address length"); 5230 + goto errout; 5231 + } 5232 + 5233 + if (cfg->fc_nh_id && cfg->fc_src_len) { 5234 + NL_SET_ERR_MSG(extack, "Nexthops can not be used with source routing"); 5235 + goto errout; 5236 + } 5237 + #else 5238 + if (cfg->fc_src_len) { 5239 + NL_SET_ERR_MSG(extack, 5240 + "Specifying source address requires IPV6_SUBTREES to be enabled"); 5241 + goto errout; 5242 + } 5243 + #endif 5244 + } 5245 + 5270 5246 err = 0; 5271 5247 errout: 5272 5248 return err; ··· 5317 5209 struct rt6_nh { 5318 5210 struct fib6_info *fib6_info; 5319 5211 struct fib6_config r_cfg; 5320 - struct list_head next; 5212 + struct list_head list; 5213 + int weight; 5321 5214 }; 5322 5215 5323 - static int ip6_route_info_append(struct net *net, 5324 - struct list_head *rt6_nh_list, 5325 - struct fib6_info *rt, 5326 - struct fib6_config *r_cfg) 5216 + static void ip6_route_mpath_info_cleanup(struct list_head *rt6_nh_list) 5327 5217 { 5328 - struct rt6_nh *nh; 5329 - int err = -EEXIST; 5218 + struct rt6_nh *nh, *nh_next; 5330 5219 5331 - list_for_each_entry(nh, rt6_nh_list, next) { 5332 - /* check if fib6_info already exists */ 5333 - if (rt6_duplicate_nexthop(nh->fib6_info, rt)) 5334 - return err; 5220 + list_for_each_entry_safe(nh, nh_next, rt6_nh_list, list) { 5221 + struct fib6_info *rt = nh->fib6_info; 5222 + 5223 + if (rt) { 5224 + free_percpu(rt->fib6_nh->nh_common.nhc_pcpu_rth_output); 5225 + free_percpu(rt->fib6_nh->rt6i_pcpu); 5226 + ip_fib_metrics_put(rt->fib6_metrics); 5227 + kfree(rt); 5228 + } 5229 + 5230 + list_del(&nh->list); 5231 + kfree(nh); 5232 + } 5233 + } 5234 + 5235 + static int ip6_route_mpath_info_create(struct list_head *rt6_nh_list, 5236 + struct fib6_config *cfg, 5237 + struct netlink_ext_ack *extack) 5238 + { 5239 + struct rtnexthop *rtnh; 5240 + int remaining; 5241 + int err; 5242 + 5243 + remaining = cfg->fc_mp_len; 5244 + rtnh = (struct rtnexthop *)cfg->fc_mp; 5245 + 5246 + /* Parse a Multipath Entry and build a list (rt6_nh_list) of 5247 + * fib6_info structs per nexthop 5248 + */ 5249 + while (rtnh_ok(rtnh, remaining)) { 5250 + struct fib6_config r_cfg; 5251 + struct fib6_info *rt; 5252 + struct rt6_nh *nh; 5253 + int attrlen; 5254 + 5255 + nh = kzalloc(sizeof(*nh), GFP_KERNEL); 5256 + if (!nh) { 5257 + err = -ENOMEM; 5258 + goto err; 5259 + } 5260 + 5261 + list_add_tail(&nh->list, rt6_nh_list); 5262 + 5263 + memcpy(&r_cfg, cfg, sizeof(*cfg)); 5264 + if (rtnh->rtnh_ifindex) 5265 + r_cfg.fc_ifindex = rtnh->rtnh_ifindex; 5266 + 5267 + attrlen = rtnh_attrlen(rtnh); 5268 + if (attrlen > 0) { 5269 + struct nlattr *nla, *attrs = rtnh_attrs(rtnh); 5270 + 5271 + nla = nla_find(attrs, attrlen, RTA_GATEWAY); 5272 + if (nla) { 5273 + r_cfg.fc_gateway = nla_get_in6_addr(nla); 5274 + r_cfg.fc_flags |= RTF_GATEWAY; 5275 + } 5276 + 5277 + r_cfg.fc_encap = nla_find(attrs, attrlen, RTA_ENCAP); 5278 + nla = nla_find(attrs, attrlen, RTA_ENCAP_TYPE); 5279 + if (nla) 5280 + r_cfg.fc_encap_type = nla_get_u16(nla); 5281 + } 5282 + 5283 + r_cfg.fc_flags |= (rtnh->rtnh_flags & RTNH_F_ONLINK); 5284 + 5285 + rt = ip6_route_info_create(&r_cfg, GFP_KERNEL, extack); 5286 + if (IS_ERR(rt)) { 5287 + err = PTR_ERR(rt); 5288 + goto err; 5289 + } 5290 + 5291 + nh->fib6_info = rt; 5292 + nh->weight = rtnh->rtnh_hops + 1; 5293 + memcpy(&nh->r_cfg, &r_cfg, sizeof(r_cfg)); 5294 + 5295 + rtnh = rtnh_next(rtnh, &remaining); 5335 5296 } 5336 5297 5337 - nh = kzalloc(sizeof(*nh), GFP_KERNEL); 5338 - if (!nh) 5339 - return -ENOMEM; 5340 - nh->fib6_info = rt; 5341 - memcpy(&nh->r_cfg, r_cfg, sizeof(*r_cfg)); 5342 - list_add_tail(&nh->next, rt6_nh_list); 5343 - 5344 5298 return 0; 5299 + err: 5300 + ip6_route_mpath_info_cleanup(rt6_nh_list); 5301 + return err; 5302 + } 5303 + 5304 + static int ip6_route_mpath_info_create_nh(struct list_head *rt6_nh_list, 5305 + struct netlink_ext_ack *extack) 5306 + { 5307 + struct rt6_nh *nh, *nh_next, *nh_tmp; 5308 + LIST_HEAD(tmp); 5309 + int err; 5310 + 5311 + list_for_each_entry_safe(nh, nh_next, rt6_nh_list, list) { 5312 + struct fib6_info *rt = nh->fib6_info; 5313 + 5314 + err = ip6_route_info_create_nh(rt, &nh->r_cfg, extack); 5315 + if (err) { 5316 + nh->fib6_info = NULL; 5317 + goto err; 5318 + } 5319 + 5320 + rt->fib6_nh->fib_nh_weight = nh->weight; 5321 + 5322 + list_move_tail(&nh->list, &tmp); 5323 + 5324 + list_for_each_entry(nh_tmp, rt6_nh_list, list) { 5325 + /* check if fib6_info already exists */ 5326 + if (rt6_duplicate_nexthop(nh_tmp->fib6_info, rt)) { 5327 + err = -EEXIST; 5328 + goto err; 5329 + } 5330 + } 5331 + } 5332 + out: 5333 + list_splice(&tmp, rt6_nh_list); 5334 + return err; 5335 + err: 5336 + ip6_route_mpath_info_cleanup(rt6_nh_list); 5337 + goto out; 5345 5338 } 5346 5339 5347 5340 static void ip6_route_mpath_notify(struct fib6_info *rt, ··· 5496 5287 return should_notify; 5497 5288 } 5498 5289 5499 - static int fib6_gw_from_attr(struct in6_addr *gw, struct nlattr *nla, 5500 - struct netlink_ext_ack *extack) 5501 - { 5502 - if (nla_len(nla) < sizeof(*gw)) { 5503 - NL_SET_ERR_MSG(extack, "Invalid IPv6 address in RTA_GATEWAY"); 5504 - return -EINVAL; 5505 - } 5506 - 5507 - *gw = nla_get_in6_addr(nla); 5508 - 5509 - return 0; 5510 - } 5511 - 5512 5290 static int ip6_route_multipath_add(struct fib6_config *cfg, 5513 5291 struct netlink_ext_ack *extack) 5514 5292 { 5515 5293 struct fib6_info *rt_notif = NULL, *rt_last = NULL; 5516 5294 struct nl_info *info = &cfg->fc_nlinfo; 5517 - struct fib6_config r_cfg; 5518 - struct rtnexthop *rtnh; 5519 - struct fib6_info *rt; 5520 - struct rt6_nh *err_nh; 5521 5295 struct rt6_nh *nh, *nh_safe; 5522 - __u16 nlflags; 5523 - int remaining; 5524 - int attrlen; 5525 - int err = 1; 5526 - int nhn = 0; 5527 - int replace = (cfg->fc_nlinfo.nlh && 5528 - (cfg->fc_nlinfo.nlh->nlmsg_flags & NLM_F_REPLACE)); 5529 5296 LIST_HEAD(rt6_nh_list); 5297 + struct rt6_nh *err_nh; 5298 + __u16 nlflags; 5299 + int nhn = 0; 5300 + int replace; 5301 + int err; 5302 + 5303 + replace = (cfg->fc_nlinfo.nlh && 5304 + (cfg->fc_nlinfo.nlh->nlmsg_flags & NLM_F_REPLACE)); 5530 5305 5531 5306 nlflags = replace ? NLM_F_REPLACE : NLM_F_CREATE; 5532 5307 if (info->nlh && info->nlh->nlmsg_flags & NLM_F_APPEND) 5533 5308 nlflags |= NLM_F_APPEND; 5534 5309 5535 - remaining = cfg->fc_mp_len; 5536 - rtnh = (struct rtnexthop *)cfg->fc_mp; 5310 + err = ip6_route_mpath_info_create(&rt6_nh_list, cfg, extack); 5311 + if (err) 5312 + return err; 5537 5313 5538 - /* Parse a Multipath Entry and build a list (rt6_nh_list) of 5539 - * fib6_info structs per nexthop 5540 - */ 5541 - while (rtnh_ok(rtnh, remaining)) { 5542 - memcpy(&r_cfg, cfg, sizeof(*cfg)); 5543 - if (rtnh->rtnh_ifindex) 5544 - r_cfg.fc_ifindex = rtnh->rtnh_ifindex; 5314 + rcu_read_lock(); 5545 5315 5546 - attrlen = rtnh_attrlen(rtnh); 5547 - if (attrlen > 0) { 5548 - struct nlattr *nla, *attrs = rtnh_attrs(rtnh); 5549 - 5550 - nla = nla_find(attrs, attrlen, RTA_GATEWAY); 5551 - if (nla) { 5552 - err = fib6_gw_from_attr(&r_cfg.fc_gateway, nla, 5553 - extack); 5554 - if (err) 5555 - goto cleanup; 5556 - 5557 - r_cfg.fc_flags |= RTF_GATEWAY; 5558 - } 5559 - r_cfg.fc_encap = nla_find(attrs, attrlen, RTA_ENCAP); 5560 - 5561 - /* RTA_ENCAP_TYPE length checked in 5562 - * lwtunnel_valid_encap_type_attr 5563 - */ 5564 - nla = nla_find(attrs, attrlen, RTA_ENCAP_TYPE); 5565 - if (nla) 5566 - r_cfg.fc_encap_type = nla_get_u16(nla); 5567 - } 5568 - 5569 - r_cfg.fc_flags |= (rtnh->rtnh_flags & RTNH_F_ONLINK); 5570 - rt = ip6_route_info_create(&r_cfg, GFP_KERNEL, extack); 5571 - if (IS_ERR(rt)) { 5572 - err = PTR_ERR(rt); 5573 - rt = NULL; 5574 - goto cleanup; 5575 - } 5576 - if (!rt6_qualify_for_ecmp(rt)) { 5577 - err = -EINVAL; 5578 - NL_SET_ERR_MSG(extack, 5579 - "Device only routes can not be added for IPv6 using the multipath API."); 5580 - fib6_info_release(rt); 5581 - goto cleanup; 5582 - } 5583 - 5584 - rt->fib6_nh->fib_nh_weight = rtnh->rtnh_hops + 1; 5585 - 5586 - err = ip6_route_info_append(info->nl_net, &rt6_nh_list, 5587 - rt, &r_cfg); 5588 - if (err) { 5589 - fib6_info_release(rt); 5590 - goto cleanup; 5591 - } 5592 - 5593 - rtnh = rtnh_next(rtnh, &remaining); 5594 - } 5595 - 5596 - if (list_empty(&rt6_nh_list)) { 5597 - NL_SET_ERR_MSG(extack, 5598 - "Invalid nexthop configuration - no valid nexthops"); 5599 - return -EINVAL; 5600 - } 5316 + err = ip6_route_mpath_info_create_nh(&rt6_nh_list, extack); 5317 + if (err) 5318 + goto cleanup; 5601 5319 5602 5320 /* for add and replace send one notification with all nexthops. 5603 5321 * Skip the notification in fib6_add_rt2node and send one with ··· 5538 5402 info->skip_notify_kernel = 1; 5539 5403 5540 5404 err_nh = NULL; 5541 - list_for_each_entry(nh, &rt6_nh_list, next) { 5405 + list_for_each_entry(nh, &rt6_nh_list, list) { 5542 5406 err = __ip6_ins_rt(nh->fib6_info, info, extack); 5543 5407 5544 5408 if (err) { ··· 5606 5470 ip6_route_mpath_notify(rt_notif, rt_last, info, nlflags); 5607 5471 5608 5472 /* Delete routes that were already added */ 5609 - list_for_each_entry(nh, &rt6_nh_list, next) { 5473 + list_for_each_entry(nh, &rt6_nh_list, list) { 5610 5474 if (err_nh == nh) 5611 5475 break; 5612 5476 ip6_route_del(&nh->r_cfg, extack); 5613 5477 } 5614 5478 5615 5479 cleanup: 5616 - list_for_each_entry_safe(nh, nh_safe, &rt6_nh_list, next) { 5480 + rcu_read_unlock(); 5481 + 5482 + list_for_each_entry_safe(nh, nh_safe, &rt6_nh_list, list) { 5617 5483 fib6_info_release(nh->fib6_info); 5618 - list_del(&nh->next); 5484 + list_del(&nh->list); 5619 5485 kfree(nh); 5620 5486 } 5621 5487 ··· 5649 5511 5650 5512 nla = nla_find(attrs, attrlen, RTA_GATEWAY); 5651 5513 if (nla) { 5652 - err = fib6_gw_from_attr(&r_cfg.fc_gateway, nla, 5653 - extack); 5654 - if (err) { 5655 - last_err = err; 5656 - goto next_rtnh; 5657 - } 5658 - 5514 + r_cfg.fc_gateway = nla_get_in6_addr(nla); 5659 5515 r_cfg.fc_flags |= RTF_GATEWAY; 5660 5516 } 5661 5517 } 5518 + 5662 5519 err = ip6_route_del(&r_cfg, extack); 5663 5520 if (err) 5664 5521 last_err = err; 5665 5522 5666 - next_rtnh: 5667 5523 rtnh = rtnh_next(rtnh, &remaining); 5668 5524 } 5669 5525 ··· 5674 5542 if (err < 0) 5675 5543 return err; 5676 5544 5677 - if (cfg.fc_nh_id && 5678 - !nexthop_find_by_id(sock_net(skb->sk), cfg.fc_nh_id)) { 5679 - NL_SET_ERR_MSG(extack, "Nexthop id does not exist"); 5680 - return -EINVAL; 5545 + if (cfg.fc_nh_id) { 5546 + rcu_read_lock(); 5547 + err = !nexthop_find_by_id(sock_net(skb->sk), cfg.fc_nh_id); 5548 + rcu_read_unlock(); 5549 + 5550 + if (err) { 5551 + NL_SET_ERR_MSG(extack, "Nexthop id does not exist"); 5552 + return -EINVAL; 5553 + } 5681 5554 } 5682 5555 5683 - if (cfg.fc_mp) 5556 + if (cfg.fc_mp) { 5684 5557 return ip6_route_multipath_del(&cfg, extack); 5685 - else { 5558 + } else { 5686 5559 cfg.fc_delete_all_nh = 1; 5687 5560 return ip6_route_del(&cfg, extack); 5688 5561 } ··· 6897 6760 6898 6761 static const struct rtnl_msg_handler ip6_route_rtnl_msg_handlers[] __initconst_or_module = { 6899 6762 {.owner = THIS_MODULE, .protocol = PF_INET6, .msgtype = RTM_NEWROUTE, 6900 - .doit = inet6_rtm_newroute}, 6763 + .doit = inet6_rtm_newroute, .flags = RTNL_FLAG_DOIT_UNLOCKED}, 6901 6764 {.owner = THIS_MODULE, .protocol = PF_INET6, .msgtype = RTM_DELROUTE, 6902 - .doit = inet6_rtm_delroute}, 6765 + .doit = inet6_rtm_delroute, .flags = RTNL_FLAG_DOIT_UNLOCKED}, 6903 6766 {.owner = THIS_MODULE, .protocol = PF_INET6, .msgtype = RTM_GETROUTE, 6904 6767 .doit = inet6_rtm_getroute, .flags = RTNL_FLAG_DOIT_UNLOCKED}, 6905 6768 };