/**************************************************************************** * * Copyright (C) 2006-2010 Sourcefire, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License Version 2 as * published by the Free Software Foundation. You may not use, modify or * distribute this program under any other version of the GNU General * Public License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ****************************************************************************/ /* * @file sfrt.c * @author Adam Keeton * @date Thu July 20 10:16:26 EDT 2006 * * Route implements two different routing table lookup mechanisms. The table * lookups have been adapted to return a void pointer so any information can * be associated with each CIDR block. * * As of this writing, the two methods used are Stefan Nilsson and Gunnar * Karlsson's LC-trie, and a multibit-trie method similar to Gupta et-al.'s * DIR-n-m. Presently, the LC-trie is used primarily for testing purposes as * the current implementation does not allow for fast dynamic inserts. * * The intended use is for a user to optionally specify large IP blocks and * then more specific information will be written into the routing tables * from RNA. Ideally, information will only move from less specific to more * specific. If a more general information is to overwrite existing entries, * the table should be free'ed and rebuilt. * * * Implementation: * * The routing tables associate an index into a "data" table with each CIDR. * Each entry in the data table stores a pointer to actual data. This * implementation was chosen so each routing entry only needs one word to * either index the data array, or point to another table. * * Inserts are performed by specifying a CIDR and a pointer to its associated * data. Since a new routing table entry may overwrite previous entries, * a flag selects whether the insert favors the most recent or favors the most * specific. Favoring most specific should be the default behvior. If * the user wishes to overwrite routing entries with more general data, the * table should be flushed, rather than using favor-most-recent. * * Before modifying the routing or data tables, the insert function performs a * lookup on the CIDR-to-be-insertted. If no entry or an entry *of differing * bit length* is found, the data is insertted into the data table, and its * index is used for the new routing table entry. If an entry is found that * is as specific as the new CIDR, the index stored points to where the new * data is written into the data table. * * If more specific CIDR blocks overwrote the data table, then the more * general routing table entries that were not overwritten will be referencing * the wrong data. Alternatively, less specific entries can only overwrite * existing routing table entries if favor-most-recent inserts are used. * * Because there is no quick way to clean the data-table if a user wishes to * use a favor-most-recent insert for more general data, the user should flush * the table with sfrt_free and create one anew. Alternatively, a small * memory leak occurs with the data table, as it will be storing pointers that * no routing table entry cares about. * * * The API calls that should be used are: * sfrt_new - create new table * sfrt_insert - insert entry * sfrt_lookup - lookup entry * sfrt_free - free table */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "sfrt.h" char *rt_error_messages[] = { "Success", "Insert Failure", "Policy Table Exceeded", "Dir Insert Failure", "Dir Lookup Failure", "Memory Allocation Failure" #ifdef SUPPORT_LCTRIE , "LC Trie Compile Failure", "LC Trie Insert Failure", "LC Trie Lookup Failure" #endif }; /* Create new lookup table * @param table_type Type of table. Uses the types enumeration in route.h * @param ip_type IPv4 or IPv6. Uses the types enumeration in route.h * @param data_size Max number of unique data entries * * Returns the new table. */ table_t *sfrt_new(char table_type, char ip_type, long data_size, uint32_t mem_cap) { table_t *table = (table_t*)malloc(sizeof(table_t)); if(!table) { return NULL; } #ifndef SUP_IP6 /* IPv6 is not supported */ if(ip_type == IPv6) { free(table); return NULL; } #endif /* If this limit is exceeded, there will be no way to distinguish * between pointers and indeces into the data table. Only * applies to DIR-n-m. */ #ifdef SUPPORT_LCTRIE #if SIZEOF_LONG_INT == 8 if(data_size >= 0x800000000000000 && table_type == LCT) #else if(data_size >= 0x8000000 && table_type != LCT) #endif #else /* SUPPORT_LCTRIE */ #if SIZEOF_LONG_INT == 8 if(data_size >= 0x800000000000000) #else if(data_size >= 0x8000000) #endif #endif { free(table); return NULL; } /* mem_cap is specified in megabytes, but internally uses bytes. Convert */ mem_cap *= 1024*1024; /* Maximum allowable number of stored entries */ table->max_size = data_size; table->data = (GENERIC*)calloc(sizeof(GENERIC) * table->max_size, 1); if(!table->data) { free(table); return NULL; } table->allocated = sizeof(table_t) + sizeof(GENERIC) * table->max_size; table->ip_type = ip_type; table->table_type = table_type; /* This will point to the actual table lookup algorithm */ table->rt = NULL; #ifdef SUP_IP6 table->rt6 = NULL; #endif /* index 0 will be used for failed lookups, so set this to 1 */ table->num_ent = 1; switch(table_type) { #ifdef SUPPORT_LCTRIE /* Setup LC-trie table */ case LCT: /* LC trie is presently not allowed */ table->insert = sfrt_lct_insert; table->lookup = sfrt_lct_lookup; table->free = sfrt_lct_free; table->usage = sfrt_lct_usage; table->rt = sfrt_lct_new(data_size); free(table->data); free(table); return NULL; break; #endif /* Setup DIR-n-m table */ case DIR_24_8: case DIR_16x2: case DIR_16_8x2: case DIR_16_4x4: case DIR_8x4: case DIR_4x8: case DIR_2x16: #ifdef SUP_IP6 case DIR_16_4x4_16x5_4x4: case DIR_16x7_4x4: case DIR_16x8: case DIR_8x16: #endif table->insert = sfrt_dir_insert; table->lookup = sfrt_dir_lookup; table->free = sfrt_dir_free; table->usage = sfrt_dir_usage; break; default: free(table->data); free(table); return NULL; }; /* Allocate the user-specified DIR-n-m table */ switch(table_type) { case DIR_24_8: table->rt = sfrt_dir_new(mem_cap, 2, 24,8); break; case DIR_16x2: table->rt = sfrt_dir_new(mem_cap, 2, 16,16); break; case DIR_16_8x2: table->rt = sfrt_dir_new(mem_cap, 3, 16,8,8); break; case DIR_16_4x4: table->rt = sfrt_dir_new(mem_cap, 5, 16,4,4,4,4); break; case DIR_8x4: table->rt = sfrt_dir_new(mem_cap, 4, 8,8,8,8); break; /* There is no reason to use 4x8 except for benchmarking and * comparison purposes. */ case DIR_4x8: table->rt = sfrt_dir_new(mem_cap, 8, 4,4,4,4,4,4,4,4); break; /* There is no reason to use 2x16 except for benchmarking and * comparison purposes. */ case DIR_2x16: table->rt = sfrt_dir_new(mem_cap, 16, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2); break; #ifdef SUP_IP6 case DIR_16_4x4_16x5_4x4: table->rt = sfrt_dir_new(mem_cap, 5, 16,4,4,4,4); table->rt6 = sfrt_dir_new(mem_cap, 14, 16,4,4,4,4,16,16,16,16,16,4,4,4,4); break; case DIR_16x7_4x4: table->rt = sfrt_dir_new(mem_cap, 5, 16,4,4,4,4); table->rt6 = sfrt_dir_new(mem_cap, 11, 16,16,16,16,16,16,16,4,4,4,4); break; case DIR_16x8: table->rt = sfrt_dir_new(mem_cap, 2, 16,16); table->rt6 = sfrt_dir_new(mem_cap, 8, 16,16,16,16,16,16,16,16); break; case DIR_8x16: table->rt = sfrt_dir_new(mem_cap, 4, 8,8,8,8); table->rt6 = sfrt_dir_new(mem_cap, 16, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8); break; #endif }; if(!table->rt) { free(table->data); free(table); return NULL; } #ifdef SUP_IP6 if (!table->rt6) { table->free( table->rt ); free(table->data); free(table); } #endif return table; } /* Free lookup table */ void sfrt_free(table_t *table) { if(!table) { /* What are you calling me for? */ return; } if(!table->data) { /* This really really should not have happened */ } else { free(table->data); } if(!table->rt) { /* This should not have happened either */ } else { table->free( table->rt ); } #ifdef SUP_IP6 if(!table->rt6) { /* This should not have happened either */ } else { table->free( table->rt6 ); } #endif free(table); } /* Perform a lookup on value contained in "ip" */ GENERIC sfrt_lookup(void *adr, table_t* table) { tuple_t tuple; #ifdef SUP_IP6 sfip_t *ip; #else uint32_t ip; #endif void *rt = NULL; if(!adr) { return NULL; } if(!table || !table->lookup) { return NULL; } #ifdef SUP_IP6 ip = adr; if (ip->family == AF_INET) { rt = table->rt; } else if (ip->family == AF_INET6) { rt = table->rt6; } #else /* IPv6 not yet supported */ if(table->ip_type == IPv6) { return NULL; } ip = *(uint32_t*)adr; rt = table->rt; #endif if (!rt) { return NULL; } tuple = table->lookup(ip, rt); if(tuple.index >= table->num_ent) { return NULL; } return table->data[tuple.index]; } void sfrt_iterate(table_t* table, sfrt_iterator_callback userfunc) { uint32_t index; if (!table) return; for (index = 0; index < table->num_ent; index++) { if (table->data[index]) userfunc(table->data[index]); } return; } int sfrt_iterate2(table_t* table, sfrt_iterator_callback3 userfunc) { uint32_t index; if (!table) return 0; for (index = 0; index < table->num_ent; index++) { if (table->data[index]) { int ret = userfunc(table->data[index]); if (ret != 0) return ret; } } return 0; } void sfrt_cleanup2( table_t* table, sfrt_iterator_callback2 cleanup_func, void *data ) { uint32_t index; if (!table) return; for (index = 0; index < table->num_ent; index++) { if (table->data[index]) cleanup_func(table->data[index], data); /* cleanup_func is supposed to free memory associated with this * table->data[index]. Set that to NULL. */ table->data[index] = NULL; } } void sfrt_cleanup(table_t* table, sfrt_iterator_callback cleanup_func) { uint32_t index; if (!table) return; for (index = 0; index < table->num_ent; index++) { if (table->data[index]) cleanup_func(table->data[index]); /* cleanup_func is supposed to free memory associated with this * table->data[index]. Set that to NULL. */ table->data[index] = NULL; } return; } GENERIC sfrt_search(void *adr, unsigned char len, table_t *table) { #ifdef SUP_IP6 sfip_t *ip; #else uint32_t ip; #endif tuple_t tuple; void *rt = NULL; if ((adr == NULL) || (table == NULL) || (len == 0)) return NULL; #ifdef SUP_IP6 ip = adr; if (ip->family == AF_INET) { rt = table->rt; } else if (ip->family == AF_INET6) { rt = table->rt6; } #else /* IPv6 not yet supported */ if(table->ip_type == IPv6) { return NULL; } ip = *(uint32_t*)adr; rt = table->rt; #endif /* IPv6 not yet supported */ if (table->ip_type == IPv6) return NULL; if( (table->ip_type == IPv4 && len > 32) || (table->ip_type == IPv6 && len > 128) ) { return NULL; } #ifdef SUP_IP6 ip = adr; #else ip = *(uint32_t*)adr; #endif tuple = table->lookup(ip, rt); if (tuple.length != len) return NULL; return table->data[tuple.index]; } /* Insert "ip", of length "len", into "table", and have it point to "ptr" */ /* Insert "ip", of length "len", into "table", and have it point to "ptr" */ int sfrt_insert(void *adr, unsigned char len, GENERIC ptr, int behavior, table_t *table) { int index; int res; #ifdef SUP_IP6 sfip_t *ip; #else uint32_t ip; #endif tuple_t tuple; void *rt = NULL; if(!adr) { return RT_INSERT_FAILURE; } if (len == 0) return RT_INSERT_FAILURE; if(!table || !table->insert || !table->data || !table->lookup) { return RT_INSERT_FAILURE; } if( (table->ip_type == IPv4 && len > 32) || (table->ip_type == IPv6 && len > 128) ) { return RT_INSERT_FAILURE; } #ifdef SUP_IP6 ip = adr; #else ip = *(uint32_t*)adr; #endif /* Check if we can reuse an existing data table entry by * seeing if there is an existing entry with the same length. */ /* Only perform this if the table is not an LC-trie */ #ifdef SUPPORT_LCTRIE if(table->table_type != LCT) { #endif #ifdef SUP_IP6 if (ip->family == AF_INET) { rt = table->rt; } else if (ip->family == AF_INET6) { rt = table->rt6; } #else rt = table->rt; #endif if (!rt) { return RT_INSERT_FAILURE; } tuple = table->lookup(ip, table->rt); #ifdef SUPPORT_LCTRIE } #endif #ifdef SUPPORT_LCTRIE if(table->table_type == LCT || tuple.length != len) { #else if(tuple.length != len) { #endif if( table->num_ent >= table->max_size) { return RT_POLICY_TABLE_EXCEEDED; } index = table->num_ent; table->num_ent++; } else { index = tuple.index; } /* Insert value into policy table */ table->data[ index ] = ptr; /* The actual value that is looked-up is an index * into the data table. */ res = table->insert(ip, len, index, behavior, rt); /* Check if we ran out of memory. If so, need to decrement * table->num_ent */ if(res == MEM_ALLOC_FAILURE) { /* From the control flow above, it's possible table->num_ent was not * incremented. It should be safe to decrement here, because the only * time it will be incremented above is when we are potentially * mallocing one or more new entries (It's not incremented when we * overwrite an existing entry). */ table->num_ent--; } return res; } uint32_t sfrt_num_entries(table_t *table) { if(!table || !table->rt || !table->allocated) { return 0; } /* There is always a root node, so subtract 1 for it */ return table->num_ent - 1; } uint32_t sfrt_usage(table_t *table) { uint32_t usage; if(!table || !table->rt || !table->allocated || !table->usage) { return 0; } usage = table->allocated + table->usage( table->rt ); #ifdef SUP_IP6 if (table->rt6) { usage += table->usage( table->rt6 ); } #endif return usage; } #ifdef DEBUG_SFRT #define NUM_IPS 32 #define NUM_DATA 4 int main() { table_t *dir; uint32_t ip_list[NUM_IPS]; /* entirely arbitrary */ char data[NUM_DATA]; /* also entirely arbitrary */ uint32_t index, val; for(index=0; index %c\n", index, ip_list[index], data[index%NUM_DATA], *(uint32_t*)sfrt_lookup(&ip_list[index], dir)); } for(index=0; index < NUM_IPS; index++) { val = *(uint32_t*)sfrt_lookup(&ip_list[index], dir); printf("\t@%d\t%x: %c. originally:\t%c\n", index, ip_list[index], val, data[index%NUM_DATA]); } printf("Usage: %d bytes\n", ((dir_table_t*)(dir->rt))->allocated); sfrt_free(dir); return 0; } #endif /* DEBUG_SFRT */