1 /* SPDX-License-Identifier: BSD-3-Clause 2 * Copyright(c) 2017 Intel Corporation 3 */ 4 5 #include <rte_compat.h> 6 #include <rte_flow_classify.h> 7 #include "rte_flow_classify_parse.h" 8 #include <rte_flow_driver.h> 9 #include <rte_table_acl.h> 10 #include <stdbool.h> 11 12 int librte_flow_classify_logtype; 13 14 static uint32_t unique_id = 1; 15 16 enum rte_flow_classify_table_type table_type 17 = RTE_FLOW_CLASSIFY_TABLE_TYPE_NONE; 18 19 struct rte_flow_classify_table_entry { 20 /* meta-data for classify rule */ 21 uint32_t rule_id; 22 23 /* Flow action */ 24 struct classify_action action; 25 }; 26 27 struct rte_cls_table { 28 /* Input parameters */ 29 struct rte_table_ops ops; 30 uint32_t entry_size; 31 enum rte_flow_classify_table_type type; 32 33 /* Handle to the low-level table object */ 34 void *h_table; 35 }; 36 37 #define RTE_FLOW_CLASSIFIER_MAX_NAME_SZ 256 38 39 struct rte_flow_classifier { 40 /* Input parameters */ 41 char name[RTE_FLOW_CLASSIFIER_MAX_NAME_SZ]; 42 int socket_id; 43 44 /* Internal */ 45 /* ntuple_filter */ 46 struct rte_eth_ntuple_filter ntuple_filter; 47 48 /* classifier tables */ 49 struct rte_cls_table tables[RTE_FLOW_CLASSIFY_TABLE_MAX]; 50 uint32_t table_mask; 51 uint32_t num_tables; 52 53 uint16_t nb_pkts; 54 struct rte_flow_classify_table_entry 55 *entries[RTE_PORT_IN_BURST_SIZE_MAX]; 56 } __rte_cache_aligned; 57 58 enum { 59 PROTO_FIELD_IPV4, 60 SRC_FIELD_IPV4, 61 DST_FIELD_IPV4, 62 SRCP_FIELD_IPV4, 63 DSTP_FIELD_IPV4, 64 NUM_FIELDS_IPV4 65 }; 66 67 struct acl_keys { 68 struct rte_table_acl_rule_add_params key_add; /* add key */ 69 struct rte_table_acl_rule_delete_params key_del; /* delete key */ 70 }; 71 72 struct classify_rules { 73 enum rte_flow_classify_rule_type type; 74 union { 75 struct rte_flow_classify_ipv4_5tuple ipv4_5tuple; 76 } u; 77 }; 78 79 struct rte_flow_classify_rule { 80 uint32_t id; /* unique ID of classify rule */ 81 enum rte_flow_classify_table_type tbl_type; /* rule table */ 82 struct classify_rules rules; /* union of rules */ 83 union { 84 struct acl_keys key; 85 } u; 86 int key_found; /* rule key found in table */ 87 struct rte_flow_classify_table_entry entry; /* rule meta data */ 88 void *entry_ptr; /* handle to the table entry for rule meta data */ 89 }; 90 91 int __rte_experimental 92 rte_flow_classify_validate( 93 struct rte_flow_classifier *cls, 94 const struct rte_flow_attr *attr, 95 const struct rte_flow_item pattern[], 96 const struct rte_flow_action actions[], 97 struct rte_flow_error *error) 98 { 99 struct rte_flow_item *items; 100 parse_filter_t parse_filter; 101 uint32_t item_num = 0; 102 uint32_t i = 0; 103 int ret; 104 105 if (error == NULL) 106 return -EINVAL; 107 108 if (cls == NULL) { 109 RTE_FLOW_CLASSIFY_LOG(ERR, 110 "%s: rte_flow_classifier parameter is NULL\n", 111 __func__); 112 return -EINVAL; 113 } 114 115 if (!attr) { 116 rte_flow_error_set(error, EINVAL, 117 RTE_FLOW_ERROR_TYPE_ATTR, 118 NULL, "NULL attribute."); 119 return -EINVAL; 120 } 121 122 if (!pattern) { 123 rte_flow_error_set(error, 124 EINVAL, RTE_FLOW_ERROR_TYPE_ITEM_NUM, 125 NULL, "NULL pattern."); 126 return -EINVAL; 127 } 128 129 if (!actions) { 130 rte_flow_error_set(error, EINVAL, 131 RTE_FLOW_ERROR_TYPE_ACTION_NUM, 132 NULL, "NULL action."); 133 return -EINVAL; 134 } 135 136 memset(&cls->ntuple_filter, 0, sizeof(cls->ntuple_filter)); 137 138 /* Get the non-void item number of pattern */ 139 while ((pattern + i)->type != RTE_FLOW_ITEM_TYPE_END) { 140 if ((pattern + i)->type != RTE_FLOW_ITEM_TYPE_VOID) 141 item_num++; 142 i++; 143 } 144 item_num++; 145 146 items = malloc(item_num * sizeof(struct rte_flow_item)); 147 if (!items) { 148 rte_flow_error_set(error, ENOMEM, 149 RTE_FLOW_ERROR_TYPE_ITEM_NUM, 150 NULL, "No memory for pattern items."); 151 return -ENOMEM; 152 } 153 154 memset(items, 0, item_num * sizeof(struct rte_flow_item)); 155 classify_pattern_skip_void_item(items, pattern); 156 157 parse_filter = classify_find_parse_filter_func(items); 158 if (!parse_filter) { 159 rte_flow_error_set(error, EINVAL, 160 RTE_FLOW_ERROR_TYPE_ITEM, 161 pattern, "Unsupported pattern"); 162 free(items); 163 return -EINVAL; 164 } 165 166 ret = parse_filter(attr, items, actions, &cls->ntuple_filter, error); 167 free(items); 168 return ret; 169 } 170 171 172 #define uint32_t_to_char(ip, a, b, c, d) do {\ 173 *a = (unsigned char)(ip >> 24 & 0xff);\ 174 *b = (unsigned char)(ip >> 16 & 0xff);\ 175 *c = (unsigned char)(ip >> 8 & 0xff);\ 176 *d = (unsigned char)(ip & 0xff);\ 177 } while (0) 178 179 static inline void 180 print_acl_ipv4_key_add(struct rte_table_acl_rule_add_params *key) 181 { 182 unsigned char a, b, c, d; 183 184 printf("%s: 0x%02hhx/0x%hhx ", __func__, 185 key->field_value[PROTO_FIELD_IPV4].value.u8, 186 key->field_value[PROTO_FIELD_IPV4].mask_range.u8); 187 188 uint32_t_to_char(key->field_value[SRC_FIELD_IPV4].value.u32, 189 &a, &b, &c, &d); 190 printf(" %hhu.%hhu.%hhu.%hhu/0x%x ", a, b, c, d, 191 key->field_value[SRC_FIELD_IPV4].mask_range.u32); 192 193 uint32_t_to_char(key->field_value[DST_FIELD_IPV4].value.u32, 194 &a, &b, &c, &d); 195 printf("%hhu.%hhu.%hhu.%hhu/0x%x ", a, b, c, d, 196 key->field_value[DST_FIELD_IPV4].mask_range.u32); 197 198 printf("%hu : 0x%x %hu : 0x%x", 199 key->field_value[SRCP_FIELD_IPV4].value.u16, 200 key->field_value[SRCP_FIELD_IPV4].mask_range.u16, 201 key->field_value[DSTP_FIELD_IPV4].value.u16, 202 key->field_value[DSTP_FIELD_IPV4].mask_range.u16); 203 204 printf(" priority: 0x%x\n", key->priority); 205 } 206 207 static inline void 208 print_acl_ipv4_key_delete(struct rte_table_acl_rule_delete_params *key) 209 { 210 unsigned char a, b, c, d; 211 212 printf("%s: 0x%02hhx/0x%hhx ", __func__, 213 key->field_value[PROTO_FIELD_IPV4].value.u8, 214 key->field_value[PROTO_FIELD_IPV4].mask_range.u8); 215 216 uint32_t_to_char(key->field_value[SRC_FIELD_IPV4].value.u32, 217 &a, &b, &c, &d); 218 printf(" %hhu.%hhu.%hhu.%hhu/0x%x ", a, b, c, d, 219 key->field_value[SRC_FIELD_IPV4].mask_range.u32); 220 221 uint32_t_to_char(key->field_value[DST_FIELD_IPV4].value.u32, 222 &a, &b, &c, &d); 223 printf("%hhu.%hhu.%hhu.%hhu/0x%x ", a, b, c, d, 224 key->field_value[DST_FIELD_IPV4].mask_range.u32); 225 226 printf("%hu : 0x%x %hu : 0x%x\n", 227 key->field_value[SRCP_FIELD_IPV4].value.u16, 228 key->field_value[SRCP_FIELD_IPV4].mask_range.u16, 229 key->field_value[DSTP_FIELD_IPV4].value.u16, 230 key->field_value[DSTP_FIELD_IPV4].mask_range.u16); 231 } 232 233 static int 234 rte_flow_classifier_check_params(struct rte_flow_classifier_params *params) 235 { 236 if (params == NULL) { 237 RTE_FLOW_CLASSIFY_LOG(ERR, 238 "%s: Incorrect value for parameter params\n", __func__); 239 return -EINVAL; 240 } 241 242 /* name */ 243 if (params->name == NULL) { 244 RTE_FLOW_CLASSIFY_LOG(ERR, 245 "%s: Incorrect value for parameter name\n", __func__); 246 return -EINVAL; 247 } 248 249 /* socket */ 250 if (params->socket_id < 0) { 251 RTE_FLOW_CLASSIFY_LOG(ERR, 252 "%s: Incorrect value for parameter socket_id\n", 253 __func__); 254 return -EINVAL; 255 } 256 257 return 0; 258 } 259 260 struct rte_flow_classifier * __rte_experimental 261 rte_flow_classifier_create(struct rte_flow_classifier_params *params) 262 { 263 struct rte_flow_classifier *cls; 264 int ret; 265 266 /* Check input parameters */ 267 ret = rte_flow_classifier_check_params(params); 268 if (ret != 0) { 269 RTE_FLOW_CLASSIFY_LOG(ERR, 270 "%s: flow classifier params check failed (%d)\n", 271 __func__, ret); 272 return NULL; 273 } 274 275 /* Allocate memory for the flow classifier */ 276 cls = rte_zmalloc_socket("FLOW_CLASSIFIER", 277 sizeof(struct rte_flow_classifier), 278 RTE_CACHE_LINE_SIZE, params->socket_id); 279 280 if (cls == NULL) { 281 RTE_FLOW_CLASSIFY_LOG(ERR, 282 "%s: flow classifier memory allocation failed\n", 283 __func__); 284 return NULL; 285 } 286 287 /* Save input parameters */ 288 snprintf(cls->name, RTE_FLOW_CLASSIFIER_MAX_NAME_SZ, "%s", 289 params->name); 290 291 cls->socket_id = params->socket_id; 292 293 return cls; 294 } 295 296 static void 297 rte_flow_classify_table_free(struct rte_cls_table *table) 298 { 299 if (table->ops.f_free != NULL) 300 table->ops.f_free(table->h_table); 301 } 302 303 int __rte_experimental 304 rte_flow_classifier_free(struct rte_flow_classifier *cls) 305 { 306 uint32_t i; 307 308 /* Check input parameters */ 309 if (cls == NULL) { 310 RTE_FLOW_CLASSIFY_LOG(ERR, 311 "%s: rte_flow_classifier parameter is NULL\n", 312 __func__); 313 return -EINVAL; 314 } 315 316 /* Free tables */ 317 for (i = 0; i < cls->num_tables; i++) { 318 struct rte_cls_table *table = &cls->tables[i]; 319 320 rte_flow_classify_table_free(table); 321 } 322 323 /* Free flow classifier memory */ 324 rte_free(cls); 325 326 return 0; 327 } 328 329 static int 330 rte_table_check_params(struct rte_flow_classifier *cls, 331 struct rte_flow_classify_table_params *params) 332 { 333 if (cls == NULL) { 334 RTE_FLOW_CLASSIFY_LOG(ERR, 335 "%s: flow classifier parameter is NULL\n", 336 __func__); 337 return -EINVAL; 338 } 339 if (params == NULL) { 340 RTE_FLOW_CLASSIFY_LOG(ERR, "%s: params parameter is NULL\n", 341 __func__); 342 return -EINVAL; 343 } 344 345 /* ops */ 346 if (params->ops == NULL) { 347 RTE_FLOW_CLASSIFY_LOG(ERR, "%s: params->ops is NULL\n", 348 __func__); 349 return -EINVAL; 350 } 351 352 if (params->ops->f_create == NULL) { 353 RTE_FLOW_CLASSIFY_LOG(ERR, 354 "%s: f_create function pointer is NULL\n", __func__); 355 return -EINVAL; 356 } 357 358 if (params->ops->f_lookup == NULL) { 359 RTE_FLOW_CLASSIFY_LOG(ERR, 360 "%s: f_lookup function pointer is NULL\n", __func__); 361 return -EINVAL; 362 } 363 364 /* De we have room for one more table? */ 365 if (cls->num_tables == RTE_FLOW_CLASSIFY_TABLE_MAX) { 366 RTE_FLOW_CLASSIFY_LOG(ERR, 367 "%s: Incorrect value for num_tables parameter\n", 368 __func__); 369 return -EINVAL; 370 } 371 372 return 0; 373 } 374 375 int __rte_experimental 376 rte_flow_classify_table_create(struct rte_flow_classifier *cls, 377 struct rte_flow_classify_table_params *params) 378 { 379 struct rte_cls_table *table; 380 void *h_table; 381 uint32_t entry_size; 382 int ret; 383 384 /* Check input arguments */ 385 ret = rte_table_check_params(cls, params); 386 if (ret != 0) 387 return ret; 388 389 /* calculate table entry size */ 390 entry_size = sizeof(struct rte_flow_classify_table_entry); 391 392 /* Create the table */ 393 h_table = params->ops->f_create(params->arg_create, cls->socket_id, 394 entry_size); 395 if (h_table == NULL) { 396 RTE_FLOW_CLASSIFY_LOG(ERR, "%s: Table creation failed\n", 397 __func__); 398 return -EINVAL; 399 } 400 401 /* Commit current table to the classifier */ 402 table = &cls->tables[cls->num_tables]; 403 table->type = params->type; 404 cls->num_tables++; 405 406 /* Save input parameters */ 407 memcpy(&table->ops, params->ops, sizeof(struct rte_table_ops)); 408 409 /* Initialize table internal data structure */ 410 table->entry_size = entry_size; 411 table->h_table = h_table; 412 413 return 0; 414 } 415 416 static struct rte_flow_classify_rule * 417 allocate_acl_ipv4_5tuple_rule(struct rte_flow_classifier *cls) 418 { 419 struct rte_flow_classify_rule *rule; 420 int log_level; 421 422 rule = malloc(sizeof(struct rte_flow_classify_rule)); 423 if (!rule) 424 return rule; 425 426 memset(rule, 0, sizeof(struct rte_flow_classify_rule)); 427 rule->id = unique_id++; 428 rule->rules.type = RTE_FLOW_CLASSIFY_RULE_TYPE_IPV4_5TUPLE; 429 430 /* key add values */ 431 rule->u.key.key_add.priority = cls->ntuple_filter.priority; 432 rule->u.key.key_add.field_value[PROTO_FIELD_IPV4].mask_range.u8 = 433 cls->ntuple_filter.proto_mask; 434 rule->u.key.key_add.field_value[PROTO_FIELD_IPV4].value.u8 = 435 cls->ntuple_filter.proto; 436 rule->rules.u.ipv4_5tuple.proto = cls->ntuple_filter.proto; 437 rule->rules.u.ipv4_5tuple.proto_mask = cls->ntuple_filter.proto_mask; 438 439 rule->u.key.key_add.field_value[SRC_FIELD_IPV4].mask_range.u32 = 440 cls->ntuple_filter.src_ip_mask; 441 rule->u.key.key_add.field_value[SRC_FIELD_IPV4].value.u32 = 442 cls->ntuple_filter.src_ip; 443 rule->rules.u.ipv4_5tuple.src_ip_mask = cls->ntuple_filter.src_ip_mask; 444 rule->rules.u.ipv4_5tuple.src_ip = cls->ntuple_filter.src_ip; 445 446 rule->u.key.key_add.field_value[DST_FIELD_IPV4].mask_range.u32 = 447 cls->ntuple_filter.dst_ip_mask; 448 rule->u.key.key_add.field_value[DST_FIELD_IPV4].value.u32 = 449 cls->ntuple_filter.dst_ip; 450 rule->rules.u.ipv4_5tuple.dst_ip_mask = cls->ntuple_filter.dst_ip_mask; 451 rule->rules.u.ipv4_5tuple.dst_ip = cls->ntuple_filter.dst_ip; 452 453 rule->u.key.key_add.field_value[SRCP_FIELD_IPV4].mask_range.u16 = 454 cls->ntuple_filter.src_port_mask; 455 rule->u.key.key_add.field_value[SRCP_FIELD_IPV4].value.u16 = 456 cls->ntuple_filter.src_port; 457 rule->rules.u.ipv4_5tuple.src_port_mask = 458 cls->ntuple_filter.src_port_mask; 459 rule->rules.u.ipv4_5tuple.src_port = cls->ntuple_filter.src_port; 460 461 rule->u.key.key_add.field_value[DSTP_FIELD_IPV4].mask_range.u16 = 462 cls->ntuple_filter.dst_port_mask; 463 rule->u.key.key_add.field_value[DSTP_FIELD_IPV4].value.u16 = 464 cls->ntuple_filter.dst_port; 465 rule->rules.u.ipv4_5tuple.dst_port_mask = 466 cls->ntuple_filter.dst_port_mask; 467 rule->rules.u.ipv4_5tuple.dst_port = cls->ntuple_filter.dst_port; 468 469 log_level = rte_log_get_level(librte_flow_classify_logtype); 470 471 if (log_level == RTE_LOG_DEBUG) 472 print_acl_ipv4_key_add(&rule->u.key.key_add); 473 474 /* key delete values */ 475 memcpy(&rule->u.key.key_del.field_value[PROTO_FIELD_IPV4], 476 &rule->u.key.key_add.field_value[PROTO_FIELD_IPV4], 477 NUM_FIELDS_IPV4 * sizeof(struct rte_acl_field)); 478 479 if (log_level == RTE_LOG_DEBUG) 480 print_acl_ipv4_key_delete(&rule->u.key.key_del); 481 482 return rule; 483 } 484 485 struct rte_flow_classify_rule * __rte_experimental 486 rte_flow_classify_table_entry_add(struct rte_flow_classifier *cls, 487 const struct rte_flow_attr *attr, 488 const struct rte_flow_item pattern[], 489 const struct rte_flow_action actions[], 490 int *key_found, 491 struct rte_flow_error *error) 492 { 493 struct rte_flow_classify_rule *rule; 494 struct rte_flow_classify_table_entry *table_entry; 495 struct classify_action *action; 496 uint32_t i; 497 int ret; 498 499 if (!error) 500 return NULL; 501 502 if (key_found == NULL) { 503 rte_flow_error_set(error, EINVAL, 504 RTE_FLOW_ERROR_TYPE_UNSPECIFIED, 505 NULL, "NULL key_found."); 506 return NULL; 507 } 508 509 /* parse attr, pattern and actions */ 510 ret = rte_flow_classify_validate(cls, attr, pattern, actions, error); 511 if (ret < 0) 512 return NULL; 513 514 switch (table_type) { 515 case RTE_FLOW_CLASSIFY_TABLE_ACL_IP4_5TUPLE: 516 rule = allocate_acl_ipv4_5tuple_rule(cls); 517 if (!rule) 518 return NULL; 519 rule->tbl_type = table_type; 520 cls->table_mask |= table_type; 521 break; 522 default: 523 return NULL; 524 } 525 526 action = classify_get_flow_action(); 527 table_entry = &rule->entry; 528 table_entry->rule_id = rule->id; 529 table_entry->action.action_mask = action->action_mask; 530 531 /* Copy actions */ 532 if (action->action_mask & (1LLU << RTE_FLOW_ACTION_TYPE_COUNT)) { 533 memcpy(&table_entry->action.act.counter, &action->act.counter, 534 sizeof(table_entry->action.act.counter)); 535 } 536 if (action->action_mask & (1LLU << RTE_FLOW_ACTION_TYPE_MARK)) { 537 memcpy(&table_entry->action.act.mark, &action->act.mark, 538 sizeof(table_entry->action.act.mark)); 539 } 540 541 for (i = 0; i < cls->num_tables; i++) { 542 struct rte_cls_table *table = &cls->tables[i]; 543 544 if (table->type == table_type) { 545 if (table->ops.f_add != NULL) { 546 ret = table->ops.f_add( 547 table->h_table, 548 &rule->u.key.key_add, 549 &rule->entry, 550 &rule->key_found, 551 &rule->entry_ptr); 552 if (ret) { 553 free(rule); 554 return NULL; 555 } 556 557 *key_found = rule->key_found; 558 } 559 560 return rule; 561 } 562 } 563 free(rule); 564 return NULL; 565 } 566 567 int __rte_experimental 568 rte_flow_classify_table_entry_delete(struct rte_flow_classifier *cls, 569 struct rte_flow_classify_rule *rule) 570 { 571 uint32_t i; 572 int ret = -EINVAL; 573 574 if (!cls || !rule) 575 return ret; 576 enum rte_flow_classify_table_type tbl_type = rule->tbl_type; 577 578 for (i = 0; i < cls->num_tables; i++) { 579 struct rte_cls_table *table = &cls->tables[i]; 580 581 if (table->type == tbl_type) { 582 if (table->ops.f_delete != NULL) { 583 ret = table->ops.f_delete(table->h_table, 584 &rule->u.key.key_del, 585 &rule->key_found, 586 &rule->entry); 587 588 return ret; 589 } 590 } 591 } 592 free(rule); 593 return ret; 594 } 595 596 static int 597 flow_classifier_lookup(struct rte_flow_classifier *cls, 598 struct rte_cls_table *table, 599 struct rte_mbuf **pkts, 600 const uint16_t nb_pkts) 601 { 602 int ret = -EINVAL; 603 uint64_t pkts_mask; 604 uint64_t lookup_hit_mask; 605 606 pkts_mask = RTE_LEN2MASK(nb_pkts, uint64_t); 607 ret = table->ops.f_lookup(table->h_table, 608 pkts, pkts_mask, &lookup_hit_mask, 609 (void **)cls->entries); 610 611 if (!ret && lookup_hit_mask) 612 cls->nb_pkts = nb_pkts; 613 else 614 cls->nb_pkts = 0; 615 616 return ret; 617 } 618 619 static int 620 action_apply(struct rte_flow_classifier *cls, 621 struct rte_flow_classify_rule *rule, 622 struct rte_flow_classify_stats *stats) 623 { 624 struct rte_flow_classify_ipv4_5tuple_stats *ntuple_stats; 625 struct rte_flow_classify_table_entry *entry = &rule->entry; 626 uint64_t count = 0; 627 uint32_t action_mask = entry->action.action_mask; 628 int i, ret = -EINVAL; 629 630 if (action_mask & (1LLU << RTE_FLOW_ACTION_TYPE_COUNT)) { 631 for (i = 0; i < cls->nb_pkts; i++) { 632 if (rule->id == cls->entries[i]->rule_id) 633 count++; 634 } 635 if (count) { 636 ret = 0; 637 ntuple_stats = stats->stats; 638 ntuple_stats->counter1 = count; 639 ntuple_stats->ipv4_5tuple = rule->rules.u.ipv4_5tuple; 640 } 641 } 642 return ret; 643 } 644 645 int __rte_experimental 646 rte_flow_classifier_query(struct rte_flow_classifier *cls, 647 struct rte_mbuf **pkts, 648 const uint16_t nb_pkts, 649 struct rte_flow_classify_rule *rule, 650 struct rte_flow_classify_stats *stats) 651 { 652 enum rte_flow_classify_table_type tbl_type; 653 uint32_t i; 654 int ret = -EINVAL; 655 656 if (!cls || !rule || !stats || !pkts || nb_pkts == 0) 657 return ret; 658 659 tbl_type = rule->tbl_type; 660 for (i = 0; i < cls->num_tables; i++) { 661 struct rte_cls_table *table = &cls->tables[i]; 662 663 if (table->type == tbl_type) { 664 ret = flow_classifier_lookup(cls, table, 665 pkts, nb_pkts); 666 if (!ret) { 667 ret = action_apply(cls, rule, stats); 668 return ret; 669 } 670 } 671 } 672 return ret; 673 } 674 675 RTE_INIT(librte_flow_classify_init_log) 676 { 677 librte_flow_classify_logtype = 678 rte_log_register("lib.flow_classify"); 679 if (librte_flow_classify_logtype >= 0) 680 rte_log_set_level(librte_flow_classify_logtype, RTE_LOG_INFO); 681 } 682