blob: a7d31528fc8cce66a525d037297558fc03a303f6 [file] [log] [blame]
Ratheesh Kannoth6307bec2021-11-25 08:26:39 +05301/*
2 * sfe_ipv4_icmp.c
3 * Shortcut forwarding engine - IPv4 ICMP implementation
4 *
5 * Copyright (c) 2013-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/lockdep.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_ipv4.h"
34
35/*
36 * sfe_ipv4_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_ipv4_recv_icmp(struct sfe_ipv4 *si, struct sk_buff *skb, struct net_device *dev,
46 unsigned int len, struct iphdr *iph, unsigned int ihl)
47{
48 struct icmphdr *icmph;
49 struct iphdr *icmp_iph;
50 unsigned int icmp_ihl_words;
51 unsigned int icmp_ihl;
52 u32 *icmp_trans_h;
53 struct udphdr *icmp_udph;
54 struct tcphdr *icmp_tcph;
55 __be32 src_ip;
56 __be32 dest_ip;
57 __be16 src_port;
58 __be16 dest_port;
59 struct sfe_ipv4_connection_match *cm;
60 struct sfe_ipv4_connection *c;
61 u32 pull_len = sizeof(struct icmphdr) + ihl;
62 bool ret;
63
64 /*
65 * Is our packet too short to contain a valid ICMP header?
66 */
67 len -= ihl;
68 if (!pskb_may_pull(skb, pull_len)) {
69 sfe_ipv4_exception_stats_inc(si, SFE_IPV4_EXCEPTION_EVENT_ICMP_HEADER_INCOMPLETE);
70
71 DEBUG_TRACE("packet too short for ICMP header\n");
72 return 0;
73 }
74
75 /*
76 * We only handle "destination unreachable" and "time exceeded" messages.
77 */
78 icmph = (struct icmphdr *)(skb->data + ihl);
79 if ((icmph->type != ICMP_DEST_UNREACH)
80 && (icmph->type != ICMP_TIME_EXCEEDED)) {
81
82 sfe_ipv4_exception_stats_inc(si, SFE_IPV4_EXCEPTION_EVENT_ICMP_UNHANDLED_TYPE);
83 DEBUG_TRACE("unhandled ICMP type: 0x%x\n", icmph->type);
84 return 0;
85 }
86
87 /*
88 * Do we have the full embedded IP header?
89 */
90 len -= sizeof(struct icmphdr);
91 pull_len += sizeof(struct iphdr);
92 if (!pskb_may_pull(skb, pull_len)) {
93
94 sfe_ipv4_exception_stats_inc(si, SFE_IPV4_EXCEPTION_EVENT_ICMP_IPV4_HEADER_INCOMPLETE);
95 DEBUG_TRACE("Embedded IP header not complete\n");
96 return 0;
97 }
98
99 /*
100 * Is our embedded IP version wrong?
101 */
102 icmp_iph = (struct iphdr *)(icmph + 1);
103 if (unlikely(icmp_iph->version != 4)) {
104
105 sfe_ipv4_exception_stats_inc(si, SFE_IPV4_EXCEPTION_EVENT_ICMP_IPV4_NON_V4);
106 DEBUG_TRACE("IP version: %u\n", icmp_iph->version);
107 return 0;
108 }
109
110 /*
111 * Do we have the full embedded IP header, including any options?
112 */
113 icmp_ihl_words = icmp_iph->ihl;
114 icmp_ihl = icmp_ihl_words << 2;
115 pull_len += icmp_ihl - sizeof(struct iphdr);
116 if (!pskb_may_pull(skb, pull_len)) {
117
118 sfe_ipv4_exception_stats_inc(si, SFE_IPV4_EXCEPTION_EVENT_ICMP_IPV4_IP_OPTIONS_INCOMPLETE);
119 DEBUG_TRACE("Embedded header not large enough for IP options\n");
120 return 0;
121 }
122
123 len -= icmp_ihl;
124 icmp_trans_h = ((u32 *)icmp_iph) + icmp_ihl_words;
125
126 /*
127 * Handle the embedded transport layer header.
128 */
129 switch (icmp_iph->protocol) {
130 case IPPROTO_UDP:
131 /*
132 * We should have 8 bytes of UDP header - that's enough to identify
133 * the connection.
134 */
135 pull_len += 8;
136 if (!pskb_may_pull(skb, pull_len)) {
137 sfe_ipv4_exception_stats_inc(si, SFE_IPV4_EXCEPTION_EVENT_ICMP_IPV4_UDP_HEADER_INCOMPLETE);
138 DEBUG_TRACE("Incomplete embedded UDP header\n");
139 return 0;
140 }
141
142 icmp_udph = (struct udphdr *)icmp_trans_h;
143 src_port = icmp_udph->source;
144 dest_port = icmp_udph->dest;
145 break;
146
147 case IPPROTO_TCP:
148 /*
149 * We should have 8 bytes of TCP header - that's enough to identify
150 * the connection.
151 */
152 pull_len += 8;
153 if (!pskb_may_pull(skb, pull_len)) {
154 sfe_ipv4_exception_stats_inc(si, SFE_IPV4_EXCEPTION_EVENT_ICMP_IPV4_TCP_HEADER_INCOMPLETE);
155 DEBUG_TRACE("Incomplete embedded TCP header\n");
156 return 0;
157 }
158
159 icmp_tcph = (struct tcphdr *)icmp_trans_h;
160 src_port = icmp_tcph->source;
161 dest_port = icmp_tcph->dest;
162 break;
163
164 default:
165 sfe_ipv4_exception_stats_inc(si, SFE_IPV4_EXCEPTION_EVENT_ICMP_IPV4_UNHANDLED_PROTOCOL);
166 DEBUG_TRACE("Unhandled embedded IP protocol: %u\n", icmp_iph->protocol);
167 return 0;
168 }
169
170 src_ip = icmp_iph->saddr;
171 dest_ip = icmp_iph->daddr;
172
173 rcu_read_lock();
174
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_ipv4_find_connection_match_rcu(si, dev, icmp_iph->protocol, dest_ip, dest_port, src_ip, src_port);
183 if (unlikely(!cm)) {
184
185 rcu_read_unlock();
186 sfe_ipv4_exception_stats_inc(si, SFE_IPV4_EXCEPTION_EVENT_ICMP_NO_CONNECTION);
187 DEBUG_TRACE("no connection found\n");
188 return 0;
189 }
190
191 /*
192 * We found a connection so now remove it from the connection list and flush
193 * its state.
194 */
195 c = cm->connection;
196 spin_lock_bh(&si->lock);
197 ret = sfe_ipv4_remove_connection(si, c);
198 spin_unlock_bh(&si->lock);
199
200 if (ret) {
201 sfe_ipv4_flush_connection(si, c, SFE_SYNC_REASON_FLUSH);
202 }
203 rcu_read_unlock();
204 sfe_ipv4_exception_stats_inc(si, SFE_IPV4_EXCEPTION_EVENT_ICMP_FLUSHED_CONNECTION);
205 return 0;
206}