blob: a28742c0e05c3ecd9bdfccfdecc55c2225f966c9 [file] [log] [blame]
Ratheesh Kannoth6307bec2021-11-25 08:26:39 +05301/*
2 * sfe_ipv6_icmp.c
3 * Shortcut forwarding engine file for IPv6 ICMP
4 *
5 * Copyright (c) 2015-2016, 2019-2020, The Linux Foundation. All rights reserved.
6 * Copyright (c) 2021 Qualcomm Innovation Center, Inc. All rights reserved.
7 *
8 * Permission to use, copy, modify, and/or distribute this software for any
9 * purpose with or without fee is hereby granted, provided that the above
10 * copyright notice and this permission notice appear in all copies.
11 *
12 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
13 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
14 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
15 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
16 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
17 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
18 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19 */
20
21#include <linux/skbuff.h>
22#include <linux/ip.h>
23#include <net/udp.h>
24#include <net/tcp.h>
25#include <net/icmp.h>
26#include <linux/etherdevice.h>
27#include <linux/version.h>
28
29#include "sfe_debug.h"
30#include "sfe_api.h"
31#include "sfe.h"
32#include "sfe_flow_cookie.h"
33#include "sfe_ipv6.h"
34
35/*
Ratheesh Kannoth6307bec2021-11-25 08:26:39 +053036 * sfe_ipv6_recv_icmp()
37 * Handle ICMP packet receives.
38 *
39 * ICMP packets aren't handled as a "fast path" and always have us process them
40 * through the default Linux stack. What we do need to do is look for any errors
41 * about connections we are handling in the fast path. If we find any such
42 * connections then we want to flush their state so that the ICMP error path
43 * within Linux has all of the correct state should it need it.
44 */
45int sfe_ipv6_recv_icmp(struct sfe_ipv6 *si, struct sk_buff *skb, struct net_device *dev,
46 unsigned int len, struct ipv6hdr *iph, unsigned int ihl)
47{
48 struct icmp6hdr *icmph;
49 struct ipv6hdr *icmp_iph;
50 struct udphdr *icmp_udph;
51 struct tcphdr *icmp_tcph;
52 struct sfe_ipv6_addr *src_ip;
53 struct sfe_ipv6_addr *dest_ip;
54 __be16 src_port;
55 __be16 dest_port;
56 struct sfe_ipv6_connection_match *cm;
57 struct sfe_ipv6_connection *c;
58 u8 next_hdr;
59 bool ret;
60
61 /*
62 * Is our packet too short to contain a valid ICMP header?
63 */
64 len -= ihl;
65 if (!pskb_may_pull(skb, ihl + sizeof(struct icmp6hdr))) {
66 sfe_ipv6_exception_stats_inc(si, SFE_IPV6_EXCEPTION_EVENT_ICMP_HEADER_INCOMPLETE);
67
68 DEBUG_TRACE("packet too short for ICMP header\n");
69 return 0;
70 }
71
72 /*
73 * We only handle "destination unreachable" and "time exceeded" messages.
74 */
75 icmph = (struct icmp6hdr *)(skb->data + ihl);
76 if ((icmph->icmp6_type != ICMPV6_DEST_UNREACH)
77 && (icmph->icmp6_type != ICMPV6_TIME_EXCEED)) {
78
79 sfe_ipv6_exception_stats_inc(si, SFE_IPV6_EXCEPTION_EVENT_ICMP_UNHANDLED_TYPE);
80 DEBUG_TRACE("unhandled ICMP type: 0x%x\n", icmph->icmp6_type);
81 return 0;
82 }
83
84 /*
85 * Do we have the full embedded IP header?
86 * We should have 8 bytes of next L4 header - that's enough to identify
87 * the connection.
88 */
89 len -= sizeof(struct icmp6hdr);
90 ihl += sizeof(struct icmp6hdr);
91 if (!pskb_may_pull(skb, ihl + sizeof(struct ipv6hdr) + sizeof(struct sfe_ipv6_ext_hdr))) {
92
93 sfe_ipv6_exception_stats_inc(si, SFE_IPV6_EXCEPTION_EVENT_ICMP_IPV6_HEADER_INCOMPLETE);
94 DEBUG_TRACE("Embedded IP header not complete\n");
95 return 0;
96 }
97
98 /*
99 * Is our embedded IP version wrong?
100 */
101 icmp_iph = (struct ipv6hdr *)(icmph + 1);
102 if (unlikely(icmp_iph->version != 6)) {
103
104 sfe_ipv6_exception_stats_inc(si, SFE_IPV6_EXCEPTION_EVENT_ICMP_IPV6_NON_V6);
105 DEBUG_TRACE("IP version: %u\n", icmp_iph->version);
106 return 0;
107 }
108
109 len -= sizeof(struct ipv6hdr);
110 ihl += sizeof(struct ipv6hdr);
111 next_hdr = icmp_iph->nexthdr;
112 while (unlikely(sfe_ipv6_is_ext_hdr(next_hdr))) {
113 struct sfe_ipv6_ext_hdr *ext_hdr;
114 unsigned int ext_hdr_len;
115
116 ext_hdr = (struct sfe_ipv6_ext_hdr *)(skb->data + ihl);
117 if (next_hdr == NEXTHDR_FRAGMENT) {
118 struct frag_hdr *frag_hdr = (struct frag_hdr *)ext_hdr;
119 unsigned int frag_off = ntohs(frag_hdr->frag_off);
120
121 if (frag_off & SFE_IPV6_FRAG_OFFSET) {
122
123 DEBUG_TRACE("non-initial fragment\n");
124 sfe_ipv6_exception_stats_inc(si, SFE_IPV6_EXCEPTION_EVENT_NON_INITIAL_FRAGMENT);
125 return 0;
126 }
127 }
128
129 ext_hdr_len = ext_hdr->hdr_len;
130 ext_hdr_len <<= 3;
131 ext_hdr_len += sizeof(struct sfe_ipv6_ext_hdr);
132 len -= ext_hdr_len;
133 ihl += ext_hdr_len;
134 /*
135 * We should have 8 bytes of next header - that's enough to identify
136 * the connection.
137 */
138 if (!pskb_may_pull(skb, ihl + sizeof(struct sfe_ipv6_ext_hdr))) {
139
140 sfe_ipv6_exception_stats_inc(si, SFE_IPV6_EXCEPTION_EVENT_HEADER_INCOMPLETE);
141 DEBUG_TRACE("extension header %d not completed\n", next_hdr);
142 return 0;
143 }
144
145 next_hdr = ext_hdr->next_hdr;
146 }
147
148 /*
149 * Handle the embedded transport layer header.
150 */
151 switch (next_hdr) {
152 case IPPROTO_UDP:
153 icmp_udph = (struct udphdr *)(skb->data + ihl);
154 src_port = icmp_udph->source;
155 dest_port = icmp_udph->dest;
156 break;
157
158 case IPPROTO_TCP:
159 icmp_tcph = (struct tcphdr *)(skb->data + ihl);
160 src_port = icmp_tcph->source;
161 dest_port = icmp_tcph->dest;
162 break;
163
164 default:
165
166 sfe_ipv6_exception_stats_inc(si, SFE_IPV6_EXCEPTION_EVENT_ICMP_IPV6_UNHANDLED_PROTOCOL);
167 DEBUG_TRACE("Unhandled embedded IP protocol: %u\n", next_hdr);
168 return 0;
169 }
170
171 src_ip = (struct sfe_ipv6_addr *)icmp_iph->saddr.s6_addr32;
172 dest_ip = (struct sfe_ipv6_addr *)icmp_iph->daddr.s6_addr32;
173
174 rcu_read_lock();
175 /*
176 * Look for a connection match. Note that we reverse the source and destination
177 * here because our embedded message contains a packet that was sent in the
178 * opposite direction to the one in which we just received it. It will have
179 * been sent on the interface from which we received it though so that's still
180 * ok to use.
181 */
182 cm = sfe_ipv6_find_connection_match_rcu(si, dev, icmp_iph->nexthdr, dest_ip, dest_port, src_ip, src_port);
183 if (unlikely(!cm)) {
184 rcu_read_unlock();
185 sfe_ipv6_exception_stats_inc(si, SFE_IPV6_EXCEPTION_EVENT_ICMP_NO_CONNECTION);
186 DEBUG_TRACE("no connection found\n");
187 return 0;
188 }
189
190 /*
191 * We found a connection so now remove it from the connection list and flush
192 * its state.
193 */
194 c = cm->connection;
195 spin_lock_bh(&si->lock);
196 ret = sfe_ipv6_remove_connection(si, c);
197 spin_unlock_bh(&si->lock);
198
199 if (ret) {
200 sfe_ipv6_flush_connection(si, c, SFE_SYNC_REASON_FLUSH);
201 }
202
203 rcu_read_unlock();
204
205 sfe_ipv6_exception_stats_inc(si, SFE_IPV6_EXCEPTION_EVENT_ICMP_FLUSHED_CONNECTION);
206 return 0;
207}