modules/rp/rp_search.c
/* [<][>][^][v][top][bottom][index][help] */
FUNCTIONS
This source file includes following functions.
- rp_exclude_datlink
- rp_preflist_search
- rp_find_smallest_span
- rp_leaf_occ_inc
- rp_exclude_exact_match
- rp_find_longest_prefix
- rp_asc_process_datlist
- rp_asc_append_datref
- rp_srch_copyresults
- rp_begend_preselection
- 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