├── .gitignore ├── CMakeLists.txt ├── cmake └── FindPCAP.cmake ├── codelist.md ├── include ├── arp.h ├── client_request.h ├── core.h ├── device.h ├── ip.h ├── packetio.h ├── rip.h ├── socket.h ├── tcp.h ├── udp.h └── util.h ├── not-implemented.md ├── src ├── arp.cc ├── core.cc ├── device.cc ├── ip.cc ├── packetio.cc ├── rip.cc ├── socket.cc ├── tcp.cc ├── udp.cc └── util.cc ├── tests ├── CMakeLists.txt ├── test_arping.cc ├── test_eth_capture.cc ├── test_logging.cc ├── test_mgmt_device.cc ├── test_server.cc └── test_unix_socket.cc ├── vnet-setup └── writing-task.md /.gitignore: -------------------------------------------------------------------------------- 1 | build/* 2 | .vscode/* -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.8) 2 | 3 | project(KHTcp) 4 | 5 | set(lib_name "khtcp") 6 | set(CMAKE_CXX_STANDARD 17) 7 | set(CMAKE_CXX_STANDARD_REQUIRED 17) 8 | 9 | file(GLOB proj_headers "include/*.h") 10 | file(GLOB proj_sources "src/*.cc") 11 | 12 | set(utils_root "${PROJECT_SOURCE_DIR}/cmake") 13 | file(GLOB cmake_utils "${utils_root}/*.cmake") 14 | foreach(util IN LISTS cmake_utils) 15 | include(${util}) 16 | endforeach(util IN LISTS cmake_utils) 17 | 18 | find_package(Boost 1.67.0 REQUIRED COMPONENTS log) 19 | include_directories("include/" ${Boost_INCLUDE_DIRS} ${PCAP_INCLUDE_DIRS}) 20 | link_libraries(${Boost_LIBRARIES} ${PCAP_LIBRARIES}) 21 | add_definitions(-DBOOST_LOG_DYN_LINK) 22 | 23 | 24 | add_library(${lib_name} SHARED ${proj_headers} ${proj_sources}) 25 | 26 | add_subdirectory(tests) 27 | -------------------------------------------------------------------------------- /cmake/FindPCAP.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # $Id$ 3 | # 4 | ################################################################### 5 | # 6 | # Copyright (c) 2006 Frederic Heem, 7 | # All rights reserved. 8 | # 9 | # Redistribution and use in source and binary forms, with or without 10 | # modification, are permitted provided that the following conditions 11 | # are met: 12 | # 13 | # * Redistributions of source code must retain the above copyright 14 | # notice, this list of conditions and the following disclaimer. 15 | # 16 | # * Redistributions in binary form must reproduce the above copyright 17 | # notice, this list of conditions and the following disclaimer in 18 | # the documentation and/or other materials provided with the 19 | # distribution. 20 | # 21 | # * Neither the name of the Telsey nor the names of its 22 | # contributors may be used to endorse or promote products derived 23 | # from this software without specific prior written permission. 24 | # 25 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 26 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 27 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 28 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 29 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 30 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 31 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 32 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 33 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 34 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 35 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 36 | # POSSIBILITY OF SUCH DAMAGE. 37 | # 38 | ################################################################### 39 | # - Find pcap 40 | # Find the PCAP includes and library 41 | # http://www.tcpdump.org/ 42 | # 43 | # The environment variable PCAPDIR allows to specficy where to find 44 | # libpcap in non standard location. 45 | # 46 | # PCAP_INCLUDE_DIRS - where to find pcap.h, etc. 47 | # PCAP_LIBRARIES - List of libraries when using pcap. 48 | # PCAP_FOUND - True if pcap found. 49 | 50 | 51 | IF(EXISTS $ENV{PCAPDIR}) 52 | FIND_PATH(PCAP_INCLUDE_DIR 53 | NAMES 54 | pcap/pcap.h 55 | pcap.h 56 | PATHS 57 | $ENV{PCAPDIR} 58 | NO_DEFAULT_PATH 59 | ) 60 | 61 | FIND_LIBRARY(PCAP_LIBRARY 62 | NAMES 63 | pcap 64 | PATHS 65 | $ENV{PCAPDIR} 66 | NO_DEFAULT_PATH 67 | ) 68 | 69 | 70 | ELSE(EXISTS $ENV{PCAPDIR}) 71 | 72 | FIND_PATH(PCAP_INCLUDE_DIR 73 | NAMES 74 | pcap/pcap.h 75 | pcap.h 76 | HINTS 77 | "${PCAP_HINTS}/include" 78 | ) 79 | 80 | FIND_LIBRARY(PCAP_LIBRARY 81 | NAMES 82 | pcap 83 | wpcap 84 | HINTS 85 | "${PCAP_HINTS}/lib" 86 | ) 87 | 88 | ENDIF(EXISTS $ENV{PCAPDIR}) 89 | 90 | SET(PCAP_INCLUDE_DIRS ${PCAP_INCLUDE_DIR}) 91 | SET(PCAP_LIBRARIES ${PCAP_LIBRARY}) 92 | 93 | IF(PCAP_INCLUDE_DIRS) 94 | MESSAGE(STATUS "Pcap include dirs set to ${PCAP_INCLUDE_DIRS}") 95 | ELSE(PCAP_INCLUDE_DIRS) 96 | MESSAGE(FATAL " Pcap include dirs cannot be found") 97 | ENDIF(PCAP_INCLUDE_DIRS) 98 | 99 | IF(PCAP_LIBRARIES) 100 | MESSAGE(STATUS "Pcap library set to ${PCAP_LIBRARIES}") 101 | ELSE(PCAP_LIBRARIES) 102 | MESSAGE(FATAL "Pcap library cannot be found") 103 | ENDIF(PCAP_LIBRARIES) 104 | 105 | #Functions 106 | INCLUDE(CheckFunctionExists) 107 | INCLUDE(CheckVariableExists) 108 | SET(CMAKE_REQUIRED_INCLUDES ${PCAP_INCLUDE_DIRS}) 109 | SET(CMAKE_REQUIRED_LIBRARIES ${PCAP_LIBRARIES}) 110 | CHECK_VARIABLE_EXISTS("pcap_version" HAVE_PCAP_VERSION) 111 | CHECK_FUNCTION_EXISTS("pcap_open_dead" HAVE_PCAP_OPEN_DEAD) 112 | CHECK_FUNCTION_EXISTS("pcap_freecode" HAVE_PCAP_FREECODE) 113 | # 114 | # Note: for pcap_breakloop() and pcap_findalldevs(), the autoconf script 115 | # checks for more than just whether the function exists, it also checks 116 | # for whether pcap.h declares it; Mac OS X software/security updates can 117 | # update libpcap without updating the headers. 118 | # 119 | CHECK_FUNCTION_EXISTS("pcap_breakloop" HAVE_PCAP_BREAKLOOP) 120 | CHECK_FUNCTION_EXISTS("pcap_create" HAVE_PCAP_CREATE) 121 | CHECK_FUNCTION_EXISTS("pcap_datalink_name_to_val" HAVE_PCAP_DATALINK_NAME_TO_VAL) 122 | CHECK_FUNCTION_EXISTS("pcap_datalink_val_to_description" HAVE_PCAP_DATALINK_VAL_TO_DESCRIPTION) 123 | CHECK_FUNCTION_EXISTS("pcap_datalink_val_to_name" HAVE_PCAP_DATALINK_VAL_TO_NAME) 124 | CHECK_FUNCTION_EXISTS("pcap_findalldevs" HAVE_PCAP_FINDALLDEVS) 125 | CHECK_FUNCTION_EXISTS("pcap_free_datalinks" HAVE_PCAP_FREE_DATALINKS) 126 | CHECK_FUNCTION_EXISTS("pcap_get_selectable_fd" HAVE_PCAP_GET_SELECTABLE_FD) 127 | CHECK_FUNCTION_EXISTS("pcap_lib_version" HAVE_PCAP_LIB_VERSION) 128 | CHECK_FUNCTION_EXISTS("pcap_list_datalinks" HAVE_PCAP_LIST_DATALINKS) 129 | CHECK_FUNCTION_EXISTS("pcap_set_datalink" HAVE_PCAP_SET_DATALINK) 130 | # Remote pcap checks 131 | CHECK_FUNCTION_EXISTS("pcap_open" H_PCAP_OPEN) 132 | CHECK_FUNCTION_EXISTS("pcap_findalldevs_ex" H_FINDALLDEVS_EX) 133 | CHECK_FUNCTION_EXISTS("pcap_createsrcstr" H_CREATESRCSTR) 134 | if(H_PCAP_OPEN AND H_FINDALLDEVS_EX AND H_CREATESRCSTR) 135 | SET(HAVE_PCAP_REMOTE 1) 136 | SET(HAVE_REMOTE 1) 137 | endif() 138 | # reset vars 139 | SET(CMAKE_REQUIRED_INCLUDES "") 140 | SET(CMAKE_REQUIRED_LIBRARIES "") 141 | 142 | INCLUDE(FindPackageHandleStandardArgs) 143 | FIND_PACKAGE_HANDLE_STANDARD_ARGS(PCAP DEFAULT_MSG PCAP_INCLUDE_DIRS PCAP_LIBRARIES) 144 | 145 | MARK_AS_ADVANCED( 146 | PCAP_LIBRARIES 147 | PCAP_INCLUDE_DIRS 148 | ) 149 | -------------------------------------------------------------------------------- /codelist.md: -------------------------------------------------------------------------------- 1 | # Codelist for each programming task 2 | 3 | ## Link-layer: Packet I/O On Ethernet 4 | 5 | - Device management functions in `khtcp/include/device.h`. 6 | - Sending/receiving Ethernet II frames functions in `khtcp/include/packetio.h`. 7 | - Server/client model. Server code in `khtcp/` and client code in `khtcpc/`. 8 | - The client and server communicates via a Unix Abstract Domain Socket (which will work independently when there are multiple network namespaces). 9 | 10 | ## Network-layer: IP Protocol 11 | 12 | - Client side: 13 | - Sending/receiving IP packets functions in `khtcpc/include/ip.h`. 14 | - Server side: 15 | - Sending/receiving IP packets functions in `khtcp/include/ip.h`. 16 | - Manual routing table manipulation functions in `khtcp/include/ip.h`. 17 | - Routing algorithm (RIPv2) function in `khtcp/include/rip.h` and `khtcp/src/rip.cc`. 18 | 19 | ## Transport-layer: UDP protocol 20 | 21 | - Client side: 22 | - Socket-like APIs (for `SOCK_DGRAM`) provided in `khtcpc/include/socket.h`. 23 | - Server side: 24 | - Sending/receiving IP packets functions in `khtcp/include/udp.h`. 25 | 26 | ## Test/Evalutaion 27 | 28 | To test, compile both client and server code. Run the `test_server` executable first, then run any of the test clients (you can run them concurrently) under the same network namespace. 29 | 30 | - Server-side: 31 | - Arping (ARP Ping) test program has been implemented to test Ethernet II functionality. Test program as `khtcp/tests/test_arping.cc`. 32 | - Test server program (direct startup, runs RIP and ARP reply) as `khtcp/tests/test_server.cc`. 33 | - Client-side Ethernet, ARP and IP sending/receiving has been implemented to test Client-Server functionality as: 34 | - `khtcpc/tests/test_eth.cc` for sending and receiving Ethernet frame 35 | - `khtcpc/tests/test_arping.cc` for client-side ARPing 36 | - `khtcpc/tests/test_ip.cc` for sending and receiving IP 37 | - `khtcpc/tests/test_udp_send.cc` for testing SOCK_DGRAM send. 38 | - `khtcpc/tests/test_udp_recv.cc` for testing SOCK_DGRAM recv. 39 | 40 | The UDP tests shall generate packets that can be successfully delivered across the Internet (where normal UDP would work, including most NATs). -------------------------------------------------------------------------------- /include/arp.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file arp.h 3 | * @author Pengcheng Xu 4 | * @brief The Address Resolution Protocol. 5 | * @version 0.1 6 | * @date 2019-10-04 7 | * 8 | * @copyright Copyright (c) 2019 9 | * 10 | */ 11 | 12 | #ifndef __KHTCP_ARP_H_ 13 | #define __KHTCP_ARP_H_ 14 | 15 | #include "device.h" 16 | #include "ip.h" 17 | #include "packetio.h" 18 | 19 | #include 20 | #include 21 | 22 | namespace khtcp { 23 | namespace arp { 24 | const int NEIGHBOR_TIMEOUT = 20; 25 | 26 | /** 27 | * @brief The read handler type. 28 | * 29 | * consumed(dev_id, opcode, sender_mac, sender_ip, target_mac, target_ip) 30 | */ 31 | using read_handler_t = std::function; 33 | 34 | /** 35 | * @brief The write handler type. 36 | * 37 | * (dev_id, ret) 38 | */ 39 | using write_handler_t = std::function; 40 | 41 | /** 42 | * @brief The resolve MAC handler type. 43 | * 44 | * (ret, MAC) 45 | * 46 | * When a resolution fails (perhaps due to an unanswered ARP), ret 47 | * will carry the errno. 48 | */ 49 | using resolve_mac_handler_t = std::function; 50 | 51 | /** 52 | * @brief Start ARP auto answering. 53 | * 54 | * @param dev_id 55 | */ 56 | void start(int dev_id); 57 | 58 | /** 59 | * @brief The ARP header. 60 | */ 61 | struct __attribute__((packed)) arp_header_t { 62 | uint16_t hardware_type; 63 | uint16_t protocol_type; 64 | uint8_t hardware_size; 65 | uint8_t protocol_size; 66 | uint16_t opcode; 67 | }; 68 | 69 | static const uint16_t ethertype = 0x0806; 70 | 71 | /** 72 | * @brief Asynchronously read an ARP packet. 73 | * 74 | * This ARP stack only supports Ethernet/IP. 75 | * 76 | * @param dev_id device id to receive ARP packet from. 77 | * @param handler handler to call once packet has been received. 78 | * @param client_id id for calling client, 0 for local (a server call). 79 | */ 80 | void async_read_arp(int dev_id, read_handler_t &&handler, int client_id = 0); 81 | 82 | /** 83 | * @brief Asynchronously write an ARP packet. 84 | * 85 | * @param dev_id device id to send ARP packet to. 86 | * @param opcode ARP opcode. 87 | * @param sender_mac sender MAC. 88 | * @param sender_ip sender IP. 89 | * @param target_mac target MAC. 90 | * @param target_ip target IP. 91 | * @param handler handler to call once packet has been received. 92 | * @param client_id id for calling client, 0 for local (a server call). 93 | */ 94 | void async_write_arp(int dev_id, uint16_t opcode, const eth::addr_t sender_mac, 95 | const ip::addr_t sender_ip, const eth::addr_t target_mac, 96 | const ip::addr_t target_ip, write_handler_t &&handler, 97 | int client_id = 0); 98 | 99 | /** 100 | * @brief Asynchronously resolve MAC address for given IP destination. 101 | * 102 | * Note that if the MAC has already been resolved and has not expired, this 103 | * function will call the handler synchronously. 104 | * 105 | * @param dev_id device ID to send resolving ARP on. 106 | * @param dst destination IP. 107 | * @param handler handler to call when MAC resolving is done. 108 | */ 109 | void async_resolve_mac(int dev_id, const ip::addr_t dst, 110 | resolve_mac_handler_t &&handler); 111 | 112 | void scan_arp_table(); 113 | 114 | } // namespace arp 115 | } // namespace khtcp 116 | 117 | #endif -------------------------------------------------------------------------------- /include/client_request.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file client_request.h 3 | * @author Pengcheng Xu 4 | * @brief Request from client. Kept in sync with khtcpc. 5 | * @version 0.1 6 | * @date 2019-10-07 7 | * 8 | * @copyright Copyright (c) 2019 9 | * 10 | */ 11 | 12 | #ifndef __KHTCP_CLIENT_REQUEST_H_ 13 | #define __KHTCP_CLIENT_REQUEST_H_ 14 | 15 | #include 16 | #include 17 | 18 | #define IFNAMSIZE 16 19 | 20 | namespace khtcp { 21 | // stupid macro from pcap.h 22 | #undef SOCKET 23 | 24 | enum request_type { 25 | FIND_DEVICE, 26 | GET_DEVICE_MAC, 27 | GET_DEVICE_IP, 28 | 29 | ETHERNET_READ, 30 | ETHERNET_WRITE, 31 | 32 | ARP_READ, 33 | ARP_WRITE, 34 | 35 | IP_READ, 36 | IP_WRITE, 37 | 38 | SOCKET, 39 | CLOSE, 40 | BIND, 41 | LISTEN, 42 | CONNECT, 43 | ACCEPT, 44 | SENDTO, 45 | RECVFROM, 46 | SEND, 47 | RECV, 48 | }; 49 | 50 | struct request { 51 | request_type type; 52 | int id; 53 | int payload_len; 54 | union { 55 | struct { 56 | char name[IFNAMSIZE]; 57 | } find_device; 58 | struct { 59 | int dev_id; 60 | } get_device_mac; 61 | struct { 62 | int dev_id; 63 | } get_device_ip; 64 | 65 | struct { 66 | int dev_id; 67 | } eth_read; 68 | struct { 69 | int dev_id; 70 | uint16_t ethertype; 71 | struct sockaddr_ll mac; 72 | } eth_write; 73 | 74 | struct { 75 | int dev_id; 76 | } arp_read; 77 | struct { 78 | int dev_id; 79 | int opcode; 80 | struct sockaddr_ll sender_mac; 81 | struct sockaddr_in sender_ip; 82 | struct sockaddr_ll target_mac; 83 | struct sockaddr_in target_ip; 84 | } arp_write; 85 | 86 | struct { 87 | uint8_t proto; 88 | } ip_read; 89 | struct { 90 | struct sockaddr_in src; 91 | struct sockaddr_in dst; 92 | uint8_t proto; 93 | uint8_t dscp; 94 | uint8_t ttl; 95 | } ip_write; 96 | 97 | struct { 98 | int type; 99 | } socket; 100 | struct { 101 | int fd; 102 | } close; 103 | struct { 104 | int fd; 105 | struct sockaddr_in addr; 106 | } bind; 107 | struct { 108 | int fd; 109 | int backlog; 110 | } listen; 111 | struct { 112 | int fd; 113 | struct sockaddr_in addr; 114 | } connect; 115 | struct { 116 | int fd; 117 | } accept; 118 | struct { 119 | int fd; 120 | struct sockaddr_in dst; 121 | } sendto; 122 | struct { 123 | int fd; 124 | } recvfrom; 125 | struct { 126 | int fd; 127 | } send; 128 | struct { 129 | int fd; 130 | } recv; 131 | }; 132 | }; 133 | 134 | struct response { 135 | request_type type; 136 | int id; 137 | int payload_len; 138 | union { 139 | struct { 140 | int dev_id; 141 | } find_device; 142 | struct { 143 | struct sockaddr_ll mac; 144 | } get_device_mac; 145 | struct { 146 | int count; 147 | } get_device_ip; 148 | 149 | struct { 150 | int dev_id; 151 | uint16_t ethertype; 152 | } eth_read; 153 | struct { 154 | int dev_id; 155 | } eth_write; 156 | 157 | struct { 158 | int dev_id; 159 | int opcode; 160 | struct sockaddr_ll sender_mac; 161 | struct sockaddr_in sender_ip; 162 | struct sockaddr_ll target_mac; 163 | struct sockaddr_in target_ip; 164 | } arp_read; 165 | struct { 166 | int dev_id; 167 | } arp_write; 168 | 169 | struct { 170 | struct sockaddr_in src; 171 | struct sockaddr_in dst; 172 | uint8_t dscp; 173 | } ip_read; 174 | struct { 175 | int ret; 176 | } ip_write; 177 | 178 | struct { 179 | int fd; 180 | } socket; 181 | struct { 182 | int ret; 183 | } close; 184 | struct { 185 | int ret; 186 | } bind; 187 | struct { 188 | int ret; 189 | } listen; 190 | struct { 191 | int ret; 192 | } connect; 193 | struct { 194 | int ret; 195 | struct sockaddr_in peer; 196 | } accept; 197 | struct { 198 | ssize_t ret; 199 | } sendto; 200 | struct { 201 | ssize_t ret; 202 | struct sockaddr_in src; 203 | } recvfrom; 204 | struct { 205 | ssize_t ret; 206 | } send; 207 | struct { 208 | ssize_t ret; 209 | } recv; 210 | }; 211 | }; 212 | 213 | } // namespace khtcp 214 | 215 | #endif -------------------------------------------------------------------------------- /include/core.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file core.h 3 | * @author Pengcheng Xu 4 | * @brief Core logic for maintaining the ASIO context. 5 | * @version 0.1 6 | * @date 2019-10-03 7 | * 8 | * @copyright Copyright (c) 2019 9 | * 10 | */ 11 | 12 | #ifndef __KHTCP_CORE_H_ 13 | #define __KHTCP_CORE_H_ 14 | 15 | #include "client_request.h" 16 | #include "device.h" 17 | #include "packetio.h" 18 | #include "socket.h" 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #define CLEANUP_BUF(resp, req) \ 26 | { \ 27 | free(resp); \ 28 | free(req); \ 29 | auto it = get().outstanding_buffers.begin(); \ 30 | while (it != get().outstanding_buffers.end()) { \ 31 | if (it->second == resp || it->second == req) { \ 32 | get().outstanding_buffers.erase(it++); \ 33 | } else { \ 34 | ++it; \ 35 | } \ 36 | } \ 37 | } 38 | 39 | namespace khtcp { 40 | namespace core { 41 | /** 42 | * @brief The core class stores the main io_context as well as queues for 43 | * communication between layers. 44 | */ 45 | struct core { 46 | boost::asio::io_context io_context; 47 | 48 | std::vector> devices; 49 | 50 | eth::frame_receive_callback eth_callback; 51 | 52 | /** 53 | * @brief Payload handler list. 54 | */ 55 | std::list read_handlers; 56 | /** 57 | * @brief Strand to prevent concurrent access to the payload handler list. 58 | */ 59 | boost::asio::io_context::strand read_handlers_strand; 60 | /** 61 | * @brief Outstanding buffers that are not yet freed. 62 | * 63 | * client_id -> [buffer] 64 | * 65 | * Buffers here will be freed when client request is finished or cancelled 66 | * (due to disconnection). 67 | */ 68 | std::multimap outstanding_buffers; 69 | 70 | /** 71 | * @brief Write handler list. 72 | */ 73 | std::list write_tasks; 74 | /** 75 | * @brief Strand to prevent concurrent access to the write handler list. 76 | */ 77 | boost::asio::io_context::strand write_tasks_strand; 78 | 79 | boost::asio::local::stream_protocol::acceptor acceptor; 80 | 81 | std::unordered_map> 83 | clients; 84 | 85 | /** 86 | * @brief Records current fd using flags. 87 | * 88 | * -> socket::socket 89 | */ 90 | std::unordered_map, socket::socket, 91 | boost::hash>> 92 | client_sockets; 93 | const static int MIN_FD = 400000; // to avoid clashing with normal file fds 94 | 95 | boost::asio::deadline_timer per_second_timer; 96 | 97 | std::unordered_map multicast_buffers; 98 | 99 | core(); 100 | 101 | /** 102 | * @brief Run the core. 103 | */ 104 | int run(); 105 | }; 106 | 107 | void record_multicast_buffer(void *buf); 108 | 109 | /** 110 | * @brief Returns the global core object reference. 111 | * 112 | * @return core& 113 | */ 114 | core &get(); 115 | 116 | /** 117 | * @brief Cleans up the client with id once we discover that it has 118 | * disconnected. 119 | * 120 | * @param client_id 121 | */ 122 | void cleanup_client(int client_id); 123 | } // namespace core 124 | } // namespace khtcp 125 | 126 | #endif -------------------------------------------------------------------------------- /include/device.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file device.h 3 | * @author Pengcheng Xu 4 | * @brief Library supporting network device management. 5 | * @version 0.1 6 | * @date 2019-10-03 7 | * 8 | * @copyright Copyright (c) 2019 9 | * 10 | */ 11 | 12 | #ifndef __KHTCP_DEVICE_H_ 13 | #define __KHTCP_DEVICE_H_ 14 | 15 | #include "packetio.h" 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | namespace khtcp { 27 | namespace device { 28 | 29 | struct eth_holder { 30 | eth::addr_t data; 31 | }; 32 | 33 | /** 34 | * @brief The read handler type. 35 | * 36 | * If the read handler's purpose has been satisfied (e.g. read for a specific 37 | * protocol), returns true. Returns false otherwise. 38 | * 39 | * The tagged int is the client ID for the handler. 40 | * 41 | * consumed(dev_id, ethertype, payload, len) 42 | */ 43 | using read_handler_t = 44 | std::pair, int>; 45 | /** 46 | * @brief The write task type. 47 | * 48 | * The device write loops in the write task list to execute these functions. 49 | * The task will be removed from the list after execution (the task shall post a 50 | * new one if it wants a delayed retry). 51 | * 52 | * The tagged int is the client ID for the task. 53 | */ 54 | using write_task_t = std::pair, int>; 55 | /** 56 | * @brief The write handler type. 57 | * 58 | * (ret) 59 | */ 60 | using write_handler_t = std::function; 61 | 62 | const size_t CAPTURE_BUFSIZ = 65536; 63 | const size_t PACKET_TIMEOUT = 2; 64 | 65 | /** 66 | * @brief Device handle for an Ethernet interface. 67 | * 68 | * Note that this structure assumes a 6-octet (i.e. Ethernet) address format. 69 | */ 70 | struct device_t { 71 | std::string name; 72 | eth::addr_t addr; 73 | int id; 74 | pcap_t *pcap_handle; 75 | 76 | // L3 addresses 77 | std::vector ip_addrs; 78 | 79 | /** 80 | * @brief Wraps the injection operation for thread safety. 81 | * 82 | * We do not know if pcap_inject is thread safe, and the inject handler may be 83 | * posted from different threads, so post through a strand instead of the 84 | * global io_context. 85 | * 86 | * @see khtcp::device::device_t::handle_inject 87 | */ 88 | boost::asio::io_context::strand inject_strand; 89 | 90 | /** 91 | * @brief The trigger fd from pcap_get_selectable_fd. 92 | * 93 | * A null_buffers() read will be performed; once triggered, some data are to 94 | * be captured via pcap_next. 95 | * 96 | * @see khtcp::device::device_t::handle_sniff 97 | */ 98 | boost::asio::posix::stream_descriptor *trigger; 99 | 100 | /** 101 | * @brief Register the device's capture task in the core io_context. 102 | */ 103 | int start_capture(); 104 | 105 | /** 106 | * @brief Do actual pcap sniff (pcap_next) and recharge the task. 107 | */ 108 | void handle_sniff(); 109 | 110 | /** 111 | * @brief Synchronously inject frame into device via pcap_inject. 112 | * 113 | * @param buf The buffer to inject. 114 | * @param len Length of the buffer. 115 | */ 116 | int inject_frame(const uint8_t *buf, size_t len); 117 | 118 | /** 119 | * @brief Asynchronously inject frame into device via pcap_inject. 120 | * 121 | * @param buf The buffer to inject. 122 | * @param len Length of the buffer. 123 | * @param handler handler to call after completion. 124 | */ 125 | void async_inject_frame(const uint8_t *buf, size_t len, 126 | write_handler_t &&handler); 127 | 128 | device_t(); 129 | device_t(const device_t &) = delete; 130 | device_t(device_t &&) = delete; 131 | ~device_t(); 132 | }; 133 | 134 | /** 135 | * @brief Add a device to the library for sending/receiving packets. 136 | * 137 | * @param device Name of network device to send/receive packet on. 138 | * @return int A non-negative _device-ID_ on success, -1 on error. 139 | */ 140 | int add_device(const char *device); 141 | 142 | /** 143 | * @brief Find a device added by `khtcp::mgmt::add_device`. 144 | * 145 | * @param device Name of the network device. 146 | * @return int A non-negative _device-ID_ on success, -1 if no such device was 147 | * found. 148 | */ 149 | int find_device(const char *device); 150 | 151 | /** 152 | * @brief Get the device handle object from global store registered by 153 | * `khtcp::mgmt::add_device`. 154 | * 155 | * @param id 156 | * @return device_t 157 | *& 158 | */ 159 | device_t &get_device_handle(int id); 160 | 161 | } // namespace device 162 | } // namespace khtcp 163 | 164 | #endif -------------------------------------------------------------------------------- /include/ip.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file ip.h 3 | * @author Pengcheng Xu 4 | * @brief The Internet Protocol. 5 | * @version 0.1 6 | * @date 2019-10-04 7 | * 8 | * @copyright Copyright (c) 2019 9 | * 10 | */ 11 | 12 | #ifndef __KHTCP_IP_H_ 13 | #define __KHTCP_IP_H_ 14 | 15 | #include "device.h" 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | namespace khtcp { 23 | namespace ip { 24 | using addr_t = uint8_t[4]; 25 | 26 | static const addr_t IP_BROADCAST = {0xff, 0xff, 0xff, 0xff}; 27 | 28 | /** 29 | * @brief The IP header. 30 | * 31 | * Options are not included in the header; instead, they're counted as part of 32 | * the payload. Note the bitfield endianness problem: fields that share the 33 | * same word are placed as in little endian. 34 | */ 35 | struct __attribute__((packed)) ip_header_t { 36 | uint8_t ihl : 4; 37 | uint8_t version : 4; 38 | uint8_t ecn : 2; 39 | uint8_t dscp : 6; 40 | uint16_t total_length; 41 | uint16_t identification; 42 | uint16_t flags; 43 | uint8_t ttl; 44 | uint8_t proto; 45 | uint16_t header_csum; 46 | addr_t src_addr; 47 | addr_t dst_addr; 48 | }; 49 | 50 | /** 51 | * @brief The read handler type. 52 | * 53 | * consumed(payload_ptr, payload_len, src, dst, dscp, opt) 54 | * 55 | * As recommended in https://tools.ietf.org/html/rfc791#section-3.3 56 | */ 57 | using read_handler_t = std::function; 59 | 60 | /** 61 | * @brief The write handler type. 62 | * 63 | * (ret) 64 | */ 65 | using write_handler_t = std::function; 66 | 67 | device::read_handler_t::first_type wrap_read_handler(int16_t proto, 68 | read_handler_t handler); 69 | 70 | // The header shall be of just 5*4=20 octets 71 | static_assert(sizeof(ip_header_t) == 5 * 4, "IP header size mismatch"); 72 | 73 | static const uint16_t ethertype = 0x0800; 74 | 75 | /** 76 | * @brief Asynchronously read an IP packet. 77 | * 78 | * As recommended in https://tools.ietf.org/html/rfc791#section-3.3 79 | * 80 | * @param proto protocol code 81 | * @param handler handler to call once packet has been received. 82 | * @param client_id id for calling client, 0 for local (a server call). 83 | */ 84 | void async_read_ip(int proto, read_handler_t &&handler, int client_id = 0); 85 | 86 | /** 87 | * @brief Asynchronously write an IP packet. 88 | * 89 | * As recommended in https://tools.ietf.org/html/rfc791#section-3.3 90 | * 91 | * @param src source IP address 92 | * @param dst destination IP address 93 | * @param proto protocol code 94 | * @param dscp DSCP field 95 | * @param ttl Time To Live 96 | * @param payload_ptr pointer to payload data 97 | * @param payload_len payload data length 98 | * @param handler handler to call once packet has been sent 99 | * @param client_id id for calling client, 0 for local (a server call). 100 | * @param identification Identification field, default = 0 101 | * @param df DF flag, default = true 102 | * @param option Option pointer, default = nullptr 103 | */ 104 | void async_write_ip(const addr_t src, const addr_t dst, uint8_t proto, 105 | uint8_t dscp, uint8_t ttl, const void *payload_ptr, 106 | uint64_t payload_len, write_handler_t &&handler, 107 | int client_id = 0, uint16_t identification = 0, 108 | bool df = true, const void *option = nullptr); 109 | 110 | /** 111 | * @brief Start IP auto answering. 112 | * 113 | */ 114 | void start(); 115 | 116 | /** 117 | * @brief An entry in the routing table. Resembles the Linux routing table 118 | * entry. 119 | */ 120 | struct route { 121 | int dev_id = -1; 122 | bool has_router = false; 123 | addr_t router; 124 | addr_t dst; 125 | uint8_t prefix; 126 | uint32_t metric = 1; 127 | int age = 0; 128 | bool changed = false; 129 | 130 | bool operator<(const struct route &a) const; 131 | }; 132 | 133 | /** 134 | * @brief Get the routing table object 135 | * 136 | * @return std::set& 137 | */ 138 | std::list &get_routing_table(); 139 | 140 | /** 141 | * @brief Add a route to the global routing table. 142 | * 143 | * @param route the route object to add 144 | * @return true the add succeeded 145 | * @return false the add failed 146 | */ 147 | bool add_route(struct route &&route); 148 | 149 | /** 150 | * @brief Lookup a destination in the global routing table. 151 | * 152 | * @param dst destination 153 | * @param out_route pointer to the route entry 154 | * @param prefix if not equal to -1, only route with that prefix length will 155 | * match 156 | * @return true a valid route has been found 157 | * @return false otherwise 158 | */ 159 | bool lookup_route(const addr_t dst, struct route **out_route, int prefix = -1); 160 | 161 | /** 162 | * @brief Print the routing table. 163 | */ 164 | void print_route(); 165 | 166 | /** 167 | * @brief Age all routes by 1. 168 | */ 169 | void update_route(); 170 | 171 | /** 172 | * @brief Delete specified route. 173 | * 174 | * @param route 175 | * @return true route found and deleted. 176 | * @return false route not found. 177 | */ 178 | bool delete_route(const struct route *route); 179 | 180 | /** 181 | * @brief Join IP multicast group. 182 | * 183 | * Joining a multicast group will result in receiving all packets sent to this 184 | * multicast address. 185 | * 186 | * @param multicast The multicast group to join. 187 | */ 188 | void join_multicast(const addr_t multicast); 189 | 190 | void test_multicast_broadcast(const ip::addr_t dst, route **r, 191 | bool *is_multicast, bool *is_broadcast); 192 | 193 | /** 194 | * @brief IP checksum. 195 | * 196 | * @param vdata data to be verified. 197 | * @param length length of the data. 198 | * @return uint16_t 199 | */ 200 | uint16_t ip_checksum(const void *vdata, size_t length); 201 | 202 | } // namespace ip 203 | } // namespace khtcp 204 | 205 | #endif -------------------------------------------------------------------------------- /include/packetio.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file packetio.h 3 | * @author Pengcheng Xu 4 | * @brief Library supporting sending/receiving Ethernet II frames. 5 | * @version 0.1 6 | * @date 2019-10-03 7 | * 8 | * @copyright Copyright (c) 2019 9 | * 10 | */ 11 | 12 | #ifndef __KHTCP_PACKETIO_H_ 13 | #define __KHTCP_PACKETIO_H_ 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | namespace khtcp { 20 | namespace eth { 21 | using addr_t = uint8_t[6]; 22 | 23 | static const addr_t ETH_BROADCAST = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; 24 | 25 | /** 26 | * @brief The Ethernet II header type. 27 | */ 28 | struct __attribute__((packed)) eth_header_t { 29 | addr_t dst; 30 | addr_t src; 31 | uint16_t ethertype; 32 | }; 33 | 34 | /** 35 | * @brief The read handler type. 36 | * 37 | * consumed(dev_id, ethertype, payload, len) 38 | */ 39 | using read_handler_t = std::function; 40 | 41 | /** 42 | * @brief The write handler type. 43 | * 44 | * (ret) 45 | */ 46 | using write_handler_t = std::function; 47 | 48 | /** 49 | * @brief Construct Ethernet II frame for sending. 50 | * 51 | * The caller has the responsibility to delete the buffer returned. 52 | * 53 | * @param buf Pointer to the payload. 54 | * @param len Length of the payload. 55 | * @param ethtype EtherType field value of this frame. 56 | * @param destmac MAC address of the destination. 57 | * @param id ID of the device (returned by `khtcp::mgmt::add_device`) to send 58 | * on. 59 | * @return std::pair the frame constructed. 60 | */ 61 | std::pair construct_frame(const void *buf, int len, 62 | int ethtype, const void *destmac, 63 | int id); 64 | 65 | /** 66 | * @brief Encapsulate some data into an Ethernet II frame and send it. 67 | * 68 | * @param buf Pointer to the payload. 69 | * @param len Length of the payload. 70 | * @param ethtype EtherType field value of this frame. 71 | * @param destmac MAC address of the destination. 72 | * @param id ID of the device (returned by `khtcp::mgmt::add_device`) to send 73 | * on. 74 | * @return 0 on success, -1 on error. 75 | * @see khtcp::mgmt::add_device 76 | */ 77 | int send_frame(const void *buf, int len, int ethtype, const void *destmac, 78 | int id); 79 | 80 | /** 81 | * @brief Asynchronously read an Ethernet frmae. 82 | * 83 | * @param id The device to operate on. 84 | * @param handler handler to call when the read operation is done. 85 | * @param client_id id for calling client, 0 for local (a server call). 86 | */ 87 | void async_read_frame(int id, read_handler_t &&handler, int client_id = 0); 88 | 89 | /** 90 | * @brief Asynchronously encapsulate some data into an Ethernet II frame and 91 | * send it. 92 | * 93 | * @param buf Pointer to the payload. 94 | * @param len Length of the payload. 95 | * @param ethtype EtherType field value of this frame. 96 | * @param destmac MAC address of the destination. 97 | * @param id ID of the device (returned by `khtcp::mgmt::add_device`) to 98 | * send on. 99 | * @param handler The handler to call on after send completes. 100 | * @param client_id id for calling client, 0 for local (a server call). 101 | * @see khtcp::mgmt::add_device 102 | */ 103 | void async_write_frame(const void *buf, int len, int ethtype, 104 | const void *destmac, int id, write_handler_t &&handler, 105 | int client_id = 0); 106 | 107 | /** 108 | * @brief Process a frame upon receiving it. 109 | * 110 | * @param buf Pointer to the frame. 111 | * @param len Length of the frame. 112 | * @param id ID of the device (returned by `khtcp::mgmt::add_device`) receiving 113 | * current frame. 114 | * @return 0 on success, -1 on error. 115 | * @see khtcp::mgmt::add_device 116 | */ 117 | using frame_receive_callback = int (*)(const void *, int, int); 118 | 119 | /** 120 | * @brief Register a callback function to be called each time an Ethernet II 121 | * frame was received. 122 | * 123 | * @param callback the callback function. 124 | * @return 0 on success, -1 on error. 125 | * @see khtcp::eth::frame_receive_callback 126 | */ 127 | int set_frame_receive_callback(frame_receive_callback callback); 128 | 129 | /** 130 | * @brief Get the frame receive callback object 131 | * 132 | * @return frame_receive_callback 133 | */ 134 | frame_receive_callback get_frame_receive_callback(); 135 | 136 | /** 137 | * @brief Simple callback to print Ethernet header of received packet. 138 | * 139 | * @param frame pointer to frame. 140 | * @param len length of the frame. 141 | * @param dev_id id of device from which the frame is received from. 142 | * @return int 143 | */ 144 | int print_eth_frame_callback(const void *frame, int len, int dev_id); 145 | 146 | /** 147 | * @brief Callback to traverse the device receive handler queue. 148 | * 149 | * The broker will discard frames with MAC address that does not match the 150 | * current device. 151 | * 152 | * @param frame pointer to frame. 153 | * @param len length of the frame. 154 | * @param dev_id id of device from which the frame is received from. 155 | * @return int 156 | */ 157 | int ethertype_broker_callback(const void *frame, int len, int dev_id); 158 | 159 | /** 160 | * @brief Join Ethernet multicast group. 161 | * 162 | * Joining a multicast group will result in receiving all frames sent to this 163 | * multicast address. 164 | * 165 | * @param multicast The multicast group to join. 166 | */ 167 | void join_multicast(const addr_t multicast); 168 | 169 | } // namespace eth 170 | } // namespace khtcp 171 | 172 | #endif -------------------------------------------------------------------------------- /include/rip.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file rip.h 3 | * @author Pengcheng Xu 4 | * @brief The Routing Information Protocol (RIPv2, RFC2453) 5 | * @version 0.1 6 | * @date 2019-10-04 7 | * 8 | * @copyright Copyright (c) 2019 9 | * 10 | */ 11 | 12 | #ifndef __KHTCP_RIP_H_ 13 | #define __KHTCP_RIP_H_ 14 | 15 | #include "ip.h" 16 | 17 | namespace khtcp { 18 | namespace rip { 19 | static const ip::addr_t RIP_MULTICAST = {0xe0, 0x00, 0x00, 0x09}; 20 | static const uint16_t port = 520; 21 | static const uint8_t version = 2; 22 | static const uint32_t infinity = 16; // to represent unreachable routes 23 | static const uint32_t cost = 1; // for simple hop-based 24 | static const int max_age = 180; 25 | static const int deletion_timeout = 120; 26 | 27 | struct __attribute__((packed)) rip_header_t { 28 | uint8_t command; 29 | uint8_t version; 30 | uint16_t zero; 31 | }; 32 | 33 | static const uint8_t request = 1; 34 | static const uint8_t response = 2; 35 | 36 | struct __attribute__((packed)) rte_t { 37 | uint16_t afi; 38 | uint16_t route_tag; 39 | ip::addr_t address; 40 | ip::addr_t mask; 41 | ip::addr_t nexthop; 42 | uint32_t metric; 43 | }; 44 | 45 | /** 46 | * @brief Start the RIP protocol. 47 | */ 48 | void start(); 49 | 50 | /** 51 | * @brief Trigger a update. 52 | */ 53 | void trigger_update(); 54 | 55 | /** 56 | * @brief Send a full-table request. 57 | */ 58 | void request_fulltable(); 59 | 60 | /** 61 | * @brief Send full routing table. 62 | * 63 | * Note: this will perform Split Horizon src_port is the RIP port (520). 64 | */ 65 | void send_fulltable(const uint8_t *src_, uint16_t src_port, const uint8_t *dst_, 66 | uint16_t dst_port); 67 | } // namespace rip 68 | } // namespace khtcp 69 | 70 | #endif -------------------------------------------------------------------------------- /include/socket.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file socket.h 3 | * @author Pengcheng Xu 4 | * @brief Common data structure for maintaining a socket structure. 5 | * @version 0.1 6 | * @date 2019-10-17 7 | * 8 | * @copyright Copyright (c) 2019 9 | * 10 | */ 11 | 12 | #ifndef __KHTCP_SOCKET_H_ 13 | #define __KHTCP_SOCKET_H_ 14 | 15 | #include "ip.h" 16 | 17 | #include 18 | 19 | namespace khtcp { 20 | namespace socket { 21 | struct socket { 22 | int fd; 23 | int type; // SOCK_STREAM or SOCK_DGRAM 24 | struct sockaddr_in bind_addr; 25 | 26 | void get_src(const ip::addr_t dst, const uint8_t **src_out, 27 | uint16_t *port_out); 28 | 29 | socket(int client_id, int type); 30 | }; 31 | } // namespace socket 32 | } // namespace khtcp 33 | 34 | #endif -------------------------------------------------------------------------------- /include/tcp.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file tcp.h 3 | * @author Pengcheng Xu 4 | * @brief The Transmission Control Protocol (TCP, RFC793) 5 | * @version 0.1 6 | * @date 2019-10-04 7 | * 8 | * @copyright Copyright (c) 2019 9 | * 10 | */ 11 | 12 | #ifndef __KHTCP_TCP_H_ 13 | #define __KHTCP_TCP_H_ 14 | 15 | #include "ip.h" 16 | 17 | #include 18 | #include 19 | 20 | namespace khtcp { 21 | namespace tcp { 22 | 23 | static const auto timeout = boost::posix_time::seconds(60); 24 | // without window scaling this would be 32KB 25 | static const auto local_window = 32768; 26 | static const uint8_t default_ttl = 64; 27 | static const uint8_t proto = 6; 28 | 29 | void start(); 30 | 31 | /** 32 | * @brief The TCP header, without the TCP options. 33 | */ 34 | struct __attribute__((packed)) tcp_header_t { 35 | uint16_t src_port; 36 | uint16_t dst_port; 37 | uint32_t seq; 38 | uint32_t ack; 39 | uint8_t data_offset; 40 | uint8_t flags; 41 | uint16_t window; 42 | uint16_t checksum; 43 | uint16_t urgent_pointer; 44 | }; 45 | 46 | // hdr->flags = fin | (syn << 1) | (rst << 2) | (psh << 3) | (ack << 4) 47 | #define FIN(x) (bool)((x)&0b1) 48 | #define SYN(x) (bool)((x)&0b10) 49 | #define RST(x) (bool)((x)&0b100) 50 | #define PSH(x) (bool)((x)&0b1000) 51 | #define ACK(x) (bool)((x)&0b10000) 52 | 53 | using send_segment_handler_t = std::function; 54 | void async_send_segment(const ip::addr_t src, uint16_t src_port, 55 | const ip::addr_t dst, uint16_t dst_port, 56 | uint32_t seq_num, uint32_t ack_num, bool ack, bool psh, 57 | bool rst, bool syn, bool fin, uint16_t window, 58 | const void *payload_ptr, uint16_t payload_len, 59 | send_segment_handler_t &&handler, int client_id = 0); 60 | 61 | using recv_segment_handler_t = 62 | std::function; 64 | void async_recv_segment(const ip::addr_t src, uint16_t src_port, 65 | const ip::addr_t dst, uint16_t dst_port, 66 | recv_segment_handler_t &&handler, int client_id = 0); 67 | 68 | struct conn_key { 69 | ip::addr_t src; 70 | uint16_t src_port; 71 | ip::addr_t dst; 72 | uint16_t dst_port; 73 | 74 | bool operator==(const conn_key &a) const; 75 | std::string to_string() const; 76 | }; 77 | 78 | struct tcb { 79 | // if the connection is UP 80 | bool up; 81 | // SEGMENTED buffer pending send 82 | std::queue pending_send; 83 | // receive buffer 84 | boost::asio::mutable_buffer pending_receive; 85 | // send_unack < seg.ack <= send_next 86 | uint32_t send_unack; 87 | uint32_t send_next; 88 | // recv_next <= seg.seq < recv_next + recv_window OR 89 | // recv_next <= seg.seq + seg.len - 1 < recv_next + recv_window 90 | uint32_t recv_next; 91 | uint32_t recv_window; 92 | }; 93 | 94 | using open_handler_t = std::function; 95 | void async_open(const ip::addr_t src, uint16_t src_port, const ip::addr_t dst, 96 | uint16_t dst_port, bool active, open_handler_t &&handler, 97 | int client_id = 0); 98 | 99 | } // namespace tcp 100 | } // namespace khtcp 101 | 102 | #endif -------------------------------------------------------------------------------- /include/udp.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file udp.h 3 | * @author Pengcheng Xu 4 | * @brief The User Datagram Protocol (UDP, RFC768) 5 | * @version 0.1 6 | * @date 2019-10-04 7 | * 8 | * @copyright Copyright (c) 2019 9 | * 10 | */ 11 | 12 | #ifndef __KHTCP_UDP_H_ 13 | #define __KHTCP_UDP_H_ 14 | 15 | #include "ip.h" 16 | 17 | namespace khtcp { 18 | namespace udp { 19 | 20 | static const uint8_t default_ttl = 114; 21 | static const uint8_t proto = 17; 22 | 23 | struct __attribute__((packed)) udp_header_t { 24 | uint16_t src_port; 25 | uint16_t dst_port; 26 | uint16_t length; 27 | uint16_t checksum; 28 | }; 29 | 30 | /** 31 | * @brief The read handler type. 32 | * 33 | * consumed(payload_ptr, payload_len, src, src_port, dst, dst_port) 34 | */ 35 | using read_handler_t = 36 | std::function; 38 | 39 | /** 40 | * @brief The write handler type. 41 | * 42 | * (ret) 43 | */ 44 | using write_handler_t = std::function; 45 | 46 | /** 47 | * @brief Asynchronously read a UDP packet. 48 | * 49 | * @param handler handler to call once packet has been received. 50 | * @param client_id id for calling client, 0 for local (a server call). 51 | */ 52 | void async_read_udp(read_handler_t &&handler, int client_id = 0); 53 | 54 | /** 55 | * @brief Asynchronously write a UDP packet. 56 | * 57 | * @param src source IP address 58 | * @param src_port source port 59 | * @param dst destination IP address 60 | * @param dst_port destination port 61 | * @param payload_ptr pointer to payload data 62 | * @param payload_len payload data length 63 | * @param handler handler to call once packet has been sent 64 | * @param client_id id for calling client, 0 for local (a server call). 65 | */ 66 | void async_write_udp(const ip::addr_t src, uint16_t src_port, 67 | const ip::addr_t dst, uint16_t dst_port, 68 | const void *payload_ptr, uint16_t payload_len, 69 | write_handler_t &&handler, int client_id = 0); 70 | 71 | } // namespace udp 72 | } // namespace khtcp 73 | 74 | #endif -------------------------------------------------------------------------------- /include/util.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file util.h 3 | * @author Pengcheng Xu 4 | * @brief Various utilities. 5 | * @version 0.1 6 | * @date 2019-10-03 7 | * 8 | * @copyright Copyright (c) 2019 9 | * 10 | */ 11 | 12 | #ifndef __KHTCP_UTIL_H_ 13 | #define __KHTCP_UTIL_H_ 14 | #include "device.h" 15 | #include "ip.h" 16 | #include "packetio.h" 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | namespace khtcp { 27 | namespace util { 28 | 29 | class colored_console_sink 30 | : public boost::log::sinks::basic_formatted_sink_backend< 31 | char, boost::log::sinks::synchronized_feeding> { 32 | public: 33 | static void consume(boost::log::record_view const &rec, 34 | string_type const &formatted_string); 35 | }; 36 | using colored_console_sink_t = 37 | boost::log::sinks::synchronous_sink; 38 | 39 | void init_logging( 40 | boost::log::trivial::severity_level level = boost::log::trivial::info); 41 | 42 | std::string mac_to_string(const eth::addr_t addr); 43 | 44 | std::string ip_to_string(const ip::addr_t addr); 45 | 46 | int string_to_ip(const std::string &str, ip::addr_t addr); 47 | 48 | int mask_to_cidr(uint32_t n); 49 | 50 | uint32_t cidr_to_mask(int prefix); 51 | 52 | void hexdump(const char *note, const void *data, size_t size); 53 | 54 | } // namespace util 55 | } // namespace khtcp 56 | #endif -------------------------------------------------------------------------------- /not-implemented.md: -------------------------------------------------------------------------------- 1 | # Tasks not implemented 2 | 3 | ## Link-layer: Packet I/O On Ethernet 4 | 5 | - When joining a multicast group, checks about whether the address is a valid multicast address are not performed. 6 | - We only join IPv4 multicast groups (for RIP), no need for the complication. 7 | - Ethernet II checksum not implemented. 8 | - The sent frames may have a random CRC field of length 4. 9 | - Ethernet minimum-length padding not implemented. 10 | - The sent frames may be too short for Ethernet. 11 | - MTU-related checks not performed. 12 | - Reason: `pcap_sendpacket` will fail if a constructed frame was too large for the device. Besides, as we use `getifaddrs` for getting physical address, it's not elegant to again use `ioctl` for getting the MTU of the interface. 13 | 14 | ## Network-layer: IP Protocol 15 | 16 | - ICMP messages not implemented. 17 | - May be implemented in the future, not what we focus on now ("may" in RFC791). 18 | - IP fragmenting/reassembly not implemented. 19 | - Per instruction as not necessary. 20 | - As a result, the Identification field is always left blank. 21 | - All packets sent have DF set. 22 | - IP options not supported and will be ignored on receiving. 23 | - Per instruction as not necessary. 24 | - Routing of multicast packets are not implemented. 25 | - The rules for routing IPv4 multicast are complicated and routing them rarely works. 26 | - Not all RIPv2 options supported: 27 | - Next Hop and Route Tag not supported. 28 | - Authentication not supported. 29 | - RIPv1 compatibility not supported. 30 | 31 | ## Transport-layer: UDP Protocol 32 | 33 | - UDP checksum not implemented. 34 | - Took too long but still can't get it right... Optional per RFC768, so set as all zero when sending and not verified when receiving. -------------------------------------------------------------------------------- /src/arp.cc: -------------------------------------------------------------------------------- 1 | #include "arp.h" 2 | #include "core.h" 3 | #include "device.h" 4 | #include "util.h" 5 | 6 | #include 7 | 8 | namespace khtcp { 9 | namespace arp { 10 | // IP -> 11 | std::map> neighbor_map; 12 | 13 | device::read_handler_t::first_type wrap_read_handler(int dev_id_, 14 | read_handler_t handler) { 15 | return [=](int dev_id, uint16_t ethertype, const uint8_t *packet_ptr, 16 | int packet_len) -> bool { 17 | if (dev_id_ != dev_id || ethertype != arp::ethertype) { 18 | return false; 19 | } 20 | auto hdr = (arp_header_t *)packet_ptr; 21 | auto &name = device::get_device_handle(dev_id).name; 22 | BOOST_LOG_TRIVIAL(trace) << "Received ARP packet on device " << name; 23 | auto opcode = boost::endian::endian_reverse(hdr->opcode); 24 | auto hw_type = boost::endian::endian_reverse(hdr->hardware_type); 25 | auto proto_type = boost::endian::endian_reverse(hdr->protocol_type); 26 | if (hw_type != 0x1) { // Ethernet 27 | BOOST_LOG_TRIVIAL(warning) << "Unsupported ARP hardware type " << hw_type 28 | << " on device " << name; 29 | return false; 30 | } 31 | if (proto_type != 0x0800) { // IPv4 32 | BOOST_LOG_TRIVIAL(warning) << "Unsupported ARP protocol type " 33 | << proto_type << " on device " << name; 34 | return false; 35 | } 36 | if (hdr->hardware_size != sizeof(eth::addr_t)) { 37 | BOOST_LOG_TRIVIAL(warning) << "ARP hardware size mismatch"; 38 | return false; 39 | } 40 | if (hdr->protocol_size != sizeof(ip::addr_t)) { 41 | BOOST_LOG_TRIVIAL(warning) << "ARP protocol size mismatch"; 42 | return false; 43 | } 44 | auto sender_mac = (uint8_t *)packet_ptr + sizeof(arp_header_t); 45 | auto sender_ip = sender_mac + sizeof(eth::addr_t); 46 | auto target_mac = sender_ip + sizeof(ip::addr_t); 47 | auto target_ip = target_mac + sizeof(eth::addr_t); 48 | 49 | // record mapping in global ARP table 50 | BOOST_LOG_TRIVIAL(trace) 51 | << "Recording neighbor " << util::ip_to_string(sender_ip) 52 | << " with MAC " << util::mac_to_string(sender_mac) << " and timeout " 53 | << NEIGHBOR_TIMEOUT; 54 | memcpy(&neighbor_map[*((uint32_t *)sender_ip)].first.data, sender_mac, 55 | sizeof(eth::addr_t)); 56 | neighbor_map[*((uint32_t *)sender_ip)].second = NEIGHBOR_TIMEOUT; 57 | 58 | return handler(dev_id, opcode, sender_mac, sender_ip, target_mac, 59 | target_ip); 60 | }; 61 | } 62 | 63 | // reply ARP on receiving request 64 | bool default_handler(int dev_id, uint16_t opcode, eth::addr_t sender_mac, 65 | ip::addr_t sender_ip, eth::addr_t target_mac, 66 | ip::addr_t target_ip) { 67 | bool ret = false; 68 | auto &device = device::get_device_handle(dev_id); 69 | if (opcode == 0x1) { // request 70 | for (const auto &ip : device.ip_addrs) { 71 | if (!memcmp(ip, target_ip, sizeof(ip::addr_t))) { 72 | async_write_arp(dev_id, 0x2, device.addr, ip, sender_mac, sender_ip, 73 | [](int dev_id, int ret) { 74 | if (ret != PCAP_ERROR) { 75 | BOOST_LOG_TRIVIAL(trace) 76 | << "Sent ARP reply on device " 77 | << device::get_device_handle(dev_id).name; 78 | } 79 | }); 80 | ret = true; 81 | } 82 | } 83 | } 84 | if (ret) { 85 | async_read_arp(dev_id, default_handler); 86 | } 87 | return ret; 88 | } 89 | 90 | // runs per second. decreases entry's ttl and purges aging ones. 91 | void scan_arp_table() { 92 | int counter = 0; 93 | auto it = neighbor_map.begin(); 94 | while (it != neighbor_map.end()) { 95 | if (it->second.second == 0) { 96 | neighbor_map.erase(it++); 97 | ++counter; 98 | } else { 99 | --(it++)->second.second; 100 | } 101 | } 102 | BOOST_LOG_TRIVIAL(trace) << "Purged " << counter 103 | << " entries during ARP table cleanup"; 104 | } 105 | 106 | void start(int dev_id) { async_read_arp(dev_id, default_handler); } 107 | 108 | void async_read_arp(int dev_id, read_handler_t &&handler, int client_id) { 109 | boost::asio::post(core::get().read_handlers_strand, [=]() { 110 | core::get().read_handlers.emplace_back(wrap_read_handler(dev_id, handler), 111 | client_id); 112 | BOOST_LOG_TRIVIAL(trace) << "ARP read handler queued for device " 113 | << device::get_device_handle(dev_id).name; 114 | }); 115 | } 116 | 117 | void async_write_arp(int dev_id, uint16_t opcode, const eth::addr_t sender_mac, 118 | const ip::addr_t sender_ip, const eth::addr_t target_mac, 119 | const ip::addr_t target_ip, write_handler_t &&handler, 120 | int client_id) { 121 | boost::asio::post(core::get().write_tasks_strand, [=]() { 122 | core::get().write_tasks.emplace_back( 123 | [=]() { 124 | auto packet_len = sizeof(arp_header_t) + 2 * sizeof(eth::addr_t) + 125 | 2 * sizeof(ip::addr_t); 126 | auto packet_buf = new uint8_t[packet_len]; 127 | auto hdr = (arp_header_t *)packet_buf; 128 | hdr->hardware_type = boost::endian::endian_reverse((uint16_t)0x1); 129 | hdr->protocol_type = boost::endian::endian_reverse((uint16_t)0x0800); 130 | hdr->opcode = boost::endian::endian_reverse(opcode); 131 | hdr->hardware_size = sizeof(eth::addr_t); 132 | hdr->protocol_size = sizeof(ip::addr_t); 133 | 134 | auto smac = packet_buf + sizeof(arp_header_t); 135 | auto sip = smac + sizeof(eth::addr_t); 136 | auto tmac = sip + sizeof(ip::addr_t); 137 | auto tip = tmac + sizeof(eth::addr_t); 138 | 139 | memcpy(tmac, target_mac, sizeof(eth::addr_t)); 140 | memcpy(smac, sender_mac, sizeof(eth::addr_t)); 141 | memcpy(tip, target_ip, sizeof(ip::addr_t)); 142 | memcpy(sip, sender_ip, sizeof(ip::addr_t)); 143 | 144 | BOOST_LOG_TRIVIAL(trace) << "Sending ARP packet on device " 145 | << device::get_device_handle(dev_id).name; 146 | 147 | eth::async_write_frame(packet_buf, packet_len, arp::ethertype, tmac, 148 | dev_id, [=](int ret) { 149 | handler(dev_id, ret); 150 | delete[] packet_buf; 151 | }); 152 | }, 153 | client_id); 154 | }); 155 | } 156 | 157 | void async_resolve_mac(int dev_id, const ip::addr_t dst, 158 | resolve_mac_handler_t &&handler) { 159 | BOOST_LOG_TRIVIAL(trace) << "Resolving MAC for " << util::ip_to_string(dst); 160 | auto t = new boost::asio::deadline_timer(core::get().io_context); 161 | static std::function 163 | check_mac = [](int n, int dev_id, auto handler, auto t, auto dst) { 164 | auto &device = device::get_device_handle(dev_id); 165 | for (const auto &ip : device.ip_addrs) { 166 | if (!memcmp(ip, dst, sizeof(ip::addr_t))) { 167 | // ip is on this device, send with device MAC address 168 | BOOST_LOG_TRIVIAL(trace) 169 | << "Destination IP " << util::ip_to_string(dst) 170 | << " is on device " << device.name; 171 | handler(0, device.addr); 172 | delete t; 173 | return; 174 | } 175 | } 176 | if (neighbor_map.find(*(uint32_t *)dst) == neighbor_map.end()) { 177 | // send ARP query. 178 | BOOST_LOG_TRIVIAL(trace) 179 | << "Sending ARP query for " << util::ip_to_string(dst); 180 | async_write_arp( 181 | dev_id, 0x1, device.addr, device.ip_addrs[0], eth::ETH_BROADCAST, 182 | dst, [=](int dev_id, int ret) { 183 | if (ret != PCAP_ERROR) { 184 | if (n < 5) { 185 | t->expires_from_now(boost::posix_time::milliseconds(200)); 186 | t->async_wait([=](auto ec) { 187 | if (!ec) { 188 | check_mac(n + 1, dev_id, handler, t, dst); 189 | } else { 190 | BOOST_LOG_TRIVIAL(warning) 191 | << "Resolve MAC timer failed: " << ec.message(); 192 | delete t; 193 | } 194 | }); 195 | } else { 196 | handler(EHOSTUNREACH, nullptr); 197 | delete t; 198 | } 199 | } else { 200 | BOOST_LOG_TRIVIAL(error) << "Failed to do ARP resolution"; 201 | } 202 | }); 203 | } else { 204 | BOOST_LOG_TRIVIAL(trace) 205 | << "ARP table hit for IP " << util::ip_to_string(dst); 206 | handler(0, neighbor_map[*(uint32_t *)dst].first.data); 207 | delete t; 208 | } 209 | }; 210 | check_mac(0, dev_id, handler, t, dst); 211 | } 212 | 213 | } // namespace arp 214 | } // namespace khtcp -------------------------------------------------------------------------------- /src/core.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "arp.h" 7 | #include "core.h" 8 | #include "packetio.h" 9 | #include "rip.h" 10 | #include "tcp.h" 11 | #include "udp.h" 12 | #include "util.h" 13 | 14 | namespace khtcp { 15 | namespace core { 16 | static core _core; 17 | 18 | void signal_handler(int sig) { get().io_context.stop(); } 19 | 20 | using namespace std::string_literals; 21 | 22 | core::core() 23 | : acceptor(io_context, boost::asio::local::stream_protocol::endpoint( 24 | "\0khtcp-server"s)), 25 | per_second_timer(io_context), read_handlers_strand(io_context), 26 | write_tasks_strand(io_context) { 27 | std::signal(SIGTERM, signal_handler); 28 | std::signal(SIGINT, signal_handler); 29 | } 30 | 31 | core &get() { return _core; } 32 | 33 | void client_request_handler(const boost::system::error_code &ec, 34 | int bytes_transferred, int client_id) try { 35 | if (!ec) { 36 | auto &client = get().clients.at(client_id); 37 | auto &sock = client.first; 38 | auto &req = client.second; 39 | struct response *resp = (struct response *)malloc(sizeof(struct response)); 40 | get().outstanding_buffers.emplace(client_id, resp); 41 | auto buf = boost::asio::buffer(resp, sizeof(*resp)); 42 | 43 | resp->type = req->type; 44 | resp->id = req->id; 45 | 46 | BOOST_LOG_TRIVIAL(trace) << "Received request #" << req->id << " of type " 47 | << req->type << " from client id " << client_id; 48 | 49 | switch (req->type) { 50 | case FIND_DEVICE: { 51 | resp->payload_len = 0; 52 | resp->find_device.dev_id = device::find_device(req->find_device.name); 53 | boost::asio::write(sock, buf); 54 | CLEANUP_BUF(resp, req) 55 | break; 56 | } 57 | case GET_DEVICE_MAC: { 58 | resp->payload_len = 0; 59 | resp->get_device_mac.mac.sll_halen = 6; 60 | memcpy(&resp->get_device_mac.mac.sll_addr, 61 | device::get_device_handle(req->get_device_mac.dev_id).addr, 62 | sizeof(eth::addr_t)); 63 | boost::asio::write(sock, buf); 64 | CLEANUP_BUF(resp, req) 65 | break; 66 | } 67 | case GET_DEVICE_IP: { 68 | auto &device = device::get_device_handle(req->get_device_ip.dev_id); 69 | resp->get_device_ip.count = device.ip_addrs.size(); 70 | resp->payload_len = 71 | resp->get_device_ip.count * sizeof(struct sockaddr_in); 72 | struct sockaddr_in *payload_ptr = 73 | (struct sockaddr_in *)malloc(resp->payload_len); 74 | for (int i = 0; i < resp->get_device_ip.count; ++i) { 75 | memcpy(&payload_ptr[i].sin_addr, device.ip_addrs[i], 76 | sizeof(ip::addr_t)); 77 | payload_ptr[i].sin_family = AF_INET; 78 | } 79 | boost::asio::write(sock, buf); 80 | boost::asio::write(sock, 81 | boost::asio::buffer(payload_ptr, resp->payload_len)); 82 | free(payload_ptr); 83 | CLEANUP_BUF(resp, req) 84 | break; 85 | } 86 | case ETHERNET_READ: 87 | eth::async_read_frame( 88 | req->eth_read.dev_id, 89 | [buf, resp, req, &sock, client_id](int dev_id, uint16_t ethertype, 90 | const uint8_t *packet_ptr, 91 | int len) -> bool { 92 | resp->payload_len = len; 93 | resp->eth_read.dev_id = dev_id; 94 | resp->eth_read.ethertype = ethertype; 95 | try { 96 | boost::asio::write(sock, buf); 97 | boost::asio::write(sock, boost::asio::buffer(packet_ptr, len)); 98 | } catch (const std::exception &e) { 99 | BOOST_LOG_TRIVIAL(error) 100 | << "Exception in client handler: " << e.what(); 101 | } 102 | CLEANUP_BUF(resp, req) 103 | return true; 104 | }, 105 | client_id); 106 | break; 107 | case ETHERNET_WRITE: { 108 | int dev_id = req->eth_read.dev_id; 109 | void *payload_ptr = malloc(req->payload_len); 110 | boost::asio::read(sock, 111 | boost::asio::buffer(payload_ptr, req->payload_len)); 112 | eth::async_write_frame( 113 | payload_ptr, req->payload_len, req->eth_write.ethertype, 114 | req->eth_write.mac.sll_addr, req->eth_write.dev_id, 115 | [buf, resp, req, payload_ptr, dev_id, &sock, client_id](int ret) { 116 | resp->payload_len = 0; 117 | resp->eth_write.dev_id = dev_id; 118 | try { 119 | boost::asio::write(sock, buf); 120 | } catch (const std::exception &e) { 121 | BOOST_LOG_TRIVIAL(error) 122 | << "Exception in client handler: " << e.what(); 123 | } 124 | free(payload_ptr); 125 | CLEANUP_BUF(resp, req) 126 | }, 127 | client_id); 128 | break; 129 | } 130 | case ARP_READ: 131 | arp::async_read_arp( 132 | req->arp_read.dev_id, 133 | [buf, resp, req, &sock, 134 | client_id](int dev_id, uint16_t opcode, eth::addr_t sender_mac, 135 | ip::addr_t sender_ip, eth::addr_t target_mac, 136 | ip::addr_t target_ip) -> bool { 137 | resp->payload_len = 0; 138 | resp->arp_read.dev_id = dev_id; 139 | resp->arp_read.opcode = opcode; 140 | memcpy(resp->arp_read.sender_mac.sll_addr, sender_mac, 6); 141 | resp->arp_read.sender_mac.sll_halen = 6; 142 | memcpy(resp->arp_read.target_mac.sll_addr, target_mac, 6); 143 | resp->arp_read.target_mac.sll_halen = 6; 144 | memcpy(&resp->arp_read.sender_ip.sin_addr, sender_ip, 4); 145 | resp->arp_read.sender_ip.sin_family = AF_INET; 146 | memcpy(&resp->arp_read.target_ip.sin_addr, target_ip, 4); 147 | resp->arp_read.target_ip.sin_family = AF_INET; 148 | try { 149 | boost::asio::write(sock, buf); 150 | } catch (const std::exception &e) { 151 | BOOST_LOG_TRIVIAL(error) 152 | << "Exception in client handler: " << e.what(); 153 | } 154 | BOOST_LOG_TRIVIAL(trace) << "Sent response #" << resp->id; 155 | CLEANUP_BUF(resp, req) 156 | return true; 157 | }, 158 | client_id); 159 | break; 160 | case ARP_WRITE: 161 | arp::async_write_arp( 162 | req->arp_write.dev_id, req->arp_write.opcode, 163 | req->arp_write.sender_mac.sll_addr, 164 | (uint8_t *)&req->arp_write.sender_ip.sin_addr, 165 | req->arp_write.target_mac.sll_addr, 166 | (uint8_t *)&req->arp_write.target_ip.sin_addr, 167 | [buf, resp, req, &sock, client_id](int dev_id, int ret) { 168 | resp->payload_len = 0; 169 | resp->arp_write.dev_id = dev_id; 170 | 171 | try { 172 | boost::asio::write(sock, buf); 173 | } catch (const std::exception &e) { 174 | BOOST_LOG_TRIVIAL(error) 175 | << "Exception in client handler: " << e.what(); 176 | } 177 | 178 | BOOST_LOG_TRIVIAL(trace) << "Sent response #" << resp->id; 179 | CLEANUP_BUF(resp, req) 180 | }, 181 | client_id); 182 | break; 183 | case IP_READ: 184 | ip::async_read_ip( 185 | req->ip_read.proto, 186 | [buf, resp, req, &sock, 187 | client_id](const void *payload_ptr, uint64_t payload_len, 188 | const khtcp::ip::addr_t src, const khtcp::ip::addr_t dst, 189 | uint8_t dscp, const void *opt) -> bool { 190 | resp->payload_len = payload_len; 191 | resp->ip_read.dscp = dscp; 192 | memcpy(&resp->ip_read.dst.sin_addr, dst, 4); 193 | resp->ip_read.dst.sin_family = AF_INET; 194 | memcpy(&resp->ip_read.src.sin_addr, src, 4); 195 | resp->ip_read.src.sin_family = AF_INET; 196 | 197 | try { 198 | boost::asio::write(sock, buf); 199 | BOOST_LOG_TRIVIAL(trace) 200 | << "Sending payload with length " << payload_len; 201 | 202 | boost::asio::write(sock, 203 | boost::asio::buffer(payload_ptr, payload_len)); 204 | } catch (const std::exception &e) { 205 | BOOST_LOG_TRIVIAL(error) 206 | << "Exception in client handler: " << e.what(); 207 | } 208 | BOOST_LOG_TRIVIAL(trace) << "Sent response #" << resp->id; 209 | CLEANUP_BUF(resp, req) 210 | return true; 211 | }, 212 | client_id); 213 | break; 214 | case IP_WRITE: { 215 | void *payload_ptr = malloc(req->payload_len); 216 | boost::asio::read(sock, 217 | boost::asio::buffer(payload_ptr, req->payload_len)); 218 | ip::async_write_ip( 219 | (uint8_t *)&req->ip_write.src.sin_addr, 220 | (uint8_t *)&req->ip_write.dst.sin_addr, req->ip_write.proto, 221 | req->ip_write.dscp, req->ip_write.ttl, payload_ptr, req->payload_len, 222 | [buf, resp, req, payload_ptr, &sock, client_id](int ret) { 223 | resp->payload_len = 0; 224 | resp->ip_write.ret = ret; 225 | 226 | try { 227 | boost::asio::write(sock, buf); 228 | } catch (const std::exception &e) { 229 | BOOST_LOG_TRIVIAL(error) 230 | << "Exception in client handler: " << e.what(); 231 | } 232 | 233 | BOOST_LOG_TRIVIAL(trace) << "Sent response #" << resp->id; 234 | CLEANUP_BUF(resp, req) 235 | free(payload_ptr); 236 | }, 237 | client_id); 238 | break; 239 | } 240 | case SOCKET: { 241 | socket::socket s(client_id, req->socket.type); 242 | resp->payload_len = 0; 243 | if (s.type == SOCK_DGRAM) { 244 | resp->socket.fd = s.fd; 245 | BOOST_LOG_TRIVIAL(trace) 246 | << "Opened DGRAM socket fd " << s.fd << " for client " << client_id; 247 | get().client_sockets.emplace(std::piecewise_construct, 248 | std::forward_as_tuple(client_id, s.fd), 249 | std::forward_as_tuple(std::move(s))); 250 | } else if (s.type == SOCK_STREAM) { 251 | resp->socket.fd = -ENOTSUP; 252 | BOOST_LOG_TRIVIAL(error) 253 | << "Client " << client_id 254 | << " tried to open STREAM socket, which is currently not supported"; 255 | } else { 256 | resp->socket.fd = -EINVAL; 257 | BOOST_LOG_TRIVIAL(error) 258 | << "Client " << client_id << " tried to open unknown socket type " 259 | << s.type; 260 | } 261 | boost::asio::write(sock, buf); 262 | CLEANUP_BUF(resp, req) 263 | break; 264 | } 265 | case CLOSE: { 266 | resp->payload_len = 0; 267 | // https://pubs.opengroup.org/onlinepubs/9699919799/functions/close.html 268 | // FIXME: note that close may fail 269 | resp->close.ret = 0; 270 | 271 | get().client_sockets.erase({client_id, req->close.fd}); 272 | BOOST_LOG_TRIVIAL(trace) << "Closed socket fd " << req->close.fd 273 | << " for client " << client_id; 274 | boost::asio::write(sock, buf); 275 | CLEANUP_BUF(resp, req) 276 | break; 277 | } 278 | case BIND: { 279 | resp->payload_len = 0; 280 | // https://pubs.opengroup.org/onlinepubs/9699919799/functions/bind.html 281 | 282 | bool ok = true; 283 | if (get().client_sockets.find({client_id, req->bind.fd}) == 284 | get().client_sockets.end()) { 285 | BOOST_LOG_TRIVIAL(error) 286 | << "Client " << client_id 287 | << " tried to bind to non-existent socket fd " << req->bind.fd; 288 | ok = false; 289 | resp->bind.ret = ENOTSOCK; 290 | } 291 | for (const auto &[k, v] : get().client_sockets) { 292 | if (k.first == client_id) { 293 | if (v.bind_addr.sin_addr.s_addr == req->bind.addr.sin_addr.s_addr && 294 | v.bind_addr.sin_port == req->bind.addr.sin_port) { 295 | BOOST_LOG_TRIVIAL(error) 296 | << "Client " << client_id 297 | << " tried to bind to address already in use (" 298 | << util::ip_to_string((const uint8_t *)&req->bind.addr.sin_addr) 299 | << ":" << ntohs(req->bind.addr.sin_port) << ") for socket fd " 300 | << req->bind.fd; 301 | ok = false; 302 | resp->bind.ret = EADDRINUSE; 303 | } 304 | } 305 | } 306 | if (ok) { 307 | memcpy(&get().client_sockets.at({client_id, req->bind.fd}).bind_addr, 308 | &req->bind.addr, sizeof(struct sockaddr_in)); 309 | resp->bind.ret = 0; 310 | 311 | BOOST_LOG_TRIVIAL(trace) 312 | << "Client " << client_id << " binded " 313 | << util::ip_to_string((const uint8_t *)&req->bind.addr.sin_addr) 314 | << ":" << ntohs(req->bind.addr.sin_port) << " to socket fd " 315 | << req->bind.fd; 316 | } 317 | boost::asio::write(sock, buf); 318 | CLEANUP_BUF(resp, req) 319 | break; 320 | } 321 | case SENDTO: { 322 | void *payload_ptr = malloc(req->payload_len); 323 | boost::asio::read(sock, 324 | boost::asio::buffer(payload_ptr, req->payload_len)); 325 | 326 | resp->payload_len = 0; 327 | auto it = get().client_sockets.find({client_id, req->sendto.fd}); 328 | if (it == get().client_sockets.end() || it->second.type != SOCK_DGRAM) { 329 | resp->sendto.ret = -ENOTSOCK; 330 | BOOST_LOG_TRIVIAL(error) 331 | << "Client " << client_id 332 | << " tried to send to invalid DGRAM socket fd " << req->sendto.fd; 333 | boost::asio::write(sock, buf); 334 | CLEANUP_BUF(resp, req); 335 | break; 336 | } 337 | const uint8_t *src; 338 | uint16_t port; 339 | it->second.get_src((const uint8_t *)&req->sendto.dst.sin_addr, &src, 340 | &port); 341 | udp::async_write_udp( 342 | src, port, (const uint8_t *)&req->sendto.dst.sin_addr, 343 | ntohs(req->sendto.dst.sin_port), payload_ptr, req->payload_len, 344 | [buf, resp, req, payload_ptr, &sock](int ret) { 345 | resp->sendto.ret = req->payload_len; 346 | try { 347 | boost::asio::write(sock, buf); 348 | } catch (const std::exception &e) { 349 | BOOST_LOG_TRIVIAL(error) 350 | << "Exception in client handler: " << e.what(); 351 | } 352 | BOOST_LOG_TRIVIAL(trace) << "Sent response #" << resp->id; 353 | CLEANUP_BUF(resp, req) 354 | free(payload_ptr); 355 | }, 356 | client_id); 357 | break; 358 | } 359 | case RECVFROM: { 360 | auto it = get().client_sockets.find({client_id, req->recvfrom.fd}); 361 | if (it == get().client_sockets.end() || it->second.type != SOCK_DGRAM) { 362 | resp->recvfrom.ret = -ENOTSOCK; 363 | BOOST_LOG_TRIVIAL(error) 364 | << "Client " << client_id 365 | << " tried to recv from invalid DGRAM socket fd " << req->sendto.fd; 366 | boost::asio::write(sock, buf); 367 | CLEANUP_BUF(resp, req); 368 | break; 369 | } 370 | BOOST_LOG_TRIVIAL(trace) 371 | << "Received recvfrom request from client with ID " << req->id; 372 | udp::async_read_udp( 373 | [it, buf, resp, req, &sock, 374 | client_id](const void *payload_ptr, uint64_t payload_len, 375 | const khtcp::ip::addr_t src, uint16_t src_port, 376 | const khtcp::ip::addr_t dst, uint16_t dst_port) -> bool { 377 | BOOST_LOG_TRIVIAL(trace) << "Got UDP packet from port " << src_port 378 | << " to port " << dst_port; 379 | if (it->second.bind_addr.sin_family == AF_INET && 380 | (memcmp(&it->second.bind_addr.sin_addr, dst, 381 | sizeof(ip::addr_t)) || 382 | dst_port != ntohs(it->second.bind_addr.sin_port))) { 383 | BOOST_LOG_TRIVIAL(trace) 384 | << "Bound socket mismatch with destination: expected " 385 | << util::ip_to_string( 386 | (uint8_t *)&it->second.bind_addr.sin_addr) 387 | << ":" << ntohs(it->second.bind_addr.sin_port) << ", got " 388 | << util::ip_to_string(dst) << ":" << dst_port; 389 | return false; 390 | } 391 | resp->payload_len = payload_len; 392 | resp->recvfrom.ret = payload_len; 393 | resp->recvfrom.src.sin_family = AF_INET; 394 | memcpy(&resp->recvfrom.src.sin_addr, src, sizeof(ip::addr_t)); 395 | resp->recvfrom.src.sin_port = htons(src_port); 396 | 397 | try { 398 | boost::asio::write(sock, buf); 399 | BOOST_LOG_TRIVIAL(trace) 400 | << "Sending payload with length " << payload_len; 401 | 402 | boost::asio::write(sock, 403 | boost::asio::buffer(payload_ptr, payload_len)); 404 | } catch (const std::exception &e) { 405 | BOOST_LOG_TRIVIAL(error) 406 | << "Exception in client handler: " << e.what(); 407 | } 408 | BOOST_LOG_TRIVIAL(trace) << "Sent response #" << resp->id; 409 | CLEANUP_BUF(resp, req) 410 | return true; 411 | }, 412 | client_id); 413 | break; 414 | } 415 | default: 416 | BOOST_LOG_TRIVIAL(warning) 417 | << "Unknown request " << req->type << " from client " << client_id; 418 | CLEANUP_BUF(resp, req) 419 | } 420 | BOOST_LOG_TRIVIAL(trace) << "Written response to client " << client_id; 421 | // fire the handler again 422 | 423 | client.second = (struct request *)malloc(sizeof(struct request)); 424 | get().outstanding_buffers.emplace(client_id, client.second); 425 | boost::asio::async_read( 426 | client.first, 427 | boost::asio::buffer(client.second, sizeof(struct request)), 428 | boost::bind(client_request_handler, boost::asio::placeholders::error, 429 | boost::asio::placeholders::bytes_transferred, client_id)); 430 | } else { 431 | if (ec == boost::asio::error::eof) { 432 | BOOST_LOG_TRIVIAL(debug) << "Client " << client_id << " disconnected."; 433 | } else { 434 | BOOST_LOG_TRIVIAL(warning) << "Client handler failed: " << ec.message(); 435 | } 436 | BOOST_LOG_TRIVIAL(trace) << "Cleaning up client " << client_id; 437 | cleanup_client(client_id); 438 | } 439 | } catch (const std::exception &e) { 440 | BOOST_LOG_TRIVIAL(error) << "Exception in client handler: " << e.what(); 441 | } 442 | 443 | void new_client_handler(const boost::system::error_code &ec, 444 | boost::asio::local::stream_protocol::socket &&sock) { 445 | auto id = rand(); 446 | // ensure that the id is non-zero - 0 is for local call 447 | while (!id) { 448 | id = rand(); 449 | } 450 | while (get().clients.find(id) != get().clients.end()) { 451 | id = rand(); 452 | } 453 | 454 | BOOST_LOG_TRIVIAL(debug) << "Accpeted new client with id " << id; 455 | get().clients.emplace(std::piecewise_construct, std::forward_as_tuple(id), 456 | std::forward_as_tuple(std::move(sock), nullptr)); 457 | 458 | auto &client = get().clients.at(id); 459 | client.second = (struct request *)malloc(sizeof(struct request)); 460 | get().outstanding_buffers.emplace(id, client.second); 461 | // start reading of the client 462 | boost::asio::async_read( 463 | client.first, boost::asio::buffer(client.second, sizeof(struct request)), 464 | boost::bind(client_request_handler, boost::asio::placeholders::error, 465 | boost::asio::placeholders::bytes_transferred, id)); 466 | 467 | // start accepting new client 468 | get().acceptor.async_accept(new_client_handler); 469 | } 470 | 471 | void cleanup_client(int client_id) { 472 | auto &wq = get().write_tasks; 473 | auto it = wq.begin(); 474 | while (it != wq.end()) { 475 | if (it->second == client_id) { 476 | wq.erase(it++); 477 | BOOST_LOG_TRIVIAL(trace) 478 | << "Removed pending write task for client " << client_id; 479 | } else { 480 | ++it; 481 | } 482 | } 483 | auto &rq = get().read_handlers; 484 | auto rit = rq.begin(); 485 | while (rit != rq.end()) { 486 | if (rit->second == client_id) { 487 | rq.erase(rit++); 488 | BOOST_LOG_TRIVIAL(trace) 489 | << "Removed pending read handler for client " << client_id; 490 | } else { 491 | ++rit; 492 | } 493 | } 494 | auto bit = get().outstanding_buffers.find(client_id); 495 | while (bit != get().outstanding_buffers.end()) { 496 | free(bit->second); 497 | get().outstanding_buffers.erase(bit); 498 | bit = get().outstanding_buffers.find(client_id); 499 | } 500 | get().clients.erase(client_id); 501 | 502 | // close all client sockets 503 | auto sit = get().client_sockets.begin(); 504 | while (sit != get().client_sockets.end()) { 505 | if (sit->first.first == client_id) { 506 | get().client_sockets.erase(sit++); 507 | } else { 508 | ++sit; 509 | } 510 | } 511 | } 512 | 513 | void per_second_tasks(const boost::system::error_code &ec) { 514 | static uint32_t counter = 0; 515 | if (!ec) { 516 | arp::scan_arp_table(); 517 | ip::update_route(); 518 | if (counter % 30 == 0) { 519 | for (int i = 0; i < get().devices.size(); ++i) { 520 | rip::send_fulltable(device::get_device_handle(i).ip_addrs[0], rip::port, 521 | rip::RIP_MULTICAST, rip::port); 522 | } 523 | } 524 | if (counter % 30 == 2) { 525 | // shift from the unsolicited response of RIP 526 | ip::print_route(); 527 | } 528 | ++counter; 529 | // fire a new round 530 | auto &timer = get().per_second_timer; 531 | timer.expires_from_now(boost::posix_time::seconds(1)); 532 | timer.async_wait(per_second_tasks); 533 | } else { 534 | BOOST_LOG_TRIVIAL(error) 535 | << "Per-second timer for periodic tasks failed with message: " 536 | << ec.message(); 537 | } 538 | } 539 | 540 | void record_multicast_buffer(void *buf) { 541 | get().multicast_buffers[buf] = get().devices.size(); 542 | } 543 | 544 | int core::run() { 545 | srand((unsigned)time(nullptr)); 546 | acceptor.async_accept(new_client_handler); 547 | 548 | // start the write tasks handler 549 | std::function write_task_executor = [&]() { 550 | auto &wq = get().write_tasks; 551 | auto it = wq.begin(); 552 | if (wq.empty()) { 553 | static boost::asio::deadline_timer t(get().io_context); 554 | t.expires_from_now(boost::posix_time::milliseconds(1)); 555 | t.async_wait([&](const auto &ec) { 556 | boost::asio::post(get().write_tasks_strand, write_task_executor); 557 | }); 558 | } else { 559 | while (it != wq.end()) { 560 | it->first(); 561 | wq.erase(it); 562 | break; 563 | ++it; 564 | } 565 | boost::asio::post(get().write_tasks_strand, write_task_executor); 566 | } 567 | }; 568 | boost::asio::post(get().write_tasks_strand, write_task_executor); 569 | 570 | // start the tasks that are run per second 571 | auto &timer = get().per_second_timer; 572 | timer.expires_from_now(boost::posix_time::seconds(1)); 573 | timer.async_wait(per_second_tasks); 574 | 575 | ip::start(); 576 | rip::start(); 577 | tcp::start(); 578 | 579 | // Join RIPv2 multicast group 580 | ip::join_multicast(rip::RIP_MULTICAST); 581 | 582 | io_context.run(); 583 | // should never reach here 584 | return -1; 585 | } // namespace core 586 | } // namespace core 587 | } // namespace khtcp -------------------------------------------------------------------------------- /src/device.cc: -------------------------------------------------------------------------------- 1 | #include "device.h" 2 | #include "arp.h" 3 | #include "core.h" 4 | #include "packetio.h" 5 | #include "util.h" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | namespace khtcp { 15 | namespace device { 16 | 17 | int device_t::start_capture() { 18 | char error_buffer[PCAP_ERRBUF_SIZE]; 19 | BOOST_LOG_TRIVIAL(debug) << "Opening capture on " << name; 20 | pcap_handle = pcap_open_live(name.c_str(), CAPTURE_BUFSIZ, false, 21 | PACKET_TIMEOUT, error_buffer); 22 | if (!pcap_handle) { 23 | BOOST_LOG_TRIVIAL(error) << "Failed to open device: " << error_buffer; 24 | return -1; 25 | } 26 | if (pcap_datalink(pcap_handle) != DLT_EN10MB) { 27 | BOOST_LOG_TRIVIAL(error) 28 | << "Device " << name << " does not support EN10MB capturing"; 29 | return -1; 30 | } 31 | 32 | trigger = new boost::asio::posix::stream_descriptor( 33 | core::get().io_context, dup(pcap_get_selectable_fd(pcap_handle))); 34 | 35 | // start the task 36 | trigger->async_read_some(boost::asio::null_buffers(), 37 | [this](auto ec, auto s) { this->handle_sniff(); }); 38 | 39 | return 0; 40 | } 41 | 42 | device_t::device_t() 43 | : inject_strand(core::get().io_context), trigger(nullptr) {} 44 | 45 | device_t::~device_t() { 46 | for (auto &ip : ip_addrs) { 47 | delete[] ip; 48 | } 49 | if (trigger) { 50 | trigger->close(); 51 | delete trigger; 52 | } 53 | if (pcap_handle) { 54 | pcap_close(pcap_handle); 55 | } 56 | } 57 | 58 | void device_t::handle_sniff() { 59 | pcap_pkthdr hdr; 60 | auto pkt = pcap_next(pcap_handle, &hdr); 61 | if (!pkt) { 62 | BOOST_LOG_TRIVIAL(error) << "Failed to read packet from pcap"; 63 | core::get().io_context.stop(); 64 | return; 65 | } 66 | BOOST_LOG_TRIVIAL(trace) << "Captured frame of length " << hdr.len 67 | << " from device " << name << ", caplen " 68 | << hdr.caplen; 69 | 70 | // invoke upper handler 71 | auto callback = eth::get_frame_receive_callback(); 72 | if (callback) { 73 | if ((*callback)(pkt, hdr.len, id)) { 74 | BOOST_LOG_TRIVIAL(error) << "Frame receive callback failed"; 75 | core::get().io_context.stop(); 76 | return; 77 | } 78 | 79 | // recharge the task 80 | trigger->async_read_some(boost::asio::null_buffers(), 81 | [this](auto ec, auto s) { this->handle_sniff(); }); 82 | } else { 83 | BOOST_LOG_TRIVIAL(fatal) << "Frame receive callback unset"; 84 | } 85 | } 86 | 87 | int device_t::inject_frame(const uint8_t *buf, size_t len) { 88 | BOOST_LOG_TRIVIAL(trace) << "Sending frame with length " << len 89 | << " on device " << name; 90 | return pcap_sendpacket(pcap_handle, buf, len); 91 | } 92 | 93 | void device_t::async_inject_frame(const uint8_t *buf, size_t len, 94 | write_handler_t &&handler) { 95 | boost::asio::post(inject_strand, 96 | [=]() { handler(this->inject_frame(buf, len)); }); 97 | } 98 | 99 | int add_device(const char *device) { 100 | int ret = -1; 101 | ifaddrs *ifaddr = nullptr; 102 | if (getifaddrs(&ifaddr) == -1) { 103 | BOOST_LOG_TRIVIAL(error) << "getifaddrs failed: " << strerror(errno); 104 | return ret; 105 | } else { 106 | for (ifaddrs *ifa = ifaddr; ifa; ifa = ifa->ifa_next) { 107 | if (!ifa->ifa_addr) 108 | continue; 109 | if ((!device && strcmp(ifa->ifa_name, "lo")) || 110 | (device && !strcmp(ifa->ifa_name, device))) { 111 | // add all devices except lo if device==nullptr 112 | switch (ifa->ifa_addr->sa_family) { 113 | case AF_PACKET: { 114 | auto s = (sockaddr_ll *)ifa->ifa_addr; 115 | auto new_device = std::make_shared(); 116 | new_device->name = std::string(ifa->ifa_name); 117 | BOOST_ASSERT_MSG(s->sll_halen == 6, "Unexpected addr length"); 118 | memcpy(new_device->addr, s->sll_addr, sizeof(eth::addr_t)); 119 | new_device->id = core::get().devices.size(); 120 | core::get().devices.push_back(new_device); 121 | BOOST_LOG_TRIVIAL(debug) 122 | << "Found requested device " << ifa->ifa_name << "(" 123 | << util::mac_to_string(new_device->addr) 124 | << ") as id=" << new_device->id; 125 | 126 | if (new_device->start_capture() == 0) { 127 | ret = new_device->id; 128 | 129 | // start the per-device auto-answering protocol stacks. 130 | arp::start(ret); 131 | } 132 | break; 133 | } 134 | case AF_INET: { 135 | auto s = (sockaddr_in *)ifa->ifa_addr; 136 | if (ret == -1) { 137 | continue; 138 | } 139 | int dev_id = find_device(ifa->ifa_name); 140 | auto &device = get_device_handle(dev_id); 141 | auto ip = new uint8_t[sizeof(ip::addr_t)]; 142 | memcpy(ip, &s->sin_addr.s_addr, sizeof(ip::addr_t)); 143 | device.ip_addrs.push_back(ip); 144 | BOOST_LOG_TRIVIAL(info) 145 | << "Added IP address " << util::ip_to_string(ip) << " to device " 146 | << device.name; 147 | 148 | // add route for subnet 149 | ip::route r; 150 | r.dev_id = dev_id; 151 | r.prefix = util::mask_to_cidr( 152 | ((const struct sockaddr_in *)ifa->ifa_netmask)->sin_addr.s_addr); 153 | r.age = -1; // never expires 154 | memset(r.dst, 0, sizeof(ip::addr_t)); 155 | memcpy(r.dst, &s->sin_addr.s_addr, r.prefix / 8); 156 | 157 | BOOST_LOG_TRIVIAL(info) 158 | << "Added device route " << util::ip_to_string(r.dst) << "/" 159 | << (int)r.prefix << " dev " << device.name << " metric " 160 | << r.metric; 161 | ip::add_route(std::move(r)); 162 | break; 163 | } 164 | } 165 | } 166 | } 167 | } 168 | if (ret == -1) { 169 | if (device) { 170 | BOOST_LOG_TRIVIAL(error) << "Device " << device << " not found"; 171 | } else { 172 | BOOST_LOG_TRIVIAL(error) << "Requested to add all devices, but none " 173 | "succeeded; check permissions?"; 174 | } 175 | } 176 | freeifaddrs(ifaddr); 177 | return ret; 178 | } 179 | 180 | int find_device(const char *device) { 181 | for (int i = 0; i < core::get().devices.size(); ++i) { 182 | if (core::get().devices[i]->name == device) { 183 | return i; 184 | } 185 | } 186 | BOOST_LOG_TRIVIAL(error) << "Device " << device << " not found"; 187 | return -1; 188 | } 189 | 190 | device_t &get_device_handle(int id) { return *core::get().devices.at(id); } 191 | } // namespace device 192 | } // namespace khtcp 193 | -------------------------------------------------------------------------------- /src/ip.cc: -------------------------------------------------------------------------------- 1 | #include "ip.h" 2 | #include "arp.h" 3 | #include "core.h" 4 | #include "device.h" 5 | #include "rip.h" 6 | #include "util.h" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace khtcp { 14 | namespace ip { 15 | 16 | struct ip_holder { 17 | addr_t data; 18 | }; 19 | 20 | std::vector multicasts; 21 | 22 | uint8_t *map_ip_multicast_to_eth(const addr_t ip) { 23 | 24 | auto eth_mult = (uint8_t *)malloc(sizeof(eth::addr_t)); 25 | memset(eth_mult, 0, sizeof(eth::addr_t)); 26 | eth_mult[0] = 0x01; 27 | (*(uint32_t *)ð_mult[2]) = boost::endian::endian_reverse( 28 | 0x5e000000 | (boost::endian::endian_reverse(*(uint32_t *)ip) & 0x7fffff)); 29 | return eth_mult; 30 | } 31 | 32 | void join_multicast(const addr_t multicast) { 33 | BOOST_LOG_TRIVIAL(info) << "Joining IP multicast group " 34 | << util::ip_to_string(multicast); 35 | multicasts.emplace_back(); 36 | memcpy(multicasts[multicasts.size() - 1].data, multicast, sizeof(addr_t)); 37 | 38 | auto eth_mult = map_ip_multicast_to_eth(multicast); 39 | eth::join_multicast(eth_mult); 40 | free(eth_mult); 41 | } 42 | 43 | uint16_t ip_checksum(const void *vdata, size_t length) { 44 | // Cast the data pointer to one that can be indexed. 45 | char *data = (char *)vdata; 46 | 47 | // Initialise the accumulator. 48 | uint64_t acc = 0xffff; 49 | 50 | // Handle any partial block at the start of the data. 51 | unsigned int offset = ((uintptr_t)data) & 3; 52 | if (offset) { 53 | size_t count = 4 - offset; 54 | if (count > length) 55 | count = length; 56 | uint32_t word = 0; 57 | memcpy(offset + (char *)&word, data, count); 58 | acc += ntohl(word); 59 | data += count; 60 | length -= count; 61 | } 62 | 63 | // Handle any complete 32-bit blocks. 64 | char *data_end = data + (length & ~3); 65 | while (data != data_end) { 66 | uint32_t word; 67 | memcpy(&word, data, 4); 68 | acc += ntohl(word); 69 | data += 4; 70 | } 71 | length &= 3; 72 | 73 | // Handle any partial block at the end of the data. 74 | if (length) { 75 | uint32_t word = 0; 76 | memcpy(&word, data, length); 77 | acc += ntohl(word); 78 | } 79 | 80 | // Handle deferred carries. 81 | acc = (acc & 0xffffffff) + (acc >> 32); 82 | while (acc >> 16) { 83 | acc = (acc & 0xffff) + (acc >> 16); 84 | } 85 | 86 | // If the data began at an odd byte address 87 | // then reverse the byte order to compensate. 88 | if (offset & 1) { 89 | acc = ((acc & 0xff00) >> 8) | ((acc & 0x00ff) << 8); 90 | } 91 | 92 | // Return the checksum in network byte order. 93 | return htons(~acc); 94 | } 95 | 96 | device::read_handler_t::first_type wrap_read_handler(int16_t proto, 97 | read_handler_t handler) { 98 | return [=](int dev_id, uint16_t ethertype, const uint8_t *packet_ptr, 99 | int packet_len) -> bool { 100 | if (ethertype != ip::ethertype) { 101 | return false; 102 | } 103 | auto hdr_ptr = (ip_header_t *)packet_ptr; 104 | 105 | auto chksum = hdr_ptr->header_csum; 106 | hdr_ptr->header_csum = 0; 107 | hdr_ptr->header_csum = ip_checksum(hdr_ptr, sizeof(ip_header_t)); 108 | 109 | if (hdr_ptr->header_csum != chksum) { 110 | BOOST_LOG_TRIVIAL(warning) 111 | << "Dropping IP packet with incorrect header checksum: expected 0x" 112 | << std::hex << std::setw(4) << std::setfill('0') 113 | << hdr_ptr->header_csum << ", got 0x" << std::hex << std::setw(4) 114 | << std::setfill('0') << chksum; 115 | return false; 116 | } 117 | 118 | BOOST_LOG_TRIVIAL(trace) << "Received IP packet on device " 119 | << device::get_device_handle(dev_id).name 120 | << " with proto " << (int)hdr_ptr->proto; 121 | auto unicast = false; 122 | auto multicast = false; 123 | auto self_sent = false; 124 | for (const auto &dev : core::get().devices) { 125 | for (const auto &ip : dev->ip_addrs) { 126 | BOOST_LOG_TRIVIAL(trace) 127 | << "Checking local address " << util::ip_to_string(ip); 128 | unicast = unicast || !memcmp(ip, hdr_ptr->dst_addr, sizeof(addr_t)); 129 | self_sent = self_sent || !memcmp(ip, hdr_ptr->src_addr, sizeof(addr_t)); 130 | } 131 | } 132 | for (const auto &m : multicasts) { 133 | multicast = 134 | multicast || !memcmp(hdr_ptr->dst_addr, m.data, sizeof(addr_t)); 135 | if (multicast) { 136 | BOOST_LOG_TRIVIAL(trace) << "Received packet to multicast group " 137 | << util::ip_to_string(m.data); 138 | break; 139 | } 140 | } 141 | if (proto < 0 || hdr_ptr->proto == proto) { 142 | uint16_t hdr_len = hdr_ptr->ihl * sizeof(uint32_t); 143 | if (hdr_len != 20) { 144 | BOOST_LOG_TRIVIAL(warning) 145 | << "Ignoring IP packet options; total header length: " << hdr_len; 146 | } 147 | auto option_ptr = ((const uint8_t *)packet_ptr) + sizeof(ip_header_t); 148 | auto payload_ptr = ((const uint8_t *)packet_ptr) + hdr_len; 149 | auto payload_len = 150 | boost::endian::endian_reverse(hdr_ptr->total_length) - hdr_len; 151 | if (unicast || multicast) { 152 | return handler(payload_ptr, payload_len, hdr_ptr->src_addr, 153 | hdr_ptr->dst_addr, hdr_ptr->dscp, option_ptr); 154 | } else if (self_sent) { 155 | // ignore packets sent by self, keeping the handler intact 156 | BOOST_LOG_TRIVIAL(trace) << "Ignoring packet sent by self"; 157 | return false; 158 | } else if (proto < 0) { 159 | // forwarding handler 160 | if (hdr_ptr->ttl == 0) { 161 | // TODO: send ICMP Time Exceeded Message back to src 162 | } else { 163 | // forward the packet, keeping the handler intact 164 | async_write_ip(hdr_ptr->src_addr, hdr_ptr->dst_addr, hdr_ptr->proto, 165 | hdr_ptr->dscp, hdr_ptr->ttl - 1, payload_ptr, 166 | payload_len, [hdr_ptr](auto ret) { 167 | if (!ret) { 168 | BOOST_LOG_TRIVIAL(trace) 169 | << "IP packet forwarded " 170 | << util::ip_to_string(hdr_ptr->src_addr) 171 | << " > " 172 | << util::ip_to_string(hdr_ptr->dst_addr); 173 | } else if (ret == ECANCELED) { 174 | // non-local broadcast 175 | BOOST_LOG_TRIVIAL(trace) 176 | << "Not forwarding non-local broadcast."; 177 | } else { 178 | BOOST_LOG_TRIVIAL(error) 179 | << "IP packet forwarding from " 180 | << util::ip_to_string(hdr_ptr->src_addr) 181 | << " to " 182 | << util::ip_to_string(hdr_ptr->dst_addr) 183 | << " failed: Errno " << ret; 184 | } 185 | }); 186 | } 187 | // call the handler: default handler has logging purposes 188 | return handler(payload_ptr, payload_len, hdr_ptr->src_addr, 189 | hdr_ptr->dst_addr, hdr_ptr->dscp, option_ptr); 190 | } else { 191 | // not forwarding handler & not ucast/mcast/self-sent: ignore 192 | return false; 193 | } 194 | } else { 195 | BOOST_LOG_TRIVIAL(trace) 196 | << "Ignoring packet with mismatch proto: expected " << proto 197 | << ", got " << (int)hdr_ptr->proto; 198 | return false; 199 | } 200 | }; 201 | } 202 | 203 | // Used to print debug information for all packets and forward all IP packets. 204 | bool default_handler(const void *payload_ptr, uint64_t payload_len, 205 | const addr_t src, const addr_t dst, uint8_t dscp, 206 | const void *opt) { 207 | BOOST_LOG_TRIVIAL(trace) << "IP packet from " << util::ip_to_string(src) 208 | << " to " << util::ip_to_string(dst) 209 | << " with payload length " << payload_len; 210 | return false; 211 | } 212 | 213 | void start() { async_read_ip(-1, default_handler); } 214 | 215 | void async_read_ip(int proto, read_handler_t &&handler, int client_id) { 216 | boost::asio::post(core::get().read_handlers_strand, [=]() { 217 | core::get().read_handlers.emplace_back(wrap_read_handler(proto, handler), 218 | client_id); 219 | BOOST_LOG_TRIVIAL(trace) << "IP read handler queued"; 220 | }); 221 | } 222 | 223 | void test_multicast_broadcast(const ip::addr_t dst, route **r, 224 | bool *is_multicast, bool *is_broadcast) { 225 | // reset the input value 226 | *is_multicast = false; 227 | *is_broadcast = false; 228 | 229 | route *r_; 230 | route **route = r ? r : &r_; 231 | bool has_route = lookup_route(dst, route); 232 | if (!has_route) { 233 | *r = nullptr; 234 | } 235 | if (has_route && !(*route)->has_router) { 236 | // check if is broadcast address 237 | uint32_t iform = boost::endian::endian_reverse(*(uint32_t *)dst); 238 | uint32_t subnet_mask = (*route)->prefix == 0 239 | ? 0 240 | : *(uint32_t *)IP_BROADCAST 241 | << (32 - (*route)->prefix); 242 | *is_broadcast = (iform | subnet_mask) == 0xffffffff; 243 | } else { 244 | *is_broadcast = false; 245 | } 246 | if (!*is_broadcast) { 247 | for (const auto &m : multicasts) { 248 | *is_multicast = *is_multicast || !memcmp(dst, m.data, sizeof(addr_t)); 249 | if (*is_multicast) { 250 | break; 251 | } 252 | } 253 | } 254 | } 255 | 256 | void async_write_ip(const addr_t src, const addr_t dst, uint8_t proto, 257 | uint8_t dscp, uint8_t ttl, const void *payload_ptr, 258 | uint64_t payload_len, write_handler_t &&handler, 259 | int client_id, uint16_t identification, bool df, 260 | const void *option) { 261 | boost::asio::post(core::get().write_tasks_strand, [=]() mutable { 262 | core::get().write_tasks.emplace_back( 263 | [=]() mutable { 264 | bool is_local = false; 265 | if (!src) { 266 | is_local = true; 267 | } else { 268 | for (const auto &dev : core::get().devices) { 269 | for (const auto &ip : dev->ip_addrs) { 270 | BOOST_LOG_TRIVIAL(trace) 271 | << "Checking local address " << util::ip_to_string(ip); 272 | if (!memcmp(ip, src, sizeof(addr_t))) { 273 | is_local = true; 274 | } 275 | } 276 | } 277 | } 278 | if (client_id != 0) { 279 | // enforce src to be local IP if request from client 280 | if (!is_local) { 281 | BOOST_LOG_TRIVIAL(error) 282 | << "Client " << client_id << " tried to send from " 283 | << util::ip_to_string(src) 284 | << ", which is not a local address"; 285 | handler(EINVAL); 286 | return; 287 | } 288 | } 289 | 290 | BOOST_LOG_TRIVIAL(trace) 291 | << "Sending IP packet with payload length " << payload_len; 292 | 293 | if (option) { 294 | BOOST_LOG_TRIVIAL(error) 295 | << "Requested to send non-null option IP packet"; 296 | handler(EINVAL); 297 | return; 298 | } 299 | 300 | struct route *route; 301 | 302 | bool is_broadcast, is_multicast; 303 | test_multicast_broadcast(dst, &route, &is_multicast, &is_broadcast); 304 | 305 | const uint8_t *resolv_mac_ip; 306 | int dev_id; 307 | if (!is_broadcast && !is_multicast) { 308 | if (!route || route->metric >= rip::infinity) { 309 | // failed to get route for destination 310 | BOOST_LOG_TRIVIAL(warning) 311 | << "No route to host " << util::ip_to_string(dst); 312 | // TODO: send ICMP Destination Unreachable Message 313 | // back to src 314 | handler(EHOSTUNREACH); 315 | return; 316 | } 317 | resolv_mac_ip = route->has_router ? route->router : dst; 318 | dev_id = route->dev_id; 319 | } 320 | 321 | auto packet_len = sizeof(ip_header_t) + payload_len; 322 | auto packet_ptr = (uint8_t *)malloc(packet_len); 323 | if (is_broadcast || is_multicast) { 324 | core::record_multicast_buffer(packet_ptr); 325 | ttl = 1; 326 | } 327 | auto hdr = (ip_header_t *)packet_ptr; 328 | auto packet_payload = ((uint8_t *)packet_ptr) + sizeof(ip_header_t); 329 | hdr->version = 4; 330 | hdr->ihl = 5; 331 | hdr->dscp = dscp; 332 | hdr->ecn = 0; 333 | hdr->total_length = 334 | boost::endian::endian_reverse((uint16_t)packet_len); 335 | hdr->identification = identification; 336 | hdr->flags = boost::endian::endian_reverse((uint16_t)(df << 14)); 337 | hdr->ttl = ttl; 338 | hdr->proto = proto; 339 | 340 | if (src) { 341 | memcpy(hdr->src_addr, src, sizeof(addr_t)); 342 | } 343 | memcpy(hdr->dst_addr, dst, sizeof(addr_t)); 344 | 345 | hdr->header_csum = 0; 346 | hdr->header_csum = ip_checksum(hdr, sizeof(ip_header_t)); 347 | 348 | memcpy(packet_payload, payload_ptr, payload_len); 349 | 350 | auto send_broadcast = [=](auto buf, auto dev_id) { 351 | if (is_broadcast) { 352 | eth::async_write_frame( 353 | buf, packet_len, ip::ethertype, eth::ETH_BROADCAST, dev_id, 354 | [=](int ret) { 355 | if (ret) { 356 | BOOST_LOG_TRIVIAL(error) 357 | << "Failed to write IP packet: Errno " << ret; 358 | } 359 | handler(ret); 360 | free(buf); 361 | }); 362 | } else { 363 | auto multi_dst = map_ip_multicast_to_eth(dst); 364 | eth::async_write_frame( 365 | buf, packet_len, ip::ethertype, multi_dst, dev_id, 366 | [=](int ret) { 367 | if (ret) { 368 | BOOST_LOG_TRIVIAL(error) 369 | << "Failed to write IP packet: Errno " << ret; 370 | } 371 | handler(ret); 372 | free(buf); 373 | free(multi_dst); 374 | }); 375 | } 376 | }; 377 | 378 | if (is_broadcast || is_multicast) { 379 | if (is_local) { 380 | if (!src) { 381 | for (int i = 0; i < core::get().devices.size(); ++i) { 382 | uint8_t *dev_buf_ptr = (uint8_t *)malloc(packet_len); 383 | memcpy(dev_buf_ptr, packet_ptr, packet_len); 384 | 385 | auto hdr = (ip_header_t *)dev_buf_ptr; 386 | memcpy(hdr->src_addr, 387 | device::get_device_handle(i).ip_addrs[0], 388 | sizeof(addr_t)); 389 | BOOST_LOG_TRIVIAL(trace) 390 | << "Setting broadcast/multicast src to " 391 | << util::ip_to_string(hdr->src_addr) << " for device" 392 | << device::get_device_handle(i).name; 393 | hdr->header_csum = 0; 394 | hdr->header_csum = ip_checksum(hdr, sizeof(ip_header_t)); 395 | send_broadcast(dev_buf_ptr, i); 396 | } 397 | free(packet_ptr); 398 | } else { 399 | int dev_id; 400 | for (int i = 0; i < core::get().devices.size(); ++i) { 401 | for (const auto &ip : device::get_device_handle(i).ip_addrs) { 402 | if (!memcmp(src, ip, sizeof(ip::addr_t))) { 403 | dev_id = i; 404 | } 405 | } 406 | } 407 | send_broadcast(packet_ptr, dev_id); 408 | } 409 | } else { 410 | BOOST_LOG_TRIVIAL(trace) 411 | << "Non-local IP " 412 | << (is_multicast ? "multicast" : "broadcast") << " to " 413 | << util::ip_to_string(dst) << " not repeated"; 414 | handler(ECANCELED); 415 | free(packet_ptr); 416 | } 417 | } else { 418 | auto mac_handler = [packet_ptr, packet_len, dev_id, resolv_mac_ip, 419 | handler](int ret, const eth::addr_t addr) { 420 | if (ret) { 421 | BOOST_LOG_TRIVIAL(warning) 422 | << "Failed to resolve MAC for " 423 | << util::ip_to_string(resolv_mac_ip) << ": Errno " << ret; 424 | handler(ret); 425 | free(packet_ptr); 426 | } else { 427 | eth::async_write_frame( 428 | packet_ptr, packet_len, ip::ethertype, addr, dev_id, 429 | [=](int ret) { 430 | if (ret) { 431 | BOOST_LOG_TRIVIAL(error) 432 | << "Failed to write IP packet: Errno " << ret; 433 | } 434 | handler(ret); 435 | free(packet_ptr); 436 | }); 437 | } 438 | }; 439 | arp::async_resolve_mac(dev_id, resolv_mac_ip, mac_handler); 440 | } 441 | }, 442 | client_id); 443 | }); 444 | } 445 | 446 | bool route::operator<(const struct route &a) const { 447 | if (prefix > a.prefix) { 448 | return true; 449 | } else if (prefix == a.prefix) { 450 | if (metric < a.metric) { 451 | return true; 452 | } else if (metric == a.metric) { 453 | return memcmp(dst, a.dst, sizeof(addr_t)); 454 | } else { 455 | return false; 456 | } 457 | } else { 458 | return false; 459 | } 460 | } 461 | 462 | std::list routing_table; 463 | 464 | std::list &get_routing_table() { return routing_table; } 465 | 466 | bool add_route(struct route &&route) { 467 | if (route.has_router) { 468 | struct route *router_route; 469 | if (!lookup_route(route.router, &router_route)) { 470 | BOOST_LOG_TRIVIAL(error) << "Tried to add route with invalid router " 471 | << util::ip_to_string(route.router); 472 | return false; 473 | } 474 | if (route.dev_id < 0) { 475 | // fill in device 476 | route.dev_id = router_route->dev_id; 477 | } 478 | } 479 | if (route.dev_id < 0) { 480 | BOOST_LOG_TRIVIAL(error) 481 | << "Tried to add route with invalid out device id " << route.dev_id; 482 | return false; 483 | } 484 | route.changed = true; 485 | routing_table.emplace_back(route); 486 | routing_table.sort(); 487 | return true; 488 | } 489 | 490 | bool lookup_route(const addr_t dst, struct route **out_route, int prefix) { 491 | BOOST_LOG_TRIVIAL(trace) << "Looking up route " << util::ip_to_string(dst) 492 | << "/" << prefix; 493 | uint32_t addr = boost::endian::endian_reverse(*(uint32_t *)dst); 494 | for (auto &r : routing_table) { 495 | BOOST_LOG_TRIVIAL(trace) << "Trying " << util::ip_to_string(r.dst); 496 | uint32_t route_dst = boost::endian::endian_reverse(*(uint32_t *)r.dst); 497 | if (!r.prefix || addr >> (32 - r.prefix) == route_dst >> (32 - r.prefix)) { 498 | if (prefix == -1 || r.prefix == prefix) { 499 | *out_route = &r; 500 | return true; 501 | } 502 | } 503 | } 504 | return false; 505 | } 506 | 507 | bool delete_route(const struct route *route) { 508 | auto it = routing_table.begin(); 509 | while (it != routing_table.end()) { 510 | if (!memcmp(route->dst, it->dst, sizeof(ip::addr_t)) && 511 | route->prefix == it->prefix) { 512 | BOOST_LOG_TRIVIAL(info) << "Route to " << util::ip_to_string(route->dst) 513 | << "/" << (int)route->prefix << " deleted."; 514 | routing_table.erase(it++); 515 | return true; 516 | } else { 517 | ++it; 518 | } 519 | } 520 | return false; 521 | } 522 | 523 | void update_route() { 524 | BOOST_LOG_TRIVIAL(trace) << "Aging routes"; 525 | for (auto ri = routing_table.begin(); ri != routing_table.end();) { 526 | if (ri->age < 0) { 527 | // manual route, don't age 528 | ++ri; 529 | continue; 530 | } 531 | ++ri->age; 532 | if (ri->age == rip::max_age) { 533 | // start deletion process 534 | BOOST_LOG_TRIVIAL(trace) << "Route to " << util::ip_to_string(ri->dst) 535 | << " started deletion countdown"; 536 | ri->metric = rip::infinity; 537 | ri->changed = true; 538 | rip::trigger_update(); 539 | } else if (ri->age > rip::max_age + rip::deletion_timeout) { 540 | // the deletion timer has also expired 541 | BOOST_LOG_TRIVIAL(info) << "Route to " << util::ip_to_string(ri->dst) 542 | << " deleted due to timeout."; 543 | routing_table.erase(ri++); 544 | continue; 545 | } 546 | ++ri; 547 | } 548 | routing_table.sort(); 549 | } 550 | 551 | void print_route() { 552 | std::cout << "\nGlobal Routing Table\n====================\n"; 553 | for (const auto &r : routing_table) { 554 | std::cout << util::ip_to_string(r.dst) << "/" << (int)r.prefix << " "; 555 | if (r.has_router) { 556 | std::cout << "via " << util::ip_to_string(r.router) << " "; 557 | } 558 | std::cout << "dev " << device::get_device_handle(r.dev_id).name 559 | << " metric " << r.metric; 560 | if (r.age != -1) { 561 | std::cout << " age " << r.age; 562 | } 563 | std::cout << std::endl; 564 | } 565 | } 566 | } // namespace ip 567 | } // namespace khtcp -------------------------------------------------------------------------------- /src/packetio.cc: -------------------------------------------------------------------------------- 1 | #include "packetio.h" 2 | #include "arp.h" 3 | #include "core.h" 4 | #include "device.h" 5 | #include "ip.h" 6 | #include "util.h" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace khtcp { 14 | namespace eth { 15 | 16 | std::pair construct_frame(const void *buf, int len, 17 | int ethtype, const void *destmac, 18 | int id) { 19 | auto &device = device::get_device_handle(id); 20 | BOOST_LOG_TRIVIAL(trace) << "Constructing outgoing frame from " 21 | << util::mac_to_string((const uint8_t *)destmac) 22 | << " to " << util::mac_to_string(device.addr) 23 | << " with payload length " << len << ", EtherType 0x" 24 | << std::setw(4) << std::setfill('0') << std::hex 25 | << ethtype << " on device " << device.name; 26 | 27 | auto frame_len = len + sizeof(eth_header_t) + 4; 28 | auto frame_buf = new uint8_t[frame_len]; 29 | 30 | auto eth_hdr = (eth_header_t *)frame_buf; 31 | memcpy(eth_hdr->dst, destmac, sizeof(eth::addr_t)); 32 | memcpy(eth_hdr->src, device.addr, sizeof(eth::addr_t)); 33 | eth_hdr->ethertype = boost::endian::endian_reverse((uint16_t)ethtype); 34 | 35 | auto payload_ptr = frame_buf + sizeof(eth_header_t); 36 | memcpy(payload_ptr, buf, len); 37 | 38 | // TODO: calculate frame checksum 39 | // auto csum_ptr = payload_ptr + len; 40 | 41 | return {frame_buf, frame_len}; 42 | } 43 | 44 | int send_frame(const void *buf, int len, int ethtype, const void *destmac, 45 | int id) { 46 | auto [frame_buf, frame_len] = construct_frame(buf, len, ethtype, destmac, id); 47 | auto ret = device::get_device_handle(id).inject_frame(frame_buf, frame_len); 48 | delete[] frame_buf; 49 | return ret; 50 | } 51 | 52 | void async_read_frame(int id, read_handler_t &&handler, int client_id) { 53 | boost::asio::post(core::get().read_handlers_strand, [=]() { 54 | core::get().read_handlers.emplace_back( 55 | [id, handler](int dev_id, uint16_t ethertype, const uint8_t *payload, 56 | int len) -> bool { 57 | if (id != dev_id) { 58 | return false; 59 | } else { 60 | return handler(dev_id, ethertype, payload, len); 61 | } 62 | }, 63 | client_id); 64 | BOOST_LOG_TRIVIAL(trace) << "Ethernet read handler queued for device " 65 | << device::get_device_handle(id).name; 66 | }); 67 | } 68 | 69 | void async_write_frame(const void *buf, int len, int ethtype, 70 | const void *destmac, int id, write_handler_t &&handler, 71 | int client_id) { 72 | boost::asio::post(core::get().write_tasks_strand, [=]() { 73 | core::get().write_tasks.emplace_back( 74 | [=]() { 75 | auto p = construct_frame(buf, len, ethtype, destmac, id); 76 | auto frame_buf = p.first; 77 | auto frame_len = p.second; 78 | 79 | device::get_device_handle(id).async_inject_frame(frame_buf, frame_len, 80 | [=](int ret) { 81 | handler(ret); 82 | delete[] frame_buf; 83 | }); 84 | }, 85 | client_id); 86 | }); 87 | } 88 | 89 | int set_frame_receive_callback(frame_receive_callback callback) { 90 | core::get().eth_callback = callback; 91 | return 0; 92 | } 93 | 94 | frame_receive_callback get_frame_receive_callback() { 95 | return core::get().eth_callback; 96 | } 97 | 98 | int print_eth_frame_callback(const void *frame, int len, int dev_id) { 99 | auto eth_hdr = (eth_header_t *)frame; 100 | std::cout << util::mac_to_string(eth_hdr->src) << " > " 101 | << util::mac_to_string(eth_hdr->dst) << " (on " 102 | << device::get_device_handle(dev_id).name << "), type 0x" 103 | << std::setfill('0') << std::setw(4) << std::hex 104 | << boost::endian::endian_reverse(eth_hdr->ethertype) << ", length " 105 | << std::dec << len << std::endl; 106 | 107 | return 0; 108 | } 109 | 110 | std::vector multicasts; 111 | 112 | void join_multicast(const addr_t multicast) { 113 | BOOST_LOG_TRIVIAL(info) << "Joining Ethernet multicast group " 114 | << util::mac_to_string(multicast); 115 | multicasts.emplace_back(); 116 | memcpy(multicasts[multicasts.size() - 1].data, multicast, sizeof(addr_t)); 117 | } 118 | 119 | int ethertype_broker_callback(const void *frame, int len, int dev_id) { 120 | auto eth_hdr = (eth_header_t *)frame; 121 | auto payload_ptr = (const uint8_t *)frame + sizeof(eth_header_t); 122 | auto payload_len = len - sizeof(eth_header_t) - 4; // checksum 123 | auto &device = device::get_device_handle(dev_id); 124 | auto ethertype = boost::endian::endian_reverse(eth_hdr->ethertype); 125 | bool pass_up = false; 126 | pass_up = !memcmp(eth_hdr->dst, device.addr, sizeof(addr_t)) || 127 | !memcmp(eth_hdr->dst, ETH_BROADCAST, sizeof(addr_t)); 128 | if (!pass_up) { 129 | for (const auto &m : multicasts) { 130 | pass_up = pass_up || !memcmp(eth_hdr->dst, m.data, sizeof(addr_t)); 131 | if (pass_up) { 132 | break; 133 | } 134 | } 135 | } 136 | if (pass_up) { 137 | BOOST_LOG_TRIVIAL(trace) << "Received frame for device " << device.name; 138 | boost::asio::post(core::get().read_handlers_strand, [=]() { 139 | auto it = core::get().read_handlers.begin(); 140 | while (it != core::get().read_handlers.end()) { 141 | if (it->first(dev_id, ethertype, payload_ptr, payload_len)) { 142 | // payload consumed 143 | core::get().read_handlers.erase(it); 144 | break; 145 | } 146 | ++it; 147 | } 148 | }); 149 | } 150 | return 0; 151 | } 152 | 153 | } // namespace eth 154 | } // namespace khtcp -------------------------------------------------------------------------------- /src/rip.cc: -------------------------------------------------------------------------------- 1 | #include "rip.h" 2 | #include "core.h" 3 | #include "ip.h" 4 | #include "socket.h" 5 | #include "udp.h" 6 | #include "util.h" 7 | 8 | #include 9 | #include 10 | 11 | namespace khtcp { 12 | namespace rip { 13 | void send_fulltable(const uint8_t *src_, uint16_t src_port, const uint8_t *dst_, 14 | uint16_t dst_port) { 15 | uint8_t *src, *dst; 16 | dst = (uint8_t *)malloc(sizeof(ip::addr_t)); 17 | memcpy(dst, dst_, sizeof(ip::addr_t)); 18 | if (src_) { 19 | src = (uint8_t *)malloc(sizeof(ip::addr_t)); 20 | memcpy(src, src_, sizeof(ip::addr_t)); 21 | } else { 22 | src = nullptr; 23 | core::record_multicast_buffer(dst); 24 | } 25 | auto &table = ip::get_routing_table(); 26 | auto it = table.begin(); 27 | auto rte_count = table.size(); 28 | while (rte_count) { 29 | int rc; 30 | if (rte_count > 25) { 31 | rte_count -= 25; 32 | rc = 25; 33 | } else { 34 | rc = rte_count; 35 | rte_count = 0; 36 | } 37 | auto packet_len = sizeof(rip_header_t) + rc * sizeof(rte_t); 38 | auto packet_ptr = (uint8_t *)malloc(packet_len); 39 | 40 | if (!src) { 41 | core::record_multicast_buffer(packet_ptr); 42 | } 43 | auto hdr = (rip_header_t *)packet_ptr; 44 | auto rte_ptr = (rte_t *)(packet_ptr + sizeof(rip_header_t)); 45 | hdr->command = response; 46 | hdr->version = version; 47 | 48 | for (int i = 0; i < rc; ++i, ++it) { 49 | rte_ptr[i].afi = boost::endian::endian_reverse((uint16_t)AF_INET); 50 | memcpy(rte_ptr[i].address, it->dst, sizeof(ip::addr_t)); 51 | uint32_t mask = util::cidr_to_mask(it->prefix); 52 | memcpy(rte_ptr[i].mask, &mask, sizeof(ip::addr_t)); 53 | uint32_t src_uint = *(uint32_t *)src; 54 | uint32_t router_uint = *(uint32_t *)it->router; 55 | if (src_port == rip::port && mask && 56 | (src_uint & mask) == (router_uint & mask)) { 57 | // Split Horizon 58 | // If A thinks it can get to D via C, its messages to C should indicate 59 | // that D is unreachable. 60 | // mask nonzero to make sure default route not poisoned 61 | rte_ptr[i].metric = boost::endian::endian_reverse(rip::infinity); 62 | BOOST_LOG_TRIVIAL(trace) 63 | << "Poisoning route to " << util::ip_to_string(it->dst) << "/" 64 | << (int)it->prefix << " in RIP response sent on " 65 | << util::ip_to_string(src) << " due to Split Horizon"; 66 | } else { 67 | rte_ptr[i].metric = boost::endian::endian_reverse(it->metric); 68 | BOOST_LOG_TRIVIAL(trace) 69 | << "Adding route to " << util::ip_to_string(it->dst) << "/" 70 | << (int)it->prefix << " to RIP response sent on " 71 | << util::ip_to_string(src); 72 | } 73 | // FIXME: nexthop is Advisory: not implemented here 74 | memset(rte_ptr[i].nexthop, 0, sizeof(ip::addr_t)); 75 | // route tag is not used here 76 | rte_ptr[i].route_tag = 0; 77 | } 78 | 79 | udp::async_write_udp( 80 | src, src_port, dst, dst_port, packet_ptr, packet_len, [=](auto ret) { 81 | if (!ret) { 82 | BOOST_LOG_TRIVIAL(trace) << "Written full table RIP packet on " 83 | << util::ip_to_string(src); 84 | } else { 85 | BOOST_LOG_TRIVIAL(warning) 86 | << "Failed to write full table RIP packet on " 87 | << util::ip_to_string(src); 88 | } 89 | if (!src) { 90 | if (!--core::get().multicast_buffers.at(packet_ptr)) { 91 | core::get().multicast_buffers.erase(packet_ptr); 92 | free(packet_ptr); 93 | } 94 | if (!--core::get().multicast_buffers.at(dst)) { 95 | core::get().multicast_buffers.erase(dst); 96 | free(dst); 97 | } 98 | } else { 99 | free(src); 100 | free(dst); 101 | free(packet_ptr); 102 | } 103 | }); 104 | } 105 | } 106 | 107 | bool rip_default_handler(const void *payload_ptr, uint16_t payload_len, 108 | const ip::addr_t src, uint16_t src_port, 109 | const ip::addr_t dst, uint16_t dst_port) { 110 | if (dst_port != rip::port) { 111 | BOOST_LOG_TRIVIAL(trace) << "Received UDP on non-RIP port " << dst_port; 112 | return false; 113 | } 114 | auto hdr = (rip_header_t *)payload_ptr; 115 | auto rte_len = payload_len - sizeof(rip_header_t); 116 | if (hdr->version != rip::version || rte_len % sizeof(rte_t) != 0) { 117 | BOOST_LOG_TRIVIAL(warning) << "Dropping unrecognized RIP packet"; 118 | return false; 119 | } 120 | auto rte_count = rte_len / sizeof(rte_t); 121 | auto rte_ptr = (rte_t *)(((uint8_t *)payload_ptr) + sizeof(rip_header_t)); 122 | switch (hdr->command) { 123 | case request: 124 | if (!rte_count) { 125 | // "If there are no entries, no response is given" 126 | BOOST_LOG_TRIVIAL(trace) << "Ignoring empty RIP request"; 127 | return false; 128 | } 129 | const uint8_t *response_addr; 130 | uint16_t response_port; 131 | socket::socket(0, SOCK_DGRAM).get_src(src, &response_addr, &response_port); 132 | if (!memcmp(RIP_MULTICAST, dst, sizeof(ip::addr_t))) { 133 | // multicast 134 | response_port = rip::port; 135 | } 136 | if (rte_count == 1 && rte_ptr->afi == 0 && 137 | boost::endian::endian_reverse(rte_ptr->metric) == 16) { 138 | // "...then this is a request to send the entire routing table" 139 | BOOST_LOG_TRIVIAL(trace) 140 | << "Sending full table to " << util::ip_to_string(src) << " from " 141 | << util::ip_to_string(response_addr); 142 | send_fulltable(response_addr, response_port, src, src_port); 143 | return false; 144 | } 145 | for (int i = 0; i < rte_count; ++i) { 146 | struct ip::route *r; 147 | if (ip::lookup_route(rte_ptr[i].address, &r)) { 148 | // a route is present 149 | rte_ptr[i].metric = boost::endian::endian_reverse(r->metric); 150 | } else { 151 | rte_ptr[i].metric = boost::endian::endian_reverse(rip::infinity); 152 | } 153 | } 154 | hdr->command = response; 155 | udp::async_write_udp(response_addr, response_port, src, src_port, 156 | payload_ptr, payload_len, [](auto ret) { 157 | if (!ret) { 158 | BOOST_LOG_TRIVIAL(trace) 159 | << "Successfully written RIP response"; 160 | } else { 161 | BOOST_LOG_TRIVIAL(warning) 162 | << "Failed to write RIP response, Errno: " 163 | << ret; 164 | } 165 | }); 166 | return false; 167 | break; 168 | case response: { 169 | struct ip::route *r; 170 | BOOST_LOG_TRIVIAL(trace) 171 | << "Received RIP response from " << util::ip_to_string(src); 172 | // check directly connected 173 | if (!ip::lookup_route(src, &r) || r->has_router) { 174 | BOOST_LOG_TRIVIAL(warning) << "Received RIP response from invalid source " 175 | << util::ip_to_string(src); 176 | return false; 177 | } 178 | // check self 179 | for (const auto &dev : core::get().devices) { 180 | for (const auto &ip : dev->ip_addrs) { 181 | if (!memcmp(src, ip, sizeof(ip::addr_t))) { 182 | BOOST_LOG_TRIVIAL(trace) << "Ignoring RIP response from self address " 183 | << util::ip_to_string(ip); 184 | return false; 185 | } 186 | } 187 | } 188 | for (int i = 0; i < rte_count; ++i) { 189 | uint32_t metric = boost::endian::endian_reverse(rte_ptr[i].metric); 190 | if (metric > rip::infinity || metric < 1) { 191 | BOOST_LOG_TRIVIAL(trace) 192 | << "Ignoring RIP RTE with invalid metric " << metric; 193 | continue; 194 | } 195 | uint32_t zero = 0; 196 | if (metric < rip::infinity && 197 | !memcmp(rte_ptr[i].address, &zero, sizeof(ip::addr_t)) && 198 | *(uint32_t *)rte_ptr[i].mask != 0) { 199 | BOOST_LOG_TRIVIAL(trace) 200 | << "Ignoring RIP RTE with zero net destination and nonzero mask " 201 | << util::ip_to_string(rte_ptr[i].mask); 202 | continue; 203 | } 204 | metric = std::min(metric + cost, infinity); 205 | auto cidr = util::mask_to_cidr(*(uint32_t *)rte_ptr[i].mask); 206 | bool has_route = ip::lookup_route(rte_ptr[i].address, &r, cidr); 207 | if (!has_route) { 208 | BOOST_LOG_TRIVIAL(trace) 209 | << "Previous route for " << util::ip_to_string(rte_ptr[i].address) 210 | << "/" << cidr << " not found..."; 211 | if (metric < infinity) { 212 | struct ip::route new_route; 213 | new_route.prefix = cidr; 214 | new_route.metric = metric; 215 | new_route.has_router = true; 216 | memcpy(new_route.dst, rte_ptr[i].address, sizeof(ip::addr_t)); 217 | memcpy(new_route.router, src, sizeof(ip::addr_t)); 218 | new_route.changed = true; 219 | 220 | BOOST_LOG_TRIVIAL(info) 221 | << "Adding route nexthop " << util::ip_to_string(src) 222 | << " metric " << metric << " for destination " 223 | << util::ip_to_string(rte_ptr[i].address) << "/" << cidr; 224 | ip::add_route(std::move(new_route)); 225 | trigger_update(); 226 | } 227 | } else { 228 | bool need_action = false; 229 | if (r->has_router && !memcmp(r->router, src, sizeof(ip::addr_t)) && 230 | r->age >= 0) { 231 | r->age = 0; // as specified in RFC; however, this may cause delayed 232 | // deletion of expired routes 233 | if (metric != r->metric) { 234 | need_action = true; 235 | } 236 | } 237 | if (metric < r->metric || 238 | (metric == r->metric && r->age >= rip::max_age / 2)) { 239 | need_action = true; 240 | } 241 | if (need_action) { 242 | // adopt the route from the datagram 243 | BOOST_LOG_TRIVIAL(info) 244 | << "Adopting new nexthop " << util::ip_to_string(src) 245 | << ", metric " << metric << " for destination " 246 | << util::ip_to_string(r->dst) << "/" << (int)r->prefix; 247 | r->metric = metric; 248 | memcpy(r->router, src, sizeof(ip::addr_t)); 249 | // trigger an update 250 | r->changed = true; 251 | trigger_update(); 252 | // timeout update 253 | if (metric == infinity) { 254 | if (r->age < rip::max_age) { 255 | BOOST_LOG_TRIVIAL(trace) 256 | << "Route to " << util::ip_to_string(r->dst) 257 | << " started deletion countdown"; 258 | r->age = rip::max_age; 259 | } 260 | } else { 261 | r->age = 0; 262 | } 263 | } 264 | } 265 | } 266 | return false; 267 | break; 268 | } 269 | default: 270 | BOOST_LOG_TRIVIAL(warning) << "Dropping unrecognized RIP packet"; 271 | return false; 272 | } 273 | } 274 | 275 | void start() { 276 | // Send full table request on all interfaces 277 | // We assume that no two devices belong to the same Ethernet network 278 | request_fulltable(); 279 | 280 | // start the default handler. The handler will always return false so that 281 | // it will stay on the receive queue 282 | udp::async_read_udp(rip_default_handler); 283 | } 284 | 285 | void trigger_update() { 286 | static bool blocked = false; 287 | static bool pending = false; 288 | static boost::asio::deadline_timer t(core::get().io_context); 289 | // if timer still working, wait till timer expires 290 | if (blocked) { 291 | pending = true; 292 | return; 293 | } 294 | 295 | blocked = true; 296 | auto &table = ip::get_routing_table(); 297 | for (int i = 0; i < core::get().devices.size(); ++i) { 298 | std::queue updated_routes; 299 | auto &device = device::get_device_handle(i); 300 | for (auto it = table.begin(); it != table.end(); ++it) { 301 | // Split Horizon 302 | // If A thinks it can get to D via C, its messages to C should indicate 303 | // that D is unreachable. 304 | uint32_t src_uint = *(uint32_t *)device.ip_addrs[0]; 305 | uint32_t router_uint = *(uint32_t *)it->router; 306 | uint32_t mask = util::cidr_to_mask(it->prefix); 307 | if (it->changed && ((src_uint & mask) != (router_uint & mask) || !mask)) { 308 | updated_routes.push(it); 309 | it->changed = false; 310 | } 311 | } 312 | 313 | auto rte_count = updated_routes.size(); 314 | while (rte_count) { 315 | int rc; 316 | if (rte_count > 25) { 317 | rte_count -= 25; 318 | rc = 25; 319 | } else { 320 | rc = rte_count; 321 | rte_count = 0; 322 | } 323 | auto packet_len = sizeof(rip_header_t) + rc * sizeof(rte_t); 324 | auto packet_ptr = (uint8_t *)malloc(packet_len); 325 | auto hdr = (rip_header_t *)packet_ptr; 326 | auto rte_ptr = (rte_t *)(packet_ptr + sizeof(rip_header_t)); 327 | hdr->command = response; 328 | hdr->version = version; 329 | 330 | for (int i = 0; i < rc; ++i) { 331 | auto &route = *updated_routes.front(); 332 | rte_ptr[i].afi = boost::endian::endian_reverse((uint16_t)AF_INET); 333 | memcpy(rte_ptr[i].address, route.dst, sizeof(ip::addr_t)); 334 | uint32_t mask = util::cidr_to_mask(route.prefix); 335 | memcpy(rte_ptr[i].mask, &mask, sizeof(ip::addr_t)); 336 | rte_ptr[i].metric = boost::endian::endian_reverse(route.metric); 337 | BOOST_LOG_TRIVIAL(trace) 338 | << "Adding route to " << util::ip_to_string(route.dst) << "/" 339 | << (int)route.prefix << " to RIP response during Triggered Updates"; 340 | // FIXME: nexthop is Advisory: not implemented here 341 | memset(rte_ptr[i].nexthop, 0, sizeof(ip::addr_t)); 342 | // route tag is not used here 343 | rte_ptr[i].route_tag = 0; 344 | } 345 | 346 | udp::async_write_udp( 347 | device.ip_addrs[0], rip::port, rip::RIP_MULTICAST, rip::port, 348 | packet_ptr, packet_len, [=](auto ret) { 349 | if (!ret) { 350 | BOOST_LOG_TRIVIAL(trace) 351 | << "Written Triggered Updates RIP packet"; 352 | } else { 353 | BOOST_LOG_TRIVIAL(warning) 354 | << "Failed to Triggered Updates table RIP packet"; 355 | } 356 | pending = false; 357 | free(packet_ptr); 358 | }); 359 | } 360 | } 361 | 362 | // hold the process 363 | t.expires_from_now(boost::posix_time::seconds(rand() % 5 + 1)); 364 | t.async_wait([](const auto &ec) { 365 | if (!ec) { 366 | blocked = false; 367 | if (pending) { 368 | trigger_update(); 369 | } 370 | } else { 371 | BOOST_LOG_TRIVIAL(error) 372 | << "RIP Triggered Updates timer failed with message: " 373 | << ec.message(); 374 | } 375 | }); 376 | } 377 | 378 | void request_fulltable() { 379 | auto packet_len = sizeof(rip_header_t) + sizeof(rte_t); 380 | auto packet_ptr = (uint8_t *)malloc(packet_len); 381 | memset(packet_ptr, 0, packet_len); 382 | core::record_multicast_buffer(packet_ptr); 383 | auto hdr = (rip_header_t *)packet_ptr; 384 | auto rte_ptr = (rte_t *)(packet_ptr + sizeof(rip_header_t)); 385 | 386 | hdr->command = request; 387 | hdr->version = version; 388 | 389 | rte_ptr->afi = 0; 390 | rte_ptr->metric = boost::endian::endian_reverse(16); 391 | 392 | udp::async_write_udp(nullptr, rip::port, RIP_MULTICAST, port, packet_ptr, 393 | packet_len, [=](auto ret) { 394 | if (ret) { 395 | BOOST_LOG_TRIVIAL(warning) 396 | << "Full table RIP request failed"; 397 | } else { 398 | BOOST_LOG_TRIVIAL(trace) 399 | << "Full table RIP request sent"; 400 | } 401 | if (!--core::get().multicast_buffers.at(packet_ptr)) { 402 | core::get().multicast_buffers.erase(packet_ptr); 403 | free(packet_ptr); 404 | } 405 | }); 406 | } 407 | } // namespace rip 408 | } // namespace khtcp -------------------------------------------------------------------------------- /src/socket.cc: -------------------------------------------------------------------------------- 1 | #include "socket.h" 2 | #include "core.h" 3 | #include "util.h" 4 | 5 | namespace khtcp { 6 | namespace socket { 7 | 8 | socket::socket(int client_id, int type) : type(type) { 9 | for (int i = core::core::MIN_FD; i > 0; ++i) { 10 | if (core::get().client_sockets.find({client_id, i}) == 11 | core::get().client_sockets.end()) { 12 | fd = i; 13 | // bind_addr.sin_family != AF_INET, an unbound socket 14 | memset(&bind_addr, 0, sizeof(bind_addr)); 15 | return; 16 | } 17 | } 18 | BOOST_LOG_TRIVIAL(error) << "Failed to assign socket fd for client " 19 | << client_id; 20 | } 21 | 22 | void socket::get_src(const ip::addr_t dst, const uint8_t **src_out, 23 | uint16_t *port_out) { 24 | if (bind_addr.sin_family == AF_INET) { 25 | // we're bound 26 | *src_out = (const uint8_t *)&bind_addr.sin_addr; 27 | *port_out = ntohs(bind_addr.sin_port); 28 | } else { 29 | struct ip::route *route; 30 | if (!ip::lookup_route(dst, &route)) { 31 | // failed to get route for destination 32 | BOOST_LOG_TRIVIAL(warning) 33 | << "No route to host " << util::ip_to_string(dst); 34 | *src_out = nullptr; 35 | return; 36 | } 37 | 38 | // take the first address and a random, > 1024 port 39 | // Ephemeral port definition according to RFC6056 40 | *src_out = device::get_device_handle(route->dev_id).ip_addrs[0]; 41 | *port_out = rand() % 65535; 42 | if (*port_out <= 1024) { 43 | *port_out = 65535 - *port_out; 44 | } 45 | } 46 | } 47 | } // namespace socket 48 | } // namespace khtcp -------------------------------------------------------------------------------- /src/tcp.cc: -------------------------------------------------------------------------------- 1 | #include "tcp.h" 2 | #include "core.h" 3 | #include "util.h" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace khtcp { 11 | namespace tcp { 12 | 13 | std::mt19937 eng; 14 | std::uniform_int_distribution distribution; 15 | 16 | struct hash_fn { 17 | std::size_t operator()(const conn_key &k) const { 18 | return std::hash()(*(uint32_t *)(k.src)) ^ 19 | std::hash()(*(uint32_t *)(k.dst)) ^ 20 | std::hash()(k.src_port) ^ 21 | std::hash()(k.dst_port); 22 | } 23 | }; 24 | 25 | std::unordered_map conns; 26 | std::unordered_set listening; 27 | 28 | bool conn_key::operator==(const conn_key &a) const { 29 | return src_port == a.src_port && dst_port == a.dst_port && 30 | !memcmp(src, a.src, sizeof(ip::addr_t)) && 31 | !memcmp(dst, a.dst, sizeof(ip::addr_t)); 32 | } 33 | 34 | std::string conn_key::to_string() const { 35 | return "(" + util::ip_to_string(src) + ":" + std::to_string(src_port) + "|" + 36 | util::ip_to_string(dst) + ":" + std::to_string(dst_port) + ")"; 37 | } 38 | 39 | void async_recv_segment(const ip::addr_t src, uint16_t src_port, 40 | const ip::addr_t dst, uint16_t dst_port, 41 | recv_segment_handler_t &&handler, int client_id) { 42 | // We cannot use the normal ip::async_read_ip as we need a timeout to revoke 43 | // request handlers 44 | boost::asio::post(core::get().read_handlers_strand, [=]() { 45 | auto t = 46 | std::make_shared(core::get().io_context); 47 | t->expires_from_now(timeout); 48 | core::get().read_handlers.emplace_back( 49 | ip::wrap_read_handler( 50 | proto, 51 | [=](const void *payload_ptr, uint64_t payload_len, 52 | const ip::addr_t s, const ip::addr_t d, uint8_t dscp, 53 | const void *opt) -> bool { 54 | if (memcmp(src, s, sizeof(ip::addr_t)) || 55 | memcmp(dst, d, sizeof(ip::addr_t))) { 56 | // IP mismatch 57 | t->cancel(); 58 | return false; 59 | } 60 | auto hdr = (struct tcp_header_t *)payload_ptr; 61 | auto hdr_len = (hdr->data_offset >> 4) << 2; 62 | auto segment_data = (uint8_t *)hdr + hdr_len; 63 | auto segment_len = payload_len - hdr_len; 64 | if (src_port != boost::endian::endian_reverse(hdr->src_port) || 65 | dst_port != boost::endian::endian_reverse(hdr->dst_port)) { 66 | // port mismatch 67 | t->cancel(); 68 | return false; 69 | } 70 | handler(0, boost::endian::endian_reverse(hdr->seq), 71 | boost::endian::endian_reverse(hdr->ack), ACK(hdr->flags), 72 | PSH(hdr->flags), RST(hdr->flags), SYN(hdr->flags), 73 | FIN(hdr->flags), 74 | boost::endian::endian_reverse(hdr->window), segment_data, 75 | segment_len); 76 | t->cancel(); 77 | return true; 78 | }), 79 | client_id); 80 | auto last = core::get().read_handlers.end(); 81 | --last; 82 | t->async_wait([=](auto ec) { 83 | if (!ec) { 84 | BOOST_LOG_TRIVIAL(warning) 85 | << "TCP read operation time out, cancelling read handler..."; 86 | core::get().read_handlers.erase(last); 87 | handler(ETIMEDOUT, 0, 0, 0, 0, 0, 0, 0, 0, nullptr, 0); 88 | } 89 | }); 90 | }); 91 | } 92 | 93 | bool default_handler(const void *payload_ptr, uint64_t payload_len, 94 | const ip::addr_t src, const ip::addr_t dst, uint8_t dscp, 95 | const void *opt) { 96 | auto hdr = (struct tcp_header_t *)payload_ptr; 97 | struct conn_key k; 98 | // reverse src/dst 99 | memcpy(k.src, dst, sizeof(ip::addr_t)); 100 | memcpy(k.dst, src, sizeof(ip::addr_t)); 101 | k.src_port = boost::endian::endian_reverse(hdr->dst_port); 102 | k.dst_port = boost::endian::endian_reverse(hdr->src_port); 103 | auto it = conns.find(k); 104 | if (it == conns.end() || !it->second.up) { 105 | // completely no record or not up (opening) 106 | if (!SYN(hdr->flags) || ((it == conns.end() && ACK(hdr->flags)) || 107 | RST(hdr->flags) || FIN(hdr->flags))) { 108 | // untracked/malformed connection: RST 109 | auto seq = boost::endian::endian_reverse(hdr->seq); 110 | BOOST_LOG_TRIVIAL(warning) << "Sending RST for unknown connection"; 111 | async_send_segment( 112 | dst, k.src_port, src, k.dst_port, 0, seq + 1, false, false, true, 113 | false, false, 0, nullptr, 0, [=](int ret) { 114 | if (!ret) { 115 | BOOST_LOG_TRIVIAL(info) 116 | << "Sent RST to unknown connection " << k.to_string(); 117 | } else { 118 | BOOST_LOG_TRIVIAL(error) << "Failed to send RST: Errno " << ret; 119 | } 120 | }); 121 | } else if (it == conns.end()) { 122 | // plain SYN: check if listening 123 | memset(k.dst, 0, sizeof(ip::addr_t)); 124 | k.dst_port = 0; 125 | if (listening.find(k) == listening.end()) { 126 | // closed port: ACK+RST 127 | auto seq = boost::endian::endian_reverse(hdr->seq); 128 | BOOST_LOG_TRIVIAL(warning) << "Sending RSTACK for closed port"; 129 | async_send_segment( 130 | dst, k.src_port, src, boost::endian::endian_reverse(hdr->src_port), 131 | 0, seq + 1, true, false, true, false, false, 0, nullptr, 0, 132 | [=](int ret) { 133 | if (!ret) { 134 | BOOST_LOG_TRIVIAL(info) 135 | << "Sent RST due to closed port " << k.src_port; 136 | } else { 137 | BOOST_LOG_TRIVIAL(error) << "Failed to send RST: Errno " << ret; 138 | } 139 | }); 140 | } 141 | } 142 | // we have the connection in record; do nothing 143 | } 144 | return false; 145 | } 146 | 147 | void start() { ip::async_read_ip(proto, default_handler); } 148 | 149 | void async_send_segment(const ip::addr_t src, uint16_t src_port, 150 | const ip::addr_t dst, uint16_t dst_port, 151 | uint32_t seq_num, uint32_t ack_num, bool ack, bool psh, 152 | bool rst, bool syn, bool fin, uint16_t window, 153 | const void *segment_ptr, uint16_t segment_len, 154 | send_segment_handler_t &&handler, int client_id) { 155 | BOOST_LOG_TRIVIAL(warning) 156 | << "Sending TCP segment with segment length " << segment_len << " " 157 | << util::ip_to_string(src) << ":" << src_port << " > " 158 | << util::ip_to_string(dst) << ":" << dst_port << ", SEQ=" << seq_num 159 | << " ACK=" << ack_num << " [" << (ack ? 'A' : '.') << (psh ? 'P' : '.') 160 | << (rst ? 'R' : '.') << (syn ? 'S' : '.') << (fin ? 'F' : '.') << "]"; 161 | 162 | uint16_t packet_len = sizeof(tcp_header_t) + segment_len; 163 | auto packet_len_bigendian = boost::endian::endian_reverse(packet_len); 164 | auto packet_csum_len = packet_len + 12; // pseudo-header 165 | auto phdr = new uint8_t[packet_csum_len]; 166 | auto pseudo_header = phdr; 167 | auto hdr = (tcp_header_t *)((uint8_t *)phdr + 12); 168 | auto packet_payload = ((uint8_t *)hdr) + sizeof(tcp_header_t); 169 | hdr->src_port = boost::endian::endian_reverse(src_port); 170 | hdr->dst_port = boost::endian::endian_reverse(dst_port); 171 | hdr->seq = boost::endian::endian_reverse(seq_num); 172 | hdr->ack = boost::endian::endian_reverse(ack_num); 173 | // we don't support any TCP option here 174 | hdr->data_offset = 5 << 4; 175 | hdr->flags = fin | (syn << 1) | (rst << 2) | (psh << 3) | (ack << 4); 176 | hdr->window = boost::endian::endian_reverse(window); 177 | // wait for checksum calculation 178 | hdr->checksum = 0; 179 | hdr->urgent_pointer = 0; 180 | 181 | memcpy(packet_payload, segment_ptr, segment_len); 182 | memcpy(pseudo_header, src, sizeof(ip::addr_t)); 183 | pseudo_header += sizeof(ip::addr_t); 184 | memcpy(pseudo_header, dst, sizeof(ip::addr_t)); 185 | pseudo_header += sizeof(ip::addr_t); 186 | memset(pseudo_header, 0, sizeof(uint8_t)); 187 | pseudo_header += sizeof(uint8_t); 188 | memcpy(pseudo_header, &proto, sizeof(proto)); 189 | pseudo_header += sizeof(proto); 190 | memcpy(pseudo_header, &packet_len_bigendian, sizeof(packet_len_bigendian)); 191 | 192 | hdr->checksum = ip::ip_checksum(phdr, packet_csum_len); 193 | 194 | ip::async_write_ip( 195 | src, dst, proto, 0, default_ttl, hdr, packet_len, 196 | [=](int ret) { 197 | if (ret) { 198 | BOOST_LOG_TRIVIAL(error) 199 | << "Failed to write TCP segment: Errno " << ret; 200 | } 201 | handler(ret); 202 | delete[] phdr; 203 | }, 204 | client_id); 205 | } 206 | 207 | void async_open(const ip::addr_t src, uint16_t src_port, const ip::addr_t dst, 208 | uint16_t dst_port, bool active, open_handler_t &&handler, 209 | int client_id) { 210 | if (active) { 211 | // we're starting a connection 212 | uint32_t isn = distribution(eng); 213 | struct conn_key k; 214 | memcpy(k.src, src, sizeof(ip::addr_t)); 215 | memcpy(k.dst, dst, sizeof(ip::addr_t)); 216 | k.src_port = src_port; 217 | k.dst_port = dst_port; 218 | conns.insert({k, {}}); 219 | async_send_segment( 220 | src, src_port, dst, dst_port, isn, 0, false, false, false, true, false, 221 | local_window, nullptr, 0, 222 | [=](int ret) { 223 | if (!ret) { 224 | // syn sent successfully, expect a synack 225 | async_recv_segment( 226 | dst, dst_port, src, src_port, 227 | [=](int ret, uint32_t seq_num, uint32_t ack_num, bool ack, 228 | bool psh, bool rst, bool syn, bool fin, uint16_t window, 229 | const void *data, uint16_t len) { 230 | if (!ret) { 231 | if (syn && ack && !rst && !fin) { 232 | // we get our synack 233 | // make sure that the synack does not carry data 234 | // check that they've acknowledged our syn 235 | if (len || ack_num != isn + 1) { 236 | BOOST_LOG_TRIVIAL(warning) 237 | << "Malformed connection setup, sending RST"; 238 | async_send_segment( 239 | src, src_port, dst, dst_port, isn + 1, 0, false, 240 | false, true, false, false, 0, nullptr, 0, 241 | [=](int ret) { 242 | if (ret) { 243 | BOOST_LOG_TRIVIAL(error) 244 | << "Failed to send RST: errno " << ret; 245 | } 246 | handler(EINVAL, {}); 247 | }, 248 | client_id); 249 | } 250 | BOOST_LOG_TRIVIAL(warning) 251 | << "Sending ACK for connection establishment"; 252 | // reply an ack 253 | async_send_segment( 254 | src, src_port, dst, dst_port, isn + 1, seq_num + 1, 255 | true, false, false, false, false, local_window, 256 | nullptr, 0, 257 | [=](int ret) { 258 | if (!ret) { 259 | // ack sent, connection now up 260 | BOOST_LOG_TRIVIAL(warning) 261 | << "Connection " << k.to_string() << " up"; 262 | auto &b = conns.at(k); 263 | b.pending_receive = boost::asio::mutable_buffer( 264 | new uint8_t[local_window], local_window); 265 | b.recv_next = seq_num + 1; 266 | b.recv_window = local_window; 267 | b.send_next = isn + 1; 268 | b.send_unack = ack_num; 269 | b.up = true; 270 | handler(0, k); 271 | } else { 272 | BOOST_LOG_TRIVIAL(error) 273 | << "Failed to send ACK: errno " << ret; 274 | handler(ret, {}); 275 | } 276 | }, 277 | client_id); 278 | } 279 | } else { 280 | BOOST_LOG_TRIVIAL(error) 281 | << "Error " << errno << " when waiting for SYNACK"; 282 | } 283 | }, 284 | client_id); 285 | } 286 | }, 287 | client_id); 288 | } 289 | } 290 | 291 | } // namespace tcp 292 | } // namespace khtcp -------------------------------------------------------------------------------- /src/udp.cc: -------------------------------------------------------------------------------- 1 | #include "udp.h" 2 | #include "core.h" 3 | #include "util.h" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace khtcp { 10 | namespace udp { 11 | 12 | void async_read_udp(read_handler_t &&handler, int client_id) { 13 | ip::async_read_ip( 14 | proto, 15 | [=](const void *payload_ptr, uint64_t payload_len, const ip::addr_t src, 16 | const ip::addr_t dst, uint8_t dscp, const void *opt) -> bool { 17 | auto hdr = (udp_header_t *)payload_ptr; 18 | auto packet_payload = ((uint8_t *)payload_ptr) + sizeof(udp_header_t); 19 | 20 | // FIXME: we do not verify UDP checksum here. 21 | return handler(packet_payload, 22 | boost::endian::endian_reverse(hdr->length) - 23 | sizeof(udp_header_t), 24 | src, boost::endian::endian_reverse(hdr->src_port), dst, 25 | boost::endian::endian_reverse(hdr->dst_port)); 26 | }, 27 | client_id); 28 | } 29 | 30 | void async_write_udp(const ip::addr_t src, uint16_t src_port, 31 | const ip::addr_t dst, uint16_t dst_port, 32 | const void *payload_ptr, uint16_t payload_len, 33 | write_handler_t &&handler, int client_id) { 34 | BOOST_LOG_TRIVIAL(trace) << "Sending UDP packet with payload length " 35 | << payload_len << " " 36 | << (src ? util::ip_to_string(src) : "(multicast)") 37 | << ":" << src_port << " > " 38 | << util::ip_to_string(dst) << ":" << dst_port; 39 | auto packet_len = sizeof(udp_header_t) + payload_len; 40 | auto packet_ptr = new uint8_t[packet_len]; 41 | 42 | if (!src) { 43 | core::record_multicast_buffer(packet_ptr); 44 | } 45 | auto hdr = (udp_header_t *)packet_ptr; 46 | auto packet_payload = ((uint8_t *)packet_ptr) + sizeof(udp_header_t); 47 | hdr->src_port = boost::endian::endian_reverse(src_port); 48 | hdr->dst_port = boost::endian::endian_reverse(dst_port); 49 | hdr->length = boost::endian::endian_reverse((uint16_t)packet_len); 50 | // FIXME: we do not calculate checksum here. 51 | 52 | memcpy(packet_payload, payload_ptr, payload_len); 53 | hdr->checksum = 0; 54 | ip::async_write_ip( 55 | src, dst, proto, 0, default_ttl, packet_ptr, packet_len, 56 | [=](int ret) { 57 | if (ret) { 58 | BOOST_LOG_TRIVIAL(error) 59 | << "Failed to write UDP packet: Errno " << ret; 60 | } 61 | handler(ret); 62 | if (!src && !--core::get().multicast_buffers.at(packet_ptr)) { 63 | core::get().multicast_buffers.erase(packet_ptr); 64 | delete[] packet_ptr; 65 | } else if (src) { 66 | delete[] packet_ptr; 67 | } 68 | }, 69 | client_id); 70 | } // namespace udp 71 | } // namespace udp 72 | } // namespace khtcp -------------------------------------------------------------------------------- /src/util.cc: -------------------------------------------------------------------------------- 1 | #include "util.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace khtcp { 14 | namespace util { 15 | 16 | #define RED "\x1B[0;31m" 17 | #define BOLD_RED "\x1B[1;31m" 18 | #define GRN "\x1B[0;32m" 19 | #define YEL "\x1B[0;33m" 20 | #define BLU "\x1B[0;34m" 21 | #define MAG "\x1B[0;35m" 22 | #define CYN "\x1B[0;36m" 23 | #define WHT "\x1B[0;37m" 24 | #define RESET "\x1B[0m" 25 | 26 | const char *get_color(boost::log::trivial::severity_level level) { 27 | switch (level) { 28 | case boost::log::trivial::trace: 29 | return RESET; 30 | case boost::log::trivial::debug: 31 | return WHT; 32 | case boost::log::trivial::info: 33 | return GRN; 34 | case boost::log::trivial::warning: 35 | return YEL; 36 | case boost::log::trivial::error: 37 | return RED; 38 | case boost::log::trivial::fatal: 39 | return BOLD_RED; 40 | default: 41 | return RESET; 42 | } 43 | } 44 | 45 | void colored_console_sink::consume(boost::log::record_view const &rec, 46 | string_type const &formatted_string) { 47 | auto level = rec.attribute_values()["Severity"] 48 | .extract(); 49 | 50 | std::cout << get_color(level.get()) << formatted_string << RESET << std::endl; 51 | } 52 | 53 | void init_logging(boost::log::trivial::severity_level level) { 54 | auto colored_console_sink = boost::make_shared(); 55 | boost::log::core::get()->add_sink(colored_console_sink); 56 | boost::log::core::get()->set_filter(boost::log::trivial::severity >= level); 57 | } 58 | 59 | std::string mac_to_string(const eth::addr_t addr) { 60 | auto len = sizeof(eth::addr_t); 61 | std::ostringstream os; 62 | for (int i = 0; i < len; ++i) { 63 | os << std::setfill('0') << std::hex << std::setw(2) << (int)addr[i]; 64 | if (i < len - 1) { 65 | os << ":"; 66 | } 67 | } 68 | return os.str(); 69 | } 70 | 71 | std::string ip_to_string(const ip::addr_t addr) { 72 | in_addr in; 73 | memcpy(&in.s_addr, addr, sizeof(ip::addr_t)); 74 | return std::string(inet_ntoa(in)); 75 | } 76 | 77 | int string_to_ip(const std::string &str, ip::addr_t addr) { 78 | return inet_aton(str.c_str(), (in_addr *)addr); 79 | } 80 | 81 | int mask_to_cidr(uint32_t n) { 82 | unsigned int count = 0; 83 | while (n) { 84 | count += n & 1; 85 | n >>= 1; 86 | } 87 | return count; 88 | } 89 | 90 | uint32_t cidr_to_mask(int prefix) { 91 | return boost::endian::endian_reverse( 92 | prefix == 0 ? 0 : (0xffffffff << (32 - prefix))); 93 | } 94 | 95 | void hexdump(const char *note, const void *data, size_t size) { 96 | printf(">> %s\n", note); 97 | char ascii[17]; 98 | size_t i, j; 99 | ascii[16] = '\0'; 100 | for (i = 0; i < size; ++i) { 101 | printf("%02X ", ((unsigned char *)data)[i]); 102 | if (((unsigned char *)data)[i] >= ' ' && 103 | ((unsigned char *)data)[i] <= '~') { 104 | ascii[i % 16] = ((unsigned char *)data)[i]; 105 | } else { 106 | ascii[i % 16] = '.'; 107 | } 108 | if ((i + 1) % 8 == 0 || i + 1 == size) { 109 | printf(" "); 110 | if ((i + 1) % 16 == 0) { 111 | printf("| %s \n", ascii); 112 | } else if (i + 1 == size) { 113 | ascii[(i + 1) % 16] = '\0'; 114 | if ((i + 1) % 16 <= 8) { 115 | printf(" "); 116 | } 117 | for (j = (i + 1) % 16; j < 16; ++j) { 118 | printf(" "); 119 | } 120 | printf("| %s \n", ascii); 121 | } 122 | } 123 | } 124 | } 125 | } // namespace util 126 | } // namespace khtcp -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | file(GLOB tests_src "*.cc") 2 | 3 | foreach(test_src IN LISTS tests_src) 4 | get_filename_component(test_name ${test_src} NAME_WE) 5 | add_executable(${test_name} ${test_src}) 6 | target_link_libraries(${test_name} ${lib_name}) 7 | endforeach(test_src IN LISTS tests_src) 8 | -------------------------------------------------------------------------------- /tests/test_arping.cc: -------------------------------------------------------------------------------- 1 | /** 2 | * @file test_arping.cc 3 | * @author Pengcheng Xu 4 | * @brief Test for sending/receiving ARP packets. Mimics the arping utility. 5 | * @version 0.1 6 | * @date 2019-10-05 7 | * 8 | * @copyright Copyright (c) 2019 9 | * 10 | */ 11 | 12 | #include "arp.h" 13 | #include "core.h" 14 | #include "device.h" 15 | #include "util.h" 16 | 17 | #include 18 | #include 19 | #include 20 | 21 | using namespace khtcp; 22 | 23 | ip::addr_t dst; 24 | ip::addr_t src; 25 | 26 | void req_once(int dev_id) { 27 | auto &device = device::get_device_handle(dev_id); 28 | arp::async_write_arp(dev_id, 0x1, device.addr, src, eth::ETH_BROADCAST, dst, 29 | [](int dev_id, int ret) { 30 | if (ret != PCAP_ERROR) { 31 | std::cout << "Broadcast sent for " 32 | << util::ip_to_string(dst) << " on device " 33 | << device::get_device_handle(dev_id).name 34 | << "\n"; 35 | } else { 36 | std::cerr << "Failed to send ARP packet on device " 37 | << device::get_device_handle(dev_id).name 38 | << "\n"; 39 | } 40 | }); 41 | arp::async_read_arp(dev_id, 42 | [](int dev_id, uint16_t opcode, eth::addr_t sender_mac, 43 | ip::addr_t sender_ip, eth::addr_t target_mac, 44 | ip::addr_t target_ip) -> bool { 45 | if (opcode == 0x2) { 46 | std::cout << "Unicast reply from " 47 | << util::ip_to_string(sender_ip) << " [" 48 | << util::mac_to_string(sender_mac) << "]\n"; 49 | return true; 50 | } else { 51 | return false; 52 | } 53 | }); 54 | } 55 | 56 | void timer_handler(int dev_id, boost::system::error_code ec, 57 | boost::asio::deadline_timer &timer) { 58 | if (!ec) { 59 | req_once(dev_id); 60 | timer.expires_from_now(boost::posix_time::seconds(1)); 61 | timer.async_wait(boost::bind(timer_handler, dev_id, 62 | boost::asio::placeholders::error, 63 | boost::ref(timer))); 64 | } else { 65 | std::cerr << "timer: " << ec.message() << "\n"; 66 | } 67 | } 68 | 69 | int main(int argc, char **argv) { 70 | if (argc != 3) { 71 | std::cout << "usage: " << argv[0] << " \n"; 72 | return -1; 73 | } 74 | 75 | util::init_logging(boost::log::trivial::warning); 76 | 77 | int id = device::add_device(argv[1]); 78 | if (id == -1) { 79 | std::cerr << "Failed to add device\n"; 80 | return -1; 81 | } 82 | 83 | auto &device = device::get_device_handle(id); 84 | if (device.ip_addrs.empty()) { 85 | std::cerr << "No IP addresses on device " << device.name << "\n"; 86 | return -1; 87 | } 88 | 89 | memcpy(src, device.ip_addrs[0], sizeof(ip::addr_t)); 90 | if (!util::string_to_ip(std::string(argv[2]), dst)) { 91 | std::cerr << "Failed to parse destination IP\n"; 92 | return -1; 93 | } 94 | 95 | std::cout << "ARPING " << util::ip_to_string(dst) << " from " 96 | << util::ip_to_string(src) << " " << device.name << "\n"; 97 | 98 | req_once(id); 99 | boost::asio::deadline_timer timer(core::get().io_context); 100 | timer.expires_from_now(boost::posix_time::seconds(1)); 101 | timer.async_wait([&](auto ec) { timer_handler(id, ec, timer); }); 102 | 103 | eth::set_frame_receive_callback(eth::ethertype_broker_callback); 104 | return core::get().run(); 105 | } -------------------------------------------------------------------------------- /tests/test_eth_capture.cc: -------------------------------------------------------------------------------- 1 | /** 2 | * @file test_eth_capture.cc 3 | * @author Pengcheng Xu 4 | * @brief Test for basic Ethernet capture. 5 | * @version 0.1 6 | * @date 2019-10-05 7 | * 8 | * @copyright Copyright (c) 2019 9 | * 10 | */ 11 | 12 | #include "core.h" 13 | #include "device.h" 14 | #include "util.h" 15 | 16 | #include 17 | 18 | int main(int argc, char **argv) { 19 | if (argc != 2) { 20 | std::cerr << "usage: " << argv[0] << " \n"; 21 | return -1; 22 | } 23 | // khtcp::util::init_logging(boost::log::trivial::trace); 24 | khtcp::util::init_logging(); 25 | 26 | khtcp::device::add_device(argv[1]); 27 | 28 | khtcp::eth::set_frame_receive_callback(khtcp::eth::print_eth_frame_callback); 29 | return khtcp::core::get().run(); 30 | } -------------------------------------------------------------------------------- /tests/test_logging.cc: -------------------------------------------------------------------------------- 1 | /** 2 | * @file test_logging.cc 3 | * @author Pengcheng Xu 4 | * @brief Test for logging facility. 5 | * @version 0.1 6 | * @date 2019-10-05 7 | * 8 | * @copyright Copyright (c) 2019 9 | * 10 | */ 11 | 12 | #include "util.h" 13 | 14 | int main() { 15 | khtcp::util::init_logging(); 16 | 17 | BOOST_LOG_TRIVIAL(trace) << "A trace severity message"; 18 | BOOST_LOG_TRIVIAL(debug) << "A debug severity message"; 19 | BOOST_LOG_TRIVIAL(info) << "An informational severity message"; 20 | BOOST_LOG_TRIVIAL(warning) << "A warning severity message"; 21 | BOOST_LOG_TRIVIAL(error) << "An error severity message"; 22 | BOOST_LOG_TRIVIAL(fatal) << "A fatal severity message"; 23 | } -------------------------------------------------------------------------------- /tests/test_mgmt_device.cc: -------------------------------------------------------------------------------- 1 | /** 2 | * @file test_mgmt_device.cc 3 | * @author Pengcheng Xu 4 | * @brief Test for device initialization. 5 | * @version 0.1 6 | * @date 2019-10-05 7 | * 8 | * @copyright Copyright (c) 2019 9 | * 10 | */ 11 | 12 | #include "device.h" 13 | #include "util.h" 14 | 15 | #include 16 | 17 | int main() { 18 | khtcp::util::init_logging(); 19 | auto id = khtcp::device::add_device("eth0"); 20 | if (id >= 0) { 21 | std::cout << "Found requested device as id=" << id << std::endl; 22 | } 23 | auto fid = khtcp::device::find_device("eth0"); 24 | if (fid == id) { 25 | std::cout << "Device id matched in find_device" << std::endl; 26 | } 27 | } -------------------------------------------------------------------------------- /tests/test_server.cc: -------------------------------------------------------------------------------- 1 | /** 2 | * @file test_server.cc 3 | * @author Pengcheng Xu 4 | * @brief Test server that accepts connections from clients. 5 | * 6 | * For ARP answering to work properly, switch off kernel ARP stack: 7 | * 8 | * ``` 9 | * ip link set arp off 10 | * ``` 11 | * 12 | * Note that this may cause you to lose connection on the interface; use 13 | * vnetUtils that comes with the lab and test in a netns. 14 | * 15 | * @version 0.1 16 | * @date 2019-10-05 17 | * 18 | * @copyright Copyright (c) 2019 19 | * 20 | */ 21 | 22 | #include "core.h" 23 | #include "device.h" 24 | #include "tcp.h" 25 | #include "util.h" 26 | 27 | #include 28 | 29 | int main(int argc, char **argv) { 30 | khtcp::util::init_logging(boost::log::trivial::info); 31 | 32 | // add all devices 33 | if (khtcp::device::add_device(nullptr) < 0) { 34 | std::cerr << "Failed to add all devices" << std::endl; 35 | return -1; 36 | } 37 | 38 | khtcp::eth::set_frame_receive_callback(khtcp::eth::ethertype_broker_callback); 39 | if (argc > 2) { 40 | std::cerr << "usage: " << argv[0] << " [default-route]\n"; 41 | return -1; 42 | } else if (argc != 1) { 43 | // we have a default route 44 | khtcp::ip::route r; 45 | khtcp::util::string_to_ip("0", r.dst); 46 | r.prefix = 0; 47 | r.has_router = true; 48 | khtcp::util::string_to_ip(argv[1], r.router); 49 | r.age = -1; // never expires 50 | if (!khtcp::ip::add_route(std::move(r))) { 51 | std::cerr << "Failed to add default route" << std::endl; 52 | return -1; 53 | } 54 | } 55 | 56 | // test TCP segment send 57 | khtcp::ip::addr_t baidu; 58 | khtcp::util::string_to_ip("39.156.69.79", baidu); 59 | boost::asio::deadline_timer t(khtcp::core::get().io_context); 60 | t.expires_from_now(boost::posix_time::seconds(2)); 61 | t.async_wait([&](auto ec) { 62 | if (!ec) { 63 | khtcp::tcp::async_open(khtcp::core::get().devices[0]->ip_addrs[0], 54321, 64 | baidu, 80, true, [](int ret, auto key) { 65 | if (!ret) { 66 | std::cout << "Successfully opened connection" 67 | << std::endl; 68 | } else { 69 | std::cerr 70 | << "Failed to open connection: errno " 71 | << ret << std::endl; 72 | } 73 | }); 74 | } 75 | }); 76 | 77 | return khtcp::core::get().run(); 78 | } 79 | -------------------------------------------------------------------------------- /tests/test_unix_socket.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | struct request { 9 | enum { 10 | ARP_READ, 11 | ARP_WRITE, 12 | } type; 13 | union { 14 | struct { 15 | int dev_id; 16 | } arp_read; 17 | struct { 18 | int dev_id; 19 | int opcode; 20 | struct sockaddr_ll sender_mac; 21 | struct sockaddr_in sender_ip; 22 | struct sockaddr_ll target_mac; 23 | struct sockaddr_in target_ip; 24 | } arp_write; 25 | }; 26 | }; 27 | 28 | void print_ll(struct sockaddr_ll &a) { 29 | for (int i = 0; i < a.sll_halen; ++i) { 30 | std::cout << std::hex << std::setw(2) << std::setfill('0') 31 | << (int)a.sll_addr[i]; 32 | if (i != a.sll_halen - 1) { 33 | std::cout << ":"; 34 | } 35 | } 36 | } 37 | 38 | #define ENDPOINT "/tmp/foobar.sock" 39 | 40 | int main(int argc, char **argv) { 41 | using namespace boost::asio; 42 | io_context io; 43 | struct request req; 44 | if (argc == 1) { 45 | struct d { 46 | d() { ::unlink(ENDPOINT); } 47 | ~d() { ::unlink(ENDPOINT); } 48 | } dd; 49 | local::stream_protocol::endpoint ep(ENDPOINT); 50 | local::stream_protocol::acceptor acceptor(io, ep); 51 | acceptor.async_accept([&](auto ec, auto &&sock) { 52 | if (!ec) { 53 | async_read(sock, buffer(&req, sizeof(struct request)), 54 | [&](auto ec, auto b) { 55 | char str[INET_ADDRSTRLEN]; 56 | std::cout << "Sender MAC: "; 57 | print_ll(req.arp_write.sender_mac); 58 | std::cout << std::endl; 59 | std::cout << "Sender IP: "; 60 | inet_ntop(AF_INET, &req.arp_write.sender_ip, str, 61 | INET_ADDRSTRLEN); 62 | std::cout << str << std::endl; 63 | std::cout << "Target MAC: "; 64 | print_ll(req.arp_write.target_mac); 65 | std::cout << std::endl; 66 | std::cout << "Target IP: "; 67 | inet_ntop(AF_INET, &req.arp_write.target_ip, str, 68 | INET_ADDRSTRLEN); 69 | std::cout << str << std::endl; 70 | }); 71 | } else { 72 | std::cerr << ec.message() << std::endl; 73 | } 74 | }); 75 | io.run(); 76 | } else { 77 | struct ifaddrs *ifaddr = nullptr; 78 | if (getifaddrs(&ifaddr) == -1) { 79 | perror("getifaddrs"); 80 | return -1; 81 | } else { 82 | char str[INET_ADDRSTRLEN]; 83 | for (struct ifaddrs *ifa = ifaddr; ifa; ifa = ifa->ifa_next) { 84 | if (!ifa->ifa_addr) 85 | continue; 86 | if (!strcmp(ifa->ifa_name, "eth0")) { 87 | switch (ifa->ifa_addr->sa_family) { 88 | case AF_PACKET: 89 | memcpy(&req.arp_write.sender_mac, ifa->ifa_addr, 90 | sizeof(struct sockaddr_ll)); 91 | memcpy(&req.arp_write.target_mac, ifa->ifa_addr, 92 | sizeof(struct sockaddr_ll)); 93 | break; 94 | case AF_INET: 95 | inet_ntop(AF_INET, &((struct sockaddr_in *)ifa->ifa_addr)->sin_addr, 96 | str, INET_ADDRSTRLEN); 97 | std::cout << str << std::endl; 98 | memcpy(&req.arp_write.sender_ip, ifa->ifa_addr, 99 | sizeof(struct sockaddr_in)); 100 | memcpy(&req.arp_write.target_ip, ifa->ifa_addr, 101 | sizeof(struct sockaddr_in)); 102 | break; 103 | } 104 | } 105 | } 106 | std::cout << "Sender MAC: "; 107 | print_ll(req.arp_write.sender_mac); 108 | std::cout << std::endl; 109 | std::cout << "Sender IP: "; 110 | inet_ntop(AF_INET, &req.arp_write.sender_ip, str, INET_ADDRSTRLEN); 111 | std::cout << str << std::endl; 112 | std::cout << "Target MAC: "; 113 | print_ll(req.arp_write.target_mac); 114 | std::cout << std::endl; 115 | std::cout << "Target IP: "; 116 | inet_ntop(AF_INET, &req.arp_write.target_ip, str, INET_ADDRSTRLEN); 117 | std::cout << str << std::endl; 118 | local::stream_protocol::endpoint ep(ENDPOINT); 119 | local::stream_protocol::socket sock(io); 120 | sock.async_connect(ep, [&](const auto &ec) { 121 | async_write(sock, buffer(&req, sizeof(struct request)), 122 | [](auto ec, auto b) { 123 | if (!ec) { 124 | std::cout << "Request written." << std::endl; 125 | } else { 126 | std::cerr << ec.message() << std::endl; 127 | } 128 | }); 129 | }); 130 | io.run(); 131 | } 132 | } 133 | } -------------------------------------------------------------------------------- /vnet-setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | IF="$(ls /sys/class/net/ | sed -e '/lo/d')" 4 | SERVER="out/khtcp/tests/test_server" 5 | 6 | # turn off IPv4 7 | iptables -P INPUT DROP 8 | iptables -P OUTPUT DROP 9 | iptables -P FORWARD DROP 10 | 11 | # turn off IPv6 12 | ip6tables -P INPUT DROP 13 | ip6tables -P OUTPUT DROP 14 | ip6tables -P FORWARD DROP 15 | 16 | # turn off ARP on all interfaces 17 | for i in ${IF}; do 18 | echo "Turing off ARP for interface $i" 19 | ip l s "$i" arp off 20 | done 21 | 22 | # launch test server 23 | exec "${SERVER}" 24 | -------------------------------------------------------------------------------- /writing-task.md: -------------------------------------------------------------------------------- 1 | # Solutions to all writing tasks 2 | 3 | ## Network-layer: IP Protocol 4 | 5 | - Solution to WT2: 6 | - ARP is implemented (`include/arp.h` on server and `include/arp.h` on client). Upon receiving a IP write request, the routing table is consulted to learn the appropriate device to send to based on the destination address. Depending on the route type, 7 | - If the destination is in the same subnet as the sender (i.e. `dev` type route), ARP resolution is done for the destination directly; 8 | - Otherwise (i.e. `via` type route), ARP resolution is done for the gateway. 9 | - ARP resolution is done in the following manner: 10 | - Upon receiving any ARP packet, record the sender IP-sender MAC mapping in the global neighbor table (a.k.a. ARP table); 11 | - Entries in the global neighbor table will expire after 20 seconds upon creation, and time to live will __NOT__ be refreshed when accessed. 12 | - If the entry being added is already present, it will be updated with the new timeout (20 seconds) and the new MAC address. 13 | - If the ARP opcode is Request, respond according to RFC826. 14 | - Consult the global neighbor table; if the requested IP address is present, return the recorded MAC; 15 | - Otherwise send ARP query to broadcast (`ff:ff:ff:ff:ff:ff`), requesting MAC for the requested IP address. Then repeat the previous step (checking the neighbor table). 16 | - A total of 5 tries in 1 second (200ms each try) will be attempted before failing. In case of a failure, the IP write operation fails. 17 | - Corner cases: 18 | - When a route is not present (neither directly-connected nor have gateway), a `EHOSTUNREACH` will be returned to the sending process. 19 | - When a route is present but the ARP resolution destination (the gateway or the direct destination) did not respond, retry will be done. 20 | - Routes will be checked that the gateway is on a directly-connected network when being added. 21 | - Solution to WT3: 22 | - RIPv2, also known as IETF RFC2453, is implemented as the routing protocol. The basic ideas are as follows: 23 | - Bellman-Ford (Distance Vector) is used as the routing algorithm 24 | - Supports default route distribution 25 | - Routes have metric of maximum 16 (aka. "infinity") and ages 26 | - Routes with metric infinity does not participate in data routing 27 | - 180 seconds before expiring (metric set to 16) 28 | - Deletion after 120 more seconds after expiring 29 | - Request / response messages: 30 | - Request is only generated when a router first starts up or for router inspection (not relevant here) 31 | - In the normal case, such requests expect a full table from neighbors 32 | - Response can be generated in three situations: 33 | - Response to Request: send full table, unicast 34 | - Regular Updates: runs every 30 seconds, sends full table after Split Horizon to neighbors, multicast 35 | - Split Horizon: when to send route into network that contains the route's gateway, poison it with metric infinity (To prevent 2-party loops) 36 | - Triggered Updates: runs when a route is updated, sends updated routes after Split Horizon to neighbors, multicast 37 | - Same split horizon definition, except that poisoned routes are not included 38 | - Random cooldown between 1-5 seconds to prevent flooding the network 39 | - Fast route acquisition, slow removal 40 | - Request & regular update on start, propagates to the entire network 41 | - No link failure detection / ICMP, can only rely on timeout to remove non-functional route 42 | - Corner cases: 43 | - Careful checks are performed to ensure neighbors behave correctly. Malformed RIP messages will be logged then ignored. 44 | - Multicast IP packets (to the Local Network Control Block) will have TTL set to 1 to prevent unexpected routing of these packets. 45 | - Split Horizon is employed to solve cases where 2 routers form a loop. 46 | - Triggered Updates are employed to solve cases where 3 or more routers form a loop. 47 | - Solution to WT4: 48 | - Standard compliance: works fine with RIPv2 routers: tested with BIRD. 49 | - Configuration file: 50 | ```conf 51 | protocol kernel { 52 | learn; 53 | ipv4 { 54 | export filter { 55 | if net = 0.0.0.0/0 then { 56 | accept; 57 | } 58 | reject; 59 | } 60 | }; 61 | } 62 | protocol device { 63 | } 64 | protocol rip { 65 | debug all; 66 | ipv4 { 67 | import filter { 68 | if net = 0.0.0.0/0 then { 69 | reject; 70 | } 71 | accept; 72 | }; 73 | export all; 74 | }; 75 | interface ""; 76 | } 77 | ``` 78 | - IP multicast & Ethernet multicast implemented 79 | - Hosts choose to join multicast group; non-present groups' messages will not be delivered 80 | - General UDP transport used to carry RIP 81 | - Hosts that do not understand RIP on port 520 will just ignore the UDP packets --------------------------------------------------------------------------------