blob: b12e36c4fbc03d5fb68d74c594112c3d4458b17f [file] [log] [blame]
Ed Warnickecb9cada2015-12-08 15:45:58 -07001/*
2 * ipsec_if_in.c : IPSec interface input node
3 *
4 * Copyright (c) 2015 Cisco and/or its affiliates.
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at:
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18#include <vnet/vnet.h>
19#include <vnet/api_errno.h>
20#include <vnet/ip/ip.h>
21
22#include <vnet/ipsec/ipsec.h>
23#include <vnet/ipsec/esp.h>
Neale Ranns918c1612019-02-21 23:34:59 -080024#include <vnet/ipsec/ipsec_io.h>
Ed Warnickecb9cada2015-12-08 15:45:58 -070025
26/* Statistics (not really errors) */
Matthew Smith831fd642018-05-15 22:03:05 -050027#define foreach_ipsec_if_input_error \
28_(RX, "good packets received") \
Neale Ranns8d7c5022019-02-06 01:41:05 -080029_(DISABLED, "ipsec packets received on disabled interface") \
30_(NO_TUNNEL, "no matching tunnel")
Ed Warnickecb9cada2015-12-08 15:45:58 -070031
Keith Burns (alagalah)166a9d42016-08-06 11:00:56 -070032static char *ipsec_if_input_error_strings[] = {
Ed Warnickecb9cada2015-12-08 15:45:58 -070033#define _(sym,string) string,
34 foreach_ipsec_if_input_error
35#undef _
36};
37
Keith Burns (alagalah)166a9d42016-08-06 11:00:56 -070038typedef enum
39{
Ed Warnickecb9cada2015-12-08 15:45:58 -070040#define _(sym,str) IPSEC_IF_INPUT_ERROR_##sym,
Keith Burns (alagalah)166a9d42016-08-06 11:00:56 -070041 foreach_ipsec_if_input_error
Ed Warnickecb9cada2015-12-08 15:45:58 -070042#undef _
43 IPSEC_IF_INPUT_N_ERROR,
44} ipsec_if_input_error_t;
45
Ed Warnickecb9cada2015-12-08 15:45:58 -070046
Keith Burns (alagalah)166a9d42016-08-06 11:00:56 -070047typedef struct
48{
Ed Warnickecb9cada2015-12-08 15:45:58 -070049 u32 spi;
50 u32 seq;
51} ipsec_if_input_trace_t;
52
Kingwel Xiec69ac312019-02-04 01:49:29 -080053static u8 *
Keith Burns (alagalah)166a9d42016-08-06 11:00:56 -070054format_ipsec_if_input_trace (u8 * s, va_list * args)
Ed Warnickecb9cada2015-12-08 15:45:58 -070055{
56 CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *);
57 CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *);
Keith Burns (alagalah)166a9d42016-08-06 11:00:56 -070058 ipsec_if_input_trace_t *t = va_arg (*args, ipsec_if_input_trace_t *);
Ed Warnickecb9cada2015-12-08 15:45:58 -070059
60 s = format (s, "IPSec: spi %u seq %u", t->spi, t->seq);
61 return s;
62}
63
Kingwel Xie00bff192019-03-07 01:25:32 -050064
65always_inline uword
66ipsec_if_input_inline (vlib_main_t * vm,
67 vlib_node_runtime_t * node, vlib_frame_t * from_frame)
Ed Warnickecb9cada2015-12-08 15:45:58 -070068{
69 ipsec_main_t *im = &ipsec_main;
Matthew Smith01034be2017-05-16 11:51:18 -050070 vnet_main_t *vnm = im->vnet_main;
71 vnet_interface_main_t *vim = &vnm->interface_main;
Kingwel Xie00bff192019-03-07 01:25:32 -050072
73 int is_trace = node->flags & VLIB_NODE_FLAG_TRACE;
Damjan Marion067cd622018-07-11 12:47:43 +020074 u32 thread_index = vm->thread_index;
Kingwel Xie00bff192019-03-07 01:25:32 -050075
76 u32 n_left_from, *from;
77 u16 nexts[VLIB_FRAME_SIZE], *next;
78 vlib_buffer_t *bufs[VLIB_FRAME_SIZE], **b;
79
80 from = vlib_frame_vector_args (from_frame);
81 n_left_from = from_frame->n_vectors;
82
83 vlib_get_buffers (vm, from, bufs, n_left_from);
84 b = bufs;
85 next = nexts;
86
87 clib_memset_u16 (nexts, im->esp4_decrypt_next_index, n_left_from);
88
Matthew Smith01034be2017-05-16 11:51:18 -050089 u64 n_bytes = 0, n_packets = 0;
Kingwel Xie00bff192019-03-07 01:25:32 -050090 u32 n_disabled = 0, n_no_tunnel = 0;
91
92 u32 last_sw_if_index = ~0;
93 u32 last_tunnel_id = ~0;
94 u64 last_key = ~0;
95
Matthew Smith831fd642018-05-15 22:03:05 -050096 vlib_combined_counter_main_t *rx_counter;
97 vlib_combined_counter_main_t *drop_counter;
Matthew Smith831fd642018-05-15 22:03:05 -050098
99 rx_counter = vim->combined_sw_if_counters + VNET_INTERFACE_COUNTER_RX;
100 drop_counter = vim->combined_sw_if_counters + VNET_INTERFACE_COUNTER_DROP;
Ed Warnickecb9cada2015-12-08 15:45:58 -0700101
Kingwel Xie00bff192019-03-07 01:25:32 -0500102 while (n_left_from >= 2)
Ed Warnickecb9cada2015-12-08 15:45:58 -0700103 {
Kingwel Xie00bff192019-03-07 01:25:32 -0500104 u32 sw_if_index0, sw_if_index1;
105 ip4_header_t *ip0, *ip1;
106 esp_header_t *esp0, *esp1;
107 u32 len0, len1;
108 u16 buf_adv0, buf_adv1;
109 u32 tid0, tid1;
110 ipsec_tunnel_if_t *t0, *t1;
111 u64 key0, key1;
Ed Warnickecb9cada2015-12-08 15:45:58 -0700112
Kingwel Xie00bff192019-03-07 01:25:32 -0500113 if (n_left_from >= 4)
Neale Ranns77eb28f2019-03-04 14:13:14 +0000114 {
Kingwel Xie00bff192019-03-07 01:25:32 -0500115 CLIB_PREFETCH (b[2], CLIB_CACHE_LINE_BYTES, STORE);
116 CLIB_PREFETCH (b[2]->data, CLIB_CACHE_LINE_BYTES, LOAD);
117 CLIB_PREFETCH (b[3], CLIB_CACHE_LINE_BYTES, STORE);
118 CLIB_PREFETCH (b[3]->data, CLIB_CACHE_LINE_BYTES, LOAD);
119 }
Neale Ranns77eb28f2019-03-04 14:13:14 +0000120
Kingwel Xie00bff192019-03-07 01:25:32 -0500121 ip0 = (ip4_header_t *) (b[0]->data + vnet_buffer (b[0])->l3_hdr_offset);
122 ip1 = (ip4_header_t *) (b[1]->data + vnet_buffer (b[1])->l3_hdr_offset);
Neale Ranns77eb28f2019-03-04 14:13:14 +0000123
Kingwel Xie00bff192019-03-07 01:25:32 -0500124 /* NAT UDP port 4500 case, don't advance any more */
125 if (ip0->protocol == IP_PROTOCOL_UDP)
126 {
127 esp0 =
128 (esp_header_t *) ((u8 *) ip0 + ip4_header_bytes (ip0) +
129 sizeof (udp_header_t));
130 buf_adv0 = 0;
131 }
132 else
133 {
134 esp0 = (esp_header_t *) ((u8 *) ip0 + ip4_header_bytes (ip0));
135 buf_adv0 = ip4_header_bytes (ip0);
136 }
137 if (ip1->protocol == IP_PROTOCOL_UDP)
138 {
139 esp1 =
140 (esp_header_t *) ((u8 *) ip1 + ip4_header_bytes (ip1) +
141 sizeof (udp_header_t));
142 buf_adv1 = 0;
143 }
144 else
145 {
146 esp1 = (esp_header_t *) ((u8 *) ip1 + ip4_header_bytes (ip1));
147 buf_adv1 = ip4_header_bytes (ip1);
148 }
Neale Ranns77eb28f2019-03-04 14:13:14 +0000149
Kingwel Xie00bff192019-03-07 01:25:32 -0500150 vlib_buffer_advance (b[0], buf_adv0);
151 vlib_buffer_advance (b[1], buf_adv1);
Neale Ranns77eb28f2019-03-04 14:13:14 +0000152
Kingwel Xie00bff192019-03-07 01:25:32 -0500153 len0 = vlib_buffer_length_in_chain (vm, b[0]);
154 len1 = vlib_buffer_length_in_chain (vm, b[1]);
Neale Ranns77eb28f2019-03-04 14:13:14 +0000155
Kingwel Xie00bff192019-03-07 01:25:32 -0500156 key0 = (u64) ip0->src_address.as_u32 << 32 | (u64) esp0->spi;
157 key1 = (u64) ip1->src_address.as_u32 << 32 | (u64) esp1->spi;
Neale Ranns77eb28f2019-03-04 14:13:14 +0000158
Kingwel Xie00bff192019-03-07 01:25:32 -0500159 if (key0 == last_key)
160 {
161 tid0 = last_tunnel_id;
162 }
163 else
164 {
165 uword *p = hash_get (im->ipsec_if_pool_index_by_key, key0);
166 if (p)
Neale Ranns77eb28f2019-03-04 14:13:14 +0000167 {
Kingwel Xie00bff192019-03-07 01:25:32 -0500168 tid0 = p[0];
169 last_tunnel_id = tid0;
170 last_key = key0;
Neale Ranns77eb28f2019-03-04 14:13:14 +0000171 }
172 else
173 {
Neale Ranns77eb28f2019-03-04 14:13:14 +0000174 n_no_tunnel++;
Kingwel Xie00bff192019-03-07 01:25:32 -0500175 next[0] = IPSEC_INPUT_NEXT_DROP;
176 goto pkt1;
177 }
178 }
179
180 t0 = pool_elt_at_index (im->tunnel_interfaces, tid0);
181 vnet_buffer (b[0])->ipsec.sad_index = t0->input_sa_index;
182
183 if (PREDICT_TRUE (t0->hw_if_index != ~0))
184 {
185 vnet_buffer (b[0])->ipsec.flags = 0;
186 sw_if_index0 = t0->sw_if_index;
187 vnet_buffer (b[0])->sw_if_index[VLIB_RX] = sw_if_index0;
188
189 if (PREDICT_FALSE (!(t0->flags & VNET_HW_INTERFACE_FLAG_LINK_UP)))
190 {
191 vlib_increment_combined_counter
192 (drop_counter, thread_index, sw_if_index0, 1, len0);
193 n_disabled++;
194 next[0] = IPSEC_INPUT_NEXT_DROP;
195 goto pkt1;
Neale Ranns77eb28f2019-03-04 14:13:14 +0000196 }
197
Kingwel Xie00bff192019-03-07 01:25:32 -0500198 if (PREDICT_TRUE (sw_if_index0 == last_sw_if_index))
Neale Ranns77eb28f2019-03-04 14:13:14 +0000199 {
Kingwel Xie00bff192019-03-07 01:25:32 -0500200 n_packets++;
201 n_bytes += len0;
Neale Ranns77eb28f2019-03-04 14:13:14 +0000202 }
203 else
204 {
Kingwel Xie00bff192019-03-07 01:25:32 -0500205 if (n_packets)
206 {
207 vlib_increment_combined_counter
208 (rx_counter, thread_index, last_sw_if_index,
209 n_packets, n_bytes);
210 }
211
212 last_sw_if_index = sw_if_index0;
213 n_packets = 1;
214 n_bytes = len0;
215 }
216 }
217 else
218 {
219 vnet_buffer (b[0])->ipsec.flags = IPSEC_FLAG_IPSEC_GRE_TUNNEL;
220 }
221
222 pkt1:
223 if (key1 == last_key)
224 {
225 tid1 = last_tunnel_id;
226 }
227 else
228 {
229 uword *p = hash_get (im->ipsec_if_pool_index_by_key, key1);
230 if (p)
231 {
232 tid1 = p[0];
233 last_tunnel_id = tid1;
234 last_key = key1;
235 }
236 else
237 {
Neale Ranns77eb28f2019-03-04 14:13:14 +0000238 n_no_tunnel++;
Kingwel Xie00bff192019-03-07 01:25:32 -0500239 next[1] = IPSEC_INPUT_NEXT_DROP;
240 goto trace1;
241 }
242 }
243
244 t1 = pool_elt_at_index (im->tunnel_interfaces, tid1);
245 vnet_buffer (b[1])->ipsec.sad_index = t1->input_sa_index;
246
247 if (PREDICT_TRUE (t1->hw_if_index != ~0))
248 {
249 vnet_buffer (b[1])->ipsec.flags = 0;
250 sw_if_index1 = t1->sw_if_index;
251 vnet_buffer (b[1])->sw_if_index[VLIB_RX] = sw_if_index1;
252
253 if (PREDICT_FALSE (!(t1->flags & VNET_HW_INTERFACE_FLAG_LINK_UP)))
254 {
255 vlib_increment_combined_counter
256 (drop_counter, thread_index, sw_if_index1, 1, len1);
257 n_disabled++;
258 next[1] = IPSEC_INPUT_NEXT_DROP;
259 goto trace1;
Neale Ranns77eb28f2019-03-04 14:13:14 +0000260 }
261
Kingwel Xie00bff192019-03-07 01:25:32 -0500262 if (PREDICT_TRUE (sw_if_index1 == last_sw_if_index))
263 {
264 n_packets++;
265 n_bytes += len1;
266 }
267 else
268 {
269 if (n_packets)
270 {
271 vlib_increment_combined_counter
272 (rx_counter, thread_index, last_sw_if_index,
273 n_packets, n_bytes);
274 }
275
276 last_sw_if_index = sw_if_index1;
277 n_packets = 1;
278 n_bytes = len1;
279 }
280 }
281 else
282 {
283 vnet_buffer (b[1])->ipsec.flags = IPSEC_FLAG_IPSEC_GRE_TUNNEL;
284 }
285
286 trace1:
287 if (is_trace)
288 {
289 if (PREDICT_FALSE (b[0]->flags & VLIB_BUFFER_IS_TRACED))
Neale Ranns77eb28f2019-03-04 14:13:14 +0000290 {
291 ipsec_if_input_trace_t *tr =
Kingwel Xie00bff192019-03-07 01:25:32 -0500292 vlib_add_trace (vm, node, b[0], sizeof (*tr));
Neale Ranns77eb28f2019-03-04 14:13:14 +0000293 tr->spi = clib_host_to_net_u32 (esp0->spi);
294 tr->seq = clib_host_to_net_u32 (esp0->seq);
295 }
Kingwel Xie00bff192019-03-07 01:25:32 -0500296 if (PREDICT_FALSE (b[1]->flags & VLIB_BUFFER_IS_TRACED))
Neale Ranns77eb28f2019-03-04 14:13:14 +0000297 {
298 ipsec_if_input_trace_t *tr =
Kingwel Xie00bff192019-03-07 01:25:32 -0500299 vlib_add_trace (vm, node, b[1], sizeof (*tr));
Neale Ranns77eb28f2019-03-04 14:13:14 +0000300 tr->spi = clib_host_to_net_u32 (esp1->spi);
301 tr->seq = clib_host_to_net_u32 (esp1->seq);
302 }
Neale Ranns77eb28f2019-03-04 14:13:14 +0000303 }
Kingwel Xie00bff192019-03-07 01:25:32 -0500304
305 /* next */
306 b += 2;
307 next += 2;
308 n_left_from -= 2;
309 }
310 while (n_left_from > 0)
311 {
312 u32 sw_if_index0;
313 ip4_header_t *ip0;
314 esp_header_t *esp0;
315 u32 len0;
316 u16 buf_adv0;
317 u32 tid0;
318 ipsec_tunnel_if_t *t0;
319 u64 key0;
320
321 ip0 = (ip4_header_t *) (b[0]->data + vnet_buffer (b[0])->l3_hdr_offset);
322
323 /* NAT UDP port 4500 case, don't advance any more */
324 if (ip0->protocol == IP_PROTOCOL_UDP)
Keith Burns (alagalah)166a9d42016-08-06 11:00:56 -0700325 {
Kingwel Xie00bff192019-03-07 01:25:32 -0500326 esp0 =
327 (esp_header_t *) ((u8 *) ip0 + ip4_header_bytes (ip0) +
328 sizeof (udp_header_t));
329 buf_adv0 = 0;
330 }
331 else
332 {
333 esp0 = (esp_header_t *) ((u8 *) ip0 + ip4_header_bytes (ip0));
334 buf_adv0 = ip4_header_bytes (ip0);
335 }
Ed Warnickecb9cada2015-12-08 15:45:58 -0700336
Kingwel Xie00bff192019-03-07 01:25:32 -0500337 /* stats for the tunnel include all the data after the IP header
338 just like a norml IP-IP tunnel */
339 vlib_buffer_advance (b[0], buf_adv0);
340 len0 = vlib_buffer_length_in_chain (vm, b[0]);
Ed Warnickecb9cada2015-12-08 15:45:58 -0700341
Kingwel Xie00bff192019-03-07 01:25:32 -0500342 key0 = (u64) ip0->src_address.as_u32 << 32 | (u64) esp0->spi;
343 if (key0 == last_key)
344 {
345 tid0 = last_tunnel_id;
346 }
347 else
348 {
349 uword *p = hash_get (im->ipsec_if_pool_index_by_key, key0);
350 if (p)
Keith Burns (alagalah)166a9d42016-08-06 11:00:56 -0700351 {
Kingwel Xie00bff192019-03-07 01:25:32 -0500352 tid0 = p[0];
353 last_tunnel_id = tid0;
354 last_key = key0;
Keith Burns (alagalah)166a9d42016-08-06 11:00:56 -0700355 }
Neale Ranns8d7c5022019-02-06 01:41:05 -0800356 else
357 {
Neale Ranns8d7c5022019-02-06 01:41:05 -0800358 n_no_tunnel++;
Kingwel Xie00bff192019-03-07 01:25:32 -0500359 next[0] = IPSEC_INPUT_NEXT_DROP;
360 goto trace00;
361 }
362 }
363
364 t0 = pool_elt_at_index (im->tunnel_interfaces, tid0);
365 vnet_buffer (b[0])->ipsec.sad_index = t0->input_sa_index;
366
367 if (PREDICT_TRUE (t0->hw_if_index != ~0))
368 {
369 vnet_buffer (b[0])->ipsec.flags = 0;
370 sw_if_index0 = t0->sw_if_index;
371 vnet_buffer (b[0])->sw_if_index[VLIB_RX] = sw_if_index0;
372
373 if (PREDICT_FALSE (!(t0->flags & VNET_HW_INTERFACE_FLAG_LINK_UP)))
374 {
375 vlib_increment_combined_counter
376 (drop_counter, thread_index, sw_if_index0, 1, len0);
377 n_disabled++;
378 next[0] = IPSEC_INPUT_NEXT_DROP;
379 goto trace00;
Neale Ranns8d7c5022019-02-06 01:41:05 -0800380 }
Ed Warnickecb9cada2015-12-08 15:45:58 -0700381
Kingwel Xie00bff192019-03-07 01:25:32 -0500382 if (PREDICT_TRUE (sw_if_index0 == last_sw_if_index))
383 {
384 n_packets++;
385 n_bytes += len0;
386 }
387 else
388 {
389 if (n_packets)
390 {
391 vlib_increment_combined_counter
392 (rx_counter, thread_index, last_sw_if_index,
393 n_packets, n_bytes);
394 }
395
396 last_sw_if_index = sw_if_index0;
397 n_packets = 1;
398 n_bytes = len0;
399 }
400 }
401 else
402 {
403 vnet_buffer (b[0])->ipsec.flags = IPSEC_FLAG_IPSEC_GRE_TUNNEL;
404 }
405
406 trace00:
407 if (is_trace)
408 {
409 if (PREDICT_FALSE (b[0]->flags & VLIB_BUFFER_IS_TRACED))
Keith Burns (alagalah)166a9d42016-08-06 11:00:56 -0700410 {
411 ipsec_if_input_trace_t *tr =
Kingwel Xie00bff192019-03-07 01:25:32 -0500412 vlib_add_trace (vm, node, b[0], sizeof (*tr));
Keith Burns (alagalah)166a9d42016-08-06 11:00:56 -0700413 tr->spi = clib_host_to_net_u32 (esp0->spi);
414 tr->seq = clib_host_to_net_u32 (esp0->seq);
415 }
Keith Burns (alagalah)166a9d42016-08-06 11:00:56 -0700416 }
Kingwel Xie00bff192019-03-07 01:25:32 -0500417
418 /* next */
419 b += 1;
420 next += 1;
421 n_left_from -= 1;
Ed Warnickecb9cada2015-12-08 15:45:58 -0700422 }
423
Kingwel Xie00bff192019-03-07 01:25:32 -0500424 if (n_packets)
Matthew Smith01034be2017-05-16 11:51:18 -0500425 {
Matthew Smith831fd642018-05-15 22:03:05 -0500426 vlib_increment_combined_counter (rx_counter,
Matthew Smith01034be2017-05-16 11:51:18 -0500427 thread_index,
428 last_sw_if_index, n_packets, n_bytes);
429 }
430
Ed Warnickecb9cada2015-12-08 15:45:58 -0700431 vlib_node_increment_counter (vm, ipsec_if_input_node.index,
Keith Burns (alagalah)166a9d42016-08-06 11:00:56 -0700432 IPSEC_IF_INPUT_ERROR_RX,
Matthew Smith831fd642018-05-15 22:03:05 -0500433 from_frame->n_vectors - n_disabled);
Matthew Smith831fd642018-05-15 22:03:05 -0500434 vlib_node_increment_counter (vm, ipsec_if_input_node.index,
435 IPSEC_IF_INPUT_ERROR_DISABLED, n_disabled);
Neale Ranns8d7c5022019-02-06 01:41:05 -0800436 vlib_node_increment_counter (vm, ipsec_if_input_node.index,
Kingwel Xie00bff192019-03-07 01:25:32 -0500437 IPSEC_IF_INPUT_ERROR_NO_TUNNEL, n_no_tunnel);
438
439 vlib_buffer_enqueue_to_next (vm, node, from, nexts, from_frame->n_vectors);
Ed Warnickecb9cada2015-12-08 15:45:58 -0700440
441 return from_frame->n_vectors;
442}
443
Kingwel Xie00bff192019-03-07 01:25:32 -0500444VLIB_NODE_FN (ipsec_if_input_node) (vlib_main_t * vm,
445 vlib_node_runtime_t * node,
446 vlib_frame_t * from_frame)
447{
448 return ipsec_if_input_inline (vm, node, from_frame);
449}
450
Keith Burns (alagalah)166a9d42016-08-06 11:00:56 -0700451/* *INDENT-OFF* */
Ed Warnickecb9cada2015-12-08 15:45:58 -0700452VLIB_REGISTER_NODE (ipsec_if_input_node) = {
Ed Warnickecb9cada2015-12-08 15:45:58 -0700453 .name = "ipsec-if-input",
454 .vector_size = sizeof (u32),
455 .format_trace = format_ipsec_if_input_trace,
456 .type = VLIB_NODE_TYPE_INTERNAL,
Ed Warnickecb9cada2015-12-08 15:45:58 -0700457 .n_errors = ARRAY_LEN(ipsec_if_input_error_strings),
458 .error_strings = ipsec_if_input_error_strings,
Pierre Pfister057b3562018-12-10 17:01:01 +0100459 .sibling_of = "ipsec4-input-feature",
Damjan Marion1c80e832016-05-11 23:07:18 +0200460};
Keith Burns (alagalah)166a9d42016-08-06 11:00:56 -0700461/* *INDENT-ON* */
Damjan Marion1c80e832016-05-11 23:07:18 +0200462
Keith Burns (alagalah)166a9d42016-08-06 11:00:56 -0700463/*
464 * fd.io coding-style-patch-verification: ON
465 *
466 * Local Variables:
467 * eval: (c-set-style "gnu")
468 * End:
469 */