commit e7bf726adbcaa30ee9e568ac184e464cdb0619f2 Author: DcruBro Date: Fri Jun 5 14:03:02 2026 +0200 initial diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0631a71 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +build/ +.DS_Store +*.o +*.obj +*.so +*.dll +*.dylib +*.exe \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..9235122 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,43 @@ +cmake_minimum_required(VERSION 3.16) + +project(miniroute + VERSION 0.1.0 + LANGUAGES C +) + +set(CMAKE_C_STANDARD 23) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) + +find_package(Threads REQUIRED) + +# --------------------------------------------------------- +# Output directories +# --------------------------------------------------------- +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + +foreach(OUTPUTCONFIG DEBUG RELEASE RELWITHDEBINFO MINSIZEREL) + string(TOUPPER ${OUTPUTCONFIG} OUTPUTCONFIG_UPPER) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIG_UPPER} ${CMAKE_BINARY_DIR}/bin) + set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_${OUTPUTCONFIG_UPPER} ${CMAKE_BINARY_DIR}/lib) + set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_${OUTPUTCONFIG_UPPER} ${CMAKE_BINARY_DIR}/lib) +endforeach() + +# Exec +file(GLOB_RECURSE SERVER_SRC CONFIGURE_DEPENDS src/*.c) +add_executable(miniroute ${SERVER_SRC}) +target_link_libraries(miniroute PRIVATE ${CMAKE_THREAD_LIBS_INIT}) +target_include_directories(miniroute PRIVATE + ${PROJECT_SOURCE_DIR}/include +) +target_compile_options(miniroute PRIVATE + -Wall + -Wextra + -Wpedantic + -g +) +target_compile_definitions(miniroute PRIVATE) +set_target_properties(miniroute PROPERTIES OUTPUT_NAME "miniroute") + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9efa6fb --- /dev/null +++ b/LICENSE @@ -0,0 +1,338 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + 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, see . + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Moe Ghoul, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..cd7f1e5 --- /dev/null +++ b/README.md @@ -0,0 +1,6 @@ +# MiniRoute + +MiniRoute is a learning project, implementing a router in C. + +License: GPLv2-only +Copyright: 2026, by DcruBro (Jonas Korene Novak) \ No newline at end of file diff --git a/include/dynarr.h b/include/dynarr.h new file mode 100644 index 0000000..b58e7b7 --- /dev/null +++ b/include/dynarr.h @@ -0,0 +1,63 @@ +#ifndef DYNARR_H +#define DYNARR_H + +#include +#include +#include + +#define DYNARR_MAX_CAPACITY ((size_t)0x7FFFFFFF) + +typedef struct { + size_t size; + size_t elemSize; + size_t capacity; + void* data; +} DynArr; + +// Do not use; Use DYNARR_CREATE macro instead. +DynArr* DynArr_create(size_t elemSize, size_t capacity); + +// Reserve n blocks in arary; New size will be n, NOT size + n; Reserving less memory that current will fail, use prune instead. +void DynArr_reserve(DynArr* p, size_t n); + +// Push data into a new block at the end of the array +void* DynArr_push_back(DynArr* p, void* value); + +// Remove the last block in the array. +void DynArr_pop_back(DynArr* p); + +// Remove first block from array. +void DynArr_pop_front(DynArr* p); + +// Remove index from array. This moves all blocks after the index block. +void DynArr_remove(DynArr* p, size_t index); + +// Erase the array. This will not free unused blocks. +void DynArr_erase(DynArr* p); + +// Prune and free unused blocks. If pruning to zero, ensure to reserve after. +void DynArr_prune(DynArr* p); + +// Get a pointer to a block by index +void* DynArr_at(DynArr* p, size_t index); + +// Get the index by block pointer +size_t DynArr_at_ptr(DynArr* p, void* ptr); + +// Get size +size_t DynArr_size(DynArr* p); + +// Get element size +size_t DynArr_elemSize(DynArr* p); + +// Get capacity +size_t DynArr_capacity(DynArr* p); + +void DynArr_destroy(DynArr* p); + +// Note: Make sure to not overread or overwrite +void* DynArr_c_arr(DynArr* p); + +#define DYNARR_CREATE(T, initialCapacity) DynArr_create(sizeof(T), initialCapacity) + +#endif diff --git a/include/dynset.h b/include/dynset.h new file mode 100644 index 0000000..016b386 --- /dev/null +++ b/include/dynset.h @@ -0,0 +1,20 @@ +#ifndef DYNSET_H +#define DYNSET_H + +#include + +// Dynamic Set structure - basically DynArr with uniqueness enforced +typedef struct { + DynArr* arr; +} DynSet; + +// Function prototypes +DynSet* DynSet_Create(size_t elemSize); +void DynSet_Destroy(DynSet* set); +int DynSet_Insert(DynSet* set, const void* element); +int DynSet_Contains(DynSet* set, const void* element); +size_t DynSet_Size(DynSet* set); +void* DynSet_Get(DynSet* set, size_t index); +void DynSet_Remove(DynSet* set, const void* element); + +#endif diff --git a/include/forward/if/interface.h b/include/forward/if/interface.h new file mode 100644 index 0000000..ade43a2 --- /dev/null +++ b/include/forward/if/interface.h @@ -0,0 +1,42 @@ +#ifndef INTERFACE_H +#define INTERFACE_H + +#include +#include +#include + +typedef struct interface_v4 interface_v4_t; +typedef struct interface_v6 interface_v6_t; + +typedef struct interface_packet { + sa_family_t family; + const uint8_t* data; + size_t length; + struct sockaddr_storage source; + struct sockaddr_storage destination; +} interface_packet_t; + +typedef void (*interface_packet_callback_t)(const interface_packet_t* packet, void* userData); + +int Interface_Init(void); +void Interface_Cleanup(void); + +interface_v4_t* Interface_Create_v4(const char* localAddress, interface_packet_callback_t callback, void* userData); +interface_v6_t* Interface_Create_v6(const char* localAddress, interface_packet_callback_t callback, void* userData); + +int Interface_Listen_v4(interface_v4_t* iface); +int Interface_Listen_v6(interface_v6_t* iface); + +void Interface_Stop_v4(interface_v4_t* iface); +void Interface_Stop_v6(interface_v6_t* iface); + +void Interface_Destroy_v4(interface_v4_t* iface); +void Interface_Destroy_v6(interface_v6_t* iface); + +int Interface_SendRaw_v4(interface_v4_t* iface, const struct in_addr* destination, const void* packet, size_t packetLength); +int Interface_SendRaw_v6(interface_v6_t* iface, const struct in6_addr* destination, const void* packet, size_t packetLength); + +int Interface_SendFrame_v4(interface_v4_t* iface, const void* frame, size_t frameLength); +int Interface_SendFrame_v6(interface_v6_t* iface, const void* frame, size_t frameLength); + +#endif diff --git a/include/forward/routetable.h b/include/forward/routetable.h new file mode 100644 index 0000000..739a097 --- /dev/null +++ b/include/forward/routetable.h @@ -0,0 +1,30 @@ +#ifndef ROUTETABLE_H +#define ROUTETABLE_H + +#include +#include + +typedef struct { + uint32_t destination; + uint32_t mask; + uint32_t nextHop; +} route_table_v4_entry_t; + +typedef struct { + __uint128_t destination; + __uint128_t mask; + __uint128_t nextHop; +} route_table_v6_entry_t; + +// General rule: 0 = success, -1 = failure (Note: per-function return values may vary, will be documented in the .c file) +int RouteTable_Init(void); +void RouteTable_Cleanup(void); + +int RouteTable_AddRoute_v4(uint32_t destination, uint32_t mask, uint32_t nextHop); +int RouteTable_AddRoute_v6(__uint128_t destination, __uint128_t mask, __uint128_t nextHop); + +// GetNextHop performs longest prefix match; writes best matching next hop into *nextHop +int RouteTable_GetNextHop_v4(uint32_t destination, uint32_t* nextHop); +int RouteTable_GetNextHop_v6(__uint128_t destination, __uint128_t* nextHop); + +#endif diff --git a/include/iputils.h b/include/iputils.h new file mode 100644 index 0000000..a2c6316 --- /dev/null +++ b/include/iputils.h @@ -0,0 +1,21 @@ +#ifndef IPUTILS_H +#define IPUTILS_H + +#include +#include +#include +#include + +uint32_t IPUtils_CIDRToMaskv4(uint8_t cidr); +uint8_t IPUtils_MaskToCIDRv4(uint32_t mask); +bool IPUtils_MatchPrefixv4(uint32_t ip, uint32_t prefix, uint8_t cidr); +// If buf is NULL, -> stdout; if NOT NULL, -> buf (must be at least 16 bytes) +void IPUtils_PrintIPv4(uint32_t ip, char* buf); + +__uint128_t IPUtils_CIDRToMaskv6(uint8_t cidr); +uint8_t IPUtils_MaskToCIDRv6(__uint128_t mask); +bool IPUtils_MatchPrefixv6(__uint128_t ip, __uint128_t prefix, uint8_t cidr); +// If buf is NULL, -> stdout; if NOT NULL, -> buf (must be at least 40 bytes) +void IPUtils_PrintIPv6(__uint128_t ip, char* buf); + +#endif diff --git a/include/libart/art.h b/include/libart/art.h new file mode 100644 index 0000000..7e5a8c6 --- /dev/null +++ b/include/libart/art.h @@ -0,0 +1,215 @@ +#include +#ifndef ART_H +#define ART_H + +#ifdef __cplusplus +extern "C" { +#endif + +#define NODE4 1 +#define NODE16 2 +#define NODE48 3 +#define NODE256 4 + +#define MAX_PREFIX_LEN 10 + +#if defined(__GNUC__) && !defined(__clang__) +# if __STDC_VERSION__ >= 199901L && 402 == (__GNUC__ * 100 + __GNUC_MINOR__) +/* + * GCC 4.2.2's C99 inline keyword support is pretty broken; avoid. Introduced in + * GCC 4.2.something, fixed in 4.3.0. So checking for specific major.minor of + * 4.2 is fine. + */ +# define BROKEN_GCC_C99_INLINE +# endif +#endif + +typedef int(*art_callback)(void *data, const unsigned char *key, uint32_t key_len, void *value); + +/** + * This struct is included as part + * of all the various node sizes + */ +typedef struct { + uint32_t partial_len; + uint8_t type; + uint8_t num_children; + unsigned char partial[MAX_PREFIX_LEN]; +} art_node; + +/** + * Small node with only 4 children + */ +typedef struct { + art_node n; + unsigned char keys[4]; + art_node *children[4]; +} art_node4; + +/** + * Node with 16 children + */ +typedef struct { + art_node n; + unsigned char keys[16]; + art_node *children[16]; +} art_node16; + +/** + * Node with 48 children, but + * a full 256 byte field. + */ +typedef struct { + art_node n; + unsigned char keys[256]; + art_node *children[48]; +} art_node48; + +/** + * Full node with 256 children + */ +typedef struct { + art_node n; + art_node *children[256]; +} art_node256; + +/** + * Represents a leaf. These are + * of arbitrary size, as they include the key. + */ +typedef struct { + void *value; + uint32_t key_len; + unsigned char key[]; +} art_leaf; + +/** + * Main struct, points to root. + */ +typedef struct { + art_node *root; + uint64_t size; +} art_tree; + +/** + * Initializes an ART tree + * @return 0 on success. + */ +int art_tree_init(art_tree *t); + +/** + * DEPRECATED + * Initializes an ART tree + * @return 0 on success. + */ +#define init_art_tree(...) art_tree_init(__VA_ARGS__) + +/** + * Destroys an ART tree + * @return 0 on success. + */ +int art_tree_destroy(art_tree *t); + +/** + * DEPRECATED + * Initializes an ART tree + * @return 0 on success. + */ +#define destroy_art_tree(...) art_tree_destroy(__VA_ARGS__) + +/** + * Returns the size of the ART tree. + */ +#ifdef BROKEN_GCC_C99_INLINE +# define art_size(t) ((t)->size) +#else +inline uint64_t art_size(art_tree *t) { + return t->size; +} +#endif + +/** + * inserts a new value into the art tree + * @arg t the tree + * @arg key the key + * @arg key_len the length of the key + * @arg value opaque value. + * @return null if the item was newly inserted, otherwise + * the old value pointer is returned. + */ +void* art_insert(art_tree *t, const unsigned char *key, int key_len, void *value); + +/** + * inserts a new value into the art tree (not replacing) + * @arg t the tree + * @arg key the key + * @arg key_len the length of the key + * @arg value opaque value. + * @return null if the item was newly inserted, otherwise + * the old value pointer is returned. + */ +void* art_insert_no_replace(art_tree *t, const unsigned char *key, int key_len, void *value); + +/** + * Deletes a value from the ART tree + * @arg t The tree + * @arg key The key + * @arg key_len The length of the key + * @return NULL if the item was not found, otherwise + * the value pointer is returned. + */ +void* art_delete(art_tree *t, const unsigned char *key, int key_len); + +/** + * Searches for a value in the ART tree + * @arg t The tree + * @arg key The key + * @arg key_len The length of the key + * @return NULL if the item was not found, otherwise + * the value pointer is returned. + */ +void* art_search(const art_tree *t, const unsigned char *key, int key_len); + +/** + * Returns the minimum valued leaf + * @return The minimum leaf or NULL + */ +art_leaf* art_minimum(art_tree *t); + +/** + * Returns the maximum valued leaf + * @return The maximum leaf or NULL + */ +art_leaf* art_maximum(art_tree *t); + +/** + * Iterates through the entries pairs in the map, + * invoking a callback for each. The call back gets a + * key, value for each and returns an integer stop value. + * If the callback returns non-zero, then the iteration stops. + * @arg t The tree to iterate over + * @arg cb The callback function to invoke + * @arg data Opaque handle passed to the callback + * @return 0 on success, or the return of the callback. + */ +int art_iter(art_tree *t, art_callback cb, void *data); + +/** + * Iterates through the entries pairs in the map, + * invoking a callback for each that matches a given prefix. + * The call back gets a key, value for each and returns an integer stop value. + * If the callback returns non-zero, then the iteration stops. + * @arg t The tree to iterate over + * @arg prefix The prefix of keys to read + * @arg prefix_len The length of the prefix + * @arg cb The callback function to invoke + * @arg data Opaque handle passed to the callback + * @return 0 on success, or the return of the callback. + */ +int art_iter_prefix(art_tree *t, const unsigned char *prefix, int prefix_len, art_callback cb, void *data); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/dynarr.c b/src/dynarr.c new file mode 100644 index 0000000..894703d --- /dev/null +++ b/src/dynarr.c @@ -0,0 +1,179 @@ +#include + +DynArr* DynArr_create(size_t elemSize, size_t capacity) { + DynArr* p = (DynArr*)malloc(sizeof(DynArr)); + if (!p) return NULL; + + p->elemSize = elemSize; + p->capacity = capacity; + p->data = malloc(elemSize * capacity); + if (!p->data) { + free(p); + return NULL; + } + p->size = 0; + + return p; +} + +// Reserve n blocks in arary; New size will be n, NOT size + n; Reserving less memory that current will fail, use prune instead. +void DynArr_reserve(DynArr* p, size_t n) { + if (n <= p->capacity) { + printf("reserve ignored; attempted to reserve less or equal to current capacity\n"); + return; + } + + if (n > DYNARR_MAX_CAPACITY) { + printf("reserve ignored; attempted to reserve over 32 bits\n"); + return; + } + + void* new_data = realloc(p->data, n * p->elemSize); + if (!new_data) { + printf("reserve failed\n"); + exit(1); + } + p->data = new_data; + p->capacity = n; +} + +// Push data into a new block at the end of the array; If value is NULL, the new block will be zeroed. +void* DynArr_push_back(DynArr* p, void* value) { + //if (value == NULL) { + // printf("push_back ignored; value is null"); + // return NULL; + //} + + if (p->size >= p->capacity) { + size_t new_cap = (p->capacity == 0) ? 1 : p->capacity * 2; + + if (new_cap < p->capacity || new_cap > DYNARR_MAX_CAPACITY) { + printf("push_back ignored; capacity overflow\n"); + return NULL; + } + + void* new_data = realloc(p->data, new_cap * p->elemSize); + if (!new_data) { + printf("push failed\n"); + exit(1); + } + p->capacity = new_cap; + p->data = new_data; + } + + void* dst = (void*)((char*)p->data + (p->size * p->elemSize)); + + if (value == NULL) { + memset(dst, 0, p->elemSize); // Handle NULL value. + } else { + memcpy((char*)dst, value, p->elemSize); + } + + p->size++; + + return dst; +} + +// Remove the last block in the array. +void DynArr_pop_back(DynArr* p) { + if (p->size == 0) { + printf("pop_back ignored; size is 0\n"); + return; + } + + p->size--; // Will automatically overwrite that memory naturally +} + +// Remove first block from array. +void DynArr_pop_front(DynArr* p) { + if (p->size == 0) { + printf("pop_front ignored; size is 0\n"); + return; + } + + memmove( + (char*)p->data, + (char*)p->data + p->elemSize, + (p->size - 1) * p->elemSize + ); + + p->size--; +} + +// Remove index from array. This moves all blocks after the index block. +void DynArr_remove(DynArr* p, size_t index) { + if (index >= p->size) return; + + memmove( + (char*)p->data + (index * p->elemSize), + (char*)p->data + (index + 1) * p->elemSize, + (p->size - index - 1) * p->elemSize + ); + + p->size--; +} + +// Erase the array. This will not free unused blocks. +void DynArr_erase(DynArr* p) { + p->size = 0; +} + +// Prune and free unused blocks. If pruning to zero, ensure to reserve after. +void DynArr_prune(DynArr* p) { + void* new_data = realloc(p->data, (p->size == 0 ? 1 : p->size) * p->elemSize); + if (!new_data) { + printf("pruning failed\n"); + exit(1); + } + + p->data = new_data; + p->capacity = p->size; +} + +// Get a pointer to a block by index +void* DynArr_at(DynArr* p, size_t index) { + if (index >= p->size) return NULL; + return (char*)p->data + (index * p->elemSize); +} + +// Get the index by block pointer +size_t DynArr_at_ptr(DynArr* p, void* ptr) { + if (!p || !ptr) { + printf("invalid pointer\n"); + exit(1); + } + + for (size_t i = 0; i < p->size; i++) { + if ((void*)(((char*)p->data) + (i * p->elemSize)) == ptr) { + return i; + } + } + + // If for some reason the array has 2^64 elements in it, then fuck it, I guess we'll just crash, I don't care. + return -1; +} + +// Get size +size_t DynArr_size(DynArr* p) { + return p->size; +} + +// Get element size +size_t DynArr_elemSize(DynArr* p) { + return p->elemSize; +} + +// Get capacity +size_t DynArr_capacity(DynArr* p) { + return p->capacity; +} + +void DynArr_destroy(DynArr* p) { + if (!p) return; + free(p->data); + free(p); +} + +void* DynArr_c_arr(DynArr* p) { + return p->data; +} diff --git a/src/dynset.c b/src/dynset.c new file mode 100644 index 0000000..f54705a --- /dev/null +++ b/src/dynset.c @@ -0,0 +1,58 @@ +#include + +DynSet* DynSet_Create(size_t elemSize) { + DynSet* set = (DynSet*)malloc(sizeof(DynSet)); + if (!set) { + return NULL; + } + set->arr = DynArr_create(elemSize, 1); + if (!set->arr) { + free(set); + return NULL; + } + return set; +} + +void DynSet_Destroy(DynSet* set) { + if (set) { + DynArr_destroy(set->arr); + free(set); + } +} + +int DynSet_Insert(DynSet* set, const void* element) { + if (DynSet_Contains(set, element)) { + return 0; // Element already exists + } + return DynArr_push_back(set->arr, element) != NULL; +} + +int DynSet_Contains(DynSet* set, const void* element) { + size_t size = DynArr_size(set->arr); + for (size_t i = 0; i < size; i++) { + void* current = DynArr_at(set->arr, i); + if (memcmp(current, element, set->arr->elemSize) == 0) { + return 1; // Found + } + } + return 0; // Not found +} + +size_t DynSet_Size(DynSet* set) { + return DynArr_size(set->arr); +} + +void* DynSet_Get(DynSet* set, size_t index) { + return DynArr_at(set->arr, index); +} + +void DynSet_Remove(DynSet* set, const void* element) { + size_t size = DynArr_size(set->arr); + for (size_t i = 0; i < size; i++) { + void* current = DynArr_at(set->arr, i); + if (memcmp(current, element, set->arr->elemSize) == 0) { + DynArr_remove(set->arr, i); + return; + } + } +} diff --git a/src/forward/if/interface.c b/src/forward/if/interface.c new file mode 100644 index 0000000..1e41294 --- /dev/null +++ b/src/forward/if/interface.c @@ -0,0 +1,413 @@ +#include + +#include +#include +#include +#include +#include +#include +#include + +#ifndef IPV6_HDRINCL +#define IPV6_HDRINCL 36 +#endif + +struct interface_v4 { + int listenFd; + int sendFd; + int listening; + struct sockaddr_in localAddress; + pthread_t thread; + interface_packet_callback_t callback; + void* userData; +}; + +struct interface_v6 { + int listenFd; + int sendFd; + int listening; + struct sockaddr_in6 localAddress; + pthread_t thread; + interface_packet_callback_t callback; + void* userData; +}; + +static int interface_parse_ipv4(const char* localAddress, struct in_addr* outAddress) { + if (!outAddress) { + return -1; + } + + if (localAddress == NULL || localAddress[0] == '\0') { + outAddress->s_addr = htonl(INADDR_ANY); + return 0; + } + + return inet_pton(AF_INET, localAddress, outAddress) == 1 ? 0 : -1; +} + +static int interface_parse_ipv6(const char* localAddress, struct in6_addr* outAddress) { + if (!outAddress) { + return -1; + } + + if (localAddress == NULL || localAddress[0] == '\0') { + *outAddress = in6addr_any; + return 0; + } + + return inet_pton(AF_INET6, localAddress, outAddress) == 1 ? 0 : -1; +} + +static int interface_open_v4(interface_v4_t* iface) { + iface->listenFd = socket(AF_INET, SOCK_RAW, IPPROTO_IP); + if (iface->listenFd < 0) { + return -1; + } + + iface->sendFd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); + if (iface->sendFd < 0) { + close(iface->listenFd); + iface->listenFd = -1; + return -1; + } + + { + int one = 1; + if (setsockopt(iface->sendFd, IPPROTO_IP, IP_HDRINCL, &one, sizeof(one)) < 0) { + close(iface->listenFd); + close(iface->sendFd); + iface->listenFd = -1; + iface->sendFd = -1; + return -1; + } + } + + if (bind(iface->listenFd, (struct sockaddr*)&iface->localAddress, sizeof(iface->localAddress)) < 0) { + close(iface->listenFd); + close(iface->sendFd); + iface->listenFd = -1; + iface->sendFd = -1; + return -1; + } + + return 0; +} + +static int interface_open_v6(interface_v6_t* iface) { + iface->listenFd = socket(AF_INET6, SOCK_RAW, IPPROTO_IPV6); + if (iface->listenFd < 0) { + return -1; + } + + iface->sendFd = socket(AF_INET6, SOCK_RAW, IPPROTO_RAW); + if (iface->sendFd < 0) { + close(iface->listenFd); + iface->listenFd = -1; + return -1; + } + + { + int one = 1; +#ifdef IPV6_HDRINCL + if (setsockopt(iface->sendFd, IPPROTO_IPV6, IPV6_HDRINCL, &one, sizeof(one)) < 0) { + close(iface->listenFd); + close(iface->sendFd); + iface->listenFd = -1; + iface->sendFd = -1; + return -1; + } +#endif + } + + if (bind(iface->listenFd, (struct sockaddr*)&iface->localAddress, sizeof(iface->localAddress)) < 0) { + close(iface->listenFd); + close(iface->sendFd); + iface->listenFd = -1; + iface->sendFd = -1; + return -1; + } + + return 0; +} + +static void interface_fill_packet_common(interface_packet_t* packet, sa_family_t family, const void* data, size_t length, const struct sockaddr* source, const struct sockaddr* destination) { + size_t sourceSize = 0; + size_t destinationSize = 0; + + packet->family = family; + packet->data = (const uint8_t*)data; + packet->length = length; + memset(&packet->source, 0, sizeof(packet->source)); + memset(&packet->destination, 0, sizeof(packet->destination)); + + if (source) { + sourceSize = (source->sa_family == AF_INET6) ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in); + memcpy(&packet->source, source, sourceSize); + } + + if (destination) { + destinationSize = (destination->sa_family == AF_INET6) ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in); + memcpy(&packet->destination, destination, destinationSize); + } +} + +static void* interface_listen_v4_thread(void* arg) { + interface_v4_t* iface = (interface_v4_t*)arg; + uint8_t buffer[65535]; + + while (iface->listening) { + struct sockaddr_storage source; + socklen_t sourceLength = sizeof(source); + ssize_t received = recvfrom(iface->listenFd, buffer, sizeof(buffer), 0, (struct sockaddr*)&source, &sourceLength); + + if (received < 0) { + if (!iface->listening || errno == EBADF || errno == EINVAL) { + break; + } + continue; + } + + if (iface->callback) { + uint8_t* packetCopy = (uint8_t*)malloc((size_t)received); + if (!packetCopy) { + continue; + } + + interface_packet_t packet; + + memcpy(packetCopy, buffer, (size_t)received); + interface_fill_packet_common(&packet, AF_INET, packetCopy, (size_t)received, (struct sockaddr*)&source, (struct sockaddr*)&iface->localAddress); + iface->callback(&packet, iface->userData); + free(packetCopy); + } + } + + return NULL; +} + +static void* interface_listen_v6_thread(void* arg) { + interface_v6_t* iface = (interface_v6_t*)arg; + uint8_t buffer[65535]; + + while (iface->listening) { + struct sockaddr_storage source; + socklen_t sourceLength = sizeof(source); + ssize_t received = recvfrom(iface->listenFd, buffer, sizeof(buffer), 0, (struct sockaddr*)&source, &sourceLength); + + if (received < 0) { + if (!iface->listening || errno == EBADF || errno == EINVAL) { + break; + } + continue; + } + + if (iface->callback) { + uint8_t* packetCopy = (uint8_t*)malloc((size_t)received); + if (!packetCopy) { + continue; + } + + interface_packet_t packet; + + memcpy(packetCopy, buffer, (size_t)received); + interface_fill_packet_common(&packet, AF_INET6, packetCopy, (size_t)received, (struct sockaddr*)&source, (struct sockaddr*)&iface->localAddress); + iface->callback(&packet, iface->userData); + free(packetCopy); + } + } + + return NULL; +} + +int Interface_Init(void) { + return 0; +} + +void Interface_Cleanup(void) { +} + +interface_v4_t* Interface_Create_v4(const char* localAddress, interface_packet_callback_t callback, void* userData) { + interface_v4_t* iface = (interface_v4_t*)calloc(1, sizeof(*iface)); + if (!iface) { + return NULL; + } + + iface->listenFd = -1; + iface->sendFd = -1; + iface->callback = callback; + iface->userData = userData; + iface->localAddress.sin_family = AF_INET; + + if (interface_parse_ipv4(localAddress, &iface->localAddress.sin_addr) < 0) { + free(iface); + return NULL; + } + + return iface; +} + +interface_v6_t* Interface_Create_v6(const char* localAddress, interface_packet_callback_t callback, void* userData) { + interface_v6_t* iface = (interface_v6_t*)calloc(1, sizeof(*iface)); + if (!iface) { + return NULL; + } + + iface->listenFd = -1; + iface->sendFd = -1; + iface->callback = callback; + iface->userData = userData; + iface->localAddress.sin6_family = AF_INET6; + + if (interface_parse_ipv6(localAddress, &iface->localAddress.sin6_addr) < 0) { + free(iface); + return NULL; + } + + return iface; +} + +int Interface_Listen_v4(interface_v4_t* iface) { + if (!iface || iface->listening) { + return -1; + } + + if (interface_open_v4(iface) < 0) { + return -1; + } + + iface->listening = 1; + if (pthread_create(&iface->thread, NULL, interface_listen_v4_thread, iface) != 0) { + iface->listening = 0; + close(iface->listenFd); + close(iface->sendFd); + iface->listenFd = -1; + iface->sendFd = -1; + return -1; + } + + return 0; +} + +int Interface_Listen_v6(interface_v6_t* iface) { + if (!iface || iface->listening) { + return -1; + } + + if (interface_open_v6(iface) < 0) { + return -1; + } + + iface->listening = 1; + if (pthread_create(&iface->thread, NULL, interface_listen_v6_thread, iface) != 0) { + iface->listening = 0; + close(iface->listenFd); + close(iface->sendFd); + iface->listenFd = -1; + iface->sendFd = -1; + return -1; + } + + return 0; +} + +void Interface_Stop_v4(interface_v4_t* iface) { + if (!iface) { + return; + } + + if (iface->listening) { + iface->listening = 0; + if (iface->listenFd >= 0) { + close(iface->listenFd); + iface->listenFd = -1; + } + pthread_join(iface->thread, NULL); + } + + if (iface->sendFd >= 0) { + close(iface->sendFd); + iface->sendFd = -1; + } +} + +void Interface_Stop_v6(interface_v6_t* iface) { + if (!iface) { + return; + } + + if (iface->listening) { + iface->listening = 0; + if (iface->listenFd >= 0) { + close(iface->listenFd); + iface->listenFd = -1; + } + pthread_join(iface->thread, NULL); + } + + if (iface->sendFd >= 0) { + close(iface->sendFd); + iface->sendFd = -1; + } +} + +void Interface_Destroy_v4(interface_v4_t* iface) { + if (!iface) { + return; + } + + Interface_Stop_v4(iface); + free(iface); +} + +void Interface_Destroy_v6(interface_v6_t* iface) { + if (!iface) { + return; + } + + Interface_Stop_v6(iface); + free(iface); +} + +int Interface_SendRaw_v4(interface_v4_t* iface, const struct in_addr* destination, const void* packet, size_t packetLength) { + struct sockaddr_in destinationAddress; + + if (!iface || iface->sendFd < 0 || !destination || !packet || packetLength == 0) { + return -1; + } + + memset(&destinationAddress, 0, sizeof(destinationAddress)); + destinationAddress.sin_family = AF_INET; + destinationAddress.sin_addr = *destination; + + return (sendto(iface->sendFd, packet, packetLength, 0, (struct sockaddr*)&destinationAddress, sizeof(destinationAddress)) < 0) ? -1 : 0; +} + +int Interface_SendRaw_v6(interface_v6_t* iface, const struct in6_addr* destination, const void* packet, size_t packetLength) { + struct sockaddr_in6 destinationAddress; + + if (!iface || iface->sendFd < 0 || !destination || !packet || packetLength == 0) { + return -1; + } + + memset(&destinationAddress, 0, sizeof(destinationAddress)); + destinationAddress.sin6_family = AF_INET6; + destinationAddress.sin6_addr = *destination; + + return (sendto(iface->sendFd, packet, packetLength, 0, (struct sockaddr*)&destinationAddress, sizeof(destinationAddress)) < 0) ? -1 : 0; +} + +int Interface_SendFrame_v4(interface_v4_t* iface, const void* frame, size_t frameLength) { + (void)iface; + (void)frame; + (void)frameLength; + errno = ENOTSUP; + return -1; +} + +int Interface_SendFrame_v6(interface_v6_t* iface, const void* frame, size_t frameLength) { + (void)iface; + (void)frame; + (void)frameLength; + errno = ENOTSUP; + return -1; +} diff --git a/src/forward/routetable.c b/src/forward/routetable.c new file mode 100644 index 0000000..98d01be --- /dev/null +++ b/src/forward/routetable.c @@ -0,0 +1,125 @@ +#include +#include +#include +#include +#include + +static art_tree route_table_v4; +static art_tree route_table_v6; + +static void build_key_v4(unsigned char out[5], uint32_t network, uint8_t plen) { + uint32_t be = htonl(network); + memcpy(out, &be, 4); + out[4] = plen; +} + +static void build_key_v6(unsigned char out[17], __uint128_t network, uint8_t plen) { + uint32_t parts[4] = { + htonl((uint32_t)(network >> 96)), + htonl((uint32_t)(network >> 64)), + htonl((uint32_t)(network >> 32)), + htonl((uint32_t)(network)) + }; + memcpy(out, parts, 16); + out[16] = plen; +} + +static int free_value_cb(void *data, const unsigned char *key, uint32_t key_len, void *value) { + (void)data; (void)key; (void)key_len; + free(value); + return 0; +} + +int RouteTable_Init(void) { + if (art_tree_init(&route_table_v4) != 0) return -1; + if (art_tree_init(&route_table_v6) != 0) { + art_tree_destroy(&route_table_v4); + return -1; + } + return 0; +} + +void RouteTable_Cleanup(void) { + art_iter(&route_table_v4, free_value_cb, NULL); + art_tree_destroy(&route_table_v4); + art_iter(&route_table_v6, free_value_cb, NULL); + art_tree_destroy(&route_table_v6); +} + +int RouteTable_AddRoute_v4(uint32_t destination, uint32_t mask, uint32_t nextHop) { + if (destination == 0 || nextHop == 0 || mask == 0) return -1; + + if (destination == 0x7F000001) return -1; + + uint8_t prefix_len = IPUtils_MaskToCIDRv4(mask); + uint32_t network = destination & mask; + + uint32_t *value = malloc(sizeof(uint32_t)); + if (!value) return -1; + *value = nextHop; + + unsigned char key[5]; + build_key_v4(key, network, prefix_len); + + void *old = art_insert(&route_table_v4, key, 5, value); + if (old) free(old); + + return 0; +} + +int RouteTable_AddRoute_v6(__uint128_t destination, __uint128_t mask, __uint128_t nextHop) { + if (destination == 0 || nextHop == 0 || mask == 0) return -1; + + if (destination == (__uint128_t)1) return -1; + + uint8_t prefix_len = IPUtils_MaskToCIDRv6(mask); + __uint128_t network = destination & mask; + + __uint128_t *value = malloc(sizeof(__uint128_t)); + if (!value) return -1; + *value = nextHop; + + unsigned char key[17]; + build_key_v6(key, network, prefix_len); + + void *old = art_insert(&route_table_v6, key, 17, value); + if (old) free(old); + + return 0; +} + +int RouteTable_GetNextHop_v4(uint32_t destination, uint32_t *nextHop) { + if (!nextHop) return -1; + + unsigned char key[5]; + for (int plen = 32; plen >= 0; plen--) { + uint32_t mask = IPUtils_CIDRToMaskv4((uint8_t)plen); + uint32_t network = destination & mask; + build_key_v4(key, network, (uint8_t)plen); + + uint32_t *val = art_search(&route_table_v4, key, 5); + if (val) { + *nextHop = *val; + return 0; + } + } + return -1; +} + +int RouteTable_GetNextHop_v6(__uint128_t destination, __uint128_t *nextHop) { + if (!nextHop) return -1; + + unsigned char key[17]; + for (int plen = 128; plen >= 0; plen--) { + __uint128_t mask = IPUtils_CIDRToMaskv6((uint8_t)plen); + __uint128_t network = destination & mask; + build_key_v6(key, network, (uint8_t)plen); + + __uint128_t *val = art_search(&route_table_v6, key, 17); + if (val) { + *nextHop = *val; + return 0; + } + } + return -1; +} diff --git a/src/iputils.c b/src/iputils.c new file mode 100644 index 0000000..9bc9832 --- /dev/null +++ b/src/iputils.c @@ -0,0 +1,89 @@ +#include + +uint32_t IPUtils_CIDRToMaskv4(uint8_t cidr) { + if (cidr > 32) { return 0x00; } + return (0xFFFFFFFFUL << (32 - cidr)) & 0xFFFFFFFFUL; +} + +uint8_t IPUtils_MaskToCIDRv4(uint32_t mask) { + uint8_t cidr = 0; + for (int i = 31; i >= 0; i--) { + if ((mask >> i) & 1) { + cidr++; + } else { + break; + } + } + return cidr; +} + +bool IPUtils_MatchPrefixv4(uint32_t ip, uint32_t prefix, uint8_t cidr) { + uint32_t mask = IPUtils_CIDRToMaskv4(cidr); + return (ip & mask) == (prefix & mask); +} + +void IPUtils_PrintIPv4(uint32_t ip, char* buf) { + if (buf) { + snprintf(buf, 16, "%u.%u.%u.%u", + (ip >> 24) & 0xFF, + (ip >> 16) & 0xFF, + (ip >> 8) & 0xFF, + ip & 0xFF); + } else { + printf("%u.%u.%u.%u", + (ip >> 24) & 0xFF, + (ip >> 16) & 0xFF, + (ip >> 8) & 0xFF, + ip & 0xFF); + } +} + +__uint128_t IPUtils_CIDRToMaskv6(uint8_t cidr) { + if (cidr > 128) { return 0x00; } + __uint128_t mask = 0; + for (int i = 0; i < cidr; i++) { + mask |= ((__uint128_t)1 << (127 - i)); + } + return mask; +} + +uint8_t IPUtils_MaskToCIDRv6(__uint128_t mask) { + uint8_t cidr = 0; + for (int i = 127; i >= 0; i--) { + if ((mask >> i) & 1) { + cidr++; + } else { + break; + } + } + return cidr; +} + +bool IPUtils_MatchPrefixv6(__uint128_t ip, __uint128_t prefix, uint8_t cidr) { + __uint128_t mask = IPUtils_CIDRToMaskv6(cidr); + return (ip & mask) == (prefix & mask); +} + +void IPUtils_PrintIPv6(__uint128_t ip, char* buf) { + if (buf) { + snprintf(buf, 40, "%x:%x:%x:%x:%x:%x:%x:%x", + (unsigned int)((ip >> 112) & 0xFFFF), + (unsigned int)((ip >> 96) & 0xFFFF), + (unsigned int)((ip >> 80) & 0xFFFF), + (unsigned int)((ip >> 64) & 0xFFFF), + (unsigned int)((ip >> 48) & 0xFFFF), + (unsigned int)((ip >> 32) & 0xFFFF), + (unsigned int)((ip >> 16) & 0xFFFF), + (unsigned int)(ip & 0xFFFF)); + } else { + printf("%x:%x:%x:%x:%x:%x:%x:%x", + (unsigned int)((ip >> 112) & 0xFFFF), + (unsigned int)((ip >> 96) & 0xFFFF), + (unsigned int)((ip >> 80) & 0xFFFF), + (unsigned int)((ip >> 64) & 0xFFFF), + (unsigned int)((ip >> 48) & 0xFFFF), + (unsigned int)((ip >> 32) & 0xFFFF), + (unsigned int)((ip >> 16) & 0xFFFF), + (unsigned int)(ip & 0xFFFF)); + } +} diff --git a/src/libart/art.c b/src/libart/art.c new file mode 100644 index 0000000..99008e2 --- /dev/null +++ b/src/libart/art.c @@ -0,0 +1,975 @@ +#include +#include +#include +#include +#include +#include + +#ifdef __i386__ + #include +#else +#ifdef __amd64__ + #include +#endif +#endif + +/** + * Macros to manipulate pointer tags + */ +#define IS_LEAF(x) (((uintptr_t)x & 1)) +#define SET_LEAF(x) ((void*)((uintptr_t)x | 1)) +#define LEAF_RAW(x) ((art_leaf*)((void*)((uintptr_t)x & ~1))) + +/** + * Allocates a node of the given type, + * initializes to zero and sets the type. + */ +static art_node* alloc_node(uint8_t type) { + art_node* n; + switch (type) { + case NODE4: + n = (art_node*)calloc(1, sizeof(art_node4)); + break; + case NODE16: + n = (art_node*)calloc(1, sizeof(art_node16)); + break; + case NODE48: + n = (art_node*)calloc(1, sizeof(art_node48)); + break; + case NODE256: + n = (art_node*)calloc(1, sizeof(art_node256)); + break; + default: + abort(); + } + n->type = type; + return n; +} + +/** + * Initializes an ART tree + * @return 0 on success. + */ +int art_tree_init(art_tree *t) { + t->root = NULL; + t->size = 0; + return 0; +} + +// Recursively destroys the tree +static void destroy_node(art_node *n) { + // Break if null + if (!n) return; + + // Special case leafs + if (IS_LEAF(n)) { + free(LEAF_RAW(n)); + return; + } + + // Handle each node type + int i, idx; + union { + art_node4 *p1; + art_node16 *p2; + art_node48 *p3; + art_node256 *p4; + } p; + switch (n->type) { + case NODE4: + p.p1 = (art_node4*)n; + for (i=0;inum_children;i++) { + destroy_node(p.p1->children[i]); + } + break; + + case NODE16: + p.p2 = (art_node16*)n; + for (i=0;inum_children;i++) { + destroy_node(p.p2->children[i]); + } + break; + + case NODE48: + p.p3 = (art_node48*)n; + for (i=0;i<256;i++) { + idx = ((art_node48*)n)->keys[i]; + if (!idx) continue; + destroy_node(p.p3->children[idx-1]); + } + break; + + case NODE256: + p.p4 = (art_node256*)n; + for (i=0;i<256;i++) { + if (p.p4->children[i]) + destroy_node(p.p4->children[i]); + } + break; + + default: + abort(); + } + + // Free ourself on the way up + free(n); +} + +/** + * Destroys an ART tree + * @return 0 on success. + */ +int art_tree_destroy(art_tree *t) { + destroy_node(t->root); + return 0; +} + +/** + * Returns the size of the ART tree. + */ + +#ifndef BROKEN_GCC_C99_INLINE +extern inline uint64_t art_size(art_tree *t); +#endif + +static art_node** find_child(art_node *n, unsigned char c) { + int i, mask, bitfield; + union { + art_node4 *p1; + art_node16 *p2; + art_node48 *p3; + art_node256 *p4; + } p; + switch (n->type) { + case NODE4: + p.p1 = (art_node4*)n; + for (i=0 ; i < n->num_children; i++) { + /* this cast works around a bug in gcc 5.1 when unrolling loops + * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=59124 + */ + if (((unsigned char*)p.p1->keys)[i] == c) + return &p.p1->children[i]; + } + break; + + { + case NODE16: + p.p2 = (art_node16*)n; + + // support non-86 architectures + #ifdef __i386__ + // Compare the key to all 16 stored keys + __m128i cmp; + cmp = _mm_cmpeq_epi8(_mm_set1_epi8(c), + _mm_loadu_si128((__m128i*)p.p2->keys)); + + // Use a mask to ignore children that don't exist + mask = (1 << n->num_children) - 1; + bitfield = _mm_movemask_epi8(cmp) & mask; + #else + #ifdef __amd64__ + // Compare the key to all 16 stored keys + __m128i cmp; + cmp = _mm_cmpeq_epi8(_mm_set1_epi8(c), + _mm_loadu_si128((__m128i*)p.p2->keys)); + + // Use a mask to ignore children that don't exist + mask = (1 << n->num_children) - 1; + bitfield = _mm_movemask_epi8(cmp) & mask; + #else + // Compare the key to all 16 stored keys + bitfield = 0; + for (i = 0; i < 16; ++i) { + if (p.p2->keys[i] == c) + bitfield |= (1 << i); + } + + // Use a mask to ignore children that don't exist + mask = (1 << n->num_children) - 1; + bitfield &= mask; + #endif + #endif + + /* + * If we have a match (any bit set) then we can + * return the pointer match using ctz to get + * the index. + */ + if (bitfield) + return &p.p2->children[__builtin_ctz(bitfield)]; + break; + } + + case NODE48: + p.p3 = (art_node48*)n; + i = p.p3->keys[c]; + if (i) + return &p.p3->children[i-1]; + break; + + case NODE256: + p.p4 = (art_node256*)n; + if (p.p4->children[c]) + return &p.p4->children[c]; + break; + + default: + abort(); + } + return NULL; +} + +// Simple inlined if +static inline int min(int a, int b) { + return (a < b) ? a : b; +} + +/** + * Returns the number of prefix characters shared between + * the key and node. + */ +static int check_prefix(const art_node *n, const unsigned char *key, int key_len, int depth) { + int max_cmp = min(min(n->partial_len, MAX_PREFIX_LEN), key_len - depth); + int idx; + for (idx=0; idx < max_cmp; idx++) { + if (n->partial[idx] != key[depth+idx]) + return idx; + } + return idx; +} + +/** + * Checks if a leaf matches + * @return 0 on success. + */ +static int leaf_matches(const art_leaf *n, const unsigned char *key, int key_len, int depth) { + (void)depth; + // Fail if the key lengths are different + if (n->key_len != (uint32_t)key_len) return 1; + + // Compare the keys starting at the depth + return memcmp(n->key, key, key_len); +} + +/** + * Searches for a value in the ART tree + * @arg t The tree + * @arg key The key + * @arg key_len The length of the key + * @return NULL if the item was not found, otherwise + * the value pointer is returned. + */ +void* art_search(const art_tree *t, const unsigned char *key, int key_len) { + art_node **child; + art_node *n = t->root; + int prefix_len, depth = 0; + while (n) { + // Might be a leaf + if (IS_LEAF(n)) { + n = (art_node*)LEAF_RAW(n); + // Check if the expanded path matches + if (!leaf_matches((art_leaf*)n, key, key_len, depth)) { + return ((art_leaf*)n)->value; + } + return NULL; + } + + // Bail if the prefix does not match + if (n->partial_len) { + prefix_len = check_prefix(n, key, key_len, depth); + if (prefix_len != min(MAX_PREFIX_LEN, n->partial_len)) + return NULL; + depth = depth + n->partial_len; + } + + // Recursively search + child = find_child(n, key[depth]); + n = (child) ? *child : NULL; + depth++; + } + return NULL; +} + +// Find the minimum leaf under a node +static art_leaf* minimum(const art_node *n) { + // Handle base cases + if (!n) return NULL; + if (IS_LEAF(n)) return LEAF_RAW(n); + + int idx; + switch (n->type) { + case NODE4: + return minimum(((const art_node4*)n)->children[0]); + case NODE16: + return minimum(((const art_node16*)n)->children[0]); + case NODE48: + idx=0; + while (!((const art_node48*)n)->keys[idx]) idx++; + idx = ((const art_node48*)n)->keys[idx] - 1; + return minimum(((const art_node48*)n)->children[idx]); + case NODE256: + idx=0; + while (!((const art_node256*)n)->children[idx]) idx++; + return minimum(((const art_node256*)n)->children[idx]); + default: + abort(); + } +} + +// Find the maximum leaf under a node +static art_leaf* maximum(const art_node *n) { + // Handle base cases + if (!n) return NULL; + if (IS_LEAF(n)) return LEAF_RAW(n); + + int idx; + switch (n->type) { + case NODE4: + return maximum(((const art_node4*)n)->children[n->num_children-1]); + case NODE16: + return maximum(((const art_node16*)n)->children[n->num_children-1]); + case NODE48: + idx=255; + while (!((const art_node48*)n)->keys[idx]) idx--; + idx = ((const art_node48*)n)->keys[idx] - 1; + return maximum(((const art_node48*)n)->children[idx]); + case NODE256: + idx=255; + while (!((const art_node256*)n)->children[idx]) idx--; + return maximum(((const art_node256*)n)->children[idx]); + default: + abort(); + } +} + +/** + * Returns the minimum valued leaf + */ +art_leaf* art_minimum(art_tree *t) { + return minimum((art_node*)t->root); +} + +/** + * Returns the maximum valued leaf + */ +art_leaf* art_maximum(art_tree *t) { + return maximum((art_node*)t->root); +} + +static art_leaf* make_leaf(const unsigned char *key, int key_len, void *value) { + art_leaf *l = (art_leaf*)calloc(1, sizeof(art_leaf)+key_len); + l->value = value; + l->key_len = key_len; + memcpy(l->key, key, key_len); + return l; +} + +static int longest_common_prefix(art_leaf *l1, art_leaf *l2, int depth) { + int max_cmp = min(l1->key_len, l2->key_len) - depth; + int idx; + for (idx=0; idx < max_cmp; idx++) { + if (l1->key[depth+idx] != l2->key[depth+idx]) + return idx; + } + return idx; +} + +static void copy_header(art_node *dest, art_node *src) { + dest->num_children = src->num_children; + dest->partial_len = src->partial_len; + memcpy(dest->partial, src->partial, min(MAX_PREFIX_LEN, src->partial_len)); +} + +static void add_child256(art_node256 *n, art_node **ref, unsigned char c, void *child) { + (void)ref; + n->n.num_children++; + n->children[c] = (art_node*)child; +} + +static void add_child48(art_node48 *n, art_node **ref, unsigned char c, void *child) { + if (n->n.num_children < 48) { + int pos = 0; + while (n->children[pos]) pos++; + n->children[pos] = (art_node*)child; + n->keys[c] = pos + 1; + n->n.num_children++; + } else { + art_node256 *new_node = (art_node256*)alloc_node(NODE256); + for (int i=0;i<256;i++) { + if (n->keys[i]) { + new_node->children[i] = n->children[n->keys[i] - 1]; + } + } + copy_header((art_node*)new_node, (art_node*)n); + *ref = (art_node*)new_node; + free(n); + add_child256(new_node, ref, c, child); + } +} + +static void add_child16(art_node16 *n, art_node **ref, unsigned char c, void *child) { + if (n->n.num_children < 16) { + unsigned mask = (1 << n->n.num_children) - 1; + + // support non-x86 architectures + #ifdef __i386__ + __m128i cmp; + + // Compare the key to all 16 stored keys + cmp = _mm_cmplt_epi8(_mm_set1_epi8(c), + _mm_loadu_si128((__m128i*)n->keys)); + + // Use a mask to ignore children that don't exist + unsigned bitfield = _mm_movemask_epi8(cmp) & mask; + #else + #ifdef __amd64__ + __m128i cmp; + + // Compare the key to all 16 stored keys + cmp = _mm_cmplt_epi8(_mm_set1_epi8(c), + _mm_loadu_si128((__m128i*)n->keys)); + + // Use a mask to ignore children that don't exist + unsigned bitfield = _mm_movemask_epi8(cmp) & mask; + #else + // Compare the key to all 16 stored keys + unsigned bitfield = 0; + for (short i = 0; i < 16; ++i) { + if (c < n->keys[i]) + bitfield |= (1 << i); + } + + // Use a mask to ignore children that don't exist + bitfield &= mask; + #endif + #endif + + // Check if less than any + unsigned idx; + if (bitfield) { + idx = __builtin_ctz(bitfield); + memmove(n->keys+idx+1,n->keys+idx,n->n.num_children-idx); + memmove(n->children+idx+1,n->children+idx, + (n->n.num_children-idx)*sizeof(void*)); + } else + idx = n->n.num_children; + + // Set the child + n->keys[idx] = c; + n->children[idx] = (art_node*)child; + n->n.num_children++; + + } else { + art_node48 *new_node = (art_node48*)alloc_node(NODE48); + + // Copy the child pointers and populate the key map + memcpy(new_node->children, n->children, + sizeof(void*)*n->n.num_children); + for (int i=0;in.num_children;i++) { + new_node->keys[n->keys[i]] = i + 1; + } + copy_header((art_node*)new_node, (art_node*)n); + *ref = (art_node*)new_node; + free(n); + add_child48(new_node, ref, c, child); + } +} + +static void add_child4(art_node4 *n, art_node **ref, unsigned char c, void *child) { + if (n->n.num_children < 4) { + int idx; + for (idx=0; idx < n->n.num_children; idx++) { + if (c < n->keys[idx]) break; + } + + // Shift to make room + memmove(n->keys+idx+1, n->keys+idx, n->n.num_children - idx); + memmove(n->children+idx+1, n->children+idx, + (n->n.num_children - idx)*sizeof(void*)); + + // Insert element + n->keys[idx] = c; + n->children[idx] = (art_node*)child; + n->n.num_children++; + + } else { + art_node16 *new_node = (art_node16*)alloc_node(NODE16); + + // Copy the child pointers and the key map + memcpy(new_node->children, n->children, + sizeof(void*)*n->n.num_children); + memcpy(new_node->keys, n->keys, + sizeof(unsigned char)*n->n.num_children); + copy_header((art_node*)new_node, (art_node*)n); + *ref = (art_node*)new_node; + free(n); + add_child16(new_node, ref, c, child); + } +} + +static void add_child(art_node *n, art_node **ref, unsigned char c, void *child) { + switch (n->type) { + case NODE4: + return add_child4((art_node4*)n, ref, c, child); + case NODE16: + return add_child16((art_node16*)n, ref, c, child); + case NODE48: + return add_child48((art_node48*)n, ref, c, child); + case NODE256: + return add_child256((art_node256*)n, ref, c, child); + default: + abort(); + } +} + +/** + * Calculates the index at which the prefixes mismatch + */ +static int prefix_mismatch(const art_node *n, const unsigned char *key, int key_len, int depth) { + int max_cmp = min(min(MAX_PREFIX_LEN, n->partial_len), key_len - depth); + int idx; + for (idx=0; idx < max_cmp; idx++) { + if (n->partial[idx] != key[depth+idx]) + return idx; + } + + // If the prefix is short we can avoid finding a leaf + if (n->partial_len > MAX_PREFIX_LEN) { + // Prefix is longer than what we've checked, find a leaf + art_leaf *l = minimum(n); + max_cmp = min(l->key_len, key_len)- depth; + for (; idx < max_cmp; idx++) { + if (l->key[idx+depth] != key[depth+idx]) + return idx; + } + } + return idx; +} + +static void* recursive_insert(art_node *n, art_node **ref, const unsigned char *key, int key_len, void *value, int depth, int *old, int replace) { + // If we are at a NULL node, inject a leaf + if (!n) { + *ref = (art_node*)SET_LEAF(make_leaf(key, key_len, value)); + return NULL; + } + + // If we are at a leaf, we need to replace it with a node + if (IS_LEAF(n)) { + art_leaf *l = LEAF_RAW(n); + + // Check if we are updating an existing value + if (!leaf_matches(l, key, key_len, depth)) { + *old = 1; + void *old_val = l->value; + if(replace) l->value = value; + return old_val; + } + + // New value, we must split the leaf into a node4 + art_node4 *new_node = (art_node4*)alloc_node(NODE4); + + // Create a new leaf + art_leaf *l2 = make_leaf(key, key_len, value); + + // Determine longest prefix + int longest_prefix = longest_common_prefix(l, l2, depth); + new_node->n.partial_len = longest_prefix; + memcpy(new_node->n.partial, key+depth, min(MAX_PREFIX_LEN, longest_prefix)); + // Add the leafs to the new node4 + *ref = (art_node*)new_node; + add_child4(new_node, ref, l->key[depth+longest_prefix], SET_LEAF(l)); + add_child4(new_node, ref, l2->key[depth+longest_prefix], SET_LEAF(l2)); + return NULL; + } + + // Check if given node has a prefix + if (n->partial_len) { + // Determine if the prefixes differ, since we need to split + int prefix_diff = prefix_mismatch(n, key, key_len, depth); + if ((uint32_t)prefix_diff >= n->partial_len) { + depth += n->partial_len; + goto RECURSE_SEARCH; + } + + // Create a new node + art_node4 *new_node = (art_node4*)alloc_node(NODE4); + *ref = (art_node*)new_node; + new_node->n.partial_len = prefix_diff; + memcpy(new_node->n.partial, n->partial, min(MAX_PREFIX_LEN, prefix_diff)); + + // Adjust the prefix of the old node + if (n->partial_len <= MAX_PREFIX_LEN) { + add_child4(new_node, ref, n->partial[prefix_diff], n); + n->partial_len -= (prefix_diff+1); + memmove(n->partial, n->partial+prefix_diff+1, + min(MAX_PREFIX_LEN, n->partial_len)); + } else { + n->partial_len -= (prefix_diff+1); + art_leaf *l = minimum(n); + add_child4(new_node, ref, l->key[depth+prefix_diff], n); + memcpy(n->partial, l->key+depth+prefix_diff+1, + min(MAX_PREFIX_LEN, n->partial_len)); + } + + // Insert the new leaf + art_leaf *l = make_leaf(key, key_len, value); + add_child4(new_node, ref, key[depth+prefix_diff], SET_LEAF(l)); + return NULL; + } + +RECURSE_SEARCH:; + + // Find a child to recurse to + art_node **child = find_child(n, key[depth]); + if (child) { + return recursive_insert(*child, child, key, key_len, value, depth+1, old, replace); + } + + // No child, node goes within us + art_leaf *l = make_leaf(key, key_len, value); + add_child(n, ref, key[depth], SET_LEAF(l)); + return NULL; +} + +/** + * inserts a new value into the art tree + * @arg t the tree + * @arg key the key + * @arg key_len the length of the key + * @arg value opaque value. + * @return null if the item was newly inserted, otherwise + * the old value pointer is returned. + */ +void* art_insert(art_tree *t, const unsigned char *key, int key_len, void *value) { + int old_val = 0; + void *old = recursive_insert(t->root, &t->root, key, key_len, value, 0, &old_val, 1); + if (!old_val) t->size++; + return old; +} + +/** + * inserts a new value into the art tree (no replace) + * @arg t the tree + * @arg key the key + * @arg key_len the length of the key + * @arg value opaque value. + * @return null if the item was newly inserted, otherwise + * the old value pointer is returned. + */ +void* art_insert_no_replace(art_tree *t, const unsigned char *key, int key_len, void *value) { + int old_val = 0; + void *old = recursive_insert(t->root, &t->root, key, key_len, value, 0, &old_val, 0); + if (!old_val) t->size++; + return old; +} + +static void remove_child256(art_node256 *n, art_node **ref, unsigned char c) { + n->children[c] = NULL; + n->n.num_children--; + + // Resize to a node48 on underflow, not immediately to prevent + // trashing if we sit on the 48/49 boundary + if (n->n.num_children == 37) { + art_node48 *new_node = (art_node48*)alloc_node(NODE48); + *ref = (art_node*)new_node; + copy_header((art_node*)new_node, (art_node*)n); + + int pos = 0; + for (int i=0;i<256;i++) { + if (n->children[i]) { + new_node->children[pos] = n->children[i]; + new_node->keys[i] = pos + 1; + pos++; + } + } + free(n); + } +} + +static void remove_child48(art_node48 *n, art_node **ref, unsigned char c) { + int pos = n->keys[c]; + n->keys[c] = 0; + n->children[pos-1] = NULL; + n->n.num_children--; + + if (n->n.num_children == 12) { + art_node16 *new_node = (art_node16*)alloc_node(NODE16); + *ref = (art_node*)new_node; + copy_header((art_node*)new_node, (art_node*)n); + + int child = 0; + for (int i=0;i<256;i++) { + pos = n->keys[i]; + if (pos) { + new_node->keys[child] = i; + new_node->children[child] = n->children[pos - 1]; + child++; + } + } + free(n); + } +} + +static void remove_child16(art_node16 *n, art_node **ref, art_node **l) { + int pos = l - n->children; + memmove(n->keys+pos, n->keys+pos+1, n->n.num_children - 1 - pos); + memmove(n->children+pos, n->children+pos+1, (n->n.num_children - 1 - pos)*sizeof(void*)); + n->n.num_children--; + + if (n->n.num_children == 3) { + art_node4 *new_node = (art_node4*)alloc_node(NODE4); + *ref = (art_node*)new_node; + copy_header((art_node*)new_node, (art_node*)n); + memcpy(new_node->keys, n->keys, 4); + memcpy(new_node->children, n->children, 4*sizeof(void*)); + free(n); + } +} + +static void remove_child4(art_node4 *n, art_node **ref, art_node **l) { + int pos = l - n->children; + memmove(n->keys+pos, n->keys+pos+1, n->n.num_children - 1 - pos); + memmove(n->children+pos, n->children+pos+1, (n->n.num_children - 1 - pos)*sizeof(void*)); + n->n.num_children--; + + // Remove nodes with only a single child + if (n->n.num_children == 1) { + art_node *child = n->children[0]; + if (!IS_LEAF(child)) { + // Concatenate the prefixes + int prefix = n->n.partial_len; + if (prefix < MAX_PREFIX_LEN) { + n->n.partial[prefix] = n->keys[0]; + prefix++; + } + if (prefix < MAX_PREFIX_LEN) { + int sub_prefix = min(child->partial_len, MAX_PREFIX_LEN - prefix); + memcpy(n->n.partial+prefix, child->partial, sub_prefix); + prefix += sub_prefix; + } + + // Store the prefix in the child + memcpy(child->partial, n->n.partial, min(prefix, MAX_PREFIX_LEN)); + child->partial_len += n->n.partial_len + 1; + } + *ref = child; + free(n); + } +} + +static void remove_child(art_node *n, art_node **ref, unsigned char c, art_node **l) { + switch (n->type) { + case NODE4: + return remove_child4((art_node4*)n, ref, l); + case NODE16: + return remove_child16((art_node16*)n, ref, l); + case NODE48: + return remove_child48((art_node48*)n, ref, c); + case NODE256: + return remove_child256((art_node256*)n, ref, c); + default: + abort(); + } +} + +static art_leaf* recursive_delete(art_node *n, art_node **ref, const unsigned char *key, int key_len, int depth) { + // Search terminated + if (!n) return NULL; + + // Handle hitting a leaf node + if (IS_LEAF(n)) { + art_leaf *l = LEAF_RAW(n); + if (!leaf_matches(l, key, key_len, depth)) { + *ref = NULL; + return l; + } + return NULL; + } + + // Bail if the prefix does not match + if (n->partial_len) { + int prefix_len = check_prefix(n, key, key_len, depth); + if (prefix_len != min(MAX_PREFIX_LEN, n->partial_len)) { + return NULL; + } + depth = depth + n->partial_len; + } + + // Find child node + art_node **child = find_child(n, key[depth]); + if (!child) return NULL; + + // If the child is leaf, delete from this node + if (IS_LEAF(*child)) { + art_leaf *l = LEAF_RAW(*child); + if (!leaf_matches(l, key, key_len, depth)) { + remove_child(n, ref, key[depth], child); + return l; + } + return NULL; + + // Recurse + } else { + return recursive_delete(*child, child, key, key_len, depth+1); + } +} + +/** + * Deletes a value from the ART tree + * @arg t The tree + * @arg key The key + * @arg key_len The length of the key + * @return NULL if the item was not found, otherwise + * the value pointer is returned. + */ +void* art_delete(art_tree *t, const unsigned char *key, int key_len) { + art_leaf *l = recursive_delete(t->root, &t->root, key, key_len, 0); + if (l) { + t->size--; + void *old = l->value; + free(l); + return old; + } + return NULL; +} + +// Recursively iterates over the tree +static int recursive_iter(art_node *n, art_callback cb, void *data) { + // Handle base cases + if (!n) return 0; + if (IS_LEAF(n)) { + art_leaf *l = LEAF_RAW(n); + return cb(data, (const unsigned char*)l->key, l->key_len, l->value); + } + + int idx, res; + switch (n->type) { + case NODE4: + for (int i=0; i < n->num_children; i++) { + res = recursive_iter(((art_node4*)n)->children[i], cb, data); + if (res) return res; + } + break; + + case NODE16: + for (int i=0; i < n->num_children; i++) { + res = recursive_iter(((art_node16*)n)->children[i], cb, data); + if (res) return res; + } + break; + + case NODE48: + for (int i=0; i < 256; i++) { + idx = ((art_node48*)n)->keys[i]; + if (!idx) continue; + + res = recursive_iter(((art_node48*)n)->children[idx-1], cb, data); + if (res) return res; + } + break; + + case NODE256: + for (int i=0; i < 256; i++) { + if (!((art_node256*)n)->children[i]) continue; + res = recursive_iter(((art_node256*)n)->children[i], cb, data); + if (res) return res; + } + break; + + default: + abort(); + } + return 0; +} + +/** + * Iterates through the entries pairs in the map, + * invoking a callback for each. The call back gets a + * key, value for each and returns an integer stop value. + * If the callback returns non-zero, then the iteration stops. + * @arg t The tree to iterate over + * @arg cb The callback function to invoke + * @arg data Opaque handle passed to the callback + * @return 0 on success, or the return of the callback. + */ +int art_iter(art_tree *t, art_callback cb, void *data) { + return recursive_iter(t->root, cb, data); +} + +/** + * Checks if a leaf prefix matches + * @return 0 on success. + */ +static int leaf_prefix_matches(const art_leaf *n, const unsigned char *prefix, int prefix_len) { + // Fail if the key length is too short + if (n->key_len < (uint32_t)prefix_len) return 1; + + // Compare the keys + return memcmp(n->key, prefix, prefix_len); +} + +/** + * Iterates through the entries pairs in the map, + * invoking a callback for each that matches a given prefix. + * The call back gets a key, value for each and returns an integer stop value. + * If the callback returns non-zero, then the iteration stops. + * @arg t The tree to iterate over + * @arg prefix The prefix of keys to read + * @arg prefix_len The length of the prefix + * @arg cb The callback function to invoke + * @arg data Opaque handle passed to the callback + * @return 0 on success, or the return of the callback. + */ +int art_iter_prefix(art_tree *t, const unsigned char *key, int key_len, art_callback cb, void *data) { + art_node **child; + art_node *n = t->root; + int prefix_len, depth = 0; + while (n) { + // Might be a leaf + if (IS_LEAF(n)) { + n = (art_node*)LEAF_RAW(n); + // Check if the expanded path matches + if (!leaf_prefix_matches((art_leaf*)n, key, key_len)) { + art_leaf *l = (art_leaf*)n; + return cb(data, (const unsigned char*)l->key, l->key_len, l->value); + } + return 0; + } + + // If the depth matches the prefix, we need to handle this node + if (depth == key_len) { + art_leaf *l = minimum(n); + if (!leaf_prefix_matches(l, key, key_len)) + return recursive_iter(n, cb, data); + return 0; + } + + // Bail if the prefix does not match + if (n->partial_len) { + prefix_len = prefix_mismatch(n, key, key_len, depth); + + // Guard if the mis-match is longer than the MAX_PREFIX_LEN + if ((uint32_t)prefix_len > n->partial_len) { + prefix_len = n->partial_len; + } + + // If there is no match, search is terminated + if (!prefix_len) { + return 0; + + // If we've matched the prefix, iterate on this node + } else if (depth + prefix_len == key_len) { + return recursive_iter(n, cb, data); + } + + // if there is a full match, go deeper + depth = depth + n->partial_len; + } + + // Recursively search + child = find_child(n, key[depth]); + n = (child) ? *child : NULL; + depth++; + } + return 0; +} diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..272504e --- /dev/null +++ b/src/main.c @@ -0,0 +1,89 @@ +#include +#include + +#include +#include +#include + +#include + +int main(void) { + if (RouteTable_Init() < 0) { + fprintf(stderr, "Failed to initialize route tables\n"); + return EXIT_FAILURE; + } + + interface_v4_t* iface = Interface_Create_v4("0.0.0.0", NULL, NULL); + interface_v6_t* iface6 = Interface_Create_v6("::", NULL, NULL); + if (!iface || !iface6) { + fprintf(stderr, "Failed to create interfaces\n"); + RouteTable_Cleanup(); + return EXIT_FAILURE; + } + + route_table_v4_entry_t entry = {.destination = 0x01000000, .mask = 0xFF000000, .nextHop = 0xC0A80101}; // Example route: 1.0.0.0/8 via 192.168.1.1 + if (RouteTable_AddRoute_v4(entry.destination, entry.mask, entry.nextHop) < 0) { + fprintf(stderr, "Failed to add route to IPv4 route table\n"); + } + + route_table_v4_entry_t entry2 = {.destination = 0x01020000, .mask = 0xFFFF0000, .nextHop = 0xC0A80102}; // Example route: 1.2.0.0/16 via 192.168.1.1 - more specific than the previous one + if (RouteTable_AddRoute_v4(entry2.destination, entry2.mask, entry2.nextHop) < 0) { + fprintf(stderr, "Failed to add route to IPv4 route table\n"); + } + + // Get said route + uint32_t nextHop; + if (RouteTable_GetNextHop_v4(0x01020005, &nextHop) == 0) { // 1.2.0.5 should match the /16 route, not the /8 route + char destStr[16], nextHopStr[16]; + IPUtils_PrintIPv4(0x01020005, destStr); + IPUtils_PrintIPv4(nextHop, nextHopStr); + printf("Next hop for destination %s is %s\n", destStr, nextHopStr); + } else { + char destStr[16]; + IPUtils_PrintIPv4(0x01020005, destStr); + fprintf(stderr, "Failed to get next hop for destination %s\n", destStr); + } + + if (RouteTable_GetNextHop_v4(0x01010001, &nextHop) == 0) { // 1.1.0.1 should match the /8 route, not the /16 route + char destStr[16], nextHopStr[16]; + IPUtils_PrintIPv4(0x01010001, destStr); + IPUtils_PrintIPv4(nextHop, nextHopStr); + printf("Next hop for destination %s is %s\n", destStr, nextHopStr); + } else { + char destStr[16]; + IPUtils_PrintIPv4(0x01010001, destStr); + fprintf(stderr, "Failed to get next hop for destination %s\n", destStr); + } + + // Some IPv6 routes + __uint128_t dest6 = (((__uint128_t)0x20010DB8 << 96) | ((__uint128_t)0x00000000 << 64) | ((__uint128_t)0x00000000 << 32) | (__uint128_t)0x00000001); // 2001:0db8::1 + __uint128_t mask6 = IPUtils_CIDRToMaskv6(64); + __uint128_t nextHop6 = (((__uint128_t)0x20010DB8 << 96) | ((__uint128_t)0x00000000 << 64) | ((__uint128_t)0x00000000 << 32) | (__uint128_t)0x00000002); // 2001:0db8::2 + if (RouteTable_AddRoute_v6(dest6, mask6, nextHop6) < 0) { + fprintf(stderr, "Failed to add route to IPv6 route table\n"); + } + + // Get said IPv6 route + __uint128_t nextHop6Out; + if (RouteTable_GetNextHop_v6(dest6, &nextHop6Out) == 0) { + char destStr[40], nextHopStr[40]; + IPUtils_PrintIPv6(dest6, destStr); + IPUtils_PrintIPv6(nextHop6Out, nextHopStr); + printf("Next hop for destination %s is %s\n", destStr, nextHopStr); + } else { + char destStr[40]; + IPUtils_PrintIPv6(dest6, destStr); + fprintf(stderr, "Failed to get next hop for destination %s\n", destStr); + } + + while (1) { + // Main loop can be used for future tasks such as monitoring, CLI, etc. + // For now, it just keeps the program running until interrupted. + pause(); + } + + RouteTable_Cleanup(); + Interface_Cleanup(); + + return 0; +}