Commit 3ec5a261 authored by David S. Miller's avatar David S. Miller

Merge branch 'redirect_via_sock'

As described in my patch series from the other day, we need to
rearrange redirect handling so that the local initiators of packets
(sockets, tunnels, xfrms, etc.) that implement the protocols compute
the route and pass this down into the ipv4/ipv6 routing code.

These changes here do so by implementing a new dst_ops->redirect
method.

No more do we have this funny code that tries several different sets
of routing keys to try and figure out which route the redirect should
actually be applied to.

No more do we have the problem wherein TOS rewriting causes problems
for us.
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 46d3ceab 1ed5c48f
...@@ -25,6 +25,7 @@ struct dst_ops { ...@@ -25,6 +25,7 @@ struct dst_ops {
struct dst_entry * (*negative_advice)(struct dst_entry *); struct dst_entry * (*negative_advice)(struct dst_entry *);
void (*link_failure)(struct sk_buff *); void (*link_failure)(struct sk_buff *);
void (*update_pmtu)(struct dst_entry *dst, u32 mtu); void (*update_pmtu)(struct dst_entry *dst, u32 mtu);
void (*redirect)(struct dst_entry *dst, struct sk_buff *skb);
int (*local_out)(struct sk_buff *skb); int (*local_out)(struct sk_buff *skb);
struct neighbour * (*neigh_lookup)(const struct dst_entry *dst, struct neighbour * (*neigh_lookup)(const struct dst_entry *dst,
struct sk_buff *skb, struct sk_buff *skb,
......
...@@ -133,17 +133,12 @@ extern int rt6_route_rcv(struct net_device *dev, ...@@ -133,17 +133,12 @@ extern int rt6_route_rcv(struct net_device *dev,
u8 *opt, int len, u8 *opt, int len,
const struct in6_addr *gwaddr); const struct in6_addr *gwaddr);
extern void rt6_redirect(const struct in6_addr *dest,
const struct in6_addr *src,
const struct in6_addr *saddr,
struct neighbour *neigh,
u8 *lladdr,
int on_link);
extern void ip6_update_pmtu(struct sk_buff *skb, struct net *net, __be32 mtu, extern void ip6_update_pmtu(struct sk_buff *skb, struct net *net, __be32 mtu,
int oif, u32 mark); int oif, u32 mark);
extern void ip6_sk_update_pmtu(struct sk_buff *skb, struct sock *sk, extern void ip6_sk_update_pmtu(struct sk_buff *skb, struct sock *sk,
__be32 mtu); __be32 mtu);
extern void ip6_redirect(struct sk_buff *skb, struct net *net, int oif, u32 mark);
extern void ip6_sk_redirect(struct sk_buff *skb, struct sock *sk);
struct netlink_callback; struct netlink_callback;
......
...@@ -251,6 +251,8 @@ static inline void fl6_sock_release(struct ip6_flowlabel *fl) ...@@ -251,6 +251,8 @@ static inline void fl6_sock_release(struct ip6_flowlabel *fl)
atomic_dec(&fl->users); atomic_dec(&fl->users);
} }
extern void icmpv6_notify(struct sk_buff *skb, u8 type, u8 code, __be32 info);
extern int ip6_ra_control(struct sock *sk, int sel); extern int ip6_ra_control(struct sock *sk, int sel);
extern int ipv6_parse_hopopts(struct sk_buff *skb); extern int ipv6_parse_hopopts(struct sk_buff *skb);
......
...@@ -47,6 +47,8 @@ enum { ...@@ -47,6 +47,8 @@ enum {
#include <linux/icmpv6.h> #include <linux/icmpv6.h>
#include <linux/in6.h> #include <linux/in6.h>
#include <linux/types.h> #include <linux/types.h>
#include <linux/if_arp.h>
#include <linux/netdevice.h>
#include <net/neighbour.h> #include <net/neighbour.h>
...@@ -80,6 +82,54 @@ struct nd_opt_hdr { ...@@ -80,6 +82,54 @@ struct nd_opt_hdr {
__u8 nd_opt_len; __u8 nd_opt_len;
} __packed; } __packed;
/* ND options */
struct ndisc_options {
struct nd_opt_hdr *nd_opt_array[__ND_OPT_ARRAY_MAX];
#ifdef CONFIG_IPV6_ROUTE_INFO
struct nd_opt_hdr *nd_opts_ri;
struct nd_opt_hdr *nd_opts_ri_end;
#endif
struct nd_opt_hdr *nd_useropts;
struct nd_opt_hdr *nd_useropts_end;
};
#define nd_opts_src_lladdr nd_opt_array[ND_OPT_SOURCE_LL_ADDR]
#define nd_opts_tgt_lladdr nd_opt_array[ND_OPT_TARGET_LL_ADDR]
#define nd_opts_pi nd_opt_array[ND_OPT_PREFIX_INFO]
#define nd_opts_pi_end nd_opt_array[__ND_OPT_PREFIX_INFO_END]
#define nd_opts_rh nd_opt_array[ND_OPT_REDIRECT_HDR]
#define nd_opts_mtu nd_opt_array[ND_OPT_MTU]
#define NDISC_OPT_SPACE(len) (((len)+2+7)&~7)
extern struct ndisc_options *ndisc_parse_options(u8 *opt, int opt_len,
struct ndisc_options *ndopts);
/*
* Return the padding between the option length and the start of the
* link addr. Currently only IP-over-InfiniBand needs this, although
* if RFC 3831 IPv6-over-Fibre Channel is ever implemented it may
* also need a pad of 2.
*/
static int ndisc_addr_option_pad(unsigned short type)
{
switch (type) {
case ARPHRD_INFINIBAND: return 2;
default: return 0;
}
}
static inline u8 *ndisc_opt_addr_data(struct nd_opt_hdr *p,
struct net_device *dev)
{
u8 *lladdr = (u8 *)(p + 1);
int lladdrlen = p->nd_opt_len << 3;
int prepad = ndisc_addr_option_pad(dev->type);
if (lladdrlen != NDISC_OPT_SPACE(dev->addr_len + prepad))
return NULL;
return lladdr + prepad;
}
static inline u32 ndisc_hashfn(const void *pkey, const struct net_device *dev, __u32 *hash_rnd) static inline u32 ndisc_hashfn(const void *pkey, const struct net_device *dev, __u32 *hash_rnd)
{ {
const u32 *p32 = pkey; const u32 *p32 = pkey;
......
...@@ -108,8 +108,6 @@ extern struct ip_rt_acct __percpu *ip_rt_acct; ...@@ -108,8 +108,6 @@ extern struct ip_rt_acct __percpu *ip_rt_acct;
struct in_device; struct in_device;
extern int ip_rt_init(void); extern int ip_rt_init(void);
extern void ip_rt_redirect(__be32 old_gw, __be32 dst, __be32 new_gw,
__be32 src, struct net_device *dev);
extern void rt_cache_flush(struct net *net, int how); extern void rt_cache_flush(struct net *net, int how);
extern void rt_cache_flush_batch(struct net *net); extern void rt_cache_flush_batch(struct net *net);
extern struct rtable *__ip_route_output_key(struct net *, struct flowi4 *flp); extern struct rtable *__ip_route_output_key(struct net *, struct flowi4 *flp);
...@@ -181,6 +179,9 @@ static inline int ip_route_input_noref(struct sk_buff *skb, __be32 dst, __be32 s ...@@ -181,6 +179,9 @@ static inline int ip_route_input_noref(struct sk_buff *skb, __be32 dst, __be32 s
extern void ipv4_update_pmtu(struct sk_buff *skb, struct net *net, u32 mtu, extern void ipv4_update_pmtu(struct sk_buff *skb, struct net *net, u32 mtu,
int oif, u32 mark, u8 protocol, int flow_flags); int oif, u32 mark, u8 protocol, int flow_flags);
extern void ipv4_sk_update_pmtu(struct sk_buff *skb, struct sock *sk, u32 mtu); extern void ipv4_sk_update_pmtu(struct sk_buff *skb, struct sock *sk, u32 mtu);
extern void ipv4_redirect(struct sk_buff *skb, struct net *net,
int oif, u32 mark, u8 protocol, int flow_flags);
extern void ipv4_sk_redirect(struct sk_buff *skb, struct sock *sk);
extern void ip_rt_send_redirect(struct sk_buff *skb); extern void ip_rt_send_redirect(struct sk_buff *skb);
extern unsigned int inet_addr_type(struct net *net, __be32 addr); extern unsigned int inet_addr_type(struct net *net, __be32 addr);
......
...@@ -162,6 +162,8 @@ struct sock *sctp_err_lookup(int family, struct sk_buff *, ...@@ -162,6 +162,8 @@ struct sock *sctp_err_lookup(int family, struct sk_buff *,
void sctp_err_finish(struct sock *, struct sctp_association *); void sctp_err_finish(struct sock *, struct sctp_association *);
void sctp_icmp_frag_needed(struct sock *, struct sctp_association *, void sctp_icmp_frag_needed(struct sock *, struct sctp_association *,
struct sctp_transport *t, __u32 pmtu); struct sctp_transport *t, __u32 pmtu);
void sctp_icmp_redirect(struct sock *, struct sctp_transport *,
struct sk_buff *);
void sctp_icmp_proto_unreachable(struct sock *sk, void sctp_icmp_proto_unreachable(struct sock *sk,
struct sctp_association *asoc, struct sctp_association *asoc,
struct sctp_transport *t); struct sctp_transport *t);
......
...@@ -115,6 +115,10 @@ static void fake_update_pmtu(struct dst_entry *dst, u32 mtu) ...@@ -115,6 +115,10 @@ static void fake_update_pmtu(struct dst_entry *dst, u32 mtu)
{ {
} }
static void fake_redirect(struct dst_entry *dst, struct sk_buff *skb)
{
}
static u32 *fake_cow_metrics(struct dst_entry *dst, unsigned long old) static u32 *fake_cow_metrics(struct dst_entry *dst, unsigned long old)
{ {
return NULL; return NULL;
...@@ -136,6 +140,7 @@ static struct dst_ops fake_dst_ops = { ...@@ -136,6 +140,7 @@ static struct dst_ops fake_dst_ops = {
.family = AF_INET, .family = AF_INET,
.protocol = cpu_to_be16(ETH_P_IP), .protocol = cpu_to_be16(ETH_P_IP),
.update_pmtu = fake_update_pmtu, .update_pmtu = fake_update_pmtu,
.redirect = fake_redirect,
.cow_metrics = fake_cow_metrics, .cow_metrics = fake_cow_metrics,
.neigh_lookup = fake_neigh_lookup, .neigh_lookup = fake_neigh_lookup,
.mtu = fake_mtu, .mtu = fake_mtu,
......
...@@ -195,6 +195,14 @@ static inline void dccp_do_pmtu_discovery(struct sock *sk, ...@@ -195,6 +195,14 @@ static inline void dccp_do_pmtu_discovery(struct sock *sk,
} /* else let the usual retransmit timer handle it */ } /* else let the usual retransmit timer handle it */
} }
static void dccp_do_redirect(struct sk_buff *skb, struct sock *sk)
{
struct dst_entry *dst = __sk_dst_check(sk, 0);
if (dst)
dst->ops->redirect(dst, skb);
}
/* /*
* This routine is called by the ICMP module when it gets some sort of error * This routine is called by the ICMP module when it gets some sort of error
* condition. If err < 0 then the socket should be closed and the error * condition. If err < 0 then the socket should be closed and the error
...@@ -259,6 +267,9 @@ static void dccp_v4_err(struct sk_buff *skb, u32 info) ...@@ -259,6 +267,9 @@ static void dccp_v4_err(struct sk_buff *skb, u32 info)
} }
switch (type) { switch (type) {
case ICMP_REDIRECT:
dccp_do_redirect(skb, sk);
goto out;
case ICMP_SOURCE_QUENCH: case ICMP_SOURCE_QUENCH:
/* Just silently ignore these. */ /* Just silently ignore these. */
goto out; goto out;
......
...@@ -130,6 +130,13 @@ static void dccp_v6_err(struct sk_buff *skb, struct inet6_skb_parm *opt, ...@@ -130,6 +130,13 @@ static void dccp_v6_err(struct sk_buff *skb, struct inet6_skb_parm *opt,
np = inet6_sk(sk); np = inet6_sk(sk);
if (type == NDISC_REDIRECT) {
struct dst_entry *dst = __sk_dst_check(sk, np->dst_cookie);
if (dst)
dst->ops->redirect(dst, skb);
}
if (type == ICMPV6_PKT_TOOBIG) { if (type == ICMPV6_PKT_TOOBIG) {
struct dst_entry *dst = NULL; struct dst_entry *dst = NULL;
......
...@@ -118,6 +118,7 @@ static void dn_dst_ifdown(struct dst_entry *, struct net_device *dev, int how); ...@@ -118,6 +118,7 @@ static void dn_dst_ifdown(struct dst_entry *, struct net_device *dev, int how);
static struct dst_entry *dn_dst_negative_advice(struct dst_entry *); static struct dst_entry *dn_dst_negative_advice(struct dst_entry *);
static void dn_dst_link_failure(struct sk_buff *); static void dn_dst_link_failure(struct sk_buff *);
static void dn_dst_update_pmtu(struct dst_entry *dst, u32 mtu); static void dn_dst_update_pmtu(struct dst_entry *dst, u32 mtu);
static void dn_dst_redirect(struct dst_entry *dst, struct sk_buff *skb);
static struct neighbour *dn_dst_neigh_lookup(const struct dst_entry *dst, static struct neighbour *dn_dst_neigh_lookup(const struct dst_entry *dst,
struct sk_buff *skb, struct sk_buff *skb,
const void *daddr); const void *daddr);
...@@ -145,6 +146,7 @@ static struct dst_ops dn_dst_ops = { ...@@ -145,6 +146,7 @@ static struct dst_ops dn_dst_ops = {
.negative_advice = dn_dst_negative_advice, .negative_advice = dn_dst_negative_advice,
.link_failure = dn_dst_link_failure, .link_failure = dn_dst_link_failure,
.update_pmtu = dn_dst_update_pmtu, .update_pmtu = dn_dst_update_pmtu,
.redirect = dn_dst_redirect,
.neigh_lookup = dn_dst_neigh_lookup, .neigh_lookup = dn_dst_neigh_lookup,
}; };
...@@ -292,6 +294,10 @@ static void dn_dst_update_pmtu(struct dst_entry *dst, u32 mtu) ...@@ -292,6 +294,10 @@ static void dn_dst_update_pmtu(struct dst_entry *dst, u32 mtu)
} }
} }
static void dn_dst_redirect(struct dst_entry *dst, struct sk_buff *skb)
{
}
/* /*
* When a route has been marked obsolete. (e.g. routing cache flush) * When a route has been marked obsolete. (e.g. routing cache flush)
*/ */
......
...@@ -398,17 +398,25 @@ static void ah4_err(struct sk_buff *skb, u32 info) ...@@ -398,17 +398,25 @@ static void ah4_err(struct sk_buff *skb, u32 info)
struct ip_auth_hdr *ah = (struct ip_auth_hdr *)(skb->data+(iph->ihl<<2)); struct ip_auth_hdr *ah = (struct ip_auth_hdr *)(skb->data+(iph->ihl<<2));
struct xfrm_state *x; struct xfrm_state *x;
if (icmp_hdr(skb)->type != ICMP_DEST_UNREACH || switch (icmp_hdr(skb)->type) {
icmp_hdr(skb)->code != ICMP_FRAG_NEEDED) case ICMP_DEST_UNREACH:
if (icmp_hdr(skb)->code != ICMP_FRAG_NEEDED)
return;
case ICMP_REDIRECT:
break;
default:
return; return;
}
x = xfrm_state_lookup(net, skb->mark, (const xfrm_address_t *)&iph->daddr, x = xfrm_state_lookup(net, skb->mark, (const xfrm_address_t *)&iph->daddr,
ah->spi, IPPROTO_AH, AF_INET); ah->spi, IPPROTO_AH, AF_INET);
if (!x) if (!x)
return; return;
pr_debug("pmtu discovery on SA AH/%08x/%08x\n",
ntohl(ah->spi), ntohl(iph->daddr)); if (icmp_hdr(skb)->type == ICMP_DEST_UNREACH)
ipv4_update_pmtu(skb, net, info, 0, 0, IPPROTO_AH, 0); ipv4_update_pmtu(skb, net, info, 0, 0, IPPROTO_AH, 0);
else
ipv4_redirect(skb, net, 0, 0, IPPROTO_AH, 0);
xfrm_state_put(x); xfrm_state_put(x);
} }
......
...@@ -484,17 +484,25 @@ static void esp4_err(struct sk_buff *skb, u32 info) ...@@ -484,17 +484,25 @@ static void esp4_err(struct sk_buff *skb, u32 info)
struct ip_esp_hdr *esph = (struct ip_esp_hdr *)(skb->data+(iph->ihl<<2)); struct ip_esp_hdr *esph = (struct ip_esp_hdr *)(skb->data+(iph->ihl<<2));
struct xfrm_state *x; struct xfrm_state *x;
if (icmp_hdr(skb)->type != ICMP_DEST_UNREACH || switch (icmp_hdr(skb)->type) {
icmp_hdr(skb)->code != ICMP_FRAG_NEEDED) case ICMP_DEST_UNREACH:
if (icmp_hdr(skb)->code != ICMP_FRAG_NEEDED)
return;
case ICMP_REDIRECT:
break;
default:
return; return;
}
x = xfrm_state_lookup(net, skb->mark, (const xfrm_address_t *)&iph->daddr, x = xfrm_state_lookup(net, skb->mark, (const xfrm_address_t *)&iph->daddr,
esph->spi, IPPROTO_ESP, AF_INET); esph->spi, IPPROTO_ESP, AF_INET);
if (!x) if (!x)
return; return;
NETDEBUG(KERN_DEBUG "pmtu discovery on SA ESP/%08x/%08x\n",
ntohl(esph->spi), ntohl(iph->daddr)); if (icmp_hdr(skb)->type == ICMP_DEST_UNREACH)
ipv4_update_pmtu(skb, net, info, 0, 0, IPPROTO_ESP, 0); ipv4_update_pmtu(skb, net, info, 0, 0, IPPROTO_ESP, 0);
else
ipv4_redirect(skb, net, 0, 0, IPPROTO_ESP, 0);
xfrm_state_put(x); xfrm_state_put(x);
} }
......
...@@ -634,18 +634,31 @@ out:; ...@@ -634,18 +634,31 @@ out:;
EXPORT_SYMBOL(icmp_send); EXPORT_SYMBOL(icmp_send);
static void icmp_socket_deliver(struct sk_buff *skb, u32 info)
{
const struct iphdr *iph = (const struct iphdr *) skb->data;
const struct net_protocol *ipprot;
int protocol = iph->protocol;
raw_icmp_error(skb, protocol, info);
rcu_read_lock();
ipprot = rcu_dereference(inet_protos[protocol]);
if (ipprot && ipprot->err_handler)
ipprot->err_handler(skb, info);
rcu_read_unlock();
}
/* /*
* Handle ICMP_DEST_UNREACH, ICMP_TIME_EXCEED, and ICMP_QUENCH. * Handle ICMP_DEST_UNREACH, ICMP_TIME_EXCEED, and ICMP_QUENCH.
*/ */
static void icmp_unreach(struct sk_buff *skb) static void icmp_unreach(struct sk_buff *skb)
{ {
const struct net_protocol *ipprot;
const struct iphdr *iph; const struct iphdr *iph;
struct icmphdr *icmph; struct icmphdr *icmph;
struct net *net; struct net *net;
u32 info = 0; u32 info = 0;
int protocol;
net = dev_net(skb_dst(skb)->dev); net = dev_net(skb_dst(skb)->dev);
...@@ -726,19 +739,7 @@ static void icmp_unreach(struct sk_buff *skb) ...@@ -726,19 +739,7 @@ static void icmp_unreach(struct sk_buff *skb)
if (!pskb_may_pull(skb, iph->ihl * 4 + 8)) if (!pskb_may_pull(skb, iph->ihl * 4 + 8))
goto out; goto out;
iph = (const struct iphdr *)skb->data; icmp_socket_deliver(skb, info);
protocol = iph->protocol;
/*
* Deliver ICMP message to raw sockets. Pretty useless feature?
*/
raw_icmp_error(skb, protocol, info);
rcu_read_lock();
ipprot = rcu_dereference(inet_protos[protocol]);
if (ipprot && ipprot->err_handler)
ipprot->err_handler(skb, info);
rcu_read_unlock();
out: out:
return; return;
...@@ -754,46 +755,15 @@ static void icmp_unreach(struct sk_buff *skb) ...@@ -754,46 +755,15 @@ static void icmp_unreach(struct sk_buff *skb)
static void icmp_redirect(struct sk_buff *skb) static void icmp_redirect(struct sk_buff *skb)
{ {
const struct iphdr *iph; if (skb->len < sizeof(struct iphdr)) {
ICMP_INC_STATS_BH(dev_net(skb->dev), ICMP_MIB_INERRORS);
if (skb->len < sizeof(struct iphdr)) return;
goto out_err;
/*
* Get the copied header of the packet that caused the redirect
*/
if (!pskb_may_pull(skb, sizeof(struct iphdr)))
goto out;
iph = (const struct iphdr *)skb->data;
switch (icmp_hdr(skb)->code & 7) {
case ICMP_REDIR_NET:
case ICMP_REDIR_NETTOS:
/*
* As per RFC recommendations now handle it as a host redirect.
*/
case ICMP_REDIR_HOST:
case ICMP_REDIR_HOSTTOS:
ip_rt_redirect(ip_hdr(skb)->saddr, iph->daddr,
icmp_hdr(skb)->un.gateway,
iph->saddr, skb->dev);
break;
} }
/* Ping wants to see redirects. if (!pskb_may_pull(skb, sizeof(struct iphdr)))
* Let's pretend they are errors of sorts... */ return;
if (iph->protocol == IPPROTO_ICMP &&
iph->ihl >= 5 &&
pskb_may_pull(skb, (iph->ihl<<2)+8)) {
ping_err(skb, icmp_hdr(skb)->un.gateway);
}
out: icmp_socket_deliver(skb, icmp_hdr(skb)->un.gateway);
return;
out_err:
ICMP_INC_STATS_BH(dev_net(skb->dev), ICMP_MIB_INERRORS);
goto out;
} }
/* /*
......
...@@ -528,6 +528,9 @@ static void ipgre_err(struct sk_buff *skb, u32 info) ...@@ -528,6 +528,9 @@ static void ipgre_err(struct sk_buff *skb, u32 info)
if (code != ICMP_EXC_TTL) if (code != ICMP_EXC_TTL)
return; return;
break; break;
case ICMP_REDIRECT:
break;
} }
rcu_read_lock(); rcu_read_lock();
...@@ -543,7 +546,11 @@ static void ipgre_err(struct sk_buff *skb, u32 info) ...@@ -543,7 +546,11 @@ static void ipgre_err(struct sk_buff *skb, u32 info)
t->parms.link, 0, IPPROTO_GRE, 0); t->parms.link, 0, IPPROTO_GRE, 0);
goto out; goto out;
} }
if (type == ICMP_REDIRECT) {
ipv4_redirect(skb, dev_net(skb->dev), t->parms.link, 0,
IPPROTO_GRE, 0);
goto out;
}
if (t->parms.iph.daddr == 0 || if (t->parms.iph.daddr == 0 ||
ipv4_is_multicast(t->parms.iph.daddr)) ipv4_is_multicast(t->parms.iph.daddr))
goto out; goto out;
......
...@@ -31,18 +31,26 @@ static void ipcomp4_err(struct sk_buff *skb, u32 info) ...@@ -31,18 +31,26 @@ static void ipcomp4_err(struct sk_buff *skb, u32 info)
struct ip_comp_hdr *ipch = (struct ip_comp_hdr *)(skb->data+(iph->ihl<<2)); struct ip_comp_hdr *ipch = (struct ip_comp_hdr *)(skb->data+(iph->ihl<<2));
struct xfrm_state *x; struct xfrm_state *x;
if (icmp_hdr(skb)->type != ICMP_DEST_UNREACH || switch (icmp_hdr(skb)->type) {
icmp_hdr(skb)->code != ICMP_FRAG_NEEDED) case ICMP_DEST_UNREACH:
if (icmp_hdr(skb)->code != ICMP_FRAG_NEEDED)
return;
case ICMP_REDIRECT:
break;
default:
return; return;
}
spi = htonl(ntohs(ipch->cpi)); spi = htonl(ntohs(ipch->cpi));
x = xfrm_state_lookup(net, skb->mark, (const xfrm_address_t *)&iph->daddr, x = xfrm_state_lookup(net, skb->mark, (const xfrm_address_t *)&iph->daddr,
spi, IPPROTO_COMP, AF_INET); spi, IPPROTO_COMP, AF_INET);
if (!x) if (!x)
return; return;
NETDEBUG(KERN_DEBUG "pmtu discovery on SA IPCOMP/%08x/%pI4\n",
spi, &iph->daddr); if (icmp_hdr(skb)->type == ICMP_DEST_UNREACH)
ipv4_update_pmtu(skb, net, info, 0, 0, IPPROTO_COMP, 0); ipv4_update_pmtu(skb, net, info, 0, 0, IPPROTO_COMP, 0);
else
ipv4_redirect(skb, net, 0, 0, IPPROTO_COMP, 0);
xfrm_state_put(x); xfrm_state_put(x);
} }
......
...@@ -360,6 +360,8 @@ static int ipip_err(struct sk_buff *skb, u32 info) ...@@ -360,6 +360,8 @@ static int ipip_err(struct sk_buff *skb, u32 info)
if (code != ICMP_EXC_TTL) if (code != ICMP_EXC_TTL)
return 0; return 0;
break; break;
case ICMP_REDIRECT:
break;
} }
err = -ENOENT; err = -ENOENT;
...@@ -376,6 +378,13 @@ static int ipip_err(struct sk_buff *skb, u32 info) ...@@ -376,6 +378,13 @@ static int ipip_err(struct sk_buff *skb, u32 info)
goto out; goto out;
} }
if (type == ICMP_REDIRECT) {
ipv4_redirect(skb, dev_net(skb->dev), t->dev->ifindex, 0,
IPPROTO_IPIP, 0);
err = 0;
goto out;
}
if (t->parms.iph.daddr == 0) if (t->parms.iph.daddr == 0)
goto out; goto out;
......
...@@ -387,6 +387,7 @@ void ping_err(struct sk_buff *skb, u32 info) ...@@ -387,6 +387,7 @@ void ping_err(struct sk_buff *skb, u32 info)
break; break;
case ICMP_REDIRECT: case ICMP_REDIRECT:
/* See ICMP_SOURCE_QUENCH */ /* See ICMP_SOURCE_QUENCH */
ipv4_sk_redirect(skb, sk);
err = EREMOTEIO; err = EREMOTEIO;
break; break;
} }
......
...@@ -218,6 +218,8 @@ static void raw_err(struct sock *sk, struct sk_buff *skb, u32 info) ...@@ -218,6 +218,8 @@ static void raw_err(struct sock *sk, struct sk_buff *skb, u32 info)
if (type == ICMP_DEST_UNREACH && code == ICMP_FRAG_NEEDED) if (type == ICMP_DEST_UNREACH && code == ICMP_FRAG_NEEDED)
ipv4_sk_update_pmtu(skb, sk, info); ipv4_sk_update_pmtu(skb, sk, info);
else if (type == ICMP_REDIRECT)
ipv4_sk_redirect(skb, sk);
/* Report error on raw socket, if: /* Report error on raw socket, if:
1. User requested ip_recverr. 1. User requested ip_recverr.
......
...@@ -149,6 +149,7 @@ static void ipv4_dst_destroy(struct dst_entry *dst); ...@@ -149,6 +149,7 @@ static void ipv4_dst_destroy(struct dst_entry *dst);
static struct dst_entry *ipv4_negative_advice(struct dst_entry *dst); static struct dst_entry *ipv4_negative_advice(struct dst_entry *dst);
static void ipv4_link_failure(struct sk_buff *skb); static void ipv4_link_failure(struct sk_buff *skb);
static void ip_rt_update_pmtu(struct dst_entry *dst, u32 mtu); static void ip_rt_update_pmtu(struct dst_entry *dst, u32 mtu);
static void ip_do_redirect(struct dst_entry *dst, struct sk_buff *skb);
static int rt_garbage_collect(struct dst_ops *ops); static int rt_garbage_collect(struct dst_ops *ops);
static void ipv4_dst_ifdown(struct dst_entry *dst, struct net_device *dev, static void ipv4_dst_ifdown(struct dst_entry *dst, struct net_device *dev,
...@@ -179,6 +180,7 @@ static struct dst_ops ipv4_dst_ops = { ...@@ -179,6 +180,7 @@ static struct dst_ops ipv4_dst_ops = {
.negative_advice = ipv4_negative_advice, .negative_advice = ipv4_negative_advice,
.link_failure = ipv4_link_failure, .link_failure = ipv4_link_failure,
.update_pmtu = ip_rt_update_pmtu, .update_pmtu = ip_rt_update_pmtu,
.redirect = ip_do_redirect,
.local_out = __ip_local_out, .local_out = __ip_local_out,
.neigh_lookup = ipv4_neigh_lookup, .neigh_lookup = ipv4_neigh_lookup,
}; };
...@@ -1271,16 +1273,35 @@ static void rt_del(unsigned int hash, struct rtable *rt) ...@@ -1271,16 +1273,35 @@ static void rt_del(unsigned int hash, struct rtable *rt)
spin_unlock_bh(rt_hash_lock_addr(hash)); spin_unlock_bh(rt_hash_lock_addr(hash));
} }
/* called in rcu_read_lock() section */ static void ip_do_redirect(struct dst_entry *dst, struct sk_buff *skb)
void ip_rt_redirect(__be32 old_gw, __be32 daddr, __be32 new_gw,
__be32 saddr, struct net_device *dev)
{ {
int s, i; const struct iphdr *iph = (const struct iphdr *) skb->data;
struct in_device *in_dev = __in_dev_get_rcu(dev); __be32 new_gw = icmp_hdr(skb)->un.gateway;
__be32 skeys[2] = { saddr, 0 }; __be32 old_gw = ip_hdr(skb)->saddr;
int ikeys[2] = { dev->ifindex, 0 }; struct net_device *dev = skb->dev;
__be32 daddr = iph->daddr;
__be32 saddr = iph->saddr;
struct in_device *in_dev;
struct neighbour *n;
struct rtable *rt;
struct net *net; struct net *net;
switch (icmp_hdr(skb)->code & 7) {
case ICMP_REDIR_NET:
case ICMP_REDIR_NETTOS:
case ICMP_REDIR_HOST:
case ICMP_REDIR_HOSTTOS:
break;
default:
return;
}
rt = (struct rtable *) dst;
if (rt->rt_gateway != old_gw)
return;
in_dev = __in_dev_get_rcu(dev);
if (!in_dev) if (!in_dev)
return; return;
...@@ -1300,45 +1321,16 @@ void ip_rt_redirect(__be32 old_gw, __be32 daddr, __be32 new_gw, ...@@ -1300,45 +1321,16 @@ void ip_rt_redirect(__be32 old_gw, __be32 daddr, __be32 new_gw,
goto reject_redirect; goto reject_redirect;
} }
for (s = 0; s < 2; s++) { n = ipv4_neigh_lookup(dst, NULL, &new_gw);
for (i = 0; i < 2; i++) { if (n) {
unsigned int hash; if (!(n->nud_state & NUD_VALID)) {
struct rtable __rcu **rthp; neigh_event_send(n, NULL);
struct rtable *rt; } else {
rt->rt_gateway = new_gw;
hash = rt_hash(daddr, skeys[s], ikeys[i], rt_genid(net)); rt->rt_flags |= RTCF_REDIRECTED;
call_netevent_notifiers(NETEVENT_NEIGH_UPDATE, n);
rthp = &rt_hash_table[hash].chain;
while ((rt = rcu_dereference(*rthp)) != NULL) {
struct neighbour *n;
rthp = &rt->dst.rt_next;
if (rt->rt_key_dst != daddr ||
rt->rt_key_src != skeys[s] ||
rt->rt_oif != ikeys[i] ||
rt_is_input_route(rt) ||
rt_is_expired(rt) ||
!net_eq(dev_net(rt->dst.dev), net) ||
rt->dst.error ||
rt->dst.dev != dev ||
rt->rt_gateway != old_gw)
continue;
n = ipv4_neigh_lookup(&rt->dst, NULL, &new_gw);
if (n) {
if (!(n->nud_state & NUD_VALID)) {
neigh_event_send(n, NULL);
} else {
rt->rt_gateway = new_gw;
rt->rt_flags |= RTCF_REDIRECTED;
call_netevent_notifiers(NETEVENT_NEIGH_UPDATE, n);
}
neigh_release(n);
}
}
} }
neigh_release(n);
} }
return; return;
...@@ -1554,6 +1546,34 @@ void ipv4_sk_update_pmtu(struct sk_buff *skb, struct sock *sk, u32 mtu) ...@@ -1554,6 +1546,34 @@ void ipv4_sk_update_pmtu(struct sk_buff *skb, struct sock *sk, u32 mtu)
} }
EXPORT_SYMBOL_GPL(ipv4_sk_update_pmtu); EXPORT_SYMBOL_GPL(ipv4_sk_update_pmtu);
void ipv4_redirect(struct sk_buff *skb, struct net *net,
int oif, u32 mark, u8 protocol, int flow_flags)
{
const struct iphdr *iph = (const struct iphdr *)skb->data;
struct flowi4 fl4;
struct rtable *rt;
flowi4_init_output(&fl4, oif, mark, RT_TOS(iph->tos), RT_SCOPE_UNIVERSE,
protocol, flow_flags, iph->daddr, iph->saddr, 0, 0);
rt = __ip_route_output_key(net, &fl4);
if (!IS_ERR(rt)) {
ip_do_redirect(&rt->dst, skb);
ip_rt_put(rt);
}
}
EXPORT_SYMBOL_GPL(ipv4_redirect);
void ipv4_sk_redirect(struct sk_buff *skb, struct sock *sk)
{
const struct inet_sock *inet = inet_sk(sk);
return ipv4_redirect(skb, sock_net(sk), sk->sk_bound_dev_if,
sk->sk_mark,
inet->hdrincl ? IPPROTO_RAW : sk->sk_protocol,
inet_sk_flowi_flags(sk));
}
EXPORT_SYMBOL_GPL(ipv4_sk_redirect);
static struct dst_entry *ipv4_dst_check(struct dst_entry *dst, u32 cookie) static struct dst_entry *ipv4_dst_check(struct dst_entry *dst, u32 cookie)
{ {
struct rtable *rt = (struct rtable *) dst; struct rtable *rt = (struct rtable *) dst;
...@@ -2571,6 +2591,10 @@ static void ipv4_rt_blackhole_update_pmtu(struct dst_entry *dst, u32 mtu) ...@@ -2571,6 +2591,10 @@ static void ipv4_rt_blackhole_update_pmtu(struct dst_entry *dst, u32 mtu)
{ {
} }
static void ipv4_rt_blackhole_redirect(struct dst_entry *dst, struct sk_buff *skb)
{
}
static u32 *ipv4_rt_blackhole_cow_metrics(struct dst_entry *dst, static u32 *ipv4_rt_blackhole_cow_metrics(struct dst_entry *dst,
unsigned long old) unsigned long old)
{ {
...@@ -2585,6 +2609,7 @@ static struct dst_ops ipv4_dst_blackhole_ops = { ...@@ -2585,6 +2609,7 @@ static struct dst_ops ipv4_dst_blackhole_ops = {
.mtu = ipv4_blackhole_mtu, .mtu = ipv4_blackhole_mtu,
.default_advmss = ipv4_default_advmss, .default_advmss = ipv4_default_advmss,
.update_pmtu = ipv4_rt_blackhole_update_pmtu, .update_pmtu = ipv4_rt_blackhole_update_pmtu,
.redirect = ipv4_rt_blackhole_redirect,
.cow_metrics = ipv4_rt_blackhole_cow_metrics, .cow_metrics = ipv4_rt_blackhole_cow_metrics,
.neigh_lookup = ipv4_neigh_lookup, .neigh_lookup = ipv4_neigh_lookup,
}; };
......
...@@ -321,6 +321,14 @@ static void do_pmtu_discovery(struct sock *sk, const struct iphdr *iph, u32 mtu) ...@@ -321,6 +321,14 @@ static void do_pmtu_discovery(struct sock *sk, const struct iphdr *iph, u32 mtu)
} /* else let the usual retransmit timer handle it */ } /* else let the usual retransmit timer handle it */
} }
static void do_redirect(struct sk_buff *skb, struct sock *sk)
{
struct dst_entry *dst = __sk_dst_check(sk, 0);
if (dst)
dst->ops->redirect(dst, skb);
}
/* /*
* This routine is called by the ICMP module when it gets some * This routine is called by the ICMP module when it gets some
* sort of error condition. If err < 0 then the socket should * sort of error condition. If err < 0 then the socket should
...@@ -394,6 +402,9 @@ void tcp_v4_err(struct sk_buff *icmp_skb, u32 info) ...@@ -394,6 +402,9 @@ void tcp_v4_err(struct sk_buff *icmp_skb, u32 info)
} }
switch (type) { switch (type) {
case ICMP_REDIRECT:
do_redirect(icmp_skb, sk);
goto out;
case ICMP_SOURCE_QUENCH: case ICMP_SOURCE_QUENCH:
/* Just silently ignore these. */ /* Just silently ignore these. */
goto out; goto out;
......
...@@ -630,6 +630,9 @@ void __udp4_lib_err(struct sk_buff *skb, u32 info, struct udp_table *udptable) ...@@ -630,6 +630,9 @@ void __udp4_lib_err(struct sk_buff *skb, u32 info, struct udp_table *udptable)
err = icmp_err_convert[code].errno; err = icmp_err_convert[code].errno;
} }
break; break;
case ICMP_REDIRECT:
ipv4_sk_redirect(skb, sk);
break;
} }
/* /*
......
...@@ -202,6 +202,14 @@ static void xfrm4_update_pmtu(struct dst_entry *dst, u32 mtu) ...@@ -202,6 +202,14 @@ static void xfrm4_update_pmtu(struct dst_entry *dst, u32 mtu)
path->ops->update_pmtu(path, mtu); path->ops->update_pmtu(path, mtu);
} }
static void xfrm4_redirect(struct dst_entry *dst, struct sk_buff *skb)
{
struct xfrm_dst *xdst = (struct xfrm_dst *)dst;
struct dst_entry *path = xdst->route;
path->ops->redirect(path, skb);
}
static void xfrm4_dst_destroy(struct dst_entry *dst) static void xfrm4_dst_destroy(struct dst_entry *dst)
{ {
struct xfrm_dst *xdst = (struct xfrm_dst *)dst; struct xfrm_dst *xdst = (struct xfrm_dst *)dst;
...@@ -225,6 +233,7 @@ static struct dst_ops xfrm4_dst_ops = { ...@@ -225,6 +233,7 @@ static struct dst_ops xfrm4_dst_ops = {
.protocol = cpu_to_be16(ETH_P_IP), .protocol = cpu_to_be16(ETH_P_IP),
.gc = xfrm4_garbage_collect, .gc = xfrm4_garbage_collect,
.update_pmtu = xfrm4_update_pmtu, .update_pmtu = xfrm4_update_pmtu,
.redirect = xfrm4_redirect,
.cow_metrics = dst_cow_metrics_generic, .cow_metrics = dst_cow_metrics_generic,
.destroy = xfrm4_dst_destroy, .destroy = xfrm4_dst_destroy,
.ifdown = xfrm4_dst_ifdown, .ifdown = xfrm4_dst_ifdown,
......
...@@ -613,16 +613,18 @@ static void ah6_err(struct sk_buff *skb, struct inet6_skb_parm *opt, ...@@ -613,16 +613,18 @@ static void ah6_err(struct sk_buff *skb, struct inet6_skb_parm *opt,
struct xfrm_state *x; struct xfrm_state *x;
if (type != ICMPV6_DEST_UNREACH && if (type != ICMPV6_DEST_UNREACH &&
type != ICMPV6_PKT_TOOBIG) type != ICMPV6_PKT_TOOBIG &&
type != NDISC_REDIRECT)
return; return;
x = xfrm_state_lookup(net, skb->mark, (xfrm_address_t *)&iph->daddr, ah->spi, IPPROTO_AH, AF_INET6); x = xfrm_state_lookup(net, skb->mark, (xfrm_address_t *)&iph->daddr, ah->spi, IPPROTO_AH, AF_INET6);
if (!x) if (!x)
return; return;
NETDEBUG(KERN_DEBUG "pmtu discovery on SA AH/%08x/%pI6\n", if (type == NDISC_REDIRECT)
ntohl(ah->spi), &iph->daddr); ip6_redirect(skb, net, 0, 0);
ip6_update_pmtu(skb, net, info, 0, 0); else
ip6_update_pmtu(skb, net, info, 0, 0);
xfrm_state_put(x); xfrm_state_put(x);
} }
......
...@@ -434,16 +434,19 @@ static void esp6_err(struct sk_buff *skb, struct inet6_skb_parm *opt, ...@@ -434,16 +434,19 @@ static void esp6_err(struct sk_buff *skb, struct inet6_skb_parm *opt,
struct xfrm_state *x; struct xfrm_state *x;
if (type != ICMPV6_DEST_UNREACH && if (type != ICMPV6_DEST_UNREACH &&
type != ICMPV6_PKT_TOOBIG) type != ICMPV6_PKT_TOOBIG &&
type != NDISC_REDIRECT)
return; return;
x = xfrm_state_lookup(net, skb->mark, (const xfrm_address_t *)&iph->daddr, x = xfrm_state_lookup(net, skb->mark, (const xfrm_address_t *)&iph->daddr,
esph->spi, IPPROTO_ESP, AF_INET6); esph->spi, IPPROTO_ESP, AF_INET6);
if (!x) if (!x)
return; return;
pr_debug("pmtu discovery on SA ESP/%08x/%pI6\n",
ntohl(esph->spi), &iph->daddr); if (type == NDISC_REDIRECT)
ip6_update_pmtu(skb, net, info, 0, 0); ip6_redirect(skb, net, 0, 0);
else
ip6_update_pmtu(skb, net, info, 0, 0);
xfrm_state_put(x); xfrm_state_put(x);
} }
......
...@@ -598,7 +598,7 @@ static void icmpv6_echo_reply(struct sk_buff *skb) ...@@ -598,7 +598,7 @@ static void icmpv6_echo_reply(struct sk_buff *skb)
icmpv6_xmit_unlock(sk); icmpv6_xmit_unlock(sk);
} }
static void icmpv6_notify(struct sk_buff *skb, u8 type, u8 code, __be32 info) void icmpv6_notify(struct sk_buff *skb, u8 type, u8 code, __be32 info)
{ {
const struct inet6_protocol *ipprot; const struct inet6_protocol *ipprot;
int inner_offset; int inner_offset;
......
...@@ -550,6 +550,9 @@ ip4ip6_err(struct sk_buff *skb, struct inet6_skb_parm *opt, ...@@ -550,6 +550,9 @@ ip4ip6_err(struct sk_buff *skb, struct inet6_skb_parm *opt,
rel_type = ICMP_DEST_UNREACH; rel_type = ICMP_DEST_UNREACH;
rel_code = ICMP_FRAG_NEEDED; rel_code = ICMP_FRAG_NEEDED;
break; break;
case NDISC_REDIRECT:
rel_type = ICMP_REDIRECT;
rel_code = ICMP_REDIR_HOST;
default: default:
return 0; return 0;
} }
...@@ -608,6 +611,8 @@ ip4ip6_err(struct sk_buff *skb, struct inet6_skb_parm *opt, ...@@ -608,6 +611,8 @@ ip4ip6_err(struct sk_buff *skb, struct inet6_skb_parm *opt,
skb_dst(skb2)->ops->update_pmtu(skb_dst(skb2), rel_info); skb_dst(skb2)->ops->update_pmtu(skb_dst(skb2), rel_info);
} }
if (rel_type == ICMP_REDIRECT)
skb_dst(skb2)->ops->redirect(skb_dst(skb2), skb2);
icmp_send(skb2, rel_type, rel_code, htonl(rel_info)); icmp_send(skb2, rel_type, rel_code, htonl(rel_info));
......
...@@ -64,7 +64,9 @@ static void ipcomp6_err(struct sk_buff *skb, struct inet6_skb_parm *opt, ...@@ -64,7 +64,9 @@ static void ipcomp6_err(struct sk_buff *skb, struct inet6_skb_parm *opt,
(struct ip_comp_hdr *)(skb->data + offset); (struct ip_comp_hdr *)(skb->data + offset);
struct xfrm_state *x; struct xfrm_state *x;
if (type != ICMPV6_DEST_UNREACH && type != ICMPV6_PKT_TOOBIG) if (type != ICMPV6_DEST_UNREACH &&
type != ICMPV6_PKT_TOOBIG &&
type != NDISC_REDIRECT)
return; return;
spi = htonl(ntohs(ipcomph->cpi)); spi = htonl(ntohs(ipcomph->cpi));
...@@ -73,9 +75,10 @@ static void ipcomp6_err(struct sk_buff *skb, struct inet6_skb_parm *opt, ...@@ -73,9 +75,10 @@ static void ipcomp6_err(struct sk_buff *skb, struct inet6_skb_parm *opt,
if (!x) if (!x)
return; return;
pr_debug("pmtu discovery on SA IPCOMP/%08x/%pI6\n", if (type == NDISC_REDIRECT)
spi, &iph->daddr); ip6_redirect(skb, net, 0, 0);
ip6_update_pmtu(skb, net, info, 0, 0); else
ip6_update_pmtu(skb, net, info, 0, 0);
xfrm_state_put(x); xfrm_state_put(x);
} }
......
...@@ -143,40 +143,6 @@ struct neigh_table nd_tbl = { ...@@ -143,40 +143,6 @@ struct neigh_table nd_tbl = {
.gc_thresh3 = 1024, .gc_thresh3 = 1024,
}; };
/* ND options */
struct ndisc_options {
struct nd_opt_hdr *nd_opt_array[__ND_OPT_ARRAY_MAX];
#ifdef CONFIG_IPV6_ROUTE_INFO
struct nd_opt_hdr *nd_opts_ri;
struct nd_opt_hdr *nd_opts_ri_end;
#endif
struct nd_opt_hdr *nd_useropts;
struct nd_opt_hdr *nd_useropts_end;
};
#define nd_opts_src_lladdr nd_opt_array[ND_OPT_SOURCE_LL_ADDR]
#define nd_opts_tgt_lladdr nd_opt_array[ND_OPT_TARGET_LL_ADDR]
#define nd_opts_pi nd_opt_array[ND_OPT_PREFIX_INFO]
#define nd_opts_pi_end nd_opt_array[__ND_OPT_PREFIX_INFO_END]
#define nd_opts_rh nd_opt_array[ND_OPT_REDIRECT_HDR]
#define nd_opts_mtu nd_opt_array[ND_OPT_MTU]
#define NDISC_OPT_SPACE(len) (((len)+2+7)&~7)
/*
* Return the padding between the option length and the start of the
* link addr. Currently only IP-over-InfiniBand needs this, although
* if RFC 3831 IPv6-over-Fibre Channel is ever implemented it may
* also need a pad of 2.
*/
static int ndisc_addr_option_pad(unsigned short type)
{
switch (type) {
case ARPHRD_INFINIBAND: return 2;
default: return 0;
}
}
static inline int ndisc_opt_addr_space(struct net_device *dev) static inline int ndisc_opt_addr_space(struct net_device *dev)
{ {
return NDISC_OPT_SPACE(dev->addr_len + ndisc_addr_option_pad(dev->type)); return NDISC_OPT_SPACE(dev->addr_len + ndisc_addr_option_pad(dev->type));
...@@ -233,8 +199,8 @@ static struct nd_opt_hdr *ndisc_next_useropt(struct nd_opt_hdr *cur, ...@@ -233,8 +199,8 @@ static struct nd_opt_hdr *ndisc_next_useropt(struct nd_opt_hdr *cur,
return cur <= end && ndisc_is_useropt(cur) ? cur : NULL; return cur <= end && ndisc_is_useropt(cur) ? cur : NULL;
} }
static struct ndisc_options *ndisc_parse_options(u8 *opt, int opt_len, struct ndisc_options *ndisc_parse_options(u8 *opt, int opt_len,
struct ndisc_options *ndopts) struct ndisc_options *ndopts)
{ {
struct nd_opt_hdr *nd_opt = (struct nd_opt_hdr *)opt; struct nd_opt_hdr *nd_opt = (struct nd_opt_hdr *)opt;
...@@ -297,17 +263,6 @@ static struct ndisc_options *ndisc_parse_options(u8 *opt, int opt_len, ...@@ -297,17 +263,6 @@ static struct ndisc_options *ndisc_parse_options(u8 *opt, int opt_len,
return ndopts; return ndopts;
} }
static inline u8 *ndisc_opt_addr_data(struct nd_opt_hdr *p,
struct net_device *dev)
{
u8 *lladdr = (u8 *)(p + 1);
int lladdrlen = p->nd_opt_len << 3;
int prepad = ndisc_addr_option_pad(dev->type);
if (lladdrlen != NDISC_OPT_SPACE(dev->addr_len + prepad))
return NULL;
return lladdr + prepad;
}
int ndisc_mc_map(const struct in6_addr *addr, char *buf, struct net_device *dev, int dir) int ndisc_mc_map(const struct in6_addr *addr, char *buf, struct net_device *dev, int dir)
{ {
switch (dev->type) { switch (dev->type) {
...@@ -1379,16 +1334,6 @@ static void ndisc_router_discovery(struct sk_buff *skb) ...@@ -1379,16 +1334,6 @@ static void ndisc_router_discovery(struct sk_buff *skb)
static void ndisc_redirect_rcv(struct sk_buff *skb) static void ndisc_redirect_rcv(struct sk_buff *skb)
{ {
struct inet6_dev *in6_dev;
struct icmp6hdr *icmph;
const struct in6_addr *dest;
const struct in6_addr *target; /* new first hop to destination */
struct neighbour *neigh;
int on_link = 0;
struct ndisc_options ndopts;
int optlen;
u8 *lladdr = NULL;
#ifdef CONFIG_IPV6_NDISC_NODETYPE #ifdef CONFIG_IPV6_NDISC_NODETYPE
switch (skb->ndisc_nodetype) { switch (skb->ndisc_nodetype) {
case NDISC_NODETYPE_HOST: case NDISC_NODETYPE_HOST:
...@@ -1405,65 +1350,7 @@ static void ndisc_redirect_rcv(struct sk_buff *skb) ...@@ -1405,65 +1350,7 @@ static void ndisc_redirect_rcv(struct sk_buff *skb)
return; return;
} }
optlen = skb->tail - skb->transport_header; icmpv6_notify(skb, NDISC_REDIRECT, 0, 0);
optlen -= sizeof(struct icmp6hdr) + 2 * sizeof(struct in6_addr);
if (optlen < 0) {
ND_PRINTK(2, warn, "Redirect: packet too short\n");
return;
}
icmph = icmp6_hdr(skb);
target = (const struct in6_addr *) (icmph + 1);
dest = target + 1;
if (ipv6_addr_is_multicast(dest)) {
ND_PRINTK(2, warn,
"Redirect: destination address is multicast\n");
return;
}
if (ipv6_addr_equal(dest, target)) {
on_link = 1;
} else if (ipv6_addr_type(target) !=
(IPV6_ADDR_UNICAST|IPV6_ADDR_LINKLOCAL)) {
ND_PRINTK(2, warn,
"Redirect: target address is not link-local unicast\n");
return;
}
in6_dev = __in6_dev_get(skb->dev);
if (!in6_dev)
return;
if (in6_dev->cnf.forwarding || !in6_dev->cnf.accept_redirects)
return;
/* RFC2461 8.1:
* The IP source address of the Redirect MUST be the same as the current
* first-hop router for the specified ICMP Destination Address.
*/
if (!ndisc_parse_options((u8*)(dest + 1), optlen, &ndopts)) {
ND_PRINTK(2, warn, "Redirect: invalid ND options\n");
return;
}
if (ndopts.nd_opts_tgt_lladdr) {
lladdr = ndisc_opt_addr_data(ndopts.nd_opts_tgt_lladdr,
skb->dev);
if (!lladdr) {
ND_PRINTK(2, warn,
"Redirect: invalid link-layer address length\n");
return;
}
}
neigh = __neigh_lookup(&nd_tbl, target, skb->dev, 1);
if (neigh) {
rt6_redirect(dest, &ipv6_hdr(skb)->daddr,
&ipv6_hdr(skb)->saddr, neigh, lladdr,
on_link);
neigh_release(neigh);
}
} }
void ndisc_send_redirect(struct sk_buff *skb, const struct in6_addr *target) void ndisc_send_redirect(struct sk_buff *skb, const struct in6_addr *target)
......
...@@ -332,6 +332,8 @@ static void rawv6_err(struct sock *sk, struct sk_buff *skb, ...@@ -332,6 +332,8 @@ static void rawv6_err(struct sock *sk, struct sk_buff *skb,
ip6_sk_update_pmtu(skb, sk, info); ip6_sk_update_pmtu(skb, sk, info);
harderr = (np->pmtudisc == IPV6_PMTUDISC_DO); harderr = (np->pmtudisc == IPV6_PMTUDISC_DO);
} }
if (type == NDISC_REDIRECT)
ip6_sk_redirect(skb, sk);
if (np->recverr) { if (np->recverr) {
u8 *payload = skb->data; u8 *payload = skb->data;
if (!inet->hdrincl) if (!inet->hdrincl)
......
...@@ -79,6 +79,7 @@ static int ip6_pkt_discard(struct sk_buff *skb); ...@@ -79,6 +79,7 @@ static int ip6_pkt_discard(struct sk_buff *skb);
static int ip6_pkt_discard_out(struct sk_buff *skb); static int ip6_pkt_discard_out(struct sk_buff *skb);
static void ip6_link_failure(struct sk_buff *skb); static void ip6_link_failure(struct sk_buff *skb);
static void ip6_rt_update_pmtu(struct dst_entry *dst, u32 mtu); static void ip6_rt_update_pmtu(struct dst_entry *dst, u32 mtu);
static void rt6_do_redirect(struct dst_entry *dst, struct sk_buff *skb);
#ifdef CONFIG_IPV6_ROUTE_INFO #ifdef CONFIG_IPV6_ROUTE_INFO
static struct rt6_info *rt6_add_route_info(struct net *net, static struct rt6_info *rt6_add_route_info(struct net *net,
...@@ -174,6 +175,7 @@ static struct dst_ops ip6_dst_ops_template = { ...@@ -174,6 +175,7 @@ static struct dst_ops ip6_dst_ops_template = {
.negative_advice = ip6_negative_advice, .negative_advice = ip6_negative_advice,
.link_failure = ip6_link_failure, .link_failure = ip6_link_failure,
.update_pmtu = ip6_rt_update_pmtu, .update_pmtu = ip6_rt_update_pmtu,
.redirect = rt6_do_redirect,
.local_out = __ip6_local_out, .local_out = __ip6_local_out,
.neigh_lookup = ip6_neigh_lookup, .neigh_lookup = ip6_neigh_lookup,
}; };
...@@ -189,6 +191,10 @@ static void ip6_rt_blackhole_update_pmtu(struct dst_entry *dst, u32 mtu) ...@@ -189,6 +191,10 @@ static void ip6_rt_blackhole_update_pmtu(struct dst_entry *dst, u32 mtu)
{ {
} }
static void ip6_rt_blackhole_redirect(struct dst_entry *dst, struct sk_buff *skb)
{
}
static u32 *ip6_rt_blackhole_cow_metrics(struct dst_entry *dst, static u32 *ip6_rt_blackhole_cow_metrics(struct dst_entry *dst,
unsigned long old) unsigned long old)
{ {
...@@ -203,6 +209,7 @@ static struct dst_ops ip6_dst_blackhole_ops = { ...@@ -203,6 +209,7 @@ static struct dst_ops ip6_dst_blackhole_ops = {
.mtu = ip6_blackhole_mtu, .mtu = ip6_blackhole_mtu,
.default_advmss = ip6_default_advmss, .default_advmss = ip6_default_advmss,
.update_pmtu = ip6_rt_blackhole_update_pmtu, .update_pmtu = ip6_rt_blackhole_update_pmtu,
.redirect = ip6_rt_blackhole_redirect,
.cow_metrics = ip6_rt_blackhole_cow_metrics, .cow_metrics = ip6_rt_blackhole_cow_metrics,
.neigh_lookup = ip6_neigh_lookup, .neigh_lookup = ip6_neigh_lookup,
}; };
...@@ -1112,6 +1119,33 @@ void ip6_sk_update_pmtu(struct sk_buff *skb, struct sock *sk, __be32 mtu) ...@@ -1112,6 +1119,33 @@ void ip6_sk_update_pmtu(struct sk_buff *skb, struct sock *sk, __be32 mtu)
} }
EXPORT_SYMBOL_GPL(ip6_sk_update_pmtu); EXPORT_SYMBOL_GPL(ip6_sk_update_pmtu);
void ip6_redirect(struct sk_buff *skb, struct net *net, int oif, u32 mark)
{
const struct ipv6hdr *iph = (struct ipv6hdr *) skb->data;
struct dst_entry *dst;
struct flowi6 fl6;
memset(&fl6, 0, sizeof(fl6));
fl6.flowi6_oif = oif;
fl6.flowi6_mark = mark;
fl6.flowi6_flags = 0;
fl6.daddr = iph->daddr;
fl6.saddr = iph->saddr;
fl6.flowlabel = (*(__be32 *) iph) & IPV6_FLOWINFO_MASK;
dst = ip6_route_output(net, NULL, &fl6);
if (!dst->error)
rt6_do_redirect(dst, skb);
dst_release(dst);
}
EXPORT_SYMBOL_GPL(ip6_redirect);
void ip6_sk_redirect(struct sk_buff *skb, struct sock *sk)
{
ip6_redirect(skb, sock_net(sk), sk->sk_bound_dev_if, sk->sk_mark);
}
EXPORT_SYMBOL_GPL(ip6_sk_redirect);
static unsigned int ip6_default_advmss(const struct dst_entry *dst) static unsigned int ip6_default_advmss(const struct dst_entry *dst)
{ {
struct net_device *dev = dst->dev; struct net_device *dev = dst->dev;
...@@ -1604,108 +1638,94 @@ static int ip6_route_del(struct fib6_config *cfg) ...@@ -1604,108 +1638,94 @@ static int ip6_route_del(struct fib6_config *cfg)
return err; return err;
} }
/* static void rt6_do_redirect(struct dst_entry *dst, struct sk_buff *skb)
* Handle redirects
*/
struct ip6rd_flowi {
struct flowi6 fl6;
struct in6_addr gateway;
};
static struct rt6_info *__ip6_route_redirect(struct net *net,
struct fib6_table *table,
struct flowi6 *fl6,
int flags)
{ {
struct ip6rd_flowi *rdfl = (struct ip6rd_flowi *)fl6; struct net *net = dev_net(skb->dev);
struct rt6_info *rt; struct netevent_redirect netevent;
struct fib6_node *fn; struct rt6_info *rt, *nrt = NULL;
const struct in6_addr *target;
struct ndisc_options ndopts;
const struct in6_addr *dest;
struct neighbour *old_neigh;
struct inet6_dev *in6_dev;
struct neighbour *neigh;
struct icmp6hdr *icmph;
int optlen, on_link;
u8 *lladdr;
/* optlen = skb->tail - skb->transport_header;
* Get the "current" route for this destination and optlen -= sizeof(struct icmp6hdr) + 2 * sizeof(struct in6_addr);
* check if the redirect has come from approriate router.
*
* RFC 2461 specifies that redirects should only be
* accepted if they come from the nexthop to the target.
* Due to the way the routes are chosen, this notion
* is a bit fuzzy and one might need to check all possible
* routes.
*/
read_lock_bh(&table->tb6_lock); if (optlen < 0) {
fn = fib6_lookup(&table->tb6_root, &fl6->daddr, &fl6->saddr); net_dbg_ratelimited("rt6_do_redirect: packet too short\n");
restart: return;
for (rt = fn->leaf; rt; rt = rt->dst.rt6_next) {
/*
* Current route is on-link; redirect is always invalid.
*
* Seems, previous statement is not true. It could
* be node, which looks for us as on-link (f.e. proxy ndisc)
* But then router serving it might decide, that we should
* know truth 8)8) --ANK (980726).
*/
if (rt6_check_expired(rt))
continue;
if (!(rt->rt6i_flags & RTF_GATEWAY))
continue;
if (fl6->flowi6_oif != rt->dst.dev->ifindex)
continue;
if (!ipv6_addr_equal(&rdfl->gateway, &rt->rt6i_gateway))
continue;
break;
} }
if (!rt) icmph = icmp6_hdr(skb);
rt = net->ipv6.ip6_null_entry; target = (const struct in6_addr *) (icmph + 1);
BACKTRACK(net, &fl6->saddr); dest = target + 1;
out:
dst_hold(&rt->dst);
read_unlock_bh(&table->tb6_lock); if (ipv6_addr_is_multicast(dest)) {
net_dbg_ratelimited("rt6_do_redirect: destination address is multicast\n");
return rt; return;
}; }
static struct rt6_info *ip6_route_redirect(const struct in6_addr *dest,
const struct in6_addr *src,
const struct in6_addr *gateway,
struct net_device *dev)
{
int flags = RT6_LOOKUP_F_HAS_SADDR;
struct net *net = dev_net(dev);
struct ip6rd_flowi rdfl = {
.fl6 = {
.flowi6_oif = dev->ifindex,
.daddr = *dest,
.saddr = *src,
},
};
rdfl.gateway = *gateway; on_link = 0;
if (ipv6_addr_equal(dest, target)) {
on_link = 1;
} else if (ipv6_addr_type(target) !=
(IPV6_ADDR_UNICAST|IPV6_ADDR_LINKLOCAL)) {
net_dbg_ratelimited("rt6_do_redirect: target address is not link-local unicast\n");
return;
}
if (rt6_need_strict(dest)) in6_dev = __in6_dev_get(skb->dev);
flags |= RT6_LOOKUP_F_IFACE; if (!in6_dev)
return;
if (in6_dev->cnf.forwarding || !in6_dev->cnf.accept_redirects)
return;
return (struct rt6_info *)fib6_rule_lookup(net, &rdfl.fl6, /* RFC2461 8.1:
flags, __ip6_route_redirect); * The IP source address of the Redirect MUST be the same as the current
} * first-hop router for the specified ICMP Destination Address.
*/
void rt6_redirect(const struct in6_addr *dest, const struct in6_addr *src, if (!ndisc_parse_options((u8*)(dest + 1), optlen, &ndopts)) {
const struct in6_addr *saddr, net_dbg_ratelimited("rt6_redirect: invalid ND options\n");
struct neighbour *neigh, u8 *lladdr, int on_link) return;
{ }
struct rt6_info *rt, *nrt = NULL;
struct netevent_redirect netevent;
struct net *net = dev_net(neigh->dev);
struct neighbour *old_neigh;
rt = ip6_route_redirect(dest, src, saddr, neigh->dev); lladdr = NULL;
if (ndopts.nd_opts_tgt_lladdr) {
lladdr = ndisc_opt_addr_data(ndopts.nd_opts_tgt_lladdr,
skb->dev);
if (!lladdr) {
net_dbg_ratelimited("rt6_redirect: invalid link-layer address length\n");
return;
}
}
rt = (struct rt6_info *) dst;
if (rt == net->ipv6.ip6_null_entry) { if (rt == net->ipv6.ip6_null_entry) {
net_dbg_ratelimited("rt6_redirect: source isn't a valid nexthop for redirect target\n"); net_dbg_ratelimited("rt6_redirect: source isn't a valid nexthop for redirect target\n");
goto out; return;
} }
/* Redirect received -> path was valid.
* Look, redirects are sent only in response to data packets,
* so that this nexthop apparently is reachable. --ANK
*/
dst_confirm(&rt->dst);
neigh = __neigh_lookup(&nd_tbl, target, skb->dev, 1);
if (!neigh)
return;
/* Duplicate redirect: silently ignore. */
old_neigh = rt->n;
if (neigh == old_neigh)
goto out;
/* /*
* We have finally decided to accept it. * We have finally decided to accept it.
*/ */
...@@ -1717,18 +1737,6 @@ void rt6_redirect(const struct in6_addr *dest, const struct in6_addr *src, ...@@ -1717,18 +1737,6 @@ void rt6_redirect(const struct in6_addr *dest, const struct in6_addr *src,
NEIGH_UPDATE_F_ISROUTER)) NEIGH_UPDATE_F_ISROUTER))
); );
/*
* Redirect received -> path was valid.
* Look, redirects are sent only in response to data packets,
* so that this nexthop apparently is reachable. --ANK
*/
dst_confirm(&rt->dst);
/* Duplicate redirect: silently ignore. */
old_neigh = rt->n;
if (neigh == old_neigh)
goto out;
nrt = ip6_rt_copy(rt, dest); nrt = ip6_rt_copy(rt, dest);
if (!nrt) if (!nrt)
goto out; goto out;
...@@ -1751,12 +1759,12 @@ void rt6_redirect(const struct in6_addr *dest, const struct in6_addr *src, ...@@ -1751,12 +1759,12 @@ void rt6_redirect(const struct in6_addr *dest, const struct in6_addr *src,
call_netevent_notifiers(NETEVENT_REDIRECT, &netevent); call_netevent_notifiers(NETEVENT_REDIRECT, &netevent);
if (rt->rt6i_flags & RTF_CACHE) { if (rt->rt6i_flags & RTF_CACHE) {
rt = (struct rt6_info *) dst_clone(&rt->dst);
ip6_del_rt(rt); ip6_del_rt(rt);
return;
} }
out: out:
dst_release(&rt->dst); neigh_release(neigh);
} }
/* /*
......
...@@ -539,6 +539,8 @@ static int ipip6_err(struct sk_buff *skb, u32 info) ...@@ -539,6 +539,8 @@ static int ipip6_err(struct sk_buff *skb, u32 info)
if (code != ICMP_EXC_TTL) if (code != ICMP_EXC_TTL)
return 0; return 0;
break; break;
case ICMP_REDIRECT:
break;
} }
err = -ENOENT; err = -ENOENT;
...@@ -557,6 +559,12 @@ static int ipip6_err(struct sk_buff *skb, u32 info) ...@@ -557,6 +559,12 @@ static int ipip6_err(struct sk_buff *skb, u32 info)
err = 0; err = 0;
goto out; goto out;
} }
if (type == ICMP_REDIRECT) {
ipv4_redirect(skb, dev_net(skb->dev), t->dev->ifindex, 0,
IPPROTO_IPV6, 0);
err = 0;
goto out;
}
if (t->parms.iph.daddr == 0) if (t->parms.iph.daddr == 0)
goto out; goto out;
......
...@@ -363,6 +363,13 @@ static void tcp_v6_err(struct sk_buff *skb, struct inet6_skb_parm *opt, ...@@ -363,6 +363,13 @@ static void tcp_v6_err(struct sk_buff *skb, struct inet6_skb_parm *opt,
np = inet6_sk(sk); np = inet6_sk(sk);
if (type == NDISC_REDIRECT) {
struct dst_entry *dst = __sk_dst_check(sk, np->dst_cookie);
if (dst)
dst->ops->redirect(dst,skb);
}
if (type == ICMPV6_PKT_TOOBIG) { if (type == ICMPV6_PKT_TOOBIG) {
struct dst_entry *dst; struct dst_entry *dst;
......
...@@ -483,6 +483,8 @@ void __udp6_lib_err(struct sk_buff *skb, struct inet6_skb_parm *opt, ...@@ -483,6 +483,8 @@ void __udp6_lib_err(struct sk_buff *skb, struct inet6_skb_parm *opt,
if (type == ICMPV6_PKT_TOOBIG) if (type == ICMPV6_PKT_TOOBIG)
ip6_sk_update_pmtu(skb, sk, info); ip6_sk_update_pmtu(skb, sk, info);
if (type == NDISC_REDIRECT)
ip6_sk_redirect(skb, sk);
np = inet6_sk(sk); np = inet6_sk(sk);
......
...@@ -215,6 +215,14 @@ static void xfrm6_update_pmtu(struct dst_entry *dst, u32 mtu) ...@@ -215,6 +215,14 @@ static void xfrm6_update_pmtu(struct dst_entry *dst, u32 mtu)
path->ops->update_pmtu(path, mtu); path->ops->update_pmtu(path, mtu);
} }
static void xfrm6_redirect(struct dst_entry *dst, struct sk_buff *skb)
{
struct xfrm_dst *xdst = (struct xfrm_dst *)dst;
struct dst_entry *path = xdst->route;
path->ops->redirect(path, skb);
}
static void xfrm6_dst_destroy(struct dst_entry *dst) static void xfrm6_dst_destroy(struct dst_entry *dst)
{ {
struct xfrm_dst *xdst = (struct xfrm_dst *)dst; struct xfrm_dst *xdst = (struct xfrm_dst *)dst;
...@@ -261,6 +269,7 @@ static struct dst_ops xfrm6_dst_ops = { ...@@ -261,6 +269,7 @@ static struct dst_ops xfrm6_dst_ops = {
.protocol = cpu_to_be16(ETH_P_IPV6), .protocol = cpu_to_be16(ETH_P_IPV6),
.gc = xfrm6_garbage_collect, .gc = xfrm6_garbage_collect,
.update_pmtu = xfrm6_update_pmtu, .update_pmtu = xfrm6_update_pmtu,
.redirect = xfrm6_redirect,
.cow_metrics = dst_cow_metrics_generic, .cow_metrics = dst_cow_metrics_generic,
.destroy = xfrm6_dst_destroy, .destroy = xfrm6_dst_destroy,
.ifdown = xfrm6_dst_ifdown, .ifdown = xfrm6_dst_ifdown,
......
...@@ -423,6 +423,18 @@ void sctp_icmp_frag_needed(struct sock *sk, struct sctp_association *asoc, ...@@ -423,6 +423,18 @@ void sctp_icmp_frag_needed(struct sock *sk, struct sctp_association *asoc,
sctp_retransmit(&asoc->outqueue, t, SCTP_RTXR_PMTUD); sctp_retransmit(&asoc->outqueue, t, SCTP_RTXR_PMTUD);
} }
void sctp_icmp_redirect(struct sock *sk, struct sctp_transport *t,
struct sk_buff *skb)
{
struct dst_entry *dst;
if (!t)
return;
dst = sctp_transport_dst_check(t);
if (dst)
dst->ops->redirect(dst, skb);
}
/* /*
* SCTP Implementer's Guide, 2.37 ICMP handling procedures * SCTP Implementer's Guide, 2.37 ICMP handling procedures
* *
...@@ -628,6 +640,10 @@ void sctp_v4_err(struct sk_buff *skb, __u32 info) ...@@ -628,6 +640,10 @@ void sctp_v4_err(struct sk_buff *skb, __u32 info)
err = EHOSTUNREACH; err = EHOSTUNREACH;
break; break;
case ICMP_REDIRECT:
sctp_icmp_redirect(sk, transport, skb);
err = 0;
break;
default: default:
goto out_unlock; goto out_unlock;
} }
......
...@@ -185,6 +185,9 @@ SCTP_STATIC void sctp_v6_err(struct sk_buff *skb, struct inet6_skb_parm *opt, ...@@ -185,6 +185,9 @@ SCTP_STATIC void sctp_v6_err(struct sk_buff *skb, struct inet6_skb_parm *opt,
goto out_unlock; goto out_unlock;
} }
break; break;
case NDISC_REDIRECT:
sctp_icmp_redirect(sk, transport, skb);
break;
default: default:
break; break;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment