├── CMakeLists.txt ├── LICENSE.md ├── README.md ├── roaming.atxt ├── roaming.pu └── src ├── .clang-format ├── CMakeLists.txt ├── alloc.h ├── arp.c ├── arp.h ├── clientmgr.c ├── clientmgr.h ├── common.h ├── config.c ├── config.h ├── error.h ├── genl.c ├── genl.h ├── icmp6.c ├── icmp6.h ├── if.h ├── intercom.c ├── intercom.h ├── ipmgr.c ├── ipmgr.h ├── l3roamd.h ├── main.c ├── packet.c ├── packet.h ├── prefix.c ├── prefix.h ├── routemgr.c ├── routemgr.h ├── socket.c ├── socket.h ├── syscallwrappers.c ├── syscallwrappers.h ├── taskqueue.c ├── taskqueue.h ├── test.c ├── timespec.c ├── timespec.h ├── types.h ├── util.c ├── util.h ├── vector.c ├── vector.h ├── version.h.in ├── wifistations.c └── wifistations.h /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8.3) 2 | project(L3ROAMD C) 3 | 4 | INCLUDE(FindPkgConfig) 5 | IF(NOT PKG_CONFIG_FOUND) 6 | MESSAGE(FATAL_ERROR "Error: pkg-config not found on this system") 7 | ENDIF(NOT PKG_CONFIG_FOUND) 8 | 9 | MESSAGE(STATUS "") 10 | MESSAGE(STATUS "Configuring libnl ...") 11 | 12 | PKG_SEARCH_MODULE(LIBNL-TINY libnl-tiny) 13 | IF(LIBNL-TINY_FOUND) 14 | INCLUDE_DIRECTORIES(${LIBNL-TINY_INCLUDE_DIRS}) 15 | LINK_DIRECTORIES(${LIBNL-TINY_LIBRARY_DIRS}) 16 | SET(LIBNL_LIBRARIES ${LIBNL-TINY_LIBRARIES}) 17 | ELSE(LIBNL-TINY_FOUND) 18 | PKG_SEARCH_MODULE(LIBNL libnl>=2.0 libnl-2.0 libnl-2) 19 | IF(LIBNL_FOUND) 20 | INCLUDE_DIRECTORIES(${LIBNL_INCLUDE_DIRS}) 21 | LINK_DIRECTORIES(${LIBNL_LIB_DIRS}) 22 | LIST(APPEND LIBNL_LIBRARIES 23 | nl-genl 24 | ) 25 | ELSE(LIBNL_FOUND) 26 | PKG_SEARCH_MODULE(LIBNL3 libnl-3 libnl-3.0 libnl-3.1) 27 | IF(LIBNL3_FOUND) 28 | INCLUDE_DIRECTORIES(${LIBNL3_INCLUDE_DIRS}) 29 | LINK_DIRECTORIES(${LIBNL3_LIB_DIRS}) 30 | SET(LIBNL_LIBRARIES ${LIBNL3_LIBRARIES}) 31 | 32 | ############################################################# 33 | MESSAGE(STATUS "") 34 | MESSAGE(STATUS "Configuring libnl-genl ...") 35 | 36 | PKG_SEARCH_MODULE(LIBNL_GENL libnl-genl>=2.0 libnl-genl-3 libnl-genl-3.0 libnl-genl-3.1) 37 | IF(LIBNL_GENL_FOUND) 38 | INCLUDE_DIRECTORIES(${LIBNL_GENL_INCLUDE_DIRS}) 39 | LINK_DIRECTORIES(${LIBNL_GENL_LIB_DIRS}) 40 | ELSEIF(LIBNL_GENL_FOUND) 41 | MESSAGE(FATAL_ERROR "Error: libnl-genl not found") 42 | ENDIF(LIBNL_GENL_FOUND) 43 | ELSE(LIBNL3_FOUND) 44 | MESSAGE(FATAL_ERROR "Error: libnl and libnl-genl not found") 45 | ENDIF(LIBNL3_FOUND) 46 | ENDIF(LIBNL_FOUND) 47 | ENDIF(LIBNL-TINY_FOUND) 48 | 49 | pkg_check_modules(_JSON_C json-c) 50 | 51 | find_path(JSON_C_INCLUDE_DIR NAMES json-c/json.h HINTS ${_JSON_C_INCLUDE_DIRS}) 52 | find_library(JSON_C_LIBRARIES NAMES json-c HINTS ${_JSON_C_LIBRARY_DIRS}) 53 | 54 | set(JSON_C_CFLAGS_OTHER "${_JSON_C_CFLAGS_OTHER}" CACHE STRING "Additional compiler flags for json-c") 55 | set(JSON_C_LDFLAGS_OTHER "${_JSON_C_LDFLAGS_OTHER}" CACHE STRING "Additional linker flags for json-c") 56 | 57 | find_package_handle_standard_args(JSON_C REQUIRED_VARS JSON_C_LIBRARIES JSON_C_INCLUDE_DIR) 58 | mark_as_advanced(JSON_C_INCLUDE_DIR JSON_C_LIBRARIES JSON_C_CFLAGS_OTHER JSON_C_LDFLAGS_OTHER) 59 | 60 | set(CMAKE_MODULE_PATH ${L3ROAMD_SOURCE_DIR}) 61 | 62 | add_subdirectory(src) 63 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # License 2 | 3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 4 | 5 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 6 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 7 | 8 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # l3roamd 2 | 3 | [![standard-readme compliant](https://img.shields.io/badge/readme%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/RichardLitt/standard-readme) 4 | 5 | > l3roamd, pronunced *ɛl θriː ɹoʊm diː*, is a core part of layer 3 mesh networks. 6 | 7 | l3roamd, is a core part of layer 3 mesh networks. 8 | At first it will be built to work with [babeld](https://github.com/jech/babeld). 9 | 10 | Integrating tightly with mac80211 and netlink, l3roamd will be doing the following things: 11 | - monitor the presence of clients 12 | - allow network-wide search for IP-addresses 13 | - manage distribution of prefixes across the mesh (for router advertisements) [RA] 14 | - monitor babeld for duplicate host routes that this node also announces 15 | 16 | Ideally, this should be split up into multiple daemons communicating over standardized protocols. 17 | 18 | ## Background 19 | 20 | ### Managing clients 21 | 22 | l3roamd will directly monitor a set of interfaces for clients. 23 | On the mac80211/fdb layer it will monitor clients and act whenever a new client appears (query the database or create an entry) 24 | or when a client disappears. 25 | In case of a disappearing client a node removes all host routes for that client. It does 26 | not yet forget about them completely in case the client re-appears later. 27 | The routes presence in the routing table is controlled by the presence of the client (subject to some timeout). 28 | The routes presence in the database is subject to the timeout of the IPs lifetime (as was announced by the RA). 29 | In worst case two nodes may have to switch routes fast and repeatably due to a client having bad connectivity 30 | to either node. The whole cycle should be 31 | These nodes may not be connected directly. 32 | 33 | 34 | ### [DB] Distributed Database 35 | 36 | *Does anyone know a client with this $MAC and if so, what IPs did it use?* 37 | 38 | This information is available by querying a special node-client-IP that is 39 | calculated based on any given clients mac address. This address is assigned to 40 | the loopback-interface of the node serving the client. 41 | 42 | Using this address, a node can be notified to drop one of its local 43 | clients, release all host routes and send an info packet to the 44 | requesting node. 45 | The node requesting this drop will parse the info-packet, extract the 46 | IP-addresses used by this client and immediately adjust the 47 | corresponding routes. 48 | 49 | #### Data stored about clients 50 | 51 | - MAC 52 | - a set of IPs 53 | - (optional) a set of multicast groups 54 | 55 | ### [RA] Router Advertisements and Prefix Management 56 | 57 | *THIS PART IS NOT IMPLEMENTED YET AND LIKELY WILL NEVER BE INSIDE L3ROAMD* 58 | Instead this should be implemented in a separate daemon called prefixd. 59 | 60 | Any node should be able to announce a prefix (a /64) to be used by clients. 61 | This must be announced both within l3roamd and as a default route with a source prefix (set to the announced prefix!) 62 | through babeld. 63 | A metric (e.g. uplink bandwidth, reliability, ...) should be included, too. 64 | Nodes should announce a small subset of prefixes from nearby uplinks (actually, metric based) to clients via normal 65 | router advertisements. 66 | Lifetime of these prefixes should be managed such that clients always use the best uplink available. 67 | This is where early loadbalancing can reasonably take place. 68 | Clients are expected to cope with changing prefixes. 69 | Clients are also expected to hold onto deprecated prefixes as long as active connections require it. 70 | Routing for all, even deprecated, prefixes will be maintained as long as reasonably possible to avoid breaking a clients (TCP) connection. 71 | Multiple default routes for prefixes may be common (think multi homed AS), in this case loadbalancing is delegated to babeld. 72 | This means, that multiple nodes will announce the same set of prefixes with possibly different metrics. 73 | l3roamd will manages the prefixes it announces to a client on a per-client basis, if possibly. 74 | I.e. it will actively deprecate prefixes of clients it deems unreliably. 75 | This is likely to happen during roaming longer distances when a completely different set of uplinks should be used. 76 | As stated before, this will not break active connections. 77 | 78 | ### IPv4? 79 | 80 | IPv4-Clients are supported as well. Inside the mesh there is only 81 | ipv6, so we need clat on the node and plat somewhere else on the net. We 82 | support using a single exit at the moment and are working on multi-exit-support for plat. 83 | 84 | ## Intercom Packets 85 | 86 | This documents version 1 of the packet format. 87 | Please refer to roaming.pu and roaming.atxt for a sequence diagram of the roaming cycle. 88 | 89 | Intercom packets are UDP-packets that can be interchanged using the node-client-IP or the multicast-address as destination. 90 | 91 | There are four packet types used by l3roamd: 92 | 93 | - SEEK, 94 | - CLAIM, 95 | - INFO 96 | - ACK 97 | 98 | SEEK are usually sent as multicast while CLAIM, INFO and ACK are sent as unicast. 99 | 100 | 101 | ### Addresses 102 | 103 | #### Multicast-address 104 | 105 | ff02::5523 will be used as destination for multicast packets. 106 | 107 | #### node-client-ip address 108 | 109 | The node-client-ip address is assigned to a node whenever a client 110 | connects to a node. It is calculated using the clients mac address and 111 | the node-client-prefix (parameter -P). 112 | 113 | ### Port 114 | 115 | The packets are directed at port 5523. 116 | 117 | ### Header 118 | 119 | Each packet consists of a common header structure: 120 | ``` 121 | 0 7 15 23 31 122 | +-----------------------------------+ 123 | | VERSION| TTL | type | empty | 124 | +--------+--------+--------+--------+ 125 | | nonce1 | nonce2 | nonce3 | nonce4 | 126 | +--------+--------+--------+--------+ 127 | |sender1 |sender2 |sender3 |sender4 | 128 | +--------+--------+--------+--------+ 129 | |sender5 |sender6 |sender7 |sender8 | 130 | +--------+--------+--------+--------+ 131 | |sender9 |sender10|sender11|sender12| 132 | +--------+--------+--------+--------+ 133 | |sender13|sender14|sender15|sender16| 134 | +-----------------------------------+ 135 | ``` 136 | VERSION - this is the version of the protocol. Meant to allow compatibility of multiple versions of l3roamd. 137 | TTL - this is decremented whenever a multicast-packet is forwarded. 138 | type - this is the packet-type, one of INTERCOM_SEEK, INTERCOM_CLAIM, INTERCOM_INFO, INTERCOM_ACK. 139 | nonce - this is a random number that is used to identify duplicate packets and drop them. 140 | sender - ipv6-address of the sender of the packet. 141 | 142 | ### SEEK 143 | The seek operation is sent to determine where a client having a specific 144 | IP address is connected. This triggers local neighbor discovery 145 | mechanisms. SEEK-packets have the following structure: 146 | 147 | addr contains the unknown ipv6-address. 148 | ``` 149 | 0 7 15 23 31 150 | +-----------------------------------+ 151 | | VERSION| TTL | type | empty | 152 | +--------+--------+--------+--------+ 153 | | nonce1 | nonce2 | nonce3 | nonce4 | 154 | +--------+--------+--------+--------+ 155 | |sender1 |sender2 |sender3 |sender4 | 156 | +--------+--------+--------+--------+ 157 | |sender5 |sender6 |sender7 |sender8 | 158 | +--------+--------+--------+--------+ 159 | |sender9 |sender10|sender11|sender12| 160 | +--------+--------+--------+--------+ 161 | |sender13|sender14|sender15|sender16| 162 | +--------+--------+--------+--------+ --- header ends here. 163 | | type | length | empty | empty | type for addr: 0x00 164 | +--------+--------+--------+--------+ 165 | | addr1 | addr2 | addr3 | addr4 | 166 | +--------+--------+--------+--------+ 167 | | addr5 | addr6 | addr7 | addr8 | 168 | +--------+--------+--------+--------+ 169 | | addr9 | addr10 | addr11 | addr12 | 170 | +--------+--------+--------+--------+ 171 | | addr13 | addr14 | addr15 | addr16 | 172 | +-----------------------------------+ 173 | ``` 174 | ### CLAIM 175 | 176 | When a client connects to a node, this node sends a claim to the special 177 | node-client IP-address via unicast. So whichever node was the previous 178 | AP for this client will receive this message, drop all host-routes for 179 | this client, drop the node-client-IP and respond with an INFO-message 180 | CLAIM-packets have the following structure: 181 | ``` 182 | 0 7 15 23 31 183 | +-----------------------------------+ 184 | | VERSION| TTL | type | empty | 185 | +--------+--------+--------+--------+ 186 | | nonce1 | nonce2 | nonce3 | nonce4 | 187 | +--------+--------+--------+--------+ 188 | |sender1 |sender2 |sender3 |sender4 | 189 | +--------+--------+--------+--------+ 190 | |sender5 |sender6 |sender7 |sender8 | 191 | +--------+--------+--------+--------+ 192 | |sender9 |sender10|sender11|sender12| 193 | +--------+--------+--------+--------+ 194 | |sender13|sender14|sender15|sender16| 195 | +--------+--------+--------+--------+ --- header ends here. 196 | | type | length | MAC1 | MAC2 | type for MAC: 0x00 197 | +--------+--------+--------+--------+ 198 | | MAC3 | MAC4 | MAC5 | MAC6 | 199 | +-----------------------------------+ 200 | ``` 201 | MAC - is the mac-address of the client being claimed. 202 | 203 | ### INFO 204 | 205 | This packet contains all IP-addresses being in active use by a given client. It 206 | will be sent in response to CLAIM via unicast. 207 | 208 | INFO-packets have the following structure: 209 | ``` 210 | 0 7 15 23 31 211 | +-----------------------------------+ 212 | | VERSION| TTL | type | empty | 213 | +--------+--------+--------+--------+ 214 | | nonce1 | nonce2 | nonce3 | nonce4 | 215 | +--------+--------+--------+--------+ 216 | |sender1 |sender2 |sender3 |sender4 | 217 | +--------+--------+--------+--------+ 218 | |sender5 |sender6 |sender7 |sender8 | 219 | +--------+--------+--------+--------+ 220 | |sender9 |sender10|sender11|sender12| 221 | +--------+--------+--------+--------+ 222 | |sender13|sender14|sender15|sender16| 223 | +--------+--------+--------+--------+ --- header ends here. 224 | | type | length | lease1 | lease2 | type for plat info: 0x00 225 | +--------+--------+-----------------+ 226 | | plat1 | plat2 | plat3 | plat4 | 227 | +--------+--------+--------+--------+ 228 | | plat5 | plat6 | plat7 | plat8 | 229 | +--------+--------+--------+--------+ 230 | | plat9 | plat10 | plat11 | plat12 | 231 | +--------+--------+--------+--------+ 232 | | plat13 | plat14 | plat15 | plat16 | 233 | +--------+--------+--------+--------+ --- plat client info ends here 234 | | type | length | MAC1 | MAC2 | type for basic info: 0x01 235 | +--------+--------+--------+--------+ 236 | | MAC3 | MAC4 | MAC5 | MAC6 | 237 | +--------+--------+--------+--------+ 238 | |addr1_1 |addr1_2 |addr1_3 |addr1_4 | 239 | +--------+--------+--------+--------+ 240 | |addr1_5 |addr1_6 |addr1_7 |addr1_8 | 241 | +--------+--------+--------+--------+ 242 | |addr1_9 |addr1_10|addr1_11|addr1_12| 243 | +--------+--------+--------+--------+ 244 | |addr1_13|addr1_14|addr1_15|addr1_16| 245 | +--------+--------+--------+--------+ 246 | |addr2_1 |addr2_2 |addr2_3 |addr2_4 | 247 | +--------+--------+--------+--------+ 248 | |addr2_5 |addr2_6 |addr2_7 |addr2_8 | 249 | +--------+--------+--------+--------+ 250 | |addr2_9 |addr2_10|addr2_11|addr2_12| 251 | +--------+--------+--------+--------+ 252 | |addr2_13|addr2_14|addr2_15|addr2_16| 253 | +--------+--------+--------+--------+ 254 | |addr#_1 |addr#_2 |addr#_3 |addr#_4 | 255 | +--------+--------+--------+--------+ 256 | |addr#_5 |addr#_6 |addr#_7 |addr#_8 | 257 | +--------+--------+--------+--------+ 258 | |addr#_9 |addr#_10|addr#_11|addr#_12| 259 | +--------+--------+--------+--------+ 260 | |addr#_13|addr#_14|addr#_15|addr#_16| 261 | +--------+--------+--------+--------+ --- basic client info ends here 262 | ``` 263 | MAC is the mac-address of the client 264 | #addr is the amount of client-6ipv6-addresses (max 15) in the segment. 265 | addr1-addr# are 1-n ipv6 addresses. 266 | plat is the plat-prefix used by this client. 267 | lease is the remaining lease time of the clients ipv4 address in seconds. 268 | 269 | #### ACK 270 | 271 | This packet is sent in reply of an INFO packet. Upon reception the retry-cycle for sending INFO packets for the client identified by the MAC is aborted. 272 | ``` 273 | 0 7 15 23 31 274 | +-----------------------------------+ 275 | | VERSION| TTL | type | empty | 276 | +--------+--------+--------+--------+ 277 | | nonce1 | nonce2 | nonce3 | nonce4 | 278 | +--------+--------+--------+--------+ 279 | |sender1 |sender2 |sender3 |sender4 | 280 | +--------+--------+--------+--------+ 281 | |sender5 |sender6 |sender7 |sender8 | 282 | +--------+--------+--------+--------+ 283 | |sender9 |sender10|sender11|sender12| 284 | +--------+--------+--------+--------+ 285 | |sender13|sender14|sender15|sender16| 286 | +--------+--------+--------+--------+ --- header ends here. 287 | | type | length | MAC1 | MAC2 | type for MAC: 0x00 288 | +--------+--------+--------+--------+ 289 | | MAC3 | MAC4 | MAC5 | MAC6 | 290 | +-----------------------------------+ 291 | ``` 292 | --- 293 | 294 | 295 | ## Install 296 | 297 | ### Dependencies 298 | 299 | The following programs / libraries are needed to build and run l3roamd: 300 | - libnl-genl 301 | - libjson-c 302 | - kmod-tun 303 | 304 | ### Build 305 | 306 | In the root of this project, run: 307 | 308 | ``` 309 | mkdir build 310 | cd build 311 | cmake .. 312 | make 313 | make install 314 | ``` 315 | 316 | to build and install the program. 317 | 318 | ## Usage 319 | 320 | L3roamd must be running on every node where clients can attach and on 321 | all exit-nodes. Use -a -p and -m to provide the address of the node, at 322 | least one client-prefix and at least one mesh-interface for the service 323 | to be useful. client-interface/bridges are optional. 324 | 325 | An example call for l3roamd would be: 326 | 327 | ``` 328 | ip a a fddd::1 dev lo 329 | l3roamd -a fddd::1 -p fddd:1::/64 -m mesh0 -b br-client 330 | ``` 331 | 332 | For further command line parameters, refert to the help that can be 333 | called with l3roamd -h 334 | 335 | ## Contribute 336 | 337 | If you can improve this specifications (typos, better wording, restructuring, …) or even new important aspects, feel free to open a pull request. 338 | Please prefix your commits with "README: $my message" and try to summarize the changes in the commit message even if the commit message turns out to be longer than the change. Say, if you change a single word, write a message like 339 | 340 | README: corrected singel to single 341 | 342 | This corrects a typo in the "Improvements welcome!" section. 343 | 344 | This approach makes reviewing and reasoning about changes a lot easier. 345 | 346 | ## License 347 | 348 | ``` 349 | Redistribution and use in source and binary forms, with or without 350 | modification, are permitted provided that the following conditions are met: 351 | 352 | 1. Redistributions of source code must retain the above copyright notice, 353 | this list of conditions and the following disclaimer. 354 | 2. Redistributions in binary form must reproduce the above copyright notice, 355 | this list of conditions and the following disclaimer in the documentation 356 | and/or other materials provided with the distribution. 357 | 358 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 359 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 360 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 361 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 362 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 363 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 364 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 365 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 366 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 367 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 368 | ``` 369 | -------------------------------------------------------------------------------- /roaming.atxt: -------------------------------------------------------------------------------- 1 | ,-. 2 | `-' 3 | /|\ 4 | | ,-----. ,-----. 5 | / \ |Node2| |Node1| 6 | Client `--+--' `--+--' 7 | ,--------------------------------------!. | 8 | |Client was connected to Node1 |_\ | 9 | |Clients routes point to Node1 | | 10 | |Client roams to Node2. | | 11 | |Node2 detects client and starts claim. | | 12 | `----------------------------------------' | 13 | | | CLAIM for Client MAC (max 15 times) | 14 | | | via unicast on node-client-IP | 15 | | |------------------------------------------->| 16 | | | | 17 | | | ----. 18 | | | | Drop client based on MAC 19 | | | <---' including its routes 20 | | | 21 | | | | 22 | | | INFO packet | 23 | | | (unicast, stop on ACK or | 24 | | | after 15 retries every 500ms) | 25 | | |<- - - - - - - - - - - - - - - - - - - - - -| 26 | | | | 27 | | ----. | 28 | | | set routes for all the | 29 | | <---' clients IP addresses | 30 | | | 31 | | | | 32 | ,--------------------------!. | | 33 | |All routes point to Node2 |_\ | | 34 | |Client can use the network | | | 35 | `----------------------------' | | 36 | | |1 ACK for every received INFO to its sender | 37 | | |------------------------------------------->| 38 | Client ,--+--. ,--+--. 39 | ,-. |Node2| |Node1| 40 | `-' `-----' `-----' 41 | /|\ 42 | | 43 | / \ 44 | -------------------------------------------------------------------------------- /roaming.pu: -------------------------------------------------------------------------------- 1 | @startuml 2 | actor Client 3 | participant Node2 4 | participant Node1 5 | 6 | note over Client: Client was connected to Node1\nClients routes point to Node1\nClient roams to Node2.\nNode2 detects client and starts claim. 7 | 8 | Node2 -> Node1: CLAIM for Client MAC (max 15 times)\nvia unicast on node-client-IP 9 | Node1 -> Node1: Drop client based on MAC\nincluding its routes 10 | Node1 --> Node2: INFO packet\n(unicast, stop on ACK or\nafter 15 retries every 500ms) 11 | Node2 -> Node2: set routes for all the\nclients IP addresses 12 | note over Client: All routes point to Node2\nClient can use the network 13 | Node2 -> Node1: 1 ACK for every received INFO to its sender 14 | 15 | @enduml 16 | -------------------------------------------------------------------------------- /src/.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | AccessModifierOffset: -1 4 | AlignAfterOpenBracket: Align 5 | AlignConsecutiveAssignments: false 6 | AlignConsecutiveDeclarations: false 7 | AlignEscapedNewlinesLeft: true 8 | AlignOperands: true 9 | AlignTrailingComments: true 10 | AllowAllParametersOfDeclarationOnNextLine: true 11 | AllowShortBlocksOnASingleLine: false 12 | AllowShortCaseLabelsOnASingleLine: false 13 | AllowShortFunctionsOnASingleLine: All 14 | AllowShortIfStatementsOnASingleLine: false 15 | AllowShortLoopsOnASingleLine: true 16 | AlwaysBreakAfterDefinitionReturnType: None 17 | AlwaysBreakAfterReturnType: None 18 | AlwaysBreakBeforeMultilineStrings: true 19 | AlwaysBreakTemplateDeclarations: true 20 | BinPackArguments: true 21 | BinPackParameters: true 22 | BraceWrapping: 23 | AfterClass: false 24 | AfterControlStatement: false 25 | AfterEnum: false 26 | AfterFunction: false 27 | AfterNamespace: false 28 | AfterObjCDeclaration: false 29 | AfterStruct: false 30 | AfterUnion: false 31 | BeforeCatch: false 32 | BeforeElse: false 33 | IndentBraces: false 34 | BreakBeforeBinaryOperators: None 35 | BreakBeforeBraces: Attach 36 | BreakBeforeTernaryOperators: true 37 | BreakConstructorInitializersBeforeComma: false 38 | ColumnLimit: 120 39 | CommentPragmas: '^ IWYU pragma:' 40 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 41 | ConstructorInitializerIndentWidth: 4 42 | ContinuationIndentWidth: 4 43 | Cpp11BracedListStyle: true 44 | DerivePointerAlignment: true 45 | DisableFormat: false 46 | ExperimentalAutoDetectBinPacking: false 47 | ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] 48 | IncludeCategories: 49 | - Regex: '^<.*\.h>' 50 | Priority: 1 51 | - Regex: '^<.*' 52 | Priority: 2 53 | - Regex: '.*' 54 | Priority: 3 55 | IndentCaseLabels: true 56 | IndentWidth: 8 57 | IndentWrappedFunctionNames: false 58 | KeepEmptyLinesAtTheStartOfBlocks: false 59 | MacroBlockBegin: '' 60 | MacroBlockEnd: '' 61 | MaxEmptyLinesToKeep: 1 62 | NamespaceIndentation: None 63 | ObjCBlockIndentWidth: 2 64 | ObjCSpaceAfterProperty: false 65 | ObjCSpaceBeforeProtocolList: false 66 | PenaltyBreakBeforeFirstCallParameter: 1 67 | PenaltyBreakComment: 300 68 | PenaltyBreakFirstLessLess: 120 69 | PenaltyBreakString: 1000 70 | PenaltyExcessCharacter: 1000000 71 | PenaltyReturnTypeOnItsOwnLine: 200 72 | PointerAlignment: Left 73 | ReflowComments: true 74 | SortIncludes: true 75 | SpaceAfterCStyleCast: false 76 | SpaceBeforeAssignmentOperators: true 77 | SpaceBeforeParens: ControlStatements 78 | SpaceInEmptyParentheses: false 79 | SpacesBeforeTrailingComments: 2 80 | SpacesInAngles: false 81 | SpacesInContainerLiterals: true 82 | SpacesInCStyleCastParentheses: false 83 | SpacesInParentheses: false 84 | SpacesInSquareBrackets: false 85 | Standard: Auto 86 | TabWidth: 8 87 | UseTab: Always 88 | ... 89 | 90 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ADD_DEFINITIONS(-D_GNU_SOURCE) 2 | 3 | 4 | list(APPEND CMAKE_REQUIRED_DEFINITIONS '-D_GNU_SOURCE') 5 | 6 | include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${L3ROAMD_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR}) 7 | 8 | list(APPEND COMMON_DEPS socket.c config.c intercom.c arp.c ipmgr.c icmp6.c syscallwrappers.c routemgr.c prefix.c vector.c wifistations.c genl.c clientmgr.c taskqueue.c timespec.c util.c packet.c) 9 | list(APPEND DAEMON_DEPS main.c) 10 | list(APPEND TEST_DEPS test.c) 11 | 12 | add_library(common_libs OBJECT ${COMMON_DEPS}) 13 | add_library(test_deps OBJECT ${TEST_DEPS}) 14 | add_library(daemon_deps OBJECT ${DAEMON_DEPS}) 15 | 16 | 17 | add_executable(l3roamd $ $) 18 | add_executable(l3roamd-test $ $) 19 | 20 | 21 | target_link_libraries(l3roamd ${LIBNL_LIBRARIES} ${LIBNL_GENL_LIBRARIES} ${JSON_C_LIBRARIES}) 22 | target_link_libraries(l3roamd-test ${LIBNL_LIBRARIES} ${LIBNL_GENL_LIBRARIES} ${JSON_C_LIBRARIES}) 23 | 24 | #set_target_properties(l3roamd PROPERTIES COMPILE_FLAGS "-std=gnu11 -Wall -fsanitize=address" LINK_FLAGS " -fno-omit-frame-pointer -fsanitize=address -static-libasan") 25 | set( CMAKE_C_FLAGS "-std=gnu11 -flto -Wall -Wcast-align -Wextra -Os " ) 26 | 27 | 28 | install(TARGETS l3roamd RUNTIME DESTINATION bin) 29 | 30 | # Get the current working branch 31 | execute_process( 32 | COMMAND git rev-parse --abbrev-ref HEAD 33 | WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} 34 | OUTPUT_VARIABLE GIT_BRANCH 35 | OUTPUT_STRIP_TRAILING_WHITESPACE 36 | ) 37 | 38 | # Get the latest abbreviated commit hash of the working branch 39 | execute_process( 40 | COMMAND git log -1 --format=%h 41 | WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} 42 | OUTPUT_VARIABLE GIT_COMMIT_HASH 43 | OUTPUT_STRIP_TRAILING_WHITESPACE 44 | ) 45 | 46 | configure_file( 47 | ${CMAKE_SOURCE_DIR}/src/version.h.in 48 | ${CMAKE_BINARY_DIR}/src/version.h 49 | ) 50 | -------------------------------------------------------------------------------- /src/alloc.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2015, Matthias Schiffer 3 | * 4 | * This file is part of project l3roamd. It's copyrighted by the contributors 5 | * recorded in the version control history of the file, available from 6 | * its original location https://github.com/freifunk-gluon/l3roamd. 7 | * 8 | * SPDX-License-Identifier: BSD-2-Clause 9 | */ 10 | 11 | /** 12 | \file 13 | 14 | \em memory allocation functions 15 | */ 16 | 17 | #pragma once 18 | 19 | #include "error.h" 20 | 21 | /** 22 | Allocates a block of uninitialized memory on the heap 23 | 24 | Terminates the process on failure. 25 | */ 26 | static inline void *l3roamd_alloc(size_t size) { 27 | void *ret = malloc(size); 28 | if (!ret) 29 | exit_errno("malloc"); 30 | 31 | return ret; 32 | } 33 | 34 | /** 35 | Allocates a block of uninitialized memory on the heap, aligned to 16 bytes 36 | 37 | Terminates the process on failure. 38 | */ 39 | static inline void *l3roamd_alloc_aligned(size_t size, size_t align) { 40 | void *ret; 41 | int err = posix_memalign(&ret, align, size); 42 | if (err) 43 | exit_error("posix_memalign: %s", strerror(err)); 44 | 45 | return ret; 46 | } 47 | 48 | /** 49 | Allocates a block of memory set to zero for an array on the heap 50 | 51 | Terminates the process on failure. 52 | */ 53 | static inline void *l3roamd_alloc0_array(size_t members, size_t size) { 54 | void *ret = calloc(members, size); 55 | if (!ret) 56 | exit_errno("calloc"); 57 | 58 | return ret; 59 | } 60 | 61 | /** 62 | Allocates a block of memory set to zero on the heap 63 | 64 | Terminates the process on failure. 65 | */ 66 | static inline void *l3roamd_alloc0(size_t size) { 67 | return l3roamd_alloc0_array(1, size); 68 | } 69 | 70 | /** 71 | Reallocates a block of memory on the heap 72 | 73 | Terminates the process on failure. 74 | */ 75 | static inline void *l3roamd_realloc(void *ptr, size_t size) { 76 | void *ret = realloc(ptr, size); 77 | // printf("realloc with size: %i\n", size); 78 | if (!ret) 79 | exit_errno("realloc"); 80 | 81 | return ret; 82 | } 83 | 84 | /** Allocates a block of uninitialized memory in the size of a given type */ 85 | #define l3roamd_new(type) ((type *)l3roamd_alloc(sizeof(type))) 86 | 87 | /** Allocates a block of uninitialized memory in the size of a given type, 88 | * aligned to 16 bytes */ 89 | #define l3roamd_new_aligned(type, align) \ 90 | ((type *)l3roamd_alloc_aligned(sizeof(type), align)) 91 | 92 | /** Allocates a block of memory set to zero in the size of a given type */ 93 | #define l3roamd_new0(type) ((type *)l3roamd_alloc0(sizeof(type))) 94 | 95 | /** Allocates a block of undefined memory for an array of elements of a given 96 | * type */ 97 | #define l3roamd_new_array(members, type) \ 98 | ((type *)l3roamd_alloc(members * sizeof(type))) 99 | 100 | /** Allocates a block of memory set to zero for an array of elements of a given 101 | * type */ 102 | #define l3roamd_new0_array(members, type) \ 103 | ((type *)l3roamd_alloc0_array(members, sizeof(type))) 104 | 105 | /** Duplicates a string (string may be NULL) */ 106 | static inline char *l3roamd_strdup(const char *s) { 107 | if (!s) 108 | return NULL; 109 | 110 | char *ret = strdup(s); 111 | if (!ret) 112 | exit_errno("strdup"); 113 | 114 | return ret; 115 | } 116 | 117 | /** Duplicates a string up to a maximum length (string may be NULL) */ 118 | static inline char *l3roamd_strndup(const char *s, size_t n) { 119 | if (!s) 120 | return NULL; 121 | 122 | char *ret = strndup(s, n); 123 | if (!ret) 124 | exit_errno("strndup"); 125 | 126 | return ret; 127 | } 128 | -------------------------------------------------------------------------------- /src/arp.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of project l3roamd. It's copyrighted by the contributors 3 | * recorded in the version control history of the file, available from 4 | * its original location https://github.com/freifunk-gluon/l3roamd. 5 | * 6 | * SPDX-License-Identifier: BSD-2-Clause 7 | */ 8 | 9 | #include "arp.h" 10 | #include "l3roamd.h" 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include "util.h" 18 | 19 | void arp_handle_in(arp_ctx *ctx, int fd) { 20 | struct msghdr msghdr; 21 | memset(&msghdr, 0, sizeof(msghdr)); 22 | 23 | char cbuf[CMSG_SPACE(sizeof(int))]; 24 | 25 | struct arp_packet packet; 26 | 27 | struct iovec iov = {.iov_base = &packet, .iov_len = sizeof(packet)}; 28 | 29 | struct sockaddr_ll lladdr; 30 | 31 | struct msghdr hdr = {.msg_name = &lladdr, 32 | .msg_namelen = sizeof(lladdr), 33 | .msg_iov = &iov, 34 | .msg_iovlen = 1, 35 | .msg_control = cbuf, 36 | .msg_controllen = sizeof(cbuf)}; 37 | 38 | ssize_t rc = recvmsg(fd, &hdr, 0); 39 | 40 | if (rc == -1) 41 | return; 42 | 43 | log_debug("handling arp event\n"); 44 | 45 | if (packet.op != htons(ARP_REPLY)) 46 | return; 47 | 48 | if (memcmp(packet.spa, "\x00\x00\x00\x00", 4) == 0) // IP is 0.0.0.0 - not sensible to add that. 49 | return; 50 | 51 | uint8_t *mac = lladdr.sll_addr; 52 | 53 | struct in6_addr address = ctx->prefix; 54 | 55 | memcpy(&address.s6_addr[12], packet.spa, 4); 56 | 57 | char str[INET6_ADDRSTRLEN]; 58 | inet_ntop(AF_INET6, &address, str, INET6_ADDRSTRLEN); 59 | log_verbose("ARP Response from %s (MAC %s)\n", str, print_mac(mac)); 60 | 61 | clientmgr_add_address(&l3ctx.clientmgr_ctx, &address, packet.sha, ctx->ifindex); 62 | } 63 | 64 | void arp_send_request(arp_ctx *ctx, const struct in6_addr *addr) { 65 | struct arp_packet packet = {.hd = htons(1), .pr = htons(0x800), .hdl = 6, .prl = 4, .op = htons(ARP_REQUEST)}; 66 | 67 | memcpy(&packet.sha, ctx->mac, 6); 68 | memcpy(&packet.spa, "\x00\x00\x00\x00", 4); 69 | memcpy(&packet.dha, "\xff\xff\xff\xff\xff\xff", 6); 70 | memcpy(&packet.dpa, &addr->s6_addr[12], 4); 71 | 72 | log_verbose("Send ARP to %s\n", print_ip(addr)); 73 | 74 | struct sockaddr_ll dst = { 75 | .sll_ifindex = ctx->ifindex, .sll_protocol = htons(ETH_P_ARP), .sll_family = PF_PACKET, .sll_halen = 6, 76 | }; 77 | 78 | memcpy(&dst.sll_addr, "\xff\xff\xff\xff\xff\xff", 6); 79 | 80 | sendto(ctx->fd, &packet, sizeof(packet), 0, (struct sockaddr *)&dst, sizeof(dst)); 81 | } 82 | 83 | void arp_init(arp_ctx *ctx) { 84 | ctx->fd = socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_ARP)); 85 | if (ctx->fd < 0) 86 | exit_errno("Can't create socket(PF_PACKET)"); 87 | 88 | arp_setup_interface(ctx); 89 | }; 90 | 91 | void arp_setup_interface(arp_ctx *ctx) { 92 | obtain_mac_from_if(ctx->mac, ctx->clientif); 93 | ctx->ifindex = if_nametoindex(ctx->clientif); 94 | 95 | struct sockaddr_ll lladdr = { 96 | .sll_family = PF_PACKET, 97 | .sll_protocol = htons(ETH_P_ARP), 98 | .sll_ifindex = ctx->ifindex, 99 | .sll_hatype = 0, 100 | .sll_pkttype = PACKET_BROADCAST, 101 | .sll_halen = ETH_ALEN, 102 | }; 103 | 104 | while (bind(ctx->fd, (struct sockaddr *)&lladdr, sizeof(lladdr)) < 0) { 105 | perror("bind on arp fd failed, retrying"); 106 | } 107 | 108 | log_verbose("initialized arp-fd (%i) on interface with index: %i\n", ctx->fd, ctx->ifindex); 109 | } 110 | 111 | void arp_interface_changed(arp_ctx *ctx, int type, const struct ifinfomsg *msg) { 112 | char ifname[IFNAMSIZ]; 113 | 114 | if (if_indextoname(msg->ifi_index, ifname) == NULL) 115 | return; 116 | 117 | if (strcmp(ifname, ctx->clientif) != 0) 118 | return; 119 | 120 | log_verbose("arp interface change detected\n"); 121 | 122 | switch (type) { 123 | case RTM_NEWLINK: 124 | case RTM_SETLINK: 125 | log_verbose("arp interface changed - NEW or SET\n"); 126 | if (msg->ifi_index > 0 && ctx->ifindex != (size_t)msg->ifi_index) { 127 | log_verbose("re-initializing arp interface %s\n", ifname); 128 | arp_setup_interface(ctx); 129 | } 130 | break; 131 | 132 | case RTM_DELLINK: 133 | log_verbose("arp interfce not ok\n"); 134 | break; 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/arp.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of project l3roamd. It's copyrighted by the contributors 3 | * recorded in the version control history of the file, available from 4 | * its original location https://github.com/freifunk-gluon/l3roamd. 5 | * 6 | * SPDX-License-Identifier: BSD-2-Clause 7 | */ 8 | #pragma once 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include "common.h" 15 | 16 | #define ARP_REQUEST 1 17 | #define ARP_REPLY 2 18 | 19 | struct __attribute__((packed)) arp_packet { 20 | uint16_t hd; 21 | uint16_t pr; 22 | uint8_t hdl; 23 | uint8_t prl; 24 | uint16_t op; 25 | uint8_t sha[ETH_ALEN]; 26 | uint8_t spa[4]; 27 | uint8_t dha[ETH_ALEN]; 28 | uint8_t dpa[4]; 29 | }; 30 | 31 | typedef struct { 32 | struct in6_addr prefix; 33 | char *clientif; 34 | unsigned int ifindex; 35 | int fd; 36 | uint8_t mac[ETH_ALEN]; 37 | } arp_ctx; 38 | 39 | void arp_handle_in(arp_ctx *ctx, int fd); 40 | void arp_send_request(arp_ctx *ctx, const struct in6_addr *addr); 41 | void arp_init(arp_ctx *ctx); 42 | void arp_interface_changed(arp_ctx *ctx, int type, const struct ifinfomsg *msg); 43 | void arp_setup_interface(arp_ctx *ctx); 44 | -------------------------------------------------------------------------------- /src/clientmgr.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017,2018, Christof Schulze 3 | * 4 | * This file is part of project l3roamd. It's copyrighted by the contributors 5 | * recorded in the version control history of the file, available from 6 | * its original location https://github.com/freifunk-gluon/l3roamd. 7 | * 8 | * SPDX-License-Identifier: BSD-2-Clause 9 | */ 10 | #include "clientmgr.h" 11 | #include "error.h" 12 | #include "icmp6.h" 13 | #include "ipmgr.h" 14 | #include "l3roamd.h" 15 | #include "prefix.h" 16 | #include "routemgr.h" 17 | #include "timespec.h" 18 | #include "util.h" 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | static const char *state_str(enum ip_state state); 28 | 29 | // struct in6_addr node_client_mcast_ip_from_mac(uint8_t mac[ETH_ALEN]) { 30 | // char addr_str[INET6_ADDRSTRLEN]; 31 | // snprintf(&addr_str[0], INET6_ADDRSTRLEN, "ff02::1:ff%02x:%02x%02x", 32 | // mac[3], mac[4], mac[5]); 33 | //} 34 | 35 | // generate mac-based ipv6-address in prefix link-local-address-style 36 | struct in6_addr mac2ipv6(uint8_t mac[ETH_ALEN], struct prefix *prefix) { 37 | struct in6_addr address = prefix->prefix; 38 | 39 | address.s6_addr[8] = mac[0] ^ 0x02; 40 | address.s6_addr[9] = mac[1]; 41 | address.s6_addr[10] = mac[2]; 42 | address.s6_addr[11] = 0xff; 43 | address.s6_addr[12] = 0xfe; 44 | address.s6_addr[13] = mac[3]; 45 | address.s6_addr[14] = mac[4]; 46 | address.s6_addr[15] = mac[5]; 47 | 48 | return address; 49 | } 50 | 51 | char *print_client(struct client *client) { 52 | // re-using the string-buffer from util.c. I am not sure I really do like this. 53 | int maxlength = STRBUFLEN; 54 | char tmpbuf[maxlength]; 55 | int offset = 0; 56 | extern union buffer strbuffer; 57 | 58 | offset += snprintf(&tmpbuf[offset], maxlength - offset, "Client %s %s", print_mac(client->mac), 59 | client_is_active(client) ? "(active)" : "(______)"); 60 | 61 | if (client->ifindex != 0) { 62 | char ifname[IFNAMSIZ]; 63 | if_indextoname(client->ifindex, ifname); 64 | offset += snprintf(&tmpbuf[offset], maxlength - offset, " on %s (%i)\n", ifname, client->ifindex); 65 | } else { 66 | offset += snprintf(&tmpbuf[offset], maxlength - offset, "\n"); 67 | } 68 | 69 | for (int i = VECTOR_LEN(client->addresses) - 1; i >= 0; i--) { 70 | struct client_ip *addr = &VECTOR_INDEX(client->addresses, i); 71 | 72 | switch (addr->state) { 73 | case IP_INACTIVE: 74 | offset += snprintf(&tmpbuf[offset], maxlength - offset, " %s %s\n", 75 | state_str(addr->state), print_ip(&addr->addr)); 76 | break; 77 | case IP_ACTIVE: 78 | offset += snprintf(&tmpbuf[offset], maxlength - offset, " %s %s (%s)\n", 79 | state_str(addr->state), print_ip(&addr->addr), 80 | print_timespec(&addr->timestamp)); 81 | break; 82 | case IP_TENTATIVE: 83 | offset += snprintf(&tmpbuf[offset], maxlength - offset, " %s %s (tries left: %d)\n", 84 | state_str(addr->state), print_ip(&addr->addr), 85 | addr->tentative_retries_left); 86 | break; 87 | default: 88 | exit_bug("Invalid IP state %i- exiting due to memory corruption", addr->state); 89 | } 90 | } 91 | 92 | memcpy(strbuffer.allofit, tmpbuf, STRBUFLEN); 93 | return strbuffer.allofit; 94 | } 95 | 96 | bool ip_is_active(const struct client_ip *ip) { 97 | if (ip->state == IP_ACTIVE || ip->state == IP_TENTATIVE) 98 | return true; 99 | return false; 100 | } 101 | 102 | /** Check whether a client is currently active. 103 | A client is considered active when at least one of its IP addresses is 104 | currently active or tentative. 105 | */ 106 | bool client_is_active(const struct client *client) { 107 | for (int i = VECTOR_LEN(client->addresses) - 1; i >= 0; i--) { 108 | struct client_ip *ip = &VECTOR_INDEX(client->addresses, i); 109 | 110 | if (ip_is_active(ip)) { 111 | return true; 112 | } 113 | } 114 | 115 | return false; 116 | } 117 | 118 | int bind_to_address(struct in6_addr *address) { 119 | int fd; 120 | rtnl_add_address(&l3ctx.routemgr_ctx, address); 121 | 122 | struct sockaddr_in6 server_addr = { 123 | .sin6_family = AF_INET6, .sin6_port = htons(INTERCOM_PORT), .sin6_addr = *address, 124 | }; 125 | 126 | fd = socket(PF_INET6, SOCK_DGRAM | SOCK_NONBLOCK, 0); 127 | if (fd < 0) { 128 | perror("creating socket for listening on node-client-IP failed:"); 129 | exit_error("creating socket for intercom on node-client-IP"); 130 | } 131 | 132 | int one = 1; 133 | if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) < 0) { 134 | perror("could not set SO_REUSEADDR"); 135 | exit_error("setsockopt: SO_REUSEADDR"); 136 | } 137 | if (setsockopt(fd, SOL_IP, IP_FREEBIND, &one, sizeof(one)) < 0) { 138 | perror("could not set IP_FREEBIND"); 139 | exit_error("setsockopt: IP_FREEBIND"); 140 | } 141 | 142 | if (bind(fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) { 143 | fprintf(stderr, "Could not bind to socket %i on special ip for address: %s. exiting.\n", fd, 144 | print_ip(address)); 145 | exit_error("bind socket to node-client-IP failed."); 146 | } 147 | 148 | add_fd(l3ctx.efd, fd, EPOLLIN); 149 | return fd; 150 | } 151 | 152 | /** Adds the special node client IP address. 153 | */ 154 | void add_special_ip(clientmgr_ctx *ctx, struct client *client) { 155 | if (client == NULL) // this can happen if the client was removed before the claim cycle was finished 156 | return; 157 | 158 | if (client->node_ip_initialized) { 159 | log_error("we already initialized the special client [%s] not doing it again\n", 160 | print_mac(client->mac)); 161 | return; 162 | } 163 | 164 | struct in6_addr address = mac2ipv6(client->mac, &ctx->node_client_prefix); 165 | client->fd = bind_to_address(&address); 166 | 167 | client->node_ip_initialized = true; 168 | } 169 | 170 | /** close and remove an fd from a client 171 | **/ 172 | void close_client_fd(int *fd) { 173 | log_debug("closing client fd %i\n", *fd); 174 | 175 | if (*fd > 0) { 176 | del_fd(l3ctx.efd, *fd); 177 | while (close(*fd)) { 178 | fprintf(stderr, "could not close client fd %i", *fd); 179 | perror("close "); 180 | } 181 | *fd = -1; 182 | } 183 | } 184 | 185 | /** Removes the special node client IP address. 186 | */ 187 | void remove_special_ip(clientmgr_ctx *ctx, struct client *client) { 188 | struct in6_addr address = mac2ipv6(client->mac, &ctx->node_client_prefix); 189 | log_verbose("Removing special address: %s\n", print_ip(&address)); 190 | close_client_fd(&client->fd); 191 | routemgr_remove_route(&l3ctx.routemgr_ctx, ctx->export_table, &address, 128); 192 | rtnl_remove_address(&l3ctx.routemgr_ctx, &address); 193 | // remove route 194 | client->node_ip_initialized = false; 195 | } 196 | 197 | /** Given an IP address returns the client-object of a client. 198 | Returns NULL if no object is found. 199 | */ 200 | struct client_ip *get_client_ip(struct client *client, const struct in6_addr *address) { 201 | for (int i = VECTOR_LEN(client->addresses) - 1; i >= 0; i--) { 202 | struct client_ip *e = &VECTOR_INDEX(client->addresses, i); 203 | 204 | if (memcmp(address, &e->addr, sizeof(struct in6_addr)) == 0) 205 | return e; 206 | } 207 | 208 | return NULL; 209 | } 210 | 211 | /** Removes an IP address from a client. Safe to call if the IP is not 212 | currently present in the clients list. 213 | */ 214 | void delete_client_ip(struct client *client, const struct in6_addr *address, bool cleanup) { 215 | for (int i = VECTOR_LEN(client->addresses) - 1; i >= 0; i--) { 216 | struct client_ip *e = &VECTOR_INDEX(client->addresses, i); 217 | 218 | if (memcmp(address, &e->addr, sizeof(struct in6_addr)) == 0) { 219 | client_ip_set_state(&l3ctx.clientmgr_ctx, client, get_client_ip(client, address), IP_INACTIVE); 220 | if (cleanup) 221 | VECTOR_DELETE(client->addresses, i); 222 | } 223 | } 224 | 225 | log_verbose("\x1b[31mDeleted IP %s from client, %zi addresses are still assigned\x1b[0m ", print_ip(address), 226 | VECTOR_LEN(client->addresses)); 227 | } 228 | 229 | /** Adds a route and a neighbour entry 230 | */ 231 | static void client_add_route(clientmgr_ctx *ctx, struct client *client, struct client_ip *ip) { 232 | if (address_is_ipv4(&ip->addr)) { 233 | struct in_addr ip4 = extractv4_v6(&ip->addr); 234 | log_verbose("Adding neighbor and route for IP (IPv4): %s\n", print_ip4(&ip4)); 235 | routemgr_insert_neighbor4(&l3ctx.routemgr_ctx, client->ifindex, &ip4, client->mac); 236 | routemgr_insert_route4(&l3ctx.routemgr_ctx, ctx->export_table, client->ifindex, &ip4, 32); 237 | } else { 238 | log_verbose("Adding neighbour and route for IP (IPv6) %s\n", print_ip(&ip->addr)); 239 | routemgr_insert_neighbor(&l3ctx.routemgr_ctx, client->ifindex, &ip->addr, client->mac); 240 | routemgr_insert_route(&l3ctx.routemgr_ctx, ctx->export_table, client->ifindex, &ip->addr, 128); 241 | } 242 | } 243 | 244 | /** Remove a route. 245 | */ 246 | static void client_remove_route(clientmgr_ctx *ctx, struct client *client, struct client_ip *ip) { 247 | if (address_is_ipv4(&ip->addr)) { 248 | struct in_addr ip4 = extractv4_v6(&ip->addr); 249 | routemgr_remove_route4(&l3ctx.routemgr_ctx, ctx->export_table, &ip4, 32); 250 | routemgr_remove_neighbor4(&l3ctx.routemgr_ctx, client->ifindex, &ip4, client->mac); 251 | } else { 252 | routemgr_remove_route(&l3ctx.routemgr_ctx, ctx->export_table, &ip->addr, 128); 253 | routemgr_remove_neighbor(&l3ctx.routemgr_ctx, client->ifindex, &ip->addr, client->mac); 254 | } 255 | } 256 | 257 | /** Given a MAC address returns a client object. 258 | Returns NULL if the client is not known. 259 | */ 260 | struct client *findinvector(void *_vector, const uint8_t mac[ETH_ALEN]) { 261 | VECTOR(struct client) *vector = _vector; 262 | for (int _vector_index = VECTOR_LEN(*vector) - 1; _vector_index >= 0; _vector_index--) { 263 | struct client *e = &VECTOR_INDEX(*vector, _vector_index); 264 | 265 | if (memcmp(mac, e->mac, sizeof(uint8_t) * 6) == 0) 266 | return e; 267 | } 268 | 269 | return NULL; 270 | } 271 | 272 | struct client *get_client(const uint8_t mac[ETH_ALEN]) { 273 | return findinvector(&l3ctx.clientmgr_ctx.clients, mac); 274 | } 275 | 276 | struct client *get_client_old(const uint8_t mac[ETH_ALEN]) { 277 | return findinvector(&l3ctx.clientmgr_ctx.oldclients, mac); 278 | } 279 | 280 | /** Given an ip-address, this returns true if there is a local client connected 281 | * having this IP-address and false otherwise 282 | */ 283 | bool clientmgr_is_known_address(clientmgr_ctx *ctx, const struct in6_addr *address, struct client **client) { 284 | for (int i = VECTOR_LEN(ctx->clients) - 1; i >= 0; i--) { 285 | struct client *c = &VECTOR_INDEX(ctx->clients, i); 286 | 287 | for (int j = VECTOR_LEN(c->addresses) - 1; j >= 0; j--) { 288 | struct client_ip *a = &VECTOR_INDEX(c->addresses, j); 289 | if (!memcmp(address, &a->addr, sizeof(struct in6_addr))) { 290 | log_debug("%s is attached to local client %s\n", print_ip(address), print_mac(c->mac)); 291 | 292 | if (client) { 293 | *client = c; 294 | } 295 | return true; 296 | } 297 | } 298 | } 299 | 300 | log_debug("%s is not attached to any of the local clients\n", print_ip(address)); 301 | 302 | return false; 303 | } 304 | 305 | struct client *create_client(client_vector *vector, const uint8_t mac[ETH_ALEN], const unsigned int ifindex) { 306 | struct client _client = {}; 307 | memcpy(_client.mac, mac, sizeof(uint8_t) * 6); 308 | _client.ifindex = ifindex; 309 | _client.node_ip_initialized = false; 310 | _client.platprefix = l3ctx.clientmgr_ctx.platprefix; 311 | _client.claimed = false; 312 | VECTOR_INIT(_client.addresses); 313 | VECTOR_ADD(*vector, _client); 314 | struct client *client = &VECTOR_INDEX(*vector, VECTOR_LEN(*vector) - 1); 315 | 316 | return client; 317 | } 318 | 319 | /** Get a client or create a new, empty one. 320 | */ 321 | struct client *get_or_create_client(const uint8_t mac[ETH_ALEN], unsigned int ifindex) { 322 | struct client *client = get_client(mac); 323 | 324 | if (client == NULL) { 325 | client = create_client(&l3ctx.clientmgr_ctx.clients, mac, ifindex); 326 | } 327 | 328 | return client; 329 | } 330 | 331 | /** Remove all client routes - used when exiting l3roamd 332 | **/ 333 | void clientmgr_purge_clients(clientmgr_ctx *ctx) { 334 | struct client *client; 335 | 336 | for (int i = VECTOR_LEN(ctx->clients) - 1; i >= 0; i--) { 337 | client = &VECTOR_INDEX(ctx->clients, i); 338 | clientmgr_delete_client(ctx, client->mac); 339 | } 340 | } 341 | 342 | void client_copy_to_old(struct client *client) { 343 | struct timespec then; 344 | struct timespec now; 345 | clock_gettime(CLOCK_MONOTONIC, &now); 346 | then.tv_sec = now.tv_sec + OLDCLIENTS_KEEP_SECONDS; 347 | then.tv_nsec = now.tv_nsec; 348 | 349 | struct client *_client = create_client(&l3ctx.clientmgr_ctx.oldclients, client->mac, client->ifindex); 350 | _client->timeout = then; 351 | _client->platprefix = client->platprefix; 352 | 353 | for (int i = VECTOR_LEN(client->addresses) - 1; i >= 0; i--) { 354 | VECTOR_ADD(_client->addresses, VECTOR_INDEX(client->addresses, i)); 355 | } 356 | 357 | log_debug("copied client[%s] to old-queue:\n", print_client(client)); 358 | } 359 | 360 | /** This will set all IP addresses of the client to IP_INACTIVE and remove the 361 | * special IP & route 362 | */ 363 | void client_deactivate(struct client *client) { 364 | struct client *_client = get_client(client->mac); 365 | client_copy_to_old(_client); 366 | for (int i = VECTOR_LEN(_client->addresses) - 1; i >= 0; i--) { 367 | struct client_ip *ip = &VECTOR_INDEX(_client->addresses, i); 368 | if (ip) 369 | client_ip_set_state(&l3ctx.clientmgr_ctx, _client, ip, IP_INACTIVE); 370 | } 371 | VECTOR_FREE(_client->addresses); 372 | remove_special_ip(&l3ctx.clientmgr_ctx, _client); 373 | } 374 | 375 | /** Given a MAC address deletes a client. Safe to call if the client is not 376 | known. 377 | */ 378 | void clientmgr_delete_client(clientmgr_ctx *ctx, uint8_t mac[ETH_ALEN]) { 379 | struct client *client = get_client(mac); 380 | 381 | if (!client) { 382 | log_debug("Client [%s] unknown: cannot delete\n", print_mac(mac)); 383 | return; 384 | } 385 | 386 | log_verbose("\033[34mREMOVING client %s and invalidating its IP-addresses\033[0m\n", print_client(client)); 387 | 388 | intercom_remove_claim(&l3ctx.intercom_ctx, client); 389 | 390 | client_copy_to_old(client); 391 | 392 | remove_special_ip(ctx, client); 393 | 394 | if (VECTOR_LEN(client->addresses) > 0) { 395 | for (int i = VECTOR_LEN(client->addresses) - 1; i >= 0; i--) { 396 | struct client_ip *e = &VECTOR_INDEX(client->addresses, i); 397 | client_ip_set_state(&l3ctx.clientmgr_ctx, client, e, IP_INACTIVE); 398 | delete_client_ip(client, &e->addr, true); 399 | } 400 | } 401 | VECTOR_FREE(client->addresses); 402 | 403 | for (int i = VECTOR_LEN(ctx->clients) - 1; i >= 0; i--) { 404 | if (memcmp(&(VECTOR_INDEX(ctx->clients, i).mac), mac, sizeof(uint8_t) * 6) == 0) { 405 | VECTOR_DELETE(ctx->clients, i); 406 | break; 407 | } 408 | } 409 | } 410 | 411 | const char *state_str(enum ip_state state) { 412 | switch (state) { 413 | case IP_INACTIVE: 414 | return "\x1b[31mINACTIVE\x1b[0m"; 415 | case IP_ACTIVE: 416 | return "\x1b[32mACTIVE\x1b[0m"; 417 | case IP_TENTATIVE: 418 | return "\x1b[33mTENTATIVE\x1b[0m"; 419 | default: 420 | return "\x1b[35m(INVALID)\x1b[0m"; 421 | } 422 | } 423 | 424 | /** Change state of an IP address. Trigger all side effects like resetting 425 | counters, timestamps and route changes. 426 | */ 427 | void client_ip_set_state(clientmgr_ctx *ctx, struct client *client, struct client_ip *ip, enum ip_state state) { 428 | struct timespec now; 429 | clock_gettime(CLOCK_MONOTONIC, &now); 430 | bool nop = false; 431 | 432 | switch (ip->state) { 433 | case IP_INACTIVE: 434 | switch (state) { 435 | case IP_INACTIVE: 436 | nop = true; 437 | break; 438 | case IP_ACTIVE: 439 | client_add_route(ctx, client, ip); 440 | ip->timestamp = now; 441 | break; 442 | case IP_TENTATIVE: 443 | ip->timestamp = now; 444 | ipmgr_seek_address(&ip->addr); 445 | break; 446 | } 447 | break; 448 | case IP_ACTIVE: 449 | switch (state) { 450 | case IP_INACTIVE: 451 | ip->timestamp = now; 452 | client_remove_route(ctx, client, ip); 453 | break; 454 | case IP_ACTIVE: 455 | nop = true; 456 | ip->timestamp = now; 457 | break; 458 | case IP_TENTATIVE: 459 | ip->timestamp = now; 460 | ipmgr_seek_address(&ip->addr); 461 | break; 462 | } 463 | break; 464 | case IP_TENTATIVE: 465 | switch (state) { 466 | case IP_INACTIVE: 467 | ip->timestamp = now; 468 | client_remove_route(ctx, client, ip); 469 | break; 470 | case IP_ACTIVE: 471 | ip->timestamp = now; 472 | client_add_route(ctx, client, ip); 473 | break; 474 | case IP_TENTATIVE: 475 | nop = true; 476 | ip->timestamp = now; 477 | break; 478 | } 479 | break; 480 | } 481 | 482 | if (!nop || l3ctx.debug) 483 | log_error("%s changes from %s to %s\n", print_ip(&ip->addr), state_str(ip->state), state_str(state)); 484 | 485 | ip->state = state; 486 | } 487 | 488 | /** Check whether an IP address is contained in a client prefix. 489 | */ 490 | bool clientmgr_valid_address(clientmgr_ctx *ctx, const struct in6_addr *address) { 491 | for (int i = VECTOR_LEN(ctx->prefixes) - 1; i >= 0; i--) { 492 | struct prefix *_prefix = &VECTOR_INDEX(ctx->prefixes, i); 493 | if (prefix_contains(_prefix, address)) { 494 | log_debug("Address %s is in the client prefix %s\n", print_ip(address), print_ip(&_prefix->prefix)); 495 | return true; 496 | } 497 | } 498 | 499 | log_debug("Address %s is not found to be in any of the client prefixes\n", print_ip(address)); 500 | 501 | return false; 502 | } 503 | 504 | /** Remove an address from a client identified by its MAC. 505 | **/ 506 | void clientmgr_remove_address(clientmgr_ctx *ctx, struct client *client, struct in6_addr *address) { 507 | log_debug("clientmgr_remove_address: %s is running for client %s\n", print_ip(address), print_mac(client->mac)); 508 | 509 | if (client) { 510 | delete_client_ip(client, address, true); 511 | } 512 | 513 | if (!client_is_active(client)) { 514 | log_verbose("no active IP-addresses left in client. Deleting client. %s\n", print_mac(client->mac)); 515 | clientmgr_delete_client(ctx, client->mac); 516 | } 517 | } 518 | 519 | void cancel_client_neigh_removal(struct client_ip *ip) { 520 | if (ip->removal_task) { 521 | log_debug("cancelling ip removal for %s\n", print_ip(&ip->addr)); 522 | drop_task(ip->removal_task); 523 | ip->removal_task = NULL; 524 | } 525 | } 526 | 527 | /** Add a new address to a client identified by its MAC. 528 | */ 529 | void clientmgr_add_address(clientmgr_ctx *ctx, const struct in6_addr *address, const uint8_t *mac, 530 | const unsigned int ifindex) { 531 | if (!clientmgr_valid_address(ctx, address)) { 532 | log_debug("address is not within a client-prefix and not an ll-address. Not adding %s to client %s\n", 533 | print_ip(address), print_mac(mac)); 534 | return; 535 | } 536 | 537 | if (l3ctx.debug) { 538 | char ifname[IFNAMSIZ]; 539 | if_indextoname(ifindex, ifname); 540 | log_debug("clientmgr_add_address: %s [%s] is running for interface %s(%i)\n", print_ip(address), 541 | print_mac(mac), ifname, ifindex); 542 | } 543 | 544 | struct client *client = get_or_create_client(mac, ifindex); 545 | struct client_ip *ip = get_client_ip(client, address); 546 | 547 | client->ifindex = ifindex; // client might have roamed to different interface on the same node 548 | 549 | bool ip_is_new = ip == NULL; 550 | 551 | if (ip_is_new) { 552 | struct client_ip _ip = {}; 553 | memcpy(&_ip.addr, address, sizeof(struct in6_addr)); 554 | ip = VECTOR_ADD(client->addresses, _ip); 555 | log_verbose("created new client: %s\n", print_client(client)); 556 | } else { 557 | cancel_client_neigh_removal(ip); 558 | } 559 | 560 | client_ip_set_state(ctx, client, ip, IP_ACTIVE); 561 | 562 | if (!client->claimed) { 563 | struct in6_addr address = mac2ipv6(client->mac, &ctx->node_client_prefix); 564 | if (has_host_route(&address)) 565 | intercom_claim(&l3ctx.intercom_ctx, &address, client); // this will set the special_ip after claiming 566 | else 567 | add_special_ip(ctx, client); 568 | 569 | client->claimed = true; 570 | } 571 | } 572 | 573 | /** Notify the client manager about a new MAC (e.g. a new wifi client). 574 | */ 575 | void clientmgr_notify_mac(clientmgr_ctx *ctx, uint8_t *mac, unsigned int ifindex) { 576 | if (memcmp(mac, "\x00\x00\x00\x00\x00\x00", 6) == 0) 577 | return; 578 | 579 | struct client *client = get_or_create_client(mac, ifindex); 580 | 581 | if (client->claimed || client_is_active(client)) { 582 | log_debug("client[%s] was detected earlier, not re-adding\n", print_mac(client->mac)); 583 | return; 584 | } 585 | 586 | char ifname[IFNAMSIZ]; 587 | if_indextoname(ifindex, ifname); 588 | 589 | log_verbose("\033[34mnew client %s on %s\033[0m\n", print_mac(client->mac), ifname); 590 | 591 | client->ifindex = ifindex; 592 | 593 | struct in6_addr address = mac2ipv6(client->mac, &ctx->node_client_prefix); 594 | 595 | if (!client->claimed && has_host_route(&address)) 596 | intercom_claim(&l3ctx.intercom_ctx, &address, client); 597 | else 598 | add_special_ip(ctx, client); 599 | 600 | client->claimed = true; 601 | 602 | for (int i = VECTOR_LEN(client->addresses) - 1; i >= 0; i--) { 603 | struct client_ip *ip = &VECTOR_INDEX(client->addresses, i); 604 | 605 | if (ip->state == IP_TENTATIVE || ip->state == IP_INACTIVE) 606 | client_ip_set_state(ctx, client, ip, IP_TENTATIVE); 607 | } 608 | 609 | // prefix does not matter here, icmp6_send_solicitation will overwrite 610 | // the first 13 bytes of the address. 611 | icmp6_send_solicitation(&l3ctx.icmp6_ctx, &address); 612 | } 613 | 614 | void free_client_addresses(struct client *client) { 615 | if (VECTOR_LEN(client->addresses) > 0) { 616 | for (int i = VECTOR_LEN(client->addresses) - 1; i >= 0; i--) { 617 | VECTOR_DELETE(client->addresses, i); 618 | } 619 | } 620 | } 621 | 622 | void purge_oldclientlist_from_old_clients() { 623 | struct client *_client = NULL; 624 | struct timespec now; 625 | clock_gettime(CLOCK_MONOTONIC, &now); 626 | 627 | log_debug("Purging old clients\n"); 628 | 629 | for (int i = VECTOR_LEN(l3ctx.clientmgr_ctx.oldclients) - 1; i >= 0; i--) { 630 | _client = &VECTOR_INDEX(l3ctx.clientmgr_ctx.oldclients, i); 631 | 632 | if (timespec_cmp(_client->timeout, now) <= 0) { 633 | log_debug("removing client from old-queue: %s\n", print_client(_client)); 634 | 635 | free_client_addresses(_client); 636 | VECTOR_DELETE(l3ctx.clientmgr_ctx.oldclients, i); 637 | } 638 | } 639 | } 640 | 641 | void purge_oldclients_task() { 642 | purge_oldclientlist_from_old_clients(); 643 | 644 | post_task(&l3ctx.taskqueue_ctx, OLDCLIENTS_KEEP_SECONDS, 0, purge_oldclients_task, NULL, NULL); 645 | } 646 | 647 | /** Handle claim (info request). return true if we acted on a local client, 648 | * false otherwise 649 | */ 650 | bool clientmgr_handle_claim(clientmgr_ctx *ctx, const struct in6_addr *sender, uint8_t mac[ETH_ALEN]) { 651 | bool old = false; 652 | struct client *client = get_client(mac); 653 | if (client == NULL) { 654 | client = get_client_old(mac); 655 | old = true; 656 | } 657 | 658 | log_debug("handling claim for %sclient %s\n", old ? "old " : " ", client ? print_client(client) : "unknown"); 659 | 660 | if (!client) 661 | return false; 662 | 663 | intercom_info(&l3ctx.intercom_ctx, sender, client); 664 | 665 | if (!old) { 666 | log_verbose("Dropping client %s in response to claim from sender %s\n", print_mac(mac), print_ip(sender)); 667 | clientmgr_delete_client(ctx, client->mac); 668 | } 669 | return true; 670 | } 671 | 672 | /** Handle incoming client info. return true if we acted on local client, false 673 | * otherwise 674 | */ 675 | bool clientmgr_handle_info(clientmgr_ctx *ctx, struct client *foreign_client) { 676 | struct client *client = get_client(foreign_client->mac); 677 | if (client == NULL) { 678 | log_debug( 679 | "received info message for client %s but client is not " 680 | "locally connected - discarding message\n", 681 | print_mac(foreign_client->mac)); 682 | return false; 683 | } 684 | 685 | log_debug("handling info message in clientmgr_handle_info() for foreign_client %s\n", 686 | print_client(foreign_client)); 687 | 688 | for (int i = VECTOR_LEN(foreign_client->addresses) - 1; i >= 0; i--) { 689 | struct client_ip *foreign_ip = &VECTOR_INDEX(foreign_client->addresses, i); 690 | bool is_client_ip_known = get_client_ip(client, &foreign_ip->addr); 691 | 692 | if (!is_client_ip_known) 693 | continue; 694 | 695 | clientmgr_add_address(ctx, &foreign_ip->addr, foreign_client->mac, l3ctx.icmp6_ctx.ifindex); 696 | } 697 | 698 | add_special_ip(ctx, client); 699 | 700 | log_verbose("Client information merged into local client %s\n", print_client(client)); 701 | return true; 702 | } 703 | 704 | static void query_route(int fd, const int ifindex, struct in6_addr *address, const int prefix_length) { 705 | struct nlrtreq req = { 706 | .nl = { 707 | .nlmsg_type = RTM_GETROUTE, 708 | .nlmsg_flags = NLM_F_REQUEST, 709 | .nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)), 710 | }, 711 | .rt = { 712 | .rtm_family = AF_INET6, 713 | .rtm_dst_len = prefix_length, 714 | .rtm_src_len = 0, 715 | .rtm_tos = 0, 716 | .rtm_protocol = RTPROT_UNSPEC, 717 | .rtm_scope = RT_SCOPE_UNIVERSE, 718 | .rtm_type = RTN_UNSPEC, 719 | .rtm_table = RT_TABLE_UNSPEC, 720 | .rtm_flags = RTM_F_LOOKUP_TABLE | 0x2000 /* fibmatch */ 721 | }, 722 | }; 723 | 724 | rtnl_addattr(&req.nl, sizeof(req), RTA_DST, (void *)address, sizeof(struct in6_addr)); 725 | 726 | if (ifindex > 0 ) 727 | rtnl_addattr(&req.nl, sizeof(req), RTA_OIF, (void *)&ifindex, sizeof(ifindex)); 728 | 729 | rtmgr_rtnl_talk(fd, (struct nlmsghdr *)&req); 730 | } 731 | 732 | bool has_host_route(struct in6_addr *addr) { 733 | int nlfd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); 734 | if (nlfd < 0) { 735 | perror("can't open RTNL socket"); 736 | return NULL; 737 | } 738 | struct sockaddr_nl snl = { 739 | .nl_family = AF_NETLINK, .nl_groups = RTMGRP_IPV6_ROUTE, 740 | }; 741 | 742 | if (bind(nlfd, (struct sockaddr *)&snl, sizeof(snl)) < 0) 743 | perror("can't bind RTNL socket"); 744 | 745 | query_route(nlfd, 0, addr, 128); 746 | 747 | uint8_t readbuffer[8192]; 748 | int count = recv(nlfd, readbuffer, sizeof(readbuffer), 0); 749 | 750 | struct nlmsghdr *nh; 751 | struct kernel_route route; 752 | bool ret = false; 753 | 754 | nh = (struct nlmsghdr *)readbuffer; 755 | if (NLMSG_OK(nh, count)) { 756 | switch (nh->nlmsg_type) { 757 | case NLMSG_DONE: 758 | break; 759 | case NLMSG_ERROR: 760 | ret = false; 761 | break; 762 | default: 763 | ret = false; 764 | if (nh->nlmsg_type == RTM_NEWROUTE) 765 | if (handle_kernel_routes(nh, &route)) 766 | ret = ( route.metric < KERNEL_INFINITY); 767 | break; 768 | } 769 | } 770 | 771 | close(nlfd); 772 | return ret; 773 | } 774 | 775 | void clientmgr_init() { 776 | VECTOR_INIT((&l3ctx.clientmgr_ctx)->clients); 777 | VECTOR_INIT((&l3ctx.clientmgr_ctx)->oldclients); 778 | post_task(&l3ctx.taskqueue_ctx, OLDCLIENTS_KEEP_SECONDS, 0, purge_oldclients_task, NULL, NULL); 779 | } 780 | 781 | int client_compare_by_mac(const struct client *a, const struct client *b) { return memcmp(&a->mac, &b->mac, ETH_ALEN); } 782 | -------------------------------------------------------------------------------- /src/clientmgr.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of project l3roamd. It's copyrighted by the contributors 3 | * recorded in the version control history of the file, available from 4 | * its original location https://github.com/freifunk-gluon/l3roamd. 5 | * 6 | * SPDX-License-Identifier: BSD-2-Clause 7 | */ 8 | #pragma once 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include "common.h" 15 | #include "prefix.h" 16 | #include "vector.h" 17 | #include "taskqueue.h" 18 | 19 | #define OLDCLIENTS_KEEP_SECONDS 5 * 60 20 | 21 | enum ip_state { 22 | IP_INACTIVE = 0, // ip address is known but not in use 23 | IP_ACTIVE, // address is in used 24 | IP_TENTATIVE // address was received info on intercom OR belongs to a 25 | // re-activated local client 26 | }; 27 | 28 | typedef VECTOR(struct client) client_vector; 29 | 30 | struct client_ip { 31 | struct in6_addr addr; 32 | struct timespec timestamp; 33 | uint8_t tentative_retries_left; 34 | enum ip_state state; 35 | taskqueue_t *removal_task; 36 | }; 37 | 38 | typedef struct client { 39 | struct in6_addr platprefix; 40 | struct timespec timeout; 41 | VECTOR(struct client_ip) addresses; 42 | int fd; 43 | unsigned int ifindex; 44 | bool node_ip_initialized; 45 | bool claimed; 46 | uint8_t mac[ETH_ALEN]; 47 | } client_t; 48 | 49 | typedef struct { 50 | struct prefix node_client_prefix; 51 | struct prefix v4prefix; 52 | struct in6_addr platprefix; 53 | VECTOR(struct prefix) prefixes; 54 | client_vector clients; 55 | client_vector oldclients; 56 | unsigned int export_table; 57 | int nat46ifindex; 58 | bool platprefix_set; 59 | } clientmgr_ctx; 60 | 61 | struct client_task { 62 | clientmgr_ctx *ctx; 63 | uint8_t mac[ETH_ALEN]; 64 | }; 65 | 66 | char *print_client(struct client *client); 67 | bool clientmgr_valid_address(clientmgr_ctx *ctx, const struct in6_addr *ip); 68 | void clientmgr_add_address(clientmgr_ctx *ctx, const struct in6_addr *address, const uint8_t *mac, 69 | const unsigned int ifindex); 70 | void clientmgr_remove_address(clientmgr_ctx *ctx, struct client *client, struct in6_addr *address); 71 | void clientmgr_notify_mac(clientmgr_ctx *ctx, uint8_t *mac, unsigned int ifindex); 72 | bool clientmgr_handle_claim(clientmgr_ctx *ctx, const struct in6_addr *sender, uint8_t mac[ETH_ALEN]); 73 | bool clientmgr_handle_info(clientmgr_ctx *ctx, struct client *foreign_client); 74 | void clientmgr_purge_clients(clientmgr_ctx *ctx); 75 | void clientmgr_delete_client(clientmgr_ctx *ctx, uint8_t mac[ETH_ALEN]); 76 | void client_ip_set_state(clientmgr_ctx *ctx, struct client *client, struct client_ip *ip, enum ip_state state); 77 | struct client *get_client(const uint8_t mac[ETH_ALEN]); 78 | bool clientmgr_is_known_address(clientmgr_ctx *ctx, const struct in6_addr *address, struct client **client); 79 | void add_special_ip(clientmgr_ctx *ctx, struct client *client); 80 | struct client_ip *get_client_ip(struct client *client, const struct in6_addr *address); 81 | struct in6_addr mac2ipv6(uint8_t mac[ETH_ALEN], struct prefix *prefix); 82 | void clientmgr_init(); 83 | bool client_is_active(const struct client *client); 84 | bool ip_is_active(const struct client_ip *ip); 85 | 86 | int client_compare_by_mac(const client_t *a, const client_t *b); 87 | bool has_host_route(struct in6_addr *addr); 88 | 89 | -------------------------------------------------------------------------------- /src/common.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of project l3roamd. It's copyrighted by the contributors 3 | * recorded in the version control history of the file, available from 4 | * its original location https://github.com/freifunk-gluon/l3roamd. 5 | * 6 | * SPDX-License-Identifier: BSD-2-Clause 7 | */ 8 | #pragma once 9 | 10 | #define ETH_ALEN 6 11 | -------------------------------------------------------------------------------- /src/config.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of project l3roamd. It's copyrighted by the contributors 3 | * recorded in the version control history of the file, available from 4 | * its original location https://github.com/freifunk-gluon/l3roamd. 5 | * 6 | * SPDX-License-Identifier: BSD-2-Clause 7 | */ 8 | #include "config.h" 9 | void parse_line(char *line) { 10 | char delimiter[] = " "; 11 | char *ptr; 12 | char *saveptr; 13 | ptr = strtok_r(line, delimiter, &saveptr); 14 | printf("key: %s\n", ptr); 15 | ptr = strtok_r(NULL, delimiter, &saveptr); 16 | printf("value: %s", ptr); 17 | 18 | // TODO: how to turn key and value into 19 | // parameters that can actually be used by 20 | // l3roamd? 21 | // 22 | // attach-mesh-interface 23 | // detach-mesh-interface 24 | // attach-client-interface 25 | // detach-client-interface 26 | // add-prefix 27 | // remove-prefix 28 | // set-export-table 29 | } 30 | 31 | bool parse_config(const char *filename) { 32 | FILE *fp; 33 | char *line = NULL; 34 | size_t len = 0; 35 | ssize_t read; 36 | 37 | fp = fopen(filename, "r"); 38 | if (fp == NULL) 39 | return false; 40 | 41 | while ((read = getline(&line, &len, fp)) != -1) parse_line(line); 42 | 43 | fclose(fp); 44 | free(line); 45 | 46 | return true; 47 | } 48 | -------------------------------------------------------------------------------- /src/config.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of project l3roamd. It's copyrighted by the contributors 3 | * recorded in the version control history of the file, available from 4 | * its original location https://github.com/freifunk-gluon/l3roamd. 5 | * 6 | * SPDX-License-Identifier: BSD-2-Clause 7 | */ 8 | #pragma once 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | void parse_line(char *line); 16 | bool parse_config(const char *filename); 17 | -------------------------------------------------------------------------------- /src/error.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015, Matthias Schiffer 3 | * 4 | * This file is part of project l3roamd. It's copyrighted by the contributors 5 | * recorded in the version control history of the file, available from 6 | * its original location https://github.com/freifunk-gluon/l3roamd. 7 | * 8 | * SPDX-License-Identifier: BSD-2-Clause 9 | */ 10 | #pragma once 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #define exit_errno(message) _exit_error(1, errno, "%s", message) 19 | 20 | static inline void _exit_error(int status, int errnum, const char *format, 21 | ...) { 22 | va_list ap; 23 | va_start(ap, format); 24 | vfprintf(stderr, format, ap); 25 | va_end(ap); 26 | 27 | if (errnum) 28 | fprintf(stderr, ": %s\n", strerror(errnum)); 29 | else 30 | fprintf(stderr, "\n"); 31 | if (status == -1) 32 | abort(); 33 | exit(status); 34 | } 35 | 36 | static inline void exit_error(const char *format, ...) { 37 | va_list ap; 38 | va_start(ap, format); 39 | _exit_error(1, 0, format, ap); 40 | va_end(ap); 41 | } 42 | 43 | static inline void exit_bug(const char *format, ...) { 44 | va_list ap; 45 | va_start(ap, format); 46 | _exit_error(-1, 0, format, ap); 47 | va_end(ap); 48 | } 49 | -------------------------------------------------------------------------------- /src/genl.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of project l3roamd. It's copyrighted by the contributors 3 | * recorded in the version control history of the file, available from 4 | * its original location https://github.com/freifunk-gluon/l3roamd. 5 | * 6 | * SPDX-License-Identifier: BSD-2-Clause 7 | */ 8 | 9 | /* 10 | * This ought to be provided by libnl 11 | */ 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include "genl.h" 22 | 23 | static int error_handler(__attribute__((__unused__)) struct sockaddr_nl *nla, struct nlmsgerr *err, void *arg) { 24 | int *ret = arg; 25 | *ret = err->error; 26 | return NL_STOP; 27 | } 28 | 29 | static int ack_handler(__attribute__((__unused__)) struct nl_msg *msg, void *arg) { 30 | int *ret = arg; 31 | *ret = 0; 32 | return NL_STOP; 33 | } 34 | 35 | struct handler_args { 36 | const char *group; 37 | int id; 38 | }; 39 | 40 | static int family_handler(struct nl_msg *msg, void *arg) { 41 | struct handler_args *grp = arg; 42 | struct nlattr *tb[CTRL_ATTR_MAX + 1]; 43 | struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); 44 | struct nlattr *mcgrp; 45 | int rem_mcgrp; 46 | 47 | nla_parse(tb, CTRL_ATTR_MAX, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), NULL); 48 | 49 | if (!tb[CTRL_ATTR_MCAST_GROUPS]) 50 | return NL_SKIP; 51 | 52 | nla_for_each_nested(mcgrp, tb[CTRL_ATTR_MCAST_GROUPS], rem_mcgrp) { 53 | struct nlattr *tb_mcgrp[CTRL_ATTR_MCAST_GRP_MAX + 1]; 54 | 55 | nla_parse(tb_mcgrp, CTRL_ATTR_MCAST_GRP_MAX, nla_data(mcgrp), nla_len(mcgrp), NULL); 56 | 57 | if (!tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME] || !tb_mcgrp[CTRL_ATTR_MCAST_GRP_ID]) 58 | continue; 59 | if (strncmp(nla_data(tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME]), grp->group, 60 | nla_len(tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME]))) 61 | continue; 62 | grp->id = nla_get_u32(tb_mcgrp[CTRL_ATTR_MCAST_GRP_ID]); 63 | break; 64 | } 65 | 66 | return NL_SKIP; 67 | } 68 | 69 | int nl_get_multicast_id(struct nl_sock *sock, const char *family, const char *group) { 70 | struct nl_msg *msg; 71 | struct nl_cb *cb; 72 | int ret, ctrlid; 73 | struct handler_args grp = { 74 | .group = group, .id = -ENOENT, 75 | }; 76 | 77 | msg = nlmsg_alloc(); 78 | if (!msg) 79 | return -ENOMEM; 80 | 81 | cb = nl_cb_alloc(NL_CB_DEFAULT); 82 | if (!cb) { 83 | ret = -ENOMEM; 84 | goto out_fail_cb; 85 | } 86 | 87 | ctrlid = genl_ctrl_resolve(sock, "nlctrl"); 88 | 89 | genlmsg_put(msg, 0, 0, ctrlid, 0, 0, CTRL_CMD_GETFAMILY, 0); 90 | 91 | ret = -ENOBUFS; 92 | NLA_PUT_STRING(msg, CTRL_ATTR_FAMILY_NAME, family); 93 | 94 | ret = nl_send_auto_complete(sock, msg); 95 | if (ret < 0) 96 | goto out; 97 | 98 | ret = 1; 99 | 100 | nl_cb_err(cb, NL_CB_CUSTOM, error_handler, &ret); 101 | nl_cb_set(cb, NL_CB_ACK, NL_CB_CUSTOM, ack_handler, &ret); 102 | nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, family_handler, &grp); 103 | 104 | while (ret > 0) nl_recvmsgs(sock, cb); 105 | 106 | if (ret == 0) 107 | ret = grp.id; 108 | nla_put_failure: 109 | out: 110 | nl_cb_put(cb); 111 | out_fail_cb: 112 | nlmsg_free(msg); 113 | return ret; 114 | } 115 | -------------------------------------------------------------------------------- /src/genl.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of project l3roamd. It's copyrighted by the contributors 3 | * recorded in the version control history of the file, available from 4 | * its original location https://github.com/freifunk-gluon/l3roamd. 5 | * 6 | * SPDX-License-Identifier: BSD-2-Clause 7 | */ 8 | #pragma once 9 | 10 | #include 11 | 12 | int nl_get_multicast_id(struct nl_sock *sock, const char *family, 13 | const char *group); 14 | -------------------------------------------------------------------------------- /src/icmp6.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of project l3roamd. It's copyrighted by the contributors 3 | * recorded in the version control history of the file, available from 4 | * its original location https://github.com/freifunk-gluon/l3roamd. 5 | * 6 | * SPDX-License-Identifier: BSD-2-Clause 7 | */ 8 | #include "icmp6.h" 9 | #include "ipmgr.h" 10 | #include "l3roamd.h" 11 | #include "packet.h" 12 | #include "util.h" 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | unsigned short csum(unsigned short *ptr, int nbytes); 28 | 29 | int icmp6_init_packet() { 30 | int sock, err; 31 | struct sock_fprog fprog; 32 | static const struct sock_filter filter[] = { 33 | BPF_STMT(BPF_LD | BPF_B | BPF_ABS, sizeof(struct ip6_hdr) + offsetof(struct icmp6_hdr, icmp6_type)), 34 | BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ND_NEIGHBOR_SOLICIT, 1, 0), BPF_STMT(BPF_RET | BPF_K, 0), 35 | BPF_STMT(BPF_RET | BPF_K, 0xffffffff), 36 | }; 37 | 38 | fprog.filter = (struct sock_filter *)filter; 39 | fprog.len = sizeof filter / sizeof filter[0]; 40 | 41 | sock = socket(PF_PACKET, SOCK_DGRAM | SOCK_NONBLOCK, htons(ETH_P_IPV6)); 42 | if (sock < 0) { 43 | perror("Can't create socket(PF_PACKET)"); 44 | } 45 | 46 | // Tie the BSD-PF filter to the socket 47 | err = setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog)); 48 | if (err < 0) { 49 | perror("setsockopt(SO_ATTACH_FILTER)"); 50 | } 51 | 52 | return sock; 53 | } 54 | 55 | static inline int setsockopt_int(int socket, int level, int option, int value) { 56 | return setsockopt(socket, level, option, &value, sizeof(value)); 57 | } 58 | 59 | void icmp6_init(icmp6_ctx *ctx) { 60 | if (l3ctx.clientif_set) { 61 | int fd = socket(PF_INET6, SOCK_RAW | SOCK_NONBLOCK, IPPROTO_ICMPV6); 62 | setsockopt_int(fd, IPPROTO_RAW, IPV6_CHECKSUM, 2); 63 | setsockopt_int(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, 255); 64 | setsockopt_int(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, 1); 65 | setsockopt_int(fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, 1); 66 | setsockopt_int(fd, IPPROTO_IPV6, IPV6_AUTOFLOWLABEL, 0); 67 | 68 | // receive NA on fd 69 | struct icmp6_filter filter; 70 | ICMP6_FILTER_SETBLOCKALL(&filter); 71 | ICMP6_FILTER_SETPASS(ND_NEIGHBOR_ADVERT, &filter); 72 | setsockopt(fd, IPPROTO_ICMPV6, ICMP6_FILTER, &filter, sizeof(filter)); 73 | ctx->fd = fd; 74 | ctx->nsfd = icmp6_init_packet(); 75 | } 76 | 77 | // send icmp6 unreachable on unreachfd6 78 | int unreachfd6 = socket(AF_INET6, SOCK_RAW | SOCK_NONBLOCK, IPPROTO_ICMPV6); 79 | struct icmp6_filter filterv6 = {}; 80 | 81 | ICMP6_FILTER_SETBLOCKALL(&filterv6); 82 | // shutdown(unreachfd6, SHUT_RD); 83 | ICMP6_FILTER_SETPASS(ICMP6_DST_UNREACH, &filterv6); 84 | setsockopt(unreachfd6, IPPROTO_ICMPV6, ICMP6_FILTER, &filterv6, sizeof(filterv6)); 85 | ctx->unreachfd6 = unreachfd6; 86 | 87 | struct icmp_filter filterv4 = {}; 88 | filterv4.data |= 1 << ICMP_DEST_UNREACH; 89 | int unreachfd4 = socket(AF_INET, SOCK_RAW | SOCK_NONBLOCK, IPPROTO_ICMP); 90 | setsockopt(unreachfd4, IPPROTO_ICMP, ICMP_FILTER, &filterv4, sizeof(filterv4)); 91 | ctx->unreachfd4 = unreachfd4; 92 | 93 | icmp6_setup_interface(ctx); 94 | } 95 | 96 | void icmp6_setup_interface(icmp6_ctx *ctx) { 97 | ctx->ok = false; 98 | 99 | if (!l3ctx.clientif_set) 100 | return; 101 | 102 | int rc = setsockopt(ctx->fd, SOL_SOCKET, SO_BINDTODEVICE, ctx->clientif, strnlen(ctx->clientif, IFNAMSIZ - 1)); 103 | log_verbose("Setting up icmp6 interface: %s %i %i %i\n", ctx->clientif, strnlen(ctx->clientif, 12), IFNAMSIZ, rc); 104 | 105 | if (rc < 0) { 106 | perror("icmp6 - setsockopt fd:"); 107 | return; 108 | } 109 | 110 | obtain_mac_from_if(ctx->mac, ctx->clientif); 111 | ctx->ifindex = if_nametoindex(ctx->clientif); 112 | 113 | struct sockaddr_ll lladdr; 114 | memset(&lladdr, 0, sizeof(lladdr)); 115 | lladdr.sll_family = PF_PACKET; 116 | lladdr.sll_protocol = htons(ETH_P_IPV6); 117 | lladdr.sll_ifindex = ctx->ifindex; 118 | lladdr.sll_hatype = 0; 119 | lladdr.sll_pkttype = 0; 120 | lladdr.sll_halen = ETH_ALEN; 121 | 122 | while (bind(ctx->nsfd, (struct sockaddr *)&lladdr, sizeof(lladdr)) < 0) { 123 | perror("bind on icmp6 ns fd failed, retrying"); 124 | } 125 | 126 | ctx->ok = true; 127 | } 128 | 129 | void icmp6_interface_changed(icmp6_ctx *ctx, int type, const struct ifinfomsg *msg) { 130 | char ifname[IFNAMSIZ]; 131 | 132 | if (if_indextoname(msg->ifi_index, ifname) == NULL) 133 | return; 134 | 135 | if (strncmp(ifname, ctx->clientif, IFNAMSIZ - 1) != 0) 136 | return; 137 | 138 | log_verbose("icmp6 interface change detected\n"); 139 | 140 | ctx->ifindex = msg->ifi_index; 141 | 142 | switch (type) { 143 | case RTM_NEWLINK: 144 | case RTM_SETLINK: 145 | icmp6_setup_interface(ctx); 146 | break; 147 | 148 | case RTM_DELLINK: 149 | ctx->ok = false; 150 | break; 151 | } 152 | } 153 | 154 | struct __attribute__((__packed__)) dest_unreach_packet { 155 | struct icmphdr hdr; 156 | uint8_t data[1272]; 157 | }; 158 | 159 | struct __attribute__((__packed__)) dest_unreach_packet6 { 160 | struct icmp6_hdr hdr; 161 | uint8_t data[1272]; 162 | }; 163 | struct __attribute__((__packed__)) sol_packet { 164 | struct nd_neighbor_solicit hdr; 165 | struct nd_opt_hdr opt; 166 | uint8_t hw_addr[ETH_ALEN]; 167 | }; 168 | 169 | struct __attribute__((__packed__)) adv_packet { 170 | struct nd_neighbor_advert hdr; 171 | struct nd_opt_hdr opt; 172 | uint8_t hw_addr[ETH_ALEN]; 173 | }; 174 | 175 | void icmp6_handle_ns_in(icmp6_ctx *ctx) { 176 | char cbuf[CMSG_SPACE(sizeof(int))]; 177 | 178 | struct __attribute__((__packed__)) { 179 | struct ip6_hdr hdr; 180 | struct sol_packet sol; 181 | } packet = {}; 182 | 183 | struct iovec iov = {.iov_base = &packet, .iov_len = sizeof(packet)}; 184 | 185 | struct sockaddr_ll lladdr; 186 | 187 | struct msghdr hdr = {.msg_name = &lladdr, 188 | .msg_namelen = sizeof(lladdr), 189 | .msg_iov = &iov, 190 | .msg_iovlen = 1, 191 | .msg_control = cbuf, 192 | .msg_controllen = sizeof(cbuf)}; 193 | 194 | while (true) { 195 | ssize_t rc = recvmsg(ctx->nsfd, &hdr, 0); 196 | 197 | if (ctx->ndp_disabled) 198 | return; 199 | 200 | if (rc <= 0) 201 | return; 202 | 203 | log_debug("handling icmp6-NDP packet\n"); 204 | 205 | uint8_t *mac = lladdr.sll_addr; 206 | 207 | if (packet.sol.hdr.nd_ns_hdr.icmp6_type == ND_NEIGHBOR_SOLICIT) { 208 | struct in6_addr ns_target = packet.sol.hdr.nd_ns_target; 209 | if (memcmp(&packet.hdr.ip6_src, 210 | "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" 211 | "\x00\x00\x00\x00\x00\x00", 212 | 16) == 0) { 213 | // client is doing DAD. We could trigger sending 214 | // NS on this IP address for a couple of times 215 | // in a while to learn its address instead of 216 | // flooding the network. If we do this, what 217 | // effects will this have on privacy extensions? 218 | log_verbose("triggering local NS cycle after DAD for address %s\n", 219 | print_ip(&ns_target)); 220 | struct ns_task *ns_data = create_ns_task(&ns_target, 221 | (struct timespec){ 222 | .tv_sec = 0, .tv_nsec = 300000000, 223 | }, 224 | 15, true); 225 | post_task(&l3ctx.taskqueue_ctx, 0, 0, ipmgr_ns_task, free, ns_data); 226 | } else { 227 | struct in6_addr ip6_src = packet.hdr.ip6_src; 228 | log_debug( 229 | "Received Neighbor Solicitation from %s [%s] for IP %s. Learning source-IP for " 230 | "client.\n", 231 | print_ip(&ip6_src), print_mac(mac), print_ip(&ns_target)); 232 | 233 | clientmgr_notify_mac(&l3ctx.clientmgr_ctx, mac, ctx->ifindex); 234 | clientmgr_add_address(&l3ctx.clientmgr_ctx, &ip6_src, mac, ctx->ifindex); 235 | } 236 | } 237 | } 238 | } 239 | 240 | void icmp6_handle_in(icmp6_ctx *ctx) { 241 | if (ctx->ndp_disabled) 242 | return; 243 | 244 | log_debug("handling icmp6 event\n"); 245 | 246 | struct msghdr msghdr; 247 | memset(&msghdr, 0, sizeof(msghdr)); 248 | 249 | struct adv_packet packet = {}; 250 | char cbuf[CMSG_SPACE(sizeof(int))]; 251 | 252 | struct iovec iov = {.iov_base = &packet, .iov_len = sizeof(packet)}; 253 | 254 | struct sockaddr_in6 peeraddr; 255 | 256 | struct msghdr hdr = {.msg_name = &peeraddr, 257 | .msg_namelen = sizeof(peeraddr), 258 | .msg_iov = &iov, 259 | .msg_iovlen = 1, 260 | .msg_control = cbuf, 261 | .msg_controllen = sizeof(cbuf)}; 262 | while (true) { 263 | ssize_t rc = recvmsg(ctx->fd, &hdr, 0); 264 | 265 | if (rc == -1) 266 | return; 267 | 268 | if (packet.hdr.nd_na_hdr.icmp6_type != ND_NEIGHBOR_ADVERT) { 269 | log_debug("not an advertisement - returning\n"); 270 | continue; 271 | } 272 | // if (packet.hdr.nd_na_hdr.icmp6_code != 0) 273 | // return; 274 | 275 | if (memcmp(packet.hw_addr, "\x00\x00\x00\x00\x00\x00", 6) == 0) 276 | continue; 277 | 278 | struct in6_addr addr = packet.hdr.nd_na_target; 279 | log_debug("Learning from Neighbour Advertisement that Client [%s] is active on ip %s\n", 280 | print_mac(packet.hw_addr), print_ip(&addr)); 281 | 282 | // TODO: stop possibly previously started NS-cycles due to DAD, 283 | 284 | clientmgr_add_address(&l3ctx.clientmgr_ctx, &addr, packet.hw_addr, ctx->ifindex); 285 | } 286 | } 287 | 288 | int icmp_send_dest_unreachable(const struct in6_addr *addr, const struct packet *data) { 289 | int len = 0, retries = 3; 290 | struct dest_unreach_packet packet = {}; 291 | 292 | memset(&packet, 0, sizeof(packet)); 293 | memset(&packet.hdr, 0, sizeof(packet.hdr)); 294 | packet.hdr.type = ICMP_DEST_UNREACH; 295 | packet.hdr.code = ICMP_HOST_UNREACH; 296 | packet.hdr.checksum = htons(0); 297 | 298 | int dlen = packet_ipv4_get_header_length((uint8_t *)data) + 8; 299 | if (dlen < packet_ipv4_get_length((uint8_t *)data)) 300 | dlen = data->len; 301 | 302 | memcpy(packet.data, data->data, dlen); 303 | 304 | struct sockaddr_in dst = {}; 305 | dst.sin_family = AF_INET; 306 | 307 | struct in_addr sin = extractv4_v6(addr); 308 | memcpy(&dst.sin_addr, &sin, sizeof(struct in_addr)); 309 | 310 | packet.hdr.checksum = 311 | csum((unsigned short *)&packet, 312 | sizeof(struct icmphdr) + dlen); // icmp dest unreachbyte is 8 Bytes according to RFC 792 313 | 314 | while (len <= 0 && retries > 0) { 315 | len = sendto(l3ctx.icmp6_ctx.unreachfd4, &packet, sizeof(struct icmphdr) + dlen, 0, 316 | (struct sockaddr *)&dst, sizeof(dst)); 317 | 318 | if (len > 0) { 319 | log_debug( 320 | "sent %i bytes ICMP destination unreachable to " 321 | "%s\n", 322 | len, print_ip(addr)); 323 | return 0; 324 | } else if (len < 0) { 325 | fprintf(stderr, 326 | "Error while sending ICMP destination " 327 | "unreachable, retrying %s\n", 328 | print_ip(addr)); 329 | perror("sendto"); 330 | } 331 | retries--; 332 | } 333 | return 1; 334 | } 335 | 336 | int icmp6_send_dest_unreachable(const struct in6_addr *addr, const struct packet *data) { 337 | struct dest_unreach_packet6 packet = {}; 338 | memset(&packet, 0, sizeof(packet)); 339 | memset(&packet.hdr, 0, sizeof(packet.hdr)); 340 | packet.hdr.icmp6_type = ICMP6_DST_UNREACH; 341 | packet.hdr.icmp6_code = ICMP6_DST_UNREACH_NOROUTE; 342 | packet.hdr.icmp6_cksum = htons(0); 343 | 344 | int dlen = 1272; 345 | if (data->len < 1272) 346 | dlen = data->len; 347 | 348 | memcpy(packet.data, data->data, dlen); 349 | 350 | struct sockaddr_in6 dst = {}; 351 | dst.sin6_family = AF_INET6; 352 | dst.sin6_flowinfo = 0; 353 | memcpy(&dst.sin6_addr, addr, 16); 354 | 355 | int len = 0; 356 | int retries = 3; 357 | 358 | while (len <= 0 && retries > 0) { 359 | len = sendto(l3ctx.icmp6_ctx.unreachfd6, &packet, sizeof(packet.hdr) + dlen, 0, (struct sockaddr *)&dst, 360 | sizeof(dst)); 361 | 362 | if (len > 0) { 363 | log_debug("sent %i bytes ICMP6 destination unreachable to %s\n", len, print_ip(addr)); 364 | return 0; 365 | } else if (len < 0) { 366 | fprintf(stderr, "Error while sending ICMP destination unreachable, retrying %s\n", 367 | print_ip(addr)); 368 | perror("sendto"); 369 | } 370 | retries--; 371 | } 372 | return 1; 373 | } 374 | 375 | void icmp6_send_solicitation(icmp6_ctx *ctx, const struct in6_addr *addr) { 376 | if (!ctx->ok) 377 | return; 378 | 379 | struct sol_packet packet = {}; 380 | 381 | memset(&packet, 0, sizeof(packet)); 382 | memset(&packet.hdr, 0, sizeof(packet.hdr)); 383 | 384 | packet.hdr.nd_ns_hdr.icmp6_type = ND_NEIGHBOR_SOLICIT; 385 | packet.hdr.nd_ns_hdr.icmp6_code = 0; 386 | packet.hdr.nd_ns_hdr.icmp6_cksum = htons(0); 387 | packet.hdr.nd_ns_reserved = htonl(0); 388 | 389 | memcpy(&packet.hdr.nd_ns_target, addr, 16); 390 | 391 | packet.opt.nd_opt_type = ND_OPT_SOURCE_LINKADDR; 392 | packet.opt.nd_opt_len = 1; 393 | 394 | memcpy(packet.hw_addr, ctx->mac, 6); 395 | 396 | struct sockaddr_in6 dst = {}; 397 | dst.sin6_family = AF_INET6; 398 | dst.sin6_flowinfo = 0; 399 | 400 | // RFC2461 dst address are multicast when the node needs to resolve an 401 | // address and unicast when the node seeks to verify the existence of a 402 | // neighbor 403 | memcpy(&dst.sin6_addr, addr, 16); 404 | memcpy(&dst.sin6_addr, "\xff\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xff", 13); 405 | 406 | struct client *_client = NULL; 407 | if (clientmgr_is_known_address(&l3ctx.clientmgr_ctx, addr, &_client)) { 408 | // find ll-address of the client. if it exists, use that as 409 | // target for our NS 410 | struct in6_addr lladdr = {}; 411 | struct prefix _prefix = {}; 412 | parse_prefix(&_prefix, "fe80::/64"); 413 | lladdr = mac2ipv6(_client->mac, &_prefix); 414 | 415 | if (clientmgr_is_known_address(&l3ctx.clientmgr_ctx, &lladdr, &_client)) { 416 | memcpy(&dst.sin6_addr, &lladdr, 16); 417 | } 418 | } 419 | 420 | char str[INET6_ADDRSTRLEN]; 421 | inet_ntop(AF_INET6, &dst.sin6_addr, str, sizeof str); 422 | 423 | int len = 0; 424 | int retries = 3; 425 | while (len <= 0 && retries > 0) { 426 | len = sendto(ctx->fd, &packet, sizeof(packet), 0, (struct sockaddr *)&dst, sizeof(dst)); 427 | log_debug("sent NS with length %i to %s %i\n", len, str); 428 | if (len < 0) 429 | perror("Error while sending NS, retrying"); 430 | retries--; 431 | } 432 | } 433 | 434 | // TODO why doesn't the kernel calculate the ICMP checksum? 435 | unsigned short csum(unsigned short *ptr, int nbytes) { 436 | u_int32_t sum; 437 | u_int16_t oddbyte; 438 | 439 | sum = 0; 440 | while (nbytes > 1) { 441 | sum += *ptr++; 442 | nbytes -= 2; 443 | } 444 | 445 | if (nbytes == 1) { 446 | oddbyte = 0; 447 | *((u_char *)&oddbyte) = *(u_char *)ptr; 448 | sum += oddbyte; 449 | } 450 | 451 | sum = (sum >> 16) + (sum & 0xffff); 452 | sum = sum + (sum >> 16); 453 | return (u_int16_t)~sum; 454 | } 455 | -------------------------------------------------------------------------------- /src/icmp6.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of project l3roamd. It's copyrighted by the contributors 3 | * recorded in the version control history of the file, available from 4 | * its original location https://github.com/freifunk-gluon/l3roamd. 5 | * 6 | * SPDX-License-Identifier: BSD-2-Clause 7 | */ 8 | #pragma once 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include "common.h" 15 | #include "ipmgr.h" 16 | 17 | typedef struct { 18 | char *clientif; 19 | int fd; // used to learn addresses from NA and send solicitations 20 | int unreachfd6; // used to send ICMP6 destination unreachable 21 | int unreachfd4; // used to send ICMP destination unreachable 22 | int nsfd; // used to read NS from clients to learn ip addresses 23 | unsigned int ifindex; 24 | bool ok; 25 | bool ndp_disabled; 26 | uint8_t mac[ETH_ALEN]; 27 | } icmp6_ctx; 28 | 29 | void icmp6_handle_in(icmp6_ctx *ctx); 30 | void icmp6_handle_ns_in(icmp6_ctx *ctx); 31 | void icmp6_send_solicitation(icmp6_ctx *ctx, const struct in6_addr *addr); 32 | void icmp6_init(icmp6_ctx *ctx); 33 | void icmp6_interface_changed(icmp6_ctx *ctx, int type, 34 | const struct ifinfomsg *msg); 35 | int icmp6_send_dest_unreachable(const struct in6_addr *addr, 36 | const struct packet *data); 37 | int icmp_send_dest_unreachable(const struct in6_addr *addr, 38 | const struct packet *data); 39 | void icmp6_setup_interface(icmp6_ctx *ctx); 40 | -------------------------------------------------------------------------------- /src/if.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of project l3roamd. It's copyrighted by the contributors 3 | * recorded in the version control history of the file, available from 4 | * its original location https://github.com/freifunk-gluon/l3roamd. 5 | * 6 | * SPDX-License-Identifier: BSD-2-Clause 7 | */ 8 | #pragma once 9 | 10 | // declarations from 11 | extern unsigned int if_nametoindex(const char *__ifname); 12 | extern char *if_indextoname(unsigned int __ifindex, char *__ifname); 13 | 14 | // old kernel headers don't include this themselves 15 | #include 16 | 17 | #include 18 | -------------------------------------------------------------------------------- /src/intercom.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of project l3roamd. It's copyrighted by the contributors 3 | * recorded in the version control history of the file, available from 4 | * its original location https://github.com/freifunk-gluon/l3roamd. 5 | * 6 | * SPDX-License-Identifier: BSD-2-Clause 7 | */ 8 | #pragma once 9 | 10 | #include "clientmgr.h" 11 | #include "if.h" 12 | #include "taskqueue.h" 13 | #include "vector.h" 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #define L3ROAMD_PACKET_FORMAT_VERSION 0 23 | #define INFO_MAX 15 // this amount * sizeof(in6_addr) + 6 (mac-address) + 2 (type, 24 | // length) must fit into uint8_t. If we have more than 15 IP 25 | // addresses for a single client, we could implement sending 26 | // multiple segments of type INFO_BASIC. 27 | #define CLAIM_RETRY_MAX 15 28 | #define INFO_RETRY_MAX 15 29 | 30 | enum { INTERCOM_SEEK, INTERCOM_CLAIM, INTERCOM_INFO, INTERCOM_ACK }; 31 | enum { INFO_PLAT, INFO_BASIC }; 32 | enum { CLAIM_MAC }; 33 | enum { ACK_MAC }; 34 | enum { SEEK_ADDRESS }; 35 | 36 | typedef struct __attribute__((__packed__)) { 37 | uint8_t version; 38 | uint8_t ttl; 39 | uint8_t type; 40 | uint8_t empty; 41 | uint32_t nonce; 42 | uint8_t sender[16]; 43 | } intercom_packet_hdr; 44 | 45 | typedef struct __attribute__((__packed__)) { 46 | intercom_packet_hdr hdr; 47 | // after this a dynamic buffer is appended to hold TLV - currently just 48 | // an ipv6 address is allowed 49 | } intercom_packet_seek; 50 | 51 | typedef struct __attribute__((__packed__)) { 52 | intercom_packet_hdr hdr; 53 | // after this a dynamic buffer is appended to hold TLV. currently just 54 | // mac address is allowed 55 | } intercom_packet_claim; 56 | 57 | typedef struct __attribute__((__packed__)) { 58 | intercom_packet_hdr hdr; 59 | // after this a dynamic buffer is appended to hold TLV. currently just 60 | // mac address is allowed 61 | } intercom_packet_ack; 62 | 63 | typedef struct __attribute__((__packed__)) { 64 | intercom_packet_hdr hdr; 65 | // after this a dynamic buffer is appended for plat info and basic 66 | // client info 67 | } intercom_packet_info; 68 | 69 | typedef struct __attribute__((__packed__)) { 70 | uint8_t type; 71 | uint8_t length; 72 | uint16_t lease; 73 | uint8_t platprefix[16]; 74 | } intercom_packet_info_plat; 75 | 76 | typedef struct { uint8_t mac[ETH_ALEN]; } mac; 77 | 78 | typedef VECTOR(client_t) client_v; 79 | 80 | typedef struct __attribute__((__packed__)) { 81 | uint8_t type; 82 | uint8_t length; 83 | uint8_t mac[ETH_ALEN]; 84 | // afterwards an array of elements of type intercom_packet_info_entry is 85 | // expected 86 | } intercom_packet_info_basic; 87 | 88 | typedef struct __attribute__((__packed__)) { uint8_t address[16]; } intercom_packet_info_entry; 89 | 90 | typedef struct intercom_if { 91 | char *ifname; 92 | unsigned int ifindex; 93 | int mcast_recv_fd; 94 | bool ok; 95 | } intercom_if_t; 96 | 97 | typedef VECTOR(intercom_if_t) intercom_if_v; 98 | 99 | struct intercom_task { 100 | uint16_t packet_len; 101 | struct client *client; 102 | uint8_t *packet; 103 | struct in6_addr *recipient; 104 | taskqueue_t *check_task; 105 | uint8_t retries_left; 106 | }; 107 | 108 | typedef struct { 109 | struct in6_addr ip; 110 | struct sockaddr_in6 groupaddr; 111 | VECTOR(intercom_packet_hdr) recent_packets; 112 | intercom_if_v interfaces; 113 | client_v repeatable_claims; 114 | client_v repeatable_infos; 115 | int unicast_nodeip_fd; 116 | int mtu; 117 | } intercom_ctx; 118 | 119 | void intercom_recently_seen_add(intercom_ctx *ctx, intercom_packet_hdr *hdr); 120 | void intercom_send_packet(intercom_ctx *ctx, uint8_t *packet, ssize_t packet_len); 121 | void intercom_seek(intercom_ctx *ctx, const struct in6_addr *address); 122 | void intercom_init_unicast(intercom_ctx *ctx); 123 | void intercom_init(intercom_ctx *ctx); 124 | void intercom_handle_in(intercom_ctx *ctx, int fd); 125 | bool intercom_add_interface(intercom_ctx *ctx, char *ifname); 126 | bool intercom_del_interface(intercom_ctx *ctx, char *ifname); 127 | void intercom_update_interfaces(intercom_ctx *ctx); 128 | void intercom_remove_claim(intercom_ctx *ctx, struct client *client); 129 | bool intercom_info(intercom_ctx *ctx, const struct in6_addr *recipient, struct client *client); 130 | bool intercom_claim(intercom_ctx *ctx, const struct in6_addr *recipient, struct client *client); 131 | bool intercom_ack(intercom_ctx *ctx, const struct in6_addr *recipient, struct client *client); 132 | -------------------------------------------------------------------------------- /src/ipmgr.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of project l3roamd. It's copyrighted by the contributors 3 | * recorded in the version control history of the file, available from 4 | * its original location https://github.com/freifunk-gluon/l3roamd. 5 | * 6 | * SPDX-License-Identifier: BSD-2-Clause 7 | */ 8 | #include "ipmgr.h" 9 | #include "alloc.h" 10 | #include "error.h" 11 | #include "if.h" 12 | #include "intercom.h" 13 | #include "l3roamd.h" 14 | #include "packet.h" 15 | #include "timespec.h" 16 | #include "util.h" 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | static void seek_task(void *d); 30 | static void ipmgr_purge_task(void *d); 31 | 32 | static int entry_compare_by_address(const struct unknown_address *a, const struct unknown_address *b) { 33 | return memcmp(&a->address, &b->address, sizeof(struct in6_addr)); 34 | } 35 | 36 | /* find an entry in the ipmgr's unknown-clients list*/ 37 | struct unknown_address *find_entry(ipmgr_ctx *ctx, const struct in6_addr *k, int *elementindex) { 38 | struct unknown_address key = {.address = *k}; 39 | struct unknown_address *ret = VECTOR_LSEARCH(&key, ctx->addrs, entry_compare_by_address); 40 | if (ret != NULL && elementindex != NULL) 41 | *elementindex = ((void *)ret - (void *)&VECTOR_INDEX(ctx->addrs, 0)) / sizeof(struct unknown_address); 42 | log_debug("%s is on the unknown-clients list", print_ip(k)); 43 | if (elementindex) 44 | log_debug(" on index %i", *elementindex); 45 | log_debug("\n"); 46 | return ret; 47 | } 48 | 49 | struct unknown_address *add_entry(const struct in6_addr *dst) { 50 | struct unknown_address e = {.address = *dst}; 51 | 52 | return VECTOR_ADD(l3ctx.ipmgr_ctx.addrs, e); 53 | } 54 | 55 | /** This will remove an entry from the ipmgr unknown-clients list */ 56 | void delete_entry(const struct in6_addr *k) { 57 | int i; 58 | find_entry(&l3ctx.ipmgr_ctx, k, &i); 59 | VECTOR_DELETE((&l3ctx.ipmgr_ctx)->addrs, i); 60 | } 61 | 62 | struct ns_task *create_ns_task(struct in6_addr *dst, struct timespec tv, int retries, bool force) { 63 | struct ns_task *task = l3roamd_alloc(sizeof(struct ns_task)); // should this be aligned? 64 | 65 | if (retries < 0) 66 | retries = -1; 67 | 68 | task->interval = tv; 69 | task->ctx = &l3ctx.ipmgr_ctx; 70 | task->retries_left = retries; 71 | task->force = force; 72 | memcpy(&task->address, dst, sizeof(struct in6_addr)); 73 | return task; 74 | } 75 | 76 | struct ip_task *create_task(struct in6_addr *dst) { 77 | struct ip_task *task = l3roamd_alloc(sizeof(struct ip_task)); // should this be aligned? 78 | 79 | task->ctx = &l3ctx.ipmgr_ctx; 80 | memcpy(&task->address, dst, sizeof(struct in6_addr)); 81 | return task; 82 | } 83 | 84 | taskqueue_t *schedule_purge_task(struct in6_addr *destination, int timeout) { 85 | struct ip_task *purge_data = create_task(destination); 86 | return post_task(&l3ctx.taskqueue_ctx, timeout, 0, ipmgr_purge_task, free, purge_data); 87 | } 88 | 89 | /** This will seek an address by checking locally and if needed querying the 90 | * network by scheduling a task */ 91 | void ipmgr_seek_address(struct in6_addr *addr) { 92 | struct timespec interval = { 93 | .tv_sec = SEEK_INTERVAL, .tv_nsec = 0, 94 | }; 95 | struct ns_task *ns_data = create_ns_task(addr, interval, -1, false); 96 | post_task(&l3ctx.taskqueue_ctx, 0, 0, ipmgr_ns_task, free, ns_data); 97 | 98 | // schedule an intercom-seek operation that in turn will only be 99 | // executed if there is no local client known 100 | struct ip_task *data = create_task(addr); 101 | post_task(&l3ctx.taskqueue_ctx, 0, 300, seek_task, free, data); 102 | } 103 | 104 | static bool ismulticast(const struct in6_addr *addr) { 105 | if (address_is_ipv4(addr)) { 106 | if (addr->s6_addr[12] >= 224 && addr->s6_addr[12] < 240) 107 | return true; 108 | } else { 109 | if (addr->s6_addr[0] == 0xff) 110 | return true; 111 | } 112 | return false; 113 | } 114 | 115 | static void handle_packet(ipmgr_ctx *ctx, uint8_t packet[], ssize_t packet_len) { 116 | struct in6_addr dst = packet_get_dst(packet); 117 | 118 | if (ismulticast(&dst)) 119 | return; 120 | 121 | if (!clientmgr_valid_address(&l3ctx.clientmgr_ctx, &dst)) { 122 | log_verbose( 123 | "The destination of the packet (%s) is not within the " 124 | "client prefixes. Ignoring packet\n", 125 | print_ip(&dst)); 126 | return; 127 | } 128 | 129 | struct in6_addr src = packet_get_src(packet); 130 | log_verbose("Got packet from %s destined to %s\n", print_ip(&src), print_ip(&dst)); 131 | 132 | struct timespec now; 133 | clock_gettime(CLOCK_MONOTONIC, &now); 134 | 135 | struct unknown_address *e = find_entry(ctx, &dst, NULL); 136 | 137 | bool new_unknown_dst = !e; 138 | 139 | if (new_unknown_dst) 140 | e = add_entry(&dst); 141 | 142 | struct packet p; 143 | 144 | p.timestamp = now; 145 | p.len = packet_len; 146 | p.data = l3roamd_alloc(packet_len); 147 | p.family = packet_get_family(packet); 148 | 149 | memcpy(p.data, packet, packet_len); 150 | 151 | VECTOR_ADD(e->packets, p); 152 | 153 | if (new_unknown_dst) { 154 | ipmgr_seek_address(&dst); 155 | e->check_task = schedule_purge_task(&dst, PACKET_TIMEOUT); 156 | } 157 | } 158 | 159 | static bool should_we_really_seek(struct in6_addr *destination, bool force) { 160 | struct client *client = NULL; 161 | struct unknown_address *e = find_entry(&l3ctx.ipmgr_ctx, destination, NULL); 162 | // if a route to this client appeared, the queue will be emptied -- no 163 | // seek necessary 164 | if (!e) { 165 | log_debug( 166 | "seek task was scheduled but no packets to be delivered to " 167 | "host: %s\n", 168 | print_ip(destination)); 169 | if (force && (!clientmgr_is_known_address(&l3ctx.clientmgr_ctx, destination, &client))) { 170 | log_debug("seeking because we do not know this IP yet: %s\n", print_ip(destination)); 171 | return true; 172 | } else { 173 | return false; 174 | } 175 | } 176 | 177 | if (clientmgr_is_known_address(&l3ctx.clientmgr_ctx, destination, &client) && client_is_active(client)) { 178 | log_error( 179 | "ERROR: seek task was scheduled, there are packets to be " 180 | "delivered to the host: %s, which is a known client. This " 181 | "should never happen. Flushing packets for this " 182 | "destination\n", 183 | print_ip(destination)); 184 | ipmgr_route_appeared(&l3ctx.ipmgr_ctx, destination); 185 | 186 | return false; 187 | } 188 | 189 | return true; 190 | } 191 | 192 | static void remove_packet_from_vector(struct unknown_address *entry, int element) { 193 | struct packet p = VECTOR_INDEX(entry->packets, element); 194 | 195 | free(p.data); 196 | 197 | VECTOR_DELETE(entry->packets, element); 198 | } 199 | 200 | static int purge_old_packets(struct in6_addr *destination) { 201 | int elementindex = 0; 202 | struct unknown_address *e = find_entry(&l3ctx.ipmgr_ctx, destination, &elementindex); 203 | 204 | if (!e) 205 | return 0; 206 | 207 | struct timespec now; 208 | if (clock_gettime(CLOCK_MONOTONIC, &now) < 0) { 209 | perror("clock_gettime"); 210 | return -1; // skip this purging-cycle 211 | } 212 | 213 | struct timespec then = {.tv_sec = now.tv_sec - PACKET_TIMEOUT, .tv_nsec = now.tv_nsec}; 214 | 215 | for (int i = VECTOR_LEN(e->packets) - 1; i >= 0; i--) { 216 | struct packet p = VECTOR_INDEX(e->packets, i); 217 | if (timespec_cmp(p.timestamp, then) <= 0) { 218 | log_debug("deleting old packet with destination %s\n", print_ip(&e->address)); 219 | 220 | struct in6_addr src = packet_get_src(p.data); 221 | if (!address_is_ipv4(&src)) 222 | icmp6_send_dest_unreachable(&src, &p); 223 | else 224 | icmp_send_dest_unreachable(&src, &p); 225 | 226 | remove_packet_from_vector(e, i); 227 | } 228 | } 229 | 230 | if (VECTOR_LEN(e->packets) == 0) { 231 | VECTOR_FREE(e->packets); 232 | VECTOR_DELETE(l3ctx.ipmgr_ctx.addrs, elementindex); 233 | return 0; 234 | } 235 | 236 | return VECTOR_LEN(e->packets); 237 | } 238 | 239 | void ipmgr_purge_task(void *d) { 240 | struct ip_task *data = d; 241 | struct unknown_address *e = find_entry(&l3ctx.ipmgr_ctx, &data->address, NULL); 242 | if (purge_old_packets(&data->address)) 243 | e->check_task = schedule_purge_task(&data->address, 1); 244 | } 245 | 246 | void ipmgr_ns_task(void *d) { 247 | struct ns_task *data = d; 248 | 249 | if (!l3ctx.clientif_set) 250 | return; 251 | 252 | if (!should_we_really_seek(&data->address, data->force)) 253 | return; 254 | 255 | log_error("\x1b[36mLooking for %s locally\x1b[0m\n", print_ip(&data->address)); 256 | log_debug("ns_task: force = %i\n", data->force); 257 | 258 | if (address_is_ipv4(&data->address)) 259 | arp_send_request(&l3ctx.arp_ctx, &data->address); 260 | else 261 | icmp6_send_solicitation(&l3ctx.icmp6_ctx, &data->address); 262 | 263 | if (!!data->retries_left) { 264 | struct ns_task *ns_data = 265 | create_ns_task(&data->address, data->interval, data->retries_left - 1, data->force); 266 | post_task(&l3ctx.taskqueue_ctx, data->interval.tv_sec, data->interval.tv_nsec, ipmgr_ns_task, free, 267 | ns_data); 268 | } 269 | } 270 | 271 | void seek_task(void *d) { 272 | struct ip_task *data = d; 273 | 274 | if (should_we_really_seek(&data->address, false)) { 275 | printf( 276 | "\x1b[36mseeking on intercom for client with the address " 277 | "%s\x1b[0m\n", 278 | print_ip(&data->address)); 279 | 280 | intercom_seek(&l3ctx.intercom_ctx, (const struct in6_addr *)&(data->address)); 281 | 282 | struct ip_task *_data = create_task(&data->address); 283 | post_task(&l3ctx.taskqueue_ctx, SEEK_INTERVAL, 0, seek_task, free, _data); 284 | } 285 | } 286 | 287 | void ipmgr_handle_in(ipmgr_ctx *ctx, int fd) { 288 | ssize_t count; 289 | uint8_t buf[l3ctx.client_mtu]; 290 | log_debug("handling ipmgr event\n"); 291 | 292 | while (1) { 293 | count = read(fd, buf, sizeof(buf)); 294 | 295 | if (count == -1) { 296 | /* If errno == EAGAIN, that means we have read all data. 297 | * So go back to the main loop. */ 298 | if (errno != EAGAIN) 299 | perror("read"); 300 | break; 301 | } else if (count == 0) { 302 | /* End of file. The remote has closed the connection. */ 303 | break; 304 | } 305 | 306 | handle_packet(ctx, buf, count); 307 | } 308 | } 309 | 310 | void ipmgr_handle_out(ipmgr_ctx *ctx, int fd) { 311 | struct timespec now, then; 312 | 313 | while (VECTOR_LEN(ctx->output_queue) > 0) { 314 | struct packet *packet = &VECTOR_INDEX(ctx->output_queue, 0); 315 | 316 | // TODO: handle ipv4 packets correctly 317 | if (write(fd, packet->data, packet->len) == -1) { 318 | if (errno != EAGAIN) 319 | perror("Could not send packet to newly visible client, discarding this packet."); 320 | else { 321 | clock_gettime(CLOCK_MONOTONIC, &now); 322 | then = now; 323 | then.tv_sec -= PACKET_TIMEOUT; 324 | perror("Could not send packet to newly visible client."); 325 | if (timespec_cmp(packet->timestamp, then) <= 0) { 326 | log_error("could not send packet - packet is still young enough, requeueing\n"); 327 | // TODO: consider if output_queue really is the correct queue when requeueing 328 | VECTOR_ADD(ctx->output_queue, *packet); 329 | } else { 330 | log_error("could not send packet - packet is too old, discarding.\n"); 331 | } 332 | } 333 | 334 | break; 335 | } else { 336 | // write was successful, free data structures 337 | free(packet->data); 338 | } 339 | VECTOR_DELETE(ctx->output_queue, 0); 340 | } 341 | } 342 | 343 | void ipmgr_route_appeared(ipmgr_ctx *ctx, const struct in6_addr *destination) { 344 | struct unknown_address *e = find_entry(ctx, destination, NULL); 345 | 346 | if (!e) { 347 | // log_debug ( "route appeared for client %s, which is 348 | // not on the unknown-list.\n", print_ip ( destination ) 349 | // ); 350 | return; 351 | } 352 | 353 | for (size_t i = 0; i < VECTOR_LEN(e->packets); i++) { 354 | struct packet p = VECTOR_INDEX(e->packets, i); 355 | VECTOR_ADD(ctx->output_queue, p); 356 | } 357 | 358 | VECTOR_FREE(e->packets); 359 | 360 | delete_entry(destination); 361 | 362 | ipmgr_handle_out(ctx, ctx->fd); 363 | } 364 | 365 | /* open l3roamd's tun device that is used to obtain packets for unknown clients 366 | */ 367 | static bool tun_open(ipmgr_ctx *ctx, const char *ifname, uint16_t mtu, const char *dev_name) { 368 | int ctl_sock = -1; 369 | struct ifreq ifr = {}; 370 | 371 | ctx->fd = open(dev_name, O_RDWR | O_NONBLOCK); 372 | if (ctx->fd < 0) 373 | exit_errno("could not open TUN/TAP device file"); 374 | 375 | if (ifname) 376 | strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1); 377 | 378 | ifr.ifr_flags = IFF_TUN | IFF_NO_PI; 379 | 380 | if (ioctl(ctx->fd, TUNSETIFF, &ifr) < 0) { 381 | puts("unable to open TUN/TAP interface: TUNSETIFF ioctl failed"); 382 | goto error; 383 | } 384 | 385 | ctx->ifname = strndup(ifr.ifr_name, IFNAMSIZ - 1); 386 | 387 | ctl_sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); 388 | if (ctl_sock < 0) 389 | exit_errno("socket"); 390 | 391 | if (ioctl(ctl_sock, SIOCGIFMTU, &ifr) < 0) 392 | exit_errno("SIOCGIFMTU ioctl failed"); 393 | 394 | if (ifr.ifr_mtu != mtu) { 395 | ifr.ifr_mtu = mtu; 396 | if (ioctl(ctl_sock, SIOCSIFMTU, &ifr) < 0) { 397 | puts( 398 | "unable to set TUN/TAP interface MTU: SIOCSIFMTU " 399 | "ioctl failed"); 400 | goto error; 401 | } 402 | } 403 | 404 | ifr.ifr_flags = IFF_UP | IFF_RUNNING | IFF_MULTICAST | IFF_NOARP | IFF_POINTOPOINT; 405 | if (ioctl(ctl_sock, SIOCSIFFLAGS, &ifr) < 0) 406 | exit_errno( 407 | "unable to set TUN/TAP interface UP: SIOCSIFFLAGS ioctl " 408 | "failed"); 409 | 410 | if (close(ctl_sock)) 411 | puts("close of ctl_sock failed."); 412 | 413 | return true; 414 | 415 | error: 416 | if (ctl_sock >= 0) { 417 | if (close(ctl_sock)) 418 | puts("close"); 419 | } 420 | free(ctx->ifname); 421 | 422 | close(ctx->fd); 423 | ctx->fd = -1; 424 | return false; 425 | } 426 | 427 | bool ipmgr_init(ipmgr_ctx *ctx, char *tun_name, unsigned int mtu) { 428 | return tun_open(ctx, tun_name, mtu, "/dev/net/tun"); 429 | } 430 | -------------------------------------------------------------------------------- /src/ipmgr.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015, Nils Schneider 3 | * Copyright (c) 2017,2018, Christof Schulze 4 | * 5 | * This file is part of project l3roamd. It's copyrighted by the contributors 6 | * recorded in the version control history of the file, available from 7 | * its original location https://github.com/freifunk-gluon/l3roamd. 8 | * 9 | * SPDX-License-Identifier: BSD-2-Clause 10 | */ 11 | #pragma once 12 | 13 | #include "taskqueue.h" 14 | #include "time.h" 15 | #include "types.h" 16 | #include "vector.h" 17 | 18 | #include 19 | #include 20 | #define PACKET_TIMEOUT \ 21 | 5 // drop packet after it sat in the unknown destination-queue for this 22 | // amount of time 23 | #define SEEK_INTERVAL 3 // retry a seek every n seconds 24 | 25 | struct unknown_address { 26 | struct in6_addr address; 27 | taskqueue_t *check_task; 28 | VECTOR(struct packet) packets; 29 | }; 30 | 31 | typedef struct { 32 | char *ifname; 33 | VECTOR(struct unknown_address) addrs; 34 | VECTOR(struct packet) output_queue; 35 | int fd; 36 | } ipmgr_ctx; 37 | 38 | struct ns_task { 39 | struct in6_addr address; 40 | struct timespec interval; 41 | ipmgr_ctx *ctx; 42 | int retries_left; 43 | bool force; 44 | }; 45 | 46 | struct ip_task { 47 | struct in6_addr address; 48 | ipmgr_ctx *ctx; 49 | }; 50 | 51 | bool ipmgr_init(ipmgr_ctx *ctx, char *tun_name, unsigned int mtu); 52 | void ipmgr_route_appeared(ipmgr_ctx *ctx, const struct in6_addr *destination); 53 | void ipmgr_handle_in(ipmgr_ctx *ctx, int fd); 54 | void ipmgr_handle_out(ipmgr_ctx *ctx, int fd); 55 | void ipmgr_seek_address(struct in6_addr *addr); 56 | struct ns_task *create_ns_task(struct in6_addr *dst, struct timespec tv, 57 | int retries, bool force); 58 | void ipmgr_ns_task(void *d); 59 | -------------------------------------------------------------------------------- /src/l3roamd.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of project l3roamd. It's copyrighted by the contributors 3 | * recorded in the version control history of the file, available from 4 | * its original location https://github.com/freifunk-gluon/l3roamd. 5 | * 6 | * SPDX-License-Identifier: BSD-2-Clause 7 | */ 8 | #pragma once 9 | 10 | #include "arp.h" 11 | #include "clientmgr.h" 12 | #include "icmp6.h" 13 | #include "if.h" 14 | #include "intercom.h" 15 | #include "ipmgr.h" 16 | #include "routemgr.h" 17 | #include "socket.h" 18 | #include "taskqueue.h" 19 | #include "types.h" 20 | #include "vector.h" 21 | #include "wifistations.h" 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | struct l3ctx { 30 | taskqueue_ctx taskqueue_ctx; 31 | intercom_ctx intercom_ctx; 32 | clientmgr_ctx clientmgr_ctx; 33 | icmp6_ctx icmp6_ctx; 34 | ipmgr_ctx ipmgr_ctx; 35 | arp_ctx arp_ctx; 36 | routemgr_ctx routemgr_ctx; 37 | wifistations_ctx wifistations_ctx; 38 | socket_ctx socket_ctx; 39 | char *l3device; 40 | int client_mtu; 41 | int efd; 42 | bool debug; 43 | bool verbose; 44 | bool clientif_set; 45 | }; 46 | 47 | extern l3ctx_t l3ctx; 48 | 49 | void add_fd(int efd, int fd, uint32_t events); 50 | void del_fd(int efd, int fd); 51 | 52 | #define INTERCOM_PORT 5523 53 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015, Nils Schneider 3 | * 4 | * This file is part of project l3roamd. It's copyrighted by the contributors 5 | * recorded in the version control history of the file, available from 6 | * its original location https://github.com/freifunk-gluon/l3roamd. 7 | * 8 | * SPDX-License-Identifier: BSD-2-Clause 9 | */ 10 | 11 | #include "alloc.h" 12 | #include "config.h" 13 | #include "error.h" 14 | #include "icmp6.h" 15 | #include "intercom.h" 16 | #include "ipmgr.h" 17 | #include "l3roamd.h" 18 | #include "prefix.h" 19 | #include "routemgr.h" 20 | #include "socket.h" 21 | #include "types.h" 22 | #include "util.h" 23 | #include "vector.h" 24 | #include "version.h" 25 | 26 | #define SIGTERM_MSG "Exiting. Removing routes for prefixes and clients.\n" 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | 40 | l3ctx_t l3ctx = {}; 41 | 42 | void sig_term_handler(__attribute__((__unused__)) int signum, __attribute__((__unused__)) siginfo_t *info, 43 | __attribute__((__unused__)) void *ptr) { 44 | write(STDERR_FILENO, SIGTERM_MSG, sizeof(SIGTERM_MSG)); 45 | 46 | for (size_t i = VECTOR_LEN(l3ctx.clientmgr_ctx.prefixes); i > 0; i--) { 47 | struct prefix *_prefix = &VECTOR_INDEX(l3ctx.clientmgr_ctx.prefixes, i); 48 | routemgr_remove_route(&l3ctx.routemgr_ctx, 254, (struct in6_addr *)(_prefix->prefix.s6_addr), 49 | _prefix->plen); 50 | del_prefix(&l3ctx.clientmgr_ctx.prefixes, *_prefix); 51 | } 52 | clientmgr_purge_clients(&l3ctx.clientmgr_ctx); 53 | _exit(EXIT_SUCCESS); 54 | } 55 | 56 | bool intercom_ready(const int fd) { 57 | for (int j = VECTOR_LEN(l3ctx.intercom_ctx.interfaces) - 1; j >= 0; j--) { 58 | if (VECTOR_INDEX(l3ctx.intercom_ctx.interfaces, j).mcast_recv_fd == fd) { 59 | log_debug( 60 | "received intercom packet on one of the mesh " 61 | "interfaces\n"); 62 | return true; 63 | } 64 | } 65 | 66 | for (int j = VECTOR_LEN(l3ctx.clientmgr_ctx.clients) - 1; j >= 0; j--) { 67 | if (VECTOR_INDEX(l3ctx.clientmgr_ctx.clients, j).fd == fd) { 68 | log_debug( 69 | "received intercom packet for a locally connected " 70 | "client\n"); 71 | return true; 72 | } 73 | } 74 | 75 | if (l3ctx.intercom_ctx.unicast_nodeip_fd == fd) { 76 | log_debug("received intercom packet for unicast_nodeip\n"); 77 | return true; 78 | } 79 | 80 | return false; 81 | } 82 | 83 | bool reconnect_fd(int fd) { 84 | del_fd(l3ctx.efd, fd); 85 | char c; 86 | while (read(fd, &c, 1) > 0) 87 | ; 88 | if (close(fd) < 0) 89 | perror("close"); 90 | 91 | if (fd == l3ctx.routemgr_ctx.fd) { 92 | routemgr_init(&l3ctx.routemgr_ctx); 93 | add_fd(l3ctx.efd, l3ctx.routemgr_ctx.fd, EPOLLIN); 94 | return true; 95 | } else if (fd == l3ctx.arp_ctx.fd) { 96 | arp_init(&l3ctx.arp_ctx); 97 | add_fd(l3ctx.efd, l3ctx.arp_ctx.fd, EPOLLIN); 98 | return true; 99 | } else if (fd == l3ctx.icmp6_ctx.fd) { 100 | del_fd(l3ctx.efd, l3ctx.icmp6_ctx.nsfd); 101 | close(l3ctx.icmp6_ctx.nsfd); 102 | icmp6_init(&l3ctx.icmp6_ctx); 103 | add_fd(l3ctx.efd, l3ctx.icmp6_ctx.fd, EPOLLIN); 104 | add_fd(l3ctx.efd, l3ctx.icmp6_ctx.nsfd, EPOLLIN); 105 | return true; 106 | } else if (fd == l3ctx.icmp6_ctx.nsfd) { 107 | del_fd(l3ctx.efd, l3ctx.icmp6_ctx.fd); 108 | close(l3ctx.icmp6_ctx.fd); 109 | icmp6_init(&l3ctx.icmp6_ctx); 110 | add_fd(l3ctx.efd, l3ctx.icmp6_ctx.fd, EPOLLIN); 111 | add_fd(l3ctx.efd, l3ctx.icmp6_ctx.nsfd, EPOLLIN); 112 | return true; 113 | } 114 | return false; 115 | } 116 | 117 | void loop() { 118 | int maxevents = 64; 119 | struct epoll_event *events; 120 | 121 | add_fd(l3ctx.efd, l3ctx.ipmgr_ctx.fd, EPOLLIN); 122 | add_fd(l3ctx.efd, l3ctx.routemgr_ctx.fd, EPOLLIN); 123 | add_fd(l3ctx.efd, l3ctx.icmp6_ctx.unreachfd6, EPOLLIN); 124 | add_fd(l3ctx.efd, l3ctx.icmp6_ctx.unreachfd4, EPOLLIN); 125 | add_fd(l3ctx.efd, l3ctx.intercom_ctx.unicast_nodeip_fd, EPOLLIN); 126 | add_fd(l3ctx.efd, l3ctx.taskqueue_ctx.fd, EPOLLIN); 127 | 128 | if (l3ctx.clientif_set) { 129 | log_verbose("adding icmp6-fd to epoll\n"); 130 | add_fd(l3ctx.efd, l3ctx.icmp6_ctx.fd, EPOLLIN); 131 | add_fd(l3ctx.efd, l3ctx.icmp6_ctx.nsfd, EPOLLIN); 132 | 133 | log_verbose("adding arp-fd to epoll\n"); 134 | add_fd(l3ctx.efd, l3ctx.arp_ctx.fd, EPOLLIN); 135 | 136 | if (l3ctx.wifistations_ctx.fd >= 0) 137 | add_fd(l3ctx.efd, l3ctx.wifistations_ctx.fd, EPOLLIN); 138 | } 139 | 140 | if (l3ctx.socket_ctx.fd >= 0) 141 | add_fd(l3ctx.efd, l3ctx.socket_ctx.fd, EPOLLIN); 142 | 143 | /* Buffer where events are returned */ 144 | events = l3roamd_alloc0_array(maxevents, sizeof(struct epoll_event)); 145 | log_verbose("starting loop\n"); 146 | 147 | /* The event loop */ 148 | while (1) { 149 | int n = epoll_wait(l3ctx.efd, events, maxevents, -1); 150 | for (int i = 0; i < n; i++) { 151 | log_debug( 152 | "handling event on fd %i. taskqueue.fd: %i " 153 | "routemgr: %i ipmgr: %i icmp6: %i icmp6.ns: %i " 154 | "arp: %i socket: %i, wifistations: %i, " 155 | "intercom_unicast_nodeip_fd: %i - ", 156 | events[i].data.fd, l3ctx.taskqueue_ctx.fd, l3ctx.routemgr_ctx.fd, l3ctx.ipmgr_ctx.fd, 157 | l3ctx.icmp6_ctx.fd, l3ctx.icmp6_ctx.nsfd, l3ctx.arp_ctx.fd, l3ctx.socket_ctx.fd, 158 | l3ctx.wifistations_ctx.fd, l3ctx.intercom_ctx.unicast_nodeip_fd); 159 | 160 | if ((events[i].events & EPOLLERR) || (events[i].events & EPOLLHUP) || 161 | (!(events[i].events & EPOLLIN))) { 162 | fprintf(stderr, 163 | "epoll error received on fd %i. Dumping " 164 | "fd: taskqueue.fd: %i routemgr: %i ipmgr: " 165 | "%i icmp6: %i icmp6.ns: %i arp: %i socket: " 166 | "%i, wifistations: %i ... continuing\n", 167 | events[i].data.fd, l3ctx.taskqueue_ctx.fd, l3ctx.routemgr_ctx.fd, 168 | l3ctx.ipmgr_ctx.fd, l3ctx.icmp6_ctx.fd, l3ctx.icmp6_ctx.nsfd, l3ctx.arp_ctx.fd, 169 | l3ctx.socket_ctx.fd, l3ctx.wifistations_ctx.fd); 170 | if (reconnect_fd(events[i].data.fd)) 171 | continue; 172 | perror( 173 | "epoll error without contingency plan. " 174 | "Exiting now."); 175 | sig_term_handler(0, 0, 0); 176 | } else if (l3ctx.wifistations_ctx.fd == events[i].data.fd) { 177 | wifistations_handle_in(&l3ctx.wifistations_ctx); 178 | } else if (l3ctx.taskqueue_ctx.fd == events[i].data.fd) { 179 | taskqueue_run(&l3ctx.taskqueue_ctx); 180 | } else if (l3ctx.routemgr_ctx.fd == events[i].data.fd) { 181 | if (events[i].events & EPOLLIN) { 182 | log_debug(" INBOUND\n"); 183 | routemgr_handle_in(&l3ctx.routemgr_ctx, events[i].data.fd); 184 | } else { 185 | log_debug("\n"); 186 | } 187 | } else if (l3ctx.ipmgr_ctx.fd == events[i].data.fd) { 188 | if (events[i].events & EPOLLIN) 189 | ipmgr_handle_in(&l3ctx.ipmgr_ctx, events[i].data.fd); 190 | } else if (l3ctx.icmp6_ctx.unreachfd6 == events[i].data.fd) { 191 | unsigned char trash[l3ctx.client_mtu]; 192 | int amount = read(l3ctx.icmp6_ctx.unreachfd6, trash, 193 | l3ctx.client_mtu); // TODO: why do we even 194 | // have to read here? 195 | // This should be 196 | // write-only 197 | log_debug( 198 | "ignoring bogus data on unreachfd6, %i " 199 | "Bytes\n", 200 | amount); 201 | } else if (l3ctx.icmp6_ctx.unreachfd4 == events[i].data.fd) { 202 | unsigned char trash[l3ctx.client_mtu]; 203 | int amount = read(l3ctx.icmp6_ctx.unreachfd4, trash, 204 | l3ctx.client_mtu); // TODO: why do we even 205 | // have to read here? 206 | // This should be 207 | // write-only 208 | log_debug( 209 | "ignoring bogus data on unreachfd4, %i " 210 | "Bytes\n", 211 | amount); 212 | } else if (l3ctx.icmp6_ctx.fd == events[i].data.fd) { 213 | if (events[i].events & EPOLLIN) 214 | icmp6_handle_in(&l3ctx.icmp6_ctx); 215 | } else if (l3ctx.icmp6_ctx.nsfd == events[i].data.fd) { 216 | if (events[i].events & EPOLLIN) 217 | icmp6_handle_ns_in(&l3ctx.icmp6_ctx); 218 | } else if (l3ctx.arp_ctx.fd == events[i].data.fd) { 219 | if (events[i].events & EPOLLIN) 220 | arp_handle_in(&l3ctx.arp_ctx, events[i].data.fd); 221 | } else if (l3ctx.socket_ctx.fd == events[i].data.fd) { 222 | socket_handle_in(&l3ctx.socket_ctx); 223 | } else if (intercom_ready(events[i].data.fd)) { 224 | log_debug("handling intercom event\n"); 225 | if (events[i].events & EPOLLIN) 226 | intercom_handle_in(&l3ctx.intercom_ctx, events[i].data.fd); 227 | } else { 228 | char buffer[512]; 229 | int tmp = read(events[i].data.fd, buffer, 512); 230 | printf( 231 | " WE JUST READ %i Byte from unknown " 232 | "socket %i with content %s - If this was 0 " 233 | "bytes, then this was a closed socket and " 234 | "everything is fine.\n", 235 | tmp, events[i].data.fd, buffer); 236 | } 237 | } 238 | } 239 | 240 | free(events); 241 | } 242 | 243 | void usage() { 244 | puts( 245 | "Usage: l3roamd [-h] [-d] [-b ] -a [-n " 246 | "] -p [-e ] [-i ] -m " 247 | "... -t [-4 prefix] [-D ]"); 248 | puts( 249 | "The order of options matters. -d and -4 should be specified " 250 | "first.\n"); 251 | puts(" -a ip address of this node"); 252 | puts( 253 | " -b this is the bridge where all clients are " 254 | "connected"); 255 | puts(" -d use debug logging"); 256 | puts(" -c configuration file"); // TODO: do we really 257 | // need this? 258 | puts( 259 | " -p Accept queries for this prefix. May be " 260 | "provided multiple times."); 261 | puts( 262 | " -P Defines the node-client prefix. Default: " 263 | "fec0::/64."); 264 | puts( 265 | " -e Defines the plat-prefix if this node is to " 266 | "be a local exit. This must be a /96"); 267 | puts( 268 | " -s provide statistics and allow control using " 269 | "this socket. See below for usage instructions."); 270 | puts(" -i client interface"); 271 | puts( 272 | " -m mesh interface. may be specified multiple " 273 | "times"); 274 | puts(" -n clat-interface."); 275 | puts(" -t export routes to this table"); 276 | puts(" -4 IPv4 translation prefix"); 277 | puts(" -V|--version show version information"); 278 | puts(" -v verbose output"); 279 | puts(" -d debug output"); 280 | puts(" -D Device name for the l3roamd tun-device"); 281 | puts( 282 | " --no-netlink do not use fdb or neighbour-table to learn " 283 | "new clients"); 284 | puts(" --no-ndp do not use ndp to learn new clients"); 285 | puts(" --no-nl80211 do not use nl80211 to learn new clients"); 286 | puts(" -h|--help this help\n"); 287 | 288 | puts("The socket will accept the following commands:"); 289 | puts( 290 | "get_clients The daemon will reply with a json " 291 | "structure, currently providing client count."); 292 | puts( 293 | "get_prefixes This return a list of all prefixes being " 294 | "handled by l3roamd."); 295 | puts( 296 | "add_meshif Add to mesh interfaces. Does " 297 | "the same as -m"); 298 | puts( 299 | "del_meshif Remove from mesh interfaces. " 300 | "Reverts add_meshif"); 301 | puts( 302 | "get_meshifs Obtain a list of all current mesh " 303 | "interfaces"); 304 | puts( 305 | "add_prefix This will treat as if it was " 306 | "added using -p"); 307 | puts( 308 | "del_prefix This will remove from the list " 309 | "of client-prefixes and stop accepting queries for clients within " 310 | "that prefix."); 311 | puts( 312 | "add_address This will add the ipv6 address to the " 313 | "client represented by "); 314 | puts( 315 | "del_address This will remove the ipv6 address from " 316 | "the client represented by "); 317 | puts( 318 | "probe This will start a neighbour discovery " 319 | "for a neighbour with address "); 320 | puts("verbosity This will set the verbosity of the l3roamd process"); 321 | } 322 | 323 | void catch_sigterm() { 324 | static struct sigaction _sigact; 325 | 326 | memset(&_sigact, 0, sizeof(_sigact)); 327 | _sigact.sa_sigaction = sig_term_handler; 328 | _sigact.sa_flags = SA_SIGINFO; 329 | 330 | sigaction(SIGTERM, &_sigact, NULL); 331 | } 332 | 333 | int main(int argc, char *argv[]) { 334 | char *socketpath = NULL; 335 | 336 | signal(SIGPIPE, SIG_IGN); 337 | 338 | l3ctx.client_mtu = 1500; 339 | l3ctx.intercom_ctx.mtu = 1500; 340 | 341 | l3ctx.routemgr_ctx.client_bridge.ifname[0] = '\0'; 342 | l3ctx.routemgr_ctx.clientif.ifname[0] = '\0'; 343 | l3ctx.icmp6_ctx.clientif = strdup("\0"); 344 | l3ctx.arp_ctx.clientif = strdup("\0"); 345 | l3ctx.clientmgr_ctx.export_table = 254; 346 | bool v4_initialized = false; 347 | bool a_initialized = false; 348 | bool p_initialized = false; 349 | l3ctx.clientif_set = false; 350 | l3ctx.routemgr_ctx.nl_disabled = false; 351 | l3ctx.wifistations_ctx.nl80211_disabled = false; 352 | l3ctx.icmp6_ctx.ndp_disabled = false; 353 | 354 | l3ctx.verbose = false; 355 | l3ctx.debug = false; 356 | l3ctx.l3device = strdup("l3roam0"); 357 | 358 | struct prefix _tprefix = {}; 359 | parse_prefix(&_tprefix, "fec0::/64"); 360 | l3ctx.clientmgr_ctx.node_client_prefix = _tprefix; 361 | l3ctx.clientmgr_ctx.platprefix_set = false; 362 | 363 | parse_prefix(&l3ctx.clientmgr_ctx.v4prefix, "0:0:0:0:0:ffff::/96"); 364 | l3ctx.arp_ctx.prefix = l3ctx.clientmgr_ctx.v4prefix.prefix; 365 | 366 | int option_index = 0; 367 | struct option long_options[] = {{"help", 0, NULL, 'h'}, 368 | {"no-netlink", 0, NULL, 'F'}, 369 | {"no-nl80211", 0, NULL, 'N'}, 370 | {"no-ndp", 0, NULL, 'X'}, 371 | {"version", 0, NULL, 'V'}}; 372 | 373 | // clients have ll-addresses - watch those. 374 | struct prefix _prefix = {}; 375 | parse_prefix(&_prefix, "fe80::/64"); 376 | add_prefix(&l3ctx.clientmgr_ctx.prefixes, _prefix); 377 | 378 | l3ctx.efd = epoll_create1(0); 379 | if (l3ctx.efd == -1) { 380 | perror("epoll_create"); 381 | abort(); 382 | } 383 | 384 | intercom_init(&l3ctx.intercom_ctx); 385 | int c; 386 | while ((c = getopt_long(argc, argv, "dhva:b:e:p:i:m:t:c:4:n:s:d:VD:P:", long_options, &option_index)) != -1) 387 | switch (c) { 388 | case 'V': 389 | printf("l3roamd %s\n", SOURCE_VERSION); 390 | #if defined(GIT_BRANCH) && defined(GIT_COMMIT_HASH) 391 | printf("branch: %s\n commit: %s\n", GIT_BRANCH, GIT_COMMIT_HASH); 392 | #endif 393 | exit(EXIT_SUCCESS); 394 | case 'b': 395 | strncpy(l3ctx.routemgr_ctx.client_bridge.ifname, optarg, IFNAMSIZ); 396 | break; 397 | case 'h': 398 | usage(); 399 | exit(EXIT_SUCCESS); 400 | case 'a': 401 | if (a_initialized) 402 | exit_error("-a must not be specified more than once"); 403 | 404 | if (inet_pton(AF_INET6, optarg, &l3ctx.intercom_ctx.ip) != 1) 405 | exit_error("Can not parse IP address %s\n", optarg); 406 | intercom_init_unicast(&l3ctx.intercom_ctx); 407 | a_initialized = true; 408 | break; 409 | case 'c': 410 | // TODO: this is not implemented. 411 | parse_config(optarg); 412 | break; 413 | case 'P':; 414 | struct prefix _ncprefix = {}; 415 | if (!parse_prefix(&_ncprefix, optarg)) 416 | exit_error("Can not parse node-client-prefix that passed by -P %s\n", optarg); 417 | l3ctx.clientmgr_ctx.node_client_prefix = _ncprefix; 418 | break; 419 | case 'p': { 420 | struct prefix _prefix = {}; 421 | if (!parse_prefix(&_prefix, optarg)) 422 | exit_error("Can not parse prefix %s\n", optarg); 423 | add_prefix(&l3ctx.clientmgr_ctx.prefixes, _prefix); 424 | p_initialized = true; 425 | } break; 426 | case 'e': { 427 | struct prefix _prefix = {}; 428 | if (!parse_prefix(&_prefix, optarg)) 429 | exit_error("Can not parse PLAT-prefix %s\n", optarg); 430 | if (_prefix.plen != 96) 431 | exit_error("PLAT-prefix must be /96"); 432 | 433 | l3ctx.clientmgr_ctx.platprefix = _prefix.prefix; 434 | l3ctx.clientmgr_ctx.platprefix_set = true; 435 | } break; 436 | case '4': 437 | if (!parse_prefix(&l3ctx.clientmgr_ctx.v4prefix, optarg)) 438 | exit_error("Can not parse IPv4 prefix"); 439 | 440 | // if (l3ctx.clientmgr_ctx.v4prefix.plen != 96) 441 | // exit_error("IPv4 prefix must be /96"); 442 | 443 | l3ctx.arp_ctx.prefix = l3ctx.clientmgr_ctx.v4prefix.prefix; 444 | 445 | v4_initialized = true; 446 | break; 447 | case 'i': 448 | if (if_nametoindex(optarg) && !l3ctx.clientif_set) { 449 | free(l3ctx.icmp6_ctx.clientif); 450 | free(l3ctx.arp_ctx.clientif); 451 | strncpy(l3ctx.routemgr_ctx.clientif.ifname, optarg, IFNAMSIZ); 452 | l3ctx.icmp6_ctx.clientif = strdupa(optarg); 453 | l3ctx.arp_ctx.clientif = strdupa(optarg); 454 | l3ctx.clientif_set = true; 455 | } else { 456 | fprintf(stderr, 457 | "ignoring unknown client-interface %s or client-interface was already " 458 | "set. Only the first client-interface will be considered.\n", 459 | optarg); 460 | } 461 | break; 462 | case 'm': 463 | intercom_add_interface(&l3ctx.intercom_ctx, strdup(optarg)); 464 | break; 465 | case 't': 466 | l3ctx.clientmgr_ctx.export_table = atoi(optarg); 467 | break; 468 | case 's': 469 | socketpath = optarg; 470 | break; 471 | case 'd': 472 | l3ctx.debug = true; 473 | /* Falls through. */ 474 | case 'v': 475 | l3ctx.verbose = true; 476 | break; 477 | case 'n': 478 | l3ctx.clientmgr_ctx.nat46ifindex = if_nametoindex(optarg); 479 | break; 480 | case 'D': 481 | free(l3ctx.l3device); 482 | l3ctx.l3device = strdupa(optarg); 483 | break; 484 | case 'F': 485 | l3ctx.routemgr_ctx.nl_disabled = true; 486 | break; 487 | case 'N': 488 | l3ctx.icmp6_ctx.ndp_disabled = true; 489 | break; 490 | case 'X': 491 | l3ctx.wifistations_ctx.nl80211_disabled = true; 492 | break; 493 | default: 494 | fprintf(stderr, "Invalid parameter %c ignored.\n", c); 495 | } 496 | 497 | if (!v4_initialized) { 498 | fprintf(stderr, 499 | "-4 was not specified. Defaulting to " 500 | "0:0:0:0:0:ffff::/96\n"); 501 | v4_initialized = true; 502 | } 503 | 504 | if (!a_initialized) 505 | exit_error("specifying -a is mandatory"); 506 | if (!p_initialized) 507 | exit_error("specifying -p is mandatory"); 508 | 509 | catch_sigterm(); 510 | 511 | socket_init(&l3ctx.socket_ctx, socketpath); 512 | if (!ipmgr_init(&l3ctx.ipmgr_ctx, l3ctx.l3device, 9000)) 513 | exit_error("could not open the tun device for l3roamd. exiting now\n"); 514 | 515 | routemgr_init(&l3ctx.routemgr_ctx); 516 | if (l3ctx.clientif_set) { 517 | wifistations_init(&l3ctx.wifistations_ctx); 518 | arp_init(&l3ctx.arp_ctx); 519 | } 520 | 521 | taskqueue_init(&l3ctx.taskqueue_ctx); 522 | clientmgr_init(); 523 | icmp6_init(&l3ctx.icmp6_ctx); 524 | 525 | loop(); 526 | 527 | return 0; 528 | } 529 | -------------------------------------------------------------------------------- /src/packet.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of project l3roamd. It's copyrighted by the contributors 3 | * recorded in the version control history of the file, available from 4 | * its original location https://github.com/freifunk-gluon/l3roamd. 5 | * 6 | * SPDX-License-Identifier: BSD-2-Clause 7 | */ 8 | #include "packet.h" 9 | #include 10 | #include 11 | #include 12 | #include "util.h" 13 | 14 | static struct in6_addr packet_get_ip4(const uint8_t packet[], int offset) { 15 | struct in_addr src; 16 | struct in6_addr src6; 17 | memcpy(&src, packet + offset, 4); 18 | mapv4_v6(&src, &src6); 19 | return src6; 20 | } 21 | 22 | static struct in6_addr packet_get_src4(const uint8_t packet[]) { return packet_get_ip4(packet, 12); } 23 | 24 | static struct in6_addr packet_get_dst4(const uint8_t packet[]) { return packet_get_ip4(packet, 16); } 25 | 26 | static struct in6_addr packet_get_ip6(const uint8_t packet[], int offset) { 27 | struct in6_addr src; 28 | memcpy(&src, packet + offset, 16); 29 | return src; 30 | } 31 | 32 | static struct in6_addr packet_get_src6(const uint8_t packet[]) { return packet_get_ip6(packet, 8); } 33 | 34 | static struct in6_addr packet_get_dst6(const uint8_t packet[]) { return packet_get_ip6(packet, 24); } 35 | 36 | uint8_t packet_ipv4_get_header_length(const uint8_t packet[]) { 37 | return (packet[0] & 0x0f) << 2; // IHL * 32 / 8 = IHL * 32/4 = IHL << 2 38 | } 39 | 40 | uint16_t packet_ipv4_get_length(const uint8_t packet[]) { 41 | uint16_t length = ((packet[3] << 8) | packet[4]); 42 | return length; 43 | } 44 | 45 | static bool packet_isv4(const uint8_t packet[]) { return (packet[0] & 0xf0) == 0x40; } 46 | 47 | static bool packet_isv6(const uint8_t packet[]) { return (packet[0] & 0xf0) == 0x60; } 48 | 49 | uint8_t packet_get_family(const uint8_t packet[]) { return (packet_isv6(packet) ? AF_INET6 : AF_INET); } 50 | 51 | struct in6_addr packet_get_src(const uint8_t packet[]) { 52 | if (packet_isv4(packet)) 53 | return packet_get_src4(packet); 54 | else if (packet_isv6(packet)) 55 | return packet_get_src6(packet); 56 | struct in6_addr ret = {}; 57 | return ret; 58 | } 59 | 60 | struct in6_addr packet_get_dst(const uint8_t packet[]) { 61 | if (packet_isv4(packet)) 62 | return packet_get_dst4(packet); 63 | else if (packet_isv6(packet)) 64 | return packet_get_dst6(packet); 65 | struct in6_addr ret = {}; 66 | return ret; 67 | } 68 | -------------------------------------------------------------------------------- /src/packet.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of project l3roamd. It's copyrighted by the contributors 3 | * recorded in the version control history of the file, available from 4 | * its original location https://github.com/freifunk-gluon/l3roamd. 5 | * 6 | * SPDX-License-Identifier: BSD-2-Clause 7 | */ 8 | #pragma once 9 | 10 | #include 11 | #include 12 | #include "timespec.h" 13 | 14 | struct packet { 15 | struct timespec timestamp; 16 | ssize_t len; 17 | uint8_t *data; 18 | uint8_t family; 19 | }; 20 | 21 | uint16_t packet_ipv4_get_length(const uint8_t packet[]); 22 | uint8_t packet_ipv4_get_header_length(const uint8_t packet[]); 23 | struct in6_addr packet_get_src(const uint8_t packet[]); 24 | struct in6_addr packet_get_dst(const uint8_t packet[]); 25 | uint8_t packet_get_family(const uint8_t packet[]); 26 | -------------------------------------------------------------------------------- /src/prefix.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017,2018 Christof Schulze 3 | * 4 | * This file is part of project l3roamd. It's copyrighted by the contributors 5 | * recorded in the version control history of the file, available from 6 | * its original location https://github.com/freifunk-gluon/l3roamd. 7 | * 8 | * SPDX-License-Identifier: BSD-2-Clause 9 | */ 10 | 11 | #include "prefix.h" 12 | #include 13 | 14 | #include 15 | #include "util.h" 16 | /* this will parse the string str and return a prefix struct 17 | */ 18 | bool parse_prefix(struct prefix *prefix, const char *str) { 19 | char *saveptr = NULL; 20 | char tmp[INET6_ADDRSTRLEN + 5]; // hold complete prefix + "/" + 3-digit prefix length + terminating null-byte 21 | strncpy(tmp, str, INET6_ADDRSTRLEN + 5); 22 | 23 | prefix->isv4 = !strchr(tmp, ':'); 24 | 25 | log_debug("parsing %s prefix: %s\n", prefix->isv4 ? "IPv4" : "IPv6", str); 26 | char *ptr = strtok_r(tmp, "/", &saveptr); 27 | if (!ptr) 28 | return false; 29 | 30 | if (prefix->isv4) { 31 | struct in_addr v4; 32 | if (inet_pton(AF_INET, ptr, &v4) != 1) 33 | return false; 34 | mapv4_v6(&v4, &prefix->prefix); 35 | } else { 36 | if (inet_pton(AF_INET6, ptr, &prefix->prefix) != 1) 37 | return false; 38 | } 39 | ptr = strtok_r(NULL, "/", &saveptr); 40 | if (!ptr) 41 | return false; 42 | 43 | prefix->plen = atoi(ptr); 44 | if (prefix->isv4) 45 | prefix->plen += 96; 46 | 47 | if (prefix->plen < 0 || prefix->plen > 128) 48 | return false; 49 | 50 | return true; 51 | } 52 | 53 | /* this will add a prefix to the prefix vector, causing l3roamd to 54 | ** accept packets to this prefix as client-prefix 55 | */ 56 | bool add_prefix(void *prefixes, struct prefix _prefix) { 57 | VECTOR(struct prefix) *_prefixes = prefixes; 58 | VECTOR_ADD(*_prefixes, _prefix); 59 | 60 | return true; 61 | } 62 | 63 | /* this will remove a prefix from the prefix vector, causing l3roamd not to 64 | ** accept packets to this prefix as client-prefix 65 | */ 66 | bool del_prefix(void *prefixes, struct prefix _prefix) { 67 | VECTOR(struct prefix) *_prefixes = prefixes; 68 | for (size_t i = 0; i < VECTOR_LEN(*_prefixes); i++) { 69 | if (!memcmp(&VECTOR_INDEX(*_prefixes, i), &_prefix, sizeof(_prefix))) { 70 | VECTOR_DELETE(*_prefixes, i); 71 | return true; 72 | } 73 | } 74 | 75 | return false; 76 | } 77 | 78 | bool prefix_contains(const struct prefix *prefix, const struct in6_addr *addr) { 79 | log_debug("checking if prefix %s/%d contains address %s\n", print_ip(&prefix->prefix), prefix->plen, print_ip(addr)); 80 | 81 | int mask = 0xff; 82 | for (int remaining_plen = prefix->plen, i = 0; remaining_plen > 0; remaining_plen -= 8) { 83 | if (remaining_plen < 8) 84 | mask = 0xff & (0xff00 >> remaining_plen); 85 | 86 | if ((addr->s6_addr[i] & mask) != prefix->prefix.s6_addr[i]) 87 | return false; 88 | i++; 89 | } 90 | return true; 91 | } 92 | -------------------------------------------------------------------------------- /src/prefix.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, Christof Schulze 3 | * 4 | * This file is part of project l3roamd. It's copyrighted by the contributors 5 | * recorded in the version control history of the file, available from 6 | * its original location https://github.com/freifunk-gluon/l3roamd. 7 | * 8 | * SPDX-License-Identifier: BSD-2-Clause 9 | */ 10 | #pragma once 11 | 12 | #include 13 | #include 14 | #include 15 | #include "vector.h" 16 | 17 | struct prefix { 18 | struct in6_addr prefix; 19 | int plen; /* in bits */ 20 | bool isv4; 21 | }; 22 | 23 | bool add_prefix(void *prefixes, struct prefix prefix); 24 | bool del_prefix(void *prefixes, struct prefix prefix); 25 | bool parse_prefix(struct prefix *prefix, const char *str); 26 | bool prefix_contains(const struct prefix *prefix, const struct in6_addr *addr); 27 | -------------------------------------------------------------------------------- /src/routemgr.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of project l3roamd. It's copyrighted by the contributors 3 | * recorded in the version control history of the file, available from 4 | * its original location https://github.com/freifunk-gluon/l3roamd. 5 | * 6 | * SPDX-License-Identifier: BSD-2-Clause 7 | */ 8 | #include "routemgr.h" 9 | #include "error.h" 10 | #include "l3roamd.h" 11 | #include "alloc.h" 12 | 13 | #include 14 | #include "clientmgr.h" 15 | #include "icmp6.h" 16 | #include "if.h" 17 | #include "util.h" 18 | 19 | #include 20 | 21 | static void rtnl_change_address(routemgr_ctx *ctx, struct in6_addr *address, int type, int flags); 22 | static void rtnl_handle_link(const struct nlmsghdr *nh); 23 | 24 | int parse_rtattr_flags(struct rtattr *tb[], int max, struct rtattr *rta, int len, unsigned short flags) { 25 | unsigned short type; 26 | 27 | while (RTA_OK(rta, len)) { 28 | type = rta->rta_type & ~flags; 29 | if ((type <= max) && (!tb[type])) { 30 | tb[type] = rta; 31 | } 32 | rta = RTA_NEXT(rta, len); 33 | } 34 | if (len) 35 | fprintf(stderr, "!!!Deficit %d, rta_len=%d\n", len, rta->rta_len); 36 | return 0; 37 | } 38 | 39 | int parse_rtattr(struct rtattr *tb[], int max, struct rtattr *rta, int len) { 40 | return parse_rtattr_flags(tb, max, rta, len, 0); 41 | } 42 | 43 | void rtmgr_client_probe_addresses(struct client *client) { 44 | for (size_t i = 0; i < VECTOR_LEN(client->addresses); i++) { 45 | routemgr_probe_neighbor(&l3ctx.routemgr_ctx, client->ifindex, &VECTOR_INDEX(client->addresses, i).addr, 46 | client->mac); 47 | } 48 | } 49 | 50 | void rtmgr_client_remove_address(struct in6_addr *dst_address) { 51 | struct client *_client = NULL; 52 | log_debug("removing address %s\n", print_ip(dst_address)); 53 | if (clientmgr_is_known_address(&l3ctx.clientmgr_ctx, dst_address, &_client)) { 54 | log_debug("removing address %s from client [%s]\n", print_ip(dst_address), print_mac(_client->mac)); 55 | clientmgr_remove_address(&l3ctx.clientmgr_ctx, _client, dst_address); 56 | rtmgr_client_probe_addresses(_client); 57 | } else { 58 | log_debug("removal of address %s was scheduled but corresponding client could not be identified.\n", 59 | print_ip(dst_address)); 60 | } 61 | } 62 | 63 | void rtmgr_remove_addr_task(void *dst_address) { 64 | rtmgr_client_remove_address((struct in6_addr*)dst_address); 65 | } 66 | 67 | void copy_rtmgr_task(struct rtmgr_task *old, struct rtmgr_task *new) { 68 | memcpy(&new->address, &old->address, sizeof(struct in6_addr)); 69 | new->retries_left = old->retries_left; 70 | new->family = old->family; 71 | } 72 | 73 | void schedule_rtmgr_retries(struct rtmgr_task *data, int ms_timeout, void (*processor)(void *data)) { 74 | struct rtmgr_task *ndata = l3roamd_alloc(sizeof(struct intercom_task)); 75 | copy_rtmgr_task(data, ndata); 76 | ndata->retries_left--; 77 | 78 | post_task(&l3ctx.taskqueue_ctx, 0, ms_timeout, processor, free, ndata); 79 | } 80 | 81 | void send_ns_task(void *d) { 82 | struct rtmgr_task *td = d; 83 | 84 | log_debug("sending scheduled ns to %s\n", print_ip(&td->address)); 85 | 86 | if (td->family == AF_INET) { 87 | arp_send_request(&l3ctx.arp_ctx, &td->address); 88 | } else { 89 | icmp6_send_solicitation(&l3ctx.icmp6_ctx, &td->address); 90 | } 91 | 92 | if (td->retries_left > 0) 93 | schedule_rtmgr_retries(td, 300, send_ns_task); 94 | } 95 | 96 | bool ns_retry(struct in6_addr *dst_address, int family) { 97 | struct rtmgr_task task_data; 98 | task_data.retries_left = 4; 99 | task_data.family = family; 100 | memcpy(&task_data.address, dst_address, sizeof(struct in6_addr)); 101 | send_ns_task(&task_data); 102 | return true; 103 | } 104 | 105 | struct client *schedule_removal(struct in6_addr *dst_address) { 106 | struct client *client = NULL; 107 | if (clientmgr_is_known_address(&l3ctx.clientmgr_ctx, dst_address, &client)) { 108 | struct client_ip *ip = NULL; 109 | ip = get_client_ip(client, dst_address); 110 | 111 | if (ip && !ip->removal_task) { 112 | log_debug("REMOVING (DELNEIGH/NUD_FAILED) %s [%s] in 2 Minutes.\n", print_ip(&ip->addr), 113 | print_mac(client->mac)); 114 | struct in6_addr *task_data_addr = l3roamd_alloc(sizeof(struct in6_addr)); 115 | memcpy(task_data_addr, dst_address, sizeof(struct in6_addr)); 116 | ip->removal_task = 117 | post_task(&l3ctx.taskqueue_ctx, 120, 0, rtmgr_remove_addr_task, free, task_data_addr); 118 | } 119 | return client; 120 | } 121 | return NULL; 122 | } 123 | 124 | void rtnl_handle_neighbour(routemgr_ctx *ctx, const struct nlmsghdr *nh) { 125 | struct rtattr *tb[NDA_MAX + 1]; 126 | memset(tb, 0, sizeof(struct rtattr *) * (NDA_MAX + 1)); 127 | char mac_str[18] = {}; 128 | char ip_str[INET6_ADDRSTRLEN] = {}; 129 | 130 | struct ndmsg *msg = NLMSG_DATA(nh); 131 | parse_rtattr(tb, NDA_MAX, NDA_RTA(msg), nh->nlmsg_len - NLMSG_LENGTH(sizeof(*msg))); 132 | 133 | if (!(ctx->clientif.index == msg->ndm_ifindex || ctx->client_bridge.index == msg->ndm_ifindex)) 134 | return; 135 | 136 | if (tb[NDA_LLADDR]) 137 | memcpy(mac_str, print_mac(RTA_DATA(tb[NDA_LLADDR])), 18); 138 | else // The only thing we could do without mac is send arp to an ip 139 | // address. whenever there is an ip, there is also a mac 140 | return; 141 | 142 | struct in6_addr dst_address = {}; 143 | 144 | if (tb[NDA_DST]) { 145 | if (msg->ndm_family == AF_INET) { 146 | mapv4_v6(RTA_DATA(tb[NDA_DST]), &dst_address); 147 | } else { 148 | memcpy(&dst_address, RTA_DATA(tb[NDA_DST]), 16); 149 | } 150 | 151 | inet_ntop(AF_INET6, &dst_address, ip_str, INET6_ADDRSTRLEN); 152 | } 153 | 154 | char ifname[IFNAMSIZ + 1] = ""; 155 | log_debug("neighbour [%s] (%s) changed on interface %s, type: %i, state: %i ... (msgif: %i cif: %i brif: %i)\n", 156 | mac_str, ip_str, if_indextoname(msg->ndm_ifindex, ifname), nh->nlmsg_type, msg->ndm_state, 157 | msg->ndm_ifindex, ctx->clientif.index, 158 | ctx->client_bridge.index); // see include/uapi/linux/neighbour.h NUD_REACHABLE for numeric values 159 | 160 | if ((nh->nlmsg_type == RTM_NEWNEIGH) && (msg->ndm_state & NUD_REACHABLE)) { 161 | log_debug("Status-Change to NUD_REACHABLE, notifying change for client-mac [%s]\n", mac_str); 162 | clientmgr_notify_mac(&l3ctx.clientmgr_ctx, RTA_DATA(tb[NDA_LLADDR]), msg->ndm_ifindex); 163 | if (tb[NDA_DST]) { 164 | log_debug("Status-Change to NUD_REACHABLE, ADDING address %s [%s]\n", ip_str, mac_str); 165 | clientmgr_add_address(&l3ctx.clientmgr_ctx, &dst_address, RTA_DATA(tb[NDA_LLADDR]), msg->ndm_ifindex); 166 | } 167 | } else if ((nh->nlmsg_type == RTM_NEWNEIGH) && (msg->ndm_state & ( NUD_PROBE | NUD_FAILED))) { 168 | log_debug("NEWNEIGH and NUD_PROBE or NUD_FAILED received - scheduling removal and attempting to re-activate %s [%s]\n", 169 | ip_str, mac_str); 170 | if (schedule_removal(&dst_address)) { 171 | // we cannot directly use probe here because that would lead to an endless loop. 172 | log_debug( 173 | "NEWNEIGH and either NUD_PROBE or NUD_FAILED received for one of our clients [%s] IPs (%s) - sending NS\n", 174 | mac_str, ip_str); 175 | ns_retry(&dst_address, msg->ndm_family); 176 | } 177 | log_debug("ending handling of NUD_PROBE / NUD_FAILED\n"); 178 | } else if (nh->nlmsg_type == RTM_DELNEIGH) { 179 | struct client *client = schedule_removal(&dst_address); 180 | if (client) 181 | rtmgr_client_probe_addresses(client); 182 | } else if (msg->ndm_state & NUD_NOARP) { 183 | log_debug("REMOVING (NOARP) %s [%s] now\n", ip_str, mac_str); 184 | rtmgr_client_remove_address(&dst_address); 185 | } 186 | } 187 | 188 | void client_bridge_changed(const struct nlmsghdr *nh, const struct ifinfomsg *msg) { 189 | struct rtattr *tb[IFLA_MAX + 1]; 190 | memset(tb, 0, sizeof(struct rtattr *) * (IFLA_MAX + 1)); 191 | char ifname[IFNAMSIZ]; 192 | if (if_indextoname(msg->ifi_index, ifname) == 0) 193 | return; 194 | 195 | if (!strncmp(ifname, l3ctx.routemgr_ctx.client_bridge.ifname, strlen(ifname))) { 196 | parse_rtattr(tb, IFLA_MAX, IFLA_RTA(msg), nh->nlmsg_len - NLMSG_LENGTH(sizeof(*msg))); 197 | 198 | if (!tb[IFLA_ADDRESS]) { 199 | log_debug( 200 | "client_bridge_changed called but mac could not be " 201 | "extracted - ignoring event.\n"); 202 | return; 203 | } 204 | 205 | if (!memcmp(RTA_DATA(tb[IFLA_ADDRESS]), l3ctx.routemgr_ctx.client_bridge.mac, 6)) { 206 | log_debug( 207 | "client_bridge_changed called, change detected BUT " 208 | "mac [%s] address is the mac of the bridge, not " 209 | "triggering any client actions\n", 210 | print_mac(RTA_DATA(tb[IFLA_ADDRESS]))); 211 | return; 212 | } 213 | 214 | switch (nh->nlmsg_type) { 215 | case RTM_NEWLINK: 216 | log_verbose("new station [%s] found in fdb on interface %s\n", 217 | print_mac(RTA_DATA(tb[IFLA_ADDRESS])), ifname); 218 | clientmgr_notify_mac(&l3ctx.clientmgr_ctx, RTA_DATA(tb[IFLA_ADDRESS]), msg->ifi_index); 219 | break; 220 | 221 | case RTM_SETLINK: 222 | log_verbose("set link %i\n", msg->ifi_index); 223 | break; 224 | 225 | case RTM_DELLINK: 226 | log_verbose("del link on %i, fdb-entry was removed for [%s].\n", msg->ifi_index, 227 | print_mac(RTA_DATA(tb[IFLA_ADDRESS]))); 228 | clientmgr_delete_client(&l3ctx.clientmgr_ctx, RTA_DATA(tb[IFLA_ADDRESS])); 229 | break; 230 | } 231 | } 232 | } 233 | 234 | void rtnl_handle_link(const struct nlmsghdr *nh) { 235 | const struct ifinfomsg *msg = NLMSG_DATA(nh); 236 | 237 | if (l3ctx.clientif_set) 238 | client_bridge_changed(nh, msg); 239 | 240 | interfaces_changed(nh->nlmsg_type, msg); 241 | } 242 | 243 | bool handle_kernel_routes(const struct nlmsghdr *nh, struct kernel_route *route) { 244 | int len = nh->nlmsg_len; 245 | struct rtmsg *rtm; 246 | 247 | rtm = (struct rtmsg *)NLMSG_DATA(nh); 248 | len -= NLMSG_LENGTH(0); 249 | 250 | /* Ignore cached routes, advertised by some kernels (linux 3.x). */ 251 | if (rtm->rtm_flags & RTM_F_CLONED) 252 | return false; 253 | 254 | if (parse_kernel_route_rta(rtm, len, route) < 0) 255 | return false; 256 | 257 | /* Ignore default unreachable routes; no idea where they come from. */ 258 | if (route->plen == 0 && route->metric >= KERNEL_INFINITY) 259 | return false; 260 | 261 | /* only interested in host routes */ 262 | if ((route->plen != 128)) 263 | return false; 264 | return true; 265 | } 266 | 267 | void rtnl_handle_msg(routemgr_ctx *ctx, const struct nlmsghdr *nh) { 268 | if (!nh || ctx->nl_disabled) 269 | return; 270 | 271 | switch (nh->nlmsg_type) { 272 | case RTM_NEWROUTE: 273 | // case RTM_DELROUTE: 274 | log_debug("handling netlink message for route change\n"); 275 | struct kernel_route route; 276 | if (handle_kernel_routes(nh, &route)) 277 | if (clientmgr_valid_address(&l3ctx.clientmgr_ctx, &route.prefix)) 278 | ipmgr_route_appeared(&l3ctx.ipmgr_ctx, &route.prefix); 279 | break; 280 | case RTM_NEWNEIGH: 281 | case RTM_DELNEIGH: 282 | log_debug("handling netlink message for neighbour change\n"); 283 | rtnl_handle_neighbour(ctx, nh); 284 | break; 285 | case RTM_NEWLINK: 286 | case RTM_DELLINK: 287 | case RTM_SETLINK: 288 | log_debug("handling netlink message for link change\n"); 289 | rtnl_handle_link(nh); 290 | break; 291 | default: 292 | log_debug("not handling unknown netlink message with type: %i\n", nh->nlmsg_type); 293 | return; 294 | } 295 | } 296 | 297 | /* obtain all neighbours by sending GETNEIGH request 298 | **/ 299 | static void routemgr_initial_neighbours(routemgr_ctx *ctx, uint8_t family) { 300 | struct nlneighreq req = {.nl = 301 | { 302 | .nlmsg_type = RTM_GETNEIGH, 303 | .nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP, 304 | .nlmsg_len = NLMSG_LENGTH(sizeof(struct ndmsg)), 305 | }, 306 | .nd = 307 | { 308 | .ndm_family = family, 309 | } 310 | 311 | }; 312 | rtmgr_rtnl_talk(ctx->fd, (struct nlmsghdr *)&req); 313 | } 314 | 315 | void routemgr_init(routemgr_ctx *ctx) { 316 | log_verbose("initializing routemgr\n"); 317 | ctx->fd = socket(AF_NETLINK, SOCK_RAW | SOCK_NONBLOCK, NETLINK_ROUTE); 318 | if (ctx->fd < 0) 319 | exit_error("can't open RTNL socket"); 320 | 321 | struct sockaddr_nl snl = { 322 | .nl_family = AF_NETLINK, .nl_groups = RTMGRP_IPV6_ROUTE | RTMGRP_LINK | RTMGRP_IPV4_ROUTE, 323 | }; 324 | 325 | if (l3ctx.clientif_set) 326 | snl.nl_groups |= RTMGRP_NEIGH; 327 | 328 | if (bind(ctx->fd, (struct sockaddr *)&snl, sizeof(snl)) < 0) 329 | exit_error("can't bind RTNL socket"); 330 | 331 | for (size_t i = 0; i < VECTOR_LEN(l3ctx.clientmgr_ctx.prefixes); i++) { 332 | struct prefix *prefix = &(VECTOR_INDEX(l3ctx.clientmgr_ctx.prefixes, i)); 333 | log_verbose("Activating route for prefix %s/%i on device %s(%i) in main routing-table\n", 334 | print_ip(&prefix->prefix), prefix->plen, l3ctx.ipmgr_ctx.ifname, 335 | if_nametoindex(l3ctx.ipmgr_ctx.ifname)); 336 | 337 | if (prefix->isv4) { 338 | struct in_addr ip4 = extractv4_v6(&prefix->prefix); 339 | log_verbose("ipv4: %s\n", print_ip4(&ip4)); 340 | routemgr_insert_route4(ctx, 254, if_nametoindex(l3ctx.ipmgr_ctx.ifname), &ip4, prefix->plen - 96); 341 | } else 342 | routemgr_insert_route(ctx, 254, if_nametoindex(l3ctx.ipmgr_ctx.ifname), 343 | (struct in6_addr *)(prefix->prefix.s6_addr), prefix->plen); 344 | } 345 | 346 | if (!l3ctx.clientif_set) { 347 | log_error("warning: we were started without -i - not initializing any client interfaces.\n"); 348 | return; 349 | } 350 | 351 | 352 | obtain_mac_from_if(ctx->client_bridge.mac, ctx->client_bridge.ifname); 353 | 354 | ctx->clientif.index = if_nametoindex(ctx->clientif.ifname); 355 | ctx->client_bridge.index = if_nametoindex(ctx->client_bridge.ifname); 356 | 357 | routemgr_initial_neighbours(ctx, AF_INET); 358 | routemgr_initial_neighbours(ctx, AF_INET6); 359 | } 360 | 361 | int parse_kernel_route_rta(struct rtmsg *rtm, int len, struct kernel_route *route) { 362 | len -= NLMSG_ALIGN(sizeof(*rtm)); 363 | 364 | memset(route, 0, sizeof(struct kernel_route)); 365 | route->proto = rtm->rtm_protocol; 366 | 367 | for (struct rtattr *rta = RTM_RTA(rtm); RTA_OK(rta, len); rta = RTA_NEXT(rta, len)) { 368 | switch (rta->rta_type) { 369 | case RTA_DST: 370 | if (rtm->rtm_family == AF_INET6) { 371 | route->plen = rtm->rtm_dst_len; 372 | memcpy(route->prefix.s6_addr, RTA_DATA(rta), 16); 373 | 374 | } else if (rtm->rtm_family == AF_INET) { 375 | struct in_addr ipv4; 376 | memcpy(&ipv4.s_addr, RTA_DATA(rta), 4); 377 | mapv4_v6(&ipv4, &route->prefix); 378 | route->plen = rtm->rtm_dst_len + 96; 379 | } 380 | log_debug("parsed route, found dst: %s\n", print_ip(&route->prefix)); 381 | break; 382 | case RTA_SRC: 383 | if (rtm->rtm_family == AF_INET6) { 384 | route->src_plen = rtm->rtm_src_len; 385 | memcpy(route->src_prefix.s6_addr, RTA_DATA(rta), 16); 386 | } else if (rtm->rtm_family == AF_INET) { 387 | struct in_addr ipv4; 388 | memcpy(&ipv4.s_addr, RTA_DATA(rta), 4); 389 | mapv4_v6(&ipv4, &route->src_prefix); 390 | route->plen = rtm->rtm_src_len + 96; 391 | } 392 | break; 393 | case RTA_GATEWAY: 394 | if (rtm->rtm_family == AF_INET6) { 395 | memcpy(route->gw.s6_addr, RTA_DATA(rta), 16); 396 | } else if (rtm->rtm_family == AF_INET) { 397 | struct in_addr ipv4; 398 | memcpy(&ipv4.s_addr, RTA_DATA(rta), 4); 399 | mapv4_v6(&ipv4, &route->prefix); 400 | } 401 | break; 402 | case RTA_OIF: 403 | route->ifindex = *(int *)RTA_DATA(rta); 404 | break; 405 | case RTA_PRIORITY: 406 | route->metric = *(int *)RTA_DATA(rta); 407 | if (route->metric < 0 || route->metric > KERNEL_INFINITY) 408 | route->metric = KERNEL_INFINITY; 409 | break; 410 | default: 411 | break; 412 | } 413 | } 414 | 415 | return 1; 416 | } 417 | 418 | void routemgr_handle_in(routemgr_ctx *ctx, int fd) { 419 | log_debug("handling routemgr_in event "); 420 | ssize_t count; 421 | struct nlmsghdr readbuffer[8192/sizeof(struct nlmsghdr)]; 422 | 423 | struct nlmsghdr *nh; 424 | struct nlmsgerr *ne; 425 | while (1) { 426 | count = recv(fd, readbuffer, sizeof readbuffer, 0); 427 | if ((count == -1) && (errno != EAGAIN)) { 428 | perror("read error"); 429 | break; 430 | } else if (count == -1) { 431 | break; // errno must be EAGAIN - we have read all data. 432 | } else if (count <= 0) 433 | break; // TODO: shouldn't we re-open the fd in this 434 | // case? 435 | 436 | log_debug("read %zi Bytes from netlink socket, readbuffer-size is %zi, ... parsing data now.\n", count, 437 | sizeof(readbuffer)); 438 | 439 | nh = (struct nlmsghdr *)readbuffer; 440 | if (NLMSG_OK(nh, count)) { 441 | switch (nh->nlmsg_type) { 442 | case NLMSG_DONE: 443 | continue; 444 | case NLMSG_ERROR: 445 | ne = NLMSG_DATA(nh); 446 | if (ne->error <= 0) 447 | continue; 448 | /* Falls through. */ 449 | default: 450 | rtnl_handle_msg(ctx, nh); 451 | } 452 | } 453 | } 454 | } 455 | 456 | 457 | int rtnl_addattr(struct nlmsghdr *n, size_t maxlen, int type, void *data, int datalen) { 458 | size_t len = RTA_LENGTH(datalen); 459 | struct rtattr *rta; 460 | if (NLMSG_ALIGN(n->nlmsg_len) + len > maxlen) 461 | return -1; 462 | rta = (struct rtattr *)(((char *)n) + NLMSG_ALIGN(n->nlmsg_len)); 463 | rta->rta_type = type; 464 | rta->rta_len = len; 465 | memcpy(RTA_DATA(rta), data, datalen); 466 | n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + len; 467 | return 0; 468 | } 469 | 470 | void rtnl_add_address(routemgr_ctx *ctx, struct in6_addr *address) { 471 | log_debug("Adding special address to lo: %s\n", print_ip(address)); 472 | rtnl_change_address(ctx, address, RTM_NEWADDR, NLM_F_CREATE | NLM_F_EXCL | NLM_F_REQUEST); 473 | } 474 | 475 | void rtnl_remove_address(routemgr_ctx *ctx, struct in6_addr *address) { 476 | rtnl_change_address(ctx, address, RTM_DELADDR, NLM_F_REQUEST | NLM_F_ACK); 477 | } 478 | 479 | void rtnl_change_address(routemgr_ctx *ctx, struct in6_addr *address, int type, int flags) { 480 | struct { 481 | struct nlmsghdr nl; 482 | struct ifaddrmsg ifa; 483 | char buf[1024]; 484 | } req = {.nl = 485 | { 486 | .nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg)), .nlmsg_type = type, .nlmsg_flags = flags, 487 | }, 488 | .ifa = 489 | { 490 | .ifa_family = AF_INET6, 491 | .ifa_prefixlen = 128, 492 | .ifa_index = 1, // get the loopback index 493 | .ifa_scope = 0, 494 | }}; 495 | 496 | rtnl_addattr(&req.nl, sizeof(req), IFA_LOCAL, address, sizeof(struct in6_addr)); 497 | 498 | rtmgr_rtnl_talk(ctx->fd, (struct nlmsghdr *)&req); 499 | } 500 | 501 | void routemgr_probe_neighbor(routemgr_ctx *ctx, const int ifindex, struct in6_addr *address, uint8_t mac[ETH_ALEN]) { 502 | int family = AF_INET6; 503 | size_t addr_len = 16; 504 | void *addr = address->s6_addr; 505 | 506 | if (address_is_ipv4(address)) { 507 | log_debug("probing for IPv4-address! %s\n", print_ip(addr)); 508 | addr = address->s6_addr + 12; 509 | addr_len = 4; 510 | family = AF_INET; 511 | } else { 512 | log_debug("probing for IPv6-address! %s\n", print_ip(address)); 513 | } 514 | 515 | struct nlneighreq req = { 516 | .nl = 517 | { 518 | .nlmsg_type = RTM_NEWNEIGH, 519 | .nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_REPLACE, 520 | .nlmsg_len = NLMSG_LENGTH(sizeof(struct ndmsg)), 521 | }, 522 | .nd = 523 | { 524 | .ndm_family = family, .ndm_state = NUD_PROBE, .ndm_ifindex = ifindex, 525 | }, 526 | }; 527 | 528 | rtnl_addattr(&req.nl, sizeof(req), NDA_DST, (void *)addr, addr_len); 529 | rtnl_addattr(&req.nl, sizeof(req), NDA_LLADDR, mac, sizeof(uint8_t) * 6); 530 | 531 | rtmgr_rtnl_talk(ctx->fd, (struct nlmsghdr *)&req); 532 | } 533 | 534 | void routemgr_insert_neighbor(routemgr_ctx *ctx, const int ifindex, struct in6_addr *address, uint8_t mac[ETH_ALEN]) { 535 | struct nlneighreq req = { 536 | .nl = 537 | { 538 | .nlmsg_type = RTM_NEWNEIGH, 539 | .nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_REPLACE, 540 | .nlmsg_len = NLMSG_LENGTH(sizeof(struct ndmsg)), 541 | }, 542 | .nd = 543 | { 544 | .ndm_family = AF_INET6, .ndm_state = NUD_REACHABLE, .ndm_ifindex = ifindex, 545 | }, 546 | }; 547 | 548 | rtnl_addattr(&req.nl, sizeof(req), NDA_DST, (void *)address, sizeof(struct in6_addr)); 549 | rtnl_addattr(&req.nl, sizeof(req), NDA_LLADDR, mac, sizeof(uint8_t) * 6); 550 | 551 | rtmgr_rtnl_talk(ctx->fd, (struct nlmsghdr *)&req); 552 | } 553 | 554 | void routemgr_remove_neighbor(routemgr_ctx *ctx, const int ifindex, struct in6_addr *address, uint8_t mac[ETH_ALEN]) { 555 | struct nlneighreq req = { 556 | .nl = 557 | { 558 | .nlmsg_type = RTM_DELNEIGH, 559 | .nlmsg_flags = NLM_F_REQUEST | NLM_F_REPLACE, 560 | .nlmsg_len = NLMSG_LENGTH(sizeof(struct ndmsg)), 561 | }, 562 | .nd = {.ndm_family = AF_INET6, .ndm_ifindex = ifindex, .ndm_flags = NTF_PROXY}, 563 | }; 564 | 565 | rtnl_addattr(&req.nl, sizeof(req), NDA_DST, (void *)address, sizeof(struct in6_addr)); 566 | rtnl_addattr(&req.nl, sizeof(req), NDA_LLADDR, mac, sizeof(uint8_t) * 6); 567 | 568 | rtmgr_rtnl_talk(ctx->fd, (struct nlmsghdr *)&req); 569 | } 570 | 571 | void routemgr_insert_route(routemgr_ctx *ctx, const int table, const int ifindex, struct in6_addr *address, 572 | const int prefix_length) { 573 | struct nlrtreq req = { 574 | .nl = 575 | { 576 | .nlmsg_type = RTM_NEWROUTE, 577 | .nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_REPLACE, 578 | .nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)), 579 | }, 580 | .rt = {.rtm_family = AF_INET6, 581 | .rtm_table = table, 582 | .rtm_protocol = ROUTE_PROTO, 583 | .rtm_scope = RT_SCOPE_UNIVERSE, 584 | .rtm_type = RTN_UNICAST, 585 | .rtm_dst_len = prefix_length}, 586 | }; 587 | 588 | rtnl_addattr(&req.nl, sizeof(req), RTA_DST, (void *)address, sizeof(struct in6_addr)); 589 | rtnl_addattr(&req.nl, sizeof(req), RTA_OIF, (void *)&ifindex, sizeof(ifindex)); 590 | 591 | rtmgr_rtnl_talk(ctx->fd, (struct nlmsghdr *)&req); 592 | } 593 | 594 | void routemgr_remove_route(routemgr_ctx *ctx, const int table, struct in6_addr *address, const int prefix_length) { 595 | struct nlrtreq req1 = { 596 | .nl = 597 | { 598 | .nlmsg_type = RTM_NEWROUTE, 599 | .nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_REPLACE, 600 | .nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)), 601 | }, 602 | .rt = {.rtm_family = AF_INET6, .rtm_table = table, .rtm_type = RTN_THROW, .rtm_dst_len = prefix_length}}; 603 | 604 | rtnl_addattr(&req1.nl, sizeof(req1), RTA_DST, (void *)address, sizeof(struct in6_addr)); 605 | rtmgr_rtnl_talk(ctx->fd, (struct nlmsghdr *)&req1); 606 | 607 | struct nlrtreq req2 = {.nl = 608 | { 609 | .nlmsg_type = RTM_DELROUTE, 610 | .nlmsg_flags = NLM_F_REQUEST, 611 | .nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)), 612 | }, 613 | .rt = {.rtm_family = AF_INET6, .rtm_table = table, .rtm_dst_len = prefix_length}}; 614 | 615 | rtnl_addattr(&req2.nl, sizeof(req2), RTA_DST, (void *)address, sizeof(struct in6_addr)); 616 | rtmgr_rtnl_talk(ctx->fd, (struct nlmsghdr *)&req2); 617 | } 618 | 619 | void rtmgr_rtnl_talk(int fd, struct nlmsghdr *req) { 620 | struct sockaddr_nl nladdr = {.nl_family = AF_NETLINK}; 621 | 622 | struct iovec iov = {req, 0}; 623 | struct msghdr msg = {&nladdr, sizeof(nladdr), &iov, 1, NULL, 0, 0}; 624 | 625 | iov.iov_len = req->nlmsg_len; 626 | 627 | int count = 0; 628 | while (sendmsg(fd, &msg, 0) <= 0 && count < 5) { 629 | fprintf(stderr, "retrying(%i/5) ", ++count); 630 | perror("sendmsg on rtmgr_rtnl_talk()"); 631 | if (errno == EBADF) { 632 | del_fd(l3ctx.efd, fd); 633 | close(fd); 634 | routemgr_init(&l3ctx.routemgr_ctx); 635 | add_fd(l3ctx.efd, l3ctx.routemgr_ctx.fd, EPOLLIN); 636 | } 637 | } 638 | } 639 | 640 | void routemgr_insert_neighbor4(routemgr_ctx *ctx, const int ifindex, struct in_addr *address, uint8_t mac[ETH_ALEN]) { 641 | struct nlneighreq req = { 642 | .nl = 643 | { 644 | .nlmsg_type = RTM_NEWNEIGH, 645 | .nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_REPLACE, 646 | .nlmsg_len = NLMSG_LENGTH(sizeof(struct ndmsg)), 647 | }, 648 | .nd = 649 | { 650 | .ndm_family = AF_INET, .ndm_state = NUD_REACHABLE, .ndm_ifindex = ifindex, 651 | }, 652 | }; 653 | 654 | rtnl_addattr(&req.nl, sizeof(req), NDA_DST, (void *)address, sizeof(struct in_addr)); 655 | rtnl_addattr(&req.nl, sizeof(req), NDA_LLADDR, mac, sizeof(uint8_t) * 6); 656 | 657 | rtmgr_rtnl_talk(ctx->fd, (struct nlmsghdr *)&req); 658 | } 659 | 660 | void routemgr_remove_neighbor4(routemgr_ctx *ctx, const int ifindex, struct in_addr *address, uint8_t mac[ETH_ALEN]) { 661 | struct nlneighreq req = { 662 | .nl = 663 | { 664 | .nlmsg_type = RTM_NEWNEIGH, 665 | .nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_REPLACE, 666 | .nlmsg_len = NLMSG_LENGTH(sizeof(struct ndmsg)), 667 | }, 668 | .nd = 669 | { 670 | .ndm_family = AF_INET, .ndm_state = NUD_NONE, .ndm_ifindex = ifindex, 671 | }, 672 | }; 673 | 674 | rtnl_addattr(&req.nl, sizeof(req), NDA_DST, (void *)address, sizeof(struct in_addr)); 675 | rtnl_addattr(&req.nl, sizeof(req), NDA_LLADDR, mac, sizeof(uint8_t) * 6); 676 | 677 | rtmgr_rtnl_talk(ctx->fd, (struct nlmsghdr *)&req); 678 | } 679 | 680 | void routemgr_insert_route4(routemgr_ctx *ctx, const int table, const int ifindex, struct in_addr *address, 681 | const int plen) { 682 | struct nlrtreq req = { 683 | .nl = 684 | { 685 | .nlmsg_type = RTM_NEWROUTE, 686 | .nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_REPLACE, 687 | .nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)), 688 | }, 689 | .rt = {.rtm_family = AF_INET, 690 | .rtm_table = table, 691 | .rtm_protocol = ROUTE_PROTO, 692 | .rtm_scope = RT_SCOPE_UNIVERSE, 693 | .rtm_type = RTN_UNICAST, 694 | .rtm_dst_len = plen}, 695 | }; 696 | 697 | rtnl_addattr(&req.nl, sizeof(req), RTA_DST, (void *)address, sizeof(struct in_addr)); 698 | rtnl_addattr(&req.nl, sizeof(req), RTA_OIF, (void *)&ifindex, sizeof(ifindex)); 699 | 700 | rtmgr_rtnl_talk(ctx->fd, (struct nlmsghdr *)&req); 701 | } 702 | 703 | void routemgr_remove_route4(routemgr_ctx *ctx, const int table, struct in_addr *address, const int plen) { 704 | struct nlrtreq req1 = { 705 | .nl = 706 | { 707 | .nlmsg_type = RTM_NEWROUTE, 708 | .nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_REPLACE, 709 | .nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)), 710 | }, 711 | .rt = {.rtm_family = AF_INET, .rtm_table = table, .rtm_type = RTN_THROW, .rtm_dst_len = plen}}; 712 | 713 | rtnl_addattr(&req1.nl, sizeof(req1), RTA_DST, (void *)&address[12], sizeof(struct in_addr)); 714 | rtmgr_rtnl_talk(ctx->fd, (struct nlmsghdr *)&req1); 715 | 716 | struct nlrtreq req2 = {.nl = 717 | { 718 | .nlmsg_type = RTM_DELROUTE, 719 | .nlmsg_flags = NLM_F_REQUEST, 720 | .nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)), 721 | }, 722 | .rt = {.rtm_family = AF_INET, .rtm_table = table, .rtm_dst_len = 32}}; 723 | 724 | rtnl_addattr(&req2.nl, sizeof(req2), RTA_DST, (void *)address, sizeof(struct in_addr)); 725 | rtmgr_rtnl_talk(ctx->fd, (struct nlmsghdr *)&req2); 726 | } 727 | -------------------------------------------------------------------------------- /src/routemgr.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of project l3roamd. It's copyrighted by the contributors 3 | * recorded in the version control history of the file, available from 4 | * its original location https://github.com/freifunk-gluon/l3roamd. 5 | * 6 | * SPDX-License-Identifier: BSD-2-Clause 7 | */ 8 | #pragma once 9 | 10 | #include "common.h" 11 | #include "if.h" 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | #define KERNEL_INFINITY 0xffff 18 | #define ROUTE_PROTO 158 19 | 20 | #ifndef NDA_RTA 21 | #define NDA_RTA(r) \ 22 | ((struct rtattr *)(((char *)(r)) + NLMSG_ALIGN(sizeof(struct ndmsg)))) 23 | #endif 24 | 25 | static inline __u32 rta_getattr_u32(const struct rtattr *rta) { 26 | return *(__u32 *)RTA_DATA(rta); 27 | } 28 | 29 | struct nlrtreq { 30 | struct nlmsghdr nl; 31 | struct rtmsg rt; 32 | char buf[1024]; 33 | }; 34 | 35 | struct nlneighreq { 36 | struct nlmsghdr nl; 37 | struct ndmsg nd; 38 | char buf[1024]; 39 | }; 40 | 41 | struct kernel_route { 42 | struct in6_addr prefix; 43 | struct in6_addr src_prefix; 44 | struct in6_addr gw; 45 | int plen; 46 | int src_plen; /* no source prefix <=> src_plen == 0 */ 47 | int metric; 48 | int proto; 49 | unsigned int ifindex; 50 | unsigned int table; 51 | }; 52 | 53 | struct rtmgr_task { 54 | struct in6_addr address; 55 | int retries_left; 56 | int family; 57 | }; 58 | 59 | typedef struct { 60 | int index; 61 | uint8_t mac[6]; 62 | char ifname[IFNAMSIZ]; 63 | } device; 64 | 65 | typedef struct { 66 | int fd; 67 | device clientif; 68 | device client_bridge; 69 | bool nl_disabled; 70 | } routemgr_ctx; 71 | 72 | void handle_route(routemgr_ctx *ctx, struct kernel_route *route); 73 | int parse_kernel_route_rta(struct rtmsg *rtm, int len, 74 | struct kernel_route *route); 75 | void routemgr_handle_in(routemgr_ctx *ctx, int fd); 76 | void routemgr_init(routemgr_ctx *ctx); 77 | void routemgr_probe_neighbor(routemgr_ctx *ctx, const int ifindex, 78 | struct in6_addr *address, uint8_t mac[ETH_ALEN]); 79 | void routemgr_insert_neighbor(routemgr_ctx *ctx, const int ifindex, 80 | struct in6_addr *address, uint8_t mac[ETH_ALEN]); 81 | void routemgr_remove_neighbor(routemgr_ctx *ctx, const int ifindex, 82 | struct in6_addr *address, uint8_t mac[ETH_ALEN]); 83 | void routemgr_insert_route(routemgr_ctx *ctx, const int table, 84 | const int ifindex, struct in6_addr *address, 85 | const int prefix_length); 86 | void routemgr_remove_route(routemgr_ctx *ctx, const int table, 87 | struct in6_addr *address, const int prefix_length); 88 | void routemgr_insert_neighbor4(routemgr_ctx *ctx, const int ifindex, 89 | struct in_addr *address, uint8_t mac[ETH_ALEN]); 90 | void routemgr_remove_neighbor4(routemgr_ctx *ctx, const int ifindex, 91 | struct in_addr *address, uint8_t mac[ETH_ALEN]); 92 | void routemgr_insert_route4(routemgr_ctx *ctx, const int table, 93 | const int ifindex, struct in_addr *address, 94 | const int prefix_length); 95 | void routemgr_remove_route4(routemgr_ctx *ctx, const int table, 96 | struct in_addr *address, const int prefix_length); 97 | void rtnl_add_address(routemgr_ctx *ctx, struct in6_addr *address); 98 | void rtnl_remove_address(routemgr_ctx *ctx, struct in6_addr *address); 99 | 100 | 101 | // some netlink functionality that will also be used by other parts. this means 102 | // we should move netlink functionality somewhere else. 103 | int rtnl_addattr(struct nlmsghdr *n, size_t maxlen, int type, void *data, int datalen); 104 | void rtmgr_rtnl_talk(int fd, struct nlmsghdr *req); 105 | void rtmgr_client_remove_address(struct in6_addr *dst_address); 106 | bool handle_kernel_routes(const struct nlmsghdr *nh, struct kernel_route *route); 107 | -------------------------------------------------------------------------------- /src/socket.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, Christof Schulze 3 | * 4 | * This file is part of project l3roamd. It's copyrighted by the contributors 5 | * recorded in the version control history of the file, available from 6 | * its original location https://github.com/freifunk-gluon/l3roamd. 7 | * 8 | * SPDX-License-Identifier: BSD-2-Clause 9 | */ 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #include "clientmgr.h" 16 | #include "error.h" 17 | #include "intercom.h" 18 | #include "l3roamd.h" 19 | #include "prefix.h" 20 | #include "routemgr.h" 21 | #include "socket.h" 22 | #include "util.h" 23 | 24 | void socket_init(socket_ctx *ctx, char *path) { 25 | if (!path) { 26 | ctx->fd = -1; 27 | return; 28 | } 29 | 30 | printf("Initializing unix socket: %s\n", path); 31 | 32 | unlink(path); 33 | 34 | size_t status_socket_len = strlen(path); 35 | size_t len = offsetof(struct sockaddr_un, sun_path) + status_socket_len + 1; 36 | uint8_t buf[len] __attribute__((aligned(__alignof__(struct sockaddr_un)))); 37 | memset(buf, 0, offsetof(struct sockaddr_un, sun_path)); 38 | 39 | struct sockaddr_un *sa = (struct sockaddr_un *)buf; 40 | sa->sun_family = AF_UNIX; 41 | memcpy(sa->sun_path, path, status_socket_len + 1); 42 | 43 | ctx->fd = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0); 44 | 45 | if (bind(ctx->fd, (struct sockaddr *)sa, len)) { 46 | switch (errno) { 47 | case EADDRINUSE: 48 | exit_error( 49 | "unable to create status socket: the path " 50 | "`%s' already exists", 51 | path); 52 | break; 53 | default: 54 | exit_errno("unable to create status socket"); 55 | } 56 | } 57 | 58 | if (listen(ctx->fd, 5)) { 59 | perror("unable to listen on unix-socket"); 60 | exit(EXIT_FAILURE); 61 | } 62 | } 63 | 64 | bool parse_command(char *cmd, enum socket_command *scmd) { 65 | if (!strncmp(cmd, "get_clients", 11)) 66 | *scmd = GET_CLIENTS; 67 | else if (!strncmp(cmd, "verbosity ", 10)) 68 | *scmd = SET_VERBOSITY; 69 | else if (!strncmp(cmd, "has_route ", 10)) 70 | *scmd = HAS_ROUTE; 71 | else if (!strncmp(cmd, "del_meshif ", 11)) 72 | *scmd = DEL_MESHIF; 73 | else if (!strncmp(cmd, "get_meshifs", 11)) 74 | *scmd = GET_MESHIFS; 75 | else if (!strncmp(cmd, "add_meshif ", 11)) 76 | *scmd = ADD_MESHIF; 77 | else if (!strncmp(cmd, "del_prefix ", 11)) 78 | *scmd = DEL_PREFIX; 79 | else if (!strncmp(cmd, "add_address ", 12)) 80 | *scmd = ADD_ADDRESS; 81 | else if (!strncmp(cmd, "del_address ", 12)) 82 | *scmd = DEL_ADDRESS; 83 | else if (!strncmp(cmd, "add_prefix ", 11)) 84 | *scmd = ADD_PREFIX; 85 | else if (!strncmp(cmd, "probe ", 6)) 86 | *scmd = PROBE; 87 | else if (!strncmp(cmd, "get_prefixes", 12)) 88 | *scmd = GET_PREFIX; 89 | else 90 | return false; 91 | 92 | return true; 93 | } 94 | 95 | void socket_get_meshifs(struct json_object *obj) { 96 | struct json_object *jmeshifs = json_object_new_array(); 97 | 98 | for (size_t i = 0; i < VECTOR_LEN(l3ctx.intercom_ctx.interfaces); i++) { 99 | intercom_if_t *iface = &VECTOR_INDEX(l3ctx.intercom_ctx.interfaces, i); 100 | json_object_array_add(jmeshifs, json_object_new_string(iface->ifname)); 101 | } 102 | json_object_object_add(obj, "mesh_interfaces", jmeshifs); 103 | } 104 | 105 | void socket_get_prefixes(struct json_object *obj) { 106 | struct json_object *jprefixes = json_object_new_array(); 107 | char str_prefix[INET6_ADDRSTRLEN] = {}; 108 | 109 | inet_ntop(AF_INET6, &l3ctx.clientmgr_ctx.v4prefix.prefix, str_prefix, INET6_ADDRSTRLEN); 110 | json_object_array_add(jprefixes, json_object_new_string(str_prefix)); 111 | 112 | for (size_t i = 0; i < VECTOR_LEN(l3ctx.clientmgr_ctx.prefixes); i++) { 113 | struct prefix *_prefix = &VECTOR_INDEX(l3ctx.clientmgr_ctx.prefixes, i); 114 | inet_ntop(AF_INET6, &_prefix->prefix, str_prefix, INET6_ADDRSTRLEN); 115 | json_object_array_add(jprefixes, json_object_new_string(str_prefix)); 116 | } 117 | json_object_object_add(obj, "prefixes", jprefixes); 118 | } 119 | 120 | void get_clients(struct json_object *obj) { 121 | size_t i = 0, j = 0; 122 | struct json_object *jclients = json_object_new_object(); 123 | 124 | json_object_object_add(obj, "clients", json_object_new_int(VECTOR_LEN(l3ctx.clientmgr_ctx.clients))); 125 | 126 | for (i = 0; i < VECTOR_LEN(l3ctx.clientmgr_ctx.clients); i++) { 127 | struct client *_client = &VECTOR_INDEX(l3ctx.clientmgr_ctx.clients, i); 128 | struct json_object *jclient = json_object_new_object(); 129 | 130 | char ifname[IFNAMSIZ] = ""; 131 | 132 | if_indextoname(_client->ifindex, ifname); 133 | json_object_object_add(jclient, "interface", json_object_new_string(ifname)); 134 | 135 | struct json_object *addresses = json_object_new_object(); 136 | for (j = 0; j < VECTOR_LEN(_client->addresses); j++) { 137 | struct json_object *address = json_object_new_object(); 138 | struct client_ip *_client_ip = &VECTOR_INDEX(_client->addresses, j); 139 | char ip_str[INET6_ADDRSTRLEN] = ""; 140 | inet_ntop(AF_INET6, &_client_ip->addr, ip_str, INET6_ADDRSTRLEN); 141 | 142 | json_object_object_add(address, "state", json_object_new_int(_client_ip->state)); 143 | json_object_object_add(addresses, ip_str, address); 144 | } 145 | 146 | if (j) { 147 | json_object_object_add(jclient, "addresses", addresses); 148 | } else { 149 | json_object_put(addresses); 150 | } 151 | 152 | json_object_object_add(jclients, print_mac(_client->mac), jclient); 153 | } 154 | 155 | if (i) { 156 | json_object_object_add(obj, "clientlist", jclients); 157 | } else { 158 | json_object_put(jclients); 159 | } 160 | } 161 | 162 | void socket_handle_in(socket_ctx *ctx) { 163 | log_debug("handling socket event\n"); 164 | 165 | int fd = accept(ctx->fd, NULL, NULL); 166 | char line[LINEBUFFER_SIZE]; 167 | 168 | int len = 0; 169 | int fill = 0; 170 | // TODO: it would be nice to be able to set a timeout here after which 171 | // the fd is closed 172 | while (fill < LINEBUFFER_SIZE) { 173 | len = read(fd, &(line[fill]), 1); 174 | if (line[fill] == '\n' || line[fill] == '\r' || len == 0) { 175 | line[fill] = '\0'; 176 | break; 177 | } 178 | fill += len; 179 | } 180 | 181 | enum socket_command cmd; 182 | if (!parse_command(line, &cmd)) { 183 | fprintf(stderr, "Could not parse command on socket (%s)\n", line); 184 | goto end; 185 | } 186 | 187 | struct prefix _prefix = {}; 188 | struct json_object *retval = json_object_new_object(); 189 | uint8_t mac[ETH_ALEN] = {0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa}; 190 | struct in6_addr address = {}; 191 | char *str_address = NULL; 192 | char *str_mac = NULL; 193 | char *str_meshif = NULL; 194 | char *verbosity = NULL; 195 | 196 | switch (cmd) { 197 | case PROBE: 198 | str_address = strtok(&line[ETH_ALEN], " "); 199 | str_mac = strtok(NULL, " "); 200 | sscanf(str_mac, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx", &mac[0], &mac[1], &mac[2], &mac[3], 201 | &mac[4], &mac[5]); 202 | if (inet_pton(AF_INET6, str_address, &address) == 1) { 203 | routemgr_probe_neighbor(&l3ctx.routemgr_ctx, l3ctx.routemgr_ctx.clientif.index, 204 | &address, mac); 205 | } 206 | break; 207 | case GET_CLIENTS: 208 | get_clients(retval); 209 | dprintf(fd, "%s", json_object_to_json_string(retval)); 210 | break; 211 | case ADD_PREFIX: 212 | if (parse_prefix(&_prefix, &line[11])) { 213 | add_prefix(&l3ctx.clientmgr_ctx.prefixes, _prefix); 214 | routemgr_insert_route(&l3ctx.routemgr_ctx, 254, if_nametoindex(l3ctx.ipmgr_ctx.ifname), 215 | (struct in6_addr *)(_prefix.prefix.s6_addr), _prefix.plen); 216 | dprintf(fd, "Added prefix: %s", &line[11]); 217 | } 218 | break; 219 | case SET_VERBOSITY: 220 | verbosity = strtok(&line[10], " "); 221 | 222 | if (!strncmp(verbosity, "none", 4)) { 223 | l3ctx.verbose = false; 224 | l3ctx.debug = false; 225 | } else if (!strncmp(verbosity, "verbose", 7)) { 226 | l3ctx.verbose = true; 227 | l3ctx.debug = false; 228 | } else if (!strncmp(verbosity, "debug", 5)) { 229 | l3ctx.verbose = true; 230 | l3ctx.debug = true; 231 | } 232 | 233 | break; 234 | case HAS_ROUTE: /* debugging use only */ 235 | str_address = strtok(&line[10], " "); 236 | if (inet_pton(AF_INET6, str_address, &address) == 1) { 237 | dprintf(fd, "found route %s/128: %s\n", str_address, has_host_route(&address)? "YES" : "NO"); 238 | } 239 | break; 240 | case ADD_ADDRESS: 241 | str_address = strtok(&line[12], " "); 242 | str_mac = strtok(NULL, " "); 243 | sscanf(str_mac, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx", &mac[0], &mac[1], &mac[2], &mac[3], 244 | &mac[4], &mac[5]); 245 | if (inet_pton(AF_INET6, str_address, &address) == 1) { 246 | clientmgr_add_address(&l3ctx.clientmgr_ctx, &address, mac, 247 | l3ctx.routemgr_ctx.clientif.index); 248 | dprintf(fd, "OK"); 249 | } else { 250 | struct in_addr ip4; 251 | if (inet_pton(AF_INET, str_address, &ip4) == 1) { 252 | mapv4_v6(&ip4, &address); 253 | clientmgr_add_address(&l3ctx.clientmgr_ctx, &address, mac, 254 | l3ctx.routemgr_ctx.clientif.index); 255 | dprintf(fd, "OK"); 256 | } else 257 | dprintf(fd, "NOT OK"); 258 | } 259 | break; 260 | case ADD_MESHIF: 261 | str_meshif = strndup(&line[11], IFNAMSIZ); 262 | if (!intercom_add_interface(&l3ctx.intercom_ctx, str_meshif)) { 263 | free(str_meshif); 264 | break; 265 | } 266 | break; 267 | case GET_MESHIFS: 268 | socket_get_meshifs(retval); 269 | dprintf(fd, "%s", json_object_to_json_string(retval)); 270 | break; 271 | case DEL_MESHIF: 272 | str_meshif = strndup(&line[11], IFNAMSIZ); 273 | if (!intercom_del_interface(&l3ctx.intercom_ctx, str_meshif)) 274 | free(str_meshif); 275 | break; 276 | case DEL_ADDRESS: 277 | str_address = strtok(&line[12], " "); 278 | str_mac = strtok(NULL, " "); 279 | sscanf(str_mac, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx", &mac[0], &mac[1], &mac[2], &mac[3], 280 | &mac[4], &mac[5]); 281 | struct client *client = get_client(mac); 282 | if (client) { 283 | if (inet_pton(AF_INET6, str_address, &address) == 1) { 284 | rtmgr_client_remove_address(&address); 285 | dprintf(fd, "OK"); 286 | } else { 287 | dprintf(fd, "NOT OK"); 288 | } 289 | } 290 | break; 291 | case DEL_PREFIX: 292 | if (parse_prefix(&_prefix, &line[11])) { 293 | del_prefix(&l3ctx.clientmgr_ctx.prefixes, _prefix); 294 | routemgr_remove_route(&l3ctx.routemgr_ctx, 254, (struct in6_addr *)(_prefix.prefix.s6_addr), 295 | _prefix.plen); 296 | dprintf(fd, "Deleted prefix: %s", &line[11]); 297 | } 298 | break; 299 | case GET_PREFIX: 300 | socket_get_prefixes(retval); 301 | dprintf(fd, "%s", json_object_to_json_string(retval)); 302 | break; 303 | } 304 | 305 | json_object_put(retval); 306 | end: 307 | close(fd); 308 | } 309 | -------------------------------------------------------------------------------- /src/socket.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of project l3roamd. It's copyrighted by the contributors 3 | * recorded in the version control history of the file, available from 4 | * its original location https://github.com/freifunk-gluon/l3roamd. 5 | * 6 | * SPDX-License-Identifier: BSD-2-Clause 7 | */ 8 | #pragma once 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #define LINEBUFFER_SIZE 1024 18 | 19 | enum socket_command { 20 | GET_CLIENTS = 0, 21 | PROBE, 22 | ADD_MESHIF, 23 | DEL_MESHIF, 24 | GET_MESHIFS, 25 | ADD_PREFIX, 26 | DEL_PREFIX, 27 | GET_PREFIX, 28 | ADD_ADDRESS, 29 | DEL_ADDRESS, 30 | HAS_ROUTE, 31 | SET_VERBOSITY 32 | }; 33 | 34 | typedef struct { 35 | int fd; 36 | } socket_ctx; 37 | 38 | void socket_init(socket_ctx *ctx, char *path); 39 | void socket_handle_in(socket_ctx *ctx); 40 | -------------------------------------------------------------------------------- /src/syscallwrappers.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of project l3roamd. It's copyrighted by the contributors 3 | * recorded in the version control history of the file, available from 4 | * its original location https://github.com/freifunk-gluon/l3roamd. 5 | * 6 | * SPDX-License-Identifier: BSD-2-Clause 7 | */ 8 | #include "syscallwrappers.h" 9 | #include "error.h" 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | int obtainrandom(void *buf, size_t buflen, unsigned int flags) { 16 | int rc = 0; 17 | size_t fill = 0; 18 | 19 | do { 20 | rc = (int)syscall(SYS_getrandom, &buf + fill, buflen - fill, flags); 21 | if (rc < 0 ) { 22 | if (errno != ENOSYS) { 23 | exit_error("syscall SYS_getrandom."); 24 | } 25 | perror("syscall SYS_getrandom failed. retrying"); 26 | } else { 27 | fill += rc; 28 | } 29 | } while (fill < buflen); 30 | 31 | return fill; 32 | } 33 | -------------------------------------------------------------------------------- /src/syscallwrappers.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of project l3roamd. It's copyrighted by the contributors 3 | * recorded in the version control history of the file, available from 4 | * its original location https://github.com/freifunk-gluon/l3roamd. 5 | * 6 | * SPDX-License-Identifier: BSD-2-Clause 7 | */ 8 | #pragma once 9 | 10 | #include 11 | 12 | int obtainrandom(void *buf, size_t buflen, unsigned int flags); 13 | -------------------------------------------------------------------------------- /src/taskqueue.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2016, Matthias Schiffer 3 | * Copyright (c) 2016, Nils Schneider 4 | * Copyright (c) 2017-2018, Christof Schulze 5 | * 6 | * This file is part of project l3roamd. It's copyrighted by the contributors 7 | * recorded in the version control history of the file, available from 8 | * its original location https://github.com/freifunk-gluon/l3roamd. 9 | * 10 | * SPDX-License-Identifier: BSD-2-Clause 11 | */ 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | #include "alloc.h" 18 | #include "error.h" 19 | #include "l3roamd.h" 20 | #include "taskqueue.h" 21 | #include "timespec.h" 22 | #include "util.h" 23 | 24 | void taskqueue_init(taskqueue_ctx *ctx) { 25 | ctx->fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK); 26 | ctx->queue = NULL; 27 | } 28 | 29 | /** this will add timeout seconds and millisecs milliseconds to the current time 30 | * to calculate at which time a task should run given an offset */ 31 | struct timespec settime(time_t timeout, long millisecs) { 32 | struct timespec due; 33 | clock_gettime(CLOCK_MONOTONIC, &due); 34 | 35 | struct timespec t = {.tv_sec = timeout, .tv_nsec = millisecs * 1000000l}; 36 | 37 | return timeAdd(&due, &t); 38 | } 39 | 40 | /** Enqueues a new task. A task with a timeout of zero is scheduled immediately. 41 | */ 42 | taskqueue_t *post_task(taskqueue_ctx *ctx, time_t timeout, long millisecs, void (*function)(void *), 43 | void (*cleanup)(void *), void *data) { 44 | taskqueue_t *task = l3roamd_alloc(sizeof(taskqueue_t)); 45 | task->children = task->next = NULL; 46 | task->pprev = NULL; 47 | 48 | task->due = settime(timeout, millisecs); 49 | 50 | task->function = function; 51 | task->cleanup = cleanup; 52 | task->data = data; 53 | taskqueue_insert(&ctx->queue, task); 54 | taskqueue_schedule(ctx); 55 | 56 | return task; 57 | } 58 | 59 | void drop_task(taskqueue_t *task) { 60 | taskqueue_remove(task); 61 | 62 | if (task->cleanup != NULL) 63 | task->cleanup(task->data); 64 | 65 | free(task); 66 | } 67 | 68 | /** Changes the timeout of a task. 69 | */ 70 | bool reschedule_task(taskqueue_ctx *ctx, taskqueue_t *task, time_t timeout, long millisecs) { 71 | if (task == NULL || !taskqueue_linked(task)) 72 | return false; 73 | 74 | struct timespec due = settime(timeout, millisecs); 75 | 76 | if (timespec_cmp(due, task->due)) { 77 | task->due = due; 78 | taskqueue_remove(task); 79 | taskqueue_insert(&ctx->queue, task); 80 | taskqueue_schedule(ctx); 81 | } 82 | 83 | return true; 84 | } 85 | 86 | void taskqueue_schedule(taskqueue_ctx *ctx) { 87 | if (ctx->queue == NULL) { 88 | log_debug("Taskqueue is empty, not scheduling another task\n"); 89 | return; 90 | } 91 | 92 | struct itimerspec t = {.it_value = ctx->queue->due}; 93 | struct timespec now; 94 | clock_gettime(CLOCK_MONOTONIC, &now); 95 | 96 | if (timespec_cmp(ctx->queue->due, now) <= 0) 97 | taskqueue_run(ctx); 98 | else { 99 | log_debug("It is now: %s, scheduling next task for %s\n", print_timespec(&now), print_timespec(&ctx->queue->due)); 100 | timerfd_settime(ctx->fd, TFD_TIMER_ABSTIME, &t, NULL); 101 | } 102 | } 103 | 104 | void taskqueue_run(taskqueue_ctx *ctx) { 105 | unsigned long long nEvents; 106 | 107 | struct timespec now; 108 | clock_gettime(CLOCK_MONOTONIC, &now); 109 | 110 | size_t rsize = read(ctx->fd, &nEvents, sizeof(nEvents)); 111 | if ( ! rsize) 112 | log_error("could not read from taskqueue fd\n"); 113 | 114 | if (ctx->queue == NULL) 115 | return; 116 | 117 | while (ctx->queue && timespec_cmp(ctx->queue->due, now) <= 0) { 118 | taskqueue_t *task = ctx->queue; 119 | log_debug("The time is now: %s, running task that was due at %s\n", print_timespec(&now), print_timespec(&task->due)); 120 | taskqueue_remove(task); 121 | task->function(task->data); 122 | 123 | if (task->cleanup) 124 | task->cleanup(task->data); 125 | 126 | free(task); 127 | } 128 | 129 | taskqueue_schedule(ctx); 130 | } 131 | 132 | /** Links an element at the position specified by \e queue */ 133 | static inline void taskqueue_link(taskqueue_t **queue, taskqueue_t *elem) { 134 | if (elem->next) 135 | exit_bug("taskqueue_link: element already linked"); 136 | 137 | elem->pprev = queue; 138 | elem->next = *queue; 139 | if (elem->next) 140 | elem->next->pprev = &elem->next; 141 | 142 | *queue = elem; 143 | } 144 | 145 | /** Unlinks an element */ 146 | static inline void taskqueue_unlink(taskqueue_t *elem) { 147 | *elem->pprev = elem->next; 148 | if (elem->next) 149 | elem->next->pprev = elem->pprev; 150 | 151 | elem->next = NULL; 152 | } 153 | 154 | /** 155 | Merges two priority queues 156 | 157 | \e queue2 may be empty (NULL) 158 | */ 159 | static taskqueue_t *taskqueue_merge(taskqueue_t *queue1, taskqueue_t *queue2) { 160 | if (!queue1) 161 | exit_bug("taskqueue_merge: queue1 unset"); 162 | if (queue1->next) 163 | exit_bug("taskqueue_merge: queue2 has successor"); 164 | if (!queue2) 165 | return queue1; 166 | if (queue2->next) 167 | exit_bug("taskqueue_merge: queue2 has successor"); 168 | 169 | taskqueue_t *lo, *hi; 170 | 171 | if (timespec_cmp(queue1->due, queue2->due) < 0) { 172 | lo = queue1; 173 | hi = queue2; 174 | } else { 175 | lo = queue2; 176 | hi = queue1; 177 | } 178 | 179 | taskqueue_link(&lo->children, hi); 180 | 181 | return lo; 182 | } 183 | 184 | /** Merges a list of priority queues */ 185 | static taskqueue_t *taskqueue_merge_pairs(taskqueue_t *queue0) { 186 | if (!queue0) 187 | return NULL; 188 | 189 | if (!queue0->pprev) 190 | exit_bug("taskqueue_merge_pairs: unlinked queue"); 191 | 192 | taskqueue_t *queue1 = queue0->next; 193 | 194 | if (!queue1) 195 | return queue0; 196 | 197 | taskqueue_t *queue2 = queue1->next; 198 | 199 | queue0->next = queue1->next = NULL; 200 | 201 | return taskqueue_merge(taskqueue_merge(queue0, queue1), taskqueue_merge_pairs(queue2)); 202 | } 203 | 204 | /** Inserts a new element into a priority queue */ 205 | void taskqueue_insert(taskqueue_t **queue, taskqueue_t *elem) { 206 | if (elem->pprev || elem->next || elem->children) 207 | exit_bug("taskqueue_insert: tried to insert linked queue element"); 208 | 209 | *queue = taskqueue_merge(elem, *queue); 210 | (*queue)->pprev = queue; 211 | } 212 | 213 | /** Removes an element from a priority queue */ 214 | void taskqueue_remove(taskqueue_t *elem) { 215 | if (!taskqueue_linked(elem)) { 216 | if (elem->children || elem->next) 217 | exit_bug("taskqueue_remove: corrupted queue item"); 218 | 219 | return; 220 | } 221 | 222 | taskqueue_t **pprev = elem->pprev; 223 | 224 | taskqueue_unlink(elem); 225 | 226 | taskqueue_t *merged = taskqueue_merge_pairs(elem->children); 227 | if (merged) 228 | taskqueue_link(pprev, merged); 229 | 230 | elem->pprev = NULL; 231 | elem->children = NULL; 232 | } 233 | -------------------------------------------------------------------------------- /src/taskqueue.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of project l3roamd. It's copyrighted by the contributors 3 | * recorded in the version control history of the file, available from 4 | * its original location https://github.com/freifunk-gluon/l3roamd. 5 | * 6 | * SPDX-License-Identifier: BSD-2-Clause 7 | */ 8 | #pragma once 9 | 10 | #include 11 | #include 12 | 13 | typedef struct taskqueue taskqueue_t; 14 | 15 | typedef struct { 16 | taskqueue_t *queue; 17 | int fd; 18 | } taskqueue_ctx; 19 | 20 | /** Element of a priority queue */ 21 | struct taskqueue { 22 | taskqueue_t **pprev; /**< \e next element of the previous element (or \e 23 | children of the parent) */ 24 | taskqueue_t *next; /**< Next sibling in the heap */ 25 | 26 | taskqueue_t *children; /**< Heap children */ 27 | 28 | struct timespec due; /**< The priority */ 29 | 30 | void (*function)(void *); 31 | void (*cleanup)(void *); 32 | void *data; 33 | }; 34 | 35 | /** Checks if an element is currently part of a priority queue */ 36 | static inline bool taskqueue_linked(taskqueue_t *elem) { return elem->pprev; } 37 | 38 | void taskqueue_insert(taskqueue_t **queue, taskqueue_t *elem); 39 | void taskqueue_remove(taskqueue_t *elem); 40 | 41 | void taskqueue_init(taskqueue_ctx *ctx); 42 | void taskqueue_run(taskqueue_ctx *ctx); 43 | void taskqueue_schedule(taskqueue_ctx *ctx); 44 | taskqueue_t *post_task(taskqueue_ctx *ctx, time_t timeout, 45 | long millisecs, void (*function)(void *), 46 | void (*cleanup)(void *), void *data); 47 | void drop_task(taskqueue_t *task); 48 | bool reschedule_task(taskqueue_ctx *ctx, taskqueue_t *task, 49 | time_t timeout, long millisecs); 50 | -------------------------------------------------------------------------------- /src/test.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015, Nils Schneider 3 | * 4 | * This file is part of project l3roamd. It's copyrighted by the contributors 5 | * recorded in the version control history of the file, available from 6 | * its original location https://github.com/freifunk-gluon/l3roamd. 7 | * 8 | * SPDX-License-Identifier: BSD-2-Clause 9 | */ 10 | 11 | // TODO EPOLLOUT beim schreiben auf den tunfd 12 | 13 | #include "alloc.h" 14 | #include "config.h" 15 | #include "error.h" 16 | #include "icmp6.h" 17 | #include "icmp6.h" 18 | #include "intercom.h" 19 | #include "ipmgr.h" 20 | #include "l3roamd.h" 21 | #include "packet.h" 22 | #include "prefix.h" 23 | #include "routemgr.h" 24 | #include "socket.h" 25 | #include "types.h" 26 | #include "util.h" 27 | #include "vector.h" 28 | #include "version.h" 29 | 30 | #define SIGTERM_MSG "Exiting. Removing routes for prefixes and clients.\n" 31 | 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | 44 | l3ctx_t l3ctx = {}; 45 | 46 | #include "util.h" 47 | int tests_run = 0; 48 | #define FAIL() printf("\nfailure in %s() line %d\n", __func__, __LINE__) 49 | #define _assert(test) \ 50 | do { \ 51 | if (!(test)) { \ 52 | FAIL(); \ 53 | return 1; \ 54 | } \ 55 | } while (0) 56 | #define _verify(test) \ 57 | do { \ 58 | int r = test(); \ 59 | tests_run++; \ 60 | if (r) \ 61 | return r; \ 62 | } while (0) 63 | 64 | int test_vector_init() { 65 | VECTOR(int) v; 66 | // initializing vector with bogus values 67 | v.desc.length = 5; 68 | v.desc.allocated = 12; 69 | 70 | VECTOR_INIT(v); 71 | _assert(v.desc.length == 0); 72 | _assert(v.desc.allocated == 0); 73 | 74 | VECTOR_ADD(v, 12); 75 | _assert(v.desc.length == 1); 76 | 77 | return 0; 78 | } 79 | 80 | int test_ntohl_ipv4() { 81 | struct in_addr address; 82 | inet_pton(AF_INET, "1.2.3.4", &address); 83 | uint32_t reverse = ntohl(address.s_addr); 84 | 85 | char str[16]; 86 | inet_ntop(AF_INET, &reverse, str, 16); 87 | printf("address: 1.2.3.4, reverse address: %s\n", str); 88 | 89 | _assert(strncmp(str, "4.3.2.1", 7) == 0); 90 | 91 | return 0; 92 | } 93 | int test_mac() { 94 | uint8_t mac1[ETH_ALEN] = {0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa}; 95 | uint8_t mac2[ETH_ALEN] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05}; 96 | uint8_t mac3[ETH_ALEN] = {0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5}; 97 | char str[120]; 98 | snprintf(str, 120, "testing mac address to string conversion for: %s, %s, %s", print_mac(mac1), print_mac(mac2), 99 | print_mac(mac3)); 100 | printf("%s\n", str); 101 | _assert(strncmp(str, 102 | "testing mac address to string conversion for: " 103 | "ff:fe:fd:fc:fb:fa, 00:01:02:03:04:05, " 104 | "a0:a1:a2:a3:a4:a5", 105 | 120) == 0); 106 | 107 | return 0; 108 | } 109 | 110 | int test_icmp_dest_unreachable4() { 111 | struct in6_addr addr = {}; 112 | 113 | struct packet data = {}; 114 | data.len = 12; 115 | uint8_t actualdata[data.len]; 116 | memcpy(actualdata, "xxxxxxxxxxxxx", data.len); 117 | data.data = actualdata; 118 | data.family = 4; 119 | 120 | if (inet_pton(AF_INET6, "::ffff:192.168.12.15", &addr) != 1) 121 | return 1; 122 | 123 | l3ctx.clientif_set = true; 124 | l3ctx.icmp6_ctx.clientif = strdupa("tst1"); 125 | 126 | icmp6_init(&l3ctx.icmp6_ctx); 127 | 128 | return icmp_send_dest_unreachable(&addr, &data); 129 | } 130 | 131 | int all_tests() { 132 | _verify(test_vector_init); 133 | _verify(test_ntohl_ipv4); 134 | _verify(test_mac); 135 | _verify(test_icmp_dest_unreachable4); 136 | return 0; 137 | } 138 | 139 | int main(__attribute__((__unused__)) int argc, __attribute__((__unused__)) char **argv) { 140 | int result = all_tests(); 141 | if (result == 0) 142 | printf("PASSED\n"); 143 | else 144 | printf("FAILED\n"); 145 | printf("Tests run: %d\n", tests_run); 146 | 147 | return result != 0; 148 | } 149 | -------------------------------------------------------------------------------- /src/timespec.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of project l3roamd. It's copyrighted by the contributors 3 | * recorded in the version control history of the file, available from 4 | * its original location https://github.com/freifunk-gluon/l3roamd. 5 | * 6 | * SPDX-License-Identifier: BSD-2-Clause 7 | */ 8 | #include 9 | 10 | #define BILLION 1000000000l 11 | struct timespec timeAdd(struct timespec *t1, struct timespec *t2) { 12 | time_t sec = t2->tv_sec + t1->tv_sec; 13 | long nsec = t2->tv_nsec + t1->tv_nsec; 14 | if (nsec >= BILLION) { 15 | nsec -= BILLION; 16 | sec++; 17 | } 18 | return (struct timespec){.tv_sec = sec, .tv_nsec = nsec}; 19 | } 20 | 21 | int timespec_cmp(struct timespec a, struct timespec b) { 22 | if (a.tv_sec < b.tv_sec) 23 | return -1; 24 | else if (a.tv_sec > b.tv_sec) 25 | return +1; 26 | else if (a.tv_nsec < b.tv_nsec) 27 | return -1; 28 | else if (a.tv_nsec > b.tv_nsec) 29 | return +1; 30 | 31 | return 0; 32 | } 33 | -------------------------------------------------------------------------------- /src/timespec.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of project l3roamd. It's copyrighted by the contributors 3 | * recorded in the version control history of the file, available from 4 | * its original location https://github.com/freifunk-gluon/l3roamd. 5 | * 6 | * SPDX-License-Identifier: BSD-2-Clause 7 | */ 8 | #pragma once 9 | #include 10 | 11 | struct timespec timeAdd(struct timespec *t1, struct timespec *t2); 12 | int timespec_cmp(struct timespec a, struct timespec b); 13 | -------------------------------------------------------------------------------- /src/types.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of project l3roamd. It's copyrighted by the contributors 3 | * recorded in the version control history of the file, available from 4 | * its original location https://github.com/freifunk-gluon/l3roamd. 5 | * 6 | * SPDX-License-Identifier: BSD-2-Clause 7 | */ 8 | #pragma once 9 | 10 | typedef struct l3ctx l3ctx_t; 11 | -------------------------------------------------------------------------------- /src/util.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of project l3roamd. It's copyrighted by the contributors 3 | * recorded in the version control history of the file, available from 4 | * its original location https://github.com/freifunk-gluon/l3roamd. 5 | * 6 | * SPDX-License-Identifier: BSD-2-Clause 7 | */ 8 | 9 | #include "util.h" 10 | #include "error.h" 11 | #include "l3roamd.h" 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | 20 | union buffer strbuffer; 21 | static int str_bufferoffset = 0; 22 | 23 | const char *print_ip4(const struct in_addr *addr) { 24 | str_bufferoffset = (str_bufferoffset + 1) % STRBUFELEMENTS; 25 | return inet_ntop(AF_INET, &(addr->s_addr), strbuffer.element[str_bufferoffset], STRBUFELEMENTLEN); 26 | } 27 | 28 | /** print a timespec to buffer 29 | */ 30 | const char *print_timespec(const struct timespec *t) { 31 | str_bufferoffset = (str_bufferoffset + 1) % STRBUFELEMENTS; 32 | snprintf(strbuffer.element[str_bufferoffset], STRBUFELEMENTLEN, "%lu.%09lu", t->tv_sec, t->tv_nsec); 33 | return strbuffer.element[str_bufferoffset]; 34 | } 35 | 36 | /* print a human-readable representation of an in6_addr struct to stdout 37 | ** */ 38 | const char *print_ip(const struct in6_addr *addr) { 39 | str_bufferoffset = (str_bufferoffset + 1) % STRBUFELEMENTS; 40 | return inet_ntop(AF_INET6, &(addr->s6_addr), strbuffer.element[str_bufferoffset], STRBUFELEMENTLEN); 41 | } 42 | 43 | void obtain_mac_from_if(uint8_t dest[6], char *ifname){ 44 | struct ifreq req = {}; 45 | int fd=socket(AF_UNIX,SOCK_DGRAM,0); 46 | 47 | if (fd==-1) 48 | exit_error("%s",strerror(errno)); 49 | 50 | strncpy(req.ifr_name, ifname, IFNAMSIZ - 1); 51 | if (ioctl(fd,SIOCGIFHWADDR,&req)==-1) { 52 | int temp_errno=errno; 53 | close(fd); 54 | exit_error("%s",strerror(temp_errno)); 55 | } 56 | close(fd); 57 | 58 | memcpy(dest, req.ifr_hwaddr.sa_data, 6); 59 | 60 | log_debug("extracted mac of interface %s: %s\n", ifname, print_mac(dest)); 61 | } 62 | 63 | const char *print_mac(const uint8_t *mac) { 64 | str_bufferoffset = (str_bufferoffset + 1) % STRBUFELEMENTS; 65 | snprintf(strbuffer.element[str_bufferoffset], STRBUFELEMENTLEN, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx", mac[0], mac[1], mac[2], 66 | mac[3], mac[4], mac[5]); 67 | return strbuffer.element[str_bufferoffset]; 68 | } 69 | 70 | inline struct in_addr extractv4_v6(const struct in6_addr *src) { 71 | struct in_addr tmp = {.s_addr = src->s6_addr[12] << 24 | src->s6_addr[13] << 16 | src->s6_addr[14] << 8 | 72 | src->s6_addr[15]}; 73 | struct in_addr ip4; 74 | ip4.s_addr = ntohl(tmp.s_addr); 75 | return ip4; 76 | } 77 | 78 | inline void mapv4_v6(const struct in_addr *src, struct in6_addr *dst) { 79 | memcpy(dst, &l3ctx.clientmgr_ctx.v4prefix, 12); 80 | memcpy(&(dst->s6_addr)[12], src, 4); 81 | } 82 | 83 | void log_error(const char *format, ...) { 84 | va_list args; 85 | va_start(args, format); 86 | vfprintf(stderr, format, args); 87 | va_end(args); 88 | } 89 | 90 | void log_debug(const char *format, ...) { 91 | if (!l3ctx.debug) 92 | return; 93 | va_list args; 94 | va_start(args, format); 95 | vfprintf(stderr, format, args); 96 | va_end(args); 97 | } 98 | 99 | void log_verbose(const char *format, ...) { 100 | if (!l3ctx.verbose) 101 | return; 102 | va_list args; 103 | va_start(args, format); 104 | vfprintf(stderr, format, args); 105 | va_end(args); 106 | } 107 | 108 | /** Check whether an IP address is contained in the IPv4 prefix or the empty 109 | * prefix. 110 | */ 111 | inline bool address_is_ipv4(const struct in6_addr *address) { 112 | return prefix_contains(&l3ctx.clientmgr_ctx.v4prefix, address); 113 | } 114 | 115 | void add_fd(int efd, int fd, uint32_t events) { 116 | struct epoll_event event = {}; 117 | event.data.fd = fd; 118 | event.events = events; 119 | 120 | int s = epoll_ctl(efd, EPOLL_CTL_ADD, fd, &event); 121 | if (s == -1) { 122 | perror("epoll_ctl (ADD):"); 123 | exit_error("epoll_ctl"); 124 | } 125 | } 126 | 127 | void del_fd(int efd, int fd) { 128 | int s = epoll_ctl(efd, EPOLL_CTL_DEL, fd, NULL); 129 | if (s == -1) { 130 | perror("epoll_ctl (DEL):"); 131 | exit_error("epoll_ctl"); 132 | } 133 | } 134 | 135 | void interfaces_changed(int type, const struct ifinfomsg *msg) { 136 | log_verbose("interfaces changed\n"); 137 | intercom_update_interfaces(&l3ctx.intercom_ctx); 138 | if (msg) { 139 | icmp6_interface_changed(&l3ctx.icmp6_ctx, type, msg); 140 | arp_interface_changed(&l3ctx.arp_ctx, type, msg); 141 | } 142 | // TODO: re-initialize routemgr-fd 143 | // TODO: re-initialize ipmgr-fd 144 | // TODO: re-initialize wifistations-fd 145 | } 146 | -------------------------------------------------------------------------------- /src/util.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of project l3roamd. It's copyrighted by the contributors 3 | * recorded in the version control history of the file, available from 4 | * its original location https://github.com/freifunk-gluon/l3roamd. 5 | * 6 | * SPDX-License-Identifier: BSD-2-Clause 7 | */ 8 | #pragma once 9 | #include 10 | #include 11 | #include 12 | 13 | #define STRBUFELEMENTLEN 64 14 | #define STRBUFLEN 256 15 | #define STRBUFELEMENTS (STRBUFLEN / STRBUFELEMENTLEN) 16 | 17 | union buffer { 18 | char element[STRBUFLEN / STRBUFELEMENTLEN][STRBUFELEMENTLEN]; 19 | char allofit[STRBUFLEN]; 20 | }; 21 | 22 | struct in_addr extractv4_v6(const struct in6_addr *src); 23 | void mapv4_v6(const struct in_addr *src, struct in6_addr *dst); 24 | void obtain_mac_from_if(uint8_t dest[6], char ifname[]); 25 | const char *print_ip4(const struct in_addr *addr); 26 | const char *print_ip(const struct in6_addr *addr); 27 | const char *print_mac(const uint8_t *mac); 28 | const char *print_timespec(const struct timespec *t); 29 | void log_verbose(const char *format, ...); 30 | void log_debug(const char *format, ...); 31 | void log_error(const char *format, ...); 32 | 33 | void add_fd(int efd, int fd, uint32_t events); 34 | void del_fd(int efd, int fd); 35 | void interfaces_changed(int type, const struct ifinfomsg *msg); 36 | 37 | bool address_is_ipv4(const struct in6_addr *ip); 38 | -------------------------------------------------------------------------------- /src/vector.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2015, Matthias Schiffer 3 | * 4 | * This file is part of project l3roamd. It's copyrighted by the contributors 5 | * recorded in the version control history of the file, available from 6 | * its original location https://github.com/freifunk-gluon/l3roamd. 7 | * 8 | * SPDX-License-Identifier: BSD-2-Clause 9 | */ 10 | 11 | /** 12 | \file 13 | 14 | Typesafe dynamically sized arrays 15 | */ 16 | 17 | #include "vector.h" 18 | #include "alloc.h" 19 | 20 | #include 21 | 22 | /** The minimum number of elements to allocate even when less elements are used 23 | */ 24 | #define MIN_VECTOR_ALLOC 4 25 | 26 | /** 27 | Resizes a vector 28 | 29 | Vector allocations are always powers of 2. 30 | 31 | Internal function, use VECTOR_RESIZE() instead. 32 | */ 33 | void _l3roamd_vector_resize(l3roamd_vector_desc_t *desc, void **data, size_t n, size_t elemsize) { 34 | desc->length = n; 35 | 36 | size_t alloc = desc->allocated; 37 | 38 | if (!alloc) { 39 | alloc = MIN_VECTOR_ALLOC; 40 | n = n * 3 / 2; 41 | } 42 | 43 | while (alloc < n) alloc <<= 1; 44 | 45 | if (alloc != desc->allocated) { 46 | desc->allocated = alloc; 47 | *data = l3roamd_realloc(*data, alloc * elemsize); 48 | } 49 | } 50 | 51 | /** 52 | Inserts an element into a vector 53 | 54 | Internal function, use VECTOR_INSERT() and VECTOR_ADD() instead. 55 | */ 56 | void *_l3roamd_vector_insert(l3roamd_vector_desc_t *desc, void **data, void *element, size_t pos, size_t elemsize) { 57 | _l3roamd_vector_resize(desc, data, desc->length + 1, elemsize); 58 | 59 | void *p = *data + pos * elemsize; 60 | 61 | memmove(p + elemsize, p, (desc->length - pos - 1) * elemsize); 62 | memcpy(p, element, elemsize); 63 | return (p); 64 | } 65 | 66 | /** 67 | Deletes an element from a vector 68 | 69 | Internal function, use VECTOR_DELETE() instead. 70 | */ 71 | void _l3roamd_vector_delete(l3roamd_vector_desc_t *desc, void **data, size_t pos, size_t elemsize) { 72 | void *p = *data + pos * elemsize; 73 | memmove(p, p + elemsize, (desc->length - pos - 1) * elemsize); 74 | 75 | _l3roamd_vector_resize(desc, data, desc->length - 1, elemsize); 76 | } 77 | -------------------------------------------------------------------------------- /src/vector.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2015, Matthias Schiffer 3 | * 4 | * This file is part of project l3roamd. It's copyrighted by the contributors 5 | * recorded in the version control history of the file, available from 6 | * its original location https://github.com/freifunk-gluon/l3roamd. 7 | * 8 | * SPDX-License-Identifier: BSD-2-Clause 9 | */ 10 | 11 | /** 12 | \file 13 | 14 | Typesafe dynamically sized arrays 15 | */ 16 | 17 | #pragma once 18 | 19 | #include 20 | #include 21 | 22 | /** A vector descriptor */ 23 | typedef struct l3roamd_vector_desc { 24 | size_t allocated; /**< The number of elements currently allocated */ 25 | size_t length; /**< The actual number of elements in the vector */ 26 | } l3roamd_vector_desc_t; 27 | 28 | /** 29 | A type for a vector of \e type elements 30 | 31 | \hideinitializer 32 | */ 33 | #define VECTOR(type) \ 34 | struct { \ 35 | l3roamd_vector_desc_t desc; \ 36 | type *data; \ 37 | } 38 | 39 | void _l3roamd_vector_resize(l3roamd_vector_desc_t *desc, void **data, size_t n, size_t elemsize); 40 | void *_l3roamd_vector_insert(l3roamd_vector_desc_t *desc, void **data, void *element, size_t pos, size_t elemsize); 41 | void _l3roamd_vector_delete(l3roamd_vector_desc_t *desc, void **data, size_t pos, size_t elemsize); 42 | 43 | /** 44 | Resizes the vector \e a to \e n elements 45 | 46 | \hideinitializer 47 | */ 48 | #define VECTOR_RESIZE(v, n) \ 49 | ({ \ 50 | __typeof__(v) *_v = &(v); \ 51 | _l3roamd_vector_resize(&_v->desc, (void **)&_v->data, (n), sizeof(*_v->data)); \ 52 | }) 53 | 54 | /** 55 | Frees all resources used by the vector \e v 56 | 57 | \hideinitializer 58 | */ 59 | #define VECTOR_FREE(v) free((v).data) 60 | 61 | /** 62 | Returns the number of elements in the vector \e v 63 | 64 | \hideinitializer 65 | */ 66 | #define VECTOR_LEN(v) ((v).desc.length) 67 | 68 | /** 69 | Returns the element with index \e i in the vector \e v 70 | 71 | \hideinitializer 72 | */ 73 | #define VECTOR_INDEX(v, i) ((v).data[i]) 74 | 75 | /** 76 | Given an element, return the index in the vector 77 | 78 | \hideinitializer 79 | */ 80 | #define VECTOR_GETINDEX(v, elem) ({ (elem - v.data); }) 81 | 82 | /** 83 | Returns a pointer to the vector elements of \e v 84 | 85 | \hideinitializer 86 | */ 87 | #define VECTOR_DATA(v) ((v).data) 88 | 89 | /** 90 | Inserts the element \e elem at index \e pos of vector \e v 91 | 92 | \hideinitializer 93 | */ 94 | #define VECTOR_INSERT(v, elem, pos) \ 95 | ({ \ 96 | __typeof__(v) *_v = &(v); \ 97 | __typeof__(*_v->data) _e = (elem); \ 98 | return _l3roamd_vector_insert(&_v->desc, (void **)&_v->data, &_e, (pos), sizeof(_e)); \ 99 | }) 100 | 101 | /** 102 | Adds the element \e elem at the end of vector \e v 103 | 104 | \hideinitializer 105 | */ 106 | #define VECTOR_ADD(v, elem) \ 107 | ({ \ 108 | __typeof__(v) *_v = &(v); \ 109 | __typeof__(*_v->data) _e = (elem); \ 110 | _l3roamd_vector_insert(&_v->desc, (void **)&_v->data, &_e, _v->desc.length, sizeof(_e)); \ 111 | }) 112 | 113 | /** 114 | Deletes the element at index \e pos of vector \e v 115 | 116 | \hideinitializer 117 | */ 118 | #define VECTOR_DELETE(v, pos) \ 119 | ({ \ 120 | __typeof__(v) *_v = &(v); \ 121 | _l3roamd_vector_delete(&_v->desc, (void **)&_v->data, (pos), sizeof(*_v->data)); \ 122 | }) 123 | 124 | /** 125 | Initializes the description of a vector v with 0 126 | \hideinitializer 127 | */ 128 | #define VECTOR_INIT(v) \ 129 | ({ \ 130 | v.data = NULL; \ 131 | v.desc.length = 0; \ 132 | v.desc.allocated = 0; \ 133 | }) 134 | 135 | /** 136 | Performs a binary search on the vector \e v, returning a pointer to a 137 | matching vector element 138 | 139 | \hideinitializer 140 | */ 141 | #define VECTOR_BSEARCH(key, v, cmp) \ 142 | ({ \ 143 | __typeof__(v) *_v = &(v); \ 144 | const __typeof__(*_v->data) *_key = (key); \ 145 | int (*_cmp)(__typeof__(_key), __typeof__(_key)) = (cmp); \ 146 | (__typeof__(_v->data)) bsearch(_key, _v->data, _v->desc.length, sizeof(*_v->data), \ 147 | (int (*)(const void *, const void *))_cmp); \ 148 | }) 149 | 150 | /** 151 | Performs a linear search on the vector \e v, returning a pointer to a 152 | matching vector element 153 | 154 | \hideinitializer 155 | */ 156 | #define VECTOR_LSEARCH(key, v, cmp) \ 157 | ({ \ 158 | __typeof__(v) *_v = &(v); \ 159 | const __typeof__(*_v->data) *_key = (key); \ 160 | int (*_cmp)(__typeof__(_key), __typeof__(_key)) = (cmp); \ 161 | (__typeof__(_v->data)) lfind(_key, _v->data, &_v->desc.length, sizeof(*_v->data), \ 162 | (int (*)(const void *, const void *))_cmp); \ 163 | }) 164 | -------------------------------------------------------------------------------- /src/version.h.in: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015, Nils Schneider 3 | * 4 | * This file is part of project l3roamd. It's copyrighted by the contributors 5 | * recorded in the version control history of the file, available from 6 | * its original location https://github.com/freifunk-gluon/l3roamd. 7 | * 8 | * SPDX-License-Identifier: BSD-2-Clause 9 | */ 10 | 11 | #ifndef VERSION_H 12 | #define VERSION_H 13 | 14 | #define SOURCE_VERSION "0.0.1" 15 | #define GIT_BRANCH "@GIT_BRANCH@" 16 | #define GIT_COMMIT_HASH "@GIT_COMMIT_HASH@" 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /src/wifistations.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of project l3roamd. It's copyrighted by the contributors 3 | * recorded in the version control history of the file, available from 4 | * its original location https://github.com/freifunk-gluon/l3roamd. 5 | * 6 | * SPDX-License-Identifier: BSD-2-Clause 7 | */ 8 | #include "wifistations.h" 9 | #include "clientmgr.h" 10 | #include "error.h" 11 | #include "genl.h" 12 | #include "if.h" 13 | #include "l3roamd.h" 14 | #include "util.h" 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include 32 | 33 | static int no_seq_check(__attribute__((__unused__)) struct nl_msg *msg, __attribute__((__unused__)) void *arg) { 34 | return NL_OK; 35 | } 36 | 37 | void wifistations_handle_in(wifistations_ctx *ctx) { nl_recvmsgs(ctx->nl_sock, ctx->cb); } 38 | 39 | int wifistations_handle_event(struct nl_msg *msg, void *arg) { 40 | log_debug("handling wifistations event\n"); 41 | wifistations_ctx *ctx = arg; 42 | if (ctx->nl80211_disabled) 43 | return 0; 44 | 45 | struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); 46 | struct nlattr *tb[8]; 47 | 48 | // TODO only handle events from interfaces we care about. 49 | 50 | // TODO warum kann das NULL sein? 51 | if (gnlh == NULL) 52 | return 0; 53 | 54 | nla_parse(tb, 8, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), NULL); 55 | 56 | if (!tb[NL80211_ATTR_MAC]) 57 | return 0; 58 | 59 | char ifname[IFNAMSIZ]; 60 | unsigned int ifindex = nla_get_u32(tb[NL80211_ATTR_IFINDEX]); 61 | if_indextoname(ifindex, ifname); 62 | 63 | switch (gnlh->cmd) { 64 | case NL80211_CMD_NEW_STATION: 65 | log_verbose("new wifi station [%s] found on interface %s\n", 66 | print_mac(nla_data(tb[NL80211_ATTR_MAC])), ifname); 67 | ifindex = l3ctx.icmp6_ctx.ifindex; 68 | clientmgr_notify_mac(&l3ctx.clientmgr_ctx, nla_data(tb[NL80211_ATTR_MAC]), ifindex); 69 | break; 70 | case NL80211_CMD_DEL_STATION: 71 | log_verbose( 72 | "NL80211_CMD_DEL_STATION for [%s] RECEIVED on " 73 | "interface %s.\n", 74 | print_mac(nla_data(tb[NL80211_ATTR_MAC])), ifname); 75 | clientmgr_delete_client(&l3ctx.clientmgr_ctx, nla_data(tb[NL80211_ATTR_MAC])); 76 | break; 77 | } 78 | 79 | return 0; 80 | } 81 | 82 | void wifistations_init(wifistations_ctx *ctx) { 83 | printf("initializing detection of wifistations\n"); 84 | ctx->nl_sock = nl_socket_alloc(); 85 | if (!ctx->nl_sock) 86 | exit_error("Failed to allocate netlink socket.\n"); 87 | 88 | nl_socket_set_buffer_size(ctx->nl_sock, 8192, 8192); 89 | /* no sequence checking for multicast messages */ 90 | nl_socket_disable_seq_check(ctx->nl_sock); 91 | 92 | if (genl_connect(ctx->nl_sock)) { 93 | fprintf(stderr, "Failed to connect to generic netlink.\n"); 94 | goto fail; 95 | } 96 | 97 | int nl80211_id = genl_ctrl_resolve(ctx->nl_sock, NL80211_GENL_NAME); 98 | if (nl80211_id < 0) { 99 | fprintf(stderr, "nl80211 not found.\n"); 100 | /* To resolve issue #29 we do not bail out, but return with an 101 | * invalid file descriptor and without a wifi socket instead. 102 | */ 103 | ctx->fd = -1; 104 | nl_socket_free(ctx->nl_sock); 105 | ctx->nl_sock = NULL; 106 | return; 107 | } 108 | 109 | /* MLME multicast group */ 110 | int mcid = nl_get_multicast_id(ctx->nl_sock, NL80211_GENL_NAME, NL80211_MULTICAST_GROUP_MLME); 111 | if (mcid >= 0) { 112 | int ret = nl_socket_add_membership(ctx->nl_sock, mcid); 113 | if (ret) 114 | goto fail; 115 | } else { 116 | printf("error obtaining multicast id\n"); 117 | } 118 | 119 | ctx->cb = nl_cb_alloc(NL_CB_DEFAULT); 120 | 121 | if (!ctx->cb) 122 | exit_error("failed to allocate netlink callbacks\n"); 123 | 124 | nl_cb_set(ctx->cb, NL_CB_SEQ_CHECK, NL_CB_CUSTOM, no_seq_check, NULL); 125 | nl_cb_set(ctx->cb, NL_CB_VALID, NL_CB_CUSTOM, wifistations_handle_event, ctx); 126 | 127 | ctx->fd = nl_socket_get_fd(ctx->nl_sock); 128 | 129 | return; 130 | 131 | fail: 132 | nl_socket_free(ctx->nl_sock); 133 | exit_error("Could not open nl80211 socket"); 134 | } 135 | -------------------------------------------------------------------------------- /src/wifistations.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of project l3roamd. It's copyrighted by the contributors 3 | * recorded in the version control history of the file, available from 4 | * its original location https://github.com/freifunk-gluon/l3roamd. 5 | * 6 | * SPDX-License-Identifier: BSD-2-Clause 7 | */ 8 | #pragma once 9 | 10 | #include 11 | #include "vector.h" 12 | 13 | typedef struct { 14 | char *ifname; 15 | unsigned int ifindex; 16 | bool ok; 17 | } wifistations_if; 18 | 19 | typedef struct { 20 | struct nl_sock *nl_sock; 21 | struct nl_cb *cb; 22 | VECTOR(wifistations_if) interfaces; 23 | int fd; 24 | bool nl80211_disabled; 25 | } wifistations_ctx; 26 | 27 | void wifistations_handle_in(wifistations_ctx *ctx); 28 | void wifistations_init(wifistations_ctx *ctx); 29 | --------------------------------------------------------------------------------