mirror of
https://gitlab.isc.org/isc-projects/kea
synced 2025-08-30 21:45:37 +00:00
Merge branch 'master' into trac2268
This commit is contained in:
48
ChangeLog
48
ChangeLog
@@ -1,3 +1,51 @@
|
||||
494. [bug] jinmei
|
||||
Fixed a problem that shutting down BIND 10 kept some of the
|
||||
processes alive. It was two-fold: when the main bind10 process
|
||||
started as a root, started b10-sockcreator with the privilege, and
|
||||
then dropped the privilege, the bind10 process cannot kill the
|
||||
sockcreator via signal any more (when it has to), but it kept
|
||||
sending the signal and didn't stop. Also, when running on Python
|
||||
3.1 (or older), the sockcreator had some additional file
|
||||
descriptor open, which prevented it from exiting even after the
|
||||
bind10 process terminated. Now the bind10 process simply gives up
|
||||
killing a subprocess if it fails due to lack of permission, and it
|
||||
makes sure the socket creator is spawned without any unnecessary
|
||||
FDs open.
|
||||
(Trac #1858, git 405d85c8a0042ba807a3a123611ff383c4081ee1)
|
||||
|
||||
493. [build] jinmei
|
||||
Fixed build failure with newer versions of clang++. These
|
||||
versions are stricter regarding "unused variable" and "unused
|
||||
(driver) arguments" warnings, and cause fatal build error
|
||||
with -Werror. The affected versions of clang++ include Apple's
|
||||
customized version 4.1 included in Xcode 4.5.1. So this fix
|
||||
will solve build errors for Mac OS X that uses newer versions of
|
||||
Xcode.
|
||||
(Trac #2340, git 55be177fc4f7537143ab6ef5a728bd44bdf9d783,
|
||||
3e2a372012e633d017a97029d13894e743199741 and commits before it
|
||||
with [2340] in the commit log)
|
||||
|
||||
492. [func] tomek
|
||||
libdhcpsrv: The DHCP Configuration Manager is now able to store
|
||||
information about IPv4 subnets and pools. It is still not possible
|
||||
to configure that information. Such capability will be implemented
|
||||
in a near future.
|
||||
(Trac #2237, git a78e560343b41f0f692c7903c938b2b2b24bf56b)
|
||||
|
||||
491. [func] tomek
|
||||
b10-dhcp6: Configuration for DHCPv6 has been implemented.
|
||||
Currently it is possible to configure IPv6 subnets and pools
|
||||
within those subnets, global and per subnet values of renew,
|
||||
rebind, preferred and valid lifetimes. Configured parameters
|
||||
are accepted, but are not used yet by the allocation engine yet.
|
||||
(Trac #2269, git 028bed9014b15facf1a29d3d4a822c9d14fc6411)
|
||||
|
||||
490. [func] tomek
|
||||
libdhcpsrv: An abstract API for lease database has been
|
||||
implemented. It offers a common interface to all concrete
|
||||
database backends.
|
||||
(Trac #2140, git df196f7609757253c4f2f918cd91012bb3af1163)
|
||||
|
||||
489. [func] muks
|
||||
The isc::dns::RRsetList class has been removed. It was now unused
|
||||
inside the BIND 10 codebase, and the interface was considered
|
||||
|
192
configure.ac
192
configure.ac
@@ -70,6 +70,106 @@ AC_TRY_LINK([],[],
|
||||
])
|
||||
LDFLAGS=$LDFLAGS_SAVED
|
||||
|
||||
# Compiler dependent settings: define some mandatory CXXFLAGS here.
|
||||
# We also use a separate variable B10_CXXFLAGS. This will (and should) be
|
||||
# used as the default value for each specific AM_CXXFLAGS:
|
||||
# AM_CXXFLAGS = $(B10_CXXFLAGS)
|
||||
# AM_CXXFLAGS += ... # add module specific flags
|
||||
# We need this so that we can disable some specific compiler warnings per
|
||||
# module basis; since AM_CXXFLAGS are placed before CXXFLAGS, and since
|
||||
# gcc's -Wno-XXX option must be specified after -Wall or -Wextra, we cannot
|
||||
# specify the default warning flags in CXXFLAGS and let specific modules
|
||||
# "override" the default.
|
||||
|
||||
# This may be used to try linker flags.
|
||||
AC_DEFUN([BIND10_CXX_TRY_FLAG], [
|
||||
AC_MSG_CHECKING([whether $CXX supports $1])
|
||||
|
||||
bind10_save_CXXFLAGS="$CXXFLAGS"
|
||||
CXXFLAGS="$CXXFLAGS $1"
|
||||
|
||||
AC_LINK_IFELSE([AC_LANG_SOURCE([int main(void){ return 0;}])],
|
||||
[bind10_cxx_flag=yes], [bind10_cxx_flag=no])
|
||||
CXXFLAGS="$bind10_save_CXXFLAGS"
|
||||
|
||||
if test "x$bind10_cxx_flag" = "xyes"; then
|
||||
ifelse([$2], , :, [$2])
|
||||
else
|
||||
ifelse([$3], , :, [$3])
|
||||
fi
|
||||
|
||||
AC_MSG_RESULT([$bind10_cxx_flag])
|
||||
])
|
||||
|
||||
# SunStudio compiler requires special compiler options for boost
|
||||
# (http://blogs.sun.com/sga/entry/boost_mini_howto)
|
||||
if test "$SUNCXX" = "yes"; then
|
||||
CXXFLAGS="$CXXFLAGS -library=stlport4 -features=tmplife -features=tmplrefstatic"
|
||||
MULTITHREADING_FLAG="-mt"
|
||||
fi
|
||||
|
||||
# Newer versions of clang++ promotes "unused driver arguments" warnings to
|
||||
# a fatal error with -Werror, causing build failure. Since we use multiple
|
||||
# compilers on multiple systems, this can easily happen due to settings for
|
||||
# non clang++ environments that could be just ignored otherwise. It can also
|
||||
# happen if clang++ is used via ccache. So, although probably suboptimal,
|
||||
# we suppress this particular warning. Note that it doesn't weaken checks
|
||||
# on the source code.
|
||||
if test "$CLANGPP" = "yes"; then
|
||||
B10_CXXFLAGS="$B10_CXXFLAGS -Qunused-arguments"
|
||||
fi
|
||||
|
||||
BIND10_CXX_TRY_FLAG([-Wno-missing-field-initializers],
|
||||
[WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG="-Wno-missing-field-initializers"])
|
||||
AC_SUBST(WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG)
|
||||
|
||||
# gcc specific settings:
|
||||
if test "X$GXX" = "Xyes"; then
|
||||
B10_CXXFLAGS="$B10_CXXFLAGS -Wall -Wextra -Wwrite-strings -Woverloaded-virtual -Wno-sign-compare"
|
||||
case "$host" in
|
||||
*-solaris*)
|
||||
MULTITHREADING_FLAG=-pthreads
|
||||
# In Solaris, IN6ADDR_ANY_INIT and IN6ADDR_LOOPBACK_INIT need -Wno-missing-braces
|
||||
B10_CXXFLAGS="$B10_CXXFLAGS -Wno-missing-braces"
|
||||
;;
|
||||
*)
|
||||
MULTITHREADING_FLAG=-pthread
|
||||
;;
|
||||
esac
|
||||
|
||||
# Don't use -Werror if configured not to
|
||||
AC_ARG_WITH(werror,
|
||||
AC_HELP_STRING([--with-werror], [Compile using -Werror (default=yes)]),
|
||||
[
|
||||
case "${withval}" in
|
||||
yes) with_werror=1 ;;
|
||||
no) with_werror=0 ;;
|
||||
*) AC_MSG_ERROR(bad value ${withval} for --with-werror) ;;
|
||||
esac],
|
||||
[with_werror=1])
|
||||
|
||||
werror_ok=0
|
||||
|
||||
# Certain versions of gcc (g++) have a bug that incorrectly warns about
|
||||
# the use of anonymous name spaces even if they're closed in a single
|
||||
# translation unit. For these versions we have to disable -Werror.
|
||||
if test $with_werror = 1; then
|
||||
CXXFLAGS_SAVED="$CXXFLAGS"
|
||||
CXXFLAGS="$CXXFLAGS $B10_CXXFLAGS -Werror"
|
||||
AC_MSG_CHECKING(for in-TU anonymous namespace breakage)
|
||||
AC_TRY_COMPILE([namespace { class Foo {}; }
|
||||
namespace isc {class Bar {Foo foo_;};} ],,
|
||||
[AC_MSG_RESULT(no)
|
||||
werror_ok=1
|
||||
B10_CXXFLAGS="$B10_CXXFLAGS -Werror"],
|
||||
[AC_MSG_RESULT(yes)])
|
||||
CXXFLAGS="$CXXFLAGS_SAVED"
|
||||
fi
|
||||
|
||||
fi dnl GXX = yes
|
||||
|
||||
AM_CONDITIONAL(GCC_WERROR_OK, test $werror_ok = 1)
|
||||
|
||||
# allow building programs with static link. we need to make it selective
|
||||
# because loadable modules cannot be statically linked.
|
||||
AC_ARG_ENABLE([static-link],
|
||||
@@ -256,95 +356,11 @@ fi
|
||||
|
||||
# TODO: check for _sqlite3.py module
|
||||
|
||||
# Compiler dependent settings: define some mandatory CXXFLAGS here.
|
||||
# We also use a separate variable B10_CXXFLAGS. This will (and should) be
|
||||
# used as the default value for each specific AM_CXXFLAGS:
|
||||
# AM_CXXFLAGS = $(B10_CXXFLAGS)
|
||||
# AM_CXXFLAGS += ... # add module specific flags
|
||||
# We need this so that we can disable some specific compiler warnings per
|
||||
# module basis; since AM_CXXFLAGS are placed before CXXFLAGS, and since
|
||||
# gcc's -Wno-XXX option must be specified after -Wall or -Wextra, we cannot
|
||||
# specify the default warning flags in CXXFLAGS and let specific modules
|
||||
# "override" the default.
|
||||
|
||||
# This may be used to try linker flags.
|
||||
AC_DEFUN([BIND10_CXX_TRY_FLAG], [
|
||||
AC_MSG_CHECKING([whether $CXX supports $1])
|
||||
|
||||
bind10_save_CXXFLAGS="$CXXFLAGS"
|
||||
CXXFLAGS="$CXXFLAGS $1"
|
||||
|
||||
AC_LINK_IFELSE([AC_LANG_SOURCE([int main(void){ return 0;}])],
|
||||
[bind10_cxx_flag=yes], [bind10_cxx_flag=no])
|
||||
CXXFLAGS="$bind10_save_CXXFLAGS"
|
||||
|
||||
if test "x$bind10_cxx_flag" = "xyes"; then
|
||||
ifelse([$2], , :, [$2])
|
||||
else
|
||||
ifelse([$3], , :, [$3])
|
||||
fi
|
||||
|
||||
AC_MSG_RESULT([$bind10_cxx_flag])
|
||||
])
|
||||
|
||||
# SunStudio compiler requires special compiler options for boost
|
||||
# (http://blogs.sun.com/sga/entry/boost_mini_howto)
|
||||
if test "$SUNCXX" = "yes"; then
|
||||
CXXFLAGS="$CXXFLAGS -library=stlport4 -features=tmplife -features=tmplrefstatic"
|
||||
MULTITHREADING_FLAG="-mt"
|
||||
fi
|
||||
|
||||
BIND10_CXX_TRY_FLAG([-Wno-missing-field-initializers],
|
||||
[WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG="-Wno-missing-field-initializers"])
|
||||
AC_SUBST(WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG)
|
||||
|
||||
# gcc specific settings:
|
||||
if test "X$GXX" = "Xyes"; then
|
||||
B10_CXXFLAGS="-Wall -Wextra -Wwrite-strings -Woverloaded-virtual -Wno-sign-compare"
|
||||
case "$host" in
|
||||
*-solaris*)
|
||||
MULTITHREADING_FLAG=-pthreads
|
||||
# In Solaris, IN6ADDR_ANY_INIT and IN6ADDR_LOOPBACK_INIT need -Wno-missing-braces
|
||||
B10_CXXFLAGS="$B10_CXXFLAGS -Wno-missing-braces"
|
||||
;;
|
||||
*)
|
||||
MULTITHREADING_FLAG=-pthread
|
||||
;;
|
||||
esac
|
||||
|
||||
# Don't use -Werror if configured not to
|
||||
AC_ARG_WITH(werror,
|
||||
AC_HELP_STRING([--with-werror], [Compile using -Werror (default=yes)]),
|
||||
[
|
||||
case "${withval}" in
|
||||
yes) with_werror=1 ;;
|
||||
no) with_werror=0 ;;
|
||||
*) AC_MSG_ERROR(bad value ${withval} for --with-werror) ;;
|
||||
esac],
|
||||
[with_werror=1])
|
||||
|
||||
werror_ok=0
|
||||
|
||||
# Certain versions of gcc (g++) have a bug that incorrectly warns about
|
||||
# the use of anonymous name spaces even if they're closed in a single
|
||||
# translation unit. For these versions we have to disable -Werror.
|
||||
if test $with_werror = 1; then
|
||||
CXXFLAGS_SAVED="$CXXFLAGS"
|
||||
CXXFLAGS="$CXXFLAGS $B10_CXXFLAGS -Werror"
|
||||
AC_MSG_CHECKING(for in-TU anonymous namespace breakage)
|
||||
AC_TRY_COMPILE([namespace { class Foo {}; }
|
||||
namespace isc {class Bar {Foo foo_;};} ],,
|
||||
[AC_MSG_RESULT(no)
|
||||
werror_ok=1
|
||||
B10_CXXFLAGS="$B10_CXXFLAGS -Werror"],
|
||||
[AC_MSG_RESULT(yes)])
|
||||
CXXFLAGS="$CXXFLAGS_SAVED"
|
||||
fi
|
||||
|
||||
# (g++ only check)
|
||||
# Python 3.2 has an unused parameter in one of its headers. This
|
||||
# has been reported, but not fixed as of yet, so we check if we need
|
||||
# to set -Wno-unused-parameter.
|
||||
if test $werror_ok = 1; then
|
||||
if test "X$GXX" = "Xyes" -a $werror_ok = 1; then
|
||||
CPPFLAGS_SAVED="$CPPFLAGS"
|
||||
CPPFLAGS=${PYTHON_INCLUDES}
|
||||
CXXFLAGS_SAVED="$CXXFLAGS"
|
||||
@@ -370,10 +386,6 @@ if test $werror_ok = 1; then
|
||||
CPPFLAGS="$CPPFLAGS_SAVED"
|
||||
fi
|
||||
|
||||
fi dnl GXX = yes
|
||||
|
||||
AM_CONDITIONAL(GCC_WERROR_OK, test $werror_ok = 1)
|
||||
|
||||
# produce PIC unless we disable shared libraries. need this for python bindings.
|
||||
if test $enable_shared != "no" -a "X$GXX" = "Xyes"; then
|
||||
B10_CXXFLAGS="$B10_CXXFLAGS -fPIC"
|
||||
|
@@ -57,29 +57,6 @@
|
||||
* that does not support msgq. That is useful for embedded environments.
|
||||
* It may also be useful in validation.
|
||||
*
|
||||
* @page dhcpv6 DHCPv6 Server Component
|
||||
*
|
||||
* BIND10 offers DHCPv6 server implementation. It is implemented as
|
||||
* b10-dhcp6 component. Its primary code is located in
|
||||
* isc::dhcp::Dhcpv6Srv class. It uses \ref libdhcp extensively,
|
||||
* especially lib::dhcp::Pkt6, isc::dhcp::Option and
|
||||
* isc::dhcp::IfaceMgr classes. Currently this code offers skeleton
|
||||
* functionality, i.e. it is able to receive and process incoming
|
||||
* requests and trasmit responses. However, it does not have database
|
||||
* management, so it returns only one, hardcoded lease to whoever asks
|
||||
* for it.
|
||||
*
|
||||
* DHCPv6 server component does not support relayed traffic yet, as
|
||||
* support for relay decapsulation is not implemented yet.
|
||||
*
|
||||
* DHCPv6 server component does not use BIND10 logging yet.
|
||||
*
|
||||
* @section dhcpv6Session BIND10 message queue integration
|
||||
*
|
||||
* DHCPv4 server component is now integrated with BIND10 message queue.
|
||||
* It follows the same principle as DHCPv4. See \ref dhcpv4Session for
|
||||
* details.
|
||||
*
|
||||
* @page libdhcp libdhcp++
|
||||
*
|
||||
* @section libdhcpIntro Libdhcp++ Library Introduction
|
||||
|
@@ -15,12 +15,17 @@
|
||||
* <a href="http://bind10.isc.org/">BIND10 webpage (http://bind10.isc.org)</a>
|
||||
*
|
||||
* @section DNS
|
||||
* - Authoritative DNS (todo)
|
||||
* - Recursive resolver (todo)
|
||||
* - @subpage DataScrubbing
|
||||
*
|
||||
* @section DHCP
|
||||
* - @subpage dhcpv4
|
||||
* - @subpage dhcpv4Session
|
||||
* - @subpage dhcpv6
|
||||
* - @subpage dhcpv6-session
|
||||
* - @subpage dhcpv6-config-parser
|
||||
* - @subpage dhcpv6-config-inherit
|
||||
* - @subpage libdhcp
|
||||
* - @subpage libdhcpIntro
|
||||
* - @subpage libdhcpIfaceMgr
|
||||
|
@@ -2751,13 +2751,13 @@ then change those defaults with config set Resolver/forward_addresses[0]/address
|
||||
<title>DHCPv4 Server Configuration</title>
|
||||
<para>
|
||||
The DHCPv4 server does not have a lease database implemented yet
|
||||
nor any support for configuration, so every time the same set
|
||||
nor any support for configuration, so the same set
|
||||
of configuration options (including the same fixed address)
|
||||
will be assigned every time.
|
||||
</para>
|
||||
<para>
|
||||
At this stage of development, the only way to alter the server
|
||||
configuration is to tweak its source code. To do so, please
|
||||
configuration is to modify its source code. To do so, please
|
||||
edit src/bin/dhcp4/dhcp4_srv.cc file and modify following
|
||||
parameters and recompile:
|
||||
<screen>
|
||||
@@ -2944,16 +2944,95 @@ const std::string HARDCODED_SERVER_ID = "192.0.2.1";</screen>
|
||||
<section id="dhcp6-config">
|
||||
<title>DHCPv6 Server Configuration</title>
|
||||
<para>
|
||||
The DHCPv6 server does not have lease database implemented yet
|
||||
or any support for configuration, so every time the same set
|
||||
of configuration options (including the same fixed address)
|
||||
will be assigned every time.
|
||||
Once the server is started, it can be configured. To view the
|
||||
current configuration, use the following command in <command>bindctl</command>:
|
||||
<screen>
|
||||
> <userinput>config show Dhcp6</userinput></screen>
|
||||
When starting Dhcp6 daemon for the first time, the default configuration
|
||||
will be available. It will look similar to this:
|
||||
<screen>
|
||||
> <userinput>config show Dhcp6</userinput>
|
||||
Dhcp6/interface "eth0" string (default)
|
||||
Dhcp6/renew-timer 1000 integer (default)
|
||||
Dhcp6/rebind-timer 2000 integer (default)
|
||||
Dhcp6/preferred-lifetime 3000 integer (default)
|
||||
Dhcp6/valid-lifetime 4000 integer (default)
|
||||
Dhcp6/subnet6 [] list (default)</screen>
|
||||
</para>
|
||||
|
||||
<para>
|
||||
To change one of the parameters, simply follow
|
||||
the usual <command>bindctl</command> procedure. For example, to make the
|
||||
leases longer, change their valid-lifetime parameter:
|
||||
<screen>
|
||||
> <userinput>config set Dhcp6/valid-lifetime 7200</userinput>
|
||||
> <userinput>config commit</userinput></screen>
|
||||
Please note that most Dhcp6 parameters are of global scope
|
||||
and apply to all defined subnets, unless they are overridden on a
|
||||
per-subnet basis.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
The essential role of DHCPv6 server is address assignment. The server
|
||||
has to be configured with at least one subnet and one pool of dynamic
|
||||
addresses to be managed. For example, assume that the server
|
||||
is connected to a network segment that uses the 2001:db8:1::/64
|
||||
prefix. The Administrator of that network has decided that addresses from range
|
||||
2001:db8:1::1 to 2001:db8:1::ffff are going to be managed by the Dhcp6
|
||||
server. Such a configuration can be achieved in the following way:
|
||||
<screen>
|
||||
> <userinput>config add Dhcp6/subnet6</userinput>
|
||||
> <userinput>config set Dhcp6/subnet6[0]/subnet "2001:db8:1::/64"</userinput>
|
||||
> <userinput>config set Dhcp6/subnet6[0]/pool [ "2001:db8:1::0 - 2001:db8:1::ffff" ]</userinput>
|
||||
> <userinput>config commit</userinput></screen>
|
||||
Note that subnet is defined as a simple string, but the pool parameter
|
||||
is actually a list of pools: for this reason, the pool definition is
|
||||
enclosed in square brackets, even though only one range of addresses
|
||||
is specified.</para>
|
||||
<para>It is possible to define more than one pool in a
|
||||
subnet: continuing the previous example, further assume that
|
||||
2001:db8:1:0:5::/80 should be also be managed by the server. It could be written as
|
||||
2001:db8:1:0:5:: to 2001:db8:1::5:ffff:ffff:ffff, but typing so many 'f's
|
||||
is cumbersome. It can be expressed more simply as 2001:db8:1:0:5::/80. Both
|
||||
formats are supported by Dhcp6 and can be mixed in the pool list.
|
||||
For example, one could define the following pools:
|
||||
<screen>
|
||||
> <userinput>config set Dhcp6/subnet6[0]/pool [ "2001:db8:1::1 - 2001:db8:1::ffff", "2001:db8:1:0:5::/80" ]</userinput>
|
||||
> <userinput>config commit</userinput></screen>
|
||||
The number of pools is not limited, but for performance reasons it is recommended to
|
||||
use as few as possible.
|
||||
</para>
|
||||
<para>
|
||||
At this stage of development, the only way to alter server
|
||||
configuration is to tweak its source code. To do so, please
|
||||
edit src/bin/dhcp6/dhcp6_srv.cc file, modify the following
|
||||
parameters and recompile:
|
||||
The server may be configured to serve more than one subnet. To add a second subnet,
|
||||
use a command similar to the following:
|
||||
<screen>
|
||||
> <userinput>config add Dhcp6/subnet6</userinput>
|
||||
> <userinput>config set Dhcp6/subnet6[1]/subnet "2001:db8:beef::/48"</userinput>
|
||||
> <userinput>config set Dhcp6/subnet6[1]/pool [ "2001:db8:beef::/48" ]</userinput>
|
||||
> <userinput>config commit</userinput></screen>
|
||||
Arrays are counted from 0. subnet[0] refers to the subnet defined in the
|
||||
previous example. The <command>config add Dhcp6/subnet6</command> adds
|
||||
another (second) subnet. It can be referred to as
|
||||
<command>Dhcp6/subnet6[1]</command>. In this example, we allow server to
|
||||
dynamically assign all addresses available in the whole subnet. Although
|
||||
very wasteful, it is certainly a valid configuration to dedicate the
|
||||
whole /48 subnet for that purpose.
|
||||
</para>
|
||||
<para>
|
||||
When configuring a DHCPv6 server using prefix/length notation, please pay
|
||||
attention to the boundary values. When specifying that the server should use
|
||||
a given pool, it will be able to allocate also first (typically network
|
||||
address) address from that pool. For example for pool 2001:db8::/64 the
|
||||
2001:db8:: address may be assigned as well. If you want to avoid this,
|
||||
please use min-max notation.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Note: Although configuration is now accepted, it is not internally used
|
||||
by they server yet. At this stage of development, the only way to alter
|
||||
server configuration is to modify its source code. To do so, please edit
|
||||
src/bin/dhcp6/dhcp6_srv.cc file, modify the following parameters and
|
||||
recompile:
|
||||
<screen>
|
||||
const std::string HARDCODED_LEASE = "2001:db8:1::1234:abcd";
|
||||
const uint32_t HARDCODED_T1 = 1500; // in seconds
|
||||
|
@@ -93,7 +93,7 @@ public:
|
||||
/// that corresponds to this derived class and prepares a new value to
|
||||
/// apply to the server.
|
||||
/// In the above example, the derived class for the identifier "param1"
|
||||
/// would be passed an data \c Element storing an integer whose value
|
||||
/// would be passed a data \c Element storing an integer whose value
|
||||
/// is 10, and would record that value internally;
|
||||
/// the derived class for the identifier "param2" would be passed a
|
||||
/// map element and (after parsing) convert it into some internal
|
||||
|
@@ -167,6 +167,30 @@ so BIND 10 will now shut down. The specific error is printed.
|
||||
% BIND10_SEND_SIGKILL sending SIGKILL to %1 (PID %2)
|
||||
The boss module is sending a SIGKILL signal to the given process.
|
||||
|
||||
% BIND10_SEND_SIGNAL_FAIL sending %1 to %2 (PID %3) failed: %4
|
||||
The boss module sent a single (either SIGTERM or SIGKILL) to a process,
|
||||
but it failed due to some system level error. There are two major cases:
|
||||
the target process has already terminated but the boss module had sent
|
||||
the signal before it noticed the termination. In this case an error
|
||||
message should indicate something like "no such process". This can be
|
||||
safely ignored. The other case is that the boss module doesn't have
|
||||
the privilege to send a signal to the process. It can typically
|
||||
happen when the boss module started as a privileged process, spawned a
|
||||
subprocess, and then dropped the privilege. It includes the case for
|
||||
the socket creator when the boss process runs with the -u command line
|
||||
option. In this case, the boss module simply gives up to terminate
|
||||
the process explicitly because it's unlikely to succeed by keeping
|
||||
sending the signal. Although the socket creator is implemented so
|
||||
that it will terminate automatically when the boss process exits
|
||||
(and that should be the case for any other future process running with
|
||||
a higher privilege), but it's recommended to check if there's any
|
||||
remaining BIND 10 process if this message is logged. For all other
|
||||
cases, the boss module will keep sending the signal until it confirms
|
||||
all child processes terminate. Although unlikely, this could prevent
|
||||
the boss module from exiting, just keeping sending the signals. So,
|
||||
again, it's advisable to check if it really terminates when this
|
||||
message is logged.
|
||||
|
||||
% BIND10_SEND_SIGTERM sending SIGTERM to %1 (PID %2)
|
||||
The boss module is sending a SIGTERM signal to the given process.
|
||||
|
||||
@@ -274,13 +298,6 @@ During the startup process, a number of messages are exchanged between the
|
||||
Boss process and the processes it starts. This error is output when a
|
||||
message received by the Boss process is not recognised.
|
||||
|
||||
% BIND10_START_AS_NON_ROOT_AUTH starting b10-auth as a user, not root. This might fail.
|
||||
The authoritative server is being started or restarted without root privileges.
|
||||
If the module needs these privileges, it may have problems starting.
|
||||
Note that this issue should be resolved by the pending 'socket-creator'
|
||||
process; once that has been implemented, modules should not need root
|
||||
privileges anymore. See tickets #800 and #801 for more information.
|
||||
|
||||
% BIND10_START_AS_NON_ROOT_RESOLVER starting b10-resolver as a user, not root. This might fail.
|
||||
The resolver is being started or restarted without root privileges.
|
||||
If the module needs these privileges, it may have problems starting.
|
||||
|
@@ -546,8 +546,6 @@ class BoB:
|
||||
"""
|
||||
Start the Authoritative server
|
||||
"""
|
||||
if self.uid is not None and self.__started:
|
||||
logger.warn(BIND10_START_AS_NON_ROOT_AUTH)
|
||||
authargs = ['b10-auth']
|
||||
if self.verbose:
|
||||
authargs += ['-v']
|
||||
@@ -693,32 +691,42 @@ class BoB:
|
||||
# from doing so
|
||||
if not self.nokill:
|
||||
# next try sending a SIGTERM
|
||||
components_to_stop = list(self.components.values())
|
||||
for component in components_to_stop:
|
||||
logger.info(BIND10_SEND_SIGTERM, component.name(), component.pid())
|
||||
try:
|
||||
component.kill()
|
||||
except OSError:
|
||||
# ignore these (usually ESRCH because the child
|
||||
# finally exited)
|
||||
pass
|
||||
# finally, send SIGKILL (unmaskable termination) until everybody dies
|
||||
self.__kill_children(False)
|
||||
# finally, send SIGKILL (unmaskable termination) until everybody
|
||||
# dies
|
||||
while self.components:
|
||||
# XXX: some delay probably useful... how much is uncertain
|
||||
time.sleep(0.1)
|
||||
self.reap_children()
|
||||
components_to_stop = list(self.components.values())
|
||||
for component in components_to_stop:
|
||||
logger.info(BIND10_SEND_SIGKILL, component.name(),
|
||||
component.pid())
|
||||
try:
|
||||
component.kill(True)
|
||||
except OSError:
|
||||
# ignore these (usually ESRCH because the child
|
||||
# finally exited)
|
||||
pass
|
||||
self.__kill_children(True)
|
||||
logger.info(BIND10_SHUTDOWN_COMPLETE)
|
||||
|
||||
def __kill_children(self, forceful):
|
||||
'''Terminate remaining subprocesses by sending a signal.
|
||||
|
||||
The forceful paramter will be passed Component.kill().
|
||||
This is a dedicated subroutine of shutdown(), just to unify two
|
||||
similar cases.
|
||||
|
||||
'''
|
||||
logmsg = BIND10_SEND_SIGKILL if forceful else BIND10_SEND_SIGTERM
|
||||
# We need to make a copy of values as the components may be modified
|
||||
# in the loop.
|
||||
for component in list(self.components.values()):
|
||||
logger.info(logmsg, component.name(), component.pid())
|
||||
try:
|
||||
component.kill(forceful)
|
||||
except OSError as ex:
|
||||
# If kill() failed due to EPERM, it doesn't make sense to
|
||||
# keep trying, so we just log the fact and forget that
|
||||
# component. Ignore other OSErrors (usually ESRCH because
|
||||
# the child finally exited)
|
||||
signame = "SIGKILL" if forceful else "SIGTERM"
|
||||
logger.info(BIND10_SEND_SIGNAL_FAIL, signame,
|
||||
component.name(), component.pid(), ex)
|
||||
if ex.errno == errno.EPERM:
|
||||
del self.components[component.pid()]
|
||||
|
||||
def _get_process_exit_status(self):
|
||||
return os.waitpid(-1, os.WNOHANG)
|
||||
|
||||
|
@@ -1181,7 +1181,7 @@ class TestBossComponents(unittest.TestCase):
|
||||
# We check somewhere else that the shutdown is actually called
|
||||
# from there (the test_kills).
|
||||
|
||||
def __real_test_kill(self, nokill = False):
|
||||
def __real_test_kill(self, nokill=False, ex_on_kill=None):
|
||||
"""
|
||||
Helper function that does the actual kill functionality testing.
|
||||
"""
|
||||
@@ -1195,8 +1195,23 @@ class TestBossComponents(unittest.TestCase):
|
||||
(anyway it is not told so). It does not die if it is killed
|
||||
the first time. It dies only when killed forcefully.
|
||||
"""
|
||||
def __init__(self):
|
||||
# number of kill() calls, preventing infinite loop.
|
||||
self.__call_count = 0
|
||||
|
||||
def kill(self, forceful=False):
|
||||
self.__call_count += 1
|
||||
if self.__call_count > 2:
|
||||
raise Exception('Too many calls to ImmortalComponent.kill')
|
||||
|
||||
killed.append(forceful)
|
||||
if ex_on_kill is not None:
|
||||
# If exception is given by the test, raise it here.
|
||||
# In the case of ESRCH, the process should have gone
|
||||
# somehow, so we clear the components.
|
||||
if ex_on_kill.errno == errno.ESRCH:
|
||||
bob.components = {}
|
||||
raise ex_on_kill
|
||||
if forceful:
|
||||
bob.components = {}
|
||||
def pid(self):
|
||||
@@ -1224,7 +1239,10 @@ class TestBossComponents(unittest.TestCase):
|
||||
if nokill:
|
||||
self.assertEqual([], killed)
|
||||
else:
|
||||
self.assertEqual([False, True], killed)
|
||||
if ex_on_kill is not None:
|
||||
self.assertEqual([False], killed)
|
||||
else:
|
||||
self.assertEqual([False, True], killed)
|
||||
|
||||
self.assertTrue(self.__called)
|
||||
|
||||
@@ -1236,6 +1254,20 @@ class TestBossComponents(unittest.TestCase):
|
||||
"""
|
||||
self.__real_test_kill()
|
||||
|
||||
def test_kill_fail(self):
|
||||
"""Test cases where kill() results in an exception due to OS error.
|
||||
|
||||
The behavior should be different for EPERM, so we test two cases.
|
||||
|
||||
"""
|
||||
|
||||
ex = OSError()
|
||||
ex.errno, ex.strerror = errno.ESRCH, 'No such process'
|
||||
self.__real_test_kill(ex_on_kill=ex)
|
||||
|
||||
ex.errno, ex.strerror = errno.EPERM, 'Operation not permitted'
|
||||
self.__real_test_kill(ex_on_kill=ex)
|
||||
|
||||
def test_nokill(self):
|
||||
"""
|
||||
Test that the boss *doesn't* kill components which don't want to
|
||||
|
@@ -46,6 +46,7 @@ pkglibexec_PROGRAMS = b10-dhcp6
|
||||
|
||||
b10_dhcp6_SOURCES = main.cc
|
||||
b10_dhcp6_SOURCES += ctrl_dhcp6_srv.cc ctrl_dhcp6_srv.h
|
||||
b10_dhcp6_SOURCES += config_parser.cc config_parser.h
|
||||
b10_dhcp6_SOURCES += dhcp6_log.cc dhcp6_log.h
|
||||
b10_dhcp6_SOURCES += dhcp6_srv.cc dhcp6_srv.h
|
||||
|
||||
@@ -62,6 +63,7 @@ b10_dhcp6_LDADD = $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
|
||||
b10_dhcp6_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
|
||||
b10_dhcp6_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
|
||||
b10_dhcp6_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
|
||||
b10_dhcp6_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcpsrv.la
|
||||
b10_dhcp6_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
|
||||
b10_dhcp6_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
|
||||
|
||||
|
797
src/bin/dhcp6/config_parser.cc
Normal file
797
src/bin/dhcp6/config_parser.cc
Normal file
@@ -0,0 +1,797 @@
|
||||
// Copyright (C) 2010 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.
|
||||
|
||||
#include <stdint.h>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <boost/foreach.hpp>
|
||||
#include <boost/shared_ptr.hpp>
|
||||
#include <boost/scoped_ptr.hpp>
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <asiolink/io_address.h>
|
||||
#include <cc/data.h>
|
||||
#include <config/ccsession.h>
|
||||
#include <log/logger_support.h>
|
||||
#include <dhcp/triplet.h>
|
||||
#include <dhcp/pool.h>
|
||||
#include <dhcp/subnet.h>
|
||||
#include <dhcp/cfgmgr.h>
|
||||
#include <dhcp6/config_parser.h>
|
||||
#include <dhcp6/dhcp6_log.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace isc::data;
|
||||
using namespace isc::asiolink;
|
||||
|
||||
namespace isc {
|
||||
namespace dhcp {
|
||||
|
||||
/// @brief auxiliary type used for storing element name and its parser
|
||||
typedef pair<string, ConstElementPtr> ConfigPair;
|
||||
|
||||
/// @brief a factory method that will create a parser for a given element name
|
||||
typedef DhcpConfigParser* ParserFactory(const std::string& config_id);
|
||||
|
||||
/// @brief a collection of factories that creates parsers for specified element names
|
||||
typedef std::map<std::string, ParserFactory*> FactoryMap;
|
||||
|
||||
/// @brief a collection of elements that store uint32 values (e.g. renew-timer = 900)
|
||||
typedef std::map<string, uint32_t> Uint32Storage;
|
||||
|
||||
/// @brief a collection of elements that store string values
|
||||
typedef std::map<string, string> StringStorage;
|
||||
|
||||
/// @brief a collection of pools
|
||||
///
|
||||
/// That type is used as intermediate storage, when pools are parsed, but there is
|
||||
/// no subnet object created yet to store them.
|
||||
typedef std::vector<Pool6Ptr> PoolStorage;
|
||||
|
||||
/// @brief Global uint32 parameters that will be used as defaults.
|
||||
Uint32Storage uint32_defaults;
|
||||
|
||||
/// @brief global string parameters that will be used as defaults.
|
||||
StringStorage string_defaults;
|
||||
|
||||
/// @brief a dummy configuration parser
|
||||
///
|
||||
/// It is a debugging parser. It does not configure anything,
|
||||
/// will accept any configuration and will just print it out
|
||||
/// on commit. Useful for debugging existing configurations and
|
||||
/// adding new ones.
|
||||
class DebugParser : public DhcpConfigParser {
|
||||
public:
|
||||
|
||||
/// @brief Constructor
|
||||
///
|
||||
/// See \ref DhcpConfigParser class for details.
|
||||
///
|
||||
/// @param param_name name of the parsed parameter
|
||||
DebugParser(const std::string& param_name)
|
||||
:param_name_(param_name) {
|
||||
}
|
||||
|
||||
/// @brief builds parameter value
|
||||
///
|
||||
/// See \ref DhcpConfigParser class for details.
|
||||
///
|
||||
/// @param new_config pointer to the new configuration
|
||||
virtual void build(ConstElementPtr new_config) {
|
||||
std::cout << "Build for token: [" << param_name_ << "] = ["
|
||||
<< value_->str() << "]" << std::endl;
|
||||
value_ = new_config;
|
||||
}
|
||||
|
||||
/// @brief pretends to apply the configuration
|
||||
///
|
||||
/// This is a method required by base class. It pretends to apply the
|
||||
/// configuration, but in fact it only prints the parameter out.
|
||||
///
|
||||
/// See \ref DhcpConfigParser class for details.
|
||||
virtual void commit() {
|
||||
// Debug message. The whole DebugParser class is used only for parser
|
||||
// debugging, and is not used in production code. It is very convenient
|
||||
// to keep it around. Please do not turn this cout into logger calls.
|
||||
std::cout << "Commit for token: [" << param_name_ << "] = ["
|
||||
<< value_->str() << "]" << std::endl;
|
||||
}
|
||||
|
||||
/// @brief factory that constructs DebugParser objects
|
||||
///
|
||||
/// @param param_name name of the parameter to be parsed
|
||||
static DhcpConfigParser* Factory(const std::string& param_name) {
|
||||
return (new DebugParser(param_name));
|
||||
}
|
||||
|
||||
protected:
|
||||
/// name of the parsed parameter
|
||||
std::string param_name_;
|
||||
|
||||
/// pointer to the actual value of the parameter
|
||||
ConstElementPtr value_;
|
||||
};
|
||||
|
||||
/// @brief Configuration parser for uint32 parameters
|
||||
///
|
||||
/// This class is a generic parser that is able to handle any uint32 integer
|
||||
/// type. By default it stores the value in external global container
|
||||
/// (uint32_defaults). If used in smaller scopes (e.g. to parse parameters
|
||||
/// in subnet config), it can be pointed to a different storage, using
|
||||
/// setStorage() method. This class follows the parser interface, laid out
|
||||
/// in its base class, \ref DhcpConfigParser.
|
||||
///
|
||||
/// For overview of usability of this generic purpose parser, see
|
||||
/// \ref dhcpv6-config-inherit page.
|
||||
class Uint32Parser : public DhcpConfigParser {
|
||||
public:
|
||||
|
||||
/// @brief constructor for Uint32Parser
|
||||
/// @param param_name name of the configuration parameter being parsed
|
||||
Uint32Parser(const std::string& param_name)
|
||||
:storage_(&uint32_defaults), param_name_(param_name) {
|
||||
}
|
||||
|
||||
/// @brief builds parameter value
|
||||
///
|
||||
/// Parses configuration entry and stores it in a storage. See
|
||||
/// \ref setStorage() for details.
|
||||
///
|
||||
/// @param value pointer to the content of parsed values
|
||||
virtual void build(ConstElementPtr value) {
|
||||
try {
|
||||
value_ = boost::lexical_cast<uint32_t>(value->str());
|
||||
} catch (const boost::bad_lexical_cast &) {
|
||||
isc_throw(BadValue, "Failed to parse value " << value->str()
|
||||
<< " as unsigned 32-bit integer.");
|
||||
}
|
||||
storage_->insert(pair<string, uint32_t>(param_name_, value_));
|
||||
}
|
||||
|
||||
/// @brief does nothing
|
||||
///
|
||||
/// This method is required for all parser. The value itself
|
||||
/// is not commited anywhere. Higher level parsers are expected to
|
||||
/// use values stored in the storage, e.g. renew-timer for a given
|
||||
/// subnet is stored in subnet-specific storage. It is not commited
|
||||
/// here, but is rather used by \ref Subnet6Parser when constructing
|
||||
/// the subnet.
|
||||
virtual void commit() {
|
||||
}
|
||||
|
||||
/// @brief factory that constructs Uint32Parser objects
|
||||
///
|
||||
/// @param param_name name of the parameter to be parsed
|
||||
static DhcpConfigParser* Factory(const std::string& param_name) {
|
||||
return (new Uint32Parser(param_name));
|
||||
}
|
||||
|
||||
/// @brief sets storage for value of this parameter
|
||||
///
|
||||
/// See \ref dhcpv6-config-inherit for details.
|
||||
///
|
||||
/// @param storage pointer to the storage container
|
||||
void setStorage(Uint32Storage* storage) {
|
||||
storage_ = storage;
|
||||
}
|
||||
|
||||
protected:
|
||||
/// pointer to the storage, where parsed value will be stored
|
||||
Uint32Storage* storage_;
|
||||
|
||||
/// name of the parameter to be parsed
|
||||
std::string param_name_;
|
||||
|
||||
/// the actual parsed value
|
||||
uint32_t value_;
|
||||
};
|
||||
|
||||
/// @brief Configuration parser for string parameters
|
||||
///
|
||||
/// This class is a generic parser that is able to handle any string
|
||||
/// parameter. By default it stores the value in external global container
|
||||
/// (string_defaults). If used in smaller scopes (e.g. to parse parameters
|
||||
/// in subnet config), it can be pointed to a different storage, using
|
||||
/// setStorage() method. This class follows the parser interface, laid out
|
||||
/// in its base class, \ref DhcpConfigParser.
|
||||
///
|
||||
/// For overview of usability of this generic purpose parser, see
|
||||
/// \ref dhcpv6-config-inherit page.
|
||||
class StringParser : public DhcpConfigParser {
|
||||
public:
|
||||
|
||||
/// @brief constructor for StringParser
|
||||
/// @param param_name name of the configuration parameter being parsed
|
||||
StringParser(const std::string& param_name)
|
||||
:storage_(&string_defaults), param_name_(param_name) {
|
||||
}
|
||||
|
||||
/// @brief parses parameter value
|
||||
///
|
||||
/// Parses configuration entry and stored it in storage. See
|
||||
/// \ref setStorage() for details.
|
||||
///
|
||||
/// @param value pointer to the content of parsed values
|
||||
virtual void build(ConstElementPtr value) {
|
||||
value_ = value->str();
|
||||
boost::erase_all(value_, "\"");
|
||||
storage_->insert(pair<string, string>(param_name_, value_));
|
||||
}
|
||||
|
||||
/// @brief does nothing
|
||||
///
|
||||
/// This method is required for all parser. The value itself
|
||||
/// is not commited anywhere. Higher level parsers are expected to
|
||||
/// use values stored in the storage, e.g. renew-timer for a given
|
||||
/// subnet is stored in subnet-specific storage. It is not commited
|
||||
/// here, but is rather used by its parent parser when constructing
|
||||
/// an object, e.g. the subnet.
|
||||
virtual void commit() {
|
||||
}
|
||||
|
||||
/// @brief factory that constructs StringParser objects
|
||||
///
|
||||
/// @param param_name name of the parameter to be parsed
|
||||
static DhcpConfigParser* Factory(const std::string& param_name) {
|
||||
return (new StringParser(param_name));
|
||||
}
|
||||
|
||||
/// @brief sets storage for value of this parameter
|
||||
///
|
||||
/// See \ref dhcpv6-config-inherit for details.
|
||||
///
|
||||
/// @param storage pointer to the storage container
|
||||
void setStorage(StringStorage* storage) {
|
||||
storage_ = storage;
|
||||
}
|
||||
|
||||
protected:
|
||||
/// pointer to the storage, where parsed value will be stored
|
||||
StringStorage* storage_;
|
||||
|
||||
/// name of the parameter to be parsed
|
||||
std::string param_name_;
|
||||
|
||||
/// the actual parsed value
|
||||
std::string value_;
|
||||
};
|
||||
|
||||
|
||||
/// @brief parser for interface list definition
|
||||
///
|
||||
/// This parser handles Dhcp6/interface entry.
|
||||
/// It contains a list of network interfaces that the server listens on.
|
||||
/// In particular, it can contain an entry called "all" or "any" that
|
||||
/// designates all interfaces.
|
||||
///
|
||||
/// It is useful for parsing Dhcp6/interface parameter.
|
||||
class InterfaceListConfigParser : public DhcpConfigParser {
|
||||
public:
|
||||
|
||||
/// @brief constructor
|
||||
///
|
||||
/// As this is a dedicated parser, it must be used to parse
|
||||
/// "interface" parameter only. All other types will throw exception.
|
||||
///
|
||||
/// @param param_name name of the configuration parameter being parsed
|
||||
InterfaceListConfigParser(const std::string& param_name) {
|
||||
if (param_name != "interface") {
|
||||
isc_throw(NotImplemented, "Internal error. Interface configuration "
|
||||
"parser called for the wrong parameter: " << param_name);
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief parses parameters value
|
||||
///
|
||||
/// Parses configuration entry (list of parameters) and stores it in
|
||||
/// storage. See \ref setStorage() for details.
|
||||
///
|
||||
/// @param value pointer to the content of parsed values
|
||||
virtual void build(ConstElementPtr value) {
|
||||
BOOST_FOREACH(ConstElementPtr iface, value->listValue()) {
|
||||
interfaces_.push_back(iface->str());
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief commits interfaces list configuration
|
||||
virtual void commit() {
|
||||
/// @todo: Implement per interface listening. Currently always listening
|
||||
/// on all interfaces.
|
||||
}
|
||||
|
||||
/// @brief factory that constructs InterfaceListConfigParser objects
|
||||
///
|
||||
/// @param param_name name of the parameter to be parsed
|
||||
static DhcpConfigParser* Factory(const std::string& param_name) {
|
||||
return (new InterfaceListConfigParser(param_name));
|
||||
}
|
||||
|
||||
protected:
|
||||
/// contains list of network interfaces
|
||||
vector<string> interfaces_;
|
||||
};
|
||||
|
||||
/// @brief parser for pool definition
|
||||
///
|
||||
/// This parser handles pool definitions, i.e. a list of entries of one
|
||||
/// of two syntaxes: min-max and prefix/len. Pool6 objects are created
|
||||
/// and stored in chosen PoolStorage container.
|
||||
///
|
||||
/// As there are no default values for pool, setStorage() must be called
|
||||
/// before build(). Otherwise exception will be thrown.
|
||||
///
|
||||
/// It is useful for parsing Dhcp6/subnet6[X]/pool parameters.
|
||||
class PoolParser : public DhcpConfigParser {
|
||||
public:
|
||||
|
||||
/// @brief constructor.
|
||||
PoolParser(const std::string& /*param_name*/)
|
||||
:pools_(NULL) {
|
||||
// ignore parameter name, it is always Dhcp6/subnet6[X]/pool
|
||||
}
|
||||
|
||||
/// @brief parses the actual list
|
||||
///
|
||||
/// This method parses the actual list of interfaces.
|
||||
/// No validation is done at this stage, everything is interpreted as
|
||||
/// interface name.
|
||||
void build(ConstElementPtr pools_list) {
|
||||
// setStorage() should have been called before build
|
||||
if (!pools_) {
|
||||
isc_throw(NotImplemented, "Parser logic error. No pool storage set,"
|
||||
" but pool parser asked to parse pools");
|
||||
}
|
||||
|
||||
BOOST_FOREACH(ConstElementPtr text_pool, pools_list->listValue()) {
|
||||
|
||||
// That should be a single pool representation. It should contain
|
||||
// text is form prefix/len or first - last. Note that spaces
|
||||
// are allowed
|
||||
string txt = text_pool->stringValue();
|
||||
|
||||
// first let's remove any whitespaces
|
||||
boost::erase_all(txt, " "); // space
|
||||
boost::erase_all(txt, "\t"); // tabulation
|
||||
|
||||
// Is this prefix/len notation?
|
||||
size_t pos = txt.find("/");
|
||||
if (pos != string::npos) {
|
||||
IOAddress addr("::");
|
||||
uint8_t len = 0;
|
||||
try {
|
||||
addr = IOAddress(txt.substr(0, pos));
|
||||
|
||||
// start with the first character after /
|
||||
string prefix_len = txt.substr(pos + 1);
|
||||
|
||||
// It is lexical cast to int and then downcast to uint8_t.
|
||||
// Direct cast to uint8_t (which is really an unsigned char)
|
||||
// will result in interpreting the first digit as output
|
||||
// value and throwing exception if length is written on two
|
||||
// digits (because there are extra characters left over).
|
||||
|
||||
// No checks for values over 128. Range correctness will
|
||||
// be checked in Pool6 constructor.
|
||||
len = boost::lexical_cast<int>(prefix_len);
|
||||
} catch (...) {
|
||||
isc_throw(Dhcp6ConfigError, "Failed to parse pool "
|
||||
"definition: " << text_pool->stringValue());
|
||||
}
|
||||
|
||||
Pool6Ptr pool(new Pool6(Pool6::TYPE_IA, addr, len));
|
||||
pools_->push_back(pool);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Is this min-max notation?
|
||||
pos = txt.find("-");
|
||||
if (pos != string::npos) {
|
||||
// using min-max notation
|
||||
IOAddress min(txt.substr(0,pos - 1));
|
||||
IOAddress max(txt.substr(pos + 1));
|
||||
|
||||
Pool6Ptr pool(new Pool6(Pool6::TYPE_IA, min, max));
|
||||
|
||||
pools_->push_back(pool);
|
||||
continue;
|
||||
}
|
||||
|
||||
isc_throw(Dhcp6ConfigError, "Failed to parse pool definition:"
|
||||
<< text_pool->stringValue() <<
|
||||
". Does not contain - (for min-max) nor / (prefix/len)");
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief sets storage for value of this parameter
|
||||
///
|
||||
/// See \ref dhcpv6-config-inherit for details.
|
||||
///
|
||||
/// @param storage pointer to the storage container
|
||||
void setStorage(PoolStorage* storage) {
|
||||
pools_ = storage;
|
||||
}
|
||||
|
||||
/// @brief does nothing.
|
||||
///
|
||||
/// This method is required for all parser. The value itself
|
||||
/// is not commited anywhere. Higher level parsers (for subnet) are expected
|
||||
/// to use values stored in the storage.
|
||||
virtual void commit() {}
|
||||
|
||||
/// @brief factory that constructs PoolParser objects
|
||||
///
|
||||
/// @param param_name name of the parameter to be parsed
|
||||
static DhcpConfigParser* Factory(const std::string& param_name) {
|
||||
return (new PoolParser(param_name));
|
||||
}
|
||||
|
||||
protected:
|
||||
/// @brief pointer to the actual Pools storage
|
||||
///
|
||||
/// That is typically a storage somewhere in Subnet parser
|
||||
/// (an upper level parser).
|
||||
PoolStorage* pools_;
|
||||
};
|
||||
|
||||
/// @brief this class parses a single subnet
|
||||
///
|
||||
/// This class parses the whole subnet definition. It creates parsers
|
||||
/// for received configuration parameters as needed.
|
||||
class Subnet6ConfigParser : public DhcpConfigParser {
|
||||
public:
|
||||
|
||||
/// @brief constructor
|
||||
Subnet6ConfigParser(const std::string& ) {
|
||||
// The parameter should always be "subnet", but we don't check here
|
||||
// against it in case some wants to reuse this parser somewhere.
|
||||
}
|
||||
|
||||
/// @brief parses parameter value
|
||||
///
|
||||
/// @param subnet pointer to the content of subnet definition
|
||||
void build(ConstElementPtr subnet) {
|
||||
|
||||
BOOST_FOREACH(ConfigPair param, subnet->mapValue()) {
|
||||
|
||||
ParserPtr parser(createSubnet6ConfigParser(param.first));
|
||||
|
||||
// if this is an Uint32 parser, tell it to store the values
|
||||
// in values_, rather than in global storage
|
||||
boost::shared_ptr<Uint32Parser> uintParser =
|
||||
boost::dynamic_pointer_cast<Uint32Parser>(parser);
|
||||
if (uintParser) {
|
||||
uintParser->setStorage(&uint32_values_);
|
||||
} else {
|
||||
|
||||
boost::shared_ptr<StringParser> stringParser =
|
||||
boost::dynamic_pointer_cast<StringParser>(parser);
|
||||
if (stringParser) {
|
||||
stringParser->setStorage(&string_values_);
|
||||
} else {
|
||||
|
||||
boost::shared_ptr<PoolParser> poolParser =
|
||||
boost::dynamic_pointer_cast<PoolParser>(parser);
|
||||
if (poolParser) {
|
||||
poolParser->setStorage(&pools_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
parser->build(param.second);
|
||||
parsers_.push_back(parser);
|
||||
}
|
||||
|
||||
// Ok, we now have subnet parsed
|
||||
}
|
||||
|
||||
/// @brief commits received configuration.
|
||||
///
|
||||
/// This method does most of the configuration. Many other parsers are just
|
||||
/// storing the values that are actually consumed here. Pool definitions
|
||||
/// created in other parsers are used here and added to newly created Subnet6
|
||||
/// objects. Subnet6 are then added to DHCP CfgMgr.
|
||||
void commit() {
|
||||
|
||||
StringStorage::const_iterator it = string_values_.find("subnet");
|
||||
if (it == string_values_.end()) {
|
||||
isc_throw(Dhcp6ConfigError,
|
||||
"Mandatory subnet definition in subnet missing");
|
||||
}
|
||||
string subnet_txt = it->second;
|
||||
boost::erase_all(subnet_txt, " ");
|
||||
boost::erase_all(subnet_txt, "\t");
|
||||
|
||||
size_t pos = subnet_txt.find("/");
|
||||
if (pos == string::npos) {
|
||||
isc_throw(Dhcp6ConfigError,
|
||||
"Invalid subnet syntax (prefix/len expected):" << it->second);
|
||||
}
|
||||
IOAddress addr(subnet_txt.substr(0, pos));
|
||||
uint8_t len = boost::lexical_cast<unsigned int>(subnet_txt.substr(pos + 1));
|
||||
|
||||
Triplet<uint32_t> t1 = getParam("renew-timer");
|
||||
Triplet<uint32_t> t2 = getParam("rebind-timer");
|
||||
Triplet<uint32_t> pref = getParam("preferred-lifetime");
|
||||
Triplet<uint32_t> valid = getParam("valid-lifetime");
|
||||
|
||||
/// @todo: Convert this to logger once the parser is working reliably
|
||||
stringstream tmp;
|
||||
tmp << addr.toText() << "/" << (int)len
|
||||
<< " with params t1=" << t1 << ", t2=" << t2 << ", pref="
|
||||
<< pref << ", valid=" << valid;
|
||||
|
||||
LOG_INFO(dhcp6_logger, DHCP6_CONFIG_NEW_SUBNET).arg(tmp.str());
|
||||
|
||||
Subnet6Ptr subnet(new Subnet6(addr, len, t1, t2, pref, valid));
|
||||
|
||||
for (PoolStorage::iterator it = pools_.begin(); it != pools_.end(); ++it) {
|
||||
subnet->addPool6(*it);
|
||||
}
|
||||
|
||||
CfgMgr::instance().addSubnet6(subnet);
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
/// @brief creates parsers for entries in subnet definition
|
||||
///
|
||||
/// @todo Add subnet-specific things here (e.g. subnet-specific options)
|
||||
///
|
||||
/// @param config_id name od the entry
|
||||
/// @return parser object for specified entry name
|
||||
DhcpConfigParser* createSubnet6ConfigParser(const std::string& config_id) {
|
||||
FactoryMap factories;
|
||||
|
||||
factories.insert(pair<string, ParserFactory*>(
|
||||
"preferred-lifetime", Uint32Parser::Factory));
|
||||
factories.insert(pair<string, ParserFactory*>(
|
||||
"valid-lifetime", Uint32Parser::Factory));
|
||||
factories.insert(pair<string, ParserFactory*>(
|
||||
"renew-timer", Uint32Parser::Factory));
|
||||
factories.insert(pair<string, ParserFactory*>(
|
||||
"rebind-timer", Uint32Parser::Factory));
|
||||
|
||||
factories.insert(pair<string, ParserFactory*>(
|
||||
"subnet", StringParser::Factory));
|
||||
|
||||
factories.insert(pair<string, ParserFactory*>(
|
||||
"pool", PoolParser::Factory));
|
||||
|
||||
FactoryMap::iterator f = factories.find(config_id);
|
||||
if (f == factories.end()) {
|
||||
// Used for debugging only.
|
||||
// return new DebugParser(config_id);
|
||||
|
||||
isc_throw(NotImplemented,
|
||||
"Parser error: Subnet6 parameter not supported: "
|
||||
<< config_id);
|
||||
}
|
||||
return (f->second(config_id));
|
||||
}
|
||||
|
||||
/// @brief returns value for a given parameter (after using inheritance)
|
||||
///
|
||||
/// This method implements inheritance. For a given parameter name, it first
|
||||
/// checks if there is a global value for it and overwrites it with specific
|
||||
/// value if such value was defined in subnet.
|
||||
///
|
||||
/// @param name name of the parameter
|
||||
/// @return triplet with the parameter name
|
||||
Triplet<uint32_t> getParam(const std::string& name) {
|
||||
uint32_t value = 0;
|
||||
bool found = false;
|
||||
Uint32Storage::iterator global = uint32_defaults.find(name);
|
||||
if (global != uint32_defaults.end()) {
|
||||
value = global->second;
|
||||
found = true;
|
||||
}
|
||||
|
||||
Uint32Storage::iterator local = uint32_values_.find(name);
|
||||
if (local != uint32_values_.end()) {
|
||||
value = local->second;
|
||||
found = true;
|
||||
}
|
||||
|
||||
if (found) {
|
||||
return (Triplet<uint32_t>(value));
|
||||
} else {
|
||||
isc_throw(Dhcp6ConfigError, "Mandatory parameter " << name
|
||||
<< " missing (no global default and no subnet-"
|
||||
<< "specific value)");
|
||||
}
|
||||
}
|
||||
|
||||
/// storage for subnet-specific uint32 values
|
||||
Uint32Storage uint32_values_;
|
||||
|
||||
/// storage for subnet-specific integer values
|
||||
StringStorage string_values_;
|
||||
|
||||
/// storage for pools belonging to this subnet
|
||||
PoolStorage pools_;
|
||||
|
||||
/// parsers are stored here
|
||||
ParserCollection parsers_;
|
||||
};
|
||||
|
||||
/// @brief this class parses list of subnets
|
||||
///
|
||||
/// This is a wrapper parser that handles the whole list of Subnet6
|
||||
/// definitions. It iterates over all entries and creates Subnet6ConfigParser
|
||||
/// for each entry.
|
||||
class Subnets6ListConfigParser : public DhcpConfigParser {
|
||||
public:
|
||||
|
||||
/// @brief constructor
|
||||
///
|
||||
Subnets6ListConfigParser(const std::string&) {
|
||||
/// parameter name is ignored
|
||||
}
|
||||
|
||||
/// @brief parses contents of the list
|
||||
///
|
||||
/// Iterates over all entries on the list and creates Subnet6ConfigParser
|
||||
/// for each entry.
|
||||
///
|
||||
/// @param subnets_list pointer to a list of IPv6 subnets
|
||||
void build(ConstElementPtr subnets_list) {
|
||||
|
||||
// No need to define FactoryMap here. There's only one type
|
||||
// used: Subnet6ConfigParser
|
||||
|
||||
BOOST_FOREACH(ConstElementPtr subnet, subnets_list->listValue()) {
|
||||
|
||||
ParserPtr parser(new Subnet6ConfigParser("subnet"));
|
||||
parser->build(subnet);
|
||||
subnets_.push_back(parser);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// @brief commits subnets definitions.
|
||||
///
|
||||
/// Iterates over all Subnet6 parsers. Each parser contains definitions
|
||||
/// of a single subnet and its parameters and commits each subnet separately.
|
||||
void commit() {
|
||||
// @todo: Implement more subtle reconfiguration than toss
|
||||
// the old one and replace with the new one.
|
||||
|
||||
// remove old subnets
|
||||
CfgMgr::instance().deleteSubnets6();
|
||||
|
||||
BOOST_FOREACH(ParserPtr subnet, subnets_) {
|
||||
subnet->commit();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// @brief Returns Subnet6ListConfigParser object
|
||||
/// @param param_name name of the parameter
|
||||
/// @return Subnets6ListConfigParser object
|
||||
static DhcpConfigParser* Factory(const std::string& param_name) {
|
||||
return (new Subnets6ListConfigParser(param_name));
|
||||
}
|
||||
|
||||
/// @brief collection of subnet parsers.
|
||||
ParserCollection subnets_;
|
||||
};
|
||||
|
||||
/// @brief creates global parsers
|
||||
///
|
||||
/// This method creates global parsers that parse global parameters, i.e.
|
||||
/// those that take format of Dhcp6/param1, Dhcp6/param2 and so forth.
|
||||
///
|
||||
/// @param config_id pointer to received global configuration entry
|
||||
/// @return parser for specified global DHCPv6 parameter
|
||||
DhcpConfigParser* createGlobalDhcpConfigParser(const std::string& config_id) {
|
||||
FactoryMap factories;
|
||||
|
||||
//
|
||||
factories.insert(pair<string, ParserFactory*>(
|
||||
"preferred-lifetime", Uint32Parser::Factory));
|
||||
factories.insert(pair<string, ParserFactory*>(
|
||||
"valid-lifetime", Uint32Parser::Factory));
|
||||
factories.insert(pair<string, ParserFactory*>(
|
||||
"renew-timer", Uint32Parser::Factory));
|
||||
factories.insert(pair<string, ParserFactory*>(
|
||||
"rebind-timer", Uint32Parser::Factory));
|
||||
|
||||
factories.insert(pair<string, ParserFactory*>(
|
||||
"interface", InterfaceListConfigParser::Factory));
|
||||
factories.insert(pair<string, ParserFactory*>(
|
||||
"subnet6", Subnets6ListConfigParser::Factory));
|
||||
|
||||
factories.insert(pair<string, ParserFactory*>(
|
||||
"version", StringParser::Factory));
|
||||
|
||||
FactoryMap::iterator f = factories.find(config_id);
|
||||
if (f == factories.end()) {
|
||||
// Used for debugging only.
|
||||
// return new DebugParser(config_id);
|
||||
|
||||
isc_throw(NotImplemented,
|
||||
"Parser error: Global configuration parameter not supported: "
|
||||
<< config_id);
|
||||
}
|
||||
return (f->second(config_id));
|
||||
}
|
||||
|
||||
/// @brief configures DHCPv6 server
|
||||
///
|
||||
/// This function is called every time a new configuration is received. The extra
|
||||
/// parameter is a reference to DHCPv6 server component. It is currently not used
|
||||
/// and CfgMgr::instance() is accessed instead.
|
||||
///
|
||||
/// This method does not throw. It catches all exceptions and returns them as
|
||||
/// reconfiguration statuses. It may return the following response codes:
|
||||
/// 0 - configuration successful
|
||||
/// 1 - malformed configuration (parsing failed)
|
||||
/// 2 - logical error (parsing was successful, but the values are invalid)
|
||||
///
|
||||
/// @param config_set a new configuration for DHCPv6 server
|
||||
/// @return answer that contains result of reconfiguration
|
||||
ConstElementPtr
|
||||
configureDhcp6Server(Dhcpv6Srv& , ConstElementPtr config_set) {
|
||||
if (!config_set) {
|
||||
isc_throw(Dhcp6ConfigError,
|
||||
"Null pointer is passed to configuration parser");
|
||||
}
|
||||
|
||||
/// @todo: append most essential info here (like "2 new subnets configured")
|
||||
string config_details;
|
||||
|
||||
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_COMMAND, DHCP6_CONFIG_START).arg(config_set->str());
|
||||
|
||||
ParserCollection parsers;
|
||||
try {
|
||||
BOOST_FOREACH(ConfigPair config_pair, config_set->mapValue()) {
|
||||
|
||||
ParserPtr parser(createGlobalDhcpConfigParser(config_pair.first));
|
||||
parser->build(config_pair.second);
|
||||
parsers.push_back(parser);
|
||||
}
|
||||
} catch (const isc::Exception& ex) {
|
||||
ConstElementPtr answer = isc::config::createAnswer(1,
|
||||
string("Configuration parsing failed:") + ex.what());
|
||||
return (answer);
|
||||
} catch (...) {
|
||||
// for things like bad_cast in boost::lexical_cast
|
||||
ConstElementPtr answer = isc::config::createAnswer(1,
|
||||
string("Configuration parsing failed"));
|
||||
}
|
||||
|
||||
try {
|
||||
BOOST_FOREACH(ParserPtr parser, parsers) {
|
||||
parser->commit();
|
||||
}
|
||||
}
|
||||
catch (const isc::Exception& ex) {
|
||||
ConstElementPtr answer = isc::config::createAnswer(2,
|
||||
string("Configuration commit failed:") + ex.what());
|
||||
return (answer);
|
||||
} catch (...) {
|
||||
// for things like bad_cast in boost::lexical_cast
|
||||
ConstElementPtr answer = isc::config::createAnswer(2,
|
||||
string("Configuration commit failed"));
|
||||
}
|
||||
|
||||
LOG_INFO(dhcp6_logger, DHCP6_CONFIG_COMPLETE).arg(config_details);
|
||||
|
||||
ConstElementPtr answer = isc::config::createAnswer(0, "Configuration commited.");
|
||||
return (answer);
|
||||
}
|
||||
|
||||
}; // end of isc::dhcp namespace
|
||||
}; // end of isc namespace
|
147
src/bin/dhcp6/config_parser.h
Normal file
147
src/bin/dhcp6/config_parser.h
Normal file
@@ -0,0 +1,147 @@
|
||||
// Copyright (C) 2012 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.
|
||||
|
||||
#include <string>
|
||||
#include <exceptions/exceptions.h>
|
||||
#include <cc/data.h>
|
||||
|
||||
#ifndef DHCP6_CONFIG_PARSER_H
|
||||
#define DHCP6_CONFIG_PARSER_H
|
||||
|
||||
namespace isc {
|
||||
namespace dhcp {
|
||||
|
||||
class Dhcpv6Srv;
|
||||
|
||||
/// An exception that is thrown if an error occurs while configuring an
|
||||
/// \c Dhcpv6Srv object.
|
||||
class Dhcp6ConfigError : public isc::Exception {
|
||||
public:
|
||||
|
||||
/// @brief constructor
|
||||
///
|
||||
/// @param file name of the file, where exception occurred
|
||||
/// @param line line of the file, where exception occurred
|
||||
/// @param what text description of the issue that caused exception
|
||||
Dhcp6ConfigError(const char* file, size_t line, const char* what) :
|
||||
isc::Exception(file, line, what) {}
|
||||
};
|
||||
|
||||
class DhcpConfigParser {
|
||||
///
|
||||
/// \name Constructors and Destructor
|
||||
///
|
||||
/// Note: The copy constructor and the assignment operator are
|
||||
/// intentionally defined as private to make it explicit that this is a
|
||||
/// pure base class.
|
||||
//@{
|
||||
private:
|
||||
DhcpConfigParser(const DhcpConfigParser& source);
|
||||
DhcpConfigParser& operator=(const DhcpConfigParser& source);
|
||||
protected:
|
||||
/// \brief The default constructor.
|
||||
///
|
||||
/// This is intentionally defined as \c protected as this base class should
|
||||
/// never be instantiated (except as part of a derived class).
|
||||
DhcpConfigParser() {}
|
||||
public:
|
||||
/// The destructor.
|
||||
virtual ~DhcpConfigParser() {}
|
||||
//@}
|
||||
|
||||
/// \brief Prepare configuration value.
|
||||
///
|
||||
/// This method parses the "value part" of the configuration identifier
|
||||
/// that corresponds to this derived class and prepares a new value to
|
||||
/// apply to the server.
|
||||
///
|
||||
/// This method must validate the given value both in terms of syntax
|
||||
/// and semantics of the configuration, so that the server will be
|
||||
/// validly configured at the time of \c commit(). Note: the given
|
||||
/// configuration value is normally syntactically validated, but the
|
||||
/// \c build() implementation must also expect invalid input. If it
|
||||
/// detects an error it may throw an exception of a derived class
|
||||
/// of \c isc::Exception.
|
||||
///
|
||||
/// Preparing a configuration value will often require resource
|
||||
/// allocation. If it fails, it may throw a corresponding standard
|
||||
/// exception.
|
||||
///
|
||||
/// This method is not expected to be called more than once in the
|
||||
/// life of the object. Although multiple calls are not prohibited
|
||||
/// by the interface, the behavior is undefined.
|
||||
///
|
||||
/// \param config_value The configuration value for the identifier
|
||||
/// corresponding to the derived class.
|
||||
virtual void build(isc::data::ConstElementPtr config_value) = 0;
|
||||
|
||||
/// \brief Apply the prepared configuration value to the server.
|
||||
///
|
||||
/// This method is expected to be exception free, and, as a consequence,
|
||||
/// it should normally not involve resource allocation.
|
||||
/// Typically it would simply perform exception free assignment or swap
|
||||
/// operation on the value prepared in \c build().
|
||||
/// In some cases, however, it may be very difficult to meet this
|
||||
/// condition in a realistic way, while the failure case should really
|
||||
/// be very rare. In such a case it may throw, and, if the parser is
|
||||
/// called via \c configureDhcp6Server(), the caller will convert the
|
||||
/// exception as a fatal error.
|
||||
///
|
||||
/// This method is expected to be called after \c build(), and only once.
|
||||
/// The result is undefined otherwise.
|
||||
virtual void commit() = 0;
|
||||
};
|
||||
|
||||
/// @brief a pointer to configuration parser
|
||||
typedef boost::shared_ptr<DhcpConfigParser> ParserPtr;
|
||||
|
||||
/// @brief a collection of parsers
|
||||
///
|
||||
/// This container is used to store pointer to parsers for a given scope.
|
||||
typedef std::vector<ParserPtr> ParserCollection;
|
||||
|
||||
|
||||
/// \brief Configure an \c Dhcpv6Srv object with a set of configuration values.
|
||||
///
|
||||
/// This function parses configuration information stored in \c config_set
|
||||
/// and configures the \c server by applying the configuration to it.
|
||||
/// It provides the strong exception guarantee as long as the underlying
|
||||
/// derived class implementations of \c DhcpConfigParser meet the assumption,
|
||||
/// that is, it ensures that either configuration is fully applied or the
|
||||
/// state of the server is intact.
|
||||
///
|
||||
/// If a syntax or semantics level error happens during the configuration
|
||||
/// (such as malformed configuration or invalid configuration parameter),
|
||||
/// this function throws an exception of class \c Dhcp6ConfigError.
|
||||
/// If the given configuration requires resource allocation and it fails,
|
||||
/// a corresponding standard exception will be thrown.
|
||||
/// Other exceptions may also be thrown, depending on the implementation of
|
||||
/// the underlying derived class of \c Dhcp6ConfigError.
|
||||
/// In any case the strong guarantee is provided as described above except
|
||||
/// in the very rare cases where the \c commit() method of a parser throws
|
||||
/// an exception. If that happens this function converts the exception
|
||||
/// into a \c FatalError exception and rethrows it. This exception is
|
||||
/// expected to be caught at the highest level of the application to terminate
|
||||
/// the program gracefully.
|
||||
///
|
||||
/// \param server The \c Dhcpv6Srv object to be configured.
|
||||
/// \param config_set A JSON style configuration to apply to \c server.
|
||||
isc::data::ConstElementPtr
|
||||
configureDhcp6Server(Dhcpv6Srv& server,
|
||||
isc::data::ConstElementPtr config_set);
|
||||
|
||||
}; // end of isc::dhcp namespace
|
||||
}; // end of isc namespace
|
||||
|
||||
#endif // DHCP6_CONFIG_PARSER_H
|
@@ -25,6 +25,7 @@
|
||||
#include <dhcp6/ctrl_dhcp6_srv.h>
|
||||
#include <dhcp6/dhcp6_log.h>
|
||||
#include <dhcp6/spec_config.h>
|
||||
#include <dhcp6/config_parser.h>
|
||||
#include <dhcp/iface_mgr.h>
|
||||
#include <exceptions/exceptions.h>
|
||||
#include <util/buffer.h>
|
||||
@@ -47,8 +48,15 @@ ConstElementPtr
|
||||
ControlledDhcpv6Srv::dhcp6ConfigHandler(ConstElementPtr new_config) {
|
||||
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_COMMAND, DHCP6_CONFIG_UPDATE)
|
||||
.arg(new_config->str());
|
||||
ConstElementPtr answer = isc::config::createAnswer(0,
|
||||
"Thank you for sending config.");
|
||||
|
||||
if (server_) {
|
||||
return (configureDhcp6Server(*server_, new_config));
|
||||
}
|
||||
|
||||
// That should never happen as we install config_handler after we instantiate
|
||||
// the server.
|
||||
ConstElementPtr answer = isc::config::createAnswer(1,
|
||||
"Configuration rejected, server is during startup/shutdown phase.");
|
||||
return (answer);
|
||||
}
|
||||
|
||||
@@ -86,7 +94,7 @@ void ControlledDhcpv6Srv::sessionReader(void) {
|
||||
}
|
||||
|
||||
void ControlledDhcpv6Srv::establishSession() {
|
||||
|
||||
|
||||
string specfile;
|
||||
if (getenv("B10_FROM_BUILD")) {
|
||||
specfile = string(getenv("B10_FROM_BUILD")) +
|
||||
@@ -96,15 +104,27 @@ void ControlledDhcpv6Srv::establishSession() {
|
||||
}
|
||||
|
||||
/// @todo: Check if session is not established already. Throw, if it is.
|
||||
|
||||
|
||||
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_CCSESSION_STARTING)
|
||||
.arg(specfile);
|
||||
cc_session_ = new Session(io_service_.get_io_service());
|
||||
config_session_ = new ModuleCCSession(specfile, *cc_session_,
|
||||
dhcp6ConfigHandler,
|
||||
NULL,
|
||||
dhcp6CommandHandler, false);
|
||||
config_session_->start();
|
||||
|
||||
// We initially create ModuleCCSession() without configHandler, as
|
||||
// the session module is too eager to send partial configuration.
|
||||
// We want to get the full configuration, so we explicitly call
|
||||
// getFullConfig() and then pass it to our configHandler.
|
||||
config_session_->setConfigHandler(dhcp6ConfigHandler);
|
||||
|
||||
try {
|
||||
configureDhcp6Server(*this, config_session_->getFullConfig());
|
||||
} catch (const Dhcp6ConfigError& ex) {
|
||||
LOG_ERROR(dhcp6_logger, DHCP6_CONFIG_LOAD_FAIL).arg(ex.what());
|
||||
}
|
||||
|
||||
/// Integrate the asynchronous I/O model of BIND 10 configuration
|
||||
/// control with the "select" model of the DHCP server. This is
|
||||
/// fully explained in \ref dhcpv6Session.
|
||||
|
79
src/bin/dhcp6/dhcp6.dox
Normal file
79
src/bin/dhcp6/dhcp6.dox
Normal file
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
@page dhcpv6 DHCPv6 Server Component
|
||||
|
||||
BIND10 offers DHCPv6 server implementation. It is implemented as
|
||||
b10-dhcp6 component. Its primary code is located in
|
||||
isc::dhcp::Dhcpv6Srv class. It uses \ref libdhcp extensively,
|
||||
especially lib::dhcp::Pkt6, isc::dhcp::Option and
|
||||
isc::dhcp::IfaceMgr classes. Currently this code offers skeleton
|
||||
functionality, i.e. it is able to receive and process incoming
|
||||
requests and trasmit responses. However, it does not have database
|
||||
management, so it returns only one, hardcoded lease to whoever asks
|
||||
for it.
|
||||
|
||||
DHCPv6 server component does not support relayed traffic yet, as
|
||||
support for relay decapsulation is not implemented yet.
|
||||
|
||||
DHCPv6 server component does not use BIND10 logging yet.
|
||||
|
||||
@section dhcpv6-session BIND10 message queue integration
|
||||
|
||||
DHCPv4 server component is now integrated with BIND10 message queue.
|
||||
It follows the same principle as DHCPv4. See \ref dhcpv4Session for
|
||||
details.
|
||||
|
||||
@section dhcpv6-config-parser Configuration Parser in DHCPv6
|
||||
|
||||
b10-dhcp6 component uses BIND10 cfgmgr for commands and configuration. During
|
||||
initial configuration (See \ref
|
||||
isc::dhcp::ControlledDhcpv6Srv::establishSession()), the configuration handler
|
||||
callback is installed (see isc::dhcp::ControlledDhcpv6Srv::dhcp6ConfigHandler().
|
||||
It is called every time there is a new configuration. In particular, it is
|
||||
called every time during daemon start process. It contains a
|
||||
isc::data::ConstElementPtr to a new configuration. This simple handler calls
|
||||
\ref isc::dhcp::configureDhcp6Server() method that processes received configuration.
|
||||
|
||||
This method iterates over list of received configuration elements and creates a
|
||||
list of parsers for each received entry. Parser is an object that is derived
|
||||
from a \ref isc::dhcp::Dhcp6ConfigParser class. Once a parser is created
|
||||
(constructor), its value is set (using build() method). Once all parsers are
|
||||
build, the configuration is then applied ("commited") and commit() method is
|
||||
called.
|
||||
|
||||
All parsers are defined in src/bin/dhcp6/config_parser.cc file. Some of them
|
||||
are generic (e.g. \ref isc::dhcp::Uint32Parser that is able to handle any
|
||||
unsigned 32 bit integer), but some are very specialized (e.g. \ref
|
||||
isc::dhcp::Subnets6ListConfigParser parses definitions of Subnet6 lists). In
|
||||
some cases, e.g. subnet6 definitions, the configuration entry is not a simple
|
||||
value, but a map or a list itself. In such case, the parser iterates over all
|
||||
elements and creates parsers for a given scope. This process may be repeated
|
||||
(sort of) recursively.
|
||||
|
||||
@section dhcpv6-config-inherit DHCPv6 Configuration Inheritance
|
||||
|
||||
One notable useful features of DHCP configuration is its parameter inheritance.
|
||||
For example, renew-timer value may be specified at a global scope and it then
|
||||
applies to all subnets. However, some subnets may have it overwritten with more
|
||||
specific values that takes precedence over global values that are considered
|
||||
defaults. Some parsers (e.g. \ref isc::dhcp::Uint32Parser and \ref
|
||||
isc::dhcp::StringParser) implement that inheritance. By default, they store
|
||||
values in global uint32_defaults and string_defaults storages. However, it is
|
||||
possible to instruct them to store parsed values in more specific
|
||||
storages. That capability is used, e.g. in \ref isc::dhcp::Subnet6ConfigParser
|
||||
that has its own storage that is unique for each subnet. Finally, during commit
|
||||
phase (commit() method), appropriate parsers can use apply parameter inheritance.
|
||||
|
||||
Debugging configuration parser may be confusing. Therefore there is a special
|
||||
class called \ref isc::dhcp::DummyParser. It does not configure anything, but just
|
||||
accepts any parameter of any type. If requested to commit configuration, it will
|
||||
print out received parameter name and its value. This class is not currently used,
|
||||
but it is convenient to have it every time a new parameter is added to DHCP
|
||||
configuration. For that purpose it should be left in the code.
|
||||
|
||||
Parameter inheritance is done during reconfiguration phase, as reconfigurations
|
||||
are rare, so extra logic here is not a problem. On the other hand, values of
|
||||
those parameters may be used thousands times per second, so its use must be as
|
||||
simple as possible. In fact, currently the code has to call Subnet6->getT1() and
|
||||
do not implement any fancy inheritance logic.
|
||||
|
||||
*/
|
@@ -4,9 +4,97 @@
|
||||
"module_description": "DHCPv6 server daemon",
|
||||
"config_data": [
|
||||
{ "item_name": "interface",
|
||||
"item_type": "string",
|
||||
"item_type": "list",
|
||||
"item_optional": false,
|
||||
"item_default": "eth0"
|
||||
"item_default": [ "all" ],
|
||||
"list_item_spec":
|
||||
{
|
||||
"item_name": "interface_name",
|
||||
"item_type": "string",
|
||||
"item_optional": false,
|
||||
"item_default": "all"
|
||||
}
|
||||
} ,
|
||||
|
||||
{ "item_name": "renew-timer",
|
||||
"item_type": "integer",
|
||||
"item_optional": false,
|
||||
"item_default": 1000
|
||||
},
|
||||
|
||||
{ "item_name": "rebind-timer",
|
||||
"item_type": "integer",
|
||||
"item_optional": false,
|
||||
"item_default": 2000
|
||||
},
|
||||
|
||||
{ "item_name": "preferred-lifetime",
|
||||
"item_type": "integer",
|
||||
"item_optional": false,
|
||||
"item_default": 3000
|
||||
},
|
||||
|
||||
{ "item_name": "valid-lifetime",
|
||||
"item_type": "integer",
|
||||
"item_optional": false,
|
||||
"item_default": 4000
|
||||
},
|
||||
|
||||
{ "item_name": "subnet6",
|
||||
"item_type": "list",
|
||||
"item_optional": false,
|
||||
"item_default": [],
|
||||
"list_item_spec":
|
||||
{
|
||||
"item_name": "single-subnet6",
|
||||
"item_type": "map",
|
||||
"item_optional": false,
|
||||
"item_default": {},
|
||||
"map_item_spec": [
|
||||
|
||||
{ "item_name": "subnet",
|
||||
"item_type": "string",
|
||||
"item_optional": false,
|
||||
"item_default": ""
|
||||
},
|
||||
|
||||
{ "item_name": "renew-timer",
|
||||
"item_type": "integer",
|
||||
"item_optional": false,
|
||||
"item_default": 1000
|
||||
},
|
||||
|
||||
{ "item_name": "rebind-timer",
|
||||
"item_type": "integer",
|
||||
"item_optional": false,
|
||||
"item_default": 2000
|
||||
},
|
||||
|
||||
{ "item_name": "preferred-lifetime",
|
||||
"item_type": "integer",
|
||||
"item_optional": false,
|
||||
"item_default": 3000
|
||||
},
|
||||
|
||||
{ "item_name": "valid-lifetime",
|
||||
"item_type": "integer",
|
||||
"item_optional": false,
|
||||
"item_default": 7200
|
||||
},
|
||||
{ "item_name": "pool",
|
||||
"item_type": "list",
|
||||
"item_optional": false,
|
||||
"item_default": [],
|
||||
"list_item_spec":
|
||||
{
|
||||
"item_name": "type",
|
||||
"item_type": "string",
|
||||
"item_optional": false,
|
||||
"item_default": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"commands": [
|
||||
|
@@ -109,3 +109,23 @@ processed any command-line switches and is starting.
|
||||
This is a debug message issued during the IPv6 DHCP server startup.
|
||||
It lists some information about the parameters with which the server
|
||||
is running.
|
||||
|
||||
% DHCP6_CONFIG_LOAD_FAIL failed to load configuration: %1
|
||||
This critical error message indicates that the initial DHCPv6
|
||||
configuration has failed. The server will start, but nothing will be
|
||||
served until the configuration has been corrected.
|
||||
|
||||
% DHCP6_CONFIG_START DHCPv6 server is processing the following configuration: %1
|
||||
This is a debug message that is issued every time the server receives a
|
||||
configuration. That happens start up and also when a server configuration
|
||||
change is committed by the administrator.
|
||||
|
||||
% DHCP6_CONFIG_NEW_SUBNET A new subnet has been added to configuration: %1
|
||||
This is an informational message reporting that the configuration has
|
||||
been extended to include the specified subnet.
|
||||
|
||||
% DHCP6_CONFIG_COMPLETE DHCPv6 server has completed configuration: %1
|
||||
This is an informational message announcing the successful processing of a
|
||||
new configuration. it is output during server startup, and when an updated
|
||||
configuration is committed by the administrator. Additional information
|
||||
may be provided.
|
||||
|
@@ -42,6 +42,13 @@ const uint32_t HARDCODED_VALID_LIFETIME = 7200; // in seconds
|
||||
const std::string HARDCODED_DNS_SERVER = "2001:db8:1::1";
|
||||
|
||||
Dhcpv6Srv::Dhcpv6Srv(uint16_t port) {
|
||||
if (port == 0) {
|
||||
// used for testing purposes. Some tests, e.g. configuration parser,
|
||||
// require Dhcpv6Srv object, but they don't really need it to do
|
||||
// anything. This speed up and simplifies the tests.
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_OPEN_SOCKET).arg(port);
|
||||
|
||||
// First call to instance() will create IfaceMgr (it's a singleton)
|
||||
|
@@ -46,9 +46,11 @@ TESTS += dhcp6_unittests
|
||||
dhcp6_unittests_SOURCES = dhcp6_unittests.cc
|
||||
dhcp6_unittests_SOURCES += dhcp6_srv_unittest.cc
|
||||
dhcp6_unittests_SOURCES += ctrl_dhcp6_srv_unittest.cc
|
||||
dhcp6_unittests_SOURCES += config_parser_unittest.cc
|
||||
dhcp6_unittests_SOURCES += ../dhcp6_srv.h ../dhcp6_srv.cc
|
||||
dhcp6_unittests_SOURCES += ../dhcp6_log.h ../dhcp6_log.cc
|
||||
dhcp6_unittests_SOURCES += ../ctrl_dhcp6_srv.cc
|
||||
dhcp6_unittests_SOURCES += ../config_parser.cc ../config_parser.h
|
||||
nodist_dhcp6_unittests_SOURCES = ../dhcp6_messages.h ../dhcp6_messages.cc
|
||||
|
||||
if USE_CLANGPP
|
||||
@@ -62,6 +64,7 @@ dhcp6_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
|
||||
dhcp6_unittests_LDADD = $(GTEST_LDADD)
|
||||
dhcp6_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
|
||||
dhcp6_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
|
||||
dhcp6_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcpsrv.la
|
||||
dhcp6_unittests_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
|
||||
dhcp6_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
|
||||
dhcp6_unittests_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
|
||||
|
243
src/bin/dhcp6/tests/config_parser_unittest.cc
Normal file
243
src/bin/dhcp6/tests/config_parser_unittest.cc
Normal file
@@ -0,0 +1,243 @@
|
||||
// Copyright (C) 2012 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.
|
||||
|
||||
#include <config.h>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <dhcp6/dhcp6_srv.h>
|
||||
#include <dhcp6/config_parser.h>
|
||||
#include <config/ccsession.h>
|
||||
#include <dhcp/subnet.h>
|
||||
#include <dhcp/cfgmgr.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace isc;
|
||||
using namespace isc::dhcp;
|
||||
using namespace isc::asiolink;
|
||||
using namespace isc::data;
|
||||
using namespace isc::config;
|
||||
|
||||
namespace {
|
||||
|
||||
class Dhcp6ParserTest : public ::testing::Test {
|
||||
public:
|
||||
Dhcp6ParserTest()
|
||||
:rcode_(-1) {
|
||||
// Open port 0 means to not do anything at all. We don't want to
|
||||
// deal with sockets here, just check if configuration handling
|
||||
// is sane.
|
||||
srv_ = new Dhcpv6Srv(0);
|
||||
}
|
||||
|
||||
~Dhcp6ParserTest() {
|
||||
delete srv_;
|
||||
};
|
||||
|
||||
Dhcpv6Srv* srv_;
|
||||
|
||||
int rcode_;
|
||||
ConstElementPtr comment_;
|
||||
};
|
||||
|
||||
// Goal of this test is a verification if a very simple config update
|
||||
// with just a bumped version number. That's the simplest possible
|
||||
// config update.
|
||||
TEST_F(Dhcp6ParserTest, version) {
|
||||
|
||||
ConstElementPtr x;
|
||||
|
||||
EXPECT_NO_THROW(x = configureDhcp6Server(*srv_,
|
||||
Element::fromJSON("{\"version\": 0}")));
|
||||
|
||||
// returned value must be 0 (configuration accepted)
|
||||
ASSERT_TRUE(x);
|
||||
comment_ = parseAnswer(rcode_, x);
|
||||
EXPECT_EQ(0, rcode_);
|
||||
}
|
||||
|
||||
/// The goal of this test is to verify that the code accepts only
|
||||
/// valid commands and malformed or unsupported parameters are rejected.
|
||||
TEST_F(Dhcp6ParserTest, bogus_command) {
|
||||
|
||||
ConstElementPtr x;
|
||||
|
||||
EXPECT_NO_THROW(x = configureDhcp6Server(*srv_,
|
||||
Element::fromJSON("{\"bogus\": 5}")));
|
||||
|
||||
// returned value must be 1 (configuration parse error)
|
||||
ASSERT_TRUE(x);
|
||||
comment_ = parseAnswer(rcode_, x);
|
||||
EXPECT_EQ(1, rcode_);
|
||||
}
|
||||
|
||||
/// The goal of this test is to verify if wrongly defined subnet will
|
||||
/// be rejected. Properly defined subnet must include at least one
|
||||
/// pool definition.
|
||||
TEST_F(Dhcp6ParserTest, empty_subnet) {
|
||||
|
||||
ConstElementPtr status;
|
||||
|
||||
EXPECT_NO_THROW(status = configureDhcp6Server(*srv_,
|
||||
Element::fromJSON("{ \"interface\": [ \"all\" ],"
|
||||
"\"preferred-lifetime\": 3000,"
|
||||
"\"rebind-timer\": 2000, "
|
||||
"\"renew-timer\": 1000, "
|
||||
"\"subnet6\": [ ], "
|
||||
"\"valid-lifetime\": 4000 }")));
|
||||
|
||||
// returned value should be 0 (success)
|
||||
ASSERT_TRUE(status);
|
||||
comment_ = parseAnswer(rcode_, status);
|
||||
EXPECT_EQ(0, rcode_);
|
||||
}
|
||||
|
||||
/// The goal of this test is to verify if defined subnet uses global
|
||||
/// parameter timer definitions.
|
||||
TEST_F(Dhcp6ParserTest, subnet_global_defaults) {
|
||||
|
||||
ConstElementPtr status;
|
||||
|
||||
string config = "{ \"interface\": [ \"all\" ],"
|
||||
"\"preferred-lifetime\": 3000,"
|
||||
"\"rebind-timer\": 2000, "
|
||||
"\"renew-timer\": 1000, "
|
||||
"\"subnet6\": [ { "
|
||||
" \"pool\": [ \"2001:db8:1::1 - 2001:db8:1::ffff\" ],"
|
||||
" \"subnet\": \"2001:db8:1::/64\" } ],"
|
||||
"\"valid-lifetime\": 4000 }";
|
||||
cout << config << endl;
|
||||
|
||||
ElementPtr json = Element::fromJSON(config);
|
||||
|
||||
EXPECT_NO_THROW(status = configureDhcp6Server(*srv_, json));
|
||||
|
||||
// check if returned status is OK
|
||||
ASSERT_TRUE(status);
|
||||
comment_ = parseAnswer(rcode_, status);
|
||||
EXPECT_EQ(0, rcode_);
|
||||
|
||||
// Now check if the configuration was indeed handled and we have
|
||||
// expected pool configured.
|
||||
Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
|
||||
ASSERT_TRUE(subnet);
|
||||
EXPECT_EQ(1000, subnet->getT1());
|
||||
EXPECT_EQ(2000, subnet->getT2());
|
||||
EXPECT_EQ(3000, subnet->getPreferred());
|
||||
EXPECT_EQ(4000, subnet->getValid());
|
||||
}
|
||||
|
||||
// This test checks if it is possible to override global values
|
||||
// on a per subnet basis.
|
||||
TEST_F(Dhcp6ParserTest, subnet_local) {
|
||||
|
||||
ConstElementPtr status;
|
||||
|
||||
string config = "{ \"interface\": [ \"all\" ],"
|
||||
"\"preferred-lifetime\": 3000,"
|
||||
"\"rebind-timer\": 2000, "
|
||||
"\"renew-timer\": 1000, "
|
||||
"\"subnet6\": [ { "
|
||||
" \"pool\": [ \"2001:db8:1::1 - 2001:db8:1::ffff\" ],"
|
||||
" \"renew-timer\": 1, "
|
||||
" \"rebind-timer\": 2, "
|
||||
" \"preferred-lifetime\": 3,"
|
||||
" \"valid-lifetime\": 4,"
|
||||
" \"subnet\": \"2001:db8:1::/64\" } ],"
|
||||
"\"valid-lifetime\": 4000 }";
|
||||
cout << config << endl;
|
||||
|
||||
ElementPtr json = Element::fromJSON(config);
|
||||
|
||||
EXPECT_NO_THROW(status = configureDhcp6Server(*srv_, json));
|
||||
|
||||
// returned value should be 0 (configuration success)
|
||||
ASSERT_TRUE(status);
|
||||
comment_ = parseAnswer(rcode_, status);
|
||||
EXPECT_EQ(0, rcode_);
|
||||
|
||||
Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
|
||||
ASSERT_TRUE(subnet);
|
||||
EXPECT_EQ(1, subnet->getT1());
|
||||
EXPECT_EQ(2, subnet->getT2());
|
||||
EXPECT_EQ(3, subnet->getPreferred());
|
||||
EXPECT_EQ(4, subnet->getValid());
|
||||
}
|
||||
|
||||
// Test verifies that a subnet with pool values that do not belong to that
|
||||
// pool are rejected.
|
||||
TEST_F(Dhcp6ParserTest, pool_out_of_subnet) {
|
||||
|
||||
ConstElementPtr status;
|
||||
|
||||
string config = "{ \"interface\": [ \"all\" ],"
|
||||
"\"preferred-lifetime\": 3000,"
|
||||
"\"rebind-timer\": 2000, "
|
||||
"\"renew-timer\": 1000, "
|
||||
"\"subnet6\": [ { "
|
||||
" \"pool\": [ \"4001:db8:1::/80\" ],"
|
||||
" \"subnet\": \"2001:db8:1::/64\" } ],"
|
||||
"\"valid-lifetime\": 4000 }";
|
||||
cout << config << endl;
|
||||
|
||||
ElementPtr json = Element::fromJSON(config);
|
||||
|
||||
EXPECT_NO_THROW(status = configureDhcp6Server(*srv_, json));
|
||||
|
||||
// returned value must be 2 (values error)
|
||||
// as the pool does not belong to that subnet
|
||||
ASSERT_TRUE(status);
|
||||
comment_ = parseAnswer(rcode_, status);
|
||||
EXPECT_EQ(2, rcode_);
|
||||
}
|
||||
|
||||
// Goal of this test is to verify if pools can be defined
|
||||
// using prefix/length notation. There is no separate test for min-max
|
||||
// notation as it was tested in several previous tests.
|
||||
TEST_F(Dhcp6ParserTest, pool_prefix_len) {
|
||||
|
||||
ConstElementPtr x;
|
||||
|
||||
string config = "{ \"interface\": [ \"all\" ],"
|
||||
"\"preferred-lifetime\": 3000,"
|
||||
"\"rebind-timer\": 2000, "
|
||||
"\"renew-timer\": 1000, "
|
||||
"\"subnet6\": [ { "
|
||||
" \"pool\": [ \"2001:db8:1::/80\" ],"
|
||||
" \"subnet\": \"2001:db8:1::/64\" } ],"
|
||||
"\"valid-lifetime\": 4000 }";
|
||||
cout << config << endl;
|
||||
|
||||
ElementPtr json = Element::fromJSON(config);
|
||||
|
||||
EXPECT_NO_THROW(x = configureDhcp6Server(*srv_, json));
|
||||
|
||||
// returned value must be 1 (configuration parse error)
|
||||
ASSERT_TRUE(x);
|
||||
comment_ = parseAnswer(rcode_, x);
|
||||
EXPECT_EQ(0, rcode_);
|
||||
|
||||
Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
|
||||
ASSERT_TRUE(subnet);
|
||||
EXPECT_EQ(1000, subnet->getT1());
|
||||
EXPECT_EQ(2000, subnet->getT2());
|
||||
EXPECT_EQ(3000, subnet->getPreferred());
|
||||
EXPECT_EQ(4000, subnet->getValid());
|
||||
}
|
||||
|
||||
};
|
@@ -57,32 +57,32 @@ Element::toWire(std::ostream& ss) const {
|
||||
}
|
||||
|
||||
bool
|
||||
Element::getValue(long int&) {
|
||||
Element::getValue(long int&) const {
|
||||
return (false);
|
||||
}
|
||||
|
||||
bool
|
||||
Element::getValue(double&) {
|
||||
Element::getValue(double&) const {
|
||||
return (false);
|
||||
}
|
||||
|
||||
bool
|
||||
Element::getValue(bool&) {
|
||||
Element::getValue(bool&) const {
|
||||
return (false);
|
||||
}
|
||||
|
||||
bool
|
||||
Element::getValue(std::string&) {
|
||||
Element::getValue(std::string&) const {
|
||||
return (false);
|
||||
}
|
||||
|
||||
bool
|
||||
Element::getValue(std::vector<ConstElementPtr>&) {
|
||||
Element::getValue(std::vector<ConstElementPtr>&) const {
|
||||
return (false);
|
||||
}
|
||||
|
||||
bool
|
||||
Element::getValue(std::map<std::string, ConstElementPtr>&) {
|
||||
Element::getValue(std::map<std::string, ConstElementPtr>&) const {
|
||||
return (false);
|
||||
}
|
||||
|
||||
@@ -167,7 +167,7 @@ Element::find(const std::string&) const {
|
||||
}
|
||||
|
||||
bool
|
||||
Element::find(const std::string&, ConstElementPtr) const {
|
||||
Element::find(const std::string&, ConstElementPtr&) const {
|
||||
return (false);
|
||||
}
|
||||
|
||||
@@ -812,7 +812,7 @@ MapElement::set(const std::string& key, ConstElementPtr value) {
|
||||
}
|
||||
|
||||
bool
|
||||
MapElement::find(const std::string& id, ConstElementPtr t) const {
|
||||
MapElement::find(const std::string& id, ConstElementPtr& t) const {
|
||||
try {
|
||||
ConstElementPtr p = find(id);
|
||||
if (p) {
|
||||
|
@@ -71,7 +71,7 @@ public:
|
||||
/// the type in question.
|
||||
///
|
||||
class Element {
|
||||
|
||||
|
||||
private:
|
||||
// technically the type could be omitted; is it useful?
|
||||
// should we remove it or replace it with a pure virtual
|
||||
@@ -112,7 +112,7 @@ public:
|
||||
/// \returns true if the other ElementPtr has the same type and
|
||||
/// value
|
||||
virtual bool equals(const Element& other) const = 0;
|
||||
|
||||
|
||||
/// Converts the Element to JSON format and appends it to
|
||||
/// the given stringstream.
|
||||
virtual void toJSON(std::ostream& ss) const = 0;
|
||||
@@ -152,12 +152,12 @@ public:
|
||||
/// data to the given reference and returning true
|
||||
///
|
||||
//@{
|
||||
virtual bool getValue(long int& t);
|
||||
virtual bool getValue(double& t);
|
||||
virtual bool getValue(bool& t);
|
||||
virtual bool getValue(std::string& t);
|
||||
virtual bool getValue(std::vector<ConstElementPtr>& t);
|
||||
virtual bool getValue(std::map<std::string, ConstElementPtr>& t);
|
||||
virtual bool getValue(long int& t) const;
|
||||
virtual bool getValue(double& t) const;
|
||||
virtual bool getValue(bool& t) const;
|
||||
virtual bool getValue(std::string& t) const;
|
||||
virtual bool getValue(std::vector<ConstElementPtr>& t) const;
|
||||
virtual bool getValue(std::map<std::string, ConstElementPtr>& t) const;
|
||||
//@}
|
||||
|
||||
///
|
||||
@@ -209,7 +209,7 @@ public:
|
||||
virtual size_t size() const;
|
||||
//@}
|
||||
|
||||
|
||||
|
||||
/// \name MapElement functions
|
||||
///
|
||||
/// \brief If the Element on which these functions are called are not
|
||||
@@ -253,12 +253,12 @@ public:
|
||||
/// \param identifier The identifier of the element to find
|
||||
/// \param t Reference to store the resulting ElementPtr, if found.
|
||||
/// \return true if the element was found, false if not.
|
||||
virtual bool find(const std::string& identifier, ConstElementPtr t) const;
|
||||
virtual bool find(const std::string& identifier, ConstElementPtr& t) const;
|
||||
//@}
|
||||
|
||||
|
||||
/// \name Factory functions
|
||||
|
||||
|
||||
// TODO: should we move all factory functions to a different class
|
||||
// so as not to burden the Element base with too many functions?
|
||||
// and/or perhaps even to a separate header?
|
||||
@@ -349,7 +349,7 @@ public:
|
||||
/// These function pparse the wireformat at the given stringstream
|
||||
/// (of the given length). If there is a parse error an exception
|
||||
/// of the type isc::cc::DecodeError is raised.
|
||||
|
||||
|
||||
//@{
|
||||
/// Creates an Element from the wire format in the given
|
||||
/// stringstream of the given length.
|
||||
@@ -378,7 +378,7 @@ public:
|
||||
IntElement(long int v) : Element(integer), i(v) { }
|
||||
long int intValue() const { return (i); }
|
||||
using Element::getValue;
|
||||
bool getValue(long int& t) { t = i; return (true); }
|
||||
bool getValue(long int& t) const { t = i; return (true); }
|
||||
using Element::setValue;
|
||||
bool setValue(const long int v) { i = v; return (true); }
|
||||
void toJSON(std::ostream& ss) const;
|
||||
@@ -392,7 +392,7 @@ public:
|
||||
DoubleElement(double v) : Element(real), d(v) {};
|
||||
double doubleValue() const { return (d); }
|
||||
using Element::getValue;
|
||||
bool getValue(double& t) { t = d; return (true); }
|
||||
bool getValue(double& t) const { t = d; return (true); }
|
||||
using Element::setValue;
|
||||
bool setValue(const double v) { d = v; return (true); }
|
||||
void toJSON(std::ostream& ss) const;
|
||||
@@ -406,7 +406,7 @@ public:
|
||||
BoolElement(const bool v) : Element(boolean), b(v) {};
|
||||
bool boolValue() const { return (b); }
|
||||
using Element::getValue;
|
||||
bool getValue(bool& t) { t = b; return (true); }
|
||||
bool getValue(bool& t) const { t = b; return (true); }
|
||||
using Element::setValue;
|
||||
bool setValue(const bool v) { b = v; return (true); }
|
||||
void toJSON(std::ostream& ss) const;
|
||||
@@ -427,7 +427,7 @@ public:
|
||||
StringElement(std::string v) : Element(string), s(v) {};
|
||||
std::string stringValue() const { return (s); }
|
||||
using Element::getValue;
|
||||
bool getValue(std::string& t) { t = s; return (true); }
|
||||
bool getValue(std::string& t) const { t = s; return (true); }
|
||||
using Element::setValue;
|
||||
bool setValue(const std::string& v) { s = v; return (true); }
|
||||
void toJSON(std::ostream& ss) const;
|
||||
@@ -441,7 +441,7 @@ public:
|
||||
ListElement() : Element(list) {}
|
||||
const std::vector<ConstElementPtr>& listValue() const { return (l); }
|
||||
using Element::getValue;
|
||||
bool getValue(std::vector<ConstElementPtr>& t) {
|
||||
bool getValue(std::vector<ConstElementPtr>& t) const {
|
||||
t = l;
|
||||
return (true);
|
||||
}
|
||||
@@ -474,7 +474,7 @@ public:
|
||||
return (m);
|
||||
}
|
||||
using Element::getValue;
|
||||
bool getValue(std::map<std::string, ConstElementPtr>& t) {
|
||||
bool getValue(std::map<std::string, ConstElementPtr>& t) const {
|
||||
t = m;
|
||||
return (true);
|
||||
}
|
||||
@@ -495,7 +495,7 @@ public:
|
||||
return (m.find(s) != m.end());
|
||||
}
|
||||
void toJSON(std::ostream& ss) const;
|
||||
|
||||
|
||||
// we should name the two finds better...
|
||||
// find the element at id; raises TypeError if one of the
|
||||
// elements at path except the one we're looking for is not a
|
||||
@@ -507,7 +507,7 @@ public:
|
||||
// returns true if found, or false if not found (either because
|
||||
// it doesnt exist or one of the elements in the path is not
|
||||
// a MapElement)
|
||||
bool find(const std::string& id, ConstElementPtr t) const;
|
||||
bool find(const std::string& id, ConstElementPtr& t) const;
|
||||
|
||||
bool equals(const Element& other) const;
|
||||
};
|
||||
@@ -569,6 +569,6 @@ bool operator!=(const Element& a, const Element& b);
|
||||
} }
|
||||
#endif // _ISC_DATA_H
|
||||
|
||||
// Local Variables:
|
||||
// Local Variables:
|
||||
// mode: c++
|
||||
// End:
|
||||
// End:
|
||||
|
@@ -112,7 +112,7 @@ TEST(Element, from_and_to_json) {
|
||||
std::string s = std::string(pe.what());
|
||||
EXPECT_EQ("String expected in <string>:1:3", s);
|
||||
}
|
||||
|
||||
|
||||
sv.clear();
|
||||
sv.push_back("{1}");
|
||||
//ElementPtr ep = Element::fromJSON("\"aaa\nbbb\"err");
|
||||
@@ -172,14 +172,14 @@ TEST(Element, from_and_to_json) {
|
||||
|
||||
}
|
||||
|
||||
TEST(Element, create_and_value_throws) {
|
||||
// this test checks whether elements throw exceptions if the
|
||||
// incorrect type is requested
|
||||
ElementPtr el;
|
||||
template <typename T>
|
||||
void
|
||||
testGetValueInt() {
|
||||
T el;
|
||||
long int i;
|
||||
double d;
|
||||
bool b;
|
||||
std::string s("asdf");
|
||||
std::string s;
|
||||
std::vector<ConstElementPtr> v;
|
||||
std::map<std::string, ConstElementPtr> m;
|
||||
|
||||
@@ -196,7 +196,161 @@ TEST(Element, create_and_value_throws) {
|
||||
EXPECT_FALSE(el->getValue(s));
|
||||
EXPECT_FALSE(el->getValue(v));
|
||||
EXPECT_FALSE(el->getValue(m));
|
||||
EXPECT_EQ(i, 1);
|
||||
EXPECT_EQ(1, i);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void
|
||||
testGetValueDouble() {
|
||||
T el;
|
||||
long int i;
|
||||
double d;
|
||||
bool b;
|
||||
std::string s;
|
||||
std::vector<ConstElementPtr> v;
|
||||
std::map<std::string, ConstElementPtr> m;
|
||||
|
||||
el = Element::create(1.1);
|
||||
EXPECT_THROW(el->intValue(), TypeError);
|
||||
EXPECT_NO_THROW(el->doubleValue());
|
||||
EXPECT_THROW(el->boolValue(), TypeError);
|
||||
EXPECT_THROW(el->stringValue(), TypeError);
|
||||
EXPECT_THROW(el->listValue(), TypeError);
|
||||
EXPECT_THROW(el->mapValue(), TypeError);
|
||||
EXPECT_FALSE(el->getValue(i));
|
||||
EXPECT_TRUE(el->getValue(d));
|
||||
EXPECT_FALSE(el->getValue(b));
|
||||
EXPECT_FALSE(el->getValue(s));
|
||||
EXPECT_FALSE(el->getValue(v));
|
||||
EXPECT_FALSE(el->getValue(m));
|
||||
EXPECT_EQ(1.1, d);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void
|
||||
testGetValueBool() {
|
||||
T el;
|
||||
long int i;
|
||||
double d;
|
||||
bool b;
|
||||
std::string s;
|
||||
std::vector<ConstElementPtr> v;
|
||||
std::map<std::string, ConstElementPtr> m;
|
||||
|
||||
el = Element::create(true);
|
||||
EXPECT_THROW(el->intValue(), TypeError);
|
||||
EXPECT_THROW(el->doubleValue(), TypeError);
|
||||
EXPECT_NO_THROW(el->boolValue());
|
||||
EXPECT_THROW(el->stringValue(), TypeError);
|
||||
EXPECT_THROW(el->listValue(), TypeError);
|
||||
EXPECT_THROW(el->mapValue(), TypeError);
|
||||
EXPECT_FALSE(el->getValue(i));
|
||||
EXPECT_FALSE(el->getValue(d));
|
||||
EXPECT_TRUE(el->getValue(b));
|
||||
EXPECT_FALSE(el->getValue(s));
|
||||
EXPECT_FALSE(el->getValue(v));
|
||||
EXPECT_FALSE(el->getValue(m));
|
||||
EXPECT_EQ(true, b);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void
|
||||
testGetValueString() {
|
||||
T el;
|
||||
long int i;
|
||||
double d;
|
||||
bool b;
|
||||
std::string s;
|
||||
std::vector<ConstElementPtr> v;
|
||||
std::map<std::string, ConstElementPtr> m;
|
||||
|
||||
el = Element::create("foo");
|
||||
EXPECT_THROW(el->intValue(), TypeError);
|
||||
EXPECT_THROW(el->doubleValue(), TypeError);
|
||||
EXPECT_THROW(el->boolValue(), TypeError);
|
||||
EXPECT_NO_THROW(el->stringValue());
|
||||
EXPECT_THROW(el->listValue(), TypeError);
|
||||
EXPECT_THROW(el->mapValue(), TypeError);
|
||||
EXPECT_FALSE(el->getValue(i));
|
||||
EXPECT_FALSE(el->getValue(d));
|
||||
EXPECT_FALSE(el->getValue(b));
|
||||
EXPECT_TRUE(el->getValue(s));
|
||||
EXPECT_FALSE(el->getValue(v));
|
||||
EXPECT_FALSE(el->getValue(m));
|
||||
EXPECT_EQ("foo", s);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void
|
||||
testGetValueList() {
|
||||
T el;
|
||||
long int i;
|
||||
double d;
|
||||
bool b;
|
||||
std::string s;
|
||||
std::vector<ConstElementPtr> v;
|
||||
std::map<std::string, ConstElementPtr> m;
|
||||
|
||||
el = Element::createList();
|
||||
EXPECT_THROW(el->intValue(), TypeError);
|
||||
EXPECT_THROW(el->doubleValue(), TypeError);
|
||||
EXPECT_THROW(el->boolValue(), TypeError);
|
||||
EXPECT_THROW(el->stringValue(), TypeError);
|
||||
EXPECT_NO_THROW(el->listValue());
|
||||
EXPECT_THROW(el->mapValue(), TypeError);
|
||||
EXPECT_FALSE(el->getValue(i));
|
||||
EXPECT_FALSE(el->getValue(d));
|
||||
EXPECT_FALSE(el->getValue(b));
|
||||
EXPECT_FALSE(el->getValue(s));
|
||||
EXPECT_TRUE(el->getValue(v));
|
||||
EXPECT_FALSE(el->getValue(m));
|
||||
EXPECT_EQ("[ ]", el->str());
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void
|
||||
testGetValueMap() {
|
||||
T el;
|
||||
long int i;
|
||||
double d;
|
||||
bool b;
|
||||
std::string s;
|
||||
std::vector<ConstElementPtr> v;
|
||||
std::map<std::string, ConstElementPtr> m;
|
||||
|
||||
el = Element::createMap();
|
||||
EXPECT_THROW(el->intValue(), TypeError);
|
||||
EXPECT_THROW(el->doubleValue(), TypeError);
|
||||
EXPECT_THROW(el->boolValue(), TypeError);
|
||||
EXPECT_THROW(el->stringValue(), TypeError);
|
||||
EXPECT_THROW(el->listValue(), TypeError);
|
||||
EXPECT_NO_THROW(el->mapValue());
|
||||
EXPECT_FALSE(el->getValue(i));
|
||||
EXPECT_FALSE(el->getValue(d));
|
||||
EXPECT_FALSE(el->getValue(b));
|
||||
EXPECT_FALSE(el->getValue(s));
|
||||
EXPECT_FALSE(el->getValue(v));
|
||||
EXPECT_TRUE(el->getValue(m));
|
||||
EXPECT_EQ("{ }", el->str());
|
||||
}
|
||||
|
||||
TEST(Element, create_and_value_throws) {
|
||||
// this test checks whether elements throw exceptions if the
|
||||
// incorrect type is requested
|
||||
ElementPtr el;
|
||||
ConstElementPtr cel;
|
||||
long int i = 0;
|
||||
double d = 0.0;
|
||||
bool b = false;
|
||||
std::string s("asdf");
|
||||
std::vector<ConstElementPtr> v;
|
||||
std::map<std::string, ConstElementPtr> m;
|
||||
ConstElementPtr tmp;
|
||||
|
||||
testGetValueInt<ElementPtr>();
|
||||
testGetValueInt<ConstElementPtr>();
|
||||
|
||||
el = Element::create(1);
|
||||
i = 2;
|
||||
EXPECT_TRUE(el->setValue(i));
|
||||
EXPECT_EQ(2, el->intValue());
|
||||
@@ -214,24 +368,12 @@ TEST(Element, create_and_value_throws) {
|
||||
EXPECT_THROW(el->set("foo", el), TypeError);
|
||||
EXPECT_THROW(el->remove("foo"), TypeError);
|
||||
EXPECT_THROW(el->contains("foo"), TypeError);
|
||||
ConstElementPtr tmp;
|
||||
EXPECT_FALSE(el->find("foo", tmp));
|
||||
|
||||
|
||||
testGetValueDouble<ElementPtr>();
|
||||
testGetValueDouble<ConstElementPtr>();
|
||||
|
||||
el = Element::create(1.1);
|
||||
EXPECT_THROW(el->intValue(), TypeError);
|
||||
EXPECT_NO_THROW(el->doubleValue());
|
||||
EXPECT_THROW(el->boolValue(), TypeError);
|
||||
EXPECT_THROW(el->stringValue(), TypeError);
|
||||
EXPECT_THROW(el->listValue(), TypeError);
|
||||
EXPECT_THROW(el->mapValue(), TypeError);
|
||||
EXPECT_FALSE(el->getValue(i));
|
||||
EXPECT_TRUE(el->getValue(d));
|
||||
EXPECT_FALSE(el->getValue(b));
|
||||
EXPECT_FALSE(el->getValue(s));
|
||||
EXPECT_FALSE(el->getValue(v));
|
||||
EXPECT_FALSE(el->getValue(m));
|
||||
EXPECT_EQ(d, 1.1);
|
||||
d = 2.2;
|
||||
EXPECT_TRUE(el->setValue(d));
|
||||
EXPECT_EQ(2.2, el->doubleValue());
|
||||
@@ -240,75 +382,77 @@ TEST(Element, create_and_value_throws) {
|
||||
EXPECT_FALSE(el->setValue(s));
|
||||
EXPECT_FALSE(el->setValue(v));
|
||||
EXPECT_FALSE(el->setValue(m));
|
||||
EXPECT_THROW(el->get(1), TypeError);
|
||||
EXPECT_THROW(el->set(1, el), TypeError);
|
||||
EXPECT_THROW(el->add(el), TypeError);
|
||||
EXPECT_THROW(el->remove(1), TypeError);
|
||||
EXPECT_THROW(el->size(), TypeError);
|
||||
EXPECT_THROW(el->get("foo"), TypeError);
|
||||
EXPECT_THROW(el->set("foo", el), TypeError);
|
||||
EXPECT_THROW(el->remove("foo"), TypeError);
|
||||
EXPECT_THROW(el->contains("foo"), TypeError);
|
||||
EXPECT_FALSE(el->find("foo", tmp));
|
||||
|
||||
testGetValueBool<ElementPtr>();
|
||||
testGetValueBool<ConstElementPtr>();
|
||||
|
||||
el = Element::create(true);
|
||||
EXPECT_THROW(el->intValue(), TypeError);
|
||||
EXPECT_THROW(el->doubleValue(), TypeError);
|
||||
EXPECT_NO_THROW(el->boolValue());
|
||||
EXPECT_THROW(el->stringValue(), TypeError);
|
||||
EXPECT_THROW(el->listValue(), TypeError);
|
||||
EXPECT_THROW(el->mapValue(), TypeError);
|
||||
EXPECT_FALSE(el->getValue(i));
|
||||
EXPECT_FALSE(el->getValue(d));
|
||||
EXPECT_TRUE(el->getValue(b));
|
||||
EXPECT_FALSE(el->getValue(s));
|
||||
EXPECT_FALSE(el->getValue(v));
|
||||
EXPECT_FALSE(el->getValue(m));
|
||||
EXPECT_EQ(b, true);
|
||||
b = false;
|
||||
EXPECT_TRUE(el->setValue(b));
|
||||
EXPECT_FALSE(el->boolValue());
|
||||
EXPECT_FALSE(el->setValue(i));
|
||||
EXPECT_FALSE(el->setValue(d));
|
||||
EXPECT_FALSE(el->setValue(s));
|
||||
EXPECT_FALSE(el->setValue(v));
|
||||
EXPECT_FALSE(el->setValue(m));
|
||||
EXPECT_THROW(el->get(1), TypeError);
|
||||
EXPECT_THROW(el->set(1, el), TypeError);
|
||||
EXPECT_THROW(el->add(el), TypeError);
|
||||
EXPECT_THROW(el->remove(1), TypeError);
|
||||
EXPECT_THROW(el->size(), TypeError);
|
||||
EXPECT_THROW(el->get("foo"), TypeError);
|
||||
EXPECT_THROW(el->set("foo", el), TypeError);
|
||||
EXPECT_THROW(el->remove("foo"), TypeError);
|
||||
EXPECT_THROW(el->contains("foo"), TypeError);
|
||||
EXPECT_FALSE(el->find("foo", tmp));
|
||||
|
||||
testGetValueString<ElementPtr>();
|
||||
testGetValueString<ConstElementPtr>();
|
||||
|
||||
el = Element::create("foo");
|
||||
EXPECT_THROW(el->intValue(), TypeError);
|
||||
EXPECT_THROW(el->doubleValue(), TypeError);
|
||||
EXPECT_THROW(el->boolValue(), TypeError);
|
||||
EXPECT_NO_THROW(el->stringValue());
|
||||
EXPECT_THROW(el->listValue(), TypeError);
|
||||
EXPECT_THROW(el->mapValue(), TypeError);
|
||||
EXPECT_FALSE(el->getValue(i));
|
||||
EXPECT_FALSE(el->getValue(d));
|
||||
EXPECT_FALSE(el->getValue(b));
|
||||
EXPECT_TRUE(el->getValue(s));
|
||||
EXPECT_FALSE(el->getValue(v));
|
||||
EXPECT_FALSE(el->getValue(m));
|
||||
EXPECT_EQ(s, "foo");
|
||||
s = "bar";
|
||||
EXPECT_TRUE(el->setValue(s));
|
||||
EXPECT_EQ("bar", el->stringValue());
|
||||
EXPECT_FALSE(el->setValue(i));
|
||||
EXPECT_FALSE(el->setValue(b));
|
||||
EXPECT_FALSE(el->setValue(d));
|
||||
EXPECT_FALSE(el->setValue(v));
|
||||
EXPECT_FALSE(el->setValue(m));
|
||||
EXPECT_THROW(el->get(1), TypeError);
|
||||
EXPECT_THROW(el->set(1, el), TypeError);
|
||||
EXPECT_THROW(el->add(el), TypeError);
|
||||
EXPECT_THROW(el->remove(1), TypeError);
|
||||
EXPECT_THROW(el->size(), TypeError);
|
||||
EXPECT_THROW(el->get("foo"), TypeError);
|
||||
EXPECT_THROW(el->set("foo", el), TypeError);
|
||||
EXPECT_THROW(el->remove("foo"), TypeError);
|
||||
EXPECT_THROW(el->contains("foo"), TypeError);
|
||||
EXPECT_FALSE(el->find("foo", tmp));
|
||||
|
||||
testGetValueList<ElementPtr>();
|
||||
testGetValueList<ConstElementPtr>();
|
||||
|
||||
el = Element::createList();
|
||||
EXPECT_THROW(el->intValue(), TypeError);
|
||||
EXPECT_THROW(el->doubleValue(), TypeError);
|
||||
EXPECT_THROW(el->boolValue(), TypeError);
|
||||
EXPECT_THROW(el->stringValue(), TypeError);
|
||||
EXPECT_NO_THROW(el->listValue());
|
||||
EXPECT_THROW(el->mapValue(), TypeError);
|
||||
EXPECT_FALSE(el->getValue(i));
|
||||
EXPECT_FALSE(el->getValue(d));
|
||||
EXPECT_FALSE(el->getValue(b));
|
||||
EXPECT_FALSE(el->getValue(s));
|
||||
EXPECT_TRUE(el->getValue(v));
|
||||
EXPECT_FALSE(el->getValue(m));
|
||||
EXPECT_EQ("[ ]", el->str());
|
||||
v.push_back(Element::create(1));
|
||||
EXPECT_TRUE(el->setValue(v));
|
||||
EXPECT_EQ("[ 1 ]", el->str());
|
||||
|
||||
el = Element::createMap();
|
||||
EXPECT_THROW(el->intValue(), TypeError);
|
||||
EXPECT_THROW(el->doubleValue(), TypeError);
|
||||
EXPECT_THROW(el->boolValue(), TypeError);
|
||||
EXPECT_THROW(el->stringValue(), TypeError);
|
||||
EXPECT_THROW(el->listValue(), TypeError);
|
||||
EXPECT_NO_THROW(el->mapValue());
|
||||
EXPECT_FALSE(el->getValue(i));
|
||||
EXPECT_FALSE(el->getValue(d));
|
||||
EXPECT_FALSE(el->getValue(b));
|
||||
EXPECT_FALSE(el->getValue(s));
|
||||
EXPECT_FALSE(el->getValue(v));
|
||||
EXPECT_TRUE(el->getValue(m));
|
||||
testGetValueMap<ElementPtr>();
|
||||
testGetValueMap<ConstElementPtr>();
|
||||
|
||||
el = Element::createMap();
|
||||
EXPECT_NO_THROW(el->set("foo", Element::create("bar")));
|
||||
EXPECT_EQ("{ \"foo\": \"bar\" }", el->str());
|
||||
}
|
||||
|
||||
// Helper for escape check; it puts the given string in a StringElement,
|
||||
@@ -382,7 +526,7 @@ TEST(Element, MapElement) {
|
||||
// this function checks the specific functions for ListElements
|
||||
ElementPtr el = Element::fromJSON("{ \"name\": \"foo\", \"value1\": \"bar\", \"value2\": { \"number\": 42 } }");
|
||||
ConstElementPtr el2;
|
||||
|
||||
|
||||
EXPECT_EQ(el->get("name")->stringValue(), "foo");
|
||||
EXPECT_EQ(el->get("value2")->getType(), Element::map);
|
||||
|
||||
@@ -396,11 +540,12 @@ TEST(Element, MapElement) {
|
||||
|
||||
EXPECT_EQ(el->find("value2/number")->intValue(), 42);
|
||||
EXPECT_TRUE(isNull(el->find("value2/nothing/")));
|
||||
|
||||
|
||||
EXPECT_EQ(el->find("value1")->stringValue(), "bar");
|
||||
EXPECT_EQ(el->find("value1/")->stringValue(), "bar");
|
||||
|
||||
|
||||
EXPECT_TRUE(el->find("value1", el2));
|
||||
EXPECT_EQ("bar", el2->stringValue());
|
||||
EXPECT_FALSE(el->find("name/error", el2));
|
||||
|
||||
// A map element whose (only) element has the maximum length of tag.
|
||||
@@ -410,7 +555,7 @@ TEST(Element, MapElement) {
|
||||
"9123456789abcdefa123456789abcdefb123456789abcdef"
|
||||
"c123456789abcdefd123456789abcdefe123456789abcdef"
|
||||
"f123456789abcde");
|
||||
|
||||
|
||||
EXPECT_EQ(255, long_maptag.length()); // check prerequisite
|
||||
el = Element::fromJSON("{ \"" + long_maptag + "\": \"bar\"}");
|
||||
EXPECT_EQ("bar", el->find(long_maptag)->stringValue());
|
||||
@@ -689,7 +834,7 @@ TEST(Element, merge) {
|
||||
c = Element::fromJSON("{ \"a\": { \"b\": \"c\" } }");
|
||||
merge(b, a);
|
||||
EXPECT_EQ(*b, *c);
|
||||
|
||||
|
||||
// And some tests with multiple values
|
||||
a = Element::fromJSON("{ \"a\": 1, \"b\": true, \"c\": null }");
|
||||
b = Element::fromJSON("{ \"a\": 1, \"b\": null, \"c\": \"a string\" }");
|
||||
|
@@ -129,7 +129,7 @@ checkConfig(ConstElementPtr config, ElementPtr errors) {
|
||||
result = false;
|
||||
} else {
|
||||
try {
|
||||
RRClass rrc(config->get("class")->stringValue());
|
||||
RRClass(config->get("class")->stringValue());
|
||||
} catch (const isc::Exception& rrce) {
|
||||
addError(errors,
|
||||
"Error parsing class config for memory backend: " +
|
||||
|
@@ -16,6 +16,7 @@ CLEANFILES = *.gcno *.gcda
|
||||
lib_LTLIBRARIES = libb10-dhcp++.la libb10-dhcpsrv.la
|
||||
libb10_dhcp___la_SOURCES =
|
||||
libb10_dhcp___la_SOURCES += libdhcp++.cc libdhcp++.h
|
||||
libb10_dhcp___la_SOURCES += lease_mgr.cc lease_mgr.h
|
||||
libb10_dhcp___la_SOURCES += iface_mgr.cc iface_mgr.h
|
||||
libb10_dhcp___la_SOURCES += iface_mgr_linux.cc
|
||||
libb10_dhcp___la_SOURCES += iface_mgr_bsd.cc
|
||||
@@ -28,6 +29,7 @@ libb10_dhcp___la_SOURCES += option4_addrlst.cc option4_addrlst.h
|
||||
libb10_dhcp___la_SOURCES += dhcp6.h dhcp4.h
|
||||
libb10_dhcp___la_SOURCES += pkt6.cc pkt6.h
|
||||
libb10_dhcp___la_SOURCES += pkt4.cc pkt4.h
|
||||
libb10_dhcp___la_SOURCES += duid.cc duid.h
|
||||
|
||||
libb10_dhcpsrv_la_SOURCES = cfgmgr.cc cfgmgr.h
|
||||
libb10_dhcpsrv_la_SOURCES += pool.cc pool.h
|
||||
|
@@ -13,17 +13,39 @@
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
#include <string.h>
|
||||
#include <exceptions/exceptions.h>
|
||||
#include <dhcp/addr_utilities.h>
|
||||
|
||||
using namespace isc::asiolink;
|
||||
|
||||
namespace isc {
|
||||
namespace dhcp {
|
||||
namespace {
|
||||
|
||||
isc::asiolink::IOAddress firstAddrInPrefix(const isc::asiolink::IOAddress& prefix,
|
||||
/// @brief mask used for first/last address calculation in a IPv4 prefix
|
||||
///
|
||||
/// Using a static mask is faster than calculating it dynamically every time.
|
||||
const uint32_t bitMask4[] = { 0xffffffff, 0x7fffffff, 0x3fffffff, 0x1fffffff,
|
||||
0x0fffffff, 0x07ffffff, 0x03ffffff, 0x01ffffff,
|
||||
0x00ffffff, 0x007fffff, 0x003fffff, 0x001fffff,
|
||||
0x000fffff, 0x0007ffff, 0x0003ffff, 0x0001ffff,
|
||||
0x0000ffff, 0x00007fff, 0x00003fff, 0x00001fff,
|
||||
0x00000fff, 0x000007ff, 0x000003ff, 0x000001ff,
|
||||
0x000000ff, 0x0000007f, 0x0000003f, 0x0000001f,
|
||||
0x0000000f, 0x00000007, 0x00000003, 0x00000001,
|
||||
0x00000000 };
|
||||
|
||||
/// @brief mask used for first/last address calculation in a IPv6 prefix
|
||||
const uint8_t bitMask6[]= { 0, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff };
|
||||
|
||||
/// @brief calculates the first IPv6 address in a IPv6 prefix
|
||||
///
|
||||
/// Note: This is a private function. Do not use it directly.
|
||||
/// Please use firstAddrInPrefix() instead.
|
||||
///
|
||||
/// @param prefix IPv6 prefix
|
||||
/// @param len prefix length
|
||||
isc::asiolink::IOAddress firstAddrInPrefix6(const isc::asiolink::IOAddress& prefix,
|
||||
uint8_t len) {
|
||||
|
||||
const static uint8_t bitMask[]= { 0, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff };
|
||||
uint8_t packed[V6ADDRESS_LEN];
|
||||
|
||||
// First we copy the whole address as 16 bytes.
|
||||
@@ -36,7 +58,7 @@ isc::asiolink::IOAddress firstAddrInPrefix(const isc::asiolink::IOAddress& prefi
|
||||
|
||||
// Get the appropriate mask. It has relevant bits (those that should
|
||||
// stay) set and irrelevant (those that should be wiped) cleared.
|
||||
uint8_t mask = bitMask[len % 8];
|
||||
uint8_t mask = bitMask6[len % 8];
|
||||
|
||||
// Let's leave only whatever the mask says should not be cleared.
|
||||
packed[len / 8] = packed[len / 8] & mask;
|
||||
@@ -55,10 +77,50 @@ isc::asiolink::IOAddress firstAddrInPrefix(const isc::asiolink::IOAddress& prefi
|
||||
return (isc::asiolink::IOAddress::from_bytes(AF_INET6, packed));
|
||||
}
|
||||
|
||||
isc::asiolink::IOAddress lastAddrInPrefix(const isc::asiolink::IOAddress& prefix,
|
||||
/// @brief calculates the first IPv4 address in a IPv4 prefix
|
||||
///
|
||||
/// Note: This is a private function. Do not use it directly.
|
||||
/// Please use firstAddrInPrefix() instead.
|
||||
///
|
||||
/// @param prefix IPv4 prefix
|
||||
/// @param len netmask length (0-32)
|
||||
isc::asiolink::IOAddress firstAddrInPrefix4(const isc::asiolink::IOAddress& prefix,
|
||||
uint8_t len) {
|
||||
uint32_t addr = prefix;
|
||||
if (len > 32) {
|
||||
isc_throw(isc::BadValue, "Too large netmask. 0..32 is allowed in IPv4");
|
||||
}
|
||||
|
||||
return (IOAddress(addr & (~bitMask4[len])));
|
||||
}
|
||||
|
||||
/// @brief calculates the last IPv4 address in a IPv4 prefix
|
||||
///
|
||||
/// Note: This is a private function. Do not use it directly.
|
||||
/// Please use firstAddrInPrefix() instead.
|
||||
///
|
||||
/// @param prefix IPv4 prefix that we calculate first address for
|
||||
/// @param len netmask length (0-32)
|
||||
isc::asiolink::IOAddress lastAddrInPrefix4(const isc::asiolink::IOAddress& prefix,
|
||||
uint8_t len) {
|
||||
uint32_t addr = prefix;
|
||||
if (len>32) {
|
||||
isc_throw(isc::BadValue, "Too large netmask. 0..32 is allowed in IPv4");
|
||||
}
|
||||
|
||||
return (IOAddress(addr | bitMask4[len]));
|
||||
}
|
||||
|
||||
/// @brief calculates the last IPv6 address in a IPv6 prefix
|
||||
///
|
||||
/// Note: This is a private function. Do not use it directly.
|
||||
/// Please use lastAddrInPrefix() instead.
|
||||
///
|
||||
/// @param prefix IPv6 prefix that we calculate first address for
|
||||
/// @param len netmask length (0-128)
|
||||
isc::asiolink::IOAddress lastAddrInPrefix6(const isc::asiolink::IOAddress& prefix,
|
||||
uint8_t len) {
|
||||
|
||||
const static uint8_t bitMask[]= { 0, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff };
|
||||
uint8_t packed[V6ADDRESS_LEN];
|
||||
|
||||
// First we copy the whole address as 16 bytes.
|
||||
@@ -70,10 +132,10 @@ isc::asiolink::IOAddress lastAddrInPrefix(const isc::asiolink::IOAddress& prefix
|
||||
if (len % 8 != 0) {
|
||||
// Get the appropriate mask. It has relevant bits (those that should
|
||||
// stay) set and irrelevant (those that should be set to 1) cleared.
|
||||
uint8_t mask = bitMask[len % 8];
|
||||
uint8_t mask = bitMask6[len % 8];
|
||||
|
||||
// Let's set those irrelevant bits with 1. It would be perhaps
|
||||
// easier to not use negation here and invert bitMask content. However,
|
||||
// easier to not use negation here and invert bitMask6 content. However,
|
||||
// with this approach, we can use the same mask in first and last
|
||||
// address calculations.
|
||||
packed[len / 8] = packed[len / 8] | ~mask;
|
||||
@@ -92,5 +154,28 @@ isc::asiolink::IOAddress lastAddrInPrefix(const isc::asiolink::IOAddress& prefix
|
||||
return (isc::asiolink::IOAddress::from_bytes(AF_INET6, packed));
|
||||
}
|
||||
|
||||
}; // end of anonymous namespace
|
||||
|
||||
namespace isc {
|
||||
namespace dhcp {
|
||||
|
||||
isc::asiolink::IOAddress firstAddrInPrefix(const isc::asiolink::IOAddress& prefix,
|
||||
uint8_t len) {
|
||||
if (prefix.getFamily() == AF_INET) {
|
||||
return firstAddrInPrefix4(prefix, len);
|
||||
} else {
|
||||
return firstAddrInPrefix6(prefix, len);
|
||||
}
|
||||
}
|
||||
|
||||
isc::asiolink::IOAddress lastAddrInPrefix(const isc::asiolink::IOAddress& prefix,
|
||||
uint8_t len) {
|
||||
if (prefix.getFamily() == AF_INET) {
|
||||
return lastAddrInPrefix4(prefix, len);
|
||||
} else {
|
||||
return lastAddrInPrefix6(prefix, len);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
};
|
||||
|
@@ -68,6 +68,39 @@ void CfgMgr::addSubnet6(const Subnet6Ptr& subnet) {
|
||||
subnets6_.push_back(subnet);
|
||||
}
|
||||
|
||||
Subnet4Ptr
|
||||
CfgMgr::getSubnet4(const isc::asiolink::IOAddress& hint) {
|
||||
|
||||
// If there's only one subnet configured, let's just use it
|
||||
// The idea is to keep small deployments easy. In a small network - one
|
||||
// router that also runs DHCPv6 server. Users specifies a single pool and
|
||||
// expects it to just work. Without this, the server would complain that it
|
||||
// doesn't have IP address on its interfaces that matches that
|
||||
// configuration. Such requirement makes sense in IPv4, but not in IPv6.
|
||||
// The server does not need to have a global address (using just link-local
|
||||
// is ok for DHCPv6 server) from the pool it serves.
|
||||
if (subnets4_.size() == 1) {
|
||||
return (subnets4_[0]);
|
||||
}
|
||||
|
||||
// If there is more than one, we need to choose the proper one
|
||||
for (Subnet4Collection::iterator subnet = subnets4_.begin();
|
||||
subnet != subnets4_.end(); ++subnet) {
|
||||
if ((*subnet)->inRange(hint)) {
|
||||
return (*subnet);
|
||||
}
|
||||
}
|
||||
|
||||
// sorry, we don't support that subnet
|
||||
return (Subnet4Ptr());
|
||||
}
|
||||
|
||||
void CfgMgr::addSubnet4(const Subnet4Ptr& subnet) {
|
||||
/// @todo: Check that this new subnet does not cross boundaries of any
|
||||
/// other already defined subnet.
|
||||
subnets4_.push_back(subnet);
|
||||
}
|
||||
|
||||
CfgMgr::CfgMgr() {
|
||||
}
|
||||
|
||||
|
@@ -72,7 +72,7 @@ public:
|
||||
/// accessing it.
|
||||
static CfgMgr& instance();
|
||||
|
||||
/// @brief get subnet by address
|
||||
/// @brief get IPv6 subnet by address
|
||||
///
|
||||
/// Finds a matching subnet, based on an address. This can be used
|
||||
/// in two cases: when trying to find an appropriate lease based on
|
||||
@@ -83,7 +83,7 @@ public:
|
||||
/// @param hint an address that belongs to a searched subnet
|
||||
Subnet6Ptr getSubnet6(const isc::asiolink::IOAddress& hint);
|
||||
|
||||
/// @brief get subnet by interface-id
|
||||
/// @brief get IPv6 subnet by interface-id
|
||||
///
|
||||
/// Another possibility to find a subnet is based on interface-id.
|
||||
///
|
||||
@@ -91,13 +91,44 @@ public:
|
||||
/// @todo This method is not currently supported.
|
||||
Subnet6Ptr getSubnet6(OptionPtr interface_id);
|
||||
|
||||
/// @brief adds a subnet6
|
||||
/// @brief adds an IPv6 subnet
|
||||
void addSubnet6(const Subnet6Ptr& subnet);
|
||||
|
||||
/// @todo: Add subnet6 removal routines. Currently it is not possible
|
||||
/// to remove subnets. The only case where subnet6 removal would be
|
||||
/// needed is a dynamic server reconfiguration - a use case that is not
|
||||
/// planned to be supported any time soon.
|
||||
|
||||
/// @brief removes all subnets
|
||||
///
|
||||
/// This method removes all existing subnets. It is used during
|
||||
/// reconfiguration - old configuration is wiped and new definitions
|
||||
/// are used to recreate subnets.
|
||||
///
|
||||
/// @todo Implement more intelligent approach. Note that comparison
|
||||
/// between old and new configuration is tricky. For example: is
|
||||
/// 2000::/64 and 2000::/48 the same subnet or is it something
|
||||
/// completely new?
|
||||
void deleteSubnets6() {
|
||||
subnets6_.clear();
|
||||
}
|
||||
|
||||
/// @brief get IPv4 subnet by address
|
||||
///
|
||||
/// Finds a matching subnet, based on an address. This can be used
|
||||
/// in two cases: when trying to find an appropriate lease based on
|
||||
/// a) relay link address (that must be the address that is on link)
|
||||
/// b) our global address on the interface the message was received on
|
||||
/// (for directly connected clients)
|
||||
///
|
||||
/// @param hint an address that belongs to a searched subnet
|
||||
Subnet4Ptr getSubnet4(const isc::asiolink::IOAddress& hint);
|
||||
|
||||
/// @brief adds a subnet4
|
||||
void addSubnet4(const Subnet4Ptr& subnet);
|
||||
|
||||
/// @brief removes all IPv4 subnets
|
||||
void removeSubnets4();
|
||||
protected:
|
||||
|
||||
/// @brief Protected constructor.
|
||||
@@ -111,13 +142,21 @@ protected:
|
||||
/// @brief virtual desctructor
|
||||
virtual ~CfgMgr();
|
||||
|
||||
/// @brief a container for Subnet6
|
||||
/// @brief a container for IPv6 subnets.
|
||||
///
|
||||
/// That is a simple vector of pointers. It does not make much sense to
|
||||
/// optimize access time (e.g. using a map), because typical search
|
||||
/// pattern will use calling inRange() method on each subnet until
|
||||
/// a match is found.
|
||||
Subnet6Collection subnets6_;
|
||||
|
||||
/// @brief a container for IPv4 subnets.
|
||||
///
|
||||
/// That is a simple vector of pointers. It does not make much sense to
|
||||
/// optimize access time (e.g. using a map), because typical search
|
||||
/// pattern will use calling inRange() method on each subnet until
|
||||
/// a match is found.
|
||||
Subnet4Collection subnets4_;
|
||||
};
|
||||
|
||||
} // namespace isc::dhcp
|
||||
|
90
src/lib/dhcp/duid.cc
Normal file
90
src/lib/dhcp/duid.cc
Normal file
@@ -0,0 +1,90 @@
|
||||
// Copyright (C) 2012 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.
|
||||
|
||||
#include <vector>
|
||||
#include <exceptions/exceptions.h>
|
||||
#include <stdint.h>
|
||||
#include <util/io_utilities.h>
|
||||
#include <dhcp/duid.h>
|
||||
|
||||
namespace isc {
|
||||
namespace dhcp {
|
||||
|
||||
DUID::DUID(const std::vector<uint8_t>& duid) {
|
||||
if (duid.size() > MAX_DUID_LEN) {
|
||||
isc_throw(OutOfRange, "DUID too large");
|
||||
} else {
|
||||
duid_ = duid;
|
||||
}
|
||||
}
|
||||
|
||||
DUID::DUID(const uint8_t * data, size_t len) {
|
||||
if (len > MAX_DUID_LEN) {
|
||||
isc_throw(OutOfRange, "DUID too large");
|
||||
}
|
||||
|
||||
duid_ = std::vector<uint8_t>(data, data + len);
|
||||
}
|
||||
|
||||
const std::vector<uint8_t> DUID::getDuid() const {
|
||||
return (duid_);
|
||||
}
|
||||
|
||||
DUID::DUIDType DUID::getType() const {
|
||||
if (duid_.size() < 2) {
|
||||
return (DUID_UNKNOWN);
|
||||
}
|
||||
uint16_t type = (duid_[0] << 8) + duid_[1];
|
||||
if (type < DUID_MAX) {
|
||||
return (static_cast<DUID::DUIDType>(type));
|
||||
} else {
|
||||
return (DUID_UNKNOWN);
|
||||
}
|
||||
}
|
||||
|
||||
bool DUID::operator == (const DUID& other) const {
|
||||
return (this->duid_ == other.duid_);
|
||||
}
|
||||
|
||||
bool DUID::operator != (const DUID& other) const {
|
||||
return (this->duid_ != other.duid_);
|
||||
}
|
||||
|
||||
/// constructor based on vector<uint8_t>
|
||||
ClientId::ClientId(const std::vector<uint8_t>& clientid)
|
||||
:DUID(clientid) {
|
||||
}
|
||||
|
||||
/// constructor based on C-style data
|
||||
ClientId::ClientId(const uint8_t *clientid, size_t len)
|
||||
:DUID(clientid, len) {
|
||||
}
|
||||
|
||||
/// @brief returns a copy of client-id data
|
||||
const std::vector<uint8_t> ClientId::getClientId() const {
|
||||
return (duid_);
|
||||
}
|
||||
|
||||
// compares two client-ids
|
||||
bool ClientId::operator == (const ClientId& other) const {
|
||||
return (this->duid_ == other.duid_);
|
||||
}
|
||||
|
||||
// compares two client-ids
|
||||
bool ClientId::operator != (const ClientId& other) const {
|
||||
return (this->duid_ != other.duid_);
|
||||
}
|
||||
|
||||
}; // end of isc::dhcp namespace
|
||||
}; // end of isc namespace
|
98
src/lib/dhcp/duid.h
Normal file
98
src/lib/dhcp/duid.h
Normal file
@@ -0,0 +1,98 @@
|
||||
// Copyright (C) 2012 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.
|
||||
|
||||
#include <stdint.h>
|
||||
#include <unistd.h>
|
||||
#include <vector>
|
||||
#include <asiolink/io_address.h>
|
||||
|
||||
|
||||
namespace isc {
|
||||
namespace dhcp {
|
||||
|
||||
/// @brief Holds DUID (DHCPv6 Unique Identifier)
|
||||
///
|
||||
/// This class holds DUID, that is used in client-id, server-id and
|
||||
/// several other options. It is used to identify DHCPv6 entity.
|
||||
class DUID {
|
||||
public:
|
||||
/// @brief maximum duid size
|
||||
/// As defined in RFC3315, section 9.1
|
||||
static const size_t MAX_DUID_LEN = 128;
|
||||
|
||||
/// @brief specifies DUID type
|
||||
typedef enum {
|
||||
DUID_UNKNOWN = 0, ///< invalid/unknown type
|
||||
DUID_LLT = 1, ///< link-layer + time, see RFC3315, section 9.2
|
||||
DUID_EN = 2, ///< enterprise-id, see RFC3315, section 9.3
|
||||
DUID_LL = 3, ///< link-layer, see RFC3315, section 9.4
|
||||
DUID_UUID = 4, ///< UUID, see RFC6355
|
||||
DUID_MAX ///< not a real type, just maximum defined value + 1
|
||||
} DUIDType;
|
||||
|
||||
/// @brief creates a DUID
|
||||
DUID(const std::vector<uint8_t>& duid);
|
||||
|
||||
/// @brief creates a DUID
|
||||
DUID(const uint8_t *duid, size_t len);
|
||||
|
||||
/// @brief returns a const reference to the actual DUID value
|
||||
///
|
||||
/// Note: For safety reasons, this method returns a copy of data as
|
||||
/// otherwise the reference would be only valid as long as the object that
|
||||
/// returned it. In any case, this method should be used only sporadically.
|
||||
/// If there are frequent uses, we must implement some other method
|
||||
/// (e.g. storeSelf()) that will avoid data copying.
|
||||
const std::vector<uint8_t> getDuid() const;
|
||||
|
||||
/// @brief returns DUID type
|
||||
DUIDType getType() const;
|
||||
|
||||
// compares two DUIDs
|
||||
bool operator == (const DUID& other) const;
|
||||
|
||||
// compares two DUIDs
|
||||
bool operator != (const DUID& other) const;
|
||||
|
||||
protected:
|
||||
/// the actual content of the DUID
|
||||
std::vector<uint8_t> duid_;
|
||||
};
|
||||
|
||||
/// @brief Holds Client identifier or client IPv4 address
|
||||
///
|
||||
/// This class is intended to be a generic IPv4 client identifier. It can hold
|
||||
/// a client-id
|
||||
class ClientId : DUID {
|
||||
public:
|
||||
|
||||
/// constructor based on vector<uint8_t>
|
||||
ClientId(const std::vector<uint8_t>& clientid);
|
||||
|
||||
/// constructor based on C-style data
|
||||
ClientId(const uint8_t *clientid, size_t len);
|
||||
|
||||
/// @brief returns reference to the client-id data
|
||||
///
|
||||
const std::vector<uint8_t> getClientId() const;
|
||||
|
||||
// compares two client-ids
|
||||
bool operator == (const ClientId& other) const;
|
||||
|
||||
// compares two client-ids
|
||||
bool operator != (const ClientId& other) const;
|
||||
};
|
||||
|
||||
}; // end of isc::dhcp namespace
|
||||
}; // end of isc namespace
|
68
src/lib/dhcp/lease_mgr.cc
Normal file
68
src/lib/dhcp/lease_mgr.cc
Normal file
@@ -0,0 +1,68 @@
|
||||
// Copyright (C) 2012 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.
|
||||
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <exceptions/exceptions.h>
|
||||
#include <boost/foreach.hpp>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include "lease_mgr.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
using namespace isc::dhcp;
|
||||
|
||||
LeaseMgr::LeaseMgr(const std::string& dbconfig) {
|
||||
|
||||
if (dbconfig.length() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
vector<string> tokens;
|
||||
|
||||
// we need to pass a string to is_any_of, not just char *. Otherwise there
|
||||
// are cryptic warnings on Debian6 running g++ 4.4 in /usr/include/c++/4.4
|
||||
// /bits/stl_algo.h:2178 "array subscript is above array bounds"
|
||||
boost::split(tokens, dbconfig, boost::is_any_of( string("\t ") ));
|
||||
BOOST_FOREACH(std::string token, tokens) {
|
||||
size_t pos = token.find("=");
|
||||
if (pos != string::npos) {
|
||||
string name = token.substr(0, pos);
|
||||
string value = token.substr(pos + 1);
|
||||
parameters_.insert(pair<string,string>(name, value));
|
||||
} else {
|
||||
isc_throw(InvalidParameter, "Cannot parse " << token
|
||||
<< ", expected format is name=value");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
std::string LeaseMgr::getParameter(const std::string& name) const {
|
||||
std::map<std::string, std::string>::const_iterator param
|
||||
= parameters_.find(name);
|
||||
if (param == parameters_.end()) {
|
||||
isc_throw(BadValue, "Parameter not found");
|
||||
}
|
||||
return (param->second);
|
||||
}
|
||||
|
||||
LeaseMgr::~LeaseMgr() {
|
||||
}
|
480
src/lib/dhcp/lease_mgr.h
Normal file
480
src/lib/dhcp/lease_mgr.h
Normal file
@@ -0,0 +1,480 @@
|
||||
// Copyright (C) 2012 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.
|
||||
|
||||
#include <string>
|
||||
#include <fstream>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <asiolink/io_address.h>
|
||||
#include <boost/shared_ptr.hpp>
|
||||
#include <dhcp/option.h>
|
||||
#include <dhcp/duid.h>
|
||||
|
||||
/// @file dhcp/lease_mgr.h
|
||||
/// @brief An abstract API for lease database
|
||||
///
|
||||
/// This file contains declarations of Lease4, Lease6 and LeaseMgr classes.
|
||||
/// They are essential components of the interface to any database backend.
|
||||
/// Each concrete database backend (e.g. MySQL) will define a class derived
|
||||
/// from LeaseMgr class.
|
||||
///
|
||||
/// Failover considerations:
|
||||
/// There are no intermediate plans to implement DHCPv4 failover
|
||||
/// (draft-ietf-dhc-failover-12.txt). Currently (Oct. 2012) the DHCPv6 failover
|
||||
/// is being defined in DHC WG in IETF (draft-ietf-dhcpv6-failover-requirements,
|
||||
/// draft-ietf-dhcpv6-dailover-design), but the work is not advanced enough
|
||||
/// for implementation plans yet. v4 failover requires additional parameters
|
||||
/// to be kept with a lease. It is likely that v6 failover will require similar
|
||||
/// fields. Such implementation will require database schema extension.
|
||||
/// We have designed a way to expand/upgrade schemas during upgrades: a database
|
||||
/// schema is versioned and sanity checks about required version will be done
|
||||
/// upon start and/or upgrade. With this mechanism in place, we can add new
|
||||
/// fields to the database. In particular we can use that capability to
|
||||
/// introduce failover related fields.
|
||||
///
|
||||
/// However, there is another approach that can be reliably used to provide
|
||||
/// failover, even without the actual failover protocol implemented. As the
|
||||
/// first backend will use MySQL, we will be able to use Multi-Master capability
|
||||
/// offered by MySQL and use two separatate Kea instances connecting to the
|
||||
/// same database.
|
||||
///
|
||||
/// Nevertheless, we hope to have failover protocol eventually implemented in
|
||||
/// the Kea.
|
||||
|
||||
namespace isc {
|
||||
namespace dhcp {
|
||||
|
||||
/// @brief specifies unique subnet identifier
|
||||
/// @todo: Move this to subnet.h once ticket #2237 is merged
|
||||
typedef uint32_t SubnetID;
|
||||
|
||||
/// @brief Structure that holds a lease for IPv4 address
|
||||
///
|
||||
/// For performance reasons it is a simple structure, not a class. If we chose
|
||||
/// make it a class, all fields would have to made private and getters/setters
|
||||
/// would be required. As this is a critical part of the code that will be used
|
||||
/// extensively, direct access is warranted.
|
||||
struct Lease4 {
|
||||
/// IPv4 address
|
||||
isc::asiolink::IOAddress addr_;
|
||||
|
||||
/// @brief Address extension
|
||||
///
|
||||
/// It is envisaged that in some cases IPv4 address will be accompanied with some
|
||||
/// additional data. One example of such use are Address + Port solutions (or
|
||||
/// Port-restricted Addresses), where several clients may get the same address, but
|
||||
/// different port ranges. This feature is not expected to be widely used.
|
||||
/// Under normal circumstances, the value should be 0.
|
||||
uint32_t ext_;
|
||||
|
||||
/// @brief hardware address
|
||||
std::vector<uint8_t> hwaddr_;
|
||||
|
||||
/// @brief client identifier
|
||||
boost::shared_ptr<ClientId> client_id_;
|
||||
|
||||
/// @brief renewal timer
|
||||
///
|
||||
/// Specifies renewal time. Although technically it is a property of IA container,
|
||||
/// not the address itself, since our data model does not define separate IA
|
||||
/// entity, we are keeping it in the lease. In case of multiple addresses/prefixes
|
||||
/// for the same IA, each must have consistent T1 and T2 values. Specified in
|
||||
/// seconds since cltt.
|
||||
uint32_t t1_;
|
||||
|
||||
/// @brief rebinding timer
|
||||
///
|
||||
/// Specifies rebinding time. Although technically it is a property of IA container,
|
||||
/// not the address itself, since our data model does not define separate IA
|
||||
/// entity, we are keeping it in the lease. In case of multiple addresses/prefixes
|
||||
/// for the same IA, each must have consistent T1 and T2 values. Specified in
|
||||
/// seconds since cltt.
|
||||
uint32_t t2_;
|
||||
|
||||
/// @brief valid lifetime
|
||||
///
|
||||
/// Expressed as number of seconds since cltt
|
||||
uint32_t valid_lft_;
|
||||
|
||||
/// @brief client last transmission time
|
||||
///
|
||||
/// Specifies a timestamp, when last transmission from a client was received.
|
||||
time_t cltt_;
|
||||
|
||||
/// @brief Subnet identifier
|
||||
///
|
||||
/// Specifies subnet-id of the subnet that the lease belongs to
|
||||
SubnetID subnet_id_;
|
||||
|
||||
/// @brief Is this a fixed lease?
|
||||
///
|
||||
/// Fixed leases are kept after they are released/expired.
|
||||
bool fixed_;
|
||||
|
||||
/// @brief client hostname
|
||||
///
|
||||
/// This field may be empty
|
||||
std::string hostname_;
|
||||
|
||||
/// @brief did we update AAAA record for this lease?
|
||||
bool fqdn_fwd_;
|
||||
|
||||
/// @brief did we update PTR record for this lease?
|
||||
bool fqdn_rev_;
|
||||
|
||||
/// @brief Lease comments.
|
||||
///
|
||||
/// Currently not used. It may be used for keeping comments made by the
|
||||
/// system administrator.
|
||||
std::string comments_;
|
||||
|
||||
/// @todo: Add DHCPv4 failover related fields here
|
||||
};
|
||||
|
||||
/// @brief Pointer to a Lease4 structure.
|
||||
typedef boost::shared_ptr<Lease4> Lease4Ptr;
|
||||
|
||||
/// @brief A collection of IPv4 leases.
|
||||
typedef std::vector< boost::shared_ptr<Lease4Ptr> > Lease4Collection;
|
||||
|
||||
/// @brief Structure that holds a lease for IPv6 address and/or prefix
|
||||
///
|
||||
/// For performance reasons it is a simple structure, not a class. Had we chose to
|
||||
/// make it a class, all fields would have to be made private and getters/setters
|
||||
/// would be required. As this is a critical part of the code that will be used
|
||||
/// extensively, direct access rather than through getters/setters is warranted.
|
||||
struct Lease6 {
|
||||
typedef enum {
|
||||
LEASE_IA_NA, /// the lease contains non-temporary IPv6 address
|
||||
LEASE_IA_TA, /// the lease contains temporary IPv6 address
|
||||
LEASE_IA_PD /// the lease contains IPv6 prefix (for prefix delegation)
|
||||
} LeaseType;
|
||||
|
||||
/// @brief specifies lease type (normal addr, temporary addr, prefix)
|
||||
LeaseType type_;
|
||||
|
||||
/// IPv6 address
|
||||
isc::asiolink::IOAddress addr_;
|
||||
|
||||
/// IPv6 prefix length (used only for PD)
|
||||
uint8_t prefixlen_;
|
||||
|
||||
/// @brief IAID
|
||||
///
|
||||
/// Identity Association IDentifier. DHCPv6 stores all addresses and prefixes
|
||||
/// in IA containers (IA_NA, IA_TA, IA_PD). Most containers may appear more
|
||||
/// than once in a message. To differentiate between them, IAID field is present
|
||||
uint32_t iaid_;
|
||||
|
||||
/// @brief hardware address
|
||||
///
|
||||
/// This field is not really used and is optional at best. The concept of identifying
|
||||
/// clients by their hardware address was replaced in DHCPv6 by DUID concept. Each
|
||||
/// client has its own unique DUID (DHCP Unique IDentifier). Furthermore, client's
|
||||
/// HW address is not always available, because client may be behind a relay (relay
|
||||
/// stores only link-local address).
|
||||
std::vector<uint8_t> hwaddr_;
|
||||
|
||||
/// @brief client identifier
|
||||
boost::shared_ptr<DUID> duid_;
|
||||
|
||||
/// @brief preferred lifetime
|
||||
///
|
||||
/// This parameter specifies preferred lifetime since the lease was assigned/renewed
|
||||
/// (cltt), expressed in seconds.
|
||||
uint32_t preferred_lft_;
|
||||
|
||||
/// @brief valid lifetime
|
||||
///
|
||||
/// This parameter specified valid lifetime since the lease was assigned/renewed
|
||||
/// (cltt), expressed in seconds.
|
||||
uint32_t valid_lft_;
|
||||
|
||||
/// @brief T1 timer
|
||||
///
|
||||
/// Specifies renewal time. Although technically it is a property of IA container,
|
||||
/// not the address itself, since our data model does not define separate IA
|
||||
/// entity, we are keeping it in the lease. In case of multiple addresses/prefixes
|
||||
/// for the same IA, each must have consistent T1 and T2 values. Specified in
|
||||
/// seconds since cltt.
|
||||
uint32_t t1_;
|
||||
|
||||
/// @brief T2 timer
|
||||
///
|
||||
/// Specifies rebinding time. Although technically it is a property of IA container,
|
||||
/// not the address itself, since our data model does not define separate IA
|
||||
/// entity, we are keeping it in the lease. In case of multiple addresses/prefixes
|
||||
/// for the same IA, each must have consistent T1 and T2 values. Specified in
|
||||
/// seconds since cltt.
|
||||
uint32_t t2_;
|
||||
|
||||
/// @brief client last transmission time
|
||||
///
|
||||
/// Specifies a timestamp, when last transmission from a client was received.
|
||||
time_t cltt_;
|
||||
|
||||
/// @brief Subnet identifier
|
||||
///
|
||||
/// Specifies subnet-id of the subnet that the lease belongs to
|
||||
SubnetID subnet_id_;
|
||||
|
||||
/// @brief Is this a fixed lease?
|
||||
///
|
||||
/// Fixed leases are kept after they are released/expired.
|
||||
bool fixed_;
|
||||
|
||||
/// @brief client hostname
|
||||
///
|
||||
/// This field may be empty
|
||||
std::string hostname_;
|
||||
|
||||
/// @brief did we update AAAA record for this lease?
|
||||
bool fqdn_fwd_;
|
||||
|
||||
/// @brief did we update PTR record for this lease?
|
||||
bool fqdn_rev_;
|
||||
|
||||
/// @brief Lease comments
|
||||
///
|
||||
/// This field is currently not used.
|
||||
std::string comments_;
|
||||
|
||||
/// @todo: Add DHCPv6 failover related fields here
|
||||
};
|
||||
|
||||
/// @brief Pointer to a Lease6 structure.
|
||||
typedef boost::shared_ptr<Lease6> Lease6Ptr;
|
||||
|
||||
/// @brief Const pointer to a Lease6 structure.
|
||||
typedef boost::shared_ptr<const Lease6> ConstLease6Ptr;
|
||||
|
||||
/// @brief A collection of IPv6 leases.
|
||||
typedef std::vector< boost::shared_ptr<Lease6Ptr> > Lease6Collection;
|
||||
|
||||
/// @brief Abstract Lease Manager
|
||||
///
|
||||
/// This is an abstract API for lease database backends. It provides unified
|
||||
/// interface to all backends. As this is an abstract class, it should not
|
||||
/// be used directly, but rather specialized derived class should be used
|
||||
/// instead.
|
||||
class LeaseMgr {
|
||||
public:
|
||||
|
||||
/// Client Hardware address
|
||||
typedef std::vector<uint8_t> HWAddr;
|
||||
|
||||
/// @brief The sole lease manager constructor
|
||||
///
|
||||
/// dbconfig is a generic way of passing parameters. Parameters
|
||||
/// are passed in the "name=value" format, separated by spaces.
|
||||
/// Values may be enclosed in double quotes, if needed.
|
||||
///
|
||||
/// @param dbconfig database configuration
|
||||
LeaseMgr(const std::string& dbconfig);
|
||||
|
||||
/// @brief Destructor (closes file)
|
||||
virtual ~LeaseMgr();
|
||||
|
||||
/// @brief Adds an IPv4 lease.
|
||||
///
|
||||
/// @param lease lease to be added
|
||||
virtual bool addLease(Lease4Ptr lease) = 0;
|
||||
|
||||
/// @brief Adds an IPv6 lease.
|
||||
///
|
||||
/// @param lease lease to be added
|
||||
virtual bool addLease(Lease6Ptr lease) = 0;
|
||||
|
||||
/// @brief Returns existing IPv4 lease for specified IPv4 address and subnet_id
|
||||
///
|
||||
/// This method is used to get a lease for specific subnet_id. There can be
|
||||
/// at most one lease for any given subnet, so this method returns a single
|
||||
/// pointer.
|
||||
///
|
||||
/// @param addr address of the searched lease
|
||||
/// @param subnet_id ID of the subnet the lease must belong to
|
||||
///
|
||||
/// @return smart pointer to the lease (or NULL if a lease is not found)
|
||||
virtual Lease4Ptr getLease4(isc::asiolink::IOAddress addr,
|
||||
SubnetID subnet_id) const = 0;
|
||||
|
||||
/// @brief Returns an IPv4 lease for specified IPv4 address
|
||||
///
|
||||
/// This method return a lease that is associated with a given address.
|
||||
/// For other query types (by hardware addr, by client-id) there can be
|
||||
/// several leases in different subnets (e.g. for mobile clients that
|
||||
/// got address in different subnets). However, for a single address
|
||||
/// there can be only one lease, so this method returns a pointer to
|
||||
/// a single lease, not a container of leases.
|
||||
///
|
||||
/// @param addr address of the searched lease
|
||||
/// @param subnet_id ID of the subnet the lease must belong to
|
||||
///
|
||||
/// @return smart pointer to the lease (or NULL if a lease is not found)
|
||||
virtual Lease4Ptr getLease4(isc::asiolink::IOAddress addr) const = 0;
|
||||
|
||||
/// @brief Returns existing IPv4 leases for specified hardware address.
|
||||
///
|
||||
/// Although in the usual case there will be only one lease, for mobile
|
||||
/// clients or clients with multiple static/fixed/reserved leases there
|
||||
/// can be more than one. Thus return type is a container, not a single
|
||||
/// pointer.
|
||||
///
|
||||
/// @param hwaddr hardware address of the client
|
||||
///
|
||||
/// @return lease collection
|
||||
virtual Lease4Collection getLease4(const HWAddr& hwaddr) const = 0;
|
||||
|
||||
/// @brief Returns existing IPv4 leases for specified hardware address
|
||||
/// and a subnet
|
||||
///
|
||||
/// There can be at most one lease for a given HW address in a single
|
||||
/// pool, so this method with either return a single lease or NULL.
|
||||
///
|
||||
/// @param hwaddr hardware address of the client
|
||||
/// @param subnet_id identifier of the subnet that lease must belong to
|
||||
///
|
||||
/// @return a pointer to the lease (or NULL if a lease is not found)
|
||||
virtual Lease4Ptr getLease4(const HWAddr& hwaddr,
|
||||
SubnetID subnet_id) const = 0;
|
||||
|
||||
/// @brief Returns existing IPv4 lease for specified client-id
|
||||
///
|
||||
/// Although in the usual case there will be only one lease, for mobile
|
||||
/// clients or clients with multiple static/fixed/reserved leases there
|
||||
/// can be more than one. Thus return type is a container, not a single
|
||||
/// pointer.
|
||||
///
|
||||
/// @param clientid client identifier
|
||||
///
|
||||
/// @return lease collection
|
||||
virtual Lease4Collection getLease4(const ClientId& clientid) const = 0;
|
||||
|
||||
/// @brief Returns existing IPv4 lease for specified client-id
|
||||
///
|
||||
/// There can be at most one lease for a given HW address in a single
|
||||
/// pool, so this method with either return a single lease or NULL.
|
||||
///
|
||||
/// @param clientid client identifier
|
||||
/// @param subnet_id identifier of the subnet that lease must belong to
|
||||
///
|
||||
/// @return a pointer to the lease (or NULL if a lease is not found)
|
||||
virtual Lease4Ptr getLease4(const ClientId& clientid,
|
||||
SubnetID subnet_id) const = 0;
|
||||
|
||||
/// @brief Returns existing IPv6 lease for a given IPv6 address.
|
||||
///
|
||||
/// For a given address, we assume that there will be only one lease.
|
||||
/// The assumtion here is that there will not be site or link-local
|
||||
/// addresses used, so there is no way of having address duplication.
|
||||
///
|
||||
/// @param addr address of the searched lease
|
||||
///
|
||||
/// @return smart pointer to the lease (or NULL if a lease is not found)
|
||||
virtual Lease6Ptr getLease6(isc::asiolink::IOAddress addr) const = 0;
|
||||
|
||||
/// @brief Returns existing IPv6 leases for a given DUID+IA combination
|
||||
///
|
||||
/// Although in the usual case there will be only one lease, for mobile
|
||||
/// clients or clients with multiple static/fixed/reserved leases there
|
||||
/// can be more than one. Thus return type is a container, not a single
|
||||
/// pointer.
|
||||
///
|
||||
/// @param duid client DUID
|
||||
/// @param iaid IA identifier
|
||||
///
|
||||
/// @return smart pointer to the lease (or NULL if a lease is not found)
|
||||
virtual Lease6Collection getLease6(const DUID& duid,
|
||||
uint32_t iaid) const = 0;
|
||||
|
||||
/// @brief Returns existing IPv6 lease for a given DUID+IA combination
|
||||
///
|
||||
/// @param duid client DUID
|
||||
/// @param iaid IA identifier
|
||||
/// @param subnet_id subnet id of the subnet the lease belongs to
|
||||
///
|
||||
/// @return smart pointer to the lease (or NULL if a lease is not found)
|
||||
virtual Lease6Ptr getLease6(const DUID& duid, uint32_t iaid,
|
||||
SubnetID subnet_id) const = 0;
|
||||
|
||||
/// @brief Updates IPv4 lease.
|
||||
///
|
||||
/// @param lease4 The lease to be updated.
|
||||
///
|
||||
/// If no such lease is present, an exception will be thrown.
|
||||
virtual void updateLease4(Lease4Ptr lease4) = 0;
|
||||
|
||||
/// @brief Updates IPv4 lease.
|
||||
///
|
||||
/// @param lease4 The lease to be updated.
|
||||
///
|
||||
/// If no such lease is present, an exception will be thrown.
|
||||
virtual void updateLease6(Lease6Ptr lease6) = 0;
|
||||
|
||||
/// @brief Deletes a lease.
|
||||
///
|
||||
/// @param addr IPv4 address of the lease to be deleted.
|
||||
///
|
||||
/// @return true if deletion was successful, false if no such lease exists
|
||||
virtual bool deleteLease4(uint32_t addr) = 0;
|
||||
|
||||
/// @brief Deletes a lease.
|
||||
///
|
||||
/// @param addr IPv4 address of the lease to be deleted.
|
||||
///
|
||||
/// @return true if deletion was successful, false if no such lease exists
|
||||
virtual bool deleteLease6(isc::asiolink::IOAddress addr) = 0;
|
||||
|
||||
/// @brief Returns backend name.
|
||||
///
|
||||
/// Each backend have specific name, e.g. "mysql" or "sqlite".
|
||||
virtual std::string getName() const = 0;
|
||||
|
||||
/// @brief Returns description of the backend.
|
||||
///
|
||||
/// This description may be multiline text that describes the backend.
|
||||
virtual std::string getDescription() const = 0;
|
||||
|
||||
/// @brief Returns backend version.
|
||||
///
|
||||
/// @todo: We will need to implement 3 version functions eventually:
|
||||
/// A. abstract API version
|
||||
/// B. backend version
|
||||
/// C. database version (stored in the database scheme)
|
||||
///
|
||||
/// and then check that:
|
||||
/// B>=A and B=C (it is ok to have newer backend, as it should be backward
|
||||
/// compatible)
|
||||
/// Also if B>C, some database upgrade procedure may be triggered
|
||||
virtual std::string getVersion() const = 0;
|
||||
|
||||
/// @todo: Add host management here
|
||||
/// As host reservation is outside of scope for 2012, support for hosts
|
||||
/// is currently postponed.
|
||||
|
||||
protected:
|
||||
/// @brief returns value of the parameter
|
||||
std::string getParameter(const std::string& name) const;
|
||||
|
||||
/// @brief list of parameters passed in dbconfig
|
||||
///
|
||||
/// That will be mostly used for storing database name, username,
|
||||
/// password and other parameters required for DB access. It is not
|
||||
/// intended to keep any DHCP-related parameters.
|
||||
std::map<std::string, std::string> parameters_;
|
||||
};
|
||||
|
||||
}; // end of isc::dhcp namespace
|
||||
|
||||
}; // end of isc namespace
|
@@ -30,6 +30,38 @@ bool Pool::inRange(const isc::asiolink::IOAddress& addr) const {
|
||||
return (first_.smallerEqual(addr) && addr.smallerEqual(last_));
|
||||
}
|
||||
|
||||
Pool4::Pool4(const isc::asiolink::IOAddress& first,
|
||||
const isc::asiolink::IOAddress& last)
|
||||
:Pool(first, last) {
|
||||
// check if specified address boundaries are sane
|
||||
if (first.getFamily() != AF_INET || last.getFamily() != AF_INET) {
|
||||
isc_throw(BadValue, "Invalid Pool4 address boundaries: not IPv4");
|
||||
}
|
||||
|
||||
if (last < first) {
|
||||
isc_throw(BadValue, "Upper boundary is smaller than lower boundary.");
|
||||
}
|
||||
}
|
||||
|
||||
Pool4::Pool4(const isc::asiolink::IOAddress& prefix,
|
||||
uint8_t prefix_len)
|
||||
:Pool(prefix, IOAddress("0.0.0.0")) {
|
||||
|
||||
// check if the prefix is sane
|
||||
if (prefix.getFamily() != AF_INET) {
|
||||
isc_throw(BadValue, "Invalid Pool4 address boundaries: not IPv4");
|
||||
}
|
||||
|
||||
// check if the prefix length is sane
|
||||
if (prefix_len == 0 || prefix_len > 32) {
|
||||
isc_throw(BadValue, "Invalid prefix length");
|
||||
}
|
||||
|
||||
// Let's now calculate the last address in defined pool
|
||||
last_ = lastAddrInPrefix(prefix, prefix_len);
|
||||
}
|
||||
|
||||
|
||||
Pool6::Pool6(Pool6Type type, const isc::asiolink::IOAddress& first,
|
||||
const isc::asiolink::IOAddress& last)
|
||||
:Pool(first, last), type_(type), prefix_len_(0) {
|
||||
@@ -52,7 +84,6 @@ Pool6::Pool6(Pool6Type type, const isc::asiolink::IOAddress& first,
|
||||
// last_ = first;
|
||||
}
|
||||
|
||||
|
||||
// TYPE_PD is not supported by this constructor. first-last style
|
||||
// parameters are for IA and TA only. There is another dedicated
|
||||
// constructor for that (it uses prefix/length)
|
||||
|
@@ -91,6 +91,33 @@ protected:
|
||||
std::string comments_;
|
||||
};
|
||||
|
||||
/// @brief Pool information for IPv4 addresses
|
||||
///
|
||||
/// It holds information about pool4, i.e. a range of IPv4 address space that
|
||||
/// is configured for DHCP allocation.
|
||||
class Pool4 : public Pool {
|
||||
public:
|
||||
/// @brief the constructor for Pool4 "min-max" style definition
|
||||
///
|
||||
/// @param first the first address in a pool
|
||||
/// @param last the last address in a pool
|
||||
Pool4(const isc::asiolink::IOAddress& first,
|
||||
const isc::asiolink::IOAddress& last);
|
||||
|
||||
/// @brief the constructor for Pool4 "prefix/len" style definition
|
||||
///
|
||||
/// @param prefix specifies prefix of the pool
|
||||
/// @param prefix_len specifies length of the prefix of the pool
|
||||
Pool4(const isc::asiolink::IOAddress& prefix,
|
||||
uint8_t prefix_len);
|
||||
};
|
||||
|
||||
/// @brief a pointer an IPv4 Pool
|
||||
typedef boost::shared_ptr<Pool4> Pool4Ptr;
|
||||
|
||||
/// @brief a container for IPv4 Pools
|
||||
typedef std::vector<Pool4Ptr> Pool4Collection;
|
||||
|
||||
/// @brief Pool information for IPv6 addresses and prefixes
|
||||
///
|
||||
/// It holds information about pool6, i.e. a range of IPv6 address space that
|
||||
|
@@ -41,6 +41,50 @@ bool Subnet::inRange(const isc::asiolink::IOAddress& addr) const {
|
||||
return ((first <= addr) && (addr <= last));
|
||||
}
|
||||
|
||||
Subnet4::Subnet4(const isc::asiolink::IOAddress& prefix, uint8_t length,
|
||||
const Triplet<uint32_t>& t1,
|
||||
const Triplet<uint32_t>& t2,
|
||||
const Triplet<uint32_t>& valid_lifetime)
|
||||
:Subnet(prefix, length, t1, t2, valid_lifetime) {
|
||||
if (prefix.getFamily() != AF_INET) {
|
||||
isc_throw(BadValue, "Non IPv4 prefix " << prefix.toText()
|
||||
<< " specified in subnet4");
|
||||
}
|
||||
}
|
||||
|
||||
void Subnet4::addPool4(const Pool4Ptr& pool) {
|
||||
IOAddress first_addr = pool->getFirstAddress();
|
||||
IOAddress last_addr = pool->getLastAddress();
|
||||
|
||||
if (!inRange(first_addr) || !inRange(last_addr)) {
|
||||
isc_throw(BadValue, "Pool4 (" << first_addr.toText() << "-" << last_addr.toText()
|
||||
<< " does not belong in this (" << prefix_ << "/" << prefix_len_
|
||||
<< ") subnet4");
|
||||
}
|
||||
|
||||
/// @todo: Check that pools do not overlap
|
||||
|
||||
pools_.push_back(pool);
|
||||
}
|
||||
|
||||
Pool4Ptr Subnet4::getPool4(const isc::asiolink::IOAddress& hint /* = IOAddress("::")*/ ) {
|
||||
Pool4Ptr candidate;
|
||||
for (Pool4Collection::iterator pool = pools_.begin(); pool != pools_.end(); ++pool) {
|
||||
|
||||
// if we won't find anything better, then let's just use the first pool
|
||||
if (!candidate) {
|
||||
candidate = *pool;
|
||||
}
|
||||
|
||||
// if the client provided a pool and there's a pool that hint is valid in,
|
||||
// then let's use that pool
|
||||
if ((*pool)->inRange(hint)) {
|
||||
return (*pool);
|
||||
}
|
||||
}
|
||||
return (candidate);
|
||||
}
|
||||
|
||||
Subnet6::Subnet6(const isc::asiolink::IOAddress& prefix, uint8_t length,
|
||||
const Triplet<uint32_t>& t1,
|
||||
const Triplet<uint32_t>& t2,
|
||||
|
@@ -93,6 +93,57 @@ protected:
|
||||
Triplet<uint32_t> valid_;
|
||||
};
|
||||
|
||||
/// @brief A configuration holder for IPv4 subnet.
|
||||
///
|
||||
/// This class represents an IPv4 subnet.
|
||||
class Subnet4 : public Subnet {
|
||||
public:
|
||||
|
||||
/// @brief Constructor with all parameters
|
||||
///
|
||||
/// @param prefix Subnet4 prefix
|
||||
/// @param length prefix length
|
||||
/// @param t1 renewal timer (in seconds)
|
||||
/// @param t2 rebind timer (in seconds)
|
||||
/// @param valid_lifetime preferred lifetime of leases (in seconds)
|
||||
Subnet4(const isc::asiolink::IOAddress& prefix, uint8_t length,
|
||||
const Triplet<uint32_t>& t1,
|
||||
const Triplet<uint32_t>& t2,
|
||||
const Triplet<uint32_t>& valid_lifetime);
|
||||
|
||||
/// @brief Returns a pool that specified address belongs to
|
||||
///
|
||||
/// @param hint address that the returned pool should cover (optional)
|
||||
/// @return Pointer to found pool4 (or NULL)
|
||||
Pool4Ptr getPool4(const isc::asiolink::IOAddress& hint =
|
||||
isc::asiolink::IOAddress("0.0.0.0"));
|
||||
|
||||
/// @brief Adds a new pool.
|
||||
/// @param pool pool to be added
|
||||
void addPool4(const Pool4Ptr& pool);
|
||||
|
||||
/// @brief returns all pools
|
||||
///
|
||||
/// The reference is only valid as long as the object that
|
||||
/// returned it.
|
||||
///
|
||||
/// @return a collection of all pools
|
||||
const Pool4Collection& getPools() const {
|
||||
return pools_;
|
||||
}
|
||||
|
||||
protected:
|
||||
/// @brief collection of pools in that list
|
||||
Pool4Collection pools_;
|
||||
};
|
||||
|
||||
/// @brief A pointer to a Subnet4 object
|
||||
typedef boost::shared_ptr<Subnet4> Subnet4Ptr;
|
||||
|
||||
/// @brief A collection of Subnet6 objects
|
||||
typedef std::vector<Subnet4Ptr> Subnet4Collection;
|
||||
|
||||
|
||||
/// @brief A configuration holder for IPv6 subnet.
|
||||
///
|
||||
/// This class represents an IPv6 subnet.
|
||||
|
@@ -28,6 +28,7 @@ TESTS += libdhcp++_unittests libdhcpsrv_unittests
|
||||
libdhcp___unittests_SOURCES = run_unittests.cc
|
||||
libdhcp___unittests_SOURCES += libdhcp++_unittest.cc
|
||||
libdhcp___unittests_SOURCES += iface_mgr_unittest.cc
|
||||
libdhcp___unittests_SOURCES += lease_mgr_unittest.cc
|
||||
libdhcp___unittests_SOURCES += option6_iaaddr_unittest.cc
|
||||
libdhcp___unittests_SOURCES += option6_ia_unittest.cc
|
||||
libdhcp___unittests_SOURCES += option6_addrlst_unittest.cc
|
||||
@@ -35,6 +36,7 @@ libdhcp___unittests_SOURCES += option4_addrlst_unittest.cc
|
||||
libdhcp___unittests_SOURCES += option_unittest.cc
|
||||
libdhcp___unittests_SOURCES += pkt6_unittest.cc
|
||||
libdhcp___unittests_SOURCES += pkt4_unittest.cc
|
||||
libdhcp___unittests_SOURCES += duid_unittest.cc
|
||||
|
||||
libdhcp___unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) $(LOG4CPLUS_INCLUDES)
|
||||
libdhcp___unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
|
||||
|
@@ -26,7 +26,67 @@ using namespace std;
|
||||
using namespace isc::dhcp;
|
||||
using namespace isc::asiolink;
|
||||
|
||||
TEST(Pool6Test, lastAddrInPrefix) {
|
||||
// This test verifies that lastAddrInPrefix is able to handle IPv4 operations.
|
||||
TEST(AddrUtilitiesTest, lastAddrInPrefix4) {
|
||||
IOAddress addr1("192.0.2.1");
|
||||
|
||||
// Prefixes rounded to addresses are easy...
|
||||
EXPECT_EQ("192.255.255.255", lastAddrInPrefix(addr1, 8).toText());
|
||||
EXPECT_EQ("192.0.255.255", lastAddrInPrefix(addr1, 16).toText());
|
||||
EXPECT_EQ("192.0.2.255", lastAddrInPrefix(addr1, 24).toText());
|
||||
|
||||
// these are trickier
|
||||
EXPECT_EQ("192.0.2.127", lastAddrInPrefix(addr1, 25).toText());
|
||||
EXPECT_EQ("192.0.2.63", lastAddrInPrefix(addr1, 26).toText());
|
||||
EXPECT_EQ("192.0.2.31", lastAddrInPrefix(addr1, 27).toText());
|
||||
EXPECT_EQ("192.0.2.15", lastAddrInPrefix(addr1, 28).toText());
|
||||
EXPECT_EQ("192.0.2.7", lastAddrInPrefix(addr1, 29).toText());
|
||||
EXPECT_EQ("192.0.2.3", lastAddrInPrefix(addr1, 30).toText());
|
||||
|
||||
// that doesn't make much sense as /31 subnet consists of network address
|
||||
// and a broadcast address, with 0 usable addresses.
|
||||
EXPECT_EQ("192.0.2.1", lastAddrInPrefix(addr1, 31).toText());
|
||||
EXPECT_EQ("192.0.2.1", lastAddrInPrefix(addr1, 32).toText());
|
||||
|
||||
// Let's check extreme cases
|
||||
IOAddress anyAddr("0.0.0.0");
|
||||
EXPECT_EQ("127.255.255.255", lastAddrInPrefix(anyAddr, 1).toText());
|
||||
EXPECT_EQ("255.255.255.255", lastAddrInPrefix(anyAddr, 0).toText());
|
||||
EXPECT_EQ("0.0.0.0", lastAddrInPrefix(anyAddr, 32).toText());
|
||||
}
|
||||
|
||||
// This test checks if firstAddrInPrefix is able to handle IPv4 operations.
|
||||
TEST(AddrUtilitiesTest, firstAddrInPrefix4) {
|
||||
IOAddress addr1("192.223.2.255");
|
||||
|
||||
// Prefixes rounded to addresses are easy...
|
||||
EXPECT_EQ("192.0.0.0", firstAddrInPrefix(addr1, 8).toText());
|
||||
EXPECT_EQ("192.223.0.0", firstAddrInPrefix(addr1, 16).toText());
|
||||
EXPECT_EQ("192.223.2.0", firstAddrInPrefix(addr1, 24).toText());
|
||||
|
||||
// these are trickier
|
||||
EXPECT_EQ("192.223.2.128", firstAddrInPrefix(addr1, 25).toText());
|
||||
EXPECT_EQ("192.223.2.192", firstAddrInPrefix(addr1, 26).toText());
|
||||
EXPECT_EQ("192.223.2.224", firstAddrInPrefix(addr1, 27).toText());
|
||||
EXPECT_EQ("192.223.2.240", firstAddrInPrefix(addr1, 28).toText());
|
||||
EXPECT_EQ("192.223.2.248", firstAddrInPrefix(addr1, 29).toText());
|
||||
EXPECT_EQ("192.223.2.252", firstAddrInPrefix(addr1, 30).toText());
|
||||
|
||||
// that doesn't make much sense as /31 subnet consists of network address
|
||||
// and a broadcast address, with 0 usable addresses.
|
||||
EXPECT_EQ("192.223.2.254", firstAddrInPrefix(addr1, 31).toText());
|
||||
EXPECT_EQ("192.223.2.255", firstAddrInPrefix(addr1, 32).toText());
|
||||
|
||||
// Let's check extreme cases.
|
||||
IOAddress bcast("255.255.255.255");
|
||||
EXPECT_EQ("128.0.0.0", firstAddrInPrefix(bcast, 1).toText());
|
||||
EXPECT_EQ("0.0.0.0", firstAddrInPrefix(bcast, 0).toText());
|
||||
EXPECT_EQ("255.255.255.255", firstAddrInPrefix(bcast, 32).toText());
|
||||
|
||||
}
|
||||
|
||||
/// This test checks if lastAddrInPrefix properly supports IPv6 operations
|
||||
TEST(AddrUtilitiesTest, lastAddrInPrefix6) {
|
||||
IOAddress addr1("2001:db8:1:1234:5678:abcd:1234:beef");
|
||||
|
||||
// Prefixes rounded to nibbles are easy...
|
||||
@@ -63,7 +123,8 @@ TEST(Pool6Test, lastAddrInPrefix) {
|
||||
EXPECT_EQ("::", lastAddrInPrefix(anyAddr, 128).toText());
|
||||
}
|
||||
|
||||
TEST(Pool6Test, firstAddrInPrefix) {
|
||||
/// This test checks if firstAddrInPrefix properly supports IPv6 operations
|
||||
TEST(AddrUtilitiesTest, firstAddrInPrefix6) {
|
||||
IOAddress addr1("2001:db8:1:1234:5678:1234:abcd:beef");
|
||||
|
||||
// Prefixes rounded to nibbles are easy...
|
||||
|
@@ -32,6 +32,38 @@ using boost::scoped_ptr;
|
||||
|
||||
namespace {
|
||||
|
||||
// This test verifies if the configuration manager is able to hold and return
|
||||
// valid leases
|
||||
TEST(CfgMgrTest, subnet4) {
|
||||
CfgMgr& cfg_mgr = CfgMgr::instance();
|
||||
|
||||
ASSERT_TRUE(&cfg_mgr != 0);
|
||||
|
||||
Subnet4Ptr subnet1(new Subnet4(IOAddress("192.0.2.0"), 26, 1, 2, 3));
|
||||
Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.64"), 26, 1, 2, 3));
|
||||
Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.2.128"), 26, 1, 2, 3));
|
||||
|
||||
// there shouldn't be any subnet configured at this stage
|
||||
EXPECT_EQ( Subnet4Ptr(), cfg_mgr.getSubnet4(IOAddress("192.0.2.0")));
|
||||
|
||||
cfg_mgr.addSubnet4(subnet1);
|
||||
|
||||
// Now we have only one subnet, any request will be served from it
|
||||
EXPECT_EQ(subnet1, cfg_mgr.getSubnet4(IOAddress("192.0.2.63")));
|
||||
|
||||
// Now we add more subnets and check that both old and new subnets
|
||||
// are accessible.
|
||||
cfg_mgr.addSubnet4(subnet2);
|
||||
cfg_mgr.addSubnet4(subnet3);
|
||||
|
||||
EXPECT_EQ(subnet3, cfg_mgr.getSubnet4(IOAddress("192.0.2.191")));
|
||||
EXPECT_EQ(subnet1, cfg_mgr.getSubnet4(IOAddress("192.0.2.15")));
|
||||
EXPECT_EQ(subnet2, cfg_mgr.getSubnet4(IOAddress("192.0.2.85")));
|
||||
|
||||
// Try to find an address that does not belong to any subnet
|
||||
EXPECT_EQ(Subnet4Ptr(), cfg_mgr.getSubnet4(IOAddress("192.0.2.192")));
|
||||
}
|
||||
|
||||
// This test verifies if the configuration manager is able to hold and return
|
||||
// valid leases
|
||||
TEST(CfgMgrTest, subnet6) {
|
||||
@@ -58,6 +90,10 @@ TEST(CfgMgrTest, subnet6) {
|
||||
EXPECT_EQ(subnet2, cfg_mgr.getSubnet6(IOAddress("3000::dead:beef")));
|
||||
EXPECT_EQ(Subnet6Ptr(), cfg_mgr.getSubnet6(IOAddress("5000::1")));
|
||||
|
||||
cfg_mgr.deleteSubnets6();
|
||||
EXPECT_EQ(Subnet6Ptr(), cfg_mgr.getSubnet6(IOAddress("200::123")));
|
||||
EXPECT_EQ(Subnet6Ptr(), cfg_mgr.getSubnet6(IOAddress("3000::123")));
|
||||
EXPECT_EQ(Subnet6Ptr(), cfg_mgr.getSubnet6(IOAddress("4000::123")));
|
||||
}
|
||||
|
||||
} // end of anonymous namespace
|
||||
|
169
src/lib/dhcp/tests/duid_unittest.cc
Normal file
169
src/lib/dhcp/tests/duid_unittest.cc
Normal file
@@ -0,0 +1,169 @@
|
||||
// Copyright (C) 2011 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.
|
||||
|
||||
#include <config.h>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <arpa/inet.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <exceptions/exceptions.h>
|
||||
#include <boost/scoped_ptr.hpp>
|
||||
#include <asiolink/io_address.h>
|
||||
#include <dhcp/duid.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace isc;
|
||||
using namespace isc::dhcp;
|
||||
using namespace isc::asiolink;
|
||||
|
||||
// don't import the entire boost namespace. It will unexpectedly hide uint8_t
|
||||
// for some systems.
|
||||
using boost::scoped_ptr;
|
||||
|
||||
namespace {
|
||||
|
||||
// This test verifies if the constructors are working as expected
|
||||
// and process passed parameters.
|
||||
TEST(DuidTest, constructor) {
|
||||
|
||||
uint8_t data1[] = {0, 1, 2, 3, 4, 5, 6};
|
||||
|
||||
vector<uint8_t> data2(data1, data1 + sizeof(data1));
|
||||
|
||||
scoped_ptr<DUID> duid1(new DUID(data1, sizeof(data1)));
|
||||
scoped_ptr<DUID> duid2(new DUID(data2));
|
||||
|
||||
vector<uint8_t> vecdata = duid1->getDuid();
|
||||
EXPECT_TRUE(data2 == vecdata);
|
||||
EXPECT_EQ(DUID::DUID_LLT, duid1->getType());
|
||||
|
||||
vecdata = duid2->getDuid();
|
||||
EXPECT_TRUE(data2 == vecdata);
|
||||
|
||||
EXPECT_EQ(DUID::DUID_LLT, duid2->getType());
|
||||
}
|
||||
|
||||
// This test verifies if DUID size restrictions are implemented
|
||||
// properly.
|
||||
TEST(DuidTest, size) {
|
||||
const int MAX_DUID_SIZE = 128;
|
||||
uint8_t data[MAX_DUID_SIZE + 1];
|
||||
vector<uint8_t> data2;
|
||||
for (uint8_t i = 0; i < MAX_DUID_SIZE + 1; ++i) {
|
||||
data[i] = i;
|
||||
if (i < MAX_DUID_SIZE)
|
||||
data2.push_back(i);
|
||||
}
|
||||
ASSERT_EQ(data2.size(), MAX_DUID_SIZE);
|
||||
|
||||
scoped_ptr<DUID> duidmaxsize1(new DUID(data, MAX_DUID_SIZE));
|
||||
scoped_ptr<DUID> duidmaxsize2(new DUID(data2));
|
||||
|
||||
EXPECT_THROW(
|
||||
scoped_ptr<DUID> toolarge1(new DUID(data, MAX_DUID_SIZE + 1)),
|
||||
OutOfRange);
|
||||
|
||||
// that's one too much
|
||||
data2.push_back(128);
|
||||
|
||||
EXPECT_THROW(
|
||||
scoped_ptr<DUID> toolarge2(new DUID(data2)),
|
||||
OutOfRange);
|
||||
}
|
||||
|
||||
// This test verifies if the implementation supports all defined
|
||||
// DUID types.
|
||||
TEST(DuidTest, getType) {
|
||||
uint8_t llt[] = {0, 1, 2, 3, 4, 5, 6};
|
||||
uint8_t en[] = {0, 2, 2, 3, 4, 5, 6};
|
||||
uint8_t ll[] = {0, 3, 2, 3, 4, 5, 6};
|
||||
uint8_t uuid[] = {0, 4, 2, 3, 4, 5, 6};
|
||||
uint8_t invalid[] = {0,55, 2, 3, 4, 5, 6};
|
||||
|
||||
scoped_ptr<DUID> duid_llt(new DUID(llt, sizeof(llt)));
|
||||
scoped_ptr<DUID> duid_en(new DUID(en, sizeof(en)));
|
||||
scoped_ptr<DUID> duid_ll(new DUID(ll, sizeof(ll)));
|
||||
scoped_ptr<DUID> duid_uuid(new DUID(uuid, sizeof(uuid)));
|
||||
scoped_ptr<DUID> duid_invalid(new DUID(invalid, sizeof(invalid)));
|
||||
|
||||
EXPECT_EQ(DUID::DUID_LLT, duid_llt->getType());
|
||||
EXPECT_EQ(DUID::DUID_EN, duid_en->getType());
|
||||
EXPECT_EQ(DUID::DUID_LL, duid_ll->getType());
|
||||
EXPECT_EQ(DUID::DUID_UUID, duid_uuid->getType());
|
||||
EXPECT_EQ(DUID::DUID_UNKNOWN, duid_invalid->getType());
|
||||
}
|
||||
|
||||
// This test checks if the comparison operators are sane.
|
||||
TEST(DuidTest, operators) {
|
||||
uint8_t data1[] = {0, 1, 2, 3, 4, 5, 6};
|
||||
uint8_t data2[] = {0, 1, 2, 3, 4};
|
||||
uint8_t data3[] = {0, 1, 2, 3, 4, 5, 7}; // last digit different
|
||||
uint8_t data4[] = {0, 1, 2, 3, 4, 5, 6}; // the same as 1
|
||||
|
||||
scoped_ptr<DUID> duid1(new DUID(data1, sizeof(data1)));
|
||||
scoped_ptr<DUID> duid2(new DUID(data2, sizeof(data2)));
|
||||
scoped_ptr<DUID> duid3(new DUID(data3, sizeof(data3)));
|
||||
scoped_ptr<DUID> duid4(new DUID(data4, sizeof(data4)));
|
||||
|
||||
EXPECT_TRUE(*duid1 == *duid4);
|
||||
EXPECT_FALSE(*duid1 == *duid2);
|
||||
EXPECT_FALSE(*duid1 == *duid3);
|
||||
|
||||
EXPECT_FALSE(*duid1 != *duid4);
|
||||
EXPECT_TRUE(*duid1 != *duid2);
|
||||
EXPECT_TRUE(*duid1 != *duid3);
|
||||
}
|
||||
|
||||
// This test verifies if the ClientId constructors are working properly
|
||||
// and passed parameters are used
|
||||
TEST(ClientIdTest, constructor) {
|
||||
IOAddress addr2("192.0.2.1");
|
||||
IOAddress addr3("2001:db8:1::1");
|
||||
|
||||
uint8_t data1[] = {0, 1, 2, 3, 4, 5, 6};
|
||||
vector<uint8_t> data2(data1, data1 + sizeof(data1));
|
||||
|
||||
// checks for C-style construtor (uint8_t * + len)
|
||||
scoped_ptr<ClientId> id1(new ClientId(data1, sizeof(data1)));
|
||||
vector<uint8_t> vecdata = id1->getClientId();
|
||||
EXPECT_TRUE(data2 == vecdata);
|
||||
|
||||
// checks for vector-based constructor
|
||||
scoped_ptr<ClientId> id2(new ClientId(data2));
|
||||
vecdata = id2->getClientId();
|
||||
EXPECT_TRUE(data2 == vecdata);
|
||||
}
|
||||
|
||||
// This test checks if the comparison operators are sane.
|
||||
TEST(ClientIdTest, operators) {
|
||||
uint8_t data1[] = {0, 1, 2, 3, 4, 5, 6};
|
||||
uint8_t data2[] = {0, 1, 2, 3, 4};
|
||||
uint8_t data3[] = {0, 1, 2, 3, 4, 5, 7}; // last digit different
|
||||
uint8_t data4[] = {0, 1, 2, 3, 4, 5, 6}; // the same as 1
|
||||
|
||||
scoped_ptr<ClientId> id1(new ClientId(data1, sizeof(data1)));
|
||||
scoped_ptr<ClientId> id2(new ClientId(data2, sizeof(data2)));
|
||||
scoped_ptr<ClientId> id3(new ClientId(data3, sizeof(data3)));
|
||||
scoped_ptr<ClientId> id4(new ClientId(data4, sizeof(data4)));
|
||||
|
||||
EXPECT_TRUE(*id1 == *id4);
|
||||
EXPECT_FALSE(*id1 == *id2);
|
||||
EXPECT_FALSE(*id1 == *id3);
|
||||
|
||||
EXPECT_FALSE(*id1 != *id4);
|
||||
EXPECT_TRUE(*id1 != *id2);
|
||||
EXPECT_TRUE(*id1 != *id3);
|
||||
}
|
||||
|
||||
} // end of anonymous namespace
|
296
src/lib/dhcp/tests/lease_mgr_unittest.cc
Normal file
296
src/lib/dhcp/tests/lease_mgr_unittest.cc
Normal file
@@ -0,0 +1,296 @@
|
||||
// Copyright (C) 2011-2012 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.
|
||||
|
||||
#include <config.h>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <asiolink/io_address.h>
|
||||
#include <dhcp/lease_mgr.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace isc;
|
||||
using namespace isc::asiolink;
|
||||
using namespace isc::dhcp;
|
||||
|
||||
// This is a concrete implementation of a Lease database.
|
||||
// It does not do anything useful now, and is used for abstract LeaseMgr
|
||||
// class testing. It may later evolve into more useful backend if the
|
||||
// need arises. We can reuse code from memfile benchmark. See code in
|
||||
// tests/tools/dhcp-ubench/memfile_bench.{cc|h}
|
||||
class Memfile_LeaseMgr : public LeaseMgr {
|
||||
public:
|
||||
|
||||
/// @brief The sole lease manager constructor
|
||||
///
|
||||
/// dbconfig is a generic way of passing parameters. Parameters
|
||||
/// are passed in the "name=value" format, separated by spaces.
|
||||
/// Values may be enclosed in double quotes, if needed.
|
||||
///
|
||||
/// @param dbconfig database configuration
|
||||
Memfile_LeaseMgr(const std::string& dbconfig);
|
||||
|
||||
/// @brief Destructor (closes file)
|
||||
virtual ~Memfile_LeaseMgr();
|
||||
|
||||
/// @brief Adds an IPv4 lease.
|
||||
///
|
||||
/// @param lease lease to be added
|
||||
virtual bool addLease(Lease4Ptr lease);
|
||||
|
||||
/// @brief Adds an IPv6 lease.
|
||||
///
|
||||
/// @param lease lease to be added
|
||||
virtual bool addLease(Lease6Ptr lease);
|
||||
|
||||
/// @brief Returns existing IPv4 lease for specified IPv4 address.
|
||||
///
|
||||
/// @param addr address of the searched lease
|
||||
///
|
||||
/// @return a collection of leases
|
||||
virtual Lease4Ptr getLease4(isc::asiolink::IOAddress addr) const;
|
||||
|
||||
/// @brief Returns existing IPv4 lease for specific address and subnet
|
||||
/// @param addr address of the searched lease
|
||||
/// @param subnet_id ID of the subnet the lease must belong to
|
||||
///
|
||||
/// @return smart pointer to the lease (or NULL if a lease is not found)
|
||||
virtual Lease4Ptr getLease4(isc::asiolink::IOAddress addr,
|
||||
SubnetID subnet_id) const;
|
||||
|
||||
/// @brief Returns existing IPv4 leases for specified hardware address.
|
||||
///
|
||||
/// Although in the usual case there will be only one lease, for mobile
|
||||
/// clients or clients with multiple static/fixed/reserved leases there
|
||||
/// can be more than one. Thus return type is a container, not a single
|
||||
/// pointer.
|
||||
///
|
||||
/// @param hwaddr hardware address of the client
|
||||
///
|
||||
/// @return lease collection
|
||||
virtual Lease4Collection getLease4(const HWAddr& hwaddr) const;
|
||||
|
||||
/// @brief Returns existing IPv4 leases for specified hardware address
|
||||
/// and a subnet
|
||||
///
|
||||
/// There can be at most one lease for a given HW address in a single
|
||||
/// pool, so this method with either return a single lease or NULL.
|
||||
///
|
||||
/// @param hwaddr hardware address of the client
|
||||
/// @param subnet_id identifier of the subnet that lease must belong to
|
||||
///
|
||||
/// @return a pointer to the lease (or NULL if a lease is not found)
|
||||
virtual Lease4Ptr getLease4(const HWAddr& hwaddr,
|
||||
SubnetID subnet_id) const;
|
||||
|
||||
/// @brief Returns existing IPv4 lease for specified client-id
|
||||
///
|
||||
/// @param clientid client identifier
|
||||
virtual Lease4Collection getLease4(const ClientId& clientid) const;
|
||||
|
||||
/// @brief Returns existing IPv4 lease for specified client-id
|
||||
///
|
||||
/// There can be at most one lease for a given HW address in a single
|
||||
/// pool, so this method with either return a single lease or NULL.
|
||||
///
|
||||
/// @param clientid client identifier
|
||||
/// @param subnet_id identifier of the subnet that lease must belong to
|
||||
///
|
||||
/// @return a pointer to the lease (or NULL if a lease is not found)
|
||||
virtual Lease4Ptr getLease4(const ClientId& clientid,
|
||||
SubnetID subnet_id) const;
|
||||
|
||||
/// @brief Returns existing IPv6 lease for a given IPv6 address.
|
||||
///
|
||||
/// @param addr address of the searched lease
|
||||
///
|
||||
/// @return smart pointer to the lease (or NULL if a lease is not found)
|
||||
Lease6Ptr getLease6(isc::asiolink::IOAddress addr) const;
|
||||
|
||||
/// @brief Returns existing IPv6 lease for a given DUID+IA combination
|
||||
///
|
||||
/// @param duid client DUID
|
||||
/// @param iaid IA identifier
|
||||
///
|
||||
/// @return collection of IPv6 leases
|
||||
Lease6Collection getLease6(const DUID& duid, uint32_t iaid) const;
|
||||
|
||||
/// @brief Returns existing IPv6 lease for a given DUID+IA combination
|
||||
///
|
||||
/// @param duid client DUID
|
||||
/// @param iaid IA identifier
|
||||
/// @param subnet_id identifier of the subnet the lease must belong to
|
||||
///
|
||||
/// @return smart pointer to the lease (or NULL if a lease is not found)
|
||||
Lease6Ptr getLease6(const DUID& duid, uint32_t iaid, SubnetID subnet_id) const;
|
||||
|
||||
/// @brief Updates IPv4 lease.
|
||||
///
|
||||
/// @param lease4 The lease to be updated.
|
||||
///
|
||||
/// If no such lease is present, an exception will be thrown.
|
||||
void updateLease4(Lease4Ptr lease4);
|
||||
|
||||
/// @brief Updates IPv4 lease.
|
||||
///
|
||||
/// @param lease4 The lease to be updated.
|
||||
///
|
||||
/// If no such lease is present, an exception will be thrown.
|
||||
void updateLease6(Lease6Ptr lease6);
|
||||
|
||||
/// @brief Deletes a lease.
|
||||
///
|
||||
/// @param addr IPv4 address of the lease to be deleted.
|
||||
///
|
||||
/// @return true if deletion was successful, false if no such lease exists
|
||||
bool deleteLease4(uint32_t addr);
|
||||
|
||||
/// @brief Deletes a lease.
|
||||
///
|
||||
/// @param addr IPv4 address of the lease to be deleted.
|
||||
///
|
||||
/// @return true if deletion was successful, false if no such lease exists
|
||||
bool deleteLease6(isc::asiolink::IOAddress addr);
|
||||
|
||||
/// @brief Returns backend name.
|
||||
///
|
||||
/// Each backend have specific name, e.g. "mysql" or "sqlite".
|
||||
std::string getName() const { return "memfile"; }
|
||||
|
||||
/// @brief Returns description of the backend.
|
||||
///
|
||||
/// This description may be multiline text that describes the backend.
|
||||
std::string getDescription() const;
|
||||
|
||||
/// @brief Returns backend version.
|
||||
std::string getVersion() const { return "test-version"; }
|
||||
|
||||
using LeaseMgr::getParameter;
|
||||
|
||||
protected:
|
||||
|
||||
|
||||
};
|
||||
|
||||
Memfile_LeaseMgr::Memfile_LeaseMgr(const std::string& dbconfig)
|
||||
: LeaseMgr(dbconfig) {
|
||||
}
|
||||
|
||||
Memfile_LeaseMgr::~Memfile_LeaseMgr() {
|
||||
}
|
||||
|
||||
bool Memfile_LeaseMgr::addLease(boost::shared_ptr<isc::dhcp::Lease4>) {
|
||||
return (false);
|
||||
}
|
||||
|
||||
bool Memfile_LeaseMgr::addLease(boost::shared_ptr<isc::dhcp::Lease6>) {
|
||||
return (false);
|
||||
}
|
||||
|
||||
Lease4Ptr Memfile_LeaseMgr::getLease4(isc::asiolink::IOAddress) const {
|
||||
return (Lease4Ptr());
|
||||
}
|
||||
|
||||
Lease4Collection Memfile_LeaseMgr::getLease4(const HWAddr& ) const {
|
||||
return (Lease4Collection());
|
||||
}
|
||||
|
||||
Lease4Ptr Memfile_LeaseMgr::getLease4(isc::asiolink::IOAddress ,
|
||||
SubnetID) const {
|
||||
return (Lease4Ptr());
|
||||
}
|
||||
|
||||
Lease4Ptr Memfile_LeaseMgr::getLease4(const HWAddr&,
|
||||
SubnetID) const {
|
||||
return (Lease4Ptr());
|
||||
}
|
||||
|
||||
|
||||
Lease4Ptr Memfile_LeaseMgr::getLease4(const ClientId&,
|
||||
SubnetID) const {
|
||||
return (Lease4Ptr());
|
||||
}
|
||||
|
||||
Lease4Collection Memfile_LeaseMgr::getLease4(const ClientId& ) const {
|
||||
return (Lease4Collection());
|
||||
}
|
||||
|
||||
Lease6Ptr Memfile_LeaseMgr::getLease6(isc::asiolink::IOAddress) const {
|
||||
return (Lease6Ptr());
|
||||
}
|
||||
|
||||
Lease6Collection Memfile_LeaseMgr::getLease6(const DUID& , uint32_t ) const {
|
||||
return (Lease6Collection());
|
||||
}
|
||||
|
||||
Lease6Ptr Memfile_LeaseMgr::getLease6(const DUID&, uint32_t,
|
||||
SubnetID) const {
|
||||
return (Lease6Ptr());
|
||||
}
|
||||
|
||||
void Memfile_LeaseMgr::updateLease4(Lease4Ptr ) {
|
||||
}
|
||||
|
||||
void Memfile_LeaseMgr::updateLease6(Lease6Ptr ) {
|
||||
|
||||
}
|
||||
|
||||
bool Memfile_LeaseMgr::deleteLease4(uint32_t ) {
|
||||
return (false);
|
||||
}
|
||||
|
||||
bool Memfile_LeaseMgr::deleteLease6(isc::asiolink::IOAddress ) {
|
||||
return (false);
|
||||
}
|
||||
|
||||
std::string Memfile_LeaseMgr::getDescription() const {
|
||||
return (string("This is a dummy memfile backend implementation.\n"
|
||||
"It does not offer any useful lease management and its only\n"
|
||||
"purpose is to test abstract lease manager API."));
|
||||
}
|
||||
|
||||
namespace {
|
||||
// empty class for now, but may be extended once Addr6 becomes bigger
|
||||
class LeaseMgrTest : public ::testing::Test {
|
||||
public:
|
||||
LeaseMgrTest() {
|
||||
}
|
||||
};
|
||||
|
||||
// This test checks if the LeaseMgr can be instantiated and that it
|
||||
// parses parameters string properly.
|
||||
TEST_F(LeaseMgrTest, constructor) {
|
||||
|
||||
// should not throw any exceptions here
|
||||
Memfile_LeaseMgr * leaseMgr = new Memfile_LeaseMgr("");
|
||||
delete leaseMgr;
|
||||
|
||||
leaseMgr = new Memfile_LeaseMgr("param1=value1 param2=value2");
|
||||
|
||||
EXPECT_EQ("value1", leaseMgr->getParameter("param1"));
|
||||
EXPECT_EQ("value2", leaseMgr->getParameter("param2"));
|
||||
EXPECT_THROW(leaseMgr->getParameter("param3"), BadValue);
|
||||
|
||||
delete leaseMgr;
|
||||
}
|
||||
|
||||
// There's no point in calling any other methods in LeaseMgr, as they
|
||||
// are purely virtual, so we would only call Memfile_LeaseMgr methods.
|
||||
// Those methods are just stubs that does not return anything.
|
||||
// It seems likely that we will need to extend the memfile code for
|
||||
// allocation engine tests, so we may implement tests that call
|
||||
// Memfile_LeaseMgr methods then.
|
||||
|
||||
}; // end of anonymous namespace
|
@@ -27,6 +27,79 @@ using namespace isc::asiolink;
|
||||
|
||||
namespace {
|
||||
|
||||
TEST(Pool4Test, constructor_first_last) {
|
||||
|
||||
// let's construct 192.0.2.1-192.0.2.255 pool
|
||||
Pool4 pool1(IOAddress("192.0.2.1"), IOAddress("192.0.2.255"));
|
||||
|
||||
EXPECT_EQ(IOAddress("192.0.2.1"), pool1.getFirstAddress());
|
||||
EXPECT_EQ(IOAddress("192.0.2.255"), pool1.getLastAddress());
|
||||
|
||||
// This is Pool4, IPv6 addresses do not belong here
|
||||
EXPECT_THROW(Pool6(Pool6::TYPE_IA, IOAddress("2001:db8::1"),
|
||||
IOAddress("192.168.0.5")), BadValue);
|
||||
EXPECT_THROW(Pool6(Pool6::TYPE_IA, IOAddress("192.168.0.2"),
|
||||
IOAddress("2001:db8::1")), BadValue);
|
||||
|
||||
// Should throw. Range should be 192.0.2.1-192.0.2.2, not
|
||||
// the other way around.
|
||||
EXPECT_THROW(Pool6(Pool6::TYPE_IA, IOAddress("192.0.2.2"),
|
||||
IOAddress("192.0.2.1")), BadValue);
|
||||
}
|
||||
|
||||
TEST(Pool4Test, constructor_prefix_len) {
|
||||
|
||||
// let's construct 2001:db8:1::/96 pool
|
||||
Pool4 pool1(IOAddress("192.0.2.0"), 25);
|
||||
|
||||
EXPECT_EQ("192.0.2.0", pool1.getFirstAddress().toText());
|
||||
EXPECT_EQ("192.0.2.127", pool1.getLastAddress().toText());
|
||||
|
||||
// No such thing as /33 prefix
|
||||
EXPECT_THROW(Pool4(IOAddress("192.0.2.1"), 33), BadValue);
|
||||
|
||||
// /0 prefix does not make sense
|
||||
EXPECT_THROW(Pool4(IOAddress("192.0.2.0"), 0), BadValue);
|
||||
|
||||
// This is Pool6, IPv4 addresses do not belong here
|
||||
EXPECT_THROW(Pool4(IOAddress("2001:db8::1"), 20), BadValue);
|
||||
}
|
||||
|
||||
TEST(Pool4Test, in_range) {
|
||||
Pool4 pool1(IOAddress("192.0.2.10"), IOAddress("192.0.2.20"));
|
||||
|
||||
EXPECT_FALSE(pool1.inRange(IOAddress("192.0.2.0")));
|
||||
EXPECT_TRUE(pool1.inRange(IOAddress("192.0.2.10")));
|
||||
EXPECT_TRUE(pool1.inRange(IOAddress("192.0.2.17")));
|
||||
EXPECT_TRUE(pool1.inRange(IOAddress("192.0.2.20")));
|
||||
EXPECT_FALSE(pool1.inRange(IOAddress("192.0.2.21")));
|
||||
EXPECT_FALSE(pool1.inRange(IOAddress("192.0.2.255")));
|
||||
EXPECT_FALSE(pool1.inRange(IOAddress("255.255.255.255")));
|
||||
EXPECT_FALSE(pool1.inRange(IOAddress("0.0.0.0")));
|
||||
}
|
||||
|
||||
// This test creates 100 pools and verifies that their IDs are unique.
|
||||
TEST(Pool4Test, unique_id) {
|
||||
|
||||
const int num_pools = 100;
|
||||
std::vector<Pool4Ptr> pools;
|
||||
|
||||
for (int i = 0; i < num_pools; ++i) {
|
||||
pools.push_back(Pool4Ptr(new Pool4(IOAddress("192.0.2.0"),
|
||||
IOAddress("192.0.2.255"))));
|
||||
}
|
||||
|
||||
for (int i = 0; i < num_pools; ++i) {
|
||||
for (int j = i + 1; j < num_pools; ++j) {
|
||||
if (pools[i]->getId() == pools[j]->getId()) {
|
||||
FAIL() << "Pool-ids must be unique";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
TEST(Pool6Test, constructor_first_last) {
|
||||
|
||||
// let's construct 2001:db8:1:: - 2001:db8:1::ffff:ffff:ffff:ffff pool
|
||||
|
@@ -29,6 +29,83 @@ using namespace isc::asiolink;
|
||||
|
||||
namespace {
|
||||
|
||||
TEST(Subnet4Test, constructor) {
|
||||
EXPECT_NO_THROW(Subnet4 subnet1(IOAddress("192.0.2.2"), 16,
|
||||
1, 2, 3));
|
||||
|
||||
EXPECT_THROW(Subnet4 subnet2(IOAddress("192.0.2.0"), 33, 1, 2, 3),
|
||||
BadValue); // invalid prefix length
|
||||
EXPECT_THROW(Subnet4 subnet3(IOAddress("2001:db8::1"), 24, 1, 2, 3),
|
||||
BadValue); // IPv6 addresses are not allowed in Subnet4
|
||||
}
|
||||
|
||||
TEST(Subnet4Test, in_range) {
|
||||
Subnet4 subnet(IOAddress("192.0.2.1"), 24, 1000, 2000, 3000);
|
||||
|
||||
EXPECT_EQ(1000, subnet.getT1());
|
||||
EXPECT_EQ(2000, subnet.getT2());
|
||||
EXPECT_EQ(3000, subnet.getValid());
|
||||
|
||||
EXPECT_FALSE(subnet.inRange(IOAddress("192.0.0.0")));
|
||||
EXPECT_TRUE(subnet.inRange(IOAddress("192.0.2.0")));
|
||||
EXPECT_TRUE(subnet.inRange(IOAddress("192.0.2.1")));
|
||||
EXPECT_TRUE(subnet.inRange(IOAddress("192.0.2.255")));
|
||||
EXPECT_FALSE(subnet.inRange(IOAddress("192.0.3.0")));
|
||||
EXPECT_FALSE(subnet.inRange(IOAddress("0.0.0.0")));
|
||||
EXPECT_FALSE(subnet.inRange(IOAddress("255.255.255.255")));
|
||||
}
|
||||
|
||||
TEST(Subnet4Test, Pool4InSubnet4) {
|
||||
|
||||
Subnet4Ptr subnet(new Subnet4(IOAddress("192.1.2.0"), 24, 1, 2, 3));
|
||||
|
||||
Pool4Ptr pool1(new Pool4(IOAddress("192.1.2.0"), 25));
|
||||
Pool4Ptr pool2(new Pool4(IOAddress("192.1.2.128"), 26));
|
||||
Pool4Ptr pool3(new Pool4(IOAddress("192.1.2.192"), 30));
|
||||
|
||||
subnet->addPool4(pool1);
|
||||
|
||||
// If there's only one pool, get that pool
|
||||
Pool4Ptr mypool = subnet->getPool4();
|
||||
EXPECT_EQ(mypool, pool1);
|
||||
|
||||
|
||||
subnet->addPool4(pool2);
|
||||
subnet->addPool4(pool3);
|
||||
|
||||
// If there are more than one pool and we didn't provide hint, we
|
||||
// should get the first pool
|
||||
mypool = subnet->getPool4();
|
||||
|
||||
EXPECT_EQ(mypool, pool1);
|
||||
|
||||
// If we provide a hint, we should get a pool that this hint belongs to
|
||||
mypool = subnet->getPool4(IOAddress("192.1.2.195"));
|
||||
|
||||
EXPECT_EQ(mypool, pool3);
|
||||
|
||||
}
|
||||
|
||||
TEST(Subnet4Test, Subnet4_Pool4_checks) {
|
||||
|
||||
Subnet4Ptr subnet(new Subnet4(IOAddress("192.0.2.0"), 8, 1, 2, 3));
|
||||
|
||||
// this one is in subnet
|
||||
Pool4Ptr pool1(new Pool4(IOAddress("192.255.0.0"), 16));
|
||||
subnet->addPool4(pool1);
|
||||
|
||||
// this one is larger than the subnet!
|
||||
Pool4Ptr pool2(new Pool4(IOAddress("193.0.0.0"), 24));
|
||||
|
||||
EXPECT_THROW(subnet->addPool4(pool2), BadValue);
|
||||
|
||||
// this one is totally out of blue
|
||||
Pool4Ptr pool3(new Pool4(IOAddress("1.2.3.4"), 16));
|
||||
EXPECT_THROW(subnet->addPool4(pool3), BadValue);
|
||||
}
|
||||
|
||||
// Tests for Subnet6
|
||||
|
||||
TEST(Subnet6Test, constructor) {
|
||||
|
||||
EXPECT_NO_THROW(Subnet6 subnet1(IOAddress("2001:db8:1::"), 64,
|
||||
@@ -48,7 +125,6 @@ TEST(Subnet6Test, in_range) {
|
||||
EXPECT_EQ(3000, subnet.getPreferred());
|
||||
EXPECT_EQ(4000, subnet.getValid());
|
||||
|
||||
|
||||
EXPECT_FALSE(subnet.inRange(IOAddress("2001:db8:0:ffff:ffff:ffff:ffff:ffff")));
|
||||
EXPECT_TRUE(subnet.inRange(IOAddress("2001:db8:1::0")));
|
||||
EXPECT_TRUE(subnet.inRange(IOAddress("2001:db8:1::1")));
|
||||
@@ -106,7 +182,9 @@ TEST(Subnet6Test, Subnet6_Pool6_checks) {
|
||||
Pool6Ptr pool3(new Pool6(Pool6::TYPE_IA, IOAddress("3000::"), 16));
|
||||
EXPECT_THROW(subnet->addPool6(pool3), BadValue);
|
||||
|
||||
|
||||
Pool6Ptr pool4(new Pool6(Pool6::TYPE_IA, IOAddress("4001:db8:1::"), 80));
|
||||
EXPECT_THROW(subnet->addPool6(pool4), BadValue);
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
@@ -12,6 +12,9 @@
|
||||
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
#ifndef TRIPLET_H
|
||||
#define TRIPLET_H
|
||||
|
||||
#include <exceptions/exceptions.h>
|
||||
|
||||
namespace isc {
|
||||
@@ -108,3 +111,5 @@ protected:
|
||||
|
||||
} // namespace isc::dhcp
|
||||
} // namespace isc
|
||||
|
||||
#endif // ifdef TRIPLET_H
|
||||
|
@@ -214,9 +214,14 @@ class Creator(Parser):
|
||||
socket.SOCK_STREAM)
|
||||
env = copy.deepcopy(os.environ)
|
||||
env['PATH'] = path
|
||||
# We explicitly set close_fs to True; it's False by default before
|
||||
# Python 3.2. If we don't close the remaining FDs, the copy of
|
||||
# 'local' will prevent the child process from terminating when
|
||||
# the parent process died abruptly.
|
||||
self.__process = subprocess.Popen(['b10-sockcreator'], env=env,
|
||||
stdin=remote.fileno(),
|
||||
stdout=remote2.fileno(),
|
||||
close_fds=True,
|
||||
preexec_fn=self.__preexec_work)
|
||||
remote.close()
|
||||
remote2.close()
|
||||
|
@@ -1,4 +1,5 @@
|
||||
SUBDIRS = . io unittests tests pyunittests python threads
|
||||
# InterprocessSyncFile has a dependency on isc::util::thread::Mutex
|
||||
SUBDIRS = io unittests threads . tests pyunittests python
|
||||
|
||||
AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
|
||||
AM_CPPFLAGS += -I$(top_srcdir)/src/lib/util -I$(top_builddir)/src/lib/util
|
||||
@@ -31,7 +32,8 @@ libb10_util_la_SOURCES += random/random_number_generator.h
|
||||
|
||||
EXTRA_DIST = python/pycppwrapper_util.h
|
||||
|
||||
libb10_util_la_LIBADD = $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
|
||||
libb10_util_la_LIBADD = $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
|
||||
libb10_util_la_LIBADD += $(top_builddir)/src/lib/util/threads/libb10-threads.la
|
||||
CLEANFILES = *.gcno *.gcda
|
||||
|
||||
libb10_util_includedir = $(includedir)/$(PACKAGE_NAME)/util
|
||||
|
@@ -14,6 +14,9 @@
|
||||
|
||||
#include "interprocess_sync_file.h"
|
||||
|
||||
#include <boost/weak_ptr.hpp>
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include <stdlib.h>
|
||||
@@ -23,9 +26,38 @@
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
using namespace isc::util::thread;
|
||||
|
||||
namespace isc {
|
||||
namespace util {
|
||||
|
||||
namespace { // unnamed namespace
|
||||
|
||||
typedef std::map<std::string, boost::weak_ptr<Mutex> > SyncMap;
|
||||
|
||||
Mutex sync_map_mutex;
|
||||
SyncMap sync_map;
|
||||
|
||||
} // end of unnamed namespace
|
||||
|
||||
InterprocessSyncFile::InterprocessSyncFile(const std::string& task_name) :
|
||||
InterprocessSync(task_name),
|
||||
fd_(-1)
|
||||
{
|
||||
Mutex::Locker locker(sync_map_mutex);
|
||||
|
||||
SyncMap::iterator it = sync_map.find(task_name);
|
||||
if (it != sync_map.end()) {
|
||||
mutex_ = it->second.lock();
|
||||
} else {
|
||||
mutex_.reset(new Mutex());
|
||||
sync_map[task_name] = mutex_;
|
||||
}
|
||||
|
||||
// Lock on sync_map_mutex is automatically unlocked during
|
||||
// destruction when basic block is exited.
|
||||
}
|
||||
|
||||
InterprocessSyncFile::~InterprocessSyncFile() {
|
||||
if (fd_ != -1) {
|
||||
// This will also release any applied locks.
|
||||
@@ -33,6 +65,23 @@ InterprocessSyncFile::~InterprocessSyncFile() {
|
||||
// The lockfile will continue to exist, and we must not delete
|
||||
// it.
|
||||
}
|
||||
|
||||
Mutex::Locker locker(sync_map_mutex);
|
||||
|
||||
// Unref the shared mutex.
|
||||
locker_.reset();
|
||||
mutex_.reset();
|
||||
|
||||
// Remove name from the map if it is unused anymore.
|
||||
SyncMap::iterator it = sync_map.find(task_name_);
|
||||
assert(it != sync_map.end());
|
||||
|
||||
if (it->second.expired()) {
|
||||
sync_map.erase(it);
|
||||
}
|
||||
|
||||
// Lock on sync_map_mutex is automatically unlocked during
|
||||
// destruction when basic block is exited.
|
||||
}
|
||||
|
||||
bool
|
||||
@@ -90,8 +139,13 @@ InterprocessSyncFile::lock() {
|
||||
return (true);
|
||||
}
|
||||
|
||||
// First grab the thread lock...
|
||||
LockerPtr locker(new Mutex::Locker(*mutex_));
|
||||
|
||||
// ... then the file lock.
|
||||
if (do_lock(F_SETLKW, F_WRLCK)) {
|
||||
is_locked_ = true;
|
||||
locker_ = locker;
|
||||
return (true);
|
||||
}
|
||||
|
||||
@@ -104,8 +158,18 @@ InterprocessSyncFile::tryLock() {
|
||||
return (true);
|
||||
}
|
||||
|
||||
// First grab the thread lock...
|
||||
LockerPtr locker;
|
||||
try {
|
||||
locker.reset(new Mutex::Locker(*mutex_, false));
|
||||
} catch (const Mutex::Locker::AlreadyLocked&) {
|
||||
return (false);
|
||||
}
|
||||
|
||||
// ... then the file lock.
|
||||
if (do_lock(F_SETLK, F_WRLCK)) {
|
||||
is_locked_ = true;
|
||||
locker_ = locker;
|
||||
return (true);
|
||||
}
|
||||
|
||||
@@ -118,12 +182,14 @@ InterprocessSyncFile::unlock() {
|
||||
return (true);
|
||||
}
|
||||
|
||||
if (do_lock(F_SETLKW, F_UNLCK)) {
|
||||
is_locked_ = false;
|
||||
return (true);
|
||||
// First release the file lock...
|
||||
if (do_lock(F_SETLKW, F_UNLCK) == 0) {
|
||||
return (false);
|
||||
}
|
||||
|
||||
return (false);
|
||||
locker_.reset();
|
||||
is_locked_ = false;
|
||||
return (true);
|
||||
}
|
||||
|
||||
} // namespace util
|
||||
|
@@ -16,8 +16,11 @@
|
||||
#define __INTERPROCESS_SYNC_FILE_H__
|
||||
|
||||
#include <util/interprocess_sync.h>
|
||||
#include <util/threads/lock.h>
|
||||
#include <exceptions/exceptions.h>
|
||||
|
||||
#include <boost/shared_ptr.hpp>
|
||||
|
||||
namespace isc {
|
||||
namespace util {
|
||||
|
||||
@@ -55,9 +58,7 @@ public:
|
||||
/// \param name Name of the synchronization task. This has to be
|
||||
/// identical among the various processes that need to be
|
||||
/// synchronized for the same task.
|
||||
InterprocessSyncFile(const std::string& task_name) :
|
||||
InterprocessSync(task_name), fd_(-1)
|
||||
{}
|
||||
InterprocessSyncFile(const std::string& task_name);
|
||||
|
||||
/// \brief Destructor
|
||||
virtual ~InterprocessSyncFile();
|
||||
@@ -83,6 +84,11 @@ private:
|
||||
bool do_lock(int cmd, short l_type);
|
||||
|
||||
int fd_; ///< The descriptor for the open file
|
||||
|
||||
typedef boost::shared_ptr<isc::util::thread::Mutex> MutexPtr;
|
||||
typedef boost::shared_ptr<isc::util::thread::Mutex::Locker> LockerPtr;
|
||||
MutexPtr mutex_; ///< A mutex for mutual exclusion among threads
|
||||
LockerPtr locker_; ///< A locker on mutex_
|
||||
};
|
||||
|
||||
} // namespace util
|
||||
|
@@ -42,13 +42,17 @@ class upgradable_mutex {
|
||||
template <typename T>
|
||||
class sharable_lock {
|
||||
public:
|
||||
sharable_lock(T) { }
|
||||
sharable_lock(T) {}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class scoped_lock {
|
||||
public:
|
||||
scoped_lock(T) { }
|
||||
scoped_lock(T) {}
|
||||
|
||||
// We need to define this explicitly. Some versions of clang++ would
|
||||
// complain about this otherwise. See Trac ticket #2340
|
||||
~scoped_lock() {}
|
||||
|
||||
void lock() {}
|
||||
void unlock() {}
|
||||
|
@@ -46,6 +46,7 @@ run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
|
||||
run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
|
||||
|
||||
run_unittests_LDADD = $(top_builddir)/src/lib/util/libb10-util.la
|
||||
run_unittests_LDADD += $(top_builddir)/src/lib/util/threads/libb10-threads.la
|
||||
run_unittests_LDADD += $(top_builddir)/src/lib/util/io/libb10-util-io.la
|
||||
run_unittests_LDADD += \
|
||||
$(top_builddir)/src/lib/util/unittests/libutil_unittests.la
|
||||
|
@@ -12,10 +12,15 @@
|
||||
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
#include "util/interprocess_sync_file.h"
|
||||
#include <util/interprocess_sync_file.h>
|
||||
#include <util/threads/thread.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <boost/bind.hpp>
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
using namespace isc::util::thread;
|
||||
using namespace std;
|
||||
|
||||
namespace isc {
|
||||
@@ -108,6 +113,46 @@ TEST(InterprocessSyncFileTest, TestLock) {
|
||||
EXPECT_EQ (0, unlink(TEST_DATA_TOPBUILDDIR "/test_lockfile"));
|
||||
}
|
||||
|
||||
void
|
||||
threadTest(bool* locked)
|
||||
{
|
||||
InterprocessSyncFile sync2("test");
|
||||
InterprocessSyncLocker locker2(sync2);
|
||||
|
||||
*locked = !locker2.tryLock();
|
||||
}
|
||||
|
||||
TEST(InterprocessSyncFileTest, TestLockThreaded) {
|
||||
InterprocessSyncFile sync("test");
|
||||
InterprocessSyncLocker locker(sync);
|
||||
|
||||
EXPECT_FALSE(locker.isLocked());
|
||||
EXPECT_TRUE(locker.lock());
|
||||
EXPECT_TRUE(locker.isLocked());
|
||||
|
||||
bool locked_in_other_thread = false;
|
||||
|
||||
// Here, we check that a lock has been taken by creating a new
|
||||
// thread and checking from the new thread that a lock exists. The
|
||||
// lock attempt must fail to pass our check.
|
||||
Thread thread(boost::bind(&threadTest, &locked_in_other_thread));
|
||||
thread.wait();
|
||||
|
||||
EXPECT_TRUE(locked_in_other_thread);
|
||||
|
||||
// Release the lock and try again. This time, the attempt must pass.
|
||||
|
||||
EXPECT_TRUE(locker.unlock());
|
||||
EXPECT_FALSE(locker.isLocked());
|
||||
|
||||
Thread thread2(boost::bind(&threadTest, &locked_in_other_thread));
|
||||
thread2.wait();
|
||||
|
||||
EXPECT_FALSE(locked_in_other_thread);
|
||||
|
||||
EXPECT_EQ (0, unlink(TEST_DATA_TOPBUILDDIR "/test_lockfile"));
|
||||
}
|
||||
|
||||
TEST(InterprocessSyncFileTest, TestMultipleFilesDirect) {
|
||||
InterprocessSyncFile sync("test1");
|
||||
InterprocessSyncLocker locker(sync);
|
||||
|
@@ -119,6 +119,19 @@ Mutex::lock() {
|
||||
++impl_->locked_count; // Only in debug mode
|
||||
}
|
||||
|
||||
bool
|
||||
Mutex::tryLock() {
|
||||
assert(impl_ != NULL);
|
||||
const int result = pthread_mutex_trylock(&impl_->mutex);
|
||||
if (result == EBUSY) {
|
||||
return (false);
|
||||
} else if (result != 0) {
|
||||
isc_throw(isc::InvalidOperation, std::strerror(result));
|
||||
}
|
||||
++impl_->locked_count; // Only in debug mode
|
||||
return (true);
|
||||
}
|
||||
|
||||
void
|
||||
Mutex::unlock() {
|
||||
assert(impl_ != NULL);
|
||||
|
@@ -15,6 +15,7 @@
|
||||
#ifndef B10_THREAD_LOCK_H
|
||||
#define B10_THREAD_LOCK_H
|
||||
|
||||
#include <exceptions/exceptions.h>
|
||||
#include <boost/noncopyable.hpp>
|
||||
|
||||
#include <cstdlib> // for NULL.
|
||||
@@ -76,21 +77,39 @@ public:
|
||||
/// of function no matter by what means.
|
||||
class Locker : public boost::noncopyable {
|
||||
public:
|
||||
/// \brief Exception thrown when the mutex is already locked and
|
||||
/// a non-blocking locker is attempted around it.
|
||||
struct AlreadyLocked : public isc::InvalidParameter {
|
||||
AlreadyLocked(const char* file, size_t line, const char* what) :
|
||||
isc::InvalidParameter(file, line, what)
|
||||
{}
|
||||
};
|
||||
|
||||
/// \brief Constructor.
|
||||
///
|
||||
/// Locks the mutex. May block for extended period of time.
|
||||
/// Locks the mutex. May block for extended period of time if
|
||||
/// \c block is true.
|
||||
///
|
||||
/// \throw isc::InvalidOperation when OS reports error. This usually
|
||||
/// means an attempt to use the mutex in a wrong way (locking
|
||||
/// a mutex second time from the same thread, for example).
|
||||
Locker(Mutex& mutex) :
|
||||
/// \throw AlreadyLocked if \c block is false and the mutex is
|
||||
/// already locked.
|
||||
Locker(Mutex& mutex, bool block = true) :
|
||||
mutex_(NULL)
|
||||
{
|
||||
// Set the mutex_ after we acquire the lock. This is because of
|
||||
// exception safety. If lock() throws, it didn't work, so we must
|
||||
// not unlock when we are destroyed. In such case, mutex_ is
|
||||
// NULL and checked in the destructor.
|
||||
mutex.lock();
|
||||
if (block) {
|
||||
mutex.lock();
|
||||
} else {
|
||||
if (!mutex.tryLock()) {
|
||||
isc_throw(AlreadyLocked, "The mutex is already locked");
|
||||
}
|
||||
}
|
||||
|
||||
mutex_ = &mutex;
|
||||
}
|
||||
|
||||
@@ -105,6 +124,7 @@ public:
|
||||
private:
|
||||
Mutex* mutex_;
|
||||
};
|
||||
|
||||
/// \brief If the mutex is currently locked
|
||||
///
|
||||
/// This is debug aiding method only. And it might be unavailable in
|
||||
@@ -113,11 +133,36 @@ public:
|
||||
///
|
||||
/// \todo Disable in non-debug build
|
||||
bool locked() const;
|
||||
|
||||
private:
|
||||
/// \brief Lock the mutex
|
||||
///
|
||||
/// This method blocks until the mutex can be locked.
|
||||
///
|
||||
/// Please consider not using this method directly and instead using
|
||||
/// a Mutex::Locker object instead.
|
||||
void lock();
|
||||
|
||||
/// \brief Try to lock the mutex
|
||||
///
|
||||
/// This method doesn't block and returns immediately with a status
|
||||
/// on whether the lock operation was successful.
|
||||
///
|
||||
/// Please consider not using this method directly and instead using
|
||||
/// a Mutex::Locker object instead.
|
||||
///
|
||||
/// \return true if the lock was successful, false otherwise.
|
||||
bool tryLock();
|
||||
|
||||
/// \brief Unlock the mutex
|
||||
///
|
||||
/// Please consider not using this method directly and instead using
|
||||
/// a Mutex::Locker object instead.
|
||||
void unlock();
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
Impl* impl_;
|
||||
void lock();
|
||||
void unlock();
|
||||
};
|
||||
|
||||
|
||||
|
@@ -37,6 +37,44 @@ TEST(MutexTest, lockMultiple) {
|
||||
Mutex::Locker l2(mutex); // Attempt to lock again.
|
||||
}, isc::InvalidOperation);
|
||||
EXPECT_TRUE(mutex.locked()); // Debug-only build
|
||||
|
||||
// block=true explicitly.
|
||||
Mutex mutex2;
|
||||
EXPECT_FALSE(mutex2.locked()); // Debug-only build
|
||||
Mutex::Locker l12(mutex2, true);
|
||||
EXPECT_TRUE(mutex2.locked()); // Debug-only build
|
||||
}
|
||||
|
||||
void
|
||||
testThread(Mutex* mutex)
|
||||
{
|
||||
// block=false (tryLock). This should not block indefinitely, but
|
||||
// throw AlreadyLocked. If block were true, this would block
|
||||
// indefinitely here.
|
||||
EXPECT_THROW({
|
||||
Mutex::Locker l3(*mutex, false);
|
||||
}, Mutex::Locker::AlreadyLocked);
|
||||
|
||||
EXPECT_TRUE(mutex->locked()); // Debug-only build
|
||||
}
|
||||
|
||||
// Test the non-blocking variant using a second thread.
|
||||
TEST(MutexTest, lockNonBlocking) {
|
||||
// block=false (tryLock).
|
||||
Mutex mutex;
|
||||
Mutex::Locker l1(mutex, false);
|
||||
EXPECT_TRUE(mutex.locked()); // Debug-only build
|
||||
|
||||
// First, try another locker from the same thread.
|
||||
EXPECT_THROW({
|
||||
Mutex::Locker l2(mutex, false);
|
||||
}, Mutex::Locker::AlreadyLocked);
|
||||
|
||||
EXPECT_TRUE(mutex.locked()); // Debug-only build
|
||||
|
||||
// Now try another locker from a different thread.
|
||||
Thread thread(boost::bind(&testThread, &mutex));
|
||||
thread.wait();
|
||||
}
|
||||
|
||||
// Destroying a locked mutex is a bad idea as well
|
||||
|
@@ -20,8 +20,7 @@ if HAVE_GTEST
|
||||
libutil_unittests_la_CPPFLAGS += $(GTEST_INCLUDES)
|
||||
endif
|
||||
|
||||
libutil_unittests_la_LIBADD = $(top_builddir)/src/lib/util/libb10-util.la
|
||||
libutil_unittests_la_LIBADD += $(top_builddir)/src/lib/util/io/libb10-util-io.la
|
||||
libutil_unittests_la_LIBADD = $(top_builddir)/src/lib/util/io/libb10-util-io.la
|
||||
libutil_unittests_la_LIBADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
|
||||
|
||||
CLEANFILES = *.gcno *.gcda
|
||||
|
Reference in New Issue
Block a user