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