Skip to content

Commit e5fd387

Browse files
mkubecekdavem330
authored andcommitted
ipv6: do not overwrite inetpeer metrics prematurely
If an IPv6 host route with metrics exists, an attempt to add a new route for the same target with different metrics fails but rewrites the metrics anyway: 12sp0:~ # ip route add fec0::1 dev eth0 rto_min 1000 12sp0:~ # ip -6 route show fe80::/64 dev eth0 proto kernel metric 256 fec0::1 dev eth0 metric 1024 rto_min lock 1s 12sp0:~ # ip route add fec0::1 dev eth0 rto_min 1500 RTNETLINK answers: File exists 12sp0:~ # ip -6 route show fe80::/64 dev eth0 proto kernel metric 256 fec0::1 dev eth0 metric 1024 rto_min lock 1.5s This is caused by all IPv6 host routes using the metrics in their inetpeer (or the shared default). This also holds for the new route created in ip6_route_add() which shares the metrics with the already existing route and thus ip6_route_add() rewrites the metrics even if the new route ends up not being used at all. Another problem is that old metrics in inetpeer can reappear unexpectedly for a new route, e.g. 12sp0:~ # ip route add fec0::1 dev eth0 rto_min 1000 12sp0:~ # ip route del fec0::1 12sp0:~ # ip route add fec0::1 dev eth0 12sp0:~ # ip route change fec0::1 dev eth0 hoplimit 10 12sp0:~ # ip -6 route show fe80::/64 dev eth0 proto kernel metric 256 fec0::1 dev eth0 metric 1024 hoplimit 10 rto_min lock 1s Resolve the first problem by moving the setting of metrics down into fib6_add_rt2node() to the point we are sure we are inserting the new route into the tree. Second problem is addressed by introducing new flag DST_METRICS_FORCE_OVERWRITE which is set for a new host route in ip6_route_add() and makes ipv6_cow_metrics() always overwrite the metrics in inetpeer (even if they are not "new"); it is reset after that. v5: use a flag in _metrics member rather than one in flags v4: fix a typo making a condition always true (thanks to Hannes Frederic Sowa) v3: rewritten based on David Miller's idea to move setting the metrics (and allocation in non-host case) down to the point we already know the route is to be inserted. Also rebased to net-next as it is quite late in the cycle. Signed-off-by: Michal Kubecek <[email protected]> Acked-by: Hannes Frederic Sowa <[email protected]> Signed-off-by: David S. Miller <[email protected]>
1 parent 4ec54f9 commit e5fd387

File tree

4 files changed

+66
-39
lines changed

4 files changed

+66
-39
lines changed

include/net/dst.h

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,16 +108,23 @@ struct dst_entry {
108108
u32 *dst_cow_metrics_generic(struct dst_entry *dst, unsigned long old);
109109
extern const u32 dst_default_metrics[];
110110

111-
#define DST_METRICS_READ_ONLY 0x1UL
111+
#define DST_METRICS_READ_ONLY 0x1UL
112+
#define DST_METRICS_FORCE_OVERWRITE 0x2UL
113+
#define DST_METRICS_FLAGS 0x3UL
112114
#define __DST_METRICS_PTR(Y) \
113-
((u32 *)((Y) & ~DST_METRICS_READ_ONLY))
115+
((u32 *)((Y) & ~DST_METRICS_FLAGS))
114116
#define DST_METRICS_PTR(X) __DST_METRICS_PTR((X)->_metrics)
115117

116118
static inline bool dst_metrics_read_only(const struct dst_entry *dst)
117119
{
118120
return dst->_metrics & DST_METRICS_READ_ONLY;
119121
}
120122

123+
static inline void dst_metrics_set_force_overwrite(struct dst_entry *dst)
124+
{
125+
dst->_metrics |= DST_METRICS_FORCE_OVERWRITE;
126+
}
127+
121128
void __dst_destroy_metrics_generic(struct dst_entry *dst, unsigned long old);
122129

123130
static inline void dst_destroy_metrics_generic(struct dst_entry *dst)

include/net/ip6_fib.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,8 @@ struct fib6_node *fib6_locate(struct fib6_node *root,
284284
void fib6_clean_all(struct net *net, int (*func)(struct rt6_info *, void *arg),
285285
void *arg);
286286

287-
int fib6_add(struct fib6_node *root, struct rt6_info *rt, struct nl_info *info);
287+
int fib6_add(struct fib6_node *root, struct rt6_info *rt, struct nl_info *info,
288+
struct nlattr *mx, int mx_len);
288289

289290
int fib6_del(struct rt6_info *rt, struct nl_info *info);
290291

net/ipv6/ip6_fib.c

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -638,12 +638,41 @@ static inline bool rt6_qualify_for_ecmp(struct rt6_info *rt)
638638
RTF_GATEWAY;
639639
}
640640

641+
static int fib6_commit_metrics(struct dst_entry *dst,
642+
struct nlattr *mx, int mx_len)
643+
{
644+
struct nlattr *nla;
645+
int remaining;
646+
u32 *mp;
647+
648+
if (dst->flags & DST_HOST) {
649+
mp = dst_metrics_write_ptr(dst);
650+
} else {
651+
mp = kzalloc(sizeof(u32) * RTAX_MAX, GFP_KERNEL);
652+
if (!mp)
653+
return -ENOMEM;
654+
dst_init_metrics(dst, mp, 0);
655+
}
656+
657+
nla_for_each_attr(nla, mx, mx_len, remaining) {
658+
int type = nla_type(nla);
659+
660+
if (type) {
661+
if (type > RTAX_MAX)
662+
return -EINVAL;
663+
664+
mp[type - 1] = nla_get_u32(nla);
665+
}
666+
}
667+
return 0;
668+
}
669+
641670
/*
642671
* Insert routing information in a node.
643672
*/
644673

645674
static int fib6_add_rt2node(struct fib6_node *fn, struct rt6_info *rt,
646-
struct nl_info *info)
675+
struct nl_info *info, struct nlattr *mx, int mx_len)
647676
{
648677
struct rt6_info *iter = NULL;
649678
struct rt6_info **ins;
@@ -653,6 +682,7 @@ static int fib6_add_rt2node(struct fib6_node *fn, struct rt6_info *rt,
653682
(info->nlh->nlmsg_flags & NLM_F_CREATE));
654683
int found = 0;
655684
bool rt_can_ecmp = rt6_qualify_for_ecmp(rt);
685+
int err;
656686

657687
ins = &fn->leaf;
658688

@@ -751,6 +781,11 @@ static int fib6_add_rt2node(struct fib6_node *fn, struct rt6_info *rt,
751781
pr_warn("NLM_F_CREATE should be set when creating new route\n");
752782

753783
add:
784+
if (mx) {
785+
err = fib6_commit_metrics(&rt->dst, mx, mx_len);
786+
if (err)
787+
return err;
788+
}
754789
rt->dst.rt6_next = iter;
755790
*ins = rt;
756791
rt->rt6i_node = fn;
@@ -770,6 +805,11 @@ static int fib6_add_rt2node(struct fib6_node *fn, struct rt6_info *rt,
770805
pr_warn("NLM_F_REPLACE set, but no existing node found!\n");
771806
return -ENOENT;
772807
}
808+
if (mx) {
809+
err = fib6_commit_metrics(&rt->dst, mx, mx_len);
810+
if (err)
811+
return err;
812+
}
773813
*ins = rt;
774814
rt->rt6i_node = fn;
775815
rt->dst.rt6_next = iter->dst.rt6_next;
@@ -806,7 +846,8 @@ void fib6_force_start_gc(struct net *net)
806846
* with source addr info in sub-trees
807847
*/
808848

809-
int fib6_add(struct fib6_node *root, struct rt6_info *rt, struct nl_info *info)
849+
int fib6_add(struct fib6_node *root, struct rt6_info *rt, struct nl_info *info,
850+
struct nlattr *mx, int mx_len)
810851
{
811852
struct fib6_node *fn, *pn = NULL;
812853
int err = -ENOMEM;
@@ -900,7 +941,7 @@ int fib6_add(struct fib6_node *root, struct rt6_info *rt, struct nl_info *info)
900941
}
901942
#endif
902943

903-
err = fib6_add_rt2node(fn, rt, info);
944+
err = fib6_add_rt2node(fn, rt, info, mx, mx_len);
904945
if (!err) {
905946
fib6_start_gc(info->nl_net, rt);
906947
if (!(rt->rt6i_flags & RTF_CACHE))

net/ipv6/route.c

Lines changed: 11 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,8 @@ static u32 *ipv6_cow_metrics(struct dst_entry *dst, unsigned long old)
149149
unsigned long prev, new;
150150

151151
p = peer->metrics;
152-
if (inet_metrics_new(peer))
152+
if (inet_metrics_new(peer) ||
153+
(old & DST_METRICS_FORCE_OVERWRITE))
153154
memcpy(p, old_p, sizeof(u32) * RTAX_MAX);
154155

155156
new = (unsigned long) p;
@@ -857,14 +858,15 @@ EXPORT_SYMBOL(rt6_lookup);
857858
be destroyed.
858859
*/
859860

860-
static int __ip6_ins_rt(struct rt6_info *rt, struct nl_info *info)
861+
static int __ip6_ins_rt(struct rt6_info *rt, struct nl_info *info,
862+
struct nlattr *mx, int mx_len)
861863
{
862864
int err;
863865
struct fib6_table *table;
864866

865867
table = rt->rt6i_table;
866868
write_lock_bh(&table->tb6_lock);
867-
err = fib6_add(&table->tb6_root, rt, info);
869+
err = fib6_add(&table->tb6_root, rt, info, mx, mx_len);
868870
write_unlock_bh(&table->tb6_lock);
869871

870872
return err;
@@ -875,7 +877,7 @@ int ip6_ins_rt(struct rt6_info *rt)
875877
struct nl_info info = {
876878
.nl_net = dev_net(rt->dst.dev),
877879
};
878-
return __ip6_ins_rt(rt, &info);
880+
return __ip6_ins_rt(rt, &info, NULL, 0);
879881
}
880882

881883
static struct rt6_info *rt6_alloc_cow(struct rt6_info *ort,
@@ -1543,17 +1545,11 @@ int ip6_route_add(struct fib6_config *cfg)
15431545

15441546
ipv6_addr_prefix(&rt->rt6i_dst.addr, &cfg->fc_dst, cfg->fc_dst_len);
15451547
rt->rt6i_dst.plen = cfg->fc_dst_len;
1546-
if (rt->rt6i_dst.plen == 128)
1547-
rt->dst.flags |= DST_HOST;
1548-
1549-
if (!(rt->dst.flags & DST_HOST) && cfg->fc_mx) {
1550-
u32 *metrics = kzalloc(sizeof(u32) * RTAX_MAX, GFP_KERNEL);
1551-
if (!metrics) {
1552-
err = -ENOMEM;
1553-
goto out;
1554-
}
1555-
dst_init_metrics(&rt->dst, metrics, 0);
1548+
if (rt->rt6i_dst.plen == 128) {
1549+
rt->dst.flags |= DST_HOST;
1550+
dst_metrics_set_force_overwrite(&rt->dst);
15561551
}
1552+
15571553
#ifdef CONFIG_IPV6_SUBTREES
15581554
ipv6_addr_prefix(&rt->rt6i_src.addr, &cfg->fc_src, cfg->fc_src_len);
15591555
rt->rt6i_src.plen = cfg->fc_src_len;
@@ -1672,31 +1668,13 @@ int ip6_route_add(struct fib6_config *cfg)
16721668
rt->rt6i_flags = cfg->fc_flags;
16731669

16741670
install_route:
1675-
if (cfg->fc_mx) {
1676-
struct nlattr *nla;
1677-
int remaining;
1678-
1679-
nla_for_each_attr(nla, cfg->fc_mx, cfg->fc_mx_len, remaining) {
1680-
int type = nla_type(nla);
1681-
1682-
if (type) {
1683-
if (type > RTAX_MAX) {
1684-
err = -EINVAL;
1685-
goto out;
1686-
}
1687-
1688-
dst_metric_set(&rt->dst, type, nla_get_u32(nla));
1689-
}
1690-
}
1691-
}
1692-
16931671
rt->dst.dev = dev;
16941672
rt->rt6i_idev = idev;
16951673
rt->rt6i_table = table;
16961674

16971675
cfg->fc_nlinfo.nl_net = dev_net(dev);
16981676

1699-
return __ip6_ins_rt(rt, &cfg->fc_nlinfo);
1677+
return __ip6_ins_rt(rt, &cfg->fc_nlinfo, cfg->fc_mx, cfg->fc_mx_len);
17001678

17011679
out:
17021680
if (dev)

0 commit comments

Comments
 (0)