Commit b6c66c43 authored by David Howells's avatar David Howells
Browse files

rxrpc: Use the core ICMP/ICMP6 parsers



Make rxrpc_encap_rcv_err() pass the ICMP/ICMP6 skbuff to ip_icmp_error() or
ipv6_icmp_error() as appropriate to do the parsing rather than trying to do
it in rxrpc.

This pushes an error report onto the UDP socket's error queue and calls
->sk_error_report() from which point rxrpc can pick it up.

It would be preferable to steal the packet directly from ip*_icmp_error()
rather than letting it get queued, but this is probably good enough.

Also note that __udp4_lib_err() calls sk_error_report() twice in some
cases.

Signed-off-by: default avatarDavid Howells <dhowells@redhat.com>
cc: Marc Dionne <marc.dionne@auristor.com>
cc: linux-afs@lists.infradead.org
parent 42fb06b3
Loading
Loading
Loading
Loading
+0 −1
Original line number Diff line number Diff line
@@ -998,7 +998,6 @@ void rxrpc_send_keepalive(struct rxrpc_peer *);
/*
 * peer_event.c
 */
void rxrpc_encap_err_rcv(struct sock *, struct sk_buff *, int, __be16, u32, u8 *);
void rxrpc_error_report(struct sock *);
void rxrpc_peer_keepalive_worker(struct work_struct *);

+13 −0
Original line number Diff line number Diff line
@@ -23,6 +23,19 @@
static void rxrpc_local_processor(struct work_struct *);
static void rxrpc_local_rcu(struct rcu_head *);

/*
 * Handle an ICMP/ICMP6 error turning up at the tunnel.  Push it through the
 * usual mechanism so that it gets parsed and presented through the UDP
 * socket's error_report().
 */
static void rxrpc_encap_err_rcv(struct sock *sk, struct sk_buff *skb, int err,
				__be16 port, u32 info, u8 *payload)
{
	if (ip_hdr(skb)->version == IPVERSION)
		return ip_icmp_error(sk, skb, err, port, info, payload);
	return ipv6_icmp_error(sk, skb, err, port, info, payload);
}

/*
 * Compare a local to an address.  Return -ve, 0 or +ve to indicate less than,
 * same or greater than.
+29 −216
Original line number Diff line number Diff line
@@ -16,220 +16,12 @@
#include <net/sock.h>
#include <net/af_rxrpc.h>
#include <net/ip.h>
#include <net/icmp.h>
#include "ar-internal.h"

static void rxrpc_adjust_mtu(struct rxrpc_peer *, unsigned int);
static void rxrpc_store_error(struct rxrpc_peer *, struct sock_exterr_skb *);
static void rxrpc_distribute_error(struct rxrpc_peer *, int,
				   enum rxrpc_call_completion);

/*
 * Find the peer associated with an ICMPv4 packet.
 */
static struct rxrpc_peer *rxrpc_lookup_peer_icmp_rcu(struct rxrpc_local *local,
						     struct sk_buff *skb,
						     __be16 udp_port,
						     struct sockaddr_rxrpc *srx)
{
	struct iphdr *ip, *ip0 = ip_hdr(skb);
	struct icmphdr *icmp = icmp_hdr(skb);

	_enter("%u,%u,%u", ip0->protocol, icmp->type, icmp->code);

	switch (icmp->type) {
	case ICMP_DEST_UNREACH:
	case ICMP_TIME_EXCEEDED:
	case ICMP_PARAMETERPROB:
		ip = (struct iphdr *)((void *)icmp + 8);
		break;
	default:
		return NULL;
	}

	memset(srx, 0, sizeof(*srx));
	srx->transport_type = local->srx.transport_type;
	srx->transport_len = local->srx.transport_len;
	srx->transport.family = local->srx.transport.family;

	/* Can we see an ICMP4 packet on an ICMP6 listening socket?  and vice
	 * versa?
	 */
	switch (srx->transport.family) {
	case AF_INET:
		srx->transport_len = sizeof(srx->transport.sin);
		srx->transport.family = AF_INET;
		srx->transport.sin.sin_port = udp_port;
		memcpy(&srx->transport.sin.sin_addr, &ip->daddr,
		       sizeof(struct in_addr));
		break;

#ifdef CONFIG_AF_RXRPC_IPV6
	case AF_INET6:
		srx->transport_len = sizeof(srx->transport.sin);
		srx->transport.family = AF_INET;
		srx->transport.sin.sin_port = udp_port;
		memcpy(&srx->transport.sin.sin_addr, &ip->daddr,
		       sizeof(struct in_addr));
		break;
#endif

	default:
		WARN_ON_ONCE(1);
		return NULL;
	}

	_net("ICMP {%pISp}", &srx->transport);
	return rxrpc_lookup_peer_rcu(local, srx);
}

#ifdef CONFIG_AF_RXRPC_IPV6
/*
 * Find the peer associated with an ICMPv6 packet.
 */
static struct rxrpc_peer *rxrpc_lookup_peer_icmp6_rcu(struct rxrpc_local *local,
						      struct sk_buff *skb,
						      __be16 udp_port,
						      struct sockaddr_rxrpc *srx)
{
	struct icmp6hdr *icmp = icmp6_hdr(skb);
	struct ipv6hdr *ip, *ip0 = ipv6_hdr(skb);

	_enter("%u,%u,%u", ip0->nexthdr, icmp->icmp6_type, icmp->icmp6_code);

	switch (icmp->icmp6_type) {
	case ICMPV6_DEST_UNREACH:
	case ICMPV6_PKT_TOOBIG:
	case ICMPV6_TIME_EXCEED:
	case ICMPV6_PARAMPROB:
		ip = (struct ipv6hdr *)((void *)icmp + 8);
		break;
	default:
		return NULL;
	}

	memset(srx, 0, sizeof(*srx));
	srx->transport_type = local->srx.transport_type;
	srx->transport_len = local->srx.transport_len;
	srx->transport.family = local->srx.transport.family;

	/* Can we see an ICMP4 packet on an ICMP6 listening socket?  and vice
	 * versa?
	 */
	switch (srx->transport.family) {
	case AF_INET:
		_net("Rx ICMP6 on v4 sock");
		srx->transport_len = sizeof(srx->transport.sin);
		srx->transport.family = AF_INET;
		srx->transport.sin.sin_port = udp_port;
		memcpy(&srx->transport.sin.sin_addr,
		       &ip->daddr.s6_addr32[3], sizeof(struct in_addr));
		break;
	case AF_INET6:
		_net("Rx ICMP6");
		srx->transport.sin.sin_port = udp_port;
		memcpy(&srx->transport.sin6.sin6_addr, &ip->daddr,
		       sizeof(struct in6_addr));
		break;
	default:
		WARN_ON_ONCE(1);
		return NULL;
	}

	_net("ICMP {%pISp}", &srx->transport);
	return rxrpc_lookup_peer_rcu(local, srx);
}
#endif /* CONFIG_AF_RXRPC_IPV6 */

/*
 * Handle an error received on the local endpoint as a tunnel.
 */
void rxrpc_encap_err_rcv(struct sock *sk, struct sk_buff *skb, int err,
			 __be16 port, u32 info, u8 *payload)
{
	struct sock_extended_err ee;
	struct sockaddr_rxrpc srx;
	struct rxrpc_local *local;
	struct rxrpc_peer *peer;
	u8 version = ip_hdr(skb)->version;
	u8 type = icmp_hdr(skb)->type;
	u8 code = icmp_hdr(skb)->code;

	rcu_read_lock();
	local = rcu_dereference_sk_user_data(sk);
	if (unlikely(!local)) {
		rcu_read_unlock();
		return;
	}

	rxrpc_new_skb(skb, rxrpc_skb_received);

	switch (ip_hdr(skb)->version) {
	case IPVERSION:
		peer = rxrpc_lookup_peer_icmp_rcu(local, skb, port, &srx);
		break;
#ifdef CONFIG_AF_RXRPC_IPV6
	case 6:
		peer = rxrpc_lookup_peer_icmp6_rcu(local, skb, port, &srx);
		break;
#endif
	default:
		rcu_read_unlock();
		return;
	}

	if (peer && !rxrpc_get_peer_maybe(peer))
		peer = NULL;
	if (!peer) {
		rcu_read_unlock();
		return;
	}

	memset(&ee, 0, sizeof(ee));

	switch (version) {
	case IPVERSION:
		if (type == ICMP_DEST_UNREACH &&
		    code == ICMP_FRAG_NEEDED) {
			rxrpc_adjust_mtu(peer, info);
			rcu_read_unlock();
			rxrpc_put_peer(peer);
			return;
		}

		ee.ee_origin = SO_EE_ORIGIN_ICMP;
		ee.ee_type = type;
		ee.ee_code = code;
		ee.ee_errno = err;
		break;

#ifdef CONFIG_AF_RXRPC_IPV6
	case 6:
		if (type == ICMPV6_PKT_TOOBIG) {
			rxrpc_adjust_mtu(peer, info);
			rcu_read_unlock();
			rxrpc_put_peer(peer);
			return;
		}

		if (err == EACCES)
			err = EHOSTUNREACH;

		ee.ee_origin = SO_EE_ORIGIN_ICMP6;
		ee.ee_type = type;
		ee.ee_code = code;
		ee.ee_errno = err;
		break;
#endif
	}

	trace_rxrpc_rx_icmp(peer, &ee, &srx);

	rxrpc_distribute_error(peer, err, RXRPC_CALL_NETWORK_ERROR);
	rcu_read_unlock();
	rxrpc_put_peer(peer);
}

/*
 * Find the peer associated with a local error.
 */
@@ -246,6 +38,9 @@ static struct rxrpc_peer *rxrpc_lookup_peer_local_rcu(struct rxrpc_local *local,
	srx->transport_len = local->srx.transport_len;
	srx->transport.family = local->srx.transport.family;

	/* Can we see an ICMP4 packet on an ICMP6 listening socket?  and vice
	 * versa?
	 */
	switch (srx->transport.family) {
	case AF_INET:
		srx->transport_len = sizeof(srx->transport.sin);
@@ -375,20 +170,38 @@ void rxrpc_error_report(struct sock *sk)
	}
	rxrpc_new_skb(skb, rxrpc_skb_received);
	serr = SKB_EXT_ERR(skb);
	if (!skb->len && serr->ee.ee_origin == SO_EE_ORIGIN_TIMESTAMPING) {
		_leave("UDP empty message");
		rcu_read_unlock();
		rxrpc_free_skb(skb, rxrpc_skb_freed);
		return;
	}

	if (serr->ee.ee_origin == SO_EE_ORIGIN_LOCAL) {
	peer = rxrpc_lookup_peer_local_rcu(local, skb, &srx);
	if (peer && !rxrpc_get_peer_maybe(peer))
		peer = NULL;
		if (peer) {
			trace_rxrpc_rx_icmp(peer, &serr->ee, &srx);
			rxrpc_store_error(peer, serr);
	if (!peer) {
		rcu_read_unlock();
		rxrpc_free_skb(skb, rxrpc_skb_freed);
		_leave(" [no peer]");
		return;
	}

	trace_rxrpc_rx_icmp(peer, &serr->ee, &srx);

	if ((serr->ee.ee_origin == SO_EE_ORIGIN_ICMP &&
	     serr->ee.ee_type == ICMP_DEST_UNREACH &&
	     serr->ee.ee_code == ICMP_FRAG_NEEDED)) {
		rxrpc_adjust_mtu(peer, serr->ee.ee_info);
		goto out;
	}

	rxrpc_store_error(peer, serr);
out:
	rcu_read_unlock();
	rxrpc_free_skb(skb, rxrpc_skb_freed);
	rxrpc_put_peer(peer);

	_leave("");
}