diff --git a/include/net/ip6_tunnel.h b/include/net/ip6_tunnel.h index 236e40ba06bf..69b4bcf880c9 100644 --- a/include/net/ip6_tunnel.h +++ b/include/net/ip6_tunnel.h @@ -69,6 +69,8 @@ struct ip6_tnl_encap_ops { size_t (*encap_hlen)(struct ip_tunnel_encap *e); int (*build_header)(struct sk_buff *skb, struct ip_tunnel_encap *e, 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 diff --git a/include/net/ip_tunnels.h b/include/net/ip_tunnels.h index b0d022ff6ea1..db6b2218a2ad 100644 --- a/include/net/ip_tunnels.h +++ b/include/net/ip_tunnels.h @@ -311,6 +311,7 @@ struct ip_tunnel_encap_ops { size_t (*encap_hlen)(struct ip_tunnel_encap *e); int (*build_header)(struct sk_buff *skb, struct ip_tunnel_encap *e, u8 *protocol, struct flowi4 *fl4); + int (*err_handler)(struct sk_buff *skb, u32 info); }; #define MAX_IPTUN_ENCAP_OPS 8 diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c index a505ee5eb92c..6f8890c5bc7e 100644 --- a/net/ipv4/udp.c +++ b/net/ipv4/udp.c @@ -105,6 +105,7 @@ #include #include #include +#include #include #include #include @@ -590,6 +591,26 @@ void udp_encap_enable(void) } 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 * reversing source and destination port: this will match tunnels that force the * same destination port on both endpoints (e.g. VXLAN, GENEVE). Note that @@ -597,28 +618,25 @@ EXPORT_SYMBOL(udp_encap_enable); * different destination ports on endpoints, in this case we won't be able to * 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 * 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, const struct iphdr *iph, struct udphdr *uh, 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; - struct udp_sock *up; 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); transport_offset = skb_transport_offset(skb); @@ -628,10 +646,20 @@ static struct sock *__udp4_lib_err_encap(struct net *net, /* Transport header needs to point to the UDP header */ skb_set_transport_header(skb, iph->ihl << 2); - up = udp_sk(sk); - lookup = READ_ONCE(up->encap_err_lookup); - if (!lookup || lookup(sk, skb)) - sk = NULL; + sk = __udp4_lib_lookup(net, iph->daddr, uh->source, + iph->saddr, uh->dest, skb->dev->ifindex, 0, + udptable, 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_network_header(skb, network_offset); @@ -668,13 +696,19 @@ int __udp4_lib_err(struct sk_buff *skb, u32 info, struct udp_table *udptable) inet_sdif(skb), udptable, NULL); if (!sk) { /* No socket for error: try tunnels before discarding */ - if (static_branch_unlikely(&udp_encap_needed_key)) - sk = __udp4_lib_err_encap(net, iph, uh, udptable, skb); - - if (!sk) { - __ICMP_INC_STATS(net, ICMP_MIB_INERRORS); - return -ENOENT; + sk = ERR_PTR(-ENOENT); + if (static_branch_unlikely(&udp_encap_needed_key)) { + sk = __udp4_lib_err_encap(net, iph, uh, udptable, skb, + info); + if (!sk) + return 0; } + + if (IS_ERR(sk)) { + __ICMP_INC_STATS(net, ICMP_MIB_INERRORS); + return PTR_ERR(sk); + } + tunnel = true; } diff --git a/net/ipv6/udp.c b/net/ipv6/udp.c index 61316ec48b51..0c0cb1611aef 100644 --- a/net/ipv6/udp.c +++ b/net/ipv6/udp.c @@ -45,6 +45,7 @@ #include #include #include +#include #include #include #include @@ -469,6 +470,29 @@ void udpv6_encap_enable(void) } 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 * reversing source and destination port: this will match tunnels that force the * same destination port on both endpoints (e.g. VXLAN, GENEVE). Note that @@ -476,28 +500,27 @@ EXPORT_SYMBOL(udpv6_encap_enable); * different destination ports on endpoints, in this case we won't be able to * 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 * 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, const struct ipv6hdr *hdr, int offset, struct udphdr *uh, 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; - struct udp_sock *up; 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); transport_offset = skb_transport_offset(skb); @@ -507,13 +530,26 @@ static struct sock *__udp6_lib_err_encap(struct net *net, /* Transport header needs to point to the UDP header */ skb_set_transport_header(skb, offset); - up = udp_sk(sk); - lookup = READ_ONCE(up->encap_err_lookup); - if (!lookup || lookup(sk, skb)) - sk = NULL; + sk = __udp6_lib_lookup(net, &hdr->daddr, uh->source, + &hdr->saddr, uh->dest, + inet6_iif(skb), 0, udptable, skb); + 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_network_header(skb, network_offset); + return sk; } @@ -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); if (!sk) { /* No socket for error: try tunnels before discarding */ + sk = ERR_PTR(-ENOENT); if (static_branch_unlikely(&udpv6_encap_needed_key)) { 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_MIB_INERRORS); - return -ENOENT; + return PTR_ERR(sk); } + tunnel = true; }