/**************************************************************************** * * Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. * Copyright (C) 2006-2013 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 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 "sf_types.h" #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 }; static inline int allocateTableIndex(table_t *table); /* 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; } /* 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->lastAllocatedIndex = 0; 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; table->rt6 = NULL; /* 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->print = NULL; table->remove = NULL; 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: case DIR_16_4x4_16x5_4x4: case DIR_16x7_4x4: case DIR_16x8: case DIR_8x16: table->insert = sfrt_dir_insert; table->lookup = sfrt_dir_lookup; table->free = sfrt_dir_free; table->usage = sfrt_dir_usage; table->print = sfrt_dir_print; table->remove = sfrt_dir_remove; 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; 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, 16,8,4,4); 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; }; if((!table->rt) || (!table->rt6)) { if (table->rt) table->free( table->rt ); if (table->rt6) table->free( table->rt6 ); free(table->data); free(table); return NULL; } 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 ); } if(!table->rt6) { /* This should not have happened either */ } else { table->free( table->rt6 ); } free(table); } /* Perform a lookup on value contained in "ip" */ GENERIC sfrt_lookup(sfaddr_t* ip, table_t* table) { tuple_t tuple; uint32_t* adr; int numAdrDwords; void *rt; if(!ip) { return NULL; } if(!table || !table->lookup) { return NULL; } if (sfaddr_family(ip) == AF_INET) { adr = sfaddr_get_ip4_ptr(ip); numAdrDwords = 1; rt = table->rt; } else { adr = sfaddr_get_ip6_ptr(ip); numAdrDwords = 4; rt = table->rt6; } tuple = table->lookup(adr, numAdrDwords, rt); if(tuple.index >= table->max_size) { return NULL; } return table->data[tuple.index]; } void sfrt_iterate(table_t* table, sfrt_iterator_callback userfunc) { uint32_t index, count; if (!table) return; for (index = 0, count = 0; index < table->max_size; index++) { if (table->data[index]) { userfunc(table->data[index]); if (++count == table->num_ent) break; } } return; } void sfrt_iterate_with_snort_config(struct _SnortConfig *sc, table_t* table, sfrt_sc_iterator_callback userfunc) { uint32_t index, count; if (!table) return; for (index = 0, count = 0; index < table->max_size; index++) { if (table->data[index]) { userfunc(sc, table->data[index]); if (++count == table->num_ent) break; } } return; } int sfrt_iterate2(table_t* table, sfrt_iterator_callback3 userfunc) { uint32_t index, count; if (!table) return 0; for (index = 0, count = 0; index < table->max_size; index++) { if (table->data[index]) { int ret = userfunc(table->data[index]); if (ret != 0) return ret; if (++count == table->num_ent) break; } } return 0; } int sfrt_iterate2_with_snort_config(struct _SnortConfig *sc, table_t* table, sfrt_sc_iterator_callback3 userfunc) { uint32_t index, count; if (!table) return 0; for (index = 0, count = 0; index < table->max_size; index++) { if (table->data[index]) { int ret = userfunc(sc, table->data[index]); if (ret != 0) return ret; if (++count == table->num_ent) break; } } return 0; } void sfrt_cleanup2( table_t* table, sfrt_iterator_callback2 cleanup_func, void *data ) { uint32_t index, count; if (!table) return; for (index = 0, count = 0; index < table->max_size; 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; if (++count == table->num_ent) break; } } } void sfrt_cleanup(table_t* table, sfrt_iterator_callback cleanup_func) { uint32_t index, count; if (!table) return; for (index = 0, count = 0; index < table->max_size; 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; if (++count == table->num_ent) break; } } return; } GENERIC sfrt_search(sfaddr_t* ip, table_t *table) { uint32_t* adr; int numAdrDwords; tuple_t tuple; void *rt = NULL; if ((ip == NULL) || (table == NULL)) return NULL; if (sfaddr_family(ip) == AF_INET) { adr = sfaddr_get_ip4_ptr(ip); numAdrDwords = 1; rt = table->rt; } else { adr = sfaddr_get_ip6_ptr(ip); numAdrDwords = 4; rt = table->rt6; } tuple = table->lookup(adr, numAdrDwords, rt); if(tuple.index >= table->max_size) 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(sfcidr_t* ip, unsigned char len, GENERIC ptr, int behavior, table_t *table) { int index; int newIndex = 0; int res; uint32_t* adr; int numAdrDwords; tuple_t tuple; void *rt = NULL; if(!ip) { return RT_INSERT_FAILURE; } if (len == 0) return RT_INSERT_FAILURE; if(!table || !table->insert || !table->data || !table->lookup) { return RT_INSERT_FAILURE; } if (len > 128) { return RT_INSERT_FAILURE; } /* 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 if (sfaddr_family(&ip->addr) == AF_INET) { if (len < 96) { return RT_INSERT_FAILURE; } len -= 96; adr = sfip_get_ip4_ptr(ip); numAdrDwords = 1; rt = table->rt; } else { adr = sfip_get_ip6_ptr(ip); numAdrDwords = 4; rt = table->rt6; } if (!rt) { return RT_INSERT_FAILURE; } tuple = table->lookup(adr, numAdrDwords, 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 = newIndex = allocateTableIndex(table); if (!index) return RT_POLICY_TABLE_EXCEEDED; } else { index = tuple.index; } /* The actual value that is looked-up is an index * into the data table. */ res = table->insert(adr, numAdrDwords, len, index, behavior, rt); if ((res == RT_SUCCESS) && newIndex) { table->num_ent++; table->data[ index ] = ptr; } return res; } /** Pretty print table * Pretty print sfrt table. * @param table - routing table. */ void sfrt_print(table_t *table) { if(!table || !table->print ) { return; } if (table->rt) table->print(table->rt); if (table->rt6) table->print(table->rt6); } 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 ); if (table->rt6) { usage += table->usage( table->rt6 ); } return usage; } /** Remove subnet from sfrt table. * Remove subnet identified by ip/len and return associated data. * @param ip - IP address * @param len - length of netmask * @param ptr - void ** that is set to value associated with subnet * @param behavior - RT_FAVOR_SPECIFIC or RT_FAVOR_TIME * @note - For RT_FAVOR_TIME behavior, if partial subnet is removed then table->data[x] is nulled. Any remaining entries * will then point to null data. This can cause hung or crosslinked data. RT_FAVOR_SPECIFIC does not have this drawback. * hung or crosslinked entries. */ int sfrt_remove(sfcidr_t* ip, unsigned char len, GENERIC *ptr, int behavior, table_t *table) { int index; uint32_t* adr; int numAdrDwords; void *rt = NULL; if(!ip) { return RT_REMOVE_FAILURE; } if (len == 0) return RT_REMOVE_FAILURE; if(!table || !table->data || !table->remove || !table->lookup ) { //remove operation will fail for LCT since this operation is not implemented return RT_REMOVE_FAILURE; } if (len > 128) { return RT_REMOVE_FAILURE; } #ifdef SUPPORT_LCTRIE if(table->table_type != LCT) { #endif if (sfaddr_family(&ip->addr) == AF_INET) { if (len < 96) { return RT_REMOVE_FAILURE; } len -= 96; adr = sfip_get_ip4_ptr(ip); numAdrDwords = 1; rt = table->rt; } else { adr = sfip_get_ip6_ptr(ip); numAdrDwords = 4; rt = table->rt6; } #ifdef SUPPORT_LCTRIE } #endif /* The actual value that is looked-up is an index * into the data table. */ index = table->remove(adr, numAdrDwords, len, behavior, rt); /* Remove value into policy table. See TBD in function header*/ if (index) { *ptr = table->data[ index ]; table->data[ index ] = NULL; table->num_ent--; } return RT_SUCCESS; } /**allocate first unused index value. With delete operation, index values can be non-contiguous. * Index 0 is error in this function but this is valid entry in table->data that is used * for failure case. Calling function must check for 0 and take appropriate error action. */ static inline int allocateTableIndex(table_t *table) { uint32_t index; //0 is special index for failed entries. for (index = table->lastAllocatedIndex+1; index != table->lastAllocatedIndex; index = (index+1) % table->max_size) { if (index && !table->data[index]) { table->lastAllocatedIndex = index; return index; } } return 0; } #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 */