diff options
Diffstat (limited to 'src/hooks/dhcp/user_chk/pkt_send_co.cc')
-rw-r--r-- | src/hooks/dhcp/user_chk/pkt_send_co.cc | 552 |
1 files changed, 552 insertions, 0 deletions
diff --git a/src/hooks/dhcp/user_chk/pkt_send_co.cc b/src/hooks/dhcp/user_chk/pkt_send_co.cc new file mode 100644 index 0000000..740bc4b --- /dev/null +++ b/src/hooks/dhcp/user_chk/pkt_send_co.cc @@ -0,0 +1,552 @@ +// Copyright (C) 2013-2020 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +/// @file pkt_send_co.cc Defines the pkt4_send and pkt6_send callout functions. + +#include <config.h> +#include <asiolink/io_address.h> +#include <hooks/hooks.h> +#include <dhcp/dhcp4.h> +#include <dhcp/dhcp6.h> +#include <dhcp/option_string.h> +#include <dhcp/option_custom.h> +#include <dhcp/option6_ia.h> +#include <dhcp/option6_iaaddr.h> +#include <dhcp/option6_iaprefix.h> +#include <dhcp/docsis3_option_defs.h> +#include <dhcp/pkt4.h> +#include <dhcp/pkt6.h> +#include <user_chk.h> + +using namespace isc::dhcp; +using namespace isc::hooks; +using namespace user_chk; +using namespace std; + +// prototypes for local helper functions +void generate_output_record(const std::string& id_type_str, + const std::string& id_val_str, + const std::string& addr_str, + const bool& registered); +std::string getV6AddrStr (Pkt6Ptr response); +std::string getAddrStrIA_NA(OptionPtr options); +std::string getAddrStrIA_PD(OptionPtr options); +bool checkIAStatus(boost::shared_ptr<Option6IA>& ia_opt); + +void add4Options(Pkt4Ptr& response, const UserPtr& user); +void add4Option(Pkt4Ptr& response, uint8_t opt_code, std::string& opt_value); +void add6Options(Pkt6Ptr& response, const UserPtr& user); +void add6Option(OptionPtr& vendor, uint8_t opt_code, std::string& opt_value); +const UserPtr& getDefaultUser4(); +const UserPtr& getDefaultUser6(); + +// Functions accessed by the hooks framework use C linkage to avoid the name +// mangling that accompanies use of the C++ compiler as well as to avoid +// issues related to namespaces. +extern "C" { + +/// @brief This callout is called at the "pkt4_send" hook. +/// +/// This function generates the user check outcome and modifies options +/// to the IPv4 response packet based on whether the user is registered or not. +/// +/// It retrieves a pointer to the registered user from the callout context. +/// This value should have been set upstream. If the registered user pointer +/// is non-null (i.e the user is registered), then a registered user outcome +/// is recorded in the outcome output and the vendor properties are altered +/// based upon this user's properties. +/// +/// A null value means the user is not registered and a unregistered user +/// outcome is recorded in the outcome output and the vendor properties +/// are altered based upon the default IPv4 user in the registry (if defined). +/// +/// @param handle CalloutHandle which provides access to context. +/// +/// @return 0 upon success, non-zero otherwise. +int pkt4_send(CalloutHandle& handle) { + CalloutHandle::CalloutNextStep status = handle.getStatus(); + if (status == CalloutHandle::NEXT_STEP_DROP) { + return (0); + } + + if (status == CalloutHandle::NEXT_STEP_SKIP) { + isc_throw(isc::InvalidOperation, "packet pack already handled"); + } + + try { + Pkt4Ptr response; + handle.getArgument("response4", response); + + uint8_t packet_type = response->getType(); + if (packet_type == DHCPNAK) { + std::cout << "DHCP UserCheckHook : pkt4_send" + << "skipping packet type: " + << static_cast<int>(packet_type) << std::endl; + return (0); + } + + // Get the user id saved from the query packet. + HWAddrPtr hwaddr; + handle.getContext(query_user_id_label, hwaddr); + + // Get registered_user pointer. + UserPtr registered_user; + handle.getContext(registered_user_label, registered_user); + + // Fetch the lease address. + isc::asiolink::IOAddress addr = response->getYiaddr(); + + if (registered_user) { + // add options based on user + // then generate registered output record + std::cout << "DHCP UserCheckHook : pkt4_send registered_user is: " + << registered_user->getUserId() << std::endl; + + // Add the outcome entry to the output file. + generate_output_record(UserId::HW_ADDRESS_STR, hwaddr->toText(), + addr.toText(), true); + add4Options(response, registered_user); + } else { + // add default options based + // then generate not registered output record + std::cout << "DHCP UserCheckHook : pkt4_send no registered_user" + << std::endl; + // Add the outcome entry to the output file. + generate_output_record(UserId::HW_ADDRESS_STR, hwaddr->toText(), + addr.toText(), false); + + add4Options(response, getDefaultUser4()); + } + } catch (const std::exception& ex) { + std::cout << "DHCP UserCheckHook : pkt4_send unexpected error: " + << ex.what() << std::endl; + return (1); + } + + return (0); +} + +/// @brief This callout is called at the "pkt6_send" hook. +/// +/// This function generates the user check outcome and modifies options +/// to the IPv6 response packet based on whether the user is registered or not. +/// +/// It retrieves a pointer to the registered user from the callout context. +/// This value should have been set upstream. If the registered user pointer +/// is non-null (i.e the user is registered), then a registered user outcome +/// is recorded in the outcome output and the vendor properties are altered +/// based upon this user's properties. +/// +/// A null value means the user is not registered and a unregistered user +/// outcome is recorded in the outcome output and the vendor properties +/// are altered based upon the default IPv6 user in the registry (if defined). +/// @param handle CalloutHandle which provides access to context. +/// +/// @return 0 upon success, non-zero otherwise. +int pkt6_send(CalloutHandle& handle) { + CalloutHandle::CalloutNextStep status = handle.getStatus(); + if (status == CalloutHandle::NEXT_STEP_DROP) { + return (0); + } + + if (status == CalloutHandle::NEXT_STEP_SKIP) { + isc_throw(isc::InvalidOperation, "packet pack already handled"); + } + + try { + Pkt6Ptr response; + handle.getArgument("response6", response); + + // Fetch the lease address as a string + std::string addr_str = getV6AddrStr(response); + if (addr_str.empty()) { + // packet did not contain an address, must be failed. + std::cout << "pkt6_send: Skipping packet address is blank" + << std::endl; + return (0); + } + + // Get the user id saved from the query packet. + DuidPtr duid; + handle.getContext(query_user_id_label, duid); + + // Get registered_user pointer. + UserPtr registered_user; + handle.getContext(registered_user_label, registered_user); + + if (registered_user) { + // add options based on user + // then generate registered output record + std::cout << "DHCP UserCheckHook : pkt6_send registered_user is: " + << registered_user->getUserId() << std::endl; + // Add the outcome entry to the output file. + generate_output_record(UserId::DUID_STR, duid->toText(), + addr_str, true); + add6Options(response, registered_user); + } else { + // add default options based + // then generate not registered output record + std::cout << "DHCP UserCheckHook : pkt6_send no registered_user" + << std::endl; + // Add the outcome entry to the output file. + generate_output_record(UserId::DUID_STR, duid->toText(), + addr_str, false); + add6Options(response, getDefaultUser6()); + } + } catch (const std::exception& ex) { + std::cout << "DHCP UserCheckHook : pkt6_send unexpected error: " + << ex.what() << std::endl; + return (1); + } + + return (0); +} + +} // extern C + +/// @brief Adds IPv4 options to the response packet based on given user +/// +/// Adds or replaces IPv4 options with values from the given user, if +/// the user has corresponding properties defined. Currently it supports +/// the following options: +/// +/// - DHO_BOOT_FILE_NAME from user property "bootfile" +/// - DHO_TFTP_SERVER_NAME from user property "tftp_server" +/// +/// @param response IPv4 response packet +/// @param user User from whom properties are sourced +void add4Options(Pkt4Ptr& response, const UserPtr& user) { + // If user is null, do nothing. + if (!user) { + return; + } + + // If the user has bootfile property, update it in the response. + std::string opt_value = user->getProperty("bootfile"); + if (!opt_value.empty()) { + std::cout << "DHCP UserCheckHook : add4Options " + << "adding boot file:" << opt_value << std::endl; + + // Add boot file to packet. + add4Option(response, DHO_BOOT_FILE_NAME, opt_value); + + // Boot file also goes in file field. + response->setFile((const uint8_t*)(opt_value.c_str()), + opt_value.length()); + } + + // If the user has tftp server property, update it in the response. + opt_value = user->getProperty("tftp_server"); + if (!opt_value.empty()) { + std::cout << "DHCP UserCheckHook : add4Options " + << "adding TFTP server:" << opt_value << std::endl; + + // Add tftp server option to packet. + add4Option(response, DHO_TFTP_SERVER_NAME, opt_value); + } + // add next option here +} + +/// @brief Adds/updates are specific IPv4 string option in response packet. +/// +/// @param response IPV4 response packet to update +/// @param opt_code DHCP standard numeric code of the option +/// @param opt_value String value of the option +void add4Option(Pkt4Ptr& response, uint8_t opt_code, std::string& opt_value) { + // Remove the option if it exists. + OptionPtr opt = response->getOption(opt_code); + if (opt) { + response->delOption(opt_code); + } + + // Now add the option. + opt.reset(new OptionString(Option::V4, opt_code, opt_value)); + response->addOption(opt); +} + + +/// @brief Adds IPv6 vendor options to the response packet based on given user +/// +/// Adds or replaces IPv6 vendor options with values from the given user, if +/// the user has the corresponding properties defined. Currently it supports +/// the following options: +/// +/// - DOCSIS3_V6_CONFIG_FILE from user property "bootfile" +/// - DOCSIS3_V6_TFTP_SERVERS from user property "tftp_server" +/// +/// @param response IPv5 response packet +/// @param user User from whom properties are sourced +void add6Options(Pkt6Ptr& response, const UserPtr& user) { + if (!user) { + return; + } + + /// @todo: if packets have no vendor opt... do we need to add it + /// if its not there? If so how? + OptionPtr vendor = response->getOption(D6O_VENDOR_OPTS); + if (!vendor) { + std::cout << "DHCP UserCheckHook : add6Options " + << "response has no vendor option to update" << std::endl; + return; + } + + // If the user defines bootfile, set the option in response. + std::string opt_value = user->getProperty("bootfile"); + if (!opt_value.empty()) { + std::cout << "DHCP UserCheckHook : add6Options " + << "adding boot file:" << opt_value << std::endl; + add6Option(vendor, DOCSIS3_V6_CONFIG_FILE, opt_value); + } + + // If the user defines tftp server, set the option in response. + opt_value = user->getProperty("tftp_server"); + if (!opt_value.empty()) { + std::cout << "DHCP UserCheckHook : add6Options " + << "adding tftp server:" << opt_value << std::endl; + + add6Option(vendor, DOCSIS3_V6_TFTP_SERVERS, opt_value); + } + + // add next option here +} + +/// @brief Adds/updates a specific IPv6 string vendor option. +/// +/// @param vendor IPv6 vendor option set to update +/// @param opt_code DHCP standard numeric code of the option +/// @param opt_value String value of the option +void add6Option(OptionPtr& vendor, uint8_t opt_code, std::string& opt_value) { + vendor->delOption(opt_code); + OptionPtr option(new OptionString(Option::V6, opt_code, opt_value)); + vendor->addOption(option); +} + + +/// @brief Adds an entry to the end of the user check outcome file. +/// +/// @todo This ought to be replaced with an abstract output similar to +/// UserDataSource to allow greater flexibility. +/// +/// Each user entry is written in an ini-like format, with one name-value pair +/// per line as follows: +/// +/// id_type=<id type><br/> +/// client=<id str><br/> +/// subnet=<addr str><br/> +/// registered=<is registered> +/// +/// where: +/// <id type> text label of the id type: "HW_ADDR" or "DUID" +/// <id str> user's id formatted as either isc::dhcp::Hwaddr.toText() or +/// isc::dhcp::DUID.toText() +/// <addr str> selected subnet formatted as isc::dhcp::Subnet4::toText() or +/// isc::dhcp::Subnet6::toText() as appropriate. +/// <is registered> "yes" or "no" +/// +/// Sample IPv4 entry would like this: +/// +/// @code +/// id_type=DUID +/// client=00:01:00:01:19:ef:e6:3b:00:0c:01:02:03:04 +/// subnet=2001:db8:2::/64 +/// registered=yes +/// id_type=duid +/// @endcode +/// +/// Sample IPv4 entry would like this: +/// +/// @code +/// id_type=DUID +/// id_type=HW_ADDR +/// client=hwtype=1 00:0c:01:02:03:05 +/// subnet=152.77.5.0/24 +/// registered=no +/// @endcode +/// +/// @param id_type_str text label identify the id type +/// @param id_val_str text representation of the user id +/// @param addr_str text representation of the selected subnet +/// @param registered boolean indicating if the user is registered or not +void generate_output_record(const std::string& id_type_str, + const std::string& id_val_str, + const std::string& addr_str, + const bool& registered) +{ + user_chk_output << "id_type=" << id_type_str << std::endl + << "client=" << id_val_str << std::endl + << "addr=" << addr_str << std::endl + << "registered=" << (registered ? "yes" : "no") + << std::endl + << std::endl; // extra line in between + + // @todo Flush is here to ensure output is immediate for demo purposes. + // Performance would generally dictate not using it. + flush(user_chk_output); +} + +/// @brief Stringify the lease address or prefix IPv6 response packet +/// +/// Converts the lease value, either an address or a prefix, into a string +/// suitable for the user check outcome output. Note that this will use +/// the first address or prefix in the response for responses with more than +/// one value. +/// +/// @param response IPv6 response packet from which to extract the lease value. +/// +/// @return A string containing the lease value. +/// @throw isc::BadValue if the response contains neither an IA_NA nor IA_PD +/// option. +std::string getV6AddrStr(Pkt6Ptr response) { + OptionPtr tmp = response->getOption(D6O_IA_NA); + if (tmp) { + return(getAddrStrIA_NA(tmp)); + } + + // IA_NA not there so try IA_PD + tmp = response->getOption(D6O_IA_PD); + if (!tmp) { + isc_throw (isc::BadValue, "Response has neither IA_NA nor IA_PD"); + } + + return(getAddrStrIA_PD(tmp)); +} + +/// @brief Stringify the lease address in an D6O_IA_NA option set +/// +/// Converts the IA_NA lease address into a string suitable for the user check +/// outcome output. +/// +/// @param options pointer to the Option6IA instance from which to extract the +/// lease address. +/// +/// @return A string containing the lease address. +/// +/// @throw isc::BadValue if the lease address cannot be extracted from options. +std::string getAddrStrIA_NA(OptionPtr options) { + boost::shared_ptr<Option6IA> ia = + boost::dynamic_pointer_cast<Option6IA>(options); + + if (!ia) { + isc_throw (isc::BadValue, "D6O_IA_NA option invalid"); + } + + // If status indicates a failure return a blank string. + if (!checkIAStatus(ia)) { + return (std::string("")); + } + + options = ia->getOption(D6O_IAADDR); + if (!options) { + isc_throw (isc::BadValue, "D6O_IAADDR option missing"); + } + + boost::shared_ptr<Option6IAAddr> addr_option; + addr_option = boost::dynamic_pointer_cast<Option6IAAddr>(options); + if (!addr_option) { + isc_throw (isc::BadValue, "D6O_IAADDR Option6IAAddr missing"); + } + + isc::asiolink::IOAddress addr = addr_option->getAddress(); + return (addr.toText()); +} + +/// @brief Stringify the lease prefix in an D6O_IA_PD option set +/// +/// Converts the IA_PD lease prefix into a string suitable for the user check +/// outcome output. +/// +/// @param options pointer to the Option6IA instance from which to extract the +/// lease prefix. +/// +/// @return A string containing lease prefix +/// +/// @throw isc::BadValue if the prefix cannot be extracted from options. +std::string getAddrStrIA_PD(OptionPtr options) { + boost::shared_ptr<Option6IA> ia = + boost::dynamic_pointer_cast<Option6IA>(options); + + // Make sure we have an IA_PD option. + if (!ia) { + isc_throw (isc::BadValue, "D6O_IA_PD option invalid"); + } + + // Check the response for success status. If it isn't a success response + // there will not be a lease prefix value which is denoted by returning + // an empty string. + if (!checkIAStatus(ia)) { + return (std::string("")); + } + + // Get the prefix option the IA_PD option. + options = ia->getOption(D6O_IAPREFIX); + if (!options) { + isc_throw(isc::BadValue, "D6O_IAPREFIX option is missing"); + } + + boost::shared_ptr<Option6IAPrefix> addr_option; + addr_option = boost::dynamic_pointer_cast<Option6IAPrefix>(options); + if (!addr_option) { + isc_throw (isc::BadValue, "D6O_IA_PD addr option bad"); + } + + // Get the address and prefix length values. + isc::asiolink::IOAddress addr = addr_option->getAddress(); + uint8_t prefix_len = addr_option->getLength(); + + // Build the output string and return it. + stringstream buf; + buf << addr.toText() << "/" << static_cast<int>(prefix_len); + return (buf.str()); +} + +/// @brief Tests given IA option set for successful status. +/// +/// This function is used to determine if the given Option6IA represents +/// a successful lease operation. If it contains no status option or a status +/// option of 0 (which is defined to mean success), then the option represents +/// success and should contain a lease value (address or prefix). +/// +/// @param ia pointer to the Option6IA to test +/// +/// @return True if the option represents success, false otherwise. +bool checkIAStatus(boost::shared_ptr<Option6IA>& ia) { + OptionCustomPtr status = + boost::dynamic_pointer_cast + <OptionCustom>(ia->getOption(D6O_STATUS_CODE)); + + // If a non-zero status is present, bail. + if (status) { + int status_val = status->readInteger<uint16_t>(0); + if (status_val != 0) { + std::cout << "SKIPPING PACKET STATUS is not success:" + << status_val << std::endl; + return (false); + } + } + + return (true); +} + +/// @brief Fetches the default IPv4 user from the registry. +/// +/// The default user may be used to provide default property values. +/// +/// @return A pointer to the IPv4 user or null if not defined. +const UserPtr& getDefaultUser4() { + return (user_registry->findUser(UserId(UserId::HW_ADDRESS, + default_user4_id_str))); +} + +/// @brief Fetches the default IPv6 user from the registry. +/// +/// The default user may be used to provide default property values. +/// +/// @return A pointer to the IPv6 user or null if not defined. +const UserPtr& getDefaultUser6() { + return (user_registry->findUser(UserId(UserId::DUID, + default_user6_id_str))); +} + |