Commit e7cc0824 authored by Stefano Brivio's avatar Stefano Brivio Committed by David S. Miller

udp: Support for error handlers of tunnels with arbitrary destination port

ICMP error handling is currently not possible for UDP tunnels not
employing a receiving socket with local destination port matching the
remote one, because we have no way to look them up.

Add an err_handler tunnel encapsulation operation that can be exported by
tunnels in order to pass the error to the protocol implementing the
encapsulation. We can't easily use a lookup function as we did for VXLAN
and GENEVE, as protocol error handlers, which would be in turn called by
implementations of this new operation, handle the errors themselves,
together with the tunnel lookup.

Without a socket, we can't be sure which encapsulation error handler is
the appropriate one: encapsulation handlers (the ones for FoU and GUE
introduced in the next patch, e.g.) will need to check the new error codes
returned by protocol handlers to figure out if errors match the given
encapsulation, and, in turn, report this error back, so that we can try
all of them in __udp{4,6}_lib_err_encap_no_sk() until we have a match.

v2:
- Name all arguments in err_handler prototypes (David Miller)
Signed-off-by: default avatarStefano Brivio <sbrivio@redhat.com>
Reviewed-by: default avatarSabrina Dubroca <sd@queasysnail.net>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 32bbd879
...@@ -69,6 +69,8 @@ struct ip6_tnl_encap_ops { ...@@ -69,6 +69,8 @@ struct ip6_tnl_encap_ops {
size_t (*encap_hlen)(struct ip_tunnel_encap *e); size_t (*encap_hlen)(struct ip_tunnel_encap *e);
int (*build_header)(struct sk_buff *skb, struct ip_tunnel_encap *e, int (*build_header)(struct sk_buff *skb, struct ip_tunnel_encap *e,
u8 *protocol, struct flowi6 *fl6); u8 *protocol, struct flowi6 *fl6);
int (*err_handler)(struct sk_buff *skb, struct inet6_skb_parm *opt,
u8 type, u8 code, int offset, __be32 info);
}; };
#ifdef CONFIG_INET #ifdef CONFIG_INET
......
...@@ -311,6 +311,7 @@ struct ip_tunnel_encap_ops { ...@@ -311,6 +311,7 @@ struct ip_tunnel_encap_ops {
size_t (*encap_hlen)(struct ip_tunnel_encap *e); size_t (*encap_hlen)(struct ip_tunnel_encap *e);
int (*build_header)(struct sk_buff *skb, struct ip_tunnel_encap *e, int (*build_header)(struct sk_buff *skb, struct ip_tunnel_encap *e,
u8 *protocol, struct flowi4 *fl4); u8 *protocol, struct flowi4 *fl4);
int (*err_handler)(struct sk_buff *skb, u32 info);
}; };
#define MAX_IPTUN_ENCAP_OPS 8 #define MAX_IPTUN_ENCAP_OPS 8
......
...@@ -105,6 +105,7 @@ ...@@ -105,6 +105,7 @@
#include <net/net_namespace.h> #include <net/net_namespace.h>
#include <net/icmp.h> #include <net/icmp.h>
#include <net/inet_hashtables.h> #include <net/inet_hashtables.h>
#include <net/ip_tunnels.h>
#include <net/route.h> #include <net/route.h>
#include <net/checksum.h> #include <net/checksum.h>
#include <net/xfrm.h> #include <net/xfrm.h>
...@@ -590,6 +591,26 @@ void udp_encap_enable(void) ...@@ -590,6 +591,26 @@ void udp_encap_enable(void)
} }
EXPORT_SYMBOL(udp_encap_enable); EXPORT_SYMBOL(udp_encap_enable);
/* Handler for tunnels with arbitrary destination ports: no socket lookup, go
* through error handlers in encapsulations looking for a match.
*/
static int __udp4_lib_err_encap_no_sk(struct sk_buff *skb, u32 info)
{
int i;
for (i = 0; i < MAX_IPTUN_ENCAP_OPS; i++) {
int (*handler)(struct sk_buff *skb, u32 info);
if (!iptun_encaps[i])
continue;
handler = rcu_dereference(iptun_encaps[i]->err_handler);
if (handler && !handler(skb, info))
return 0;
}
return -ENOENT;
}
/* Try to match ICMP errors to UDP tunnels by looking up a socket without /* Try to match ICMP errors to UDP tunnels by looking up a socket without
* reversing source and destination port: this will match tunnels that force the * reversing source and destination port: this will match tunnels that force the
* same destination port on both endpoints (e.g. VXLAN, GENEVE). Note that * same destination port on both endpoints (e.g. VXLAN, GENEVE). Note that
...@@ -597,28 +618,25 @@ EXPORT_SYMBOL(udp_encap_enable); ...@@ -597,28 +618,25 @@ EXPORT_SYMBOL(udp_encap_enable);
* different destination ports on endpoints, in this case we won't be able to * different destination ports on endpoints, in this case we won't be able to
* trace ICMP messages back to them. * trace ICMP messages back to them.
* *
* If this doesn't match any socket, probe tunnels with arbitrary destination
* ports (e.g. FoU, GUE): there, the receiving socket is useless, as the port
* we've sent packets to won't necessarily match the local destination port.
*
* Then ask the tunnel implementation to match the error against a valid * Then ask the tunnel implementation to match the error against a valid
* association. * association.
* *
* Return the socket if we have a match. * Return an error if we can't find a match, the socket if we need further
* processing, zero otherwise.
*/ */
static struct sock *__udp4_lib_err_encap(struct net *net, static struct sock *__udp4_lib_err_encap(struct net *net,
const struct iphdr *iph, const struct iphdr *iph,
struct udphdr *uh, struct udphdr *uh,
struct udp_table *udptable, struct udp_table *udptable,
struct sk_buff *skb) struct sk_buff *skb, u32 info)
{ {
int (*lookup)(struct sock *sk, struct sk_buff *skb);
int network_offset, transport_offset; int network_offset, transport_offset;
struct udp_sock *up;
struct sock *sk; struct sock *sk;
sk = __udp4_lib_lookup(net, iph->daddr, uh->source,
iph->saddr, uh->dest, skb->dev->ifindex, 0,
udptable, NULL);
if (!sk)
return NULL;
network_offset = skb_network_offset(skb); network_offset = skb_network_offset(skb);
transport_offset = skb_transport_offset(skb); transport_offset = skb_transport_offset(skb);
...@@ -628,10 +646,20 @@ static struct sock *__udp4_lib_err_encap(struct net *net, ...@@ -628,10 +646,20 @@ static struct sock *__udp4_lib_err_encap(struct net *net,
/* Transport header needs to point to the UDP header */ /* Transport header needs to point to the UDP header */
skb_set_transport_header(skb, iph->ihl << 2); skb_set_transport_header(skb, iph->ihl << 2);
up = udp_sk(sk); sk = __udp4_lib_lookup(net, iph->daddr, uh->source,
lookup = READ_ONCE(up->encap_err_lookup); iph->saddr, uh->dest, skb->dev->ifindex, 0,
if (!lookup || lookup(sk, skb)) udptable, NULL);
sk = NULL; if (sk) {
int (*lookup)(struct sock *sk, struct sk_buff *skb);
struct udp_sock *up = udp_sk(sk);
lookup = READ_ONCE(up->encap_err_lookup);
if (!lookup || lookup(sk, skb))
sk = NULL;
}
if (!sk)
sk = ERR_PTR(__udp4_lib_err_encap_no_sk(skb, info));
skb_set_transport_header(skb, transport_offset); skb_set_transport_header(skb, transport_offset);
skb_set_network_header(skb, network_offset); skb_set_network_header(skb, network_offset);
...@@ -668,13 +696,19 @@ int __udp4_lib_err(struct sk_buff *skb, u32 info, struct udp_table *udptable) ...@@ -668,13 +696,19 @@ int __udp4_lib_err(struct sk_buff *skb, u32 info, struct udp_table *udptable)
inet_sdif(skb), udptable, NULL); inet_sdif(skb), udptable, NULL);
if (!sk) { if (!sk) {
/* No socket for error: try tunnels before discarding */ /* No socket for error: try tunnels before discarding */
if (static_branch_unlikely(&udp_encap_needed_key)) sk = ERR_PTR(-ENOENT);
sk = __udp4_lib_err_encap(net, iph, uh, udptable, skb); if (static_branch_unlikely(&udp_encap_needed_key)) {
sk = __udp4_lib_err_encap(net, iph, uh, udptable, skb,
info);
if (!sk)
return 0;
}
if (!sk) { if (IS_ERR(sk)) {
__ICMP_INC_STATS(net, ICMP_MIB_INERRORS); __ICMP_INC_STATS(net, ICMP_MIB_INERRORS);
return -ENOENT; return PTR_ERR(sk);
} }
tunnel = true; tunnel = true;
} }
......
...@@ -45,6 +45,7 @@ ...@@ -45,6 +45,7 @@
#include <net/raw.h> #include <net/raw.h>
#include <net/tcp_states.h> #include <net/tcp_states.h>
#include <net/ip6_checksum.h> #include <net/ip6_checksum.h>
#include <net/ip6_tunnel.h>
#include <net/xfrm.h> #include <net/xfrm.h>
#include <net/inet_hashtables.h> #include <net/inet_hashtables.h>
#include <net/inet6_hashtables.h> #include <net/inet6_hashtables.h>
...@@ -469,6 +470,29 @@ void udpv6_encap_enable(void) ...@@ -469,6 +470,29 @@ void udpv6_encap_enable(void)
} }
EXPORT_SYMBOL(udpv6_encap_enable); EXPORT_SYMBOL(udpv6_encap_enable);
/* Handler for tunnels with arbitrary destination ports: no socket lookup, go
* through error handlers in encapsulations looking for a match.
*/
static int __udp6_lib_err_encap_no_sk(struct sk_buff *skb,
struct inet6_skb_parm *opt,
u8 type, u8 code, int offset, u32 info)
{
int i;
for (i = 0; i < MAX_IPTUN_ENCAP_OPS; i++) {
int (*handler)(struct sk_buff *skb, struct inet6_skb_parm *opt,
u8 type, u8 code, int offset, u32 info);
if (!ip6tun_encaps[i])
continue;
handler = rcu_dereference(ip6tun_encaps[i]->err_handler);
if (handler && !handler(skb, opt, type, code, offset, info))
return 0;
}
return -ENOENT;
}
/* Try to match ICMP errors to UDP tunnels by looking up a socket without /* Try to match ICMP errors to UDP tunnels by looking up a socket without
* reversing source and destination port: this will match tunnels that force the * reversing source and destination port: this will match tunnels that force the
* same destination port on both endpoints (e.g. VXLAN, GENEVE). Note that * same destination port on both endpoints (e.g. VXLAN, GENEVE). Note that
...@@ -476,28 +500,27 @@ EXPORT_SYMBOL(udpv6_encap_enable); ...@@ -476,28 +500,27 @@ EXPORT_SYMBOL(udpv6_encap_enable);
* different destination ports on endpoints, in this case we won't be able to * different destination ports on endpoints, in this case we won't be able to
* trace ICMP messages back to them. * trace ICMP messages back to them.
* *
* If this doesn't match any socket, probe tunnels with arbitrary destination
* ports (e.g. FoU, GUE): there, the receiving socket is useless, as the port
* we've sent packets to won't necessarily match the local destination port.
*
* Then ask the tunnel implementation to match the error against a valid * Then ask the tunnel implementation to match the error against a valid
* association. * association.
* *
* Return the socket if we have a match. * Return an error if we can't find a match, the socket if we need further
* processing, zero otherwise.
*/ */
static struct sock *__udp6_lib_err_encap(struct net *net, static struct sock *__udp6_lib_err_encap(struct net *net,
const struct ipv6hdr *hdr, int offset, const struct ipv6hdr *hdr, int offset,
struct udphdr *uh, struct udphdr *uh,
struct udp_table *udptable, struct udp_table *udptable,
struct sk_buff *skb) struct sk_buff *skb,
struct inet6_skb_parm *opt,
u8 type, u8 code, __be32 info)
{ {
int (*lookup)(struct sock *sk, struct sk_buff *skb);
int network_offset, transport_offset; int network_offset, transport_offset;
struct udp_sock *up;
struct sock *sk; struct sock *sk;
sk = __udp6_lib_lookup(net, &hdr->daddr, uh->source,
&hdr->saddr, uh->dest,
inet6_iif(skb), 0, udptable, skb);
if (!sk)
return NULL;
network_offset = skb_network_offset(skb); network_offset = skb_network_offset(skb);
transport_offset = skb_transport_offset(skb); transport_offset = skb_transport_offset(skb);
...@@ -507,13 +530,26 @@ static struct sock *__udp6_lib_err_encap(struct net *net, ...@@ -507,13 +530,26 @@ static struct sock *__udp6_lib_err_encap(struct net *net,
/* Transport header needs to point to the UDP header */ /* Transport header needs to point to the UDP header */
skb_set_transport_header(skb, offset); skb_set_transport_header(skb, offset);
up = udp_sk(sk); sk = __udp6_lib_lookup(net, &hdr->daddr, uh->source,
lookup = READ_ONCE(up->encap_err_lookup); &hdr->saddr, uh->dest,
if (!lookup || lookup(sk, skb)) inet6_iif(skb), 0, udptable, skb);
sk = NULL; if (sk) {
int (*lookup)(struct sock *sk, struct sk_buff *skb);
struct udp_sock *up = udp_sk(sk);
lookup = READ_ONCE(up->encap_err_lookup);
if (!lookup || lookup(sk, skb))
sk = NULL;
}
if (!sk) {
sk = ERR_PTR(__udp6_lib_err_encap_no_sk(skb, opt, type, code,
offset, info));
}
skb_set_transport_header(skb, transport_offset); skb_set_transport_header(skb, transport_offset);
skb_set_network_header(skb, network_offset); skb_set_network_header(skb, network_offset);
return sk; return sk;
} }
...@@ -536,16 +572,21 @@ int __udp6_lib_err(struct sk_buff *skb, struct inet6_skb_parm *opt, ...@@ -536,16 +572,21 @@ int __udp6_lib_err(struct sk_buff *skb, struct inet6_skb_parm *opt,
inet6_iif(skb), inet6_sdif(skb), udptable, skb); inet6_iif(skb), inet6_sdif(skb), udptable, skb);
if (!sk) { if (!sk) {
/* No socket for error: try tunnels before discarding */ /* No socket for error: try tunnels before discarding */
sk = ERR_PTR(-ENOENT);
if (static_branch_unlikely(&udpv6_encap_needed_key)) { if (static_branch_unlikely(&udpv6_encap_needed_key)) {
sk = __udp6_lib_err_encap(net, hdr, offset, uh, sk = __udp6_lib_err_encap(net, hdr, offset, uh,
udptable, skb); udptable, skb,
opt, type, code, info);
if (!sk)
return 0;
} }
if (!sk) { if (IS_ERR(sk)) {
__ICMP6_INC_STATS(net, __in6_dev_get(skb->dev), __ICMP6_INC_STATS(net, __in6_dev_get(skb->dev),
ICMP6_MIB_INERRORS); ICMP6_MIB_INERRORS);
return -ENOENT; return PTR_ERR(sk);
} }
tunnel = true; tunnel = true;
} }
......
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