## modules/rp/rp_search.c

/* [<][>][^][v][top][bottom][index][help] */

## DEFINITIONS

This source file includes following functions.

1. rp_exclude_datlink
2. rp_preflist_search
3. rp_find_smallest_span
4. rp_leaf_occ_inc
5. rp_exclude_exact_match
6. rp_find_longest_prefix
7. rp_asc_process_datlist
8. rp_asc_append_datref
9. rp_srch_copyresults
10. rp_begend_preselection
11. RP_asc_search

       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)
         /* [<][>][^][v][top][bottom][index][help] */
      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 (
         /* [<][>][^][v][top][bottom][index][help] */
      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 ) {
         /* [<][>][^][v][top][bottom][index][help] */
     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)
         /* [<][>][^][v][top][bottom][index][help] */
     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)
         /* [<][>][^][v][top][bottom][index][help] */
     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)
         /* [<][>][^][v][top][bottom][index][help] */
     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(
         /* [<][>][^][v][top][bottom][index][help] */
     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)
         /* [<][>][^][v][top][bottom][index][help] */
     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,
         /* [<][>][^][v][top][bottom][index][help] */
     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)
         /* [<][>][^][v][top][bottom][index][help] */
     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 (
         /* [<][>][^][v][top][bottom][index][help] */
     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

/* [<][>][^][v][top][bottom][index][help] */