1 | /*************************************** 2 | $Revision: 1.17 $ 3 | 4 | Radix payload (rp) - user level functions for storing data in radix trees 5 | 6 | rp_search = search the loaded radix trees using an ascii key 7 | 8 | Motto: "And all that for inetnums..." 9 | 10 | Status: NOT REVIEWED, TESTED 11 | 12 | Design and implementation by: Marek Bukowy 13 | 14 | ******************/ /****************** 15 | Copyright (c) 1999,2000,2001,2002 RIPE NCC 16 | 17 | All Rights Reserved 18 | 19 | Permission to use, copy, modify, and distribute this software and its 20 | documentation for any purpose and without fee is hereby granted, 21 | provided that the above copyright notice appear in all copies and that 22 | both that copyright notice and this permission notice appear in 23 | supporting documentation, and that the name of the author not be 24 | used in advertising or publicity pertaining to distribution of the 25 | software without specific, written prior permission. 26 | 27 | THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING 28 | ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS; IN NO EVENT SHALL 29 | AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY 30 | DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN 31 | AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 32 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 33 | ***************************************/ 34 | 35 | #include "rip.h" 36 | 37 | static 38 | void 39 | rp_exclude_datlink(GList **datlist, GList *element) 40 | { 41 | /* remove element from list(becomes a self-consistent list) */ 42 | *datlist = g_list_remove_link(*datlist, element); 43 | 44 | /* free it and the payload */ 45 | wr_clear_list( &element ); 46 | } 47 | 48 | 49 | /**************************************************************************/ 50 | /*+++++++++++ 51 | helper: 52 | this routine goes through the list of prefixes and performs a bin_search 53 | on each of them; attaches the results to datlist. 54 | +++++++++++*/ 55 | static 56 | er_ret_t 57 | rp_preflist_search ( 58 | rx_srch_mt search_mode, 59 | int par_a, 60 | int par_b, 61 | rx_tree_t *mytree, 62 | GList **preflist, 63 | GList **datlist 64 | ) 65 | 66 | { 67 | char prefstr[IP_PREFSTR_MAX]; 68 | GList *qitem; 69 | ip_prefix_t *querypref; 70 | er_ret_t err; 71 | 72 | for( qitem = g_list_first(*preflist); 73 | qitem != NULL; 74 | qitem = g_list_next(qitem)) { 75 | 76 | querypref = qitem->data; 77 | 78 | if( IP_pref_b2a( querypref, prefstr, IP_PREFSTR_MAX) != IP_OK ) { 79 | die; 80 | } 81 | ER_dbg_va(FAC_RP, ASP_RP_SRCH_DET, 82 | "rx_preflist_search: mode %d (%s) (par %d) for %s", 83 | search_mode, RX_text_srch_mode(search_mode), par_a, prefstr); 84 | 85 | if (mytree->num_nodes > 0) { 86 | err = RX_bin_search( search_mode, par_a, par_b, mytree, querypref, 87 | datlist, RX_ANS_ALL); 88 | if( err != RX_OK ) { 89 | return err; 90 | } 91 | } 92 | } 93 | 94 | return RX_OK; 95 | } 96 | 97 | /*++++ 98 | this is a helper: goes through a datlist and returns the smallest 99 | size of a range 100 | 101 | works for IPv4 only 102 | +++*/ 103 | static 104 | ip_rangesize_t 105 | rp_find_smallest_span( GList *datlist ) { 106 | ip_rangesize_t min_span, span; 107 | GList *ditem; 108 | 109 | min_span = 0xffffffff; /* IPv4 only!!!!*/ 110 | 111 | /* go through the list and find the shortest range. */ 112 | for(ditem = g_list_first(datlist); 113 | ditem != NULL; 114 | ditem = g_list_next(ditem)) { 115 | rx_datref_t *refptr = (rx_datref_t *) (ditem->data); 116 | 117 | span = IP_rang_span( & refptr->leafptr->iprange); 118 | 119 | if( span < min_span ) { 120 | min_span = span; 121 | } 122 | } 123 | ER_dbg_va(FAC_RP, ASP_RP_SRCH_DET, 124 | "rp_find_smallest_span: minimal span is %d", min_span); 125 | 126 | return min_span; 127 | } 128 | 129 | 130 | 131 | /* helper for the inetnum/exless search - for this one a hash of pairs 132 | (leafptr,occurences) must be maintained. 133 | 134 | This routine increments the counter for a leafptr, creating a new 135 | pair if this leafptr was not referenced before. 136 | 137 | */ 138 | static 139 | int rp_leaf_occ_inc(GHashTable *hash, rx_dataleaf_t *leafptr) 140 | { 141 | /* one little trick: store the number of occurences 142 | as cast (void *) */ 143 | int val; 144 | 145 | val = (int) g_hash_table_lookup(hash, leafptr); 146 | /* 0 if it's not known yet. anyway: put it in the hash (value==key) */ 147 | 148 | g_hash_table_insert(hash, leafptr, (void *) ++val); 149 | 150 | return val; 151 | } 152 | 153 | /* exclude exact match - not to be merged with preselction :-( */ 154 | static void 155 | rp_exclude_exact_match( GList **datlist, ip_range_t *testrang) 156 | { 157 | GList *ditem, *newitem; 158 | 159 | ditem = g_list_first(*datlist); 160 | 161 | while( ditem != NULL ) { 162 | rx_datref_t *refptr = (rx_datref_t *) (ditem->data); 163 | 164 | newitem = g_list_next(ditem); 165 | 166 | if( memcmp( & refptr->leafptr->iprange, 167 | testrang, sizeof(ip_range_t)) == 0 ) { 168 | rp_exclude_datlink(datlist, ditem); 169 | ER_dbg_va(FAC_RP, ASP_RP_SRCH_DET, 170 | "process_datlist: discarded an exact match"); 171 | } 172 | ditem = newitem; 173 | } /* while */ 174 | } 175 | 176 | static int 177 | rp_find_longest_prefix(GList **datlist) 178 | { 179 | GList *ditem; 180 | int max_pref=0; 181 | 182 | for(ditem = g_list_first(*datlist); 183 | ditem != NULL; 184 | ditem = g_list_next(ditem)) { 185 | rx_datref_t *refptr = (rx_datref_t *) (ditem->data); 186 | 187 | if( refptr->leafptr->preflen > max_pref ) { 188 | max_pref = refptr->leafptr->preflen; 189 | } 190 | } 191 | 192 | return max_pref; 193 | } 194 | 195 | 196 | /*+ rp_asc_process_datlist() - helper for RP_asc_search() 197 | 198 | fetches the copies of objects from the radix tree into datlist 199 | 200 | ASSUMES LOCKED TREE 201 | 202 | the behaviour for a default inetnum (range) query is: 203 | do an exact match; 204 | if it fails, do an exless match on the encompassing prefix 205 | for routes(prefixes): 206 | do an exless match 207 | 208 | So if it's the default search mode on an inetnum tree, 209 | and the key is a range, 210 | then an exact search is performed on one of the composing prefixes. 211 | 212 | Then the resulting data leaves are checked for exact matching with 213 | the range queried for. 214 | Any dataleaves that do not match are discarded, and if none are left, 215 | the procedure falls back to searching for the encompassing prefix. 216 | (calculated in the smart_conv routine). 217 | Add the dataleaf copies to the list of answers, 218 | taking span into account 219 | +*/ 220 | static 221 | er_ret_t 222 | rp_asc_process_datlist( 223 | rx_srch_mt search_mode, 224 | int par_a, 225 | rx_fam_t fam_id, 226 | int prefnumber, 227 | GList **datlist, 228 | ip_range_t *testrang, 229 | int *hits 230 | ) 231 | { 232 | ip_rangesize_t min_span=0, span; 233 | int use_span = 0; 234 | int max_pref = -1; 235 | GList *ditem, *newitem; 236 | GHashTable *lohash = g_hash_table_new(NULL, NULL); 237 | 238 | /* in MORE and LESS(1) search exact match must not be displayed */ 239 | if ( search_mode == RX_SRCH_MORE 240 | || ( search_mode == RX_SRCH_LESS && par_a == 1 ) ) { 241 | rp_exclude_exact_match(datlist, testrang); 242 | } 243 | 244 | /* Preselection moved to processing, only span calculation done here * 245 | * 246 | 247 | EXLESS and LESS(1) search: the smallest span must be found, 248 | but if the less spec node is not the same for all composing prefixes, 249 | it means it's not really this one. 250 | 251 | we check that by the number of references to this node is less than 252 | the number of composing prefixes 253 | 254 | We do the same for the less specific search - a node must be less 255 | specific to all prefixes. 256 | 257 | if the number of references is not enough, then return no hits, 258 | another try will be made, this time with one, encompassing prefix. 259 | */ 260 | 261 | if ( (search_mode == RX_SRCH_EXLESS ) 262 | || ( search_mode == RX_SRCH_LESS && par_a == 1 ) ) { 263 | /* span works only for IP_V4. We use it only for inetnums, 264 | although RT/v4 would work too */ 265 | if( testrang->begin.space == IP_V4 && 266 | fam_id == RX_FAM_IN ) { 267 | min_span = rp_find_smallest_span(*datlist); 268 | use_span = 1; 269 | } 270 | else { 271 | /* in IPv6 and RT trees in general, we can obtain the same 272 | result by selecting the longest prefix */ 273 | max_pref = rp_find_longest_prefix(datlist); 274 | } 275 | } 276 | 277 | /* Process the dataleaf copies and add to the list of answers. */ 278 | ditem = g_list_first(*datlist); 279 | while(ditem != NULL) { 280 | rx_datref_t *refptr = (rx_datref_t *) (ditem->data); 281 | int exclude = 0; 282 | 283 | if(search_mode == RX_SRCH_EXLESS || search_mode == RX_SRCH_LESS ) { 284 | 285 | /* min_span defined <=> EXLESS or LESS(1) search of INETNUMS: 286 | the smallest span must be returned */ 287 | if( !exclude && use_span 288 | && (span = IP_rang_span( &refptr->leafptr->iprange))!=min_span) { 289 | ER_dbg_va(FAC_RP, ASP_RP_SRCH_DET, 290 | "process_datlist: (EX)LESS: discarded object with span %d", span); 291 | exclude = 1; 292 | } 293 | /* max_pref defined <=> EXLESS search of INETNUMS or LESS(1) of RT: 294 | */ 295 | if( !exclude && max_pref >= 0 296 | && refptr->leafptr->preflen < max_pref ) { 297 | ER_dbg_va(FAC_RP, ASP_RP_SRCH_DET, 298 | "process_datlist: (EX)LESS: discarded object with preflen %d", 299 | refptr->leafptr->preflen); 300 | exclude = 1; 301 | } 302 | 303 | /* number of occurences */ 304 | /* XXX this will go when the old algorithm goes */ 305 | if( !exclude 306 | && prefnumber > 1 ) { /* do not check if all will be approved */ 307 | 308 | if( rp_leaf_occ_inc(lohash, refptr->leafptr) < prefnumber ) { 309 | ER_dbg_va(FAC_RP, ASP_RP_SRCH_DET, 310 | "process_datlist: (EX)LESS: leafptr %x not enough",refptr->leafptr); 311 | exclude = 1; 312 | } 313 | else { 314 | ER_dbg_va(FAC_RP, ASP_RP_SRCH_DET, 315 | "process_datlist: (EX)LESS: leafptr %x GOOD enough",refptr->leafptr); 316 | } 317 | } 318 | } 319 | else if( search_mode == RX_SRCH_EXACT ) { 320 | /* EXACT search - discard if the range does not match */ 321 | if( memcmp( & refptr->leafptr->iprange, 322 | testrang, sizeof(ip_range_t)) != 0) { 323 | 324 | ER_dbg_va(FAC_RP, ASP_RP_SRCH_DET, 325 | "process_datlist: EXACT; discarded a mismatch"); 326 | exclude = 1; 327 | } /* EXACT match */ 328 | } 329 | else if( search_mode == RX_SRCH_MORE ) { 330 | /* MORE: exclude if not fully contained in the search term */ 331 | if( ! (IP_addr_in_rang(&refptr->leafptr->iprange.begin, testrang ) 332 | && IP_addr_in_rang(&refptr->leafptr->iprange.end, testrang ))) { 333 | ER_dbg_va(FAC_RP, ASP_RP_SRCH_DET, 334 | "process_datlist: MORE; discarded a not-fully contained one"); 335 | exclude = 1; 336 | } 337 | } 338 | 339 | 340 | /* get next item now, before the current gets deleted */ 341 | newitem = g_list_next(ditem); 342 | if( exclude ) { 343 | /* get rid of it */ 344 | rp_exclude_datlink(datlist, ditem); 345 | } 346 | else { 347 | /* OK, so we ACCEPT these results*/ 348 | /* uniqueness ensured in copy_results */ 349 | (*hits)++; 350 | } 351 | ditem = newitem; 352 | } /* while ditem */ 353 | 354 | /* wr_clear_list(&lolist); */ 355 | g_hash_table_destroy(lohash); 356 | return RX_OK; 357 | } 358 | 359 | /**************************************************************************/ 360 | 361 | /*+ appends the element pointed to by datref to finallist +*/ 362 | static 363 | er_ret_t 364 | rp_asc_append_datref(rx_datref_t *refptr, GList **finallist) 365 | { 366 | rx_datcpy_t *datcpy; 367 | void *dataptr; 368 | 369 | /* OK, so we ACCEPT this result. Copy it.*/ 370 | 371 | datcpy = (rx_datcpy_t *)UT_calloc(1, sizeof(rx_datcpy_t)); 372 | 373 | datcpy->leafcpy = *(refptr->leafptr); 374 | 375 | /* copy the immediate data too. Set the ptr.*/ 376 | 377 | dataptr = UT_calloc(1, refptr->leafptr->data_len); 378 | memcpy(dataptr, refptr->leafptr->data_ptr, refptr->leafptr->data_len); 379 | 380 | datcpy->leafcpy.data_ptr = dataptr; 381 | 382 | *finallist = g_list_prepend(*finallist, datcpy); 383 | 384 | /* XXX this wouldn't work in access_control */ 385 | ER_dbg_va(FAC_RP, ASP_RP_SRCH_DATA, 386 | "rp_asc_append 'ed: %s", dataptr); 387 | 388 | return RX_OK; 389 | } 390 | 391 | /*+ goes through datlist (list of references "datref") and add copies of 392 | leaves referenced to the finallist 393 | 394 | maintains its own uniqhash which holds pointers to copied dataleaves. 395 | 396 | modifies: finallist 397 | 398 | returns: error from wr_malloc 399 | 400 | +*/ 401 | static 402 | er_ret_t 403 | rp_srch_copyresults(GList *datlist, 404 | GList **finallist, 405 | int maxcount) 406 | { 407 | er_ret_t err; 408 | GList *ditem; 409 | GHashTable *uniqhash = g_hash_table_new(NULL, NULL); /* defaults */ 410 | int count = 0; 411 | 412 | ER_dbg_va(FAC_RP, ASP_RP_SRCH_DET, "srch_copyresults"); 413 | 414 | /* copy dataleaves pointed to by entries from the datlist 415 | only once (check uniqueness in the hash table) */ 416 | for(ditem = g_list_first(datlist); 417 | ditem != NULL; 418 | ditem = g_list_next(ditem)) { 419 | rx_datref_t *refptr = (rx_datref_t *) (ditem->data); 420 | rx_dataleaf_t *ansptr = refptr->leafptr; 421 | 422 | /* search for every ansptr (dataleaf pointer) in uniqhash */ 423 | if( g_hash_table_lookup(uniqhash, ansptr) == NULL ) { 424 | 425 | /* it's not known yet. OK: put it in the hash (value==key) */ 426 | g_hash_table_insert(uniqhash, ansptr, ansptr); 427 | 428 | /* and copy the dataleaf */ 429 | if( !NOERR(err = rp_asc_append_datref(refptr, finallist)) ) { 430 | return err; 431 | } 432 | } 433 | 434 | /* check the limit on number of objects if defined ( >0) */ 435 | count++; 436 | if( maxcount > 0 && count > maxcount ) { 437 | break; 438 | } 439 | 440 | } /* foreach (datlist) */ 441 | 442 | g_hash_table_destroy(uniqhash); /* elements are still linked to through datlist */ 443 | 444 | return RP_OK; 445 | } 446 | 447 | static 448 | void 449 | rp_begend_preselection(GList **datlist, rx_fam_t fam_id, ip_range_t *testrang) 450 | { 451 | GList *ditem, *newitem; 452 | 453 | ditem = g_list_first(*datlist); 454 | 455 | while( ditem != NULL ) { 456 | rx_datref_t *refptr = (rx_datref_t *) (ditem->data); 457 | newitem = g_list_next(ditem); 458 | 459 | /* the test is indentical for route & inetnum trees */ 460 | if( IP_addr_in_rang(&testrang->end, &refptr->leafptr->iprange) == 0 ) { 461 | 462 | ER_dbg_va(FAC_RP, ASP_RP_SRCH_DET, 463 | "process_datlist: discarded an uncovering leafptr %x", 464 | refptr->leafptr); 465 | rp_exclude_datlink(datlist, ditem); 466 | } 467 | ditem = newitem; 468 | } /* while */ 469 | } 470 | 471 | /*+++++++++++++++ 472 | search. 473 | 474 | 2 approaches: 475 | 476 | 1. (most modes): look up all less specifics of beginning and end of range, 477 | compare/select/etc. 478 | 479 | 2. More spec mode: break up the query range into prefixes, [erform a search 480 | for each of them. Add all results together. 481 | 482 | translates a query into a binary prefix (or prefixes, if range). 483 | for registry+space (or if they are zero, for all 484 | registries/spaces) 485 | finds tree 486 | calls RX_bin_search (returning node copies). 487 | will not put duplicate entries (composed inetnums). 488 | returns some sort of error code :-) 489 | 490 | Cuts the number of answers from RX_bin_search down to max_count, 491 | but since some of the answers may have been "normalized" in the 492 | underlying functions (multiple occurences removed), 493 | the result is _at_most_ max_count. 494 | 495 | appends to a given list of data blocks (not nodes!) 496 | 497 | The EXLESS search on inetnum tree should return the shortest range 498 | that was found, by means of comparing span (size) of the range. 499 | If there are more of size equal to the smallest one, they are also 500 | returned. 501 | 502 | returns RX_OK or a code from an underlying function 503 | ++++++++++++*/ 504 | er_ret_t 505 | RP_asc_search ( 506 | rx_srch_mt search_mode, 507 | int par_a, 508 | int par_b, 509 | char *key, /*+ search term: (string) prefix/range/IP +*/ 510 | rp_regid_t reg_id, 511 | rp_attr_t attr, /*+ extra tree id (within the same reg/spc/fam +*/ 512 | GList **finallist, /*+ answers go here, please +*/ 513 | int max_count /*+ max # of answers. RX_ALLANS == unlimited +*/ 514 | ) 515 | { 516 | GList *preflist = NULL; 517 | GList *datlist = NULL; 518 | er_ret_t err; 519 | ip_range_t testrang; 520 | int locked = 0; 521 | ip_keytype_t key_type; 522 | ip_space_t spc_id; 523 | rx_fam_t fam_id = RP_attr2fam( attr ); 524 | rx_tree_t *mytree; 525 | int hits=0; 526 | ip_prefix_t beginpref; 527 | 528 | 529 | /* abort on error (but unlock the tree) */ 530 | ER_dbg_va(FAC_RP, ASP_RP_SRCH_GEN, 531 | "RP_NEW_asc_search: query %s : mode %d (%s) (par %d) for %s", 532 | DF_get_attribute_name(attr), 533 | search_mode, RX_text_srch_mode(search_mode), par_a, key); 534 | 535 | 536 | /* parse the key into a prefix list */ 537 | if( ( err = IP_smart_conv(key, 0, 0, 538 | &preflist, IP_EXPN, &key_type)) != IP_OK ) { 539 | /* operational trouble (UT_*) or invalid key (IP_INVARG)*/ 540 | return err; 541 | } 542 | 543 | /* set the test values */ 544 | IP_smart_range(key, &testrang, IP_EXPN, &key_type); 545 | 546 | /* find the tree */ 547 | /* I took out the surrounding "if" because it is always taken when 548 | we get to this point, and it causes compiler warnings otherwise - shane */ 549 | /*if( NOERR(err) ) {*/ 550 | spc_id = IP_pref_b2_space( g_list_first(preflist)->data ); 551 | if( ! NOERR(err = RP_tree_get( &mytree, reg_id, spc_id, attr ))) { 552 | return err; 553 | } 554 | /*}*/ 555 | /* the point of no return: now we lock the tree. From here, even if errors 556 | occur, we still go through all procedure to unlock the tree at the end */ 557 | 558 | /* lock the tree */ 559 | TH_acquire_read_lockw( &(mytree->rwlock) ); 560 | locked = 1; 561 | 562 | /* Collection: this procedure is used for some search_modes only */ 563 | if( search_mode == RX_SRCH_EXLESS 564 | || search_mode == RX_SRCH_LESS 565 | || search_mode == RX_SRCH_EXACT ) { 566 | 567 | /* 1. compose a /32(/128) prefix for beginning of range */ 568 | beginpref.ip = testrang.begin; 569 | beginpref.bits = IP_sizebits(spc_id); 570 | 571 | /* 2. dataleaves collection: look up the beginning prefix in LESS(255) mode */ 572 | if( NOERR(err) ) { 573 | err = RX_bin_search( RX_SRCH_LESS, 255, 0, mytree, &beginpref, 574 | &datlist, RX_ANS_ALL); 575 | } 576 | 577 | /* 3. preselection: exclude those that do not include end of range 578 | */ 579 | if( NOERR(err) ) { 580 | rp_begend_preselection(&datlist, fam_id, &testrang); 581 | } 582 | 583 | } /* if exless|less|exact */ 584 | else { 585 | /* MORE */ 586 | 587 | /* standard collection using the traditional method: 588 | repeat the search for all prefixes and join results */ 589 | 590 | if( NOERR(err) ) { 591 | err = rp_preflist_search ( search_mode, par_a, par_b, 592 | mytree, &preflist, &datlist); 593 | } 594 | } /* collection */ 595 | 596 | ER_dbg_va(FAC_RP, ASP_RP_SRCH_GEN, 597 | "RP_NEW_asc_search: collected %d references ", 598 | g_list_length(datlist)); 599 | 600 | 601 | /* 5. processing - using the same processing function */ 602 | if( NOERR(err) ) { 603 | err = rp_asc_process_datlist( search_mode, par_a, fam_id, 604 | 1, /* one occurence is enough */ 605 | &datlist, 606 | &testrang, &hits ); 607 | } 608 | 609 | /* 6. copy results */ 610 | if( NOERR(err) ) { 611 | err = rp_srch_copyresults(datlist, finallist, max_count); /* and uniq */ 612 | } 613 | 614 | if( locked ) { 615 | /* 100. unlock the tree */ 616 | TH_release_read_lockw( &(mytree->rwlock) ); 617 | } 618 | 619 | /* clean up */ 620 | wr_clear_list( &preflist ); 621 | wr_clear_list( &datlist ); 622 | 623 | /* NOTE if error occured, finallist may be partly filled in. */ 624 | return err; 625 | } 626 |