1 // Copyright (C) 2013-2020 Internet Systems Consortium, Inc. ("ISC")
2 //
3 // This Source Code Form is subject to the terms of the Mozilla Public
4 // License, v. 2.0. If a copy of the MPL was not distributed with this
5 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7 /// @file pkt_send_co.cc Defines the pkt4_send and pkt6_send callout functions.
8
9 #include <config.h>
10 #include <asiolink/io_address.h>
11 #include <hooks/hooks.h>
12 #include <dhcp/dhcp4.h>
13 #include <dhcp/dhcp6.h>
14 #include <dhcp/option_string.h>
15 #include <dhcp/option_custom.h>
16 #include <dhcp/option6_ia.h>
17 #include <dhcp/option6_iaaddr.h>
18 #include <dhcp/option6_iaprefix.h>
19 #include <dhcp/docsis3_option_defs.h>
20 #include <dhcp/pkt4.h>
21 #include <dhcp/pkt6.h>
22 #include <user_chk.h>
23
24 using namespace isc::dhcp;
25 using namespace isc::hooks;
26 using namespace user_chk;
27 using namespace std;
28
29 // prototypes for local helper functions
30 void generate_output_record(const std::string& id_type_str,
31 const std::string& id_val_str,
32 const std::string& addr_str,
33 const bool& registered);
34 std::string getV6AddrStr (Pkt6Ptr response);
35 std::string getAddrStrIA_NA(OptionPtr options);
36 std::string getAddrStrIA_PD(OptionPtr options);
37 bool checkIAStatus(boost::shared_ptr<Option6IA>& ia_opt);
38
39 void add4Options(Pkt4Ptr& response, const UserPtr& user);
40 void add4Option(Pkt4Ptr& response, uint8_t opt_code, std::string& opt_value);
41 void add6Options(Pkt6Ptr& response, const UserPtr& user);
42 void add6Option(OptionPtr& vendor, uint8_t opt_code, std::string& opt_value);
43 const UserPtr& getDefaultUser4();
44 const UserPtr& getDefaultUser6();
45
46 // Functions accessed by the hooks framework use C linkage to avoid the name
47 // mangling that accompanies use of the C++ compiler as well as to avoid
48 // issues related to namespaces.
49 extern "C" {
50
51 /// @brief This callout is called at the "pkt4_send" hook.
52 ///
53 /// This function generates the user check outcome and modifies options
54 /// to the IPv4 response packet based on whether the user is registered or not.
55 ///
56 /// It retrieves a pointer to the registered user from the callout context.
57 /// This value should have been set upstream. If the registered user pointer
58 /// is non-null (i.e the user is registered), then a registered user outcome
59 /// is recorded in the outcome output and the vendor properties are altered
60 /// based upon this user's properties.
61 ///
62 /// A null value means the user is not registered and a unregistered user
63 /// outcome is recorded in the outcome output and the vendor properties
64 /// are altered based upon the default IPv4 user in the registry (if defined).
65 ///
66 /// @param handle CalloutHandle which provides access to context.
67 ///
68 /// @return 0 upon success, non-zero otherwise.
pkt4_send(CalloutHandle & handle)69 int pkt4_send(CalloutHandle& handle) {
70 CalloutHandle::CalloutNextStep status = handle.getStatus();
71 if (status == CalloutHandle::NEXT_STEP_DROP) {
72 return (0);
73 }
74
75 if (status == CalloutHandle::NEXT_STEP_SKIP) {
76 isc_throw(isc::InvalidOperation, "packet pack already handled");
77 }
78
79 try {
80 Pkt4Ptr response;
81 handle.getArgument("response4", response);
82
83 uint8_t packet_type = response->getType();
84 if (packet_type == DHCPNAK) {
85 std::cout << "DHCP UserCheckHook : pkt4_send"
86 << "skipping packet type: "
87 << static_cast<int>(packet_type) << std::endl;
88 return (0);
89 }
90
91 // Get the user id saved from the query packet.
92 HWAddrPtr hwaddr;
93 handle.getContext(query_user_id_label, hwaddr);
94
95 // Get registered_user pointer.
96 UserPtr registered_user;
97 handle.getContext(registered_user_label, registered_user);
98
99 // Fetch the lease address.
100 isc::asiolink::IOAddress addr = response->getYiaddr();
101
102 if (registered_user) {
103 // add options based on user
104 // then generate registered output record
105 std::cout << "DHCP UserCheckHook : pkt4_send registered_user is: "
106 << registered_user->getUserId() << std::endl;
107
108 // Add the outcome entry to the output file.
109 generate_output_record(UserId::HW_ADDRESS_STR, hwaddr->toText(),
110 addr.toText(), true);
111 add4Options(response, registered_user);
112 } else {
113 // add default options based
114 // then generate not registered output record
115 std::cout << "DHCP UserCheckHook : pkt4_send no registered_user"
116 << std::endl;
117 // Add the outcome entry to the output file.
118 generate_output_record(UserId::HW_ADDRESS_STR, hwaddr->toText(),
119 addr.toText(), false);
120
121 add4Options(response, getDefaultUser4());
122 }
123 } catch (const std::exception& ex) {
124 std::cout << "DHCP UserCheckHook : pkt4_send unexpected error: "
125 << ex.what() << std::endl;
126 return (1);
127 }
128
129 return (0);
130 }
131
132 /// @brief This callout is called at the "pkt6_send" hook.
133 ///
134 /// This function generates the user check outcome and modifies options
135 /// to the IPv6 response packet based on whether the user is registered or not.
136 ///
137 /// It retrieves a pointer to the registered user from the callout context.
138 /// This value should have been set upstream. If the registered user pointer
139 /// is non-null (i.e the user is registered), then a registered user outcome
140 /// is recorded in the outcome output and the vendor properties are altered
141 /// based upon this user's properties.
142 ///
143 /// A null value means the user is not registered and a unregistered user
144 /// outcome is recorded in the outcome output and the vendor properties
145 /// are altered based upon the default IPv6 user in the registry (if defined).
146 /// @param handle CalloutHandle which provides access to context.
147 ///
148 /// @return 0 upon success, non-zero otherwise.
pkt6_send(CalloutHandle & handle)149 int pkt6_send(CalloutHandle& handle) {
150 CalloutHandle::CalloutNextStep status = handle.getStatus();
151 if (status == CalloutHandle::NEXT_STEP_DROP) {
152 return (0);
153 }
154
155 if (status == CalloutHandle::NEXT_STEP_SKIP) {
156 isc_throw(isc::InvalidOperation, "packet pack already handled");
157 }
158
159 try {
160 Pkt6Ptr response;
161 handle.getArgument("response6", response);
162
163 // Fetch the lease address as a string
164 std::string addr_str = getV6AddrStr(response);
165 if (addr_str.empty()) {
166 // packet did not contain an address, must be failed.
167 std::cout << "pkt6_send: Skipping packet address is blank"
168 << std::endl;
169 return (0);
170 }
171
172 // Get the user id saved from the query packet.
173 DuidPtr duid;
174 handle.getContext(query_user_id_label, duid);
175
176 // Get registered_user pointer.
177 UserPtr registered_user;
178 handle.getContext(registered_user_label, registered_user);
179
180 if (registered_user) {
181 // add options based on user
182 // then generate registered output record
183 std::cout << "DHCP UserCheckHook : pkt6_send registered_user is: "
184 << registered_user->getUserId() << std::endl;
185 // Add the outcome entry to the output file.
186 generate_output_record(UserId::DUID_STR, duid->toText(),
187 addr_str, true);
188 add6Options(response, registered_user);
189 } else {
190 // add default options based
191 // then generate not registered output record
192 std::cout << "DHCP UserCheckHook : pkt6_send no registered_user"
193 << std::endl;
194 // Add the outcome entry to the output file.
195 generate_output_record(UserId::DUID_STR, duid->toText(),
196 addr_str, false);
197 add6Options(response, getDefaultUser6());
198 }
199 } catch (const std::exception& ex) {
200 std::cout << "DHCP UserCheckHook : pkt6_send unexpected error: "
201 << ex.what() << std::endl;
202 return (1);
203 }
204
205 return (0);
206 }
207
208 } // extern C
209
210 /// @brief Adds IPv4 options to the response packet based on given user
211 ///
212 /// Adds or replaces IPv4 options with values from the given user, if
213 /// the user has corresponding properties defined. Currently it supports
214 /// the following options:
215 ///
216 /// - DHO_BOOT_FILE_NAME from user property "bootfile"
217 /// - DHO_TFTP_SERVER_NAME from user property "tftp_server"
218 ///
219 /// @param response IPv4 response packet
220 /// @param user User from whom properties are sourced
add4Options(Pkt4Ptr & response,const UserPtr & user)221 void add4Options(Pkt4Ptr& response, const UserPtr& user) {
222 // If user is null, do nothing.
223 if (!user) {
224 return;
225 }
226
227 // If the user has bootfile property, update it in the response.
228 std::string opt_value = user->getProperty("bootfile");
229 if (!opt_value.empty()) {
230 std::cout << "DHCP UserCheckHook : add4Options "
231 << "adding boot file:" << opt_value << std::endl;
232
233 // Add boot file to packet.
234 add4Option(response, DHO_BOOT_FILE_NAME, opt_value);
235
236 // Boot file also goes in file field.
237 response->setFile((const uint8_t*)(opt_value.c_str()),
238 opt_value.length());
239 }
240
241 // If the user has tftp server property, update it in the response.
242 opt_value = user->getProperty("tftp_server");
243 if (!opt_value.empty()) {
244 std::cout << "DHCP UserCheckHook : add4Options "
245 << "adding TFTP server:" << opt_value << std::endl;
246
247 // Add tftp server option to packet.
248 add4Option(response, DHO_TFTP_SERVER_NAME, opt_value);
249 }
250 // add next option here
251 }
252
253 /// @brief Adds/updates are specific IPv4 string option in response packet.
254 ///
255 /// @param response IPV4 response packet to update
256 /// @param opt_code DHCP standard numeric code of the option
257 /// @param opt_value String value of the option
add4Option(Pkt4Ptr & response,uint8_t opt_code,std::string & opt_value)258 void add4Option(Pkt4Ptr& response, uint8_t opt_code, std::string& opt_value) {
259 // Remove the option if it exists.
260 OptionPtr opt = response->getOption(opt_code);
261 if (opt) {
262 response->delOption(opt_code);
263 }
264
265 // Now add the option.
266 opt.reset(new OptionString(Option::V4, opt_code, opt_value));
267 response->addOption(opt);
268 }
269
270
271 /// @brief Adds IPv6 vendor options to the response packet based on given user
272 ///
273 /// Adds or replaces IPv6 vendor options with values from the given user, if
274 /// the user has the corresponding properties defined. Currently it supports
275 /// the following options:
276 ///
277 /// - DOCSIS3_V6_CONFIG_FILE from user property "bootfile"
278 /// - DOCSIS3_V6_TFTP_SERVERS from user property "tftp_server"
279 ///
280 /// @param response IPv5 response packet
281 /// @param user User from whom properties are sourced
add6Options(Pkt6Ptr & response,const UserPtr & user)282 void add6Options(Pkt6Ptr& response, const UserPtr& user) {
283 if (!user) {
284 return;
285 }
286
287 /// @todo: if packets have no vendor opt... do we need to add it
288 /// if its not there? If so how?
289 OptionPtr vendor = response->getOption(D6O_VENDOR_OPTS);
290 if (!vendor) {
291 std::cout << "DHCP UserCheckHook : add6Options "
292 << "response has no vendor option to update" << std::endl;
293 return;
294 }
295
296 // If the user defines bootfile, set the option in response.
297 std::string opt_value = user->getProperty("bootfile");
298 if (!opt_value.empty()) {
299 std::cout << "DHCP UserCheckHook : add6Options "
300 << "adding boot file:" << opt_value << std::endl;
301 add6Option(vendor, DOCSIS3_V6_CONFIG_FILE, opt_value);
302 }
303
304 // If the user defines tftp server, set the option in response.
305 opt_value = user->getProperty("tftp_server");
306 if (!opt_value.empty()) {
307 std::cout << "DHCP UserCheckHook : add6Options "
308 << "adding tftp server:" << opt_value << std::endl;
309
310 add6Option(vendor, DOCSIS3_V6_TFTP_SERVERS, opt_value);
311 }
312
313 // add next option here
314 }
315
316 /// @brief Adds/updates a specific IPv6 string vendor option.
317 ///
318 /// @param vendor IPv6 vendor option set to update
319 /// @param opt_code DHCP standard numeric code of the option
320 /// @param opt_value String value of the option
add6Option(OptionPtr & vendor,uint8_t opt_code,std::string & opt_value)321 void add6Option(OptionPtr& vendor, uint8_t opt_code, std::string& opt_value) {
322 vendor->delOption(opt_code);
323 OptionPtr option(new OptionString(Option::V6, opt_code, opt_value));
324 vendor->addOption(option);
325 }
326
327
328 /// @brief Adds an entry to the end of the user check outcome file.
329 ///
330 /// @todo This ought to be replaced with an abstract output similar to
331 /// UserDataSource to allow greater flexibility.
332 ///
333 /// Each user entry is written in an ini-like format, with one name-value pair
334 /// per line as follows:
335 ///
336 /// id_type=<id type><br/>
337 /// client=<id str><br/>
338 /// subnet=<addr str><br/>
339 /// registered=<is registered>
340 ///
341 /// where:
342 /// <id type> text label of the id type: "HW_ADDR" or "DUID"
343 /// <id str> user's id formatted as either isc::dhcp::Hwaddr.toText() or
344 /// isc::dhcp::DUID.toText()
345 /// <addr str> selected subnet formatted as isc::dhcp::Subnet4::toText() or
346 /// isc::dhcp::Subnet6::toText() as appropriate.
347 /// <is registered> "yes" or "no"
348 ///
349 /// Sample IPv4 entry would like this:
350 ///
351 /// @code
352 /// id_type=DUID
353 /// client=00:01:00:01:19:ef:e6:3b:00:0c:01:02:03:04
354 /// subnet=2001:db8:2::/64
355 /// registered=yes
356 /// id_type=duid
357 /// @endcode
358 ///
359 /// Sample IPv4 entry would like this:
360 ///
361 /// @code
362 /// id_type=DUID
363 /// id_type=HW_ADDR
364 /// client=hwtype=1 00:0c:01:02:03:05
365 /// subnet=152.77.5.0/24
366 /// registered=no
367 /// @endcode
368 ///
369 /// @param id_type_str text label identify the id type
370 /// @param id_val_str text representation of the user id
371 /// @param addr_str text representation of the selected subnet
372 /// @param registered boolean indicating if the user is registered or not
generate_output_record(const std::string & id_type_str,const std::string & id_val_str,const std::string & addr_str,const bool & registered)373 void generate_output_record(const std::string& id_type_str,
374 const std::string& id_val_str,
375 const std::string& addr_str,
376 const bool& registered)
377 {
378 user_chk_output << "id_type=" << id_type_str << std::endl
379 << "client=" << id_val_str << std::endl
380 << "addr=" << addr_str << std::endl
381 << "registered=" << (registered ? "yes" : "no")
382 << std::endl
383 << std::endl; // extra line in between
384
385 // @todo Flush is here to ensure output is immediate for demo purposes.
386 // Performance would generally dictate not using it.
387 flush(user_chk_output);
388 }
389
390 /// @brief Stringify the lease address or prefix IPv6 response packet
391 ///
392 /// Converts the lease value, either an address or a prefix, into a string
393 /// suitable for the user check outcome output. Note that this will use
394 /// the first address or prefix in the response for responses with more than
395 /// one value.
396 ///
397 /// @param response IPv6 response packet from which to extract the lease value.
398 ///
399 /// @return A string containing the lease value.
400 /// @throw isc::BadValue if the response contains neither an IA_NA nor IA_PD
401 /// option.
getV6AddrStr(Pkt6Ptr response)402 std::string getV6AddrStr(Pkt6Ptr response) {
403 OptionPtr tmp = response->getOption(D6O_IA_NA);
404 if (tmp) {
405 return(getAddrStrIA_NA(tmp));
406 }
407
408 // IA_NA not there so try IA_PD
409 tmp = response->getOption(D6O_IA_PD);
410 if (!tmp) {
411 isc_throw (isc::BadValue, "Response has neither IA_NA nor IA_PD");
412 }
413
414 return(getAddrStrIA_PD(tmp));
415 }
416
417 /// @brief Stringify the lease address in an D6O_IA_NA option set
418 ///
419 /// Converts the IA_NA lease address into a string suitable for the user check
420 /// outcome output.
421 ///
422 /// @param options pointer to the Option6IA instance from which to extract the
423 /// lease address.
424 ///
425 /// @return A string containing the lease address.
426 ///
427 /// @throw isc::BadValue if the lease address cannot be extracted from options.
getAddrStrIA_NA(OptionPtr options)428 std::string getAddrStrIA_NA(OptionPtr options) {
429 boost::shared_ptr<Option6IA> ia =
430 boost::dynamic_pointer_cast<Option6IA>(options);
431
432 if (!ia) {
433 isc_throw (isc::BadValue, "D6O_IA_NA option invalid");
434 }
435
436 // If status indicates a failure return a blank string.
437 if (!checkIAStatus(ia)) {
438 return (std::string(""));
439 }
440
441 options = ia->getOption(D6O_IAADDR);
442 if (!options) {
443 isc_throw (isc::BadValue, "D6O_IAADDR option missing");
444 }
445
446 boost::shared_ptr<Option6IAAddr> addr_option;
447 addr_option = boost::dynamic_pointer_cast<Option6IAAddr>(options);
448 if (!addr_option) {
449 isc_throw (isc::BadValue, "D6O_IAADDR Option6IAAddr missing");
450 }
451
452 isc::asiolink::IOAddress addr = addr_option->getAddress();
453 return (addr.toText());
454 }
455
456 /// @brief Stringify the lease prefix in an D6O_IA_PD option set
457 ///
458 /// Converts the IA_PD lease prefix into a string suitable for the user check
459 /// outcome output.
460 ///
461 /// @param options pointer to the Option6IA instance from which to extract the
462 /// lease prefix.
463 ///
464 /// @return A string containing lease prefix
465 ///
466 /// @throw isc::BadValue if the prefix cannot be extracted from options.
getAddrStrIA_PD(OptionPtr options)467 std::string getAddrStrIA_PD(OptionPtr options) {
468 boost::shared_ptr<Option6IA> ia =
469 boost::dynamic_pointer_cast<Option6IA>(options);
470
471 // Make sure we have an IA_PD option.
472 if (!ia) {
473 isc_throw (isc::BadValue, "D6O_IA_PD option invalid");
474 }
475
476 // Check the response for success status. If it isn't a success response
477 // there will not be a lease prefix value which is denoted by returning
478 // an empty string.
479 if (!checkIAStatus(ia)) {
480 return (std::string(""));
481 }
482
483 // Get the prefix option the IA_PD option.
484 options = ia->getOption(D6O_IAPREFIX);
485 if (!options) {
486 isc_throw(isc::BadValue, "D6O_IAPREFIX option is missing");
487 }
488
489 boost::shared_ptr<Option6IAPrefix> addr_option;
490 addr_option = boost::dynamic_pointer_cast<Option6IAPrefix>(options);
491 if (!addr_option) {
492 isc_throw (isc::BadValue, "D6O_IA_PD addr option bad");
493 }
494
495 // Get the address and prefix length values.
496 isc::asiolink::IOAddress addr = addr_option->getAddress();
497 uint8_t prefix_len = addr_option->getLength();
498
499 // Build the output string and return it.
500 stringstream buf;
501 buf << addr.toText() << "/" << static_cast<int>(prefix_len);
502 return (buf.str());
503 }
504
505 /// @brief Tests given IA option set for successful status.
506 ///
507 /// This function is used to determine if the given Option6IA represents
508 /// a successful lease operation. If it contains no status option or a status
509 /// option of 0 (which is defined to mean success), then the option represents
510 /// success and should contain a lease value (address or prefix).
511 ///
512 /// @param ia pointer to the Option6IA to test
513 ///
514 /// @return True if the option represents success, false otherwise.
checkIAStatus(boost::shared_ptr<Option6IA> & ia)515 bool checkIAStatus(boost::shared_ptr<Option6IA>& ia) {
516 OptionCustomPtr status =
517 boost::dynamic_pointer_cast
518 <OptionCustom>(ia->getOption(D6O_STATUS_CODE));
519
520 // If a non-zero status is present, bail.
521 if (status) {
522 int status_val = status->readInteger<uint16_t>(0);
523 if (status_val != 0) {
524 std::cout << "SKIPPING PACKET STATUS is not success:"
525 << status_val << std::endl;
526 return (false);
527 }
528 }
529
530 return (true);
531 }
532
533 /// @brief Fetches the default IPv4 user from the registry.
534 ///
535 /// The default user may be used to provide default property values.
536 ///
537 /// @return A pointer to the IPv4 user or null if not defined.
getDefaultUser4()538 const UserPtr& getDefaultUser4() {
539 return (user_registry->findUser(UserId(UserId::HW_ADDRESS,
540 default_user4_id_str)));
541 }
542
543 /// @brief Fetches the default IPv6 user from the registry.
544 ///
545 /// The default user may be used to provide default property values.
546 ///
547 /// @return A pointer to the IPv6 user or null if not defined.
getDefaultUser6()548 const UserPtr& getDefaultUser6() {
549 return (user_registry->findUser(UserId(UserId::DUID,
550 default_user6_id_str)));
551 }
552
553