ip_tunnel(ipv4): fix tunnels with "local any remote $remote_ip"

Ipv4 tunnels created with "local any remote $ip" didn't work properly since
7d442fab0 (ipv4: Cache dst in tunnels). 99% of packets sent via those tunnels
had src addr = 0.0.0.0. That was because only dst_entry was cached, although
fl4.saddr has to be cached too. Every time ip_tunnel_xmit used cached dst_entry
(tunnel_rtable_get returned non-NULL), fl4.saddr was initialized with
tnl_params->saddr (= 0 in our case), and wasn't changed until iptunnel_xmit().

This patch adds saddr to ip_tunnel->dst_cache, fixing this issue.

Reported-by: Sergey Popov <pinkbyte@gentoo.org>
Signed-off-by: Dmitry Popov <ixaphire@qrator.net>
Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
Dmitry Popov 2014-07-29 03:07:52 +04:00 committed by David S. Miller
parent dabf24d168
commit 95cb574598
2 changed files with 19 additions and 11 deletions

View File

@ -40,6 +40,7 @@ struct ip_tunnel_prl_entry {
struct ip_tunnel_dst { struct ip_tunnel_dst {
struct dst_entry __rcu *dst; struct dst_entry __rcu *dst;
__be32 saddr;
}; };
struct ip_tunnel { struct ip_tunnel {

View File

@ -69,23 +69,25 @@ static unsigned int ip_tunnel_hash(__be32 key, __be32 remote)
} }
static void __tunnel_dst_set(struct ip_tunnel_dst *idst, static void __tunnel_dst_set(struct ip_tunnel_dst *idst,
struct dst_entry *dst) struct dst_entry *dst, __be32 saddr)
{ {
struct dst_entry *old_dst; struct dst_entry *old_dst;
dst_clone(dst); dst_clone(dst);
old_dst = xchg((__force struct dst_entry **)&idst->dst, dst); old_dst = xchg((__force struct dst_entry **)&idst->dst, dst);
dst_release(old_dst); dst_release(old_dst);
idst->saddr = saddr;
} }
static void tunnel_dst_set(struct ip_tunnel *t, struct dst_entry *dst) static void tunnel_dst_set(struct ip_tunnel *t,
struct dst_entry *dst, __be32 saddr)
{ {
__tunnel_dst_set(this_cpu_ptr(t->dst_cache), dst); __tunnel_dst_set(this_cpu_ptr(t->dst_cache), dst, saddr);
} }
static void tunnel_dst_reset(struct ip_tunnel *t) static void tunnel_dst_reset(struct ip_tunnel *t)
{ {
tunnel_dst_set(t, NULL); tunnel_dst_set(t, NULL, 0);
} }
void ip_tunnel_dst_reset_all(struct ip_tunnel *t) void ip_tunnel_dst_reset_all(struct ip_tunnel *t)
@ -93,20 +95,25 @@ void ip_tunnel_dst_reset_all(struct ip_tunnel *t)
int i; int i;
for_each_possible_cpu(i) for_each_possible_cpu(i)
__tunnel_dst_set(per_cpu_ptr(t->dst_cache, i), NULL); __tunnel_dst_set(per_cpu_ptr(t->dst_cache, i), NULL, 0);
} }
EXPORT_SYMBOL(ip_tunnel_dst_reset_all); EXPORT_SYMBOL(ip_tunnel_dst_reset_all);
static struct rtable *tunnel_rtable_get(struct ip_tunnel *t, u32 cookie) static struct rtable *tunnel_rtable_get(struct ip_tunnel *t,
u32 cookie, __be32 *saddr)
{ {
struct ip_tunnel_dst *idst;
struct dst_entry *dst; struct dst_entry *dst;
rcu_read_lock(); rcu_read_lock();
dst = rcu_dereference(this_cpu_ptr(t->dst_cache)->dst); idst = this_cpu_ptr(t->dst_cache);
dst = rcu_dereference(idst->dst);
if (dst && !atomic_inc_not_zero(&dst->__refcnt)) if (dst && !atomic_inc_not_zero(&dst->__refcnt))
dst = NULL; dst = NULL;
if (dst) { if (dst) {
if (dst->obsolete && dst->ops->check(dst, cookie) == NULL) { if (!dst->obsolete || dst->ops->check(dst, cookie)) {
*saddr = idst->saddr;
} else {
tunnel_dst_reset(t); tunnel_dst_reset(t);
dst_release(dst); dst_release(dst);
dst = NULL; dst = NULL;
@ -367,7 +374,7 @@ static int ip_tunnel_bind_dev(struct net_device *dev)
if (!IS_ERR(rt)) { if (!IS_ERR(rt)) {
tdev = rt->dst.dev; tdev = rt->dst.dev;
tunnel_dst_set(tunnel, &rt->dst); tunnel_dst_set(tunnel, &rt->dst, fl4.saddr);
ip_rt_put(rt); ip_rt_put(rt);
} }
if (dev->type != ARPHRD_ETHER) if (dev->type != ARPHRD_ETHER)
@ -610,7 +617,7 @@ void ip_tunnel_xmit(struct sk_buff *skb, struct net_device *dev,
init_tunnel_flow(&fl4, protocol, dst, tnl_params->saddr, init_tunnel_flow(&fl4, protocol, dst, tnl_params->saddr,
tunnel->parms.o_key, RT_TOS(tos), tunnel->parms.link); tunnel->parms.o_key, RT_TOS(tos), tunnel->parms.link);
rt = connected ? tunnel_rtable_get(tunnel, 0) : NULL; rt = connected ? tunnel_rtable_get(tunnel, 0, &fl4.saddr) : NULL;
if (!rt) { if (!rt) {
rt = ip_route_output_key(tunnel->net, &fl4); rt = ip_route_output_key(tunnel->net, &fl4);
@ -620,7 +627,7 @@ void ip_tunnel_xmit(struct sk_buff *skb, struct net_device *dev,
goto tx_error; goto tx_error;
} }
if (connected) if (connected)
tunnel_dst_set(tunnel, &rt->dst); tunnel_dst_set(tunnel, &rt->dst, fl4.saddr);
} }
if (rt->dst.dev == dev) { if (rt->dst.dev == dev) {