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