xref: /dpdk/examples/ipsec-secgw/sp6.c (revision 2a7bb4fdf61e9edfb7adbaecb50e728b82da9e23)
1 /* SPDX-License-Identifier: BSD-3-Clause
2  * Copyright(c) 2016 Intel Corporation
3  */
4 
5 /*
6  * Security Policies
7  */
8 #include <sys/types.h>
9 #include <netinet/in.h>
10 #include <netinet/ip6.h>
11 
12 #include <rte_acl.h>
13 #include <rte_ip.h>
14 
15 #include "ipsec.h"
16 #include "parser.h"
17 
18 #define MAX_ACL_RULE_NUM	1024
19 
20 enum {
21 	IP6_PROTO,
22 	IP6_SRC0,
23 	IP6_SRC1,
24 	IP6_SRC2,
25 	IP6_SRC3,
26 	IP6_DST0,
27 	IP6_DST1,
28 	IP6_DST2,
29 	IP6_DST3,
30 	IP6_SRCP,
31 	IP6_DSTP,
32 	IP6_NUM
33 };
34 
35 #define IP6_ADDR_SIZE 16
36 
37 static struct rte_acl_field_def ip6_defs[IP6_NUM] = {
38 	{
39 	.type = RTE_ACL_FIELD_TYPE_BITMASK,
40 	.size = sizeof(uint8_t),
41 	.field_index = IP6_PROTO,
42 	.input_index = IP6_PROTO,
43 	.offset = 0,
44 	},
45 	{
46 	.type = RTE_ACL_FIELD_TYPE_MASK,
47 	.size = 4,
48 	.field_index = IP6_SRC0,
49 	.input_index = IP6_SRC0,
50 	.offset = 2
51 	},
52 	{
53 	.type = RTE_ACL_FIELD_TYPE_MASK,
54 	.size = 4,
55 	.field_index = IP6_SRC1,
56 	.input_index = IP6_SRC1,
57 	.offset = 6
58 	},
59 	{
60 	.type = RTE_ACL_FIELD_TYPE_MASK,
61 	.size = 4,
62 	.field_index = IP6_SRC2,
63 	.input_index = IP6_SRC2,
64 	.offset = 10
65 	},
66 	{
67 	.type = RTE_ACL_FIELD_TYPE_MASK,
68 	.size = 4,
69 	.field_index = IP6_SRC3,
70 	.input_index = IP6_SRC3,
71 	.offset = 14
72 	},
73 	{
74 	.type = RTE_ACL_FIELD_TYPE_MASK,
75 	.size = 4,
76 	.field_index = IP6_DST0,
77 	.input_index = IP6_DST0,
78 	.offset = 18
79 	},
80 	{
81 	.type = RTE_ACL_FIELD_TYPE_MASK,
82 	.size = 4,
83 	.field_index = IP6_DST1,
84 	.input_index = IP6_DST1,
85 	.offset = 22
86 	},
87 	{
88 	.type = RTE_ACL_FIELD_TYPE_MASK,
89 	.size = 4,
90 	.field_index = IP6_DST2,
91 	.input_index = IP6_DST2,
92 	.offset = 26
93 	},
94 	{
95 	.type = RTE_ACL_FIELD_TYPE_MASK,
96 	.size = 4,
97 	.field_index = IP6_DST3,
98 	.input_index = IP6_DST3,
99 	.offset = 30
100 	},
101 	{
102 	.type = RTE_ACL_FIELD_TYPE_RANGE,
103 	.size = sizeof(uint16_t),
104 	.field_index = IP6_SRCP,
105 	.input_index = IP6_SRCP,
106 	.offset = 34
107 	},
108 	{
109 	.type = RTE_ACL_FIELD_TYPE_RANGE,
110 	.size = sizeof(uint16_t),
111 	.field_index = IP6_DSTP,
112 	.input_index = IP6_SRCP,
113 	.offset = 36
114 	}
115 };
116 
117 RTE_ACL_RULE_DEF(acl6_rules, RTE_DIM(ip6_defs));
118 
119 static struct acl6_rules acl6_rules_out[MAX_ACL_RULE_NUM];
120 static uint32_t nb_acl6_rules_out;
121 
122 static struct acl6_rules acl6_rules_in[MAX_ACL_RULE_NUM];
123 static uint32_t nb_acl6_rules_in;
124 
125 void
126 parse_sp6_tokens(char **tokens, uint32_t n_tokens,
127 	struct parse_status *status)
128 {
129 	struct acl6_rules *rule_ipv6 = NULL;
130 
131 	uint32_t *ri = NULL; /* rule index */
132 	uint32_t ti = 0; /* token index */
133 
134 	uint32_t esp_p = 0;
135 	uint32_t protect_p = 0;
136 	uint32_t bypass_p = 0;
137 	uint32_t discard_p = 0;
138 	uint32_t pri_p = 0;
139 	uint32_t src_p = 0;
140 	uint32_t dst_p = 0;
141 	uint32_t proto_p = 0;
142 	uint32_t sport_p = 0;
143 	uint32_t dport_p = 0;
144 
145 	if (strcmp(tokens[1], "in") == 0) {
146 		ri = &nb_acl6_rules_in;
147 
148 		APP_CHECK(*ri <= MAX_ACL_RULE_NUM - 1, status, "too "
149 			"many sp rules, abort insertion\n");
150 		if (status->status < 0)
151 			return;
152 
153 		rule_ipv6 = &acl6_rules_in[*ri];
154 
155 	} else if (strcmp(tokens[1], "out") == 0) {
156 		ri = &nb_acl6_rules_out;
157 
158 		APP_CHECK(*ri <= MAX_ACL_RULE_NUM - 1, status, "too "
159 			"many sp rules, abort insertion\n");
160 		if (status->status < 0)
161 			return;
162 
163 		rule_ipv6 = &acl6_rules_out[*ri];
164 
165 	} else {
166 		APP_CHECK(0, status, "unrecognized input \"%s\", expect"
167 			" \"in\" or \"out\"\n", tokens[ti]);
168 		return;
169 	}
170 
171 	rule_ipv6->data.category_mask = 1;
172 
173 
174 	for (ti = 2; ti < n_tokens; ti++) {
175 		if (strcmp(tokens[ti], "esp") == 0) {
176 			/* currently do nothing */
177 			APP_CHECK_PRESENCE(esp_p, tokens[ti], status);
178 			if (status->status < 0)
179 				return;
180 			esp_p = 1;
181 			continue;
182 		}
183 
184 		if (strcmp(tokens[ti], "protect") == 0) {
185 			APP_CHECK_PRESENCE(protect_p, tokens[ti], status);
186 			if (status->status < 0)
187 				return;
188 			APP_CHECK(bypass_p == 0, status, "conflict item "
189 				"between \"%s\" and \"%s\"", tokens[ti],
190 				"bypass");
191 			if (status->status < 0)
192 				return;
193 			APP_CHECK(discard_p == 0, status, "conflict item "
194 				"between \"%s\" and \"%s\"", tokens[ti],
195 				"discard");
196 			if (status->status < 0)
197 				return;
198 			INCREMENT_TOKEN_INDEX(ti, n_tokens, status);
199 			if (status->status < 0)
200 				return;
201 			APP_CHECK_TOKEN_IS_NUM(tokens, ti, status);
202 			if (status->status < 0)
203 				return;
204 
205 			rule_ipv6->data.userdata =
206 				PROTECT(atoi(tokens[ti]));
207 
208 			protect_p = 1;
209 			continue;
210 		}
211 
212 		if (strcmp(tokens[ti], "bypass") == 0) {
213 			APP_CHECK_PRESENCE(bypass_p, tokens[ti], status);
214 			if (status->status < 0)
215 				return;
216 			APP_CHECK(protect_p == 0, status, "conflict item "
217 				"between \"%s\" and \"%s\"", tokens[ti],
218 				"protect");
219 			if (status->status < 0)
220 				return;
221 			APP_CHECK(discard_p == 0, status, "conflict item "
222 				"between \"%s\" and \"%s\"", tokens[ti],
223 				"discard");
224 			if (status->status < 0)
225 				return;
226 
227 			rule_ipv6->data.userdata = BYPASS;
228 
229 			bypass_p = 1;
230 			continue;
231 		}
232 
233 		if (strcmp(tokens[ti], "discard") == 0) {
234 			APP_CHECK_PRESENCE(discard_p, tokens[ti], status);
235 			if (status->status < 0)
236 				return;
237 			APP_CHECK(protect_p == 0, status, "conflict item "
238 				"between \"%s\" and \"%s\"", tokens[ti],
239 				"protect");
240 			if (status->status < 0)
241 				return;
242 			APP_CHECK(bypass_p == 0, status, "conflict item "
243 				"between \"%s\" and \"%s\"", tokens[ti],
244 				"discard");
245 			if (status->status < 0)
246 				return;
247 
248 			rule_ipv6->data.userdata = DISCARD;
249 
250 			discard_p = 1;
251 			continue;
252 		}
253 
254 		if (strcmp(tokens[ti], "pri") == 0) {
255 			APP_CHECK_PRESENCE(pri_p, tokens[ti], status);
256 			if (status->status < 0)
257 				return;
258 			INCREMENT_TOKEN_INDEX(ti, n_tokens, status);
259 			if (status->status < 0)
260 				return;
261 			APP_CHECK_TOKEN_IS_NUM(tokens, ti, status);
262 			if (status->status < 0)
263 				return;
264 
265 			rule_ipv6->data.priority = atoi(tokens[ti]);
266 
267 			pri_p = 1;
268 			continue;
269 		}
270 
271 		if (strcmp(tokens[ti], "src") == 0) {
272 			struct in6_addr ip;
273 			uint32_t depth;
274 
275 			APP_CHECK_PRESENCE(src_p, tokens[ti], status);
276 			if (status->status < 0)
277 				return;
278 			INCREMENT_TOKEN_INDEX(ti, n_tokens, status);
279 			if (status->status < 0)
280 				return;
281 
282 			APP_CHECK(parse_ipv6_addr(tokens[ti], &ip,
283 				&depth) == 0, status, "unrecognized "
284 				"input \"%s\", expect valid ipv6 "
285 				"addr", tokens[ti]);
286 			if (status->status < 0)
287 				return;
288 
289 			rule_ipv6->field[1].value.u32 =
290 				(uint32_t)ip.s6_addr[0] << 24 |
291 				(uint32_t)ip.s6_addr[1] << 16 |
292 				(uint32_t)ip.s6_addr[2] << 8 |
293 				(uint32_t)ip.s6_addr[3];
294 			rule_ipv6->field[1].mask_range.u32 =
295 				(depth > 32) ? 32 : depth;
296 			depth = (depth > 32) ? (depth - 32) : 0;
297 			rule_ipv6->field[2].value.u32 =
298 				(uint32_t)ip.s6_addr[4] << 24 |
299 				(uint32_t)ip.s6_addr[5] << 16 |
300 				(uint32_t)ip.s6_addr[6] << 8 |
301 				(uint32_t)ip.s6_addr[7];
302 			rule_ipv6->field[2].mask_range.u32 =
303 				(depth > 32) ? 32 : depth;
304 			depth = (depth > 32) ? (depth - 32) : 0;
305 			rule_ipv6->field[3].value.u32 =
306 				(uint32_t)ip.s6_addr[8] << 24 |
307 				(uint32_t)ip.s6_addr[9] << 16 |
308 				(uint32_t)ip.s6_addr[10] << 8 |
309 				(uint32_t)ip.s6_addr[11];
310 			rule_ipv6->field[3].mask_range.u32 =
311 				(depth > 32) ? 32 : depth;
312 			depth = (depth > 32) ? (depth - 32) : 0;
313 			rule_ipv6->field[4].value.u32 =
314 				(uint32_t)ip.s6_addr[12] << 24 |
315 				(uint32_t)ip.s6_addr[13] << 16 |
316 				(uint32_t)ip.s6_addr[14] << 8 |
317 				(uint32_t)ip.s6_addr[15];
318 			rule_ipv6->field[4].mask_range.u32 =
319 				(depth > 32) ? 32 : depth;
320 
321 			src_p = 1;
322 			continue;
323 		}
324 
325 		if (strcmp(tokens[ti], "dst") == 0) {
326 			struct in6_addr ip;
327 			uint32_t depth;
328 
329 			APP_CHECK_PRESENCE(dst_p, tokens[ti], status);
330 			if (status->status < 0)
331 				return;
332 			INCREMENT_TOKEN_INDEX(ti, n_tokens, status);
333 			if (status->status < 0)
334 				return;
335 
336 			APP_CHECK(parse_ipv6_addr(tokens[ti], &ip,
337 				&depth) == 0, status, "unrecognized "
338 				"input \"%s\", expect valid ipv6 "
339 				"addr", tokens[ti]);
340 			if (status->status < 0)
341 				return;
342 
343 			rule_ipv6->field[5].value.u32 =
344 				(uint32_t)ip.s6_addr[0] << 24 |
345 				(uint32_t)ip.s6_addr[1] << 16 |
346 				(uint32_t)ip.s6_addr[2] << 8 |
347 				(uint32_t)ip.s6_addr[3];
348 			rule_ipv6->field[5].mask_range.u32 =
349 				(depth > 32) ? 32 : depth;
350 			depth = (depth > 32) ? (depth - 32) : 0;
351 			rule_ipv6->field[6].value.u32 =
352 				(uint32_t)ip.s6_addr[4] << 24 |
353 				(uint32_t)ip.s6_addr[5] << 16 |
354 				(uint32_t)ip.s6_addr[6] << 8 |
355 				(uint32_t)ip.s6_addr[7];
356 			rule_ipv6->field[6].mask_range.u32 =
357 				(depth > 32) ? 32 : depth;
358 			depth = (depth > 32) ? (depth - 32) : 0;
359 			rule_ipv6->field[7].value.u32 =
360 				(uint32_t)ip.s6_addr[8] << 24 |
361 				(uint32_t)ip.s6_addr[9] << 16 |
362 				(uint32_t)ip.s6_addr[10] << 8 |
363 				(uint32_t)ip.s6_addr[11];
364 			rule_ipv6->field[7].mask_range.u32 =
365 				(depth > 32) ? 32 : depth;
366 			depth = (depth > 32) ? (depth - 32) : 0;
367 			rule_ipv6->field[8].value.u32 =
368 				(uint32_t)ip.s6_addr[12] << 24 |
369 				(uint32_t)ip.s6_addr[13] << 16 |
370 				(uint32_t)ip.s6_addr[14] << 8 |
371 				(uint32_t)ip.s6_addr[15];
372 			rule_ipv6->field[8].mask_range.u32 =
373 				(depth > 32) ? 32 : depth;
374 
375 			dst_p = 1;
376 			continue;
377 		}
378 
379 		if (strcmp(tokens[ti], "proto") == 0) {
380 			uint16_t low, high;
381 
382 			APP_CHECK_PRESENCE(proto_p, tokens[ti], status);
383 			if (status->status < 0)
384 				return;
385 			INCREMENT_TOKEN_INDEX(ti, n_tokens, status);
386 			if (status->status < 0)
387 				return;
388 
389 			APP_CHECK(parse_range(tokens[ti], &low, &high)
390 				== 0, status, "unrecognized input \"%s\""
391 				", expect \"from:to\"", tokens[ti]);
392 			if (status->status < 0)
393 				return;
394 			APP_CHECK(low <= 0xff, status, "proto low "
395 				"over-limit");
396 			if (status->status < 0)
397 				return;
398 			APP_CHECK(high <= 0xff, status, "proto high "
399 				"over-limit");
400 			if (status->status < 0)
401 				return;
402 
403 			rule_ipv6->field[0].value.u8 = (uint8_t)low;
404 			rule_ipv6->field[0].mask_range.u8 = (uint8_t)high;
405 
406 			proto_p = 1;
407 			continue;
408 		}
409 
410 		if (strcmp(tokens[ti], "sport") == 0) {
411 			uint16_t port_low, port_high;
412 
413 			APP_CHECK_PRESENCE(sport_p, tokens[ti], status);
414 			if (status->status < 0)
415 				return;
416 			INCREMENT_TOKEN_INDEX(ti, n_tokens, status);
417 			if (status->status < 0)
418 				return;
419 
420 			APP_CHECK(parse_range(tokens[ti], &port_low,
421 				&port_high) == 0, status, "unrecognized "
422 				"input \"%s\", expect \"port_from:"
423 				"port_to\"", tokens[ti]);
424 			if (status->status < 0)
425 				return;
426 
427 			rule_ipv6->field[9].value.u16 = port_low;
428 			rule_ipv6->field[9].mask_range.u16 = port_high;
429 
430 			sport_p = 1;
431 			continue;
432 		}
433 
434 		if (strcmp(tokens[ti], "dport") == 0) {
435 			uint16_t port_low, port_high;
436 
437 			APP_CHECK_PRESENCE(dport_p, tokens[ti], status);
438 			if (status->status < 0)
439 				return;
440 			INCREMENT_TOKEN_INDEX(ti, n_tokens, status);
441 			if (status->status < 0)
442 				return;
443 
444 			APP_CHECK(parse_range(tokens[ti], &port_low,
445 				&port_high) == 0, status, "unrecognized "
446 				"input \"%s\", expect \"port_from:"
447 				"port_to\"", tokens[ti]);
448 			if (status->status < 0)
449 				return;
450 
451 			rule_ipv6->field[10].value.u16 = port_low;
452 			rule_ipv6->field[10].mask_range.u16 = port_high;
453 
454 			dport_p = 1;
455 			continue;
456 		}
457 
458 		/* unrecognizeable input */
459 		APP_CHECK(0, status, "unrecognized input \"%s\"",
460 			tokens[ti]);
461 		return;
462 	}
463 
464 	/* check if argument(s) are missing */
465 	APP_CHECK(esp_p == 1, status, "missing argument \"esp\"");
466 	if (status->status < 0)
467 		return;
468 
469 	APP_CHECK(protect_p | bypass_p | discard_p, status, "missing "
470 		"argument \"protect\", \"bypass\", or \"discard\"");
471 	if (status->status < 0)
472 		return;
473 
474 	*ri = *ri + 1;
475 }
476 
477 static inline void
478 print_one_ip6_rule(const struct acl6_rules *rule, int32_t extra)
479 {
480 	uint8_t a, b, c, d;
481 
482 	uint32_t_to_char(rule->field[IP6_SRC0].value.u32,
483 		&a, &b, &c, &d);
484 	printf("%.2x%.2x:%.2x%.2x", a, b, c, d);
485 	uint32_t_to_char(rule->field[IP6_SRC1].value.u32,
486 		&a, &b, &c, &d);
487 	printf(":%.2x%.2x:%.2x%.2x", a, b, c, d);
488 	uint32_t_to_char(rule->field[IP6_SRC2].value.u32,
489 		&a, &b, &c, &d);
490 	printf(":%.2x%.2x:%.2x%.2x", a, b, c, d);
491 	uint32_t_to_char(rule->field[IP6_SRC3].value.u32,
492 		&a, &b, &c, &d);
493 	printf(":%.2x%.2x:%.2x%.2x/%u ", a, b, c, d,
494 			rule->field[IP6_SRC0].mask_range.u32
495 			+ rule->field[IP6_SRC1].mask_range.u32
496 			+ rule->field[IP6_SRC2].mask_range.u32
497 			+ rule->field[IP6_SRC3].mask_range.u32);
498 
499 	uint32_t_to_char(rule->field[IP6_DST0].value.u32,
500 		&a, &b, &c, &d);
501 	printf("%.2x%.2x:%.2x%.2x", a, b, c, d);
502 	uint32_t_to_char(rule->field[IP6_DST1].value.u32,
503 		&a, &b, &c, &d);
504 	printf(":%.2x%.2x:%.2x%.2x", a, b, c, d);
505 	uint32_t_to_char(rule->field[IP6_DST2].value.u32,
506 		&a, &b, &c, &d);
507 	printf(":%.2x%.2x:%.2x%.2x", a, b, c, d);
508 	uint32_t_to_char(rule->field[IP6_DST3].value.u32,
509 		&a, &b, &c, &d);
510 	printf(":%.2x%.2x:%.2x%.2x/%u ", a, b, c, d,
511 			rule->field[IP6_DST0].mask_range.u32
512 			+ rule->field[IP6_DST1].mask_range.u32
513 			+ rule->field[IP6_DST2].mask_range.u32
514 			+ rule->field[IP6_DST3].mask_range.u32);
515 
516 	printf("%hu : %hu %hu : %hu 0x%hhx/0x%hhx ",
517 		rule->field[IP6_SRCP].value.u16,
518 		rule->field[IP6_SRCP].mask_range.u16,
519 		rule->field[IP6_DSTP].value.u16,
520 		rule->field[IP6_DSTP].mask_range.u16,
521 		rule->field[IP6_PROTO].value.u8,
522 		rule->field[IP6_PROTO].mask_range.u8);
523 	if (extra)
524 		printf("0x%x-0x%x-0x%x ",
525 			rule->data.category_mask,
526 			rule->data.priority,
527 			rule->data.userdata);
528 }
529 
530 static inline void
531 dump_ip6_rules(const struct acl6_rules *rule, int32_t num, int32_t extra)
532 {
533 	int32_t i;
534 
535 	for (i = 0; i < num; i++, rule++) {
536 		printf("\t%d:", i + 1);
537 		print_one_ip6_rule(rule, extra);
538 		printf("\n");
539 	}
540 }
541 
542 static struct rte_acl_ctx *
543 acl6_init(const char *name, int32_t socketid, const struct acl6_rules *rules,
544 		uint32_t rules_nb)
545 {
546 	char s[PATH_MAX];
547 	struct rte_acl_param acl_param;
548 	struct rte_acl_config acl_build_param;
549 	struct rte_acl_ctx *ctx;
550 
551 	printf("Creating SP context with %u max rules\n", MAX_ACL_RULE_NUM);
552 
553 	memset(&acl_param, 0, sizeof(acl_param));
554 
555 	/* Create ACL contexts */
556 	snprintf(s, sizeof(s), "%s_%d", name, socketid);
557 
558 	printf("IPv4 %s entries [%u]:\n", s, rules_nb);
559 	dump_ip6_rules(rules, rules_nb, 1);
560 
561 	acl_param.name = s;
562 	acl_param.socket_id = socketid;
563 	acl_param.rule_size = RTE_ACL_RULE_SZ(RTE_DIM(ip6_defs));
564 	acl_param.max_rule_num = MAX_ACL_RULE_NUM;
565 
566 	ctx = rte_acl_create(&acl_param);
567 	if (ctx == NULL)
568 		rte_exit(EXIT_FAILURE, "Failed to create ACL context\n");
569 
570 	if (rte_acl_add_rules(ctx, (const struct rte_acl_rule *)rules,
571 				rules_nb) < 0)
572 		rte_exit(EXIT_FAILURE, "add rules failed\n");
573 
574 	/* Perform builds */
575 	memset(&acl_build_param, 0, sizeof(acl_build_param));
576 
577 	acl_build_param.num_categories = DEFAULT_MAX_CATEGORIES;
578 	acl_build_param.num_fields = RTE_DIM(ip6_defs);
579 	memcpy(&acl_build_param.defs, ip6_defs, sizeof(ip6_defs));
580 
581 	if (rte_acl_build(ctx, &acl_build_param) != 0)
582 		rte_exit(EXIT_FAILURE, "Failed to build ACL trie\n");
583 
584 	rte_acl_dump(ctx);
585 
586 	return ctx;
587 }
588 
589 void
590 sp6_init(struct socket_ctx *ctx, int32_t socket_id)
591 {
592 	const char *name;
593 
594 	if (ctx == NULL)
595 		rte_exit(EXIT_FAILURE, "NULL context.\n");
596 
597 	if (ctx->sp_ip6_in != NULL)
598 		rte_exit(EXIT_FAILURE, "Inbound IPv6 SP DB for socket %u "
599 				"already initialized\n", socket_id);
600 
601 	if (ctx->sp_ip6_out != NULL)
602 		rte_exit(EXIT_FAILURE, "Outbound IPv6 SP DB for socket %u "
603 				"already initialized\n", socket_id);
604 
605 	if (nb_acl6_rules_in > 0) {
606 		name = "sp_ip6_in";
607 		ctx->sp_ip6_in = (struct sp_ctx *)acl6_init(name,
608 			socket_id, acl6_rules_in, nb_acl6_rules_in);
609 	} else
610 		RTE_LOG(WARNING, IPSEC, "No IPv6 SP Inbound rule "
611 			"specified\n");
612 
613 	if (nb_acl6_rules_out > 0) {
614 		name = "sp_ip6_out";
615 		ctx->sp_ip6_out = (struct sp_ctx *)acl6_init(name,
616 			socket_id, acl6_rules_out, nb_acl6_rules_out);
617 	} else
618 		RTE_LOG(WARNING, IPSEC, "No IPv6 SP Outbound rule "
619 			"specified\n");
620 }
621 
622 /*
623  * Search though SP rules for given SPI.
624  */
625 int
626 sp6_spi_present(uint32_t spi, int inbound)
627 {
628 	uint32_t i, num;
629 	const struct acl6_rules *acr;
630 
631 	if (inbound != 0) {
632 		acr = acl6_rules_in;
633 		num = nb_acl6_rules_in;
634 	} else {
635 		acr = acl6_rules_out;
636 		num = nb_acl6_rules_out;
637 	}
638 
639 	for (i = 0; i != num; i++) {
640 		if (acr[i].data.userdata == PROTECT(spi))
641 			return i;
642 	}
643 
644 	return -ENOENT;
645 }
646