Commit 34fddf2e authored by Thomas Markwalder's avatar Thomas Markwalder
Browse files

Merge branch 'trac3207'

   Completes initial implmementation of example hooks libary, user_chk.
parents 7715d341 94be3098
......@@ -667,6 +667,7 @@ INPUT = ../src/bin/auth \
../src/bin/dhcp6 \
../src/bin/resolver \
../src/bin/sockcreator \
../src/hooks/dhcp/user_chk \
../src/lib/acl \
../src/lib/asiolink \
../src/lib/bench \
......
......@@ -46,6 +46,7 @@
* - @subpage dhcpv6Hooks
* - @subpage hooksComponentDeveloperGuide
* - @subpage hooksmgMaintenanceGuide
* - @subpage libdhcp_user_chk
*
* @section dnsMaintenanceGuide DNS Maintenance Guide
* - Authoritative DNS (todo)
......
......@@ -25,7 +25,7 @@ AM_CXXFLAGS += $(WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG)
BUILT_SOURCES =
# Ensure that the message file is included in the distribution
EXTRA_DIST =
EXTRA_DIST = libdhcp_user_chk.dox
# Get rid of generated message files on a clean
#CLEANFILES = *.gcno *.gcda user_chk_messages.h user_chk_messages.cc s-messages
......@@ -34,8 +34,11 @@ CLEANFILES = *.gcno *.gcda
lib_LTLIBRARIES = libdhcp_user_chk.la
libdhcp_user_chk_la_SOURCES =
libdhcp_user_chk_la_SOURCES += load_unload.cc
libdhcp_user_chk_la_SOURCES += pkt_receive_co.cc
libdhcp_user_chk_la_SOURCES += pkt_send_co.cc
libdhcp_user_chk_la_SOURCES += subnet_select_co.cc
libdhcp_user_chk_la_SOURCES += user.cc user.h
libdhcp_user_chk_la_SOURCES += user_chk.h
# Until logging in dynamically loaded libraries is fixed, exclude these.
#libdhcp_user_chk_la_SOURCES += user_chk_log.cc user_chk_log.h
libdhcp_user_chk_la_SOURCES += user_data_source.h
......
// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
/**
@page libdhcp_user_chk DHCP User Check Hooks Library
@section libdhcp_user_chkIntro Libdhcp_user_chk: An Example Hooks Library
## Introduction
libdhcp_user_chk is an example hooks library which customizes the DHCP query
processing provided by BIND X DHCP server modules (b10-dhcp4 and b10-dhcp6).
Specifically it allows subnet selection and DHCP response option customization
based upon a registry of DHCP clients. Note that the words "client" and "user" are used interchangeably herein. The intent of the custom behavior is three
fold:
1. To assign "new" or "unregistered" users to a restricted subnet, while "known"
or "registered" users are assigned to unrestricted subnets.
2. To allow DHCP response options or vendor option values to be customized
based upon user identity.
3. To provide a real time record of the user registration activity which can be sampled by an external consumer.
## User Registry Classes
At the heart of the library is a class hierarchy centered around the class,
user_chk::UserRegistry. This class represents a maintainable, searchable
registry of "known" users and their attributes. It provides services to load,
clear, and refresh the registry as well as to add, find, and remove users.
Each entry in the registry is an instance of the class, user_chk::User. Users
are uniquely identified by their user_chk::UserId. UserIds are comprised of
data taken from the DHCP request. IPv4 users have a type of "HW_ADDR" and
their id is the hardware address from the request. IPv6 users have a type of
"DUID" and their id is the DUID from the request.
The registry may be manually populated or loaded from a source of data which
implements the UserDataSource interface. Currently, a single implementation has
been implemented, user_chk::UserFile. UserFile supports reading the registry
from a text file in which each line is a user entry in JSON format. Each entry
contains the id type and user id along with optional attributes. Attributes are
essentially name/value pairs whose significance is left up to the calling layer.
UserFile does not enforce any specific content beyond id type and id.
(See user_file.h for more details on file content).
## Callout Processing
The library implements callouts for packet receive, subnet select, and packet
send for both IPv4 and IPv6. Regardless of the protocol type, the process
flow upon receipt of an inbound request is the same and is as follows:
-# pkt_receive callout is invoked
-# Refresh the user registry
-# Extract user id from DHCP request and store it to context
-# Look up user id in registry and store resultant user pointer to context
Note that each time a packet is received, the user registry is refreshed.
This ensures that the registry content always has the latest external
updates. The primary goal at this stage is check the registry for the
user and push the result to the context making it available to subsequent
callouts.
-# subnet_select callout is invoked
-# Retrieve the user pointer from context
-# If pointer is null (i.e. user is not registered), replace subnet
selection with restricted subnet
By convention, the last subnet in the collection of subnets available is
assumed to be the "restricted access" subnet. A more sophisticated mechanism is likely to be needed.
-# pkt_send callout is invoked:
-# Retrieve the user id and user pointer from context
-# If user is not null add the options based on user's attributes,
otherwise use default user's attributes
-# Generate user check outcome
This final step is what produces the real time record, referred to as the
"user check outcome" file.
## Using the library
Two steps are required in order to use the library:
-# The user registry file must be created and deployed
-# The BIND10 DHCP module(s) must be configured to load the library
### Creating the Registry File
Currently, the library uses a hard coded pathname for the user registry defined
in load_unload.cc:
const char* registry_fname = "/tmp/user_chk_registry.txt";
Each line in the file is a self-contained JSON snippet which must have the
following two entries:
- "type" whose value is "HW_ADDR" for IPv4 users or "DUID" for IPv6 users
- "id" whose value is either the hardware address or the DUID from the
request formatted as a string of hex digits, with or without ":" delimiters.
and may have the zero or more of the following entries:
- "bootfile" whose value is the pathname of the desired file
- "tftp_server" whose value is the hostname or IP address of the desired
server
Sample user registry file is shown below:
@code
{ "type" : "HW_ADDR", "id" : "0c:0e:0a:01:ff:04", "bootfile" : "/tmp/v4bootfile" }
{ "type" : "HW_ADDR", "id" : "0c:0e:0a:01:ff:06", "tftp_server" : "tftp.v4.example.com" }
{ "type" : "DUID", "id" : "00:01:00:01:19:ef:e6:3b:00:0c:01:02:03:04", "bootfile" : "/tmp/v6bootfile" }
{ "type" : "DUID", "id" : "00:01:00:01:19:ef:e6:3b:00:0c:01:02:03:06", "tftp_server" : "tftp.v6.example.com" }
@endcode
It is possible to specify additional attributes. They will be loaded and stored with the user's entry in the registry. This allows the library to be extended to perform additional actions based on these attributes.
Upon start up the library will attempt to load this file. If it does not exist
the library will unload.
### Configuring the DHCP Modules
it must be configured as a hook library for the
desired DHCP server modules. Note that the user_chk library is installed alongside the BIND10 libraries in "<install-dir>/lib" where <install-dir> is determined by the --prefix option of the configure script. It defaults to "/usr/local". Assuming the default value then, configuring b10-dhcp4 to load the user_chk
library could be done with the following BIND10 configuration commands:
@code
config add Dhcp4/hook_libraries
config set Dhcp4/hook_libraries[0] "/usr/local/lib/libdhcp_user_chk.so"
config commit
@endcode
To configure it for b10-dhcp6, the commands are simply as shown below:
@code
config add Dhcp6/hook_libraries
config set Dhcp6/hook_libraries[0] "/usr/local/lib/libdhcp_user_chk.so"
config commit
@endcode
## User Check Outcome
Once up and running, the library should begin adding entries to the outcome
file. Currently, the library uses a hard coded pathname for the user registry defined in load_unload.cc:
const char* user_chk_output_fname = "/tmp/user_chk_outcome.txt";
If the file cannot be created (or opened), the library will unload.
For each lease granted, the library will add the following information to the
end of the file: the id type, the user id, the lease or prefix granted, and
whether or not the user was found in the registry. This information is written
in the form of "name=value" with one value per line. (See subnet_callout.cc for details.)
A sample outcome file is shown below:
@code
id_type=HW_ADDR
client=hwtype=1 0c:0e:0a:01:ff:04
addr=175.16.1.100
registered=yes
id_type=HW_ADDR
client=hwtype=1 0c:0e:0a:01:ff:05
addr=152.0.2.10
registered=no
id_type=HW_ADDR
client=hwtype=1 0c:0e:0a:01:ff:06
addr=175.16.1.101
registered=yes
id_type=HW_ADDR
client=hwtype=1 0c:0e:0a:01:ff:04
addr=175.16.1.102
registered=yes
id_type=DUID
client=00:01:00:01:19:ef:e6:3b:00:0c:01:02:03:04
addr=2001:db8:2::1:0:0/96
registered=yes
id_type=DUID
client=00:01:00:01:19:ef:e6:3b:00:0c:01:02:03:04
addr=2001:db8:2::
registered=yes
id_type=DUID
client=00:01:00:01:19:ef:e6:3b:00:0c:01:02:03:05
addr=5005:778:2::
registered=no
id_type=DUID
client=00:01:00:01:19:ef:e6:3b:00:0c:01:02:03:06
addr=2001:db8:2::1
registered=yes
@endcode
Note the library always opens this file in append mode and does not limit its size.
*/
......@@ -12,7 +12,7 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
/// @file This file defines the load and unload hooks library functions.
/// @file load_unload.cc Defines the load and unload hooks library functions.
#include <hooks/hooks.h>
#include <user_registry.h>
......@@ -23,6 +23,7 @@
#include <errno.h>
using namespace isc::hooks;
using namespace user_chk;
/// @brief Pointer to the registry instance.
UserRegistryPtr user_registry;
......@@ -38,6 +39,24 @@ const char* registry_fname = "/tmp/user_chk_registry.txt";
/// @todo Hard-coded for now, this should be configurable.
const char* user_chk_output_fname = "/tmp/user_chk_outcome.txt";
/// @brief Text label of user id in the inbound query in callout context
const char* query_user_id_label = "query_user_id";
/// @brief Text label of registered user pointer in callout context
const char* registered_user_label = "registered_user";
/// @brief Text id used to identify the default IPv4 user in the registry
/// The format is a string containing an even number of hex digits. This
/// value is to look up the default IPv4 user in the user registry for the
/// the purpose of retrieving default values for user options.
const char* default_user4_id_str = "00000000";
/// @brief Text id used to identify the default IPv6 user in the registry
/// The format is a string containing an even number of hex digits. This
/// value is to look up the default IPv6 user in the user registry for the
/// the purpose of retrieving default values for user options.
const char *default_user6_id_str = "00000000";
// 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.
......
// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
/// @file pkt_receive.cc Defines the pkt4_receive and pkt6_receive callout functions.
#include <hooks/hooks.h>
#include <dhcp/pkt4.h>
#include <dhcp/dhcp6.h>
#include <dhcp/pkt6.h>
#include <user_chk.h>
using namespace isc::dhcp;
using namespace isc::hooks;
using namespace user_chk;
using namespace std;
// 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_receive" hook.
///
/// This function determines if the DHCP client identified by the inbound
/// DHCP query packet is in the user registry.
/// Upon entry, the registry is refreshed. Next the hardware address is
/// extracted from query and saved to the context as the "query_user_id".
/// This id is then used to search the user registry. The resultant UserPtr
/// whether the user is found or not, is saved to the callout context as
/// "registered_user". This makes the registered user, if not null, available
/// to subsequent callouts.
///
/// @param handle CalloutHandle which provides access to context.
///
/// @return 0 upon success, non-zero otherwise.
int pkt4_receive(CalloutHandle& handle) {
if (!user_registry) {
std::cout << "DHCP UserCheckHook : pkt4_receive UserRegistry is null"
<< std::endl;
return (1);
}
try {
// Refresh the registry.
user_registry->refresh();
// Get the HWAddress to use as the user identifier.
Pkt4Ptr query;
handle.getArgument("query4", query);
HWAddrPtr hwaddr = query->getHWAddr();
// Store the id we search with so it is available down the road.
handle.setContext(query_user_id_label, hwaddr);
// Look for the user in the registry.
UserPtr registered_user = user_registry->findUser(*hwaddr);
// Store user regardless. Empty user pointer means non-found. It is
// cheaper to fetch it and test it, than to use an exception throw.
handle.setContext(registered_user_label, registered_user);
std::cout << "DHCP UserCheckHook : pkt4_receive user : "
<< hwaddr->toText() << " is "
<< (registered_user ? " registered" : " not registered")
<< std::endl;
} catch (const std::exception& ex) {
std::cout << "DHCP UserCheckHook : pkt4_receive unexpected error: "
<< ex.what() << std::endl;
return (1);
}
return (0);
}
/// @brief This callout is called at the "pkt6_receive" hook.
///
/// This function determines if the DHCP client identified by the inbound
/// DHCP query packet is in the user registry.
/// Upon entry, the registry is refreshed. Next the DUID is extracted from
/// query and saved to the context as the "query_user_id". This id is then
/// used to search the user registry. The resultant UserPtr whether the user
/// is found or not, is saved to the callout context as "registered_user".
/// This makes the registered user, if not null, available to subsequent
/// callouts.
///
/// @param handle CalloutHandle which provides access to context.
///
/// @return 0 upon success, non-zero otherwise.
int pkt6_receive(CalloutHandle& handle) {
if (!user_registry) {
std::cout << "DHCP UserCheckHook : pkt6_receive UserRegistry is null"
<< std::endl;
return (1);
}
try {
// Refresh the registry.
user_registry->refresh();
// Fetch the inbound packet.
Pkt6Ptr query;
handle.getArgument("query6", query);
// Get the DUID to use as the user identifier.
OptionPtr opt_duid = query->getOption(D6O_CLIENTID);
if (!opt_duid) {
std::cout << "DHCP6 query is missing DUID" << std::endl;
return (1);
}
DuidPtr duid = DuidPtr(new DUID(opt_duid->getData()));
// Store the id we search with so it is available down the road.
handle.setContext(query_user_id_label, duid);
// Look for the user in the registry.
UserPtr registered_user = user_registry->findUser(*duid);
// Store user regardless. Empty user pointer means non-found. It is
// cheaper to fetch it and test it, than to use an exception throw.
handle.setContext(registered_user_label, registered_user);
std::cout << "DHCP UserCheckHook : pkt6_receive user : "
<< duid->toText() << " is "
<< (registered_user ? " registered" : " not registered")
<< std::endl;
} catch (const std::exception& ex) {
std::cout << "DHCP UserCheckHook : pkt6_receive unexpected error: "
<< ex.what() << std::endl;
return (1);
}
return (0);
}
} // end extern "C"
This diff is collapsed.
......@@ -12,100 +12,37 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
/// @file Defines the subnet4_select and subnet6_select callout functions.
/// @file subnet_select.cc Defines the subnet4_select and subnet6_select callout functions.
#include <hooks/hooks.h>
#include <dhcp/pkt4.h>
#include <dhcp/dhcp6.h>
#include <dhcp/pkt6.h>
#include <dhcpsrv/subnet.h>
#include <user_registry.h>
#include <fstream>
#include <string>
#include <user_chk.h>
using namespace isc::dhcp;
using namespace isc::hooks;
using namespace user_chk;
using namespace std;
extern UserRegistryPtr user_registry;
extern std::fstream user_chk_output;
extern const char* registry_fname;
extern const char* user_chk_output_fname;
// 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 Adds an entry to the end of the user check outcome file.
///
/// Each user entry is written in an ini-like format, with one name-value pair
/// per line as follows:
///
/// id_type=<id type>
/// client=<id str>
/// subnet=<subnet str>
/// 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()
/// <subnet 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 subnet_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& subnet_str,
const bool& registered)
{
user_chk_output << "id_type=" << id_type_str << std::endl
<< "client=" << id_val_str << std::endl
<< "subnet=" << subnet_str << std::endl
<< "registered=" << (registered ? "yes" : "no")
<< std::endl;
// @todo Flush is here to ensure output is immediate for demo purposes.
// Performance would generally dictate not using it.
flush(user_chk_output);
}
/// @brief This callout is called at the "subnet4_select" hook.
///
/// This function searches the UserRegistry for the client indicated by the
/// inbound IPv4 DHCP packet. If the client is found in the registry output
/// the generate outcome record and return.
/// This function alters the selected subnet based upon whether or not the
/// requesting DHCP client is a "registered user". It fetches a pointer to
/// the registered user from the callout context. This value is presumed to
/// have been set upstream. If it is non-null that it points to the client's
/// user entry in the UserRegistry. If it is null, the client is not
/// registered.
///
/// If the client is not found in the registry replace the selected subnet
/// with the restricted access subnet, then generate the outcome record and
/// return. By convention, it is assumed that last subnet in the list of
/// available subnets is the restricted access subnet.
/// If the client is registered, then replace the selected subnet with the
/// restricted access subnet. By convention, it is assumed that last subnet in
/// the list of available subnets is the restricted access subnet.
///
/// @param handle CalloutHandle which provides access to context.
///
......@@ -122,35 +59,23 @@ int subnet4_select(CalloutHandle& handle) {
const isc::dhcp::Subnet4Collection *subnets = NULL;
handle.getArgument("subnet4collection", subnets);
if (subnets->empty()) {
return 0;
return (0);
}
// Refresh the registry.
user_registry->refresh();
// Get the HWAddress as the user identifier.
Pkt4Ptr query;
handle.getArgument("query4", query);
HWAddrPtr hwaddr = query->getHWAddr();
// Get registered_user pointer.
UserPtr registered_user;
handle.getContext(registered_user_label, registered_user);
// Look for the user.
UserPtr registered_user = user_registry->findUser(*hwaddr);
if (registered_user) {
// User is in the registry, so leave the pre-selected subnet alone.
Subnet4Ptr subnet;
handle.getArgument("subnet4", subnet);
// Add the outcome entry to the output file.
generate_output_record(UserId::HW_ADDRESS_STR, hwaddr->toText(),
subnet->toText(), true);
} else {
// User is not in the registry, so assign them to the last subnet
// in the collection. By convention we are assuming this is the
// restricted subnet.
Subnet4Ptr subnet = subnets->back();
handle.setArgument("subnet4", subnet);
// Add the outcome entry to the output file.
generate_output_record(UserId::HW_ADDRESS_STR, hwaddr->toText(),
subnet->toText(), false);
}
} catch (const std::exception& ex) {
std::cout << "DHCP UserCheckHook : subnet6_select unexpected error: "
......@@ -163,14 +88,16 @@ int subnet4_select(CalloutHandle& handle) {
/// @brief This callout is called at the "subnet6_select" hook.
///
/// This function searches the UserRegistry for the client indicated by the
/// inbound IPv6 DHCP packet. If the client is found in the registry generate
/// the outcome record and return.
/// This function alters the selected subnet based upon whether or not the
/// requesting DHCP client is a "registered user". It fetches a pointer to
/// the registered user from the callout context. This value is presumed to
/// have been set upstream. If it is non-null that it points to the client's
/// user entry in the UserRegistry. If it is null, the client is not
/// registered.
///
/// If the client is not found in the registry replace the selected subnet
/// with the restricted access subnet, then generate the outcome record and
/// return. By convention, it is assumed that last subnet in the list of
/// available subnets is the restricted access subnet.
/// If the client is registered, then replace the selected subnet with the
/// restricted access subnet. By convention, it is assumed that last subnet in
/// the list of available subnets is the restricted access subnet.
///
/// @param handle CalloutHandle which provides access to context.
///
......@@ -187,43 +114,23 @@ int subnet6_select(CalloutHandle& handle) {
const isc::dhcp::Subnet6Collection *subnets = NULL;
handle.getArgument("subnet6collection", subnets);
if (subnets->empty()) {
return 0;
}
// Refresh the registry.
user_registry->refresh();