├── .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 | [](https://travis-ci.org/misje/dhcpoptinj) [](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 | [](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 |
--------------------------------------------------------------------------------