diff --git a/include/linux/ipv6_route.h b/include/linux/ipv6_route.h index 1e7d8af..9845953 100644 --- a/include/linux/ipv6_route.h +++ b/include/linux/ipv6_route.h @@ -44,6 +44,7 @@ struct in6_rtmsg { struct in6_addr rtmsg_dst; struct in6_addr rtmsg_src; + struct in6_addr rtmsg_prefsrc; struct in6_addr rtmsg_gateway; __u32 rtmsg_type; __u16 rtmsg_dst_len; diff --git a/include/net/ip6_fib.h b/include/net/ip6_fib.h index bc3cde0..98348d5 100644 --- a/include/net/ip6_fib.h +++ b/include/net/ip6_fib.h @@ -42,6 +42,7 @@ struct fib6_config { struct in6_addr fc_dst; struct in6_addr fc_src; + struct in6_addr fc_prefsrc; struct in6_addr fc_gateway; unsigned long fc_expires; @@ -107,6 +108,7 @@ struct rt6_info { struct rt6key rt6i_dst ____cacheline_aligned_in_smp; u32 rt6i_flags; struct rt6key rt6i_src; + struct rt6key rt6i_prefsrc; u32 rt6i_metric; u32 rt6i_peer_genid; diff --git a/include/net/ip6_route.h b/include/net/ip6_route.h index c850e5f..2b37c20 100644 --- a/include/net/ip6_route.h +++ b/include/net/ip6_route.h @@ -141,6 +141,7 @@ struct rt6_rtnl_dump_arg { extern int rt6_dump_route(struct rt6_info *rt, void *p_arg); extern void rt6_ifdown(struct net *net, struct net_device *dev); extern void rt6_mtu_change(struct net_device *dev, unsigned mtu); +extern void rt6_remove_prefsrc(struct inet6_ifaddr *ifp); /* diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c index 3daaf3c..26f9e14 100644 --- a/net/ipv6/addrconf.c +++ b/net/ipv6/addrconf.c @@ -825,6 +825,8 @@ static void ipv6_del_addr(struct inet6_ifaddr *ifp) dst_release(&rt->dst); } + /* clean up prefsrc entries */ + rt6_remove_prefsrc(ifp); out: in6_ifa_put(ifp); } diff --git a/net/ipv6/ip6_output.c b/net/ipv6/ip6_output.c index 1820887..e2d8463 100644 --- a/net/ipv6/ip6_output.c +++ b/net/ipv6/ip6_output.c @@ -930,10 +930,14 @@ static int ip6_dst_lookup_tail(struct sock *sk, goto out_err_release; if (ipv6_addr_any(&fl6->saddr)) { - err = ipv6_dev_get_saddr(net, ip6_dst_idev(*dst)->dev, - &fl6->daddr, - sk ? inet6_sk(sk)->srcprefs : 0, - &fl6->saddr); + struct rt6_info *rt = (struct rt6_info *) *dst; + if (rt->rt6i_prefsrc.plen) + ipv6_addr_copy(&fl6->saddr, &rt->rt6i_prefsrc.addr); + else + err = ipv6_dev_get_saddr(net, ip6_dst_idev(*dst)->dev, + &fl6->daddr, + sk ? inet6_sk(sk)->srcprefs : 0, + &fl6->saddr); if (err) goto out_err_release; } diff --git a/net/ipv6/route.c b/net/ipv6/route.c index 843406f..b53089d 100644 --- a/net/ipv6/route.c +++ b/net/ipv6/route.c @@ -1325,6 +1325,20 @@ int ip6_route_add(struct fib6_config *cfg) if (dev == NULL) goto out; + /* check if prefsrc is set */ + if (!ipv6_addr_any(&cfg->fc_prefsrc)) { + struct in6_addr saddr_buf; + ipv6_addr_copy(&saddr_buf, &cfg->fc_prefsrc); + if (!ipv6_chk_addr(net, &saddr_buf, dev, 0)) { + printk(KERN_DEBUG "invalid pref_src\n"); + err = -EINVAL; + goto out; + } + ipv6_addr_copy(&rt->rt6i_prefsrc.addr, &cfg->fc_prefsrc); + rt->rt6i_prefsrc.plen = 128; + } else + rt->rt6i_prefsrc.plen = 0; + if (cfg->fc_flags & (RTF_GATEWAY | RTF_NONEXTHOP)) { rt->rt6i_nexthop = __neigh_lookup_errno(&nd_tbl, &rt->rt6i_gateway, dev); if (IS_ERR(rt->rt6i_nexthop)) { @@ -1893,6 +1907,7 @@ static void rtmsg_to_fib6_config(struct net *net, ipv6_addr_copy(&cfg->fc_dst, &rtmsg->rtmsg_dst); ipv6_addr_copy(&cfg->fc_src, &rtmsg->rtmsg_src); ipv6_addr_copy(&cfg->fc_gateway, &rtmsg->rtmsg_gateway); + ipv6_addr_copy(&cfg->fc_prefsrc, &rtmsg->rtmsg_prefsrc); } int ipv6_route_ioctl(struct net *net, unsigned int cmd, void __user *arg) @@ -2037,6 +2052,39 @@ struct rt6_info *addrconf_dst_alloc(struct inet6_dev *idev, return rt; } +/* remove deleted ip from prefsrc entries */ +struct arg_dev_net_ip { + struct net_device *dev; + struct net *net; + struct in6_addr *addr; +}; + +static int fib6_remove_prefsrc(struct rt6_info *rt, void *arg) +{ + struct net_device *dev = ((struct arg_dev_net_ip *)arg)->dev; + struct net *net = ((struct arg_dev_net_ip *)arg)->net; + struct in6_addr *addr = ((struct arg_dev_net_ip *)arg)->addr; + + if (((void *)rt->rt6i_dev == dev || dev == NULL) && + rt != net->ipv6.ip6_null_entry && + ipv6_addr_equal(addr, &rt->rt6i_prefsrc.addr)) { + /* remove prefsrc entry */ + rt->rt6i_prefsrc.plen = 0; + } + return 0; +} + +void rt6_remove_prefsrc(struct inet6_ifaddr *ifp) +{ + struct net *net = dev_net(ifp->idev->dev); + struct arg_dev_net_ip adni = { + .dev = ifp->idev->dev, + .net = net, + .addr = &ifp->addr, + }; + fib6_clean_all(net, fib6_remove_prefsrc, 0, &adni); +} + struct arg_dev_net { struct net_device *dev; struct net *net; @@ -2183,6 +2231,9 @@ static int rtm_to_fib6_config(struct sk_buff *skb, struct nlmsghdr *nlh, nla_memcpy(&cfg->fc_src, tb[RTA_SRC], plen); } + if (tb[RTA_PREFSRC]) + nla_memcpy(&cfg->fc_prefsrc, tb[RTA_PREFSRC], 16); + if (tb[RTA_OIF]) cfg->fc_ifindex = nla_get_u32(tb[RTA_OIF]); @@ -2325,11 +2376,22 @@ static int rt6_fill_node(struct net *net, #endif NLA_PUT_U32(skb, RTA_IIF, iif); } else if (dst) { - struct inet6_dev *idev = ip6_dst_idev(&rt->dst); struct in6_addr saddr_buf; - if (ipv6_dev_get_saddr(net, idev ? idev->dev : NULL, - dst, 0, &saddr_buf) == 0) + if (rt->rt6i_prefsrc.plen) { + ipv6_addr_copy(&saddr_buf, &rt->rt6i_prefsrc.addr); NLA_PUT(skb, RTA_PREFSRC, 16, &saddr_buf); + } else { + struct inet6_dev *idev = ip6_dst_idev(&rt->dst); + if (ipv6_dev_get_saddr(net, idev ? idev->dev : NULL, + dst, 0, &saddr_buf) == 0) + NLA_PUT(skb, RTA_PREFSRC, 16, &saddr_buf); + } + } + + if (rt->rt6i_prefsrc.plen) { + struct in6_addr saddr_buf; + ipv6_addr_copy(&saddr_buf, &rt->rt6i_prefsrc.addr); + NLA_PUT(skb, RTA_PREFSRC, 16, &saddr_buf); } if (rtnetlink_put_metrics(skb, dst_metrics_ptr(&rt->dst)) < 0)