aboutsummaryrefslogtreecommitdiffstats
path: root/net/ipv6/ip6_fib.c
diff options
context:
space:
mode:
authorMichal Kubeček <mkubecek@suse.cz>2014-03-27 08:04:08 -0400
committerDavid S. Miller <davem@davemloft.net>2014-03-27 15:09:07 -0400
commite5fd387ad5b30ca3971fbccb0735c843cdebf967 (patch)
treed0fb62b5c313a0d6944692dced80918ebf6b0b90 /net/ipv6/ip6_fib.c
parent4ec54f95736f2d90e4d9e4fcc75cf1228f0f3ede (diff)
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 <mkubecek@suse.cz> Acked-by: Hannes Frederic Sowa <hannes@stressinduktion.org> Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'net/ipv6/ip6_fib.c')
-rw-r--r--net/ipv6/ip6_fib.c47
1 files changed, 44 insertions, 3 deletions
diff --git a/net/ipv6/ip6_fib.c b/net/ipv6/ip6_fib.c
index 075602fc6b6a..4ee487b103ae 100644
--- a/net/ipv6/ip6_fib.c
+++ b/net/ipv6/ip6_fib.c
@@ -638,12 +638,41 @@ static inline bool rt6_qualify_for_ecmp(struct rt6_info *rt)
638 RTF_GATEWAY; 638 RTF_GATEWAY;
639} 639}
640 640
641static 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
641/* 670/*
642 * Insert routing information in a node. 671 * Insert routing information in a node.
643 */ 672 */
644 673
645static int fib6_add_rt2node(struct fib6_node *fn, struct rt6_info *rt, 674static 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)
647{ 676{
648 struct rt6_info *iter = NULL; 677 struct rt6_info *iter = NULL;
649 struct rt6_info **ins; 678 struct rt6_info **ins;
@@ -653,6 +682,7 @@ static int fib6_add_rt2node(struct fib6_node *fn, struct rt6_info *rt,
653 (info->nlh->nlmsg_flags & NLM_F_CREATE)); 682 (info->nlh->nlmsg_flags & NLM_F_CREATE));
654 int found = 0; 683 int found = 0;
655 bool rt_can_ecmp = rt6_qualify_for_ecmp(rt); 684 bool rt_can_ecmp = rt6_qualify_for_ecmp(rt);
685 int err;
656 686
657 ins = &fn->leaf; 687 ins = &fn->leaf;
658 688
@@ -751,6 +781,11 @@ static int fib6_add_rt2node(struct fib6_node *fn, struct rt6_info *rt,
751 pr_warn("NLM_F_CREATE should be set when creating new route\n"); 781 pr_warn("NLM_F_CREATE should be set when creating new route\n");
752 782
753add: 783add:
784 if (mx) {
785 err = fib6_commit_metrics(&rt->dst, mx, mx_len);
786 if (err)
787 return err;
788 }
754 rt->dst.rt6_next = iter; 789 rt->dst.rt6_next = iter;
755 *ins = rt; 790 *ins = rt;
756 rt->rt6i_node = fn; 791 rt->rt6i_node = fn;
@@ -770,6 +805,11 @@ add:
770 pr_warn("NLM_F_REPLACE set, but no existing node found!\n"); 805 pr_warn("NLM_F_REPLACE set, but no existing node found!\n");
771 return -ENOENT; 806 return -ENOENT;
772 } 807 }
808 if (mx) {
809 err = fib6_commit_metrics(&rt->dst, mx, mx_len);
810 if (err)
811 return err;
812 }
773 *ins = rt; 813 *ins = rt;
774 rt->rt6i_node = fn; 814 rt->rt6i_node = fn;
775 rt->dst.rt6_next = iter->dst.rt6_next; 815 rt->dst.rt6_next = iter->dst.rt6_next;
@@ -806,7 +846,8 @@ void fib6_force_start_gc(struct net *net)
806 * with source addr info in sub-trees 846 * with source addr info in sub-trees
807 */ 847 */
808 848
809int fib6_add(struct fib6_node *root, struct rt6_info *rt, struct nl_info *info) 849int fib6_add(struct fib6_node *root, struct rt6_info *rt, struct nl_info *info,
850 struct nlattr *mx, int mx_len)
810{ 851{
811 struct fib6_node *fn, *pn = NULL; 852 struct fib6_node *fn, *pn = NULL;
812 int err = -ENOMEM; 853 int err = -ENOMEM;
@@ -900,7 +941,7 @@ int fib6_add(struct fib6_node *root, struct rt6_info *rt, struct nl_info *info)
900 } 941 }
901#endif 942#endif
902 943
903 err = fib6_add_rt2node(fn, rt, info); 944 err = fib6_add_rt2node(fn, rt, info, mx, mx_len);
904 if (!err) { 945 if (!err) {
905 fib6_start_gc(info->nl_net, rt); 946 fib6_start_gc(info->nl_net, rt);
906 if (!(rt->rt6i_flags & RTF_CACHE)) 947 if (!(rt->rt6i_flags & RTF_CACHE))