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 |