(IPV6_ADDR_MULTICAST | IPV6_ADDR_LINKLOCAL | IPV6_ADDR_LOOPBACK);
 }
 
+static inline bool rt6_qualify_for_ecmp(const struct rt6_info *rt)
+{
+       return (rt->rt6i_flags & (RTF_GATEWAY|RTF_ADDRCONF|RTF_DYNAMIC)) ==
+              RTF_GATEWAY;
+}
+
 void ip6_route_input(struct sk_buff *skb);
 struct dst_entry *ip6_route_input_lookup(struct net *net,
                                         struct net_device *dev,
 void rt6_sync_up(struct net_device *dev, unsigned int nh_flags);
 void rt6_disable_ip(struct net_device *dev, unsigned long event);
 void rt6_sync_down_dev(struct net_device *dev, unsigned long event);
+void rt6_multipath_rebalance(struct rt6_info *rt);
 
 static inline const struct rt6_info *skb_rt6_info(const struct sk_buff *skb)
 {
 
        };
 };
 
+static struct rt6_info *rt6_multipath_first_sibling(const struct rt6_info *rt)
+{
+       struct rt6_info *iter;
+       struct fib6_node *fn;
+
+       fn = rcu_dereference_protected(rt->rt6i_node,
+                       lockdep_is_held(&rt->rt6i_table->tb6_lock));
+       iter = rcu_dereference_protected(fn->leaf,
+                       lockdep_is_held(&rt->rt6i_table->tb6_lock));
+       while (iter) {
+               if (iter->rt6i_metric == rt->rt6i_metric &&
+                   rt6_qualify_for_ecmp(iter))
+                       return iter;
+               iter = rcu_dereference_protected(iter->rt6_next,
+                               lockdep_is_held(&rt->rt6i_table->tb6_lock));
+       }
+
+       return NULL;
+}
+
+static bool rt6_is_dead(const struct rt6_info *rt)
+{
+       if (rt->rt6i_nh_flags & RTNH_F_DEAD ||
+           (rt->rt6i_nh_flags & RTNH_F_LINKDOWN &&
+            rt->rt6i_idev->cnf.ignore_routes_with_linkdown))
+               return true;
+
+       return false;
+}
+
+static int rt6_multipath_total_weight(const struct rt6_info *rt)
+{
+       struct rt6_info *iter;
+       int total = 0;
+
+       if (!rt6_is_dead(rt))
+               total++;
+
+       list_for_each_entry(iter, &rt->rt6i_siblings, rt6i_siblings) {
+               if (!rt6_is_dead(iter))
+                       total++;
+       }
+
+       return total;
+}
+
+static void rt6_upper_bound_set(struct rt6_info *rt, int *weight, int total)
+{
+       int upper_bound = -1;
+
+       if (!rt6_is_dead(rt)) {
+               (*weight)++;
+               upper_bound = DIV_ROUND_CLOSEST_ULL((u64) (*weight) << 31,
+                                                   total) - 1;
+       }
+       atomic_set(&rt->rt6i_nh_upper_bound, upper_bound);
+}
+
+static void rt6_multipath_upper_bound_set(struct rt6_info *rt, int total)
+{
+       struct rt6_info *iter;
+       int weight = 0;
+
+       rt6_upper_bound_set(rt, &weight, total);
+
+       list_for_each_entry(iter, &rt->rt6i_siblings, rt6i_siblings)
+               rt6_upper_bound_set(iter, &weight, total);
+}
+
+void rt6_multipath_rebalance(struct rt6_info *rt)
+{
+       struct rt6_info *first;
+       int total;
+
+       /* In case the entire multipath route was marked for flushing,
+        * then there is no need to rebalance upon the removal of every
+        * sibling route.
+        */
+       if (!rt->rt6i_nsiblings || rt->should_flush)
+               return;
+
+       /* During lookup routes are evaluated in order, so we need to
+        * make sure upper bounds are assigned from the first sibling
+        * onwards.
+        */
+       first = rt6_multipath_first_sibling(rt);
+       if (WARN_ON_ONCE(!first))
+               return;
+
+       total = rt6_multipath_total_weight(first);
+       rt6_multipath_upper_bound_set(first, total);
+}
+
 static int fib6_ifup(struct rt6_info *rt, void *p_arg)
 {
        const struct arg_netdev_event *arg = p_arg;
        if (rt != net->ipv6.ip6_null_entry && rt->dst.dev == arg->dev) {
                rt->rt6i_nh_flags &= ~arg->nh_flags;
                fib6_update_sernum_upto_root(dev_net(rt->dst.dev), rt);
+               rt6_multipath_rebalance(rt);
        }
 
        return 0;
                        rt6_multipath_nh_flags_set(rt, dev, RTNH_F_DEAD |
                                                   RTNH_F_LINKDOWN);
                        fib6_update_sernum(rt);
+                       rt6_multipath_rebalance(rt);
                }
                return -2;
        case NETDEV_CHANGE:
                    rt->rt6i_flags & (RTF_LOCAL | RTF_ANYCAST))
                        break;
                rt->rt6i_nh_flags |= RTNH_F_LINKDOWN;
+               rt6_multipath_rebalance(rt);
                break;
        }