1 /****************************************************************************
2 *
3 * Copyright (C) 2014-2021 Cisco and/or its affiliates. All rights reserved.
4 * Copyright (C) 2006-2013 Sourcefire, Inc.
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License Version 2 as
8 * published by the Free Software Foundation. You may not use, modify or
9 * distribute this program under any other version of the GNU General
10 * Public License.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 *
21 ****************************************************************************/
22
23 /*
24 * @file sfrt.c
25 * @author Adam Keeton <akeeton@sourcefire.com>
26 * @date Thu July 20 10:16:26 EDT 2006
27 *
28 * Route implements two different routing table lookup mechanisms. The table
29 * lookups have been adapted to return a void pointer so any information can
30 * be associated with each CIDR block.
31 *
32 * As of this writing, the two methods used are Stefan Nilsson and Gunnar
33 * Karlsson's LC-trie, and a multibit-trie method similar to Gupta et-al.'s
34 * DIR-n-m. Presently, the LC-trie is used primarily for testing purposes as
35 * the current implementation does not allow for fast dynamic inserts.
36 *
37 * The intended use is for a user to optionally specify large IP blocks and
38 * then more specific information will be written into the routing tables
39 * from RNA. Ideally, information will only move from less specific to more
40 * specific. If a more general information is to overwrite existing entries,
41 * the table should be free'ed and rebuilt.
42 *
43 *
44 * Implementation:
45 *
46 * The routing tables associate an index into a "data" table with each CIDR.
47 * Each entry in the data table stores a pointer to actual data. This
48 * implementation was chosen so each routing entry only needs one word to
49 * either index the data array, or point to another table.
50 *
51 * Inserts are performed by specifying a CIDR and a pointer to its associated
52 * data. Since a new routing table entry may overwrite previous entries,
53 * a flag selects whether the insert favors the most recent or favors the most
54 * specific. Favoring most specific should be the default behvior. If
55 * the user wishes to overwrite routing entries with more general data, the
56 * table should be flushed, rather than using favor-most-recent.
57 *
58 * Before modifying the routing or data tables, the insert function performs a
59 * lookup on the CIDR-to-be-insertted. If no entry or an entry *of differing
60 * bit length* is found, the data is insertted into the data table, and its
61 * index is used for the new routing table entry. If an entry is found that
62 * is as specific as the new CIDR, the index stored points to where the new
63 * data is written into the data table.
64 *
65 * If more specific CIDR blocks overwrote the data table, then the more
66 * general routing table entries that were not overwritten will be referencing
67 * the wrong data. Alternatively, less specific entries can only overwrite
68 * existing routing table entries if favor-most-recent inserts are used.
69 *
70 * Because there is no quick way to clean the data-table if a user wishes to
71 * use a favor-most-recent insert for more general data, the user should flush
72 * the table with sfrt_free and create one anew. Alternatively, a small
73 * memory leak occurs with the data table, as it will be storing pointers that
74 * no routing table entry cares about.
75 *
76 *
77 * The API calls that should be used are:
78 * sfrt_new - create new table
79 * sfrt_insert - insert entry
80 * sfrt_lookup - lookup entry
81 * sfrt_free - free table
82 */
83
84 #ifdef HAVE_CONFIG_H
85 #include "config.h"
86 #endif
87
88 #include "sf_types.h"
89 #include "sfrt.h"
90
91 char *rt_error_messages[] =
92 {
93 "Success",
94 "Insert Failure",
95 "Policy Table Exceeded",
96 "Dir Insert Failure",
97 "Dir Lookup Failure",
98 "Memory Allocation Failure"
99 #ifdef SUPPORT_LCTRIE
100 ,
101 "LC Trie Compile Failure",
102 "LC Trie Insert Failure",
103 "LC Trie Lookup Failure"
104 #endif
105 };
106
107 static inline int allocateTableIndex(table_t *table);
108
109 /* Create new lookup table
110 * @param table_type Type of table. Uses the types enumeration in route.h
111 * @param ip_type IPv4 or IPv6. Uses the types enumeration in route.h
112 * @param data_size Max number of unique data entries
113 *
114 * Returns the new table. */
sfrt_new(char table_type,char ip_type,long data_size,uint32_t mem_cap)115 table_t *sfrt_new(char table_type, char ip_type, long data_size, uint32_t mem_cap)
116 {
117 table_t *table = (table_t*)malloc(sizeof(table_t));
118
119 if(!table)
120 {
121 return NULL;
122 }
123
124
125 /* If this limit is exceeded, there will be no way to distinguish
126 * between pointers and indeces into the data table. Only
127 * applies to DIR-n-m. */
128 #ifdef SUPPORT_LCTRIE
129 #if SIZEOF_LONG_INT == 8
130 if(data_size >= 0x800000000000000 && table_type == LCT)
131 #else
132 if(data_size >= 0x8000000 && table_type != LCT)
133 #endif
134 #else /* SUPPORT_LCTRIE */
135 #if SIZEOF_LONG_INT == 8
136 if(data_size >= 0x800000000000000)
137 #else
138 if(data_size >= 0x8000000)
139 #endif
140 #endif
141 {
142 free(table);
143 return NULL;
144 }
145
146 /* mem_cap is specified in megabytes, but internally uses bytes. Convert */
147 mem_cap *= 1024*1024;
148
149 /* Maximum allowable number of stored entries */
150 table->max_size = data_size;
151 table->lastAllocatedIndex = 0;
152
153 table->data = (GENERIC*)calloc(sizeof(GENERIC) * table->max_size, 1);
154
155 if(!table->data)
156 {
157 free(table);
158 return NULL;
159 }
160
161 table->allocated = sizeof(table_t) + sizeof(GENERIC) * table->max_size;
162
163 table->ip_type = ip_type;
164 table->table_type = table_type;
165
166 /* This will point to the actual table lookup algorithm */
167 table->rt = NULL;
168 table->rt6 = NULL;
169
170 /* index 0 will be used for failed lookups, so set this to 1 */
171 table->num_ent = 1;
172
173 switch(table_type)
174 {
175 #ifdef SUPPORT_LCTRIE
176 /* Setup LC-trie table */
177 case LCT:
178 /* LC trie is presently not allowed */
179 table->insert = sfrt_lct_insert;
180 table->lookup = sfrt_lct_lookup;
181 table->free = sfrt_lct_free;
182 table->usage = sfrt_lct_usage;
183 table->print = NULL;
184 table->remove = NULL;
185
186 table->rt = sfrt_lct_new(data_size);
187 free(table->data);
188 free(table);
189 return NULL;
190
191 break;
192 #endif
193 /* Setup DIR-n-m table */
194 case DIR_24_8:
195 case DIR_16x2:
196 case DIR_16_8x2:
197 case DIR_16_4x4:
198 case DIR_8x4:
199 case DIR_4x8:
200 case DIR_2x16:
201 case DIR_16_4x4_16x5_4x4:
202 case DIR_16x7_4x4:
203 case DIR_16x8:
204 case DIR_8x16:
205 table->insert = sfrt_dir_insert;
206 table->lookup = sfrt_dir_lookup;
207 table->free = sfrt_dir_free;
208 table->usage = sfrt_dir_usage;
209 table->print = sfrt_dir_print;
210 table->remove = sfrt_dir_remove;
211
212 break;
213
214 default:
215 free(table->data);
216 free(table);
217 return NULL;
218 };
219
220 /* Allocate the user-specified DIR-n-m table */
221 switch(table_type)
222 {
223 case DIR_24_8:
224 table->rt = sfrt_dir_new(mem_cap, 2, 24,8);
225 break;
226 case DIR_16x2:
227 table->rt = sfrt_dir_new(mem_cap, 2, 16,16);
228 break;
229 case DIR_16_8x2:
230 table->rt = sfrt_dir_new(mem_cap, 3, 16,8,8);
231 break;
232 case DIR_16_4x4:
233 table->rt = sfrt_dir_new(mem_cap, 5, 16,4,4,4,4);
234 break;
235 case DIR_8x4:
236 table->rt = sfrt_dir_new(mem_cap, 4, 8,8,8,8);
237 break;
238 /* There is no reason to use 4x8 except for benchmarking and
239 * comparison purposes. */
240 case DIR_4x8:
241 table->rt = sfrt_dir_new(mem_cap, 8, 4,4,4,4,4,4,4,4);
242 break;
243 /* There is no reason to use 2x16 except for benchmarking and
244 * comparison purposes. */
245 case DIR_2x16:
246 table->rt = sfrt_dir_new(mem_cap, 16,
247 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2);
248 break;
249 case DIR_16_4x4_16x5_4x4:
250 table->rt = sfrt_dir_new(mem_cap, 5, 16,4,4,4,4);
251 table->rt6 = sfrt_dir_new(mem_cap, 14, 16,4,4,4,4,16,16,16,16,16,4,4,4,4);
252 break;
253 case DIR_16x7_4x4:
254 table->rt = sfrt_dir_new(mem_cap, 5, 16,4,4,4,4);
255 table->rt6 = sfrt_dir_new(mem_cap, 11, 16,16,16,16,16,16,16,4,4,4,4);
256 break;
257 case DIR_16x8:
258 table->rt = sfrt_dir_new(mem_cap, 2, 16,16);
259 table->rt6 = sfrt_dir_new(mem_cap, 8, 16,16,16,16,16,16,16,16);
260 break;
261 case DIR_8x16:
262 table->rt = sfrt_dir_new(mem_cap, 4, 16,8,4,4);
263 table->rt6 = sfrt_dir_new(mem_cap, 16,
264 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8);
265 break;
266 };
267
268 if((!table->rt) || (!table->rt6))
269 {
270 if (table->rt)
271 table->free( table->rt );
272 if (table->rt6)
273 table->free( table->rt6 );
274 free(table->data);
275 free(table);
276 return NULL;
277 }
278
279 return table;
280 }
281
282 /* Free lookup table */
sfrt_free(table_t * table)283 void sfrt_free(table_t *table)
284 {
285 if(!table)
286 {
287 /* What are you calling me for? */
288 return;
289 }
290
291 if(!table->data)
292 {
293 /* This really really should not have happened */
294 }
295 else
296 {
297 free(table->data);
298 }
299
300 if(!table->rt)
301 {
302 /* This should not have happened either */
303 }
304 else
305 {
306 table->free( table->rt );
307 }
308
309 if(!table->rt6)
310 {
311 /* This should not have happened either */
312 }
313 else
314 {
315 table->free( table->rt6 );
316 }
317
318 free(table);
319 }
320
321 /* Perform a lookup on value contained in "ip" */
sfrt_lookup(sfaddr_t * ip,table_t * table)322 GENERIC sfrt_lookup(sfaddr_t* ip, table_t* table)
323 {
324 tuple_t tuple;
325 uint32_t* adr;
326 int numAdrDwords;
327 void *rt;
328
329 if(!ip)
330 {
331 return NULL;
332 }
333
334 if(!table || !table->lookup)
335 {
336 return NULL;
337 }
338
339 if (sfaddr_family(ip) == AF_INET)
340 {
341 adr = sfaddr_get_ip4_ptr(ip);
342 numAdrDwords = 1;
343 rt = table->rt;
344 }
345 else
346 {
347 adr = sfaddr_get_ip6_ptr(ip);
348 numAdrDwords = 4;
349 rt = table->rt6;
350 }
351
352 tuple = table->lookup(adr, numAdrDwords, rt);
353
354 if(tuple.index >= table->max_size)
355 {
356 return NULL;
357 }
358
359 return table->data[tuple.index];
360 }
361
sfrt_iterate(table_t * table,sfrt_iterator_callback userfunc)362 void sfrt_iterate(table_t* table, sfrt_iterator_callback userfunc)
363 {
364 uint32_t index, count;
365
366 if (!table)
367 return;
368
369 for (index = 0, count = 0;
370 index < table->max_size;
371 index++)
372 {
373 if (table->data[index])
374 {
375 userfunc(table->data[index]);
376 if (++count == table->num_ent) break;
377 }
378 }
379
380 return;
381 }
382
sfrt_iterate_with_snort_config(struct _SnortConfig * sc,table_t * table,sfrt_sc_iterator_callback userfunc)383 void sfrt_iterate_with_snort_config(struct _SnortConfig *sc, table_t* table, sfrt_sc_iterator_callback userfunc)
384 {
385 uint32_t index, count;
386
387 if (!table)
388 return;
389
390 for (index = 0, count = 0;
391 index < table->max_size;
392 index++)
393 {
394 if (table->data[index])
395 {
396 userfunc(sc, table->data[index]);
397 if (++count == table->num_ent) break;
398 }
399 }
400
401 return;
402 }
403
sfrt_iterate2(table_t * table,sfrt_iterator_callback3 userfunc)404 int sfrt_iterate2(table_t* table, sfrt_iterator_callback3 userfunc)
405 {
406 uint32_t index, count;
407 if (!table)
408 return 0;
409
410 for (index = 0, count = 0;
411 index < table->max_size;
412 index++)
413 {
414 if (table->data[index])
415 {
416 int ret = userfunc(table->data[index]);
417 if (ret != 0)
418 return ret;
419 if (++count == table->num_ent) break;
420 }
421 }
422
423 return 0;
424 }
425
sfrt_iterate2_with_snort_config(struct _SnortConfig * sc,table_t * table,sfrt_sc_iterator_callback3 userfunc)426 int sfrt_iterate2_with_snort_config(struct _SnortConfig *sc, table_t* table, sfrt_sc_iterator_callback3 userfunc)
427 {
428 uint32_t index, count;
429 if (!table)
430 return 0;
431
432 for (index = 0, count = 0;
433 index < table->max_size;
434 index++)
435 {
436 if (table->data[index])
437 {
438 int ret = userfunc(sc, table->data[index]);
439 if (ret != 0)
440 return ret;
441 if (++count == table->num_ent) break;
442 }
443 }
444
445 return 0;
446 }
447
sfrt_cleanup2(table_t * table,sfrt_iterator_callback2 cleanup_func,void * data)448 void sfrt_cleanup2(
449 table_t* table,
450 sfrt_iterator_callback2 cleanup_func,
451 void *data
452 )
453 {
454 uint32_t index, count;
455 if (!table)
456 return;
457
458 for (index = 0, count = 0;
459 index < table->max_size;
460 index++)
461 {
462 if (table->data[index])
463 {
464 cleanup_func(table->data[index], data);
465
466 /* cleanup_func is supposed to free memory associated with this
467 * table->data[index]. Set that to NULL.
468 */
469 table->data[index] = NULL;
470 if (++count == table->num_ent) break;
471 }
472 }
473 }
474
sfrt_cleanup(table_t * table,sfrt_iterator_callback cleanup_func)475 void sfrt_cleanup(table_t* table, sfrt_iterator_callback cleanup_func)
476 {
477 uint32_t index, count;
478
479 if (!table)
480 return;
481
482 for (index = 0, count = 0;
483 index < table->max_size;
484 index++)
485 {
486 if (table->data[index])
487 {
488 cleanup_func(table->data[index]);
489
490 /* cleanup_func is supposed to free memory associated with this
491 * table->data[index]. Set that to NULL.
492 */
493 table->data[index] = NULL;
494
495 if (++count == table->num_ent) break;
496 }
497 }
498
499 return;
500 }
501
sfrt_search(sfaddr_t * ip,table_t * table)502 GENERIC sfrt_search(sfaddr_t* ip, table_t *table)
503 {
504 uint32_t* adr;
505 int numAdrDwords;
506 tuple_t tuple;
507 void *rt = NULL;
508
509 if ((ip == NULL) || (table == NULL))
510 return NULL;
511
512 if (sfaddr_family(ip) == AF_INET)
513 {
514 adr = sfaddr_get_ip4_ptr(ip);
515 numAdrDwords = 1;
516 rt = table->rt;
517 }
518 else
519 {
520 adr = sfaddr_get_ip6_ptr(ip);
521 numAdrDwords = 4;
522 rt = table->rt6;
523 }
524
525 tuple = table->lookup(adr, numAdrDwords, rt);
526
527 if(tuple.index >= table->max_size)
528 return NULL;
529
530 return table->data[tuple.index];
531 }
532
533 /* Insert "ip", of length "len", into "table", and have it point to "ptr" */
534 /* Insert "ip", of length "len", into "table", and have it point to "ptr" */
sfrt_insert(sfcidr_t * ip,unsigned char len,GENERIC ptr,int behavior,table_t * table)535 int sfrt_insert(sfcidr_t* ip, unsigned char len, GENERIC ptr,
536 int behavior, table_t *table)
537 {
538 int index;
539 int newIndex = 0;
540 int res;
541 uint32_t* adr;
542 int numAdrDwords;
543 tuple_t tuple;
544 void *rt = NULL;
545
546 if(!ip)
547 {
548 return RT_INSERT_FAILURE;
549 }
550
551 if (len == 0)
552 return RT_INSERT_FAILURE;
553
554 if(!table || !table->insert || !table->data || !table->lookup)
555 {
556 return RT_INSERT_FAILURE;
557 }
558
559 if (len > 128)
560 {
561 return RT_INSERT_FAILURE;
562 }
563
564 /* Check if we can reuse an existing data table entry by
565 * seeing if there is an existing entry with the same length. */
566 /* Only perform this if the table is not an LC-trie */
567 #ifdef SUPPORT_LCTRIE
568 if(table->table_type != LCT)
569 {
570 #endif
571
572 if (sfaddr_family(&ip->addr) == AF_INET)
573 {
574 if (len < 96)
575 {
576 return RT_INSERT_FAILURE;
577 }
578 len -= 96;
579 adr = sfip_get_ip4_ptr(ip);
580 numAdrDwords = 1;
581 rt = table->rt;
582 }
583 else
584 {
585 adr = sfip_get_ip6_ptr(ip);
586 numAdrDwords = 4;
587 rt = table->rt6;
588 }
589 if (!rt)
590 {
591 return RT_INSERT_FAILURE;
592 }
593
594 tuple = table->lookup(adr, numAdrDwords, rt);
595
596 #ifdef SUPPORT_LCTRIE
597 }
598 #endif
599
600 #ifdef SUPPORT_LCTRIE
601 if(table->table_type == LCT || tuple.length != len)
602 {
603 #else
604 if(tuple.length != len)
605 {
606 #endif
607 if( table->num_ent >= table->max_size)
608 {
609 return RT_POLICY_TABLE_EXCEEDED;
610 }
611
612 index = newIndex = allocateTableIndex(table);
613 if (!index)
614 return RT_POLICY_TABLE_EXCEEDED;
615 }
616 else
617 {
618 index = tuple.index;
619 }
620
621 /* The actual value that is looked-up is an index
622 * into the data table. */
623 res = table->insert(adr, numAdrDwords, len, index, behavior, rt);
624
625 if ((res == RT_SUCCESS) && newIndex)
626 {
627 table->num_ent++;
628 table->data[ index ] = ptr;
629 }
630
631 return res;
632 }
633 /** Pretty print table
634 * Pretty print sfrt table.
635 * @param table - routing table.
636 */
637 void sfrt_print(table_t *table)
638 {
639 if(!table || !table->print )
640 {
641 return;
642 }
643
644 if (table->rt)
645 table->print(table->rt);
646 if (table->rt6)
647 table->print(table->rt6);
648 }
649
650 uint32_t sfrt_num_entries(table_t *table)
651 {
652 if(!table || !table->rt || !table->allocated)
653 {
654 return 0;
655 }
656
657 /* There is always a root node, so subtract 1 for it */
658 return table->num_ent - 1;
659 }
660
661 uint32_t sfrt_usage(table_t *table)
662 {
663 uint32_t usage;
664 if(!table || !table->rt || !table->allocated || !table->usage)
665 {
666 return 0;
667 }
668
669 usage = table->allocated + table->usage( table->rt );
670
671 if (table->rt6)
672 {
673 usage += table->usage( table->rt6 );
674 }
675
676 return usage;
677 }
678
679 /** Remove subnet from sfrt table.
680 * Remove subnet identified by ip/len and return associated data.
681 * @param ip - IP address
682 * @param len - length of netmask
683 * @param ptr - void ** that is set to value associated with subnet
684 * @param behavior - RT_FAVOR_SPECIFIC or RT_FAVOR_TIME
685 * @note - For RT_FAVOR_TIME behavior, if partial subnet is removed then table->data[x] is nulled. Any remaining entries
686 * will then point to null data. This can cause hung or crosslinked data. RT_FAVOR_SPECIFIC does not have this drawback.
687 * hung or crosslinked entries.
688 */
689 int sfrt_remove(sfcidr_t* ip, unsigned char len, GENERIC *ptr,
690 int behavior, table_t *table)
691 {
692 int index;
693 uint32_t* adr;
694 int numAdrDwords;
695 void *rt = NULL;
696
697 if(!ip)
698 {
699 return RT_REMOVE_FAILURE;
700 }
701
702 if (len == 0)
703 return RT_REMOVE_FAILURE;
704
705 if(!table || !table->data || !table->remove || !table->lookup )
706 {
707 //remove operation will fail for LCT since this operation is not implemented
708 return RT_REMOVE_FAILURE;
709 }
710
711 if (len > 128)
712 {
713 return RT_REMOVE_FAILURE;
714 }
715
716 #ifdef SUPPORT_LCTRIE
717 if(table->table_type != LCT)
718 {
719 #endif
720
721 if (sfaddr_family(&ip->addr) == AF_INET)
722 {
723 if (len < 96)
724 {
725 return RT_REMOVE_FAILURE;
726 }
727 len -= 96;
728 adr = sfip_get_ip4_ptr(ip);
729 numAdrDwords = 1;
730 rt = table->rt;
731 }
732 else
733 {
734 adr = sfip_get_ip6_ptr(ip);
735 numAdrDwords = 4;
736 rt = table->rt6;
737 }
738
739 #ifdef SUPPORT_LCTRIE
740 }
741 #endif
742
743 /* The actual value that is looked-up is an index
744 * into the data table. */
745 index = table->remove(adr, numAdrDwords, len, behavior, rt);
746
747 /* Remove value into policy table. See TBD in function header*/
748 if (index)
749 {
750 *ptr = table->data[ index ];
751 table->data[ index ] = NULL;
752 table->num_ent--;
753 }
754
755 return RT_SUCCESS;
756 }
757
758 /**allocate first unused index value. With delete operation, index values can be non-contiguous.
759 * Index 0 is error in this function but this is valid entry in table->data that is used
760 * for failure case. Calling function must check for 0 and take appropriate error action.
761 */
762 static inline int allocateTableIndex(table_t *table)
763 {
764 uint32_t index;
765
766 //0 is special index for failed entries.
767 for (index = table->lastAllocatedIndex+1;
768 index != table->lastAllocatedIndex;
769 index = (index+1) % table->max_size)
770 {
771 if (index && !table->data[index])
772 {
773 table->lastAllocatedIndex = index;
774 return index;
775 }
776 }
777 return 0;
778 }
779
780 #ifdef DEBUG_SFRT
781
782 #define NUM_IPS 32
783 #define NUM_DATA 4
784
785 int main()
786 {
787 table_t *dir;
788 uint32_t ip_list[NUM_IPS]; /* entirely arbitrary */
789 char data[NUM_DATA]; /* also entirely arbitrary */
790 uint32_t index, val;
791
792 for(index=0; index<NUM_IPS; index++)
793 {
794 ip_list[index] = (uint32_t)rand()%NUM_IPS;
795 data[index%NUM_DATA] = index%26 + 65; /* Random letter */
796 }
797
798 dir = sfrt_new(DIR_16x2, IPv4, NUM_IPS, 20);
799
800 if(!dir)
801 {
802 printf("Failed to create DIR\n");
803 return 1;
804 }
805
806 for(index=0; index < NUM_IPS; index++)
807 {
808 if(sfrt_insert(&ip_list[index], 32, &data[index%NUM_DATA],
809 RT_FAVOR_SPECIFIC, dir) != RT_SUCCESS)
810 {
811 printf("DIR Insertion failure\n");
812 return 1;
813 }
814
815 printf("%d\t %x: %c -> %c\n", index, ip_list[index],
816 data[index%NUM_DATA], *(uint32_t*)sfrt_lookup(&ip_list[index], dir));
817
818 }
819
820 for(index=0; index < NUM_IPS; index++)
821 {
822 val = *(uint32_t*)sfrt_lookup(&ip_list[index], dir);
823 printf("\t@%d\t%x: %c. originally:\t%c\n",
824 index, ip_list[index], val, data[index%NUM_DATA]);
825 }
826
827 printf("Usage: %d bytes\n", ((dir_table_t*)(dir->rt))->allocated);
828
829 sfrt_free(dir);
830 return 0;
831 }
832
833 #endif /* DEBUG_SFRT */
834
835