1 /* SPDX-License-Identifier: BSD-3-Clause 2 * Copyright(c) 2019 Intel Corporation 3 */ 4 5 #include <string.h> 6 7 #include <rte_eal_memconfig.h> 8 #include <rte_errno.h> 9 #include <rte_hash.h> 10 #include <rte_hash_crc.h> 11 #include <rte_malloc.h> 12 #include <rte_random.h> 13 #include <rte_rwlock.h> 14 #include <rte_tailq.h> 15 16 #include "rte_ipsec_sad.h" 17 18 /* 19 * Rules are stored in three hash tables depending on key_type. 20 * Each rule will also be stored in SPI_ONLY table. 21 * for each data entry within this table last two bits are reserved to 22 * indicate presence of entries with the same SPI in DIP and DIP+SIP tables. 23 */ 24 25 #define SAD_PREFIX "SAD_" 26 /* "SAD_<name>" */ 27 #define SAD_FORMAT SAD_PREFIX "%s" 28 29 #define DEFAULT_HASH_FUNC rte_hash_crc 30 #define MIN_HASH_ENTRIES 8U /* From rte_cuckoo_hash.h */ 31 32 struct hash_cnt { 33 uint32_t cnt_dip; 34 uint32_t cnt_dip_sip; 35 }; 36 37 struct rte_ipsec_sad { 38 char name[RTE_IPSEC_SAD_NAMESIZE]; 39 struct rte_hash *hash[RTE_IPSEC_SAD_KEY_TYPE_MASK]; 40 uint32_t keysize[RTE_IPSEC_SAD_KEY_TYPE_MASK]; 41 uint32_t init_val; 42 /* Array to track number of more specific rules 43 * (spi_dip or spi_dip_sip). Used only in add/delete 44 * as a helper struct. 45 */ 46 __extension__ struct hash_cnt cnt_arr[]; 47 }; 48 49 TAILQ_HEAD(rte_ipsec_sad_list, rte_tailq_entry); 50 static struct rte_tailq_elem rte_ipsec_sad_tailq = { 51 .name = "RTE_IPSEC_SAD", 52 }; 53 EAL_REGISTER_TAILQ(rte_ipsec_sad_tailq) 54 55 #define SET_BIT(ptr, bit) (void *)((uintptr_t)(ptr) | (uintptr_t)(bit)) 56 #define CLEAR_BIT(ptr, bit) (void *)((uintptr_t)(ptr) & ~(uintptr_t)(bit)) 57 #define GET_BIT(ptr, bit) (void *)((uintptr_t)(ptr) & (uintptr_t)(bit)) 58 59 /* 60 * @internal helper function 61 * Add a rule of type SPI_DIP or SPI_DIP_SIP. 62 * Inserts a rule into an appropriate hash table, 63 * updates the value for a given SPI in SPI_ONLY hash table 64 * reflecting presence of more specific rule type in two LSBs. 65 * Updates a counter that reflects the number of rules whith the same SPI. 66 */ 67 static inline int 68 add_specific(struct rte_ipsec_sad *sad, const void *key, 69 int key_type, void *sa) 70 { 71 void *tmp_val; 72 int ret, notexist; 73 74 /* Check if the key is present in the table. 75 * Need for further accaunting in cnt_arr 76 */ 77 ret = rte_hash_lookup_with_hash(sad->hash[key_type], key, 78 rte_hash_crc(key, sad->keysize[key_type], sad->init_val)); 79 notexist = (ret == -ENOENT); 80 81 /* Add an SA to the corresponding table.*/ 82 ret = rte_hash_add_key_with_hash_data(sad->hash[key_type], key, 83 rte_hash_crc(key, sad->keysize[key_type], sad->init_val), sa); 84 if (ret != 0) 85 return ret; 86 87 /* Check if there is an entry in SPI only table with the same SPI */ 88 ret = rte_hash_lookup_with_hash_data(sad->hash[RTE_IPSEC_SAD_SPI_ONLY], 89 key, rte_hash_crc(key, sad->keysize[RTE_IPSEC_SAD_SPI_ONLY], 90 sad->init_val), &tmp_val); 91 if (ret < 0) 92 tmp_val = NULL; 93 tmp_val = SET_BIT(tmp_val, key_type); 94 95 /* Add an entry into SPI only table */ 96 ret = rte_hash_add_key_with_hash_data( 97 sad->hash[RTE_IPSEC_SAD_SPI_ONLY], key, 98 rte_hash_crc(key, sad->keysize[RTE_IPSEC_SAD_SPI_ONLY], 99 sad->init_val), tmp_val); 100 if (ret != 0) 101 return ret; 102 103 /* Update a counter for a given SPI */ 104 ret = rte_hash_lookup_with_hash(sad->hash[RTE_IPSEC_SAD_SPI_ONLY], key, 105 rte_hash_crc(key, sad->keysize[RTE_IPSEC_SAD_SPI_ONLY], 106 sad->init_val)); 107 if (ret < 0) 108 return ret; 109 if (key_type == RTE_IPSEC_SAD_SPI_DIP) 110 sad->cnt_arr[ret].cnt_dip += notexist; 111 else 112 sad->cnt_arr[ret].cnt_dip_sip += notexist; 113 114 return 0; 115 } 116 117 int 118 rte_ipsec_sad_add(struct rte_ipsec_sad *sad, 119 const union rte_ipsec_sad_key *key, 120 int key_type, void *sa) 121 { 122 void *tmp_val; 123 int ret; 124 125 if ((sad == NULL) || (key == NULL) || (sa == NULL) || 126 /* sa must be 4 byte aligned */ 127 (GET_BIT(sa, RTE_IPSEC_SAD_KEY_TYPE_MASK) != 0)) 128 return -EINVAL; 129 130 /* 131 * Rules are stored in three hash tables depending on key_type. 132 * All rules will also have an entry in SPI_ONLY table, with entry 133 * value's two LSB's also indicating presence of rule with this SPI 134 * in other tables. 135 */ 136 switch (key_type) { 137 case(RTE_IPSEC_SAD_SPI_ONLY): 138 ret = rte_hash_lookup_with_hash_data(sad->hash[key_type], 139 key, rte_hash_crc(key, sad->keysize[key_type], 140 sad->init_val), &tmp_val); 141 if (ret >= 0) 142 tmp_val = SET_BIT(sa, GET_BIT(tmp_val, 143 RTE_IPSEC_SAD_KEY_TYPE_MASK)); 144 else 145 tmp_val = sa; 146 ret = rte_hash_add_key_with_hash_data(sad->hash[key_type], 147 key, rte_hash_crc(key, sad->keysize[key_type], 148 sad->init_val), tmp_val); 149 return ret; 150 case(RTE_IPSEC_SAD_SPI_DIP): 151 case(RTE_IPSEC_SAD_SPI_DIP_SIP): 152 return add_specific(sad, key, key_type, sa); 153 default: 154 return -EINVAL; 155 } 156 } 157 158 /* 159 * @internal helper function 160 * Delete a rule of type SPI_DIP or SPI_DIP_SIP. 161 * Deletes an entry from an appropriate hash table and decrements 162 * an entry counter for given SPI. 163 * If entry to remove is the last one with given SPI within the table, 164 * then it will also update related entry in SPI_ONLY table. 165 * Removes an entry from SPI_ONLY hash table if there no rule left 166 * for this SPI in any table. 167 */ 168 static inline int 169 del_specific(struct rte_ipsec_sad *sad, const void *key, int key_type) 170 { 171 void *tmp_val; 172 int ret; 173 uint32_t *cnt; 174 175 /* Remove an SA from the corresponding table.*/ 176 ret = rte_hash_del_key_with_hash(sad->hash[key_type], key, 177 rte_hash_crc(key, sad->keysize[key_type], sad->init_val)); 178 if (ret < 0) 179 return ret; 180 181 /* Get an index of cnt_arr entry for a given SPI */ 182 ret = rte_hash_lookup_with_hash_data(sad->hash[RTE_IPSEC_SAD_SPI_ONLY], 183 key, rte_hash_crc(key, sad->keysize[RTE_IPSEC_SAD_SPI_ONLY], 184 sad->init_val), &tmp_val); 185 if (ret < 0) 186 return ret; 187 cnt = (key_type == RTE_IPSEC_SAD_SPI_DIP) ? 188 &sad->cnt_arr[ret].cnt_dip : 189 &sad->cnt_arr[ret].cnt_dip_sip; 190 if (--(*cnt) != 0) 191 return 0; 192 193 /* corresponding counter is 0, clear the bit indicating 194 * the presence of more specific rule for a given SPI. 195 */ 196 tmp_val = CLEAR_BIT(tmp_val, key_type); 197 198 /* if there are no rules left with same SPI, 199 * remove an entry from SPI_only table 200 */ 201 if (tmp_val == NULL) 202 ret = rte_hash_del_key_with_hash( 203 sad->hash[RTE_IPSEC_SAD_SPI_ONLY], key, 204 rte_hash_crc(key, sad->keysize[RTE_IPSEC_SAD_SPI_ONLY], 205 sad->init_val)); 206 else 207 ret = rte_hash_add_key_with_hash_data( 208 sad->hash[RTE_IPSEC_SAD_SPI_ONLY], key, 209 rte_hash_crc(key, sad->keysize[RTE_IPSEC_SAD_SPI_ONLY], 210 sad->init_val), tmp_val); 211 if (ret < 0) 212 return ret; 213 return 0; 214 } 215 216 int 217 rte_ipsec_sad_del(struct rte_ipsec_sad *sad, 218 const union rte_ipsec_sad_key *key, 219 int key_type) 220 { 221 void *tmp_val; 222 int ret; 223 224 if ((sad == NULL) || (key == NULL)) 225 return -EINVAL; 226 switch (key_type) { 227 case(RTE_IPSEC_SAD_SPI_ONLY): 228 ret = rte_hash_lookup_with_hash_data(sad->hash[key_type], 229 key, rte_hash_crc(key, sad->keysize[key_type], 230 sad->init_val), &tmp_val); 231 if (ret < 0) 232 return ret; 233 if (GET_BIT(tmp_val, RTE_IPSEC_SAD_KEY_TYPE_MASK) == 0) { 234 ret = rte_hash_del_key_with_hash(sad->hash[key_type], 235 key, rte_hash_crc(key, sad->keysize[key_type], 236 sad->init_val)); 237 ret = ret < 0 ? ret : 0; 238 } else { 239 tmp_val = GET_BIT(tmp_val, 240 RTE_IPSEC_SAD_KEY_TYPE_MASK); 241 ret = rte_hash_add_key_with_hash_data( 242 sad->hash[key_type], key, 243 rte_hash_crc(key, sad->keysize[key_type], 244 sad->init_val), tmp_val); 245 } 246 return ret; 247 case(RTE_IPSEC_SAD_SPI_DIP): 248 case(RTE_IPSEC_SAD_SPI_DIP_SIP): 249 return del_specific(sad, key, key_type); 250 default: 251 return -EINVAL; 252 } 253 } 254 255 struct rte_ipsec_sad * 256 rte_ipsec_sad_create(const char *name, const struct rte_ipsec_sad_conf *conf) 257 { 258 char hash_name[RTE_HASH_NAMESIZE]; 259 char sad_name[RTE_IPSEC_SAD_NAMESIZE]; 260 struct rte_tailq_entry *te; 261 struct rte_ipsec_sad_list *sad_list; 262 struct rte_ipsec_sad *sad, *tmp_sad = NULL; 263 struct rte_hash_parameters hash_params = {0}; 264 int ret; 265 uint32_t sa_sum; 266 267 RTE_BUILD_BUG_ON(RTE_IPSEC_SAD_KEY_TYPE_MASK != 3); 268 269 if ((name == NULL) || (conf == NULL) || 270 ((conf->max_sa[RTE_IPSEC_SAD_SPI_ONLY] == 0) && 271 (conf->max_sa[RTE_IPSEC_SAD_SPI_DIP] == 0) && 272 (conf->max_sa[RTE_IPSEC_SAD_SPI_DIP_SIP] == 0))) { 273 rte_errno = EINVAL; 274 return NULL; 275 } 276 277 ret = snprintf(sad_name, RTE_IPSEC_SAD_NAMESIZE, SAD_FORMAT, name); 278 if (ret < 0 || ret >= RTE_IPSEC_SAD_NAMESIZE) { 279 rte_errno = ENAMETOOLONG; 280 return NULL; 281 } 282 283 /** Init SAD*/ 284 sa_sum = RTE_MAX(MIN_HASH_ENTRIES, 285 conf->max_sa[RTE_IPSEC_SAD_SPI_ONLY]) + 286 RTE_MAX(MIN_HASH_ENTRIES, 287 conf->max_sa[RTE_IPSEC_SAD_SPI_DIP]) + 288 RTE_MAX(MIN_HASH_ENTRIES, 289 conf->max_sa[RTE_IPSEC_SAD_SPI_DIP_SIP]); 290 sad = rte_zmalloc_socket(NULL, sizeof(*sad) + 291 (sizeof(struct hash_cnt) * sa_sum), 292 RTE_CACHE_LINE_SIZE, conf->socket_id); 293 if (sad == NULL) { 294 rte_errno = ENOMEM; 295 return NULL; 296 } 297 memcpy(sad->name, sad_name, sizeof(sad_name)); 298 299 hash_params.hash_func = DEFAULT_HASH_FUNC; 300 hash_params.hash_func_init_val = rte_rand(); 301 sad->init_val = hash_params.hash_func_init_val; 302 hash_params.socket_id = conf->socket_id; 303 hash_params.name = hash_name; 304 if (conf->flags & RTE_IPSEC_SAD_FLAG_RW_CONCURRENCY) 305 hash_params.extra_flag = RTE_HASH_EXTRA_FLAGS_RW_CONCURRENCY; 306 307 /** Init hash[RTE_IPSEC_SAD_SPI_ONLY] for SPI only */ 308 snprintf(hash_name, sizeof(hash_name), "sad_1_%p", sad); 309 hash_params.key_len = sizeof(((struct rte_ipsec_sadv4_key *)0)->spi); 310 sad->keysize[RTE_IPSEC_SAD_SPI_ONLY] = hash_params.key_len; 311 hash_params.entries = sa_sum; 312 sad->hash[RTE_IPSEC_SAD_SPI_ONLY] = rte_hash_create(&hash_params); 313 if (sad->hash[RTE_IPSEC_SAD_SPI_ONLY] == NULL) { 314 rte_ipsec_sad_destroy(sad); 315 return NULL; 316 } 317 318 /** Init hash[RTE_IPSEC_SAD_SPI_DIP] for SPI + DIP */ 319 snprintf(hash_name, sizeof(hash_name), "sad_2_%p", sad); 320 if (conf->flags & RTE_IPSEC_SAD_FLAG_IPV6) 321 hash_params.key_len += 322 sizeof(((struct rte_ipsec_sadv6_key *)0)->dip); 323 else 324 hash_params.key_len += 325 sizeof(((struct rte_ipsec_sadv4_key *)0)->dip); 326 sad->keysize[RTE_IPSEC_SAD_SPI_DIP] = hash_params.key_len; 327 hash_params.entries = RTE_MAX(MIN_HASH_ENTRIES, 328 conf->max_sa[RTE_IPSEC_SAD_SPI_DIP]); 329 sad->hash[RTE_IPSEC_SAD_SPI_DIP] = rte_hash_create(&hash_params); 330 if (sad->hash[RTE_IPSEC_SAD_SPI_DIP] == NULL) { 331 rte_ipsec_sad_destroy(sad); 332 return NULL; 333 } 334 335 /** Init hash[[RTE_IPSEC_SAD_SPI_DIP_SIP] for SPI + DIP + SIP */ 336 snprintf(hash_name, sizeof(hash_name), "sad_3_%p", sad); 337 if (conf->flags & RTE_IPSEC_SAD_FLAG_IPV6) 338 hash_params.key_len += 339 sizeof(((struct rte_ipsec_sadv6_key *)0)->sip); 340 else 341 hash_params.key_len += 342 sizeof(((struct rte_ipsec_sadv4_key *)0)->sip); 343 sad->keysize[RTE_IPSEC_SAD_SPI_DIP_SIP] = hash_params.key_len; 344 hash_params.entries = RTE_MAX(MIN_HASH_ENTRIES, 345 conf->max_sa[RTE_IPSEC_SAD_SPI_DIP_SIP]); 346 sad->hash[RTE_IPSEC_SAD_SPI_DIP_SIP] = rte_hash_create(&hash_params); 347 if (sad->hash[RTE_IPSEC_SAD_SPI_DIP_SIP] == NULL) { 348 rte_ipsec_sad_destroy(sad); 349 return NULL; 350 } 351 352 sad_list = RTE_TAILQ_CAST(rte_ipsec_sad_tailq.head, 353 rte_ipsec_sad_list); 354 rte_mcfg_tailq_write_lock(); 355 /* guarantee there's no existing */ 356 TAILQ_FOREACH(te, sad_list, next) { 357 tmp_sad = (struct rte_ipsec_sad *)te->data; 358 if (strncmp(sad_name, tmp_sad->name, 359 RTE_IPSEC_SAD_NAMESIZE) == 0) 360 break; 361 } 362 if (te != NULL) { 363 rte_mcfg_tailq_write_unlock(); 364 rte_errno = EEXIST; 365 rte_ipsec_sad_destroy(sad); 366 return NULL; 367 } 368 369 /* allocate tailq entry */ 370 te = rte_zmalloc("IPSEC_SAD_TAILQ_ENTRY", sizeof(*te), 0); 371 if (te == NULL) { 372 rte_mcfg_tailq_write_unlock(); 373 rte_errno = ENOMEM; 374 rte_ipsec_sad_destroy(sad); 375 return NULL; 376 } 377 378 te->data = (void *)sad; 379 TAILQ_INSERT_TAIL(sad_list, te, next); 380 rte_mcfg_tailq_write_unlock(); 381 return sad; 382 } 383 384 struct rte_ipsec_sad * 385 rte_ipsec_sad_find_existing(const char *name) 386 { 387 char sad_name[RTE_IPSEC_SAD_NAMESIZE]; 388 struct rte_ipsec_sad *sad = NULL; 389 struct rte_tailq_entry *te; 390 struct rte_ipsec_sad_list *sad_list; 391 int ret; 392 393 ret = snprintf(sad_name, RTE_IPSEC_SAD_NAMESIZE, SAD_FORMAT, name); 394 if (ret < 0 || ret >= RTE_IPSEC_SAD_NAMESIZE) { 395 rte_errno = ENAMETOOLONG; 396 return NULL; 397 } 398 399 sad_list = RTE_TAILQ_CAST(rte_ipsec_sad_tailq.head, 400 rte_ipsec_sad_list); 401 402 rte_mcfg_tailq_read_lock(); 403 TAILQ_FOREACH(te, sad_list, next) { 404 sad = (struct rte_ipsec_sad *) te->data; 405 if (strncmp(sad_name, sad->name, RTE_IPSEC_SAD_NAMESIZE) == 0) 406 break; 407 } 408 rte_mcfg_tailq_read_unlock(); 409 410 if (te == NULL) { 411 rte_errno = ENOENT; 412 return NULL; 413 } 414 415 return sad; 416 } 417 418 void 419 rte_ipsec_sad_destroy(struct rte_ipsec_sad *sad) 420 { 421 struct rte_tailq_entry *te; 422 struct rte_ipsec_sad_list *sad_list; 423 424 if (sad == NULL) 425 return; 426 427 sad_list = RTE_TAILQ_CAST(rte_ipsec_sad_tailq.head, 428 rte_ipsec_sad_list); 429 rte_mcfg_tailq_write_lock(); 430 TAILQ_FOREACH(te, sad_list, next) { 431 if (te->data == (void *)sad) 432 break; 433 } 434 if (te != NULL) 435 TAILQ_REMOVE(sad_list, te, next); 436 437 rte_mcfg_tailq_write_unlock(); 438 439 rte_hash_free(sad->hash[RTE_IPSEC_SAD_SPI_ONLY]); 440 rte_hash_free(sad->hash[RTE_IPSEC_SAD_SPI_DIP]); 441 rte_hash_free(sad->hash[RTE_IPSEC_SAD_SPI_DIP_SIP]); 442 rte_free(sad); 443 if (te != NULL) 444 rte_free(te); 445 } 446 447 /* 448 * @internal helper function 449 * Lookup a batch of keys in three hash tables. 450 * First lookup key in SPI_ONLY table. 451 * If there is an entry for the corresponding SPI check its value. 452 * Two least significant bits of the value indicate 453 * the presence of more specific rule in other tables. 454 * Perform additional lookup in corresponding hash tables 455 * and update the value if lookup succeeded. 456 */ 457 static int 458 __ipsec_sad_lookup(const struct rte_ipsec_sad *sad, 459 const union rte_ipsec_sad_key *keys[], void *sa[], uint32_t n) 460 { 461 const void *keys_2[RTE_HASH_LOOKUP_BULK_MAX]; 462 const void *keys_3[RTE_HASH_LOOKUP_BULK_MAX]; 463 void *vals_2[RTE_HASH_LOOKUP_BULK_MAX] = {NULL}; 464 void *vals_3[RTE_HASH_LOOKUP_BULK_MAX] = {NULL}; 465 uint32_t idx_2[RTE_HASH_LOOKUP_BULK_MAX]; 466 uint32_t idx_3[RTE_HASH_LOOKUP_BULK_MAX]; 467 uint64_t mask_1, mask_2, mask_3; 468 uint64_t map, map_spec; 469 uint32_t n_2 = 0; 470 uint32_t n_3 = 0; 471 uint32_t i; 472 int found = 0; 473 hash_sig_t hash_sig[RTE_HASH_LOOKUP_BULK_MAX]; 474 hash_sig_t hash_sig_2[RTE_HASH_LOOKUP_BULK_MAX]; 475 hash_sig_t hash_sig_3[RTE_HASH_LOOKUP_BULK_MAX]; 476 477 for (i = 0; i < n; i++) { 478 sa[i] = NULL; 479 hash_sig[i] = rte_hash_crc_4byte(keys[i]->v4.spi, 480 sad->init_val); 481 } 482 483 /* 484 * Lookup keys in SPI only hash table first. 485 */ 486 rte_hash_lookup_with_hash_bulk_data(sad->hash[RTE_IPSEC_SAD_SPI_ONLY], 487 (const void **)keys, hash_sig, n, &mask_1, sa); 488 for (map = mask_1; map; map &= (map - 1)) { 489 i = rte_bsf64(map); 490 /* 491 * if returned value indicates presence of a rule in other 492 * tables save a key for further lookup. 493 */ 494 if ((uintptr_t)sa[i] & RTE_IPSEC_SAD_SPI_DIP_SIP) { 495 idx_3[n_3] = i; 496 hash_sig_3[n_3] = rte_hash_crc(keys[i], 497 sad->keysize[RTE_IPSEC_SAD_SPI_DIP_SIP], 498 sad->init_val); 499 keys_3[n_3++] = keys[i]; 500 } 501 if ((uintptr_t)sa[i] & RTE_IPSEC_SAD_SPI_DIP) { 502 idx_2[n_2] = i; 503 hash_sig_2[n_2] = rte_hash_crc(keys[i], 504 sad->keysize[RTE_IPSEC_SAD_SPI_DIP], 505 sad->init_val); 506 keys_2[n_2++] = keys[i]; 507 } 508 /* clear 2 LSB's which indicate the presence 509 * of more specific rules 510 */ 511 sa[i] = CLEAR_BIT(sa[i], RTE_IPSEC_SAD_KEY_TYPE_MASK); 512 } 513 514 /* Lookup for more specific rules in SPI_DIP table */ 515 if (n_2 != 0) { 516 rte_hash_lookup_with_hash_bulk_data( 517 sad->hash[RTE_IPSEC_SAD_SPI_DIP], 518 keys_2, hash_sig_2, n_2, &mask_2, vals_2); 519 for (map_spec = mask_2; map_spec; map_spec &= (map_spec - 1)) { 520 i = rte_bsf64(map_spec); 521 sa[idx_2[i]] = vals_2[i]; 522 } 523 } 524 /* Lookup for more specific rules in SPI_DIP_SIP table */ 525 if (n_3 != 0) { 526 rte_hash_lookup_with_hash_bulk_data( 527 sad->hash[RTE_IPSEC_SAD_SPI_DIP_SIP], 528 keys_3, hash_sig_3, n_3, &mask_3, vals_3); 529 for (map_spec = mask_3; map_spec; map_spec &= (map_spec - 1)) { 530 i = rte_bsf64(map_spec); 531 sa[idx_3[i]] = vals_3[i]; 532 } 533 } 534 535 for (i = 0; i < n; i++) 536 found += (sa[i] != NULL); 537 538 return found; 539 } 540 541 int 542 rte_ipsec_sad_lookup(const struct rte_ipsec_sad *sad, 543 const union rte_ipsec_sad_key *keys[], void *sa[], uint32_t n) 544 { 545 uint32_t num, i = 0; 546 int found = 0; 547 548 if (unlikely((sad == NULL) || (keys == NULL) || (sa == NULL))) 549 return -EINVAL; 550 551 do { 552 num = RTE_MIN(n - i, (uint32_t)RTE_HASH_LOOKUP_BULK_MAX); 553 found += __ipsec_sad_lookup(sad, 554 &keys[i], &sa[i], num); 555 i += num; 556 } while (i != n); 557 558 return found; 559 } 560