├── .travis.yml ├── CHANGELOG.md ├── CMakeLists.txt ├── LICENSE ├── README.md ├── cmake_uninstall.cmake.in └── src ├── config.c ├── config.h ├── dhcp.c ├── dhcp.h ├── dhcpoptinj.c ├── ipv4.c ├── ipv4.h ├── options.c ├── options.h └── udp.h /.travis.yml: -------------------------------------------------------------------------------- 1 | addons: 2 | apt: 3 | update: true 4 | packages: 5 | - libnetfilter-queue-dev 6 | dist: xenial 7 | language: c 8 | compiler: 9 | - clang 10 | - gcc 11 | before_script: 12 | - mkdir build 13 | script: 14 | - cd build 15 | - cmake .. 16 | - make -j2 17 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## Unreleased 8 | 9 | ## 0.5.3 - 2019-08-06 10 | ### Fixed 11 | - Fix two format arguments in debug output printing (fairly pedantic; not even 12 | caught by clang analyser). 13 | - Limit DHCP options to 255 bytes (not 256). 14 | - Exit if a DHCP option is too long. 15 | 16 | ## 0.5.2 - 2019-05-09 17 | ### Added 18 | - Add example output "screenshot" to README. 19 | 20 | ### Changed 21 | - Create PID file after initialising signal handler. 22 | - Remove incompatible compiler warning option when using clang. 23 | - Use a variable for the project/binary name in CMakeLists.txt. 24 | 25 | ### Fixed 26 | - Indicate that configuration file is an optional argument in usage output. 27 | - Fix error message output when passing option/keyward too many times. 28 | - Improve wording in configuration file parsing error messages. 29 | 30 | ## 0.5.1 - 2019-04-16 31 | ### Changed 32 | - Use 1-byte alignment on DHCP options. 33 | - Refer to salsa.debian.org for deb package source. 34 | - Remove old bug reference in README. 35 | 36 | ### Fixed 37 | - Allow optional values to configuration file keywords (correctly support 38 | "pid-file" as on the command line). 39 | 40 | ## 0.5.0 - 2019-04-09 41 | ### Added 42 | - Parse configuration from file. 43 | - Add copyright to usage output. 44 | 45 | ### Fixed 46 | - Fix pedantic errors from clang. 47 | 48 | ## 0.4.4 - 2019-03-25 49 | ### Fixed 50 | - Update version number in binary. 51 | 52 | ## 0.4.3 - 2019-03-19 53 | ### Added 54 | - DHCP option names are printed along with their option codes. 55 | 56 | ### Changed 57 | - Debug output is more detailed and aligned. 58 | 59 | ### Fixed 60 | - Alignment and explicit data type conversions are used to compile without 61 | errors on 32-bit architectures. 62 | - Do not fail on strict-overflow warnings, as some may be ignored. 63 | - Do not use non-ASCII characters in debug output. They were not strictly 64 | needed. 65 | 66 | ## 0.4.2 - 2019-01-17 67 | ### Changed 68 | - Change usage string to reflect formatting used by man page. 69 | - Add very strict compiler flags. 70 | 71 | ### Fixed 72 | - Fix new compiler warnings (pedantic signed/unsigned issues and void function 73 | declarations). 74 | 75 | ## 0.4.1 - 2016-12-18 76 | ### Fixed 77 | - Update version number in --version output from 0.3.0. 78 | 79 | ## 0.4.0 - 2016-12-13 80 | ### Changed 81 | - Use constant for maximum queue length instead of hard-coded value. 82 | 83 | ### Fixed 84 | - Fix program name simplification bug. 85 | - Remove typo in help text. 86 | 87 | ## 0.3.0 - 2016-06-10 88 | ### Added 89 | - Add support for replacing existing DHCP options. 90 | - Allow injecting multiple options of same type. 91 | 92 | ### Changed 93 | - Update README. 94 | - Improve debug output. 95 | - Improve help text. 96 | 97 | ### Fixed 98 | - Fix incorrect --version output. 99 | - Drop/accept if packet fragmented depending on --forward-on-fail. 100 | - Safe-guard against empty DHCP options as result of invalid hex strings. 101 | - Fix erroneous new packet size calculation. 102 | - Fix other minor bugs and warnings. 103 | 104 | ## 0.2.1 - 2015-07-28 105 | ### Changed 106 | - Improve documentation 107 | 108 | ### Fixed 109 | - Fix memory leak on exit with --version/--help. 110 | 111 | ## 0.2.0 - 2015-07-27 112 | Initial release 113 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0) 2 | # Allow setting PROJECT_VERSION through project(): 3 | cmake_policy(SET CMP0048 NEW) 4 | 5 | # Don't bother writing the project name all the time: 6 | set(PROJECT dhcpoptinj) 7 | 8 | if(DEFINED CMAKE_BUILD_TYPE) 9 | set(CMAKE_BUILD_TYPE ${CMAKE_BUILD_TYPE} CACHE STRING "Choose the type of build, Debug or Release") 10 | else() 11 | SET(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build, Debug or Release") 12 | endif() 13 | if (DEFINED CMAKE_INSTALL_PREFIX) 14 | set(CMAKE_BUILD_TYPE ${CMAKE_BUILD_TYPE} CACHE STRING "Choose install prefix") 15 | else() 16 | set(CMAKE_INSTALL_PREFIX /usr CACHE STRING "Choose install prefix") 17 | endif() 18 | 19 | project( 20 | ${PROJECT} 21 | VERSION 0.5.3 22 | DESCRIPTION "DHCP option injector" 23 | LANGUAGES C 24 | ) 25 | add_definitions(-DDHCPOPTINJ_VERSION="${PROJECT_VERSION}") 26 | set(SOURCES 27 | src/config.c 28 | src/dhcp.c 29 | src/dhcpoptinj.c 30 | src/ipv4.c 31 | src/options.c 32 | ) 33 | set(HEADERS 34 | src/config.h 35 | src/dhcp.h 36 | src/ipv4.h 37 | src/options.h 38 | src/udp.h 39 | ) 40 | add_executable(${PROJECT} ${SOURCES} ${HEADERS}) 41 | set_property(TARGET ${PROJECT} PROPERTY C_STANDARD 99) 42 | target_compile_options(${PROJECT} PRIVATE 43 | -Wall 44 | -Wextra 45 | -pedantic 46 | -Wcast-align 47 | -Wcast-qual 48 | -Wdisabled-optimization 49 | -Wformat=2 50 | -Winit-self 51 | -Wmissing-declarations 52 | -Wmissing-include-dirs 53 | -Wredundant-decls 54 | -Wshadow 55 | -Wsign-conversion 56 | -Wstrict-overflow=5 57 | -Wno-error=strict-overflow 58 | -Wswitch-default 59 | -Wundef 60 | -Werror 61 | -Wno-unused 62 | -Wmissing-prototypes 63 | -Wstrict-prototypes 64 | -Wold-style-definition 65 | -fstack-protector 66 | -Wwrite-strings 67 | -Wmissing-field-initializers 68 | -D_POSIX_SOURCE 69 | -D_DEFAULT_SOURCE 70 | -D_FORTIFY_SOURCE=2 71 | ) 72 | # Only add -Wlogical-op if using gcc; clang does not support this warning option: 73 | if (CMAKE_C_COMPILER_ID STREQUAL "GNU") 74 | target_compile_options(${PROJECT} PRIVATE "-Wlogical-op") 75 | endif() 76 | 77 | find_library(NFQ_LIB netfilter_queue REQUIRED) 78 | target_link_libraries(${PROJECT} ${NFQ_LIB}) 79 | 80 | install(TARGETS ${PROJECT} DESTINATION sbin) 81 | # Add uninstall target, since cmake does not: 82 | configure_file( 83 | "${CMAKE_CURRENT_SOURCE_DIR}/cmake_uninstall.cmake.in" 84 | "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" 85 | IMMEDIATE @ONLY) 86 | add_custom_target(uninstall 87 | COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake) 88 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DHCP option injector 2 | [![Build Status](https://travis-ci.org/misje/dhcpoptinj.svg?branch=dev)](https://travis-ci.org/misje/dhcpoptinj) [![Total alerts](https://img.shields.io/lgtm/alerts/g/misje/dhcpoptinj.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/misje/dhcpoptinj/alerts/) 3 | 4 | Have you ever wanted to intercept DHCP requests and squeeze in a few extra DHCP 5 | options, unbeknownst to the sender? Probably not. However, should the need ever 6 | come, **dhcpoptinj** will (hopefully) help you. 7 | 8 | ## Why 9 | 10 | There can be many a reason to mangle DHCP requests, although chances are you 11 | ought to look for a much better method for solving your problem. Perhaps you do 12 | not have access to the DHCP server/clients and need to modify their DHCP 13 | options, perhaps the DHCP software is difficult to configure (or does not 14 | support what you want to do), perhaps you have a very complex and/or odd setup, 15 | or perhaps you just want to experiment sending exotic or malformed options? 16 | There is a small chance that dhcoptinj might actually be of some use. 17 | 18 | ## How 19 | 20 | dhcpoptinj waits for packets to arrive in a netfilter queue. It will ensure 21 | that a packet is in fact a BOOTP/DHCP packet, and if so proceed to inject 22 | options. It will recalculate the IPv4 header checksum, disable the UDP 23 | checksum (for a simpler implementation) and then give the packet back to 24 | netfilter. 25 | 26 | You need an iptables rule in order to intercept packets and send them to 27 | dhcpoptinj. Let us say you have two interfaces bridged together, *eth0* and 28 | *eth1*. Let us say you want to intercept all BOOTP requests coming from *eth0* 29 | and inject the [relay agent information 30 | option](https://tools.ietf.org/html/rfc3046) (82/0x52). Let us make up a silly 31 | payload: An [agent circuit ID 32 | sub-option](https://tools.ietf.org/html/rfc3046#section-3.1) with the value 33 | "Fjas". 34 | 35 | Add a rule to the iptables mangle table:`sudo iptables -t mangle -A PREROUTING 36 | -m physdev --physdev-in eth0 -p udp --dport 67 -j NFQUEUE --queue-num 42`. 37 | 38 | Then run dhcpoptinj (let us run it in the foreground with extra debug output): 39 | `sudo dhcpoptinj -d -f -q 42 -o'52 01 04 46 6A 61 73'`. Note that dhcpoptinj 40 | must be run by a user with the CAP\_NET\_ADMIN capability. You do not need to, 41 | and you really should not run dhcpoptinj as root. Instead, you can for instance 42 | grant the CAP\_NET\_ADMIN capability to the binary (using *setcap*) and limit 43 | execution rights to only a specific user or group. This is a method used for 44 | running wireshark as non-root, so you will find several guides helping you 45 | accomplish this. 46 | 47 | Now send a DHCP packet to the *eth0* interface and watch it (using a tool like 48 | [wireshark](https://www.wireshark.org/)) having been modified when it reaches 49 | the bridged interface. It should have the injected option at the end of the 50 | option list. If you capture the incoming DHCP packet with Wireshark, it will 51 | appear unmodified although it will in fact be mangled. 52 | 53 | Note the format of the argument to the *-o* option: It should be a hexadecimal 54 | string starting with the DHCP option code followed by the option payload. The 55 | option length (the byte that normally follows the option code) is automatically 56 | calculated and must not be specified. The hex string can be delimited by 57 | non-hexadecimal characters for readability. All options must have a payload, 58 | except for the special [pad 59 | option](https://tools.ietf.org/html/rfc2132#section-2) (code 0). 60 | 61 | The layout of the nonsensical option used in this example (first the [DHCP 62 | option layout](https://tools.ietf.org/html/rfc2132#section-2), then the 63 | specific [relay agent information option sub-option 64 | layout](https://tools.ietf.org/html/rfc3046#section-2.0)) is as follows: 65 | 66 | | Code | Length | Data | 67 | |------|--------|----------------------------| 68 | | 52 | (auto) | 01 04 46 6A 61 73 ("Fjas") | 69 | 70 | | Sub-opt. | Length | Data | 71 | |----------|--------|----------------------| 72 | | 01 | 4 | 46 6A 61 73 ("Fjas") | 73 | 74 | Note that dhcpoptinj does not care about what you write in the option payloads, 75 | nor does it check whether your option code exists. It does however forbid you 76 | to use the option code 255 (the terminating end option). dhcpoptinj inserts 77 | this option as the last option automatically. 78 | 79 | ## Screenshot 80 | ``` 81 | dhcpoptinj -f -d -q42 -r -o'0C 66 6A 61 73 65 68 6F 73 74' -o'52 01 04 46 6A 61 73' -o 320A141E28 82 | ``` 83 | ``` 84 | 3 DHCP option(s) to inject (with a total of 25 bytes): 12 (0x0C) (Hostname), 82 (0x52) (Relay Agent Information), 50 (0x32) (Address Request) 85 | Existing options will be removed 86 | Initialising netfilter queue 87 | Initialising signal handler 88 | Initialisation completed. Waiting for packets to mangle on queue 42 89 | Received 416 bytes 90 | Inspecting 328-byte DHCP packet from B6:40:FE:41:30:DC to 255.255.255.255:67 91 | Mangling packet 92 | Found option 53 (0x35) (DHCP message type) DHCPREQUEST (copying) 93 | Found option 54 (0x36) (DHCP Server Id) with 4-byte payload 0A 14 1E 01 (copying) 94 | Found option 50 (0x32) (Address Request) with 4-byte payload 0A 14 1E 28 (removing) 95 | Found option 12 (0x0C) (Hostname) with 12-byte payload 33 31 36 64 65 39 31 34 64 61 62 34 (removing) 96 | Found option 55 (0x37) (Parameter List) with 13-byte payload 01 1C 02 03 0F 06 77 0C 2C 2F 1A 79 2A (copying) 97 | Found END option (removing) 98 | Injecting option 12 (0x0C) (Hostname) with 9-byte payload 66 6A 61 73 65 68 6F 73 74 99 | Injecting option 82 (0x52) (Relay Agent Information) with 6-byte payload 01 04 46 6A 61 73 100 | Injecting option 50 (0x32) (Address Request) with 4-byte payload 0A 14 1E 28 101 | Inserting END option 102 | Padding with 10 byte(s) to meet minimal BOOTP payload size 103 | Sending mangled packet 104 | 105 | ``` 106 | 107 | ## Installing 108 | [![Packaging status](https://repology.org/badge/vertical-allrepos/dhcpoptinj.svg)](https://repology.org/project/dhcpoptinj/versions) 109 | 110 | dhcpoptinj is in Debian/Ubuntu. The deb package is under source control at 111 | [salsa](https://salsa.debian.org/misje-guest/dhcpoptinj). Installing 112 | dhcpoptinj from the deb package is recommended over the following manual 113 | installation procedure, because it also includes a man page, bash completion 114 | rules, example files etc. 115 | 116 | ### Prerequisites 117 | 118 | You need [cmake](http://www.cmake.org/) and 119 | [libnetfilter\_queue](http://www.netfilter.org/projects/libnetfilter_queue/) 120 | (and a C compiler that supports C99). Hopefully, you are using a Debian-like 121 | system, in which case you can run the following to install them: `sudo apt-get 122 | install cmake libnetfilter-queue-dev`. 123 | 124 | ### Build 125 | 126 | 1. Download or clone the source: `git clone git://github.com/misje/dhcpoptinj` 127 | 1. Enter the directory: `cd dhcpoptinj` 128 | 1. Create a build directory and enter it (optional, but recommended): `mkdir 129 | build && cd build` 130 | 1. Run cmake: `cmake ..` (or `cmake -DCMAKE_BUILD_TYPE=Debug ..` if you want a 131 | debug build) 132 | 1. Run make: `make -j4` 133 | 1. Install (optional, but you will benefit from having dhcpoptinj in your 134 | PATH): `sudo make install` 135 | 136 | ### Demolish 137 | 138 | 1. Run `sudo make uninstall` from your build directory 139 | 140 | The build directory with all its contents can be safely removed. If you did not 141 | use a build directory, you can get rid of all the cmake rubbish by running `git 142 | clean -dfx`. Note, however, that this removes **everything** in the project 143 | directory that is not under source control. 144 | 145 | ## Configuration file 146 | 147 | dhcptopinj will attempt to parse /etc/dhcpoptinj.conf or the file passed with 148 | -c/--conf-file. The syntax of the configuration file is 149 | * **key=value**, where *key* is the long option name, or 150 | * **key** if the option does not take an argument 151 | 152 | Whitespace is optional. Anything after and including the character **#** is 153 | considered a comment. DHCP options are listed one-by-one as *option=01:02:03*. 154 | Quotes around the option hex string is optional, and the bytes may be separated 155 | by any number of non-hexadecimal characters. 156 | 157 | The options *version*, *help* and *conf-file* are not accepted in a 158 | configuration file. 159 | 160 | Example: 161 | ```apache 162 | # Run in foreground: 163 | foreground 164 | # Enable debug output: 165 | debug 166 | # Override hostname to "fjasehost": 167 | option = '0C 66 6A 61 73 65 68 6F 73 74' 168 | # Send agent ID "Fjas": 169 | option = "52:01:04:46:6A:61:73" 170 | # Override address request to ask for 10.20.30.40: 171 | option=320A141E28 172 | # Use queue 12: 173 | queue = 12 174 | 175 | remove-existing-opt # Remove options before inserting 176 | ``` 177 | 178 | ## Help 179 | 180 | This readme should have got you started. Also check out the man page (in the 181 | deb package) and the help output (`dhcpoptinj -h`), which should cover 182 | everything the utility has to offer. 183 | 184 | For bugs and suggestions please create an issue. 185 | 186 | ### Limitations 187 | 188 | dhcpoptinj is simple and will hopefully stay that way. Nonetheless, the 189 | following are missing features that hopefully will be added some day: 190 | 191 | 1. Remove options instead of having to replace them 192 | 2. Filter incoming packets by their DHCP message type (code 53) before mangling 193 | them 194 | 195 | ### Troubleshooting 196 | 197 | 1. *Failed to bind queue handler to AF_INET: Operation not permitted* 198 | 199 | Most likely you do not have CAP\_NET\_ADMIN capability or there is another 200 | process (perhaps another dhcpoptinj instance?) bound to the same netfilter 201 | queue number. 202 | 203 | ### Known issues 204 | 205 | 1. Memory leak on non-normal exit. 206 | 207 | This is not considered a leak. However, there should be no memory leak on a 208 | normal exit (catching SIGTERM, SIGINT or SIGHUP). 209 | 210 | ## Useful information 211 | 212 | When creating iptables rules to use with dhcpoptinj, the following options can 213 | be useful: 214 | 215 | - `--queue-bypass` 216 | 217 | Do not drop packets, but let them pass through if dhcpoptinj is not running 218 | (or not listening on the correct queue number). 219 | 220 | ## Contributing 221 | 222 | If you have any suggestions please leave an issue, and I will come back to you. 223 | You are welcome to contribute and pull requests are much appreciated. 224 | 225 | If you find dhcpoptinj useful I would love to hear what you are using it for. 226 | Update the [wiki 227 | page](https://github.com/misje/dhcpoptinj/wiki#practical-use-cases) and 228 | describe your use. 229 | 230 | ## License 231 | 232 | I have chosen to use GPL for this project. If that does not suit you, contact 233 | me, and we can agree on a different license. 234 | -------------------------------------------------------------------------------- /cmake_uninstall.cmake.in: -------------------------------------------------------------------------------- 1 | if(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") 2 | message(FATAL_ERROR "Cannot find install manifest: @CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") 3 | endif(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") 4 | 5 | file(READ "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt" files) 6 | string(REGEX REPLACE "\n" ";" files "${files}") 7 | foreach(file ${files}) 8 | message(STATUS "Uninstalling $ENV{DESTDIR}${file}") 9 | if(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") 10 | exec_program( 11 | "@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\"" 12 | OUTPUT_VARIABLE rm_out 13 | RETURN_VALUE rm_retval 14 | ) 15 | if(NOT "${rm_retval}" STREQUAL 0) 16 | message(FATAL_ERROR "Problem when removing $ENV{DESTDIR}${file}") 17 | endif(NOT "${rm_retval}" STREQUAL 0) 18 | else(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") 19 | message(STATUS "File $ENV{DESTDIR}${file} does not exist.") 20 | endif(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") 21 | endforeach(file) 22 | -------------------------------------------------------------------------------- /src/config.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2015–2019 Andreas Misje 3 | * 4 | * This file is part of dhcpoptinj. 5 | * 6 | * dhcpoptinj is free software: you can redistribute it and/or modify it under 7 | * the terms of the GNU General Public License as published by the Free 8 | * Software Foundation, either version 3 of the License, or (at your option) 9 | * any later version. 10 | * 11 | * dhcpoptinj is distributed in the hope that it will be useful, but WITHOUT 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 14 | * more details. 15 | * 16 | * You should have received a copy of the GNU General Public License along 17 | * with dhcpoptinj. If not, see . 18 | */ 19 | 20 | #include 21 | #include 22 | #include "config.h" 23 | #include 24 | #include 25 | #include 26 | #include "options.h" 27 | #include 28 | #include "dhcp.h" 29 | #include 30 | #include 31 | 32 | static const char programName[] = "dhcpoptinj"; 33 | static const char defaultPIDFilePath[] = "/var/run/dhcpoptinj.pid"; 34 | static const char defaultConfFilePath[] = "/etc/dhcpoptinj.conf"; 35 | 36 | /* DHCP option lists for later serialisation: One for command line input and 37 | * one for configuration file(s). They need to be separated so that one source 38 | * does not override the other; the configuration file may be read in the 39 | * middle of the command line option parsing. */ 40 | static struct DHCPOptList *cmdDHCPOptList, *fileDHCPOptList; 41 | 42 | enum Source 43 | { 44 | SOURCE_CMD_LINE = 0, 45 | SOURCE_FILE, 46 | }; 47 | 48 | enum ConfFileParseOption 49 | { 50 | PARSE_ALLOW_NOEXIST = 0, 51 | PARSE_REQUIRE_EXIST = 1, 52 | }; 53 | 54 | /* Definitions for long-only options that cannot be identified with an ASCII 55 | * character: */ 56 | enum LongOnlyOpt { 57 | ForwardOnFail = 1000, 58 | }; 59 | 60 | /* Option definitions used to index options[] and optionCount[]: */ 61 | enum Option 62 | { 63 | OPT_CONF_FILE = 0, 64 | OPT_DEBUG, 65 | OPT_FOREGROUND, 66 | OPT_FORWARD_ON_FAIL, 67 | OPT_HELP, 68 | OPT_IGNORE_EXISTING_OPT, 69 | OPT_OPTION, 70 | OPT_PID_FILE, 71 | OPT_QUEUE, 72 | OPT_REMOVE_EXISTING_OPT, 73 | OPT_VERSION, 74 | 75 | OPT_COUNT, 76 | }; 77 | 78 | static const int sources[] = 79 | { 80 | SOURCE_CMD_LINE, 81 | SOURCE_FILE, 82 | }; 83 | 84 | static const struct option options[] = 85 | { 86 | [OPT_CONF_FILE] = { "conf-file", optional_argument, NULL, 'c' }, 87 | [OPT_DEBUG] = { "debug", no_argument, NULL, 'd' }, 88 | [OPT_FOREGROUND] = { "foreground", no_argument, NULL, 'f' }, 89 | [OPT_FORWARD_ON_FAIL] = { "forward-on-fail", no_argument, NULL, ForwardOnFail }, 90 | [OPT_HELP] = { "help", no_argument, NULL, 'h' }, 91 | [OPT_IGNORE_EXISTING_OPT] = { "ignore-existing-opt", no_argument, NULL, 'i' }, 92 | [OPT_OPTION] = { "option", required_argument, NULL, 'o' }, 93 | [OPT_PID_FILE] = { "pid-file", optional_argument, NULL, 'p' }, 94 | [OPT_QUEUE] = { "queue", required_argument, NULL, 'q' }, 95 | [OPT_REMOVE_EXISTING_OPT] = { "remove-existing-opt", no_argument, NULL, 'r' }, 96 | [OPT_VERSION] = { "version", no_argument, NULL, 'v' }, 97 | [OPT_COUNT] = {0}, 98 | }; 99 | /* Count the number of times arguments have been passed on the command line 100 | * and listed as keywords in the configuration file: */ 101 | static unsigned int optionCount[][OPT_COUNT] = 102 | { 103 | [SOURCE_CMD_LINE] = {0}, 104 | [SOURCE_FILE] = {0}, 105 | }; 106 | 107 | static struct Config *createDefaultConfig(void); 108 | static void printUsage(void); 109 | static void printHelp(void); 110 | static void printVersion(void); 111 | static int parseQueueNum(const char *string, uint16_t *queueNum); 112 | static void addDHCPOption(struct DHCPOptList *list, const char *string); 113 | static void parseConfFile(struct Config *config, const char *filePath, int parseOpts); 114 | static void parseOption(struct Config *config, int option, char *arg, enum Source source); 115 | static void validateOptionCombinations(void); 116 | static unsigned int totalOptionCount(int option); 117 | static char *trim(char *text); 118 | static int parseKeyValue(const char *key, const char *value, const char *filePath, 119 | unsigned lineNo); 120 | 121 | 122 | struct Config *conf_parseOpts(int argc, char * const *argv) 123 | { 124 | cmdDHCPOptList = dhcpOpt_createList(); 125 | fileDHCPOptList = dhcpOpt_createList(); 126 | if (!cmdDHCPOptList || !fileDHCPOptList) 127 | { 128 | fputs("Failed to allocate memory for DHCP option list\n", stderr); 129 | exit(EXIT_FAILURE); 130 | } 131 | 132 | struct Config *config = createDefaultConfig(); 133 | 134 | while (true) 135 | { 136 | int optVal = getopt_long(argc, argv, "c::dfhio:p::q:rv", options, NULL); 137 | 138 | /* Parsing finished: */ 139 | if (optVal == -1) 140 | break; 141 | 142 | int option = 0; 143 | for (; option < OPT_COUNT; ++option) 144 | { 145 | /* Look for the option in the option list: */ 146 | if (optVal == options[option].val) 147 | { 148 | parseOption(config, option, optarg, SOURCE_CMD_LINE); 149 | break; 150 | } 151 | } 152 | /* The option was not found and is invalid: */ 153 | if (option == OPT_COUNT) 154 | { 155 | printUsage(); 156 | exit(EXIT_FAILURE); 157 | } 158 | } 159 | 160 | /* If a config file path was not specified on the command line load the 161 | * default file, but do not complain if it does not exist: */ 162 | if (!optionCount[SOURCE_CMD_LINE][OPT_CONF_FILE]) 163 | parseConfFile(config, defaultConfFilePath, PARSE_ALLOW_NOEXIST); 164 | 165 | validateOptionCombinations(); 166 | 167 | /* dhcpoptinj does not accept any arguments, only options: */ 168 | if (argc - optind > 0) 169 | { 170 | fputs("No non-option arguments expected, but the following was passed: ", stderr); 171 | for (int i = optind; i < argc; ++i) 172 | fprintf(stderr, "\"%s\"%s", argv[i], i == argc - 1 ? "\n" : ", "); 173 | 174 | printUsage(); 175 | exit(EXIT_FAILURE); 176 | } 177 | 178 | /* If no DHCP options were passed on the command line use the options from 179 | * the configuration file: */ 180 | struct DHCPOptList *dhcpOptList = dhcpOpt_count(cmdDHCPOptList) ? 181 | cmdDHCPOptList : fileDHCPOptList; 182 | /* Add obligatory DHCP end option and serialise options: */ 183 | if (dhcpOpt_serialise(dhcpOptList, &config->dhcpOpts, &config->dhcpOptsSize)) 184 | { 185 | fputs("Failed to create DHCP option list\n", stderr); 186 | exit(EXIT_FAILURE); 187 | } 188 | /* Create an array of just the DHCP option codes: */ 189 | if (dhcpOpt_optCodes(dhcpOptList, &config->dhcpOptCodes, &config->dhcpOptCodeCount)) 190 | { 191 | fputs("Failed to create DHCP option code list\n", stderr); 192 | exit(EXIT_FAILURE); 193 | } 194 | dhcpOpt_destroyList(cmdDHCPOptList); 195 | dhcpOpt_destroyList(fileDHCPOptList); 196 | 197 | return config; 198 | } 199 | 200 | void conf_destroy(struct Config *config) 201 | { 202 | if (!config) 203 | return; 204 | 205 | free(config->pidFile); 206 | free(config->dhcpOpts); 207 | free(config->dhcpOptCodes); 208 | free(config); 209 | } 210 | 211 | static struct Config *createDefaultConfig(void) 212 | { 213 | struct Config *config = malloc(sizeof(*config)); 214 | if (!config) 215 | { 216 | fputs("Could not allocate space for configuration object\n", stderr); 217 | exit(EXIT_FAILURE); 218 | } 219 | *config = (struct Config) {0}; 220 | 221 | return config; 222 | } 223 | 224 | static void printUsage(void) 225 | { 226 | int progNameLen = (int)sizeof(programName) - 1; 227 | printVersion(); 228 | printf( 229 | "\n" 230 | "Usage: %s [-df] [--forward-on-fail] [-i|-r] [-p [pid_file]] \n" 231 | " %*s [-c [config_file]]\n" 232 | " %*s -q queue_num -o dhcp_option [(-o dhcp_option) ...]\n" 233 | " %s -h|-v\n" 234 | , 235 | programName, 236 | progNameLen, "", 237 | progNameLen, "", 238 | programName 239 | ); 240 | } 241 | 242 | static void printHelp(void) 243 | { 244 | printUsage(); 245 | printf( 246 | "\n" 247 | "%s takes a packet from a netfilter queue, ensures that it is a\n" 248 | "BOOTP/DHCP request, and injects additional DHCP options before\n" 249 | "accepting the packet. The following criteria must be fulfilled for\n" 250 | "%s to touch a packet:\n" 251 | " - The UDP packet must be BOOTP packet with a DHCP cookie\n" 252 | " - The UDP packet must not be fragmented\n" 253 | "\n" 254 | "Packets given to %s's queue are matched against protocol UDP\n" 255 | "and port 67/68. The packet is then assumed to be a BOOTP message. If\n" 256 | "it has the correct DHCP magic cookie value, %s will proceed to\n" 257 | "inject new options (removing existing options if requested). If the\n" 258 | "packet is not deemed a valid DHCP packet, it will be ignored and accepted.\n" 259 | "If it is a valid DHCP packet it cannot be fragmented. If it is, it will\n" 260 | "be dropped.\n" 261 | "\n" 262 | "Options:\n" 263 | "\n" 264 | " -c, --conf-file [file] Specify a different configuration file,\n" 265 | " or skip loading one altogether\n" 266 | " -d, --debug Make %s tell you as much as possible\n" 267 | " about what it does and tries to do\n" 268 | " -f, --foreground Prevent %s from running in the\n" 269 | " background\n" 270 | " --forward-on-fail If the process of injecting options should\n" 271 | " fail, let the unaltered DHCP packet pass\n" 272 | " through. The default behaviour is to drop\n" 273 | " the packet if options could not be injected\n" 274 | " -h, --help Print this help text\n" 275 | " -i, --ignore-existing-opt Proceed if an injected option already exists\n" 276 | " in the original packet. Unless\n" 277 | " --remove-existing-opt is provided, the\n" 278 | " default behaviour is to drop the packet\n" 279 | " -o, --option dhcp_option DHCP option to inject as a hex string,\n" 280 | " where the first byte indicates the option\n" 281 | " code. The option length field is automatically\n" 282 | " calculated and must be omitted. Several\n" 283 | " options may be injected\n" 284 | " -p, --pid-file [file] Write PID to file, using specified path\n" 285 | " or a default sensible location\n" 286 | " -q, --queue queue_num Netfilter queue number to use\n" 287 | " -r, --remove-existing-opt Remove existing DHCP options of the same\n" 288 | " kind as those to be injected\n" 289 | " -v, --version Display version\n" 290 | , 291 | programName, 292 | programName, 293 | programName, 294 | programName, 295 | programName, 296 | programName); 297 | printf( 298 | "\n" 299 | "%s will read %s (or the file specified with\n" 300 | "--conf-file) for options, specified as long option names with values\n" 301 | "separated by \"=\". \"conf-file\" is forbidden in a configuration file.\n" 302 | "Options passed on the command line will override options in the\n" 303 | "configuration file\n" 304 | "\n" 305 | "All the DHCP options specified with the -o/--option flag will be\n" 306 | "added before the terminating option (end option, 255). The packet is\n" 307 | "padded if necessary and sent back to netfilter. The IPv4 header\n" 308 | "checksum is recalculated, but the UDP checksum is set to 0 (disabled).\n" 309 | "None of the added options are checked for whether they are valid, or\n" 310 | "whether the option codes are valid. Options are currently not\n" 311 | "(automatically) padded individually, but they can be manually padded\n" 312 | "by adding options with code 0 (one pad byte per option). This special\n" 313 | "option is the only option that does not have any payload (the end\n" 314 | "option, 255, cannot be manually added). Padding individual options\n" 315 | "should not be necessary.\n" 316 | "\n" 317 | "The option hex string is written as a series of two-digit pairs,\n" 318 | "optionally delimited by one or more non-hexadecimal characters:\n" 319 | "'466A6173','46 6A 61 73', '46:6A:61:73' etc. There is a maximum limit\n" 320 | "of 255 bytes per option, excluding the option code (the first byte)\n" 321 | "and the automatically inserted length byte. At least one option must\n" 322 | "be provided.\n" 323 | "\n" 324 | "If the packet already contains a DHCP option that is to be injected\n" 325 | "(matched by code), the behaviour depends on the command line options\n" 326 | "--ignore-existing-opt and --remove-existing-opt:\n" 327 | " (none) The packet will be dropped\n" 328 | " -i The existing options are ignored and the injected options\n" 329 | " are added\n" 330 | " -r Any existing options are removed and the injected options\n" 331 | " are added.\n" 332 | "\n" 333 | "Note that injected options will not be injected in the same place as\n" 334 | "those that may have been removed if using -r. However, this should not\n" 335 | "matter.\n" 336 | "\n" 337 | "This utility allows you to do things that you probably should not do.\n" 338 | "Be good and leave packets alone.\n" 339 | , 340 | programName, 341 | defaultConfFilePath); 342 | } 343 | 344 | static void printVersion(void) 345 | { 346 | printf( 347 | "%s - DHCP option injector, version %s\n" 348 | "Copyright (C) 2015-2019 by Andreas Misje\n" 349 | "\n" 350 | "%s comes with ABSOLUTELY NO WARRANTY. This is free software,\n" 351 | "and you are welcome to redistribute it under certain conditions. See\n" 352 | "the GNU General Public Licence for details.\n", 353 | programName, 354 | DHCPOPTINJ_VERSION, 355 | programName); 356 | } 357 | 358 | static int parseQueueNum(const char *string, uint16_t *queueNum) 359 | { 360 | char *lastCh; 361 | long int num = strtol(string, &lastCh, 10); 362 | if (num == LONG_MAX || *lastCh != '\0' || num < 0 || num >= UINT16_MAX) 363 | return 1; 364 | 365 | *queueNum = num; 366 | return 0; 367 | } 368 | 369 | static void addDHCPOption(struct DHCPOptList *list, const char *string) 370 | { 371 | if (!string) 372 | return; 373 | 374 | /* Make room for option code byte and payload */ 375 | uint8_t buffer[1 + UINT8_MAX]; 376 | size_t length = 0; 377 | for (size_t i = 0; i < strlen(string) && length < sizeof(buffer);) 378 | { 379 | if (isxdigit(string[i]) && sscanf(&string[i], "%2hhx", &buffer[length]) == 1) 380 | { 381 | i += 2; 382 | ++length; 383 | } 384 | else 385 | ++i; 386 | } 387 | /* Will not happen; the cmd.line parsing code expects an argument: */ 388 | if (!length) 389 | return; 390 | if (length > UINT8_MAX) 391 | { 392 | fprintf(stderr, "DHCP option size exceeds the limit of %u bytes\n", 393 | UINT8_MAX); 394 | exit(EXIT_FAILURE); 395 | } 396 | 397 | uint16_t optCode = buffer[0]; 398 | 399 | if (optCode == DHCPOPT_END) 400 | { 401 | fputs("The DHCP end option (255) cannot be manually added", stderr); 402 | exit(EXIT_FAILURE); 403 | } 404 | else if (optCode == DHCPOPT_PAD) 405 | length = 1; 406 | else 407 | { 408 | if (length < 2) 409 | { 410 | fprintf(stderr, "The DHCP option string is too short (payload expected): %s\n", 411 | string); 412 | exit(EXIT_FAILURE); 413 | } 414 | } 415 | 416 | if (dhcpOpt_add(list, optCode, buffer + 1, length - 1)) 417 | { 418 | fputs("Failed to add DHCP option\n", stderr); 419 | exit(EXIT_FAILURE); 420 | } 421 | } 422 | 423 | static void parseConfFile(struct Config *config, const char *filePath, int parseOpts) 424 | { 425 | FILE *file = fopen(filePath, "r"); 426 | if (!file && (parseOpts & PARSE_REQUIRE_EXIST)) 427 | { 428 | fprintf(stderr, "Failed to open configuration file \"%s\": %s\n", 429 | filePath, strerror(errno)); 430 | exit(EXIT_FAILURE); 431 | } 432 | else if (!file) 433 | return; 434 | 435 | printf("Parsing configuration file \"%s\"\n", filePath); 436 | 437 | unsigned int lineNo = 0; 438 | char line[1024]; 439 | while (fgets(line, sizeof(line), file)) 440 | { 441 | ++lineNo; 442 | { 443 | /* If the comment character '#' is found, terminate the string at 444 | * this position: */ 445 | char *commentStart = strchr(line, '#'); 446 | if (commentStart) 447 | *commentStart = '\0'; 448 | } 449 | char *key = line; 450 | /* Keywords and values are separated by '=': */ 451 | char *value = strchr(line, '='); 452 | /* Ensure that the "value" pointer is not at the end of the buffer, 453 | * since we aim to access data past it: */ 454 | if (value && value - key < (ptrdiff_t)(sizeof(line) - 1)) 455 | { 456 | *value = '\0'; 457 | ++value; 458 | value = trim(value); 459 | } 460 | key = trim(key); 461 | /* Line is a comment. Do not parse: */ 462 | if (!*key) 463 | continue; 464 | 465 | int option = parseKeyValue(key, value, filePath, lineNo); 466 | parseOption(config, option, value, SOURCE_FILE); 467 | } 468 | 469 | fclose(file); 470 | } 471 | 472 | static void parseOption(struct Config *config, int option, char *arg, enum Source source) 473 | { 474 | /* Do not override command line options from configuration file: */ 475 | if (source == SOURCE_FILE && optionCount[SOURCE_CMD_LINE][option]) 476 | return; 477 | 478 | ++optionCount[source][option]; 479 | switch (option) 480 | { 481 | case OPT_CONF_FILE: 482 | /* An empty argument is allowed, in which case no file is ever loaded 483 | * (including the default one), so do nothing now that optionCount 484 | * has been incremented: */ 485 | if (arg) 486 | parseConfFile(config, arg, PARSE_REQUIRE_EXIST); 487 | 488 | break; 489 | 490 | case OPT_DEBUG: 491 | config->debug = true; 492 | break; 493 | 494 | case OPT_FOREGROUND: 495 | config->foreground = true; 496 | break; 497 | 498 | case OPT_FORWARD_ON_FAIL: 499 | config->fwdOnFail = true; 500 | break; 501 | 502 | case OPT_HELP: 503 | if (source == SOURCE_FILE) 504 | { 505 | fprintf(stderr, "The option \"%s\" doesn't make sense in a configuration " 506 | "file\n", options[option].name); 507 | exit(EXIT_FAILURE); 508 | } 509 | printHelp(); 510 | dhcpOpt_destroyList(cmdDHCPOptList); 511 | dhcpOpt_destroyList(fileDHCPOptList); 512 | conf_destroy(config); 513 | exit(EXIT_SUCCESS); 514 | break; 515 | 516 | case OPT_IGNORE_EXISTING_OPT: 517 | config->ignoreExistOpt = true; 518 | break; 519 | 520 | case OPT_PID_FILE: 521 | if (config->pidFile) 522 | break; 523 | { 524 | const char *src = arg ? arg : defaultPIDFilePath; 525 | size_t pidFilePathLen = strlen(src); 526 | config->pidFile = malloc(pidFilePathLen + 1); 527 | if (!config->pidFile) 528 | { 529 | fputs("Could not allocate space for PID file name\n", stderr); 530 | exit(EXIT_FAILURE); 531 | } 532 | // NOLINTNEXTLINE(clang-analyzer-security.insecureAPI.strcpy) 533 | strcpy(config->pidFile, src); 534 | } 535 | break; 536 | 537 | case OPT_OPTION: 538 | addDHCPOption(source == SOURCE_FILE ? fileDHCPOptList : cmdDHCPOptList, arg); 539 | break; 540 | 541 | case OPT_QUEUE: 542 | if (!arg || parseQueueNum(arg, &config->queue)) 543 | { 544 | fprintf(stderr, "Invalid queue number: %s\n", arg); 545 | printUsage(); 546 | exit(EXIT_FAILURE); 547 | } 548 | break; 549 | 550 | case OPT_REMOVE_EXISTING_OPT: 551 | config->removeExistOpt = true; 552 | break; 553 | 554 | case OPT_VERSION: 555 | if (source == SOURCE_FILE) 556 | { 557 | fprintf(stderr, "The option \"%s\" doesn't make sense in a configuration " 558 | "file\n", options[option].name); 559 | exit(EXIT_FAILURE); 560 | } 561 | printVersion(); 562 | dhcpOpt_destroyList(cmdDHCPOptList); 563 | dhcpOpt_destroyList(fileDHCPOptList); 564 | conf_destroy(config); 565 | exit(EXIT_SUCCESS); 566 | break; 567 | 568 | default: 569 | /* Only valid options are passed to this function */ 570 | break; 571 | } 572 | } 573 | 574 | static void validateOptionCombinations(void) 575 | { 576 | for (size_t source = 0; source < sizeof(sources)/sizeof(sources[0]); ++source) 577 | for (size_t option = 0; option < OPT_COUNT; ++option) 578 | /* If an option other than --option is passed more than once, freak out: */ 579 | if (optionCount[source][option] > 1 && option != OPT_OPTION) 580 | { 581 | fprintf(stderr, "%s%s can only be %s once\n", 582 | source == SOURCE_CMD_LINE ? "Option --" : "Keyword ", 583 | options[option].name, 584 | source == SOURCE_CMD_LINE ? "passed" : "specified"); 585 | printUsage(); 586 | exit(EXIT_FAILURE); 587 | } 588 | 589 | if (!totalOptionCount(OPT_QUEUE)) 590 | { 591 | fputs("Queue number required\n", stderr); 592 | printUsage(); 593 | exit(EXIT_FAILURE); 594 | } 595 | 596 | if (!totalOptionCount(OPT_OPTION)) 597 | { 598 | fputs("At least one DHCP option is required\n", stderr); 599 | printUsage(); 600 | exit(EXIT_FAILURE); 601 | } 602 | 603 | if (totalOptionCount(OPT_IGNORE_EXISTING_OPT) && totalOptionCount( 604 | OPT_REMOVE_EXISTING_OPT)) 605 | { 606 | fprintf(stderr, "Both %s%s and %s%s cannot be used at the same time\n", 607 | optionCount[SOURCE_CMD_LINE][OPT_IGNORE_EXISTING_OPT] ? "--" : "", 608 | options[OPT_IGNORE_EXISTING_OPT].name, 609 | optionCount[SOURCE_CMD_LINE][OPT_REMOVE_EXISTING_OPT] ? "--" : "", 610 | options[OPT_REMOVE_EXISTING_OPT].name); 611 | printUsage(); 612 | exit(EXIT_FAILURE); 613 | } 614 | } 615 | 616 | static unsigned int totalOptionCount(int option) 617 | { 618 | return optionCount[SOURCE_CMD_LINE][option] + optionCount[SOURCE_FILE][option]; 619 | } 620 | 621 | static char *trim(char *text) 622 | { 623 | if (!*text) 624 | return text; 625 | 626 | /* Trim leading and trailing whitespace and quote characters: */ 627 | for (char *ch = text + strlen(text) - 1; 628 | isspace((int)*ch) || *ch == '\'' || *ch == '\"'; *ch-- = '\0'); 629 | for (; isspace((int)*text) || *text == '\'' || *text == '\"'; *text++ = '\0'); 630 | 631 | return text; 632 | } 633 | 634 | static int parseKeyValue(const char *key, const char *value, const char *filePath, 635 | unsigned lineNo) 636 | { 637 | for (int option = 0; option < OPT_COUNT; ++option) 638 | { 639 | if (strcmp(key, options[option].name)) 640 | continue; 641 | 642 | if (options[option].has_arg == required_argument && !value) 643 | { 644 | fprintf(stderr, "Failed to parse \"%s\" at line %u: keyword \"%s\" requires an argument\n", 645 | filePath, lineNo, options[option].name); 646 | exit(EXIT_FAILURE); 647 | } 648 | else if (!options[option].has_arg && value) 649 | { 650 | fprintf(stderr, "Failed to parse \"%s\" at line %u: keyword \"%s\" does not take an argument\n", 651 | filePath, lineNo, options[option].name); 652 | exit(EXIT_FAILURE); 653 | } 654 | 655 | return option; 656 | } 657 | 658 | fprintf(stderr, "Failed to parse \"%s\" at line %u: \"%s\" is not a valid keyword\n", 659 | filePath, lineNo, key); 660 | exit(EXIT_FAILURE); 661 | return -1; 662 | } 663 | -------------------------------------------------------------------------------- /src/config.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2015–2019 Andreas Misje 3 | * 4 | * This file is part of dhcpoptinj. 5 | * 6 | * dhcpoptinj is free software: you can redistribute it and/or modify it under 7 | * the terms of the GNU General Public License as published by the Free 8 | * Software Foundation, either version 3 of the License, or (at your option) 9 | * any later version. 10 | * 11 | * dhcpoptinj is distributed in the hope that it will be useful, but WITHOUT 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 14 | * more details. 15 | * 16 | * You should have received a copy of the GNU General Public License along 17 | * with dhcpoptinj. If not, see . 18 | */ 19 | 20 | #ifndef DHCPOPTINJ_CONFIG_H 21 | #define DHCPOPTINJ_CONFIG_H 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | struct Config 28 | { 29 | /* Do not daemonise */ 30 | bool foreground; 31 | /* Print a lot of extra information */ 32 | bool debug; 33 | /* Absolute path to PID file, or NULL if writing PID is diabled */ 34 | char *pidFile; 35 | /* netfilter queue number */ 36 | uint16_t queue; 37 | /* DHCP options to be injected in a serialised format */ 38 | uint8_t *dhcpOpts; 39 | /* Size of serialised data */ 40 | size_t dhcpOptsSize; 41 | /* List of DHCP option codes to be injected */ 42 | uint8_t *dhcpOptCodes; 43 | /* Size of DHCP option code array */ 44 | size_t dhcpOptCodeCount; 45 | /* (none): Whine and drop packet 46 | * ignore: Ignore existing options and add new options 47 | * remove: Remove all exisiting options and add new options 48 | */ 49 | bool ignoreExistOpt; 50 | bool removeExistOpt; 51 | /* If option injection should fail, forward/accept packet instead of 52 | * dropping it */ 53 | bool fwdOnFail; 54 | }; 55 | 56 | struct Config *conf_parseOpts(int argc, char * const *argv); 57 | void conf_destroy(struct Config *config); 58 | 59 | #endif // DHCPOPTINJ_CONFIG_H 60 | -------------------------------------------------------------------------------- /src/dhcp.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2015–2019 Andreas Misje 3 | * 4 | * This file is part of dhcpoptinj. 5 | * 6 | * dhcpoptinj is free software: you can redistribute it and/or modify it under 7 | * the terms of the GNU General Public License as published by the Free 8 | * Software Foundation, either version 3 of the License, or (at your option) 9 | * any later version. 10 | * 11 | * dhcpoptinj is distributed in the hope that it will be useful, but WITHOUT 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 14 | * more details. 15 | * 16 | * You should have received a copy of the GNU General Public License along 17 | * with dhcpoptinj. If not, see . 18 | */ 19 | 20 | #include "dhcp.h" 21 | 22 | const char *dhcp_msgTypeString(uint8_t msgType) 23 | { 24 | switch (msgType) 25 | { 26 | case 1: 27 | return "DHCPDISCOVER"; 28 | case 2: 29 | return "DHCPOFFER"; 30 | case 3: 31 | return "DHCPREQUEST"; 32 | case 4: 33 | return "DHCPDECLINE"; 34 | case 5: 35 | return "DHCPACK"; 36 | case 6: 37 | return "DHCPNAK"; 38 | case 7: 39 | return "DHCPRELEASE"; 40 | case 8: 41 | return "DHCPINFORM"; 42 | default: 43 | return "??"; 44 | } 45 | } 46 | 47 | const char *dhcp_optionString(uint8_t option) 48 | { 49 | // From https://www.iana.org/assignments/bootp-dhcp-parameters/bootp-dhcp-parameters.xhtml 50 | static const char * const names[] = 51 | { 52 | [0] = "Pad", 53 | [1] = "Subnet Mask", 54 | [2] = "Time Offset", 55 | [3] = "Router", 56 | [4] = "Time Server", 57 | [5] = "Name Server", 58 | [6] = "Domain Server", 59 | [7] = "Log Server", 60 | [8] = "Quotes Server", 61 | [9] = "LPR Server", 62 | [10] = "Impress Server", 63 | [11] = "RLP Server", 64 | [12] = "Hostname", 65 | [13] = "Boot File Size", 66 | [14] = "Merit Dump File", 67 | [15] = "Domain Name", 68 | [16] = "Swap Server", 69 | [17] = "Root Path", 70 | [18] = "Extension File", 71 | [19] = "Forward On/Off", 72 | [20] = "SrcRte On/Off", 73 | [21] = "Policy Filter", 74 | [22] = "Max DG Assembly", 75 | [23] = "Default IP TTL", 76 | [24] = "MTU Timeout", 77 | [25] = "MTU Plateau", 78 | [26] = "MTU Interface", 79 | [27] = "MTU Subnet", 80 | [28] = "Broadcast Address", 81 | [29] = "Mask Discovery", 82 | [30] = "Mask Supplier", 83 | [31] = "Router Discovery", 84 | [32] = "Router Request", 85 | [33] = "Static Route", 86 | [34] = "Trailers", 87 | [35] = "ARP Timeout", 88 | [36] = "Ethernet", 89 | [37] = "Default TCP TTL", 90 | [38] = "Keepalive Time", 91 | [39] = "Keepalive Data", 92 | [40] = "NIS Domain", 93 | [41] = "NIS Servers", 94 | [42] = "NTP Servers", 95 | [43] = "Vendor Specific", 96 | [44] = "NETBIOS Name Srv", 97 | [45] = "NETBIOS Dist Srv", 98 | [46] = "NETBIOS Node Type", 99 | [47] = "NETBIOS Scope", 100 | [48] = "X Window Font", 101 | [49] = "X Window Manager", 102 | [50] = "Address Request", 103 | [51] = "Address Time", 104 | [52] = "Overload", 105 | [53] = "DHCP Msg Type", 106 | [54] = "DHCP Server Id", 107 | [55] = "Parameter List", 108 | [56] = "DHCP Message", 109 | [57] = "DHCP Max Msg Size", 110 | [58] = "Renewal Time", 111 | [59] = "Rebinding Time", 112 | [60] = "Class Id", 113 | [61] = "Client Id", 114 | [62] = "NetWare/IP Domain", 115 | [63] = "NetWare/IP Option", 116 | [64] = "NIS-Domain-Name", 117 | [65] = "NIS-Server-Addr", 118 | [66] = "Server-Name", 119 | [67] = "Bootfile-Name", 120 | [68] = "Home-Agent-Addrs", 121 | [69] = "SMTP-Server", 122 | [70] = "POP3-Server", 123 | [71] = "NNTP-Server", 124 | [72] = "WWW-Server", 125 | [73] = "Finger-Server", 126 | [74] = "IRC-Server", 127 | [75] = "StreetTalk-Server", 128 | [76] = "STDA-Server", 129 | [77] = "User-Class", 130 | [78] = "Directory Agent", 131 | [79] = "Service Scope", 132 | [80] = "Rapid Commit", 133 | [81] = "Client FQDN", 134 | [82] = "Relay Agent Information", 135 | [83] = "iSNS", 136 | // 84 removed/unassigned 137 | [85] = "NDS Servers", 138 | [86] = "NDS Tree Name", 139 | [87] = "NDS Context", 140 | [88] = "BCMCS Controller Domain Name list", 141 | [89] = "BCMCS Controller IPv4 address option", 142 | [90] = "Authentication", 143 | [91] = "client-last-transaction-time option", 144 | [92] = "associated-ip option", 145 | [93] = "Client System", 146 | [94] = "Client NDI", 147 | [95] = "LDAP", 148 | // 96 removed/unassigned 149 | [97] = "UUID/GUID", 150 | [98] = "User-Auth", 151 | [99] = "GEOCONF_CIVIC", 152 | [100] = "PCode", 153 | [101] = "TCode", 154 | // 102–108 removed/unassigned 155 | [109] = "OPTION_DHCP4O6_S46_SADDR", 156 | // 110 removed/unassigned 157 | // 111 removed/unassigned 158 | [112] = "Netinfo Address", 159 | [113] = "Netinfo Tag", 160 | [114] = "URL", 161 | // 115 removed/unassigned 162 | [116] = "Auto-Config", 163 | [117] = "Name Service Search", 164 | [118] = "Subnet Selection Option", 165 | [119] = "Domain Search", 166 | [120] = "SIP Servers DHCP Option", 167 | [121] = "Classless Static Route Option", 168 | [122] = "CCC", 169 | [123] = "GeoConf Option", 170 | [124] = "V-I Vendor Class", 171 | [125] = "V-I Vendor-Specific Information", 172 | // 126 removed/unassigned 173 | // 127 removed/unassigned 174 | [128] = "PXE / Etherboot signature", 175 | [129] = "PXE / Kernel options / Call Server IP address", 176 | [130] = "PXE / Ethernet interface / Discrimination string", 177 | [131] = "PXE / Remote statistics server IP address", 178 | [132] = "PXE", 179 | [133] = "PXE", 180 | [134] = "PXE", 181 | [135] = "PXE / HTTP Proxy for phone-specific applications", 182 | [136] = "OPTION_PANA_AGENT", 183 | [137] = "OPTION_V4_LOST", 184 | [138] = "OPTION_CAPWAP_AC_V4", 185 | [139] = "OPTION-IPv4_Address-MoS", 186 | [140] = "OPTION-IPv4_FQDN-MoS", 187 | [141] = "SIP UA Configuration Service Domains", 188 | [142] = "OPTION-IPv4_Address-ANDSF", 189 | [143] = "OPTION_V4_SZTP_REDIRECT", 190 | [144] = "GeoLoc", 191 | [145] = "FORCERENEW_NONCE_CAPABLE", 192 | [146] = "RDNSS Selection", 193 | // 147–149 unassigned 194 | [150] = "TFTP server address / Etherboot / GRUB configuration path name", 195 | [151] = "status-code", 196 | [152] = "base-time", 197 | [153] = "start-time-of-state", 198 | [154] = "query-start-time", 199 | [155] = "query-end-time", 200 | [156] = "dhcp-state", 201 | [157] = "data-source", 202 | [158] = "OPTION_V4_PCP_SERVER", 203 | [159] = "OPTION_V4_PORTPARAMS", 204 | [160] = "DHCP Captive-Portal", 205 | [161] = "OPTION_MUD_URL_V4", 206 | // 162–174 unassigned 207 | [175] = "Etherboot", 208 | [176] = "IP Telephone", 209 | [177] = "Etherboot / PacketCable and CableHome", 210 | // 178–207 unassigned 211 | [208] = "PXELINUX Magic", 212 | [209] = "Configuration File", 213 | [210] = "Path Prefix", 214 | [211] = "Reboot Time", 215 | [212] = "OPTION_6RD", 216 | [213] = "OPTION_V4_ACCESS_DOMAIN", 217 | // 214–219 unassigned 218 | [220] = "Subnet Allocation Option", 219 | [221] = "Virtual Subnet Selection (VSS) Option", 220 | // 222–223 unassigned 221 | // 224–254 reserved 222 | [255] = "End", 223 | }; 224 | 225 | return names[option] ? names[option] : "(unassigned/reserved)"; 226 | } 227 | -------------------------------------------------------------------------------- /src/dhcp.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2015–2019 Andreas Misje 3 | * 4 | * This file is part of dhcpoptinj. 5 | * 6 | * dhcpoptinj is free software: you can redistribute it and/or modify it under 7 | * the terms of the GNU General Public License as published by the Free 8 | * Software Foundation, either version 3 of the License, or (at your option) 9 | * any later version. 10 | * 11 | * dhcpoptinj is distributed in the hope that it will be useful, but WITHOUT 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 14 | * more details. 15 | * 16 | * You should have received a copy of the GNU General Public License along 17 | * with dhcpoptinj. If not, see . 18 | */ 19 | 20 | #ifndef DHCPOPTINJ_DHCP_H 21 | #define DHCPOPTINJ_DHCP_H 22 | 23 | #include 24 | 25 | #define DHCP_MAGIC_COOKIE 0x63825363 26 | #define DHCPOPT_PAD 0 27 | #define DHCPOPT_END 0xff 28 | #define DHCPOPT_TYPE 0x35 29 | 30 | #pragma pack(4) 31 | struct BootP 32 | { 33 | uint8_t op; 34 | uint8_t hwAddrType; 35 | uint8_t hwAddrLen; 36 | uint8_t hops; 37 | uint32_t xID; 38 | uint16_t secs; 39 | uint16_t flags; 40 | uint32_t clientAddr; 41 | uint32_t ownAddr; 42 | uint32_t serverAddr; 43 | uint32_t gwAddr; 44 | uint8_t clientHwAddr[16]; 45 | uint8_t serverName[64]; 46 | uint8_t file[128]; 47 | uint32_t cookie; 48 | // options … 49 | }; 50 | #pragma pack() 51 | 52 | #pragma pack(1) 53 | struct DHCPOption 54 | { 55 | uint8_t code; 56 | uint8_t length; 57 | uint8_t data[]; 58 | }; 59 | #pragma pack() 60 | 61 | const char *dhcp_msgTypeString(uint8_t msgType); 62 | const char *dhcp_optionString(uint8_t option); 63 | 64 | #endif // DHCPOPTINJ_DHCP_H 65 | -------------------------------------------------------------------------------- /src/dhcpoptinj.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2015–2019 Andreas Misje 3 | * 4 | * This file is part of dhcpoptinj. 5 | * 6 | * dhcpoptinj is free software: you can redistribute it and/or modify it under 7 | * the terms of the GNU General Public License as published by the Free 8 | * Software Foundation, either version 3 of the License, or (at your option) 9 | * any later version. 10 | * 11 | * dhcpoptinj is distributed in the hope that it will be useful, but WITHOUT 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 14 | * more details. 15 | * 16 | * You should have received a copy of the GNU General Public License along 17 | * with dhcpoptinj. If not, see . 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include "config.h" 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include "ipv4.h" 39 | #include "udp.h" 40 | #include "dhcp.h" 41 | #include 42 | 43 | #define MIN_BOOTP_SIZE 300 44 | 45 | #pragma pack(1) 46 | struct Packet 47 | { 48 | struct IPv4Header ipHeader; 49 | struct UDPHeader udpHeader; 50 | struct BootP bootp; 51 | }; 52 | #pragma pack() 53 | 54 | enum MangleResult 55 | { 56 | Mangle_OK = 0, 57 | Mangle_mallocFail, 58 | Mangle_optExists, 59 | }; 60 | 61 | /* Somewhat arbitrary, feel free to change */ 62 | #define MAX_PACKET_SIZE 2048 63 | 64 | /* The netfilter queue length 20 is also arbitrary. Hopefully it is 65 | * sufficient. */ 66 | static const uint32_t maxQueueLen = 20; 67 | static struct Config *config; 68 | static bool daemonised; 69 | static sig_atomic_t escapeMainLoop; 70 | static sig_atomic_t signalCaught; 71 | 72 | static int inspectPacket(struct nfq_q_handle *queue, struct nfgenmsg *pktInfo, 73 | struct nfq_data *pktData, void *userData); 74 | static bool packetIsComplete(const uint8_t *data, size_t size); 75 | static bool packetIsDHCP(const uint8_t *data); 76 | /* Inject DHCP options into DHCP packet */ 77 | static enum MangleResult manglePacket(const uint8_t *origData, size_t origDataSize, 78 | uint8_t **newData, size_t *newDataSize); 79 | static enum MangleResult mangleOptions(const uint8_t *origData, size_t origDataSize, 80 | uint8_t *newData, size_t *newDataSize); 81 | /* Write a message to syslog or standard stream, depending on whether the 82 | * process is run as a daemon or not */ 83 | static void logMessage(int priority, const char *format, ...); 84 | static void simplifyProgramName(char *programName); 85 | static void writePID(void); 86 | static void removePIDFile(void); 87 | static void destroyConfig(void); 88 | static void initSignalHandler(void); 89 | static void setEscapeMainLoopFlag(int signal); 90 | static void initLog(const char *programName); 91 | /* Debug-print all options to inject */ 92 | static void debugLogOptions(void); 93 | /* Very simple check of the provided option codes, warning user if something 94 | * looks incorrect */ 95 | static void inspectOptions(void); 96 | /* Debug-print packet header */ 97 | static void debugLogPacketHeader(const uint8_t *data, size_t size); 98 | /* Debug-print packet's existing DHCP options */ 99 | static void debugLogOptionFound(const struct DHCPOption *option); 100 | static void debugLogOption(const char *action, const struct DHCPOption *option); 101 | static void debugLogInjectedOptions(void); 102 | 103 | int main(int argc, char *argv[]) 104 | { 105 | simplifyProgramName(argv[0]); 106 | config = conf_parseOpts(argc, argv); 107 | initLog(argv[0]); 108 | 109 | debugLogOptions(); 110 | inspectOptions(); 111 | 112 | logMessage(LOG_DEBUG, "Initialising netfilter queue\n"); 113 | 114 | struct nfq_handle *nfq = nfq_open(); 115 | if (!nfq) 116 | { 117 | /* Most likely causes are insufficient permissions (missing 118 | * CAP_NET_ADMIN capability) or an another process already bound to the 119 | * same queue. */ 120 | logMessage(LOG_ERR, "Failed to initialise netfilter queue library: %s\n", 121 | strerror(errno)); 122 | exit(EXIT_FAILURE); 123 | } 124 | nfq_unbind_pf(nfq, AF_INET); 125 | if (nfq_bind_pf(nfq, AF_INET) < 0) 126 | { 127 | logMessage(LOG_ERR, "Failed to bind queue handler to AF_INET: %s\n", 128 | strerror(errno)); 129 | exit(EXIT_FAILURE); 130 | } 131 | 132 | struct nfq_q_handle *queue = nfq_create_queue(nfq, config->queue, &inspectPacket, 133 | NULL); 134 | if (!queue) 135 | { 136 | logMessage(LOG_ERR, "Failed to create netfilter queue for queue %d: %s\n", 137 | config->queue, strerror(errno)); 138 | exit(EXIT_FAILURE); 139 | } 140 | 141 | if (nfq_set_mode(queue, NFQNL_COPY_PACKET, MAX_PACKET_SIZE) < 0) 142 | { 143 | logMessage(LOG_ERR, "Failed to set netfilter queue mode: %s\n", strerror( 144 | errno)); 145 | exit(EXIT_FAILURE); 146 | } 147 | 148 | if (nfq_set_queue_maxlen(queue, maxQueueLen) < 0) 149 | { 150 | logMessage(LOG_ERR, "Failed to set netfilter queue length: %s\n", strerror( 151 | errno)); 152 | exit(EXIT_FAILURE); 153 | } 154 | 155 | if (!config->foreground) 156 | { 157 | logMessage(LOG_DEBUG, "Daemonising\n"); 158 | if (daemon(false, false)) 159 | { 160 | logMessage(LOG_ERR, "Failed to daemonise: daemon() failed: %s\n", 161 | strerror(errno)); 162 | exit(EXIT_FAILURE); 163 | } 164 | umask(022); 165 | daemonised = true; 166 | } 167 | 168 | initSignalHandler(); 169 | writePID(); 170 | 171 | if (config->debug) 172 | logMessage(LOG_DEBUG, "Initialisation completed. Waiting for packets to " 173 | "mangle on queue %" PRIu16 "\n", config->queue); 174 | else 175 | logMessage(LOG_INFO, "Started\n"); 176 | 177 | int exitCode = EXIT_SUCCESS; 178 | int queueFd = nfq_fd(nfq); 179 | for (; !escapeMainLoop; ) 180 | { 181 | char packet[MAX_PACKET_SIZE] __attribute__((aligned)); 182 | ssize_t bytes = recv(queueFd, packet, sizeof(packet), 0); 183 | if (bytes < -1) 184 | { 185 | logMessage(LOG_ERR, "Failed to retrieve packet: %s\n", strerror(errno)); 186 | exitCode = EXIT_FAILURE; 187 | break; 188 | } 189 | else if (bytes > 0) 190 | { 191 | logMessage(LOG_DEBUG, "Received %zd bytes\n", bytes); 192 | if (nfq_handle_packet(nfq, packet, bytes)) 193 | logMessage(LOG_WARNING, "Failed to handle packet: %s\n", strerror(errno)); 194 | } 195 | } 196 | 197 | if (signalCaught) 198 | { 199 | const char *signalName = 200 | signalCaught == SIGINT ? "SIGINT" : 201 | signalCaught == SIGTERM ? "SIGTERM" : 202 | signalCaught == SIGHUP ? "SIGHUP" : "??"; 203 | 204 | logMessage(LOG_NOTICE, "Caught signal %s\n", signalName); 205 | } 206 | 207 | logMessage(LOG_DEBUG, "Destroying netfilter queue\n"); 208 | nfq_destroy_queue(queue); 209 | 210 | /* According to libnetfilter_queue's nfqnl_test.c example, nfq_unbind_pf(…) 211 | * should NOT be called during clean up. */ 212 | nfq_close(nfq); 213 | 214 | logMessage(LOG_NOTICE, "Exiting\n"); 215 | removePIDFile(); 216 | destroyConfig(); 217 | 218 | return exitCode; 219 | } 220 | 221 | static int inspectPacket(struct nfq_q_handle *queue, struct nfgenmsg *pktInfo, 222 | struct nfq_data *pktData, void *userData) 223 | { 224 | (void)pktInfo; 225 | (void)userData; 226 | 227 | uint8_t *packet; 228 | ssize_t size = nfq_get_payload(pktData, &packet); 229 | if (size < 0) 230 | { 231 | logMessage(LOG_WARNING, "Failed to retrieve packet from queue: %s\n", 232 | strerror(errno)); 233 | return 1; 234 | } 235 | 236 | struct nfqnl_msg_packet_hdr *metaHeader = nfq_get_msg_packet_hdr(pktData); 237 | if (!packetIsComplete(packet, (size_t)size)) 238 | { 239 | logMessage(LOG_INFO, "Dropping the packet because it is incomplete\n"); 240 | return nfq_set_verdict(queue, ntohl(metaHeader->packet_id), NF_DROP, 0, NULL); 241 | } 242 | if (!packetIsDHCP(packet)) 243 | { 244 | logMessage(LOG_DEBUG, "Ignoring non-DHCP packet\n"); 245 | return nfq_set_verdict(queue, ntohl(metaHeader->packet_id), NF_ACCEPT, 0, NULL); 246 | } 247 | /* We do not have the logic needed to support fragmented packets: */ 248 | if (ipv4_packetFragmented(&((const struct Packet *)packet)->ipHeader)) 249 | { 250 | uint32_t verdict = config->fwdOnFail ? NF_ACCEPT : NF_DROP; 251 | if (config->fwdOnFail) 252 | logMessage(LOG_INFO, "Ignoring fragmented packet\n"); 253 | else 254 | logMessage(LOG_INFO, "Dropping the packet because it is fragmented\n"); 255 | 256 | return nfq_set_verdict(queue, ntohl(metaHeader->packet_id), verdict, 0, NULL); 257 | } 258 | if (config->debug) 259 | debugLogPacketHeader(packet, (size_t)size); 260 | 261 | logMessage(LOG_INFO, "Mangling packet\n"); 262 | 263 | uint8_t *mangledData = NULL; 264 | size_t mangledDataSize = 0; 265 | enum MangleResult result = manglePacket(packet, (size_t)size, &mangledData, 266 | &mangledDataSize); 267 | if (result == Mangle_mallocFail) 268 | { 269 | logMessage(LOG_WARNING, "Failed to allocate memory for mangled packet\n"); 270 | uint32_t verdict = config->fwdOnFail ? NF_ACCEPT : NF_DROP; 271 | return nfq_set_verdict(queue, ntohl(metaHeader->packet_id), verdict, 0, NULL); 272 | } 273 | else if (result == Mangle_optExists) 274 | { 275 | logMessage(LOG_INFO, "Dropping the packet because option already exists\n"); 276 | return nfq_set_verdict(queue, ntohl(metaHeader->packet_id), NF_DROP, 0, NULL); 277 | } 278 | else if (result != Mangle_OK) 279 | { 280 | logMessage(LOG_ERR, "Internal error: unexpected return value from manglePacket(): %d\n", 281 | result); 282 | uint32_t verdict = config->fwdOnFail ? NF_ACCEPT : NF_DROP; 283 | return nfq_set_verdict(queue, ntohl(metaHeader->packet_id), verdict, 0, NULL); 284 | } 285 | 286 | if (config->debug) 287 | logMessage(LOG_DEBUG, "Sending mangled packet\n"); 288 | 289 | int res = nfq_set_verdict(queue, ntohl(metaHeader->packet_id), NF_ACCEPT, 290 | mangledDataSize, mangledData); 291 | free(mangledData); 292 | return res; 293 | } 294 | 295 | static bool packetIsComplete(const uint8_t *data, size_t size) 296 | { 297 | if (size < sizeof(struct IPv4Header)) 298 | return false; 299 | 300 | const struct Packet *packet = (const struct Packet *)data; 301 | return packet->ipHeader.totalLen >= sizeof(*packet); 302 | } 303 | 304 | static bool packetIsDHCP(const uint8_t *data) 305 | { 306 | const struct Packet *packet = (const struct Packet *)data; 307 | 308 | if (packet->ipHeader.protocol != IPPROTO_UDP) 309 | return false; 310 | 311 | uint16_t destPort = ntohs(packet->udpHeader.destPort); 312 | if (!(destPort == 67 || destPort == 68)) 313 | return false; 314 | if (packet->udpHeader.length < sizeof(struct UDPHeader) + sizeof(struct BootP)) 315 | return false; 316 | 317 | const struct BootP *dhcp = &packet->bootp; 318 | if (ntohl(dhcp->cookie) != DHCP_MAGIC_COOKIE) 319 | return false; 320 | 321 | return true; 322 | } 323 | 324 | static enum MangleResult manglePacket(const uint8_t *origData, size_t origDataSize, 325 | uint8_t **newData, size_t *newDataSize) 326 | { 327 | const struct Packet *origPacket = (const struct Packet *)origData; 328 | size_t ipHdrSize = ipv4_headerLen(&origPacket->ipHeader); 329 | size_t udpHdrSize = sizeof(struct UDPHeader); 330 | size_t headersSize = ipHdrSize + udpHdrSize + sizeof(struct BootP); 331 | /* Allocate size for a new packet, slightly larger than needed in order to 332 | * avoid reallocation.: */ 333 | *newDataSize = origDataSize + config->dhcpOptsSize + 1; /* room for padding */ 334 | size_t newPayloadSize = *newDataSize - ipHdrSize - udpHdrSize; 335 | /* Ensure that the DHCP packet (the BOOTP header and payload) is at least 336 | * MIN_BOOTP_SIZE bytes long (as per the RFC 1542 requirement): */ 337 | if (newPayloadSize < MIN_BOOTP_SIZE) 338 | *newDataSize += MIN_BOOTP_SIZE - newPayloadSize; 339 | 340 | *newData = malloc(*newDataSize); 341 | if (!*newData) 342 | return Mangle_mallocFail; 343 | 344 | /* Copy 'static' data (everything but the DHCP options) from original 345 | * packet: */ 346 | memcpy(*newData, origPacket, headersSize); 347 | enum MangleResult result = mangleOptions(origData, origDataSize, *newData, 348 | newDataSize); 349 | if (result != Mangle_OK) 350 | { 351 | free(*newData); 352 | return result; 353 | } 354 | 355 | /* Recalculate actual size (and potential padding) after mangling options 356 | * (the initially calculated size is possibly slightly too large, since it 357 | * could not forsee how many bytes of DHCP options that was going to be 358 | * removed; however, the header size fields need to be correct): */ 359 | newPayloadSize = *newDataSize - ipHdrSize - udpHdrSize; 360 | size_t padding = (2 - (newPayloadSize % 2)) % 2; 361 | if (newPayloadSize < MIN_BOOTP_SIZE) 362 | padding = MIN_BOOTP_SIZE - newPayloadSize; 363 | 364 | newPayloadSize += padding; 365 | *newDataSize = ipHdrSize + udpHdrSize + newPayloadSize; 366 | 367 | struct Packet *newPacket = (struct Packet *)*newData; 368 | struct IPv4Header *ipHeader = &newPacket->ipHeader; 369 | ipHeader->totalLen = htons(*newDataSize); 370 | ipHeader->checksum = 0; 371 | ipHeader->checksum = ipv4_checksum(ipHeader); 372 | 373 | struct UDPHeader *udpHeader = &newPacket->udpHeader; 374 | udpHeader->length = htons(udpHdrSize + newPayloadSize); 375 | udpHeader->checksum = 0; 376 | 377 | if (padding && config->debug) 378 | logMessage(LOG_DEBUG, "Padding with %zu byte(s) to meet minimal BOOTP payload " 379 | "size\n", padding); 380 | 381 | /* Pad to (at least) MIN_BOOTP_SIZE bytes: */ 382 | for (size_t i = *newDataSize - padding; i < *newDataSize; ++i) 383 | (*newData)[i] = DHCPOPT_PAD; 384 | 385 | return Mangle_OK; 386 | } 387 | 388 | static enum MangleResult mangleOptions(const uint8_t *origData, size_t origDataSize, 389 | uint8_t *newData, size_t *newDataSize) 390 | { 391 | /* Start with position of the first DHCP option: */ 392 | size_t origOffset = offsetof(struct Packet, bootp) + sizeof(struct BootP); 393 | size_t newOffset = origOffset; 394 | size_t padCount = 0; 395 | while (origOffset < origDataSize) 396 | { 397 | const struct DHCPOption *option = (const struct DHCPOption *)(origData + origOffset); 398 | size_t optSize = 399 | option->code == DHCPOPT_PAD || option->code == DHCPOPT_END ? 1 400 | : sizeof(struct DHCPOption) + option->length; 401 | 402 | if (config->debug) 403 | { 404 | if (option->code == DHCPOPT_PAD) 405 | ++padCount; 406 | else 407 | { 408 | if (padCount) 409 | logMessage(LOG_DEBUG, "Found %zu PAD options (removing)\n", padCount); 410 | 411 | debugLogOptionFound(option); 412 | padCount = 0; 413 | } 414 | } 415 | 416 | if (option->code == DHCPOPT_END) 417 | break; 418 | /* If existing options are to be ignored and not removed, just copy 419 | * them: */ 420 | else if (config->ignoreExistOpt && !config->removeExistOpt) 421 | { 422 | if (config->debug) 423 | logMessage(LOG_DEBUG, " (copying)\n"); 424 | 425 | memcpy(newData + newOffset, option, optSize); 426 | newOffset += optSize; 427 | } 428 | /* Otherwise we need to check whether one of the injected options are 429 | * already present: */ 430 | else 431 | { 432 | bool optFound = false; 433 | if (option->code != DHCPOPT_END) 434 | for (size_t i = 0; i < config->dhcpOptCodeCount; ++i) 435 | if (option->code == config->dhcpOptCodes[i]) 436 | { 437 | optFound = true; 438 | break; 439 | } 440 | 441 | /* If the option already exists in original payload, but is not to be 442 | * removed, and ignore command line option is not provided, drop 443 | * packet: */ 444 | if (optFound && !config->removeExistOpt && !config->ignoreExistOpt) 445 | { 446 | if (config->debug) 447 | logMessage(LOG_DEBUG, " (conflict)\n"); 448 | 449 | return Mangle_optExists; 450 | } 451 | /* Copy option if it is not to be removed: */ 452 | else if ((optFound && !config->removeExistOpt) || !optFound) 453 | { 454 | if (config->debug) 455 | logMessage(LOG_DEBUG, " (copying)\n"); 456 | 457 | memcpy(newData + newOffset, option, optSize); 458 | newOffset += optSize; 459 | } 460 | else if (config->debug) 461 | logMessage(LOG_DEBUG, " (removing)\n"); 462 | } 463 | origOffset += optSize; 464 | } 465 | 466 | if (config->debug) 467 | debugLogInjectedOptions(); 468 | 469 | /* Inject DHCP options: */ 470 | for (size_t i = 0; i < config->dhcpOptsSize; ++i) 471 | newData[newOffset + i] = config->dhcpOpts[i]; 472 | 473 | newOffset += config->dhcpOptsSize; 474 | 475 | if (config->debug) 476 | logMessage(LOG_DEBUG, "Inserting END option\n"); 477 | 478 | /* Finally insert the END option: */ 479 | newData[newOffset++] = DHCPOPT_END; 480 | /* Update (reduce) packet size: */ 481 | *newDataSize = newOffset; 482 | return Mangle_OK; 483 | } 484 | 485 | /* Instruct clang that "format" is a printf-style format parameter to avoid 486 | * non-literal format string warnings in clang: */ 487 | __attribute__((__format__ (__printf__, 2, 0))) 488 | static void logMessage(int priority, const char *format, ...) 489 | { 490 | if (priority == LOG_DEBUG && !config->debug) 491 | return; 492 | 493 | va_list args1, args2; 494 | va_start(args1, format); 495 | va_copy(args2, args1); 496 | 497 | if (config->foreground || !daemonised) 498 | { 499 | FILE *f = stderr; 500 | if (priority == LOG_NOTICE || priority == LOG_INFO || priority == LOG_DEBUG) 501 | f = stdout; 502 | 503 | /* NOLINTNEXTLINE(clang-analyzer-valist.Uninitialized) */ 504 | vfprintf(f, format, args1); 505 | } 506 | va_end(args1); 507 | 508 | if (!config->foreground) 509 | vsyslog(priority, format, args2); 510 | 511 | va_end(args2); 512 | } 513 | 514 | static void simplifyProgramName(char *programName) 515 | { 516 | char *simplifiedName = basename(programName); 517 | size_t len = strlen(simplifiedName); 518 | memmove(programName, simplifiedName, len); 519 | programName[len] = '\0'; 520 | } 521 | 522 | static void writePID(void) 523 | { 524 | if (!config->pidFile) 525 | return; 526 | 527 | pid_t pid = getpid(); 528 | logMessage(LOG_DEBUG, "Writing PID %ld to %s\n", (long)pid, config->pidFile); 529 | 530 | FILE *f = fopen(config->pidFile, "w"); 531 | if (!f) 532 | { 533 | logMessage(LOG_ERR, "Failed to write PID to %s: %s\n", config->pidFile, 534 | strerror(errno)); 535 | exit(EXIT_FAILURE); 536 | } 537 | fprintf(f, "%ld", (long)pid); 538 | fclose(f); 539 | } 540 | 541 | static void removePIDFile(void) 542 | { 543 | if (config->pidFile) 544 | { 545 | logMessage(LOG_DEBUG, "Removing PID file %s\n", config->pidFile); 546 | unlink(config->pidFile); 547 | } 548 | } 549 | 550 | static void destroyConfig(void) 551 | { 552 | conf_destroy(config); 553 | } 554 | 555 | static void initSignalHandler(void) 556 | { 557 | logMessage(LOG_DEBUG, "Initialising signal handler\n"); 558 | 559 | struct sigaction sigAction = { .sa_handler = &setEscapeMainLoopFlag }; 560 | 561 | if (sigaction(SIGTERM, &sigAction, NULL) || sigaction(SIGINT, &sigAction, NULL) || 562 | sigaction(SIGHUP, &sigAction, NULL)) 563 | { 564 | logMessage(LOG_ERR, "Failed to initialise signal handler: %s\n", strerror( 565 | errno)); 566 | exit(EXIT_FAILURE); 567 | } 568 | } 569 | 570 | static void setEscapeMainLoopFlag(int signal) 571 | { 572 | signalCaught = signal; 573 | escapeMainLoop = true; 574 | } 575 | 576 | static void initLog(const char *programName) 577 | { 578 | openlog(programName, 0, LOG_DAEMON); 579 | if (config->debug) 580 | setlogmask(LOG_UPTO(LOG_DEBUG)); 581 | else 582 | setlogmask(LOG_UPTO(LOG_INFO)); 583 | } 584 | 585 | static void debugLogOptions(void) 586 | { 587 | if (!config->debug) 588 | return; 589 | 590 | logMessage(LOG_DEBUG, "%zu DHCP option(s) to inject (with a total of %zu bytes): ", 591 | config->dhcpOptCodeCount, config->dhcpOptsSize); 592 | 593 | for (size_t i = 0; i < config->dhcpOptCodeCount; ++i) 594 | { 595 | uint8_t code = config->dhcpOptCodes[i]; 596 | bool atEnd = i == config->dhcpOptCodeCount - 1; 597 | const char *delim = atEnd ? "\n" : ", "; 598 | logMessage(LOG_DEBUG, "%u (0x%02X) (%s)%s", code, code, dhcp_optionString( 599 | code), delim); 600 | } 601 | logMessage(LOG_DEBUG, "Existing options will be %s\n", config->removeExistOpt ? 602 | "removed" : "left in place"); 603 | } 604 | 605 | static void inspectOptions(void) 606 | { 607 | size_t nonSpecialOptCount = 0; 608 | for (size_t i = 0; i < config->dhcpOptCodeCount; ++i) 609 | { 610 | uint8_t code = config->dhcpOptCodes[i]; 611 | if (code != DHCPOPT_PAD && code != DHCPOPT_END) 612 | ++nonSpecialOptCount; 613 | } 614 | 615 | if (!nonSpecialOptCount) 616 | logMessage(LOG_WARNING, "Warning: Only padding options added\n"); 617 | } 618 | 619 | static void debugLogPacketHeader(const uint8_t *data, size_t size) 620 | { 621 | const struct Packet *packet = (const struct Packet *)data; 622 | const uint8_t *mac = packet->bootp.clientHwAddr; 623 | struct IPAddr 624 | { 625 | uint8_t o1; 626 | uint8_t o2; 627 | uint8_t o3; 628 | uint8_t o4; 629 | } __attribute__((packed)); 630 | 631 | const struct IPAddr *destIP = (const struct IPAddr *)&packet->ipHeader.destAddr; 632 | 633 | logMessage(LOG_DEBUG, "Inspecting %zu-byte DHCP packet from " 634 | "%02X:%02X:%02X:%02X:%02X:%02X to %d.%d.%d.%d:%d\n", 635 | size, 636 | mac[0], mac[1], mac[2], mac[3], mac[4], mac[5], 637 | destIP->o1, destIP->o2, destIP->o3, destIP->o4, 638 | ntohs(packet->udpHeader.destPort) 639 | ); 640 | } 641 | 642 | static void debugLogOptionFound(const struct DHCPOption *option) 643 | { 644 | if (option->code == DHCPOPT_PAD) 645 | return; 646 | else if (option->code == DHCPOPT_END) 647 | logMessage(LOG_DEBUG,"Found END option %s\n", config->dhcpOptCodeCount ? 648 | "(removing)" : "(copying)"); 649 | else if (option->code == DHCPOPT_TYPE && option->length == 1) 650 | logMessage(LOG_DEBUG, "Found option % 3hhd (0x%02hhX) (DHCP message type) %s", 651 | option->code, option->code, dhcp_msgTypeString(option->data[0])); 652 | else 653 | debugLogOption("Found", option); 654 | } 655 | 656 | static void debugLogOption(const char *action, const struct DHCPOption *option) 657 | { 658 | /* String buffer for hex string (maximum DHCP option length (255) times 659 | * three characters (two digits and a space)) */ 660 | char optPayload[UINT8_MAX * 3]; 661 | size_t i = 0; 662 | for (; i < option->length; ++i) 663 | sprintf(optPayload + 3*i, "%02X ", option->data[i]); 664 | 665 | /* Remove last space: */ 666 | if (i) 667 | optPayload[3*i - 1] = '\0'; 668 | 669 | const char *optName = dhcp_optionString(option->code); 670 | size_t optNameLen = strlen(optName); 671 | const size_t alignedWidth = 24; 672 | logMessage(LOG_DEBUG, "%s option % 3hhd (0x%02hhX) (%s)%*s with % 3d-byte payload %s", 673 | action, 674 | option->code, 675 | option->code, 676 | optName, 677 | (int)(optNameLen > alignedWidth ? 0 : alignedWidth - optNameLen), 678 | "", 679 | option->length, 680 | optPayload); 681 | } 682 | 683 | static void debugLogInjectedOptions(void) 684 | { 685 | for (size_t offset = 0; offset < config->dhcpOptsSize;) 686 | { 687 | const struct DHCPOption *option = (const struct DHCPOption *)(&config->dhcpOpts[offset]); 688 | debugLogOption("Injecting", option); 689 | logMessage(LOG_DEBUG, "%s", "\n"); 690 | offset += option->code == DHCPOPT_PAD || option->code == DHCPOPT_END ? 1 691 | : sizeof(struct DHCPOption) + option->length; 692 | } 693 | } 694 | -------------------------------------------------------------------------------- /src/ipv4.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2015–2019 Andreas Misje 3 | * 4 | * This file is part of dhcpoptinj. 5 | * 6 | * dhcpoptinj is free software: you can redistribute it and/or modify it under 7 | * the terms of the GNU General Public License as published by the Free 8 | * Software Foundation, either version 3 of the License, or (at your option) 9 | * any later version. 10 | * 11 | * dhcpoptinj is distributed in the hope that it will be useful, but WITHOUT 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 14 | * more details. 15 | * 16 | * You should have received a copy of the GNU General Public License along 17 | * with dhcpoptinj. If not, see . 18 | */ 19 | 20 | #include "ipv4.h" 21 | #include 22 | #include 23 | 24 | uint16_t ipv4_checksum(const struct IPv4Header *ipv4Header) 25 | { 26 | const uint16_t *data = (const uint16_t *)ipv4Header; 27 | size_t len = sizeof(*ipv4Header); 28 | uint32_t checksum = 0; 29 | 30 | while (len > 1) 31 | { 32 | checksum += *data++; 33 | len -= 2; 34 | } 35 | 36 | if (len > 0) 37 | checksum += *(const uint8_t *)data; 38 | 39 | while (checksum >> 16) 40 | checksum = (checksum & 0xffff) + (checksum >> 16); 41 | 42 | return ~checksum; 43 | } 44 | 45 | size_t ipv4_headerLen(const struct IPv4Header *ipHeader) 46 | { 47 | return (ipHeader->verIHL & 0xf) * 4U; 48 | } 49 | 50 | bool ipv4_packetFragmented(const struct IPv4Header *ipHeader) 51 | { 52 | uint16_t field = ntohs(ipHeader->flagsFrag); 53 | bool fragmentsToCome = (field >> 13) & 4; 54 | uint16_t fragmentOffset = field & 0x1fff; 55 | return fragmentsToCome || fragmentOffset; 56 | } 57 | -------------------------------------------------------------------------------- /src/ipv4.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2015–2019 Andreas Misje 3 | * 4 | * This file is part of dhcpoptinj. 5 | * 6 | * dhcpoptinj is free software: you can redistribute it and/or modify it under 7 | * the terms of the GNU General Public License as published by the Free 8 | * Software Foundation, either version 3 of the License, or (at your option) 9 | * any later version. 10 | * 11 | * dhcpoptinj is distributed in the hope that it will be useful, but WITHOUT 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 14 | * more details. 15 | * 16 | * You should have received a copy of the GNU General Public License along 17 | * with dhcpoptinj. If not, see . 18 | */ 19 | 20 | #ifndef DHCPOPTINJ_IPV4_H 21 | #define DHCPOPTINJ_IPV4_H 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | #pragma pack(2) 28 | struct IPv4Header 29 | { 30 | uint8_t verIHL; 31 | uint8_t dscpECN; 32 | uint16_t totalLen; 33 | uint16_t id; 34 | uint16_t flagsFrag; 35 | uint8_t ttl; 36 | uint8_t protocol; 37 | uint16_t checksum; 38 | uint32_t sourceAddr; 39 | uint32_t destAddr; 40 | }; 41 | #pragma pack() 42 | 43 | uint16_t ipv4_checksum(const struct IPv4Header *ipv4Header); 44 | size_t ipv4_headerLen(const struct IPv4Header *ipv4Header); 45 | bool ipv4_packetFragmented(const struct IPv4Header *ipHeader); 46 | 47 | #endif // DHCPOPTINJ_IPV4_H 48 | -------------------------------------------------------------------------------- /src/options.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2015–2019 Andreas Misje 3 | * 4 | * This file is part of dhcpoptinj. 5 | * 6 | * dhcpoptinj is free software: you can redistribute it and/or modify it under 7 | * the terms of the GNU General Public License as published by the Free 8 | * Software Foundation, either version 3 of the License, or (at your option) 9 | * any later version. 10 | * 11 | * dhcpoptinj is distributed in the hope that it will be useful, but WITHOUT 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 14 | * more details. 15 | * 16 | * You should have received a copy of the GNU General Public License along 17 | * with dhcpoptinj. If not, see . 18 | */ 19 | 20 | #include "options.h" 21 | #include 22 | #include 23 | #include "dhcp.h" 24 | 25 | /* Just like struct DHCPOption in dhcp.h, but with (fixed) storage for option 26 | * payload) */ 27 | struct DHCPOpt 28 | { 29 | uint8_t code; 30 | uint8_t length; 31 | uint8_t data[UINT8_MAX]; 32 | }; 33 | 34 | struct DHCPOptList 35 | { 36 | struct DHCPOpt *options; 37 | size_t count; 38 | size_t capacity; 39 | }; 40 | 41 | static int resizeList(struct DHCPOptList *list); 42 | /* Total number of bytes needed to serialise list */ 43 | static size_t totalSize(const struct DHCPOptList *list); 44 | 45 | struct DHCPOptList *dhcpOpt_createList(void) 46 | { 47 | struct DHCPOptList *list = malloc(sizeof(*list)); 48 | *list = (struct DHCPOptList){0}; 49 | if (resizeList(list)) 50 | { 51 | dhcpOpt_destroyList(list); 52 | return NULL; 53 | } 54 | return list; 55 | } 56 | 57 | void dhcpOpt_destroyList(struct DHCPOptList *list) 58 | { 59 | free(list->options); 60 | free(list); 61 | } 62 | 63 | bool dhcpOpt_optExists(const struct DHCPOptList *list, int code) 64 | { 65 | for (size_t i = 0; i < list->count; ++i) 66 | if (code == list->options[i].code) 67 | return true; 68 | 69 | return false; 70 | } 71 | 72 | int dhcpOpt_add(struct DHCPOptList *list, int code, const void *data, size_t size) 73 | { 74 | if (resizeList(list)) 75 | return 1; 76 | 77 | struct DHCPOpt *opt = &list->options[list->count]; 78 | opt->code = code; 79 | opt->length = size; 80 | if (data && size) 81 | memcpy(opt->data, data, size); 82 | 83 | ++list->count; 84 | return 0; 85 | } 86 | 87 | size_t dhcpOpt_count(struct DHCPOptList *list) 88 | { 89 | return list->count; 90 | } 91 | 92 | int dhcpOpt_serialise(const struct DHCPOptList *list, uint8_t **buffer, size_t *size) 93 | { 94 | *size = totalSize(list); 95 | if (!*size) 96 | return 1; 97 | 98 | *buffer = malloc(*size); 99 | if (!*buffer) 100 | { 101 | *size = 0; 102 | return 1; 103 | } 104 | 105 | size_t bufI = 0; 106 | for (size_t optI = 0; optI < list->count; ++optI) 107 | { 108 | struct DHCPOpt *opt = &list->options[optI]; 109 | (*buffer)[bufI++] = opt->code; 110 | /* Only copy option length and payload if it actually has a payload (the 111 | * special options 'pad' and 'end' are one-byte options) */ 112 | if (opt->code != DHCPOPT_PAD && opt->code != DHCPOPT_END) 113 | { 114 | (*buffer)[bufI++] = opt->length; 115 | for (size_t optDataI = 0; optDataI < opt->length; ++optDataI) 116 | (*buffer)[bufI++] = opt->data[optDataI]; 117 | } 118 | } 119 | 120 | return 0; 121 | } 122 | 123 | int dhcpOpt_optCodes(const struct DHCPOptList *list, uint8_t **buffer, size_t *size) 124 | { 125 | *size = list->count; 126 | *buffer = malloc(*size); 127 | if (!*buffer) 128 | { 129 | *size = 0; 130 | return 1; 131 | } 132 | 133 | for (size_t i = 0; i < list->count; ++i) 134 | (*buffer)[i] = list->options[i].code; 135 | 136 | return 0; 137 | } 138 | 139 | static int resizeList(struct DHCPOptList *list) 140 | { 141 | if (list->count < list->capacity) 142 | return 0; 143 | 144 | /* Inital capacity of 24 options somewhat arbitrary, but should be 145 | * sufficient for most cases */ 146 | size_t newCapacity = list->capacity ? list->capacity * 2 : 24; 147 | struct DHCPOpt *newOptList = realloc(list->options, newCapacity * sizeof( 148 | struct DHCPOpt)); 149 | if (!newOptList) 150 | return 1; 151 | 152 | list->options = newOptList; 153 | list->capacity = newCapacity; 154 | return 0; 155 | } 156 | 157 | static size_t totalSize(const struct DHCPOptList *list) 158 | { 159 | size_t size = 0; 160 | for (size_t i = 0; i < list->count; ++i) 161 | { 162 | struct DHCPOpt *opt = &list->options[i]; 163 | /* The special options 'pad' and 'end' are only one byte long, whilst 164 | * other options are minimum two bytes long */ 165 | if (opt->code == DHCPOPT_PAD || opt->code == DHCPOPT_END) 166 | ++size; 167 | else 168 | size += 2U + list->options[i].length; 169 | } 170 | 171 | return size; 172 | } 173 | -------------------------------------------------------------------------------- /src/options.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2015–2019 Andreas Misje 3 | * 4 | * This file is part of dhcpoptinj. 5 | * 6 | * dhcpoptinj is free software: you can redistribute it and/or modify it under 7 | * the terms of the GNU General Public License as published by the Free 8 | * Software Foundation, either version 3 of the License, or (at your option) 9 | * any later version. 10 | * 11 | * dhcpoptinj is distributed in the hope that it will be useful, but WITHOUT 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 14 | * more details. 15 | * 16 | * You should have received a copy of the GNU General Public License along 17 | * with dhcpoptinj. If not, see . 18 | */ 19 | 20 | /* This is a small help module for creating and extending a list of DHCP 21 | * options. When finished, the list can be serialised to a buffer and added to 22 | * a BOOTP packet, completing a DHCP request. 23 | */ 24 | 25 | #ifndef DHCPOPTINJ_OPTIONS_H 26 | #define DHCPOPTINJ_OPTIONS_H 27 | 28 | #include 29 | #include 30 | #include 31 | 32 | struct DHCPOptList; 33 | 34 | struct DHCPOptList *dhcpOpt_createList(void); 35 | void dhcpOpt_destroyList(struct DHCPOptList *list); 36 | bool dhcpOpt_optExists(const struct DHCPOptList *list, int code); 37 | int dhcpOpt_add(struct DHCPOptList *list, int code, const void *data, size_t size); 38 | size_t dhcpOpt_count(struct DHCPOptList *list); 39 | /* Serialise option list to an array (code + length + payload) */ 40 | int dhcpOpt_serialise(const struct DHCPOptList *list, uint8_t **buffer, size_t *size); 41 | /* Create an array containg the integer codes of all the DHCP options in the 42 | * list */ 43 | int dhcpOpt_optCodes(const struct DHCPOptList *list, uint8_t **buffer, size_t *size); 44 | 45 | #endif // DHCPOPTINJ_OPTIONS_H 46 | -------------------------------------------------------------------------------- /src/udp.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2015–2019 Andreas Misje 3 | * 4 | * This file is part of dhcpoptinj. 5 | * 6 | * dhcpoptinj is free software: you can redistribute it and/or modify it under 7 | * the terms of the GNU General Public License as published by the Free 8 | * Software Foundation, either version 3 of the License, or (at your option) 9 | * any later version. 10 | * 11 | * dhcpoptinj is distributed in the hope that it will be useful, but WITHOUT 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 14 | * more details. 15 | * 16 | * You should have received a copy of the GNU General Public License along 17 | * with dhcpoptinj. If not, see . 18 | */ 19 | 20 | #ifndef DHCPOPTINJ_UDP_H 21 | #define DHCPOPTINJ_UDP_H 22 | 23 | #include 24 | 25 | #pragma pack(2) 26 | struct UDPHeader 27 | { 28 | uint16_t sourcePort; 29 | uint16_t destPort; 30 | uint16_t length; 31 | uint16_t checksum; 32 | }; 33 | #pragma pack() 34 | 35 | #endif // DHCPOPTINJ_UDP_H 36 | --------------------------------------------------------------------------------