blob: a28742c0e05c3ecd9bdfccfdecc55c2225f966c9 [file] [log] [blame]
/*
* sfe_ipv6_icmp.c
* Shortcut forwarding engine file for IPv6 ICMP
*
* Copyright (c) 2015-2016, 2019-2020, The Linux Foundation. All rights reserved.
* Copyright (c) 2021 Qualcomm Innovation Center, Inc. All rights reserved.
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <linux/skbuff.h>
#include <linux/ip.h>
#include <net/udp.h>
#include <net/tcp.h>
#include <net/icmp.h>
#include <linux/etherdevice.h>
#include <linux/version.h>
#include "sfe_debug.h"
#include "sfe_api.h"
#include "sfe.h"
#include "sfe_flow_cookie.h"
#include "sfe_ipv6.h"
/*
* sfe_ipv6_recv_icmp()
* Handle ICMP packet receives.
*
* ICMP packets aren't handled as a "fast path" and always have us process them
* through the default Linux stack. What we do need to do is look for any errors
* about connections we are handling in the fast path. If we find any such
* connections then we want to flush their state so that the ICMP error path
* within Linux has all of the correct state should it need it.
*/
int sfe_ipv6_recv_icmp(struct sfe_ipv6 *si, struct sk_buff *skb, struct net_device *dev,
unsigned int len, struct ipv6hdr *iph, unsigned int ihl)
{
struct icmp6hdr *icmph;
struct ipv6hdr *icmp_iph;
struct udphdr *icmp_udph;
struct tcphdr *icmp_tcph;
struct sfe_ipv6_addr *src_ip;
struct sfe_ipv6_addr *dest_ip;
__be16 src_port;
__be16 dest_port;
struct sfe_ipv6_connection_match *cm;
struct sfe_ipv6_connection *c;
u8 next_hdr;
bool ret;
/*
* Is our packet too short to contain a valid ICMP header?
*/
len -= ihl;
if (!pskb_may_pull(skb, ihl + sizeof(struct icmp6hdr))) {
sfe_ipv6_exception_stats_inc(si, SFE_IPV6_EXCEPTION_EVENT_ICMP_HEADER_INCOMPLETE);
DEBUG_TRACE("packet too short for ICMP header\n");
return 0;
}
/*
* We only handle "destination unreachable" and "time exceeded" messages.
*/
icmph = (struct icmp6hdr *)(skb->data + ihl);
if ((icmph->icmp6_type != ICMPV6_DEST_UNREACH)
&& (icmph->icmp6_type != ICMPV6_TIME_EXCEED)) {
sfe_ipv6_exception_stats_inc(si, SFE_IPV6_EXCEPTION_EVENT_ICMP_UNHANDLED_TYPE);
DEBUG_TRACE("unhandled ICMP type: 0x%x\n", icmph->icmp6_type);
return 0;
}
/*
* Do we have the full embedded IP header?
* We should have 8 bytes of next L4 header - that's enough to identify
* the connection.
*/
len -= sizeof(struct icmp6hdr);
ihl += sizeof(struct icmp6hdr);
if (!pskb_may_pull(skb, ihl + sizeof(struct ipv6hdr) + sizeof(struct sfe_ipv6_ext_hdr))) {
sfe_ipv6_exception_stats_inc(si, SFE_IPV6_EXCEPTION_EVENT_ICMP_IPV6_HEADER_INCOMPLETE);
DEBUG_TRACE("Embedded IP header not complete\n");
return 0;
}
/*
* Is our embedded IP version wrong?
*/
icmp_iph = (struct ipv6hdr *)(icmph + 1);
if (unlikely(icmp_iph->version != 6)) {
sfe_ipv6_exception_stats_inc(si, SFE_IPV6_EXCEPTION_EVENT_ICMP_IPV6_NON_V6);
DEBUG_TRACE("IP version: %u\n", icmp_iph->version);
return 0;
}
len -= sizeof(struct ipv6hdr);
ihl += sizeof(struct ipv6hdr);
next_hdr = icmp_iph->nexthdr;
while (unlikely(sfe_ipv6_is_ext_hdr(next_hdr))) {
struct sfe_ipv6_ext_hdr *ext_hdr;
unsigned int ext_hdr_len;
ext_hdr = (struct sfe_ipv6_ext_hdr *)(skb->data + ihl);
if (next_hdr == NEXTHDR_FRAGMENT) {
struct frag_hdr *frag_hdr = (struct frag_hdr *)ext_hdr;
unsigned int frag_off = ntohs(frag_hdr->frag_off);
if (frag_off & SFE_IPV6_FRAG_OFFSET) {
DEBUG_TRACE("non-initial fragment\n");
sfe_ipv6_exception_stats_inc(si, SFE_IPV6_EXCEPTION_EVENT_NON_INITIAL_FRAGMENT);
return 0;
}
}
ext_hdr_len = ext_hdr->hdr_len;
ext_hdr_len <<= 3;
ext_hdr_len += sizeof(struct sfe_ipv6_ext_hdr);
len -= ext_hdr_len;
ihl += ext_hdr_len;
/*
* We should have 8 bytes of next header - that's enough to identify
* the connection.
*/
if (!pskb_may_pull(skb, ihl + sizeof(struct sfe_ipv6_ext_hdr))) {
sfe_ipv6_exception_stats_inc(si, SFE_IPV6_EXCEPTION_EVENT_HEADER_INCOMPLETE);
DEBUG_TRACE("extension header %d not completed\n", next_hdr);
return 0;
}
next_hdr = ext_hdr->next_hdr;
}
/*
* Handle the embedded transport layer header.
*/
switch (next_hdr) {
case IPPROTO_UDP:
icmp_udph = (struct udphdr *)(skb->data + ihl);
src_port = icmp_udph->source;
dest_port = icmp_udph->dest;
break;
case IPPROTO_TCP:
icmp_tcph = (struct tcphdr *)(skb->data + ihl);
src_port = icmp_tcph->source;
dest_port = icmp_tcph->dest;
break;
default:
sfe_ipv6_exception_stats_inc(si, SFE_IPV6_EXCEPTION_EVENT_ICMP_IPV6_UNHANDLED_PROTOCOL);
DEBUG_TRACE("Unhandled embedded IP protocol: %u\n", next_hdr);
return 0;
}
src_ip = (struct sfe_ipv6_addr *)icmp_iph->saddr.s6_addr32;
dest_ip = (struct sfe_ipv6_addr *)icmp_iph->daddr.s6_addr32;
rcu_read_lock();
/*
* Look for a connection match. Note that we reverse the source and destination
* here because our embedded message contains a packet that was sent in the
* opposite direction to the one in which we just received it. It will have
* been sent on the interface from which we received it though so that's still
* ok to use.
*/
cm = sfe_ipv6_find_connection_match_rcu(si, dev, icmp_iph->nexthdr, dest_ip, dest_port, src_ip, src_port);
if (unlikely(!cm)) {
rcu_read_unlock();
sfe_ipv6_exception_stats_inc(si, SFE_IPV6_EXCEPTION_EVENT_ICMP_NO_CONNECTION);
DEBUG_TRACE("no connection found\n");
return 0;
}
/*
* We found a connection so now remove it from the connection list and flush
* its state.
*/
c = cm->connection;
spin_lock_bh(&si->lock);
ret = sfe_ipv6_remove_connection(si, c);
spin_unlock_bh(&si->lock);
if (ret) {
sfe_ipv6_flush_connection(si, c, SFE_SYNC_REASON_FLUSH);
}
rcu_read_unlock();
sfe_ipv6_exception_stats_inc(si, SFE_IPV6_EXCEPTION_EVENT_ICMP_FLUSHED_CONNECTION);
return 0;
}