Skip to content
GitLab
Projects
Groups
Snippets
Help
Loading...
Help
What's new
7
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Open sidebar
ISC Open Source Projects
Kea
Commits
2bf854c0
Commit
2bf854c0
authored
Oct 11, 2019
by
Francis Dupont
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
[219-allow-an-option-value-to-be-set-from-an-expression] Addressed comments
parent
3a1cec32
Changes
5
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
105 additions
and
112 deletions
+105
-112
doc/sphinx/arm/hooks.rst
doc/sphinx/arm/hooks.rst
+29
-35
src/hooks/dhcp/flex_option/flex_option.cc
src/hooks/dhcp/flex_option/flex_option.cc
+56
-74
src/hooks/dhcp/flex_option/flex_option.dox
src/hooks/dhcp/flex_option/flex_option.dox
+3
-2
src/hooks/dhcp/flex_option/flex_option.h
src/hooks/dhcp/flex_option/flex_option.h
+14
-0
src/hooks/dhcp/flex_option/tests/flex_option_unittests.cc
src/hooks/dhcp/flex_option/tests/flex_option_unittests.cc
+3
-1
No files found.
doc/sphinx/arm/hooks.rst
View file @
2bf854c0
...
@@ -1170,31 +1170,23 @@ In the DHCPv6 case, the corresponding query will look similar to this:
...
@@ -1170,31 +1170,23 @@ In the DHCPv6 case, the corresponding query will look similar to this:
flex_option Flexible Option for Option value settings
flex_option Flexible Option for Option value settings
=====================================================
=====================================================
This sectiom describes a hook application dedicated to generate flexible
This library allows you to define an action to take, for a given option,
option values in response packets. The Kea software provides a way to
based upon on the result of an expression. These actions are carried
configure option values of response packets based on global configuration,
out during the final stages of constructing a query response packet,
client classes, shared networks, subnets, pools and host reservations but
just before it is sent to the client. The three actions currently
in all cases values are static. However, there are sometimes scenarios
supported are ``add``, ``supersede``, and ``remove``.
where the value should be computed from elements from the query packet.
These scenarios are addressed by the Flexible Option hook application.
The syntax used for the action expressions is the same syntax used
for client classification and the Flex Identifier hook library
This hook is available since Kea 1.7.1.
(See either :ref:`classification-using-expressions` or :ref:`flex-id`
for detailed description of the syntax).
.. note::
The ``add`` and ``supersede`` actions use an expression returning a
This library may only be loaded by the ``kea-dhp4`` or ``kea-dhp6``
string, doing nothing when it evaluates to the empty string. The
process.
``remove`` application uses an expression returning true or false,
doing nothing on false. When it is necessary to set an option to the
The library allows the definition of an action per option using an
empty value this mechanism does not work but a client class can be
expression on the query packer as for the Flex Indentifier hook library
used instead.
(See :ref:`flex-id`) or for client classification (See
:ref:`classification-using-expressions` for a detailed description of
the syntax available.) The ``add`` and ``supersede`` actions use an
expression returning a string, doing nothing when it evaluates to
the empty string. The ``remove`` application uses an expression returning
true or false, doing nothing on false. When it is necessary to set
an option to the empty value this mechanism does not work but a client
class can be used instead.
The ``add`` action adds an option only when the option does not already
The ``add`` action adds an option only when the option does not already
exist and the expression does not evaluate to the empty string.
exist and the expression does not evaluate to the empty string.
...
@@ -1203,16 +1195,18 @@ if it already exists. The ``remove`` action removes the option from
...
@@ -1203,16 +1195,18 @@ if it already exists. The ``remove`` action removes the option from
the response packet if it already exists and the expression evaluates to
the response packet if it already exists and the expression evaluates to
true.
true.
The option where an action applies is specified by its code point or
The option to which an action applies may be specified by either its
by its name. At least the code or the name must be specified. The option
numeric code or its name.. At least the code or the name must be
space is the DHCPv4 or DHCPv6 spaces depending of the server where the
specified. The option space is the DHCPv4 or DHCPv6 spaces depending
hook library is loaded. Other spaces as vendor spaces could be supported
of the server where the hook library is loaded. Other spaces as vendor
in a further version.
spaces could be supported in a further version.
The library can be loaded in a similar way as other hook libraries. It takes
The library is available since Kea 1.7.1 and can be loaded in a
a mandatory ``options`` parameter holding a list of per option parameter
similar way as other hook libraries by the ``kea-dhp4`` or `kea-dhp6``
maps with code, name, add, supersede and remove actions. Action entries
process.. It takes a mandatory ``options`` parameter holding a list of
take a string value representing an expression.
per option parameter maps with code, name, add, supersede and remove
actions. Action entries take a string value representing an
expression.
::
::
...
...
src/hooks/dhcp/flex_option/flex_option.cc
View file @
2bf854c0
...
@@ -20,9 +20,55 @@ using namespace isc;
...
@@ -20,9 +20,55 @@ using namespace isc;
using
namespace
isc
::
data
;
using
namespace
isc
::
data
;
using
namespace
isc
::
dhcp
;
using
namespace
isc
::
dhcp
;
using
namespace
isc
::
eval
;
using
namespace
isc
::
eval
;
using
namespace
isc
::
flex_option
;
using
namespace
isc
::
log
;
using
namespace
isc
::
log
;
using
namespace
std
;
using
namespace
std
;
namespace
{
/// @brief Parse an action.
///
/// @param option The option element.
/// @param opt_cfg The option configuration.
/// @param name The action name.
/// @param action The action.
/// @param parser_type The type of the parser of the expression.
void
parseAction
(
ConstElementPtr
option
,
FlexOptionImpl
::
OptionConfigPtr
opt_cfg
,
Option
::
Universe
universe
,
const
string
&
name
,
FlexOptionImpl
::
Action
action
,
EvalContext
::
ParserType
parser_type
)
{
ConstElementPtr
elem
=
option
->
get
(
name
);
if
(
elem
)
{
if
(
elem
->
getType
()
!=
Element
::
string
)
{
isc_throw
(
BadValue
,
"'"
<<
name
<<
"' must be a string: "
<<
elem
->
str
());
}
string
expr_text
=
elem
->
stringValue
();
if
(
expr_text
.
empty
())
{
isc_throw
(
BadValue
,
"'"
<<
name
<<
"' must not be empty"
);
}
if
(
opt_cfg
->
getAction
()
!=
FlexOptionImpl
::
NONE
)
{
isc_throw
(
BadValue
,
"multiple actions: "
<<
option
->
str
());
}
opt_cfg
->
setAction
(
action
);
opt_cfg
->
setText
(
expr_text
);
try
{
EvalContext
eval_ctx
(
universe
);
eval_ctx
.
parseString
(
expr_text
,
parser_type
);
ExpressionPtr
expr
(
new
Expression
(
eval_ctx
.
expression
));
opt_cfg
->
setExpr
(
expr
);
}
catch
(
const
std
::
exception
&
ex
)
{
isc_throw
(
BadValue
,
"can't parse "
<<
name
<<
" expression ["
<<
expr_text
<<
"] error: "
<<
ex
.
what
());
}
}
}
}
// end of anonymous namespace
namespace
isc
{
namespace
isc
{
namespace
flex_option
{
namespace
flex_option
{
...
@@ -130,8 +176,9 @@ FlexOptionImpl::parseOptionConfig(ConstElementPtr option) {
...
@@ -130,8 +176,9 @@ FlexOptionImpl::parseOptionConfig(ConstElementPtr option) {
<<
space
<<
"' space"
);
<<
space
<<
"' space"
);
}
}
if
(
code_elem
&&
(
def
->
getCode
()
!=
code
))
{
if
(
code_elem
&&
(
def
->
getCode
()
!=
code
))
{
isc_throw
(
BadValue
,
"option '"
<<
name
<<
"' has code "
isc_throw
(
BadValue
,
"option '"
<<
name
<<
"' is defined as code: "
<<
def
->
getCode
()
<<
" but 'code' is "
<<
code
);
<<
def
->
getCode
()
<<
", not the specified code: "
<<
code
);
}
}
code
=
def
->
getCode
();
code
=
def
->
getCode
();
}
}
...
@@ -148,78 +195,13 @@ FlexOptionImpl::parseOptionConfig(ConstElementPtr option) {
...
@@ -148,78 +195,13 @@ FlexOptionImpl::parseOptionConfig(ConstElementPtr option) {
}
}
OptionConfigPtr
opt_cfg
(
new
OptionConfig
(
code
));
OptionConfigPtr
opt_cfg
(
new
OptionConfig
(
code
));
ConstElementPtr
add_elem
=
option
->
get
(
"add"
);
// opt_cfg initial action is NONE.
if
(
add_elem
)
{
parseAction
(
option
,
opt_cfg
,
universe
,
if
(
add_elem
->
getType
()
!=
Element
::
string
)
{
"add"
,
ADD
,
EvalContext
::
PARSER_STRING
);
isc_throw
(
BadValue
,
"'add' must be a string: "
parseAction
(
option
,
opt_cfg
,
universe
,
<<
add_elem
->
str
());
"supersede"
,
SUPERSEDE
,
EvalContext
::
PARSER_STRING
);
}
parseAction
(
option
,
opt_cfg
,
universe
,
string
add
=
add_elem
->
stringValue
();
"remove"
,
REMOVE
,
EvalContext
::
PARSER_BOOL
);
if
(
add
.
empty
())
{
isc_throw
(
BadValue
,
"'add' must not be empty"
);
}
opt_cfg
->
setAction
(
ADD
);
opt_cfg
->
setText
(
add
);
try
{
EvalContext
eval_ctx
(
universe
);
eval_ctx
.
parseString
(
add
,
EvalContext
::
PARSER_STRING
);
ExpressionPtr
expr
(
new
Expression
(
eval_ctx
.
expression
));
opt_cfg
->
setExpr
(
expr
);
}
catch
(
const
std
::
exception
&
ex
)
{
isc_throw
(
BadValue
,
"can't parse add expression ["
<<
add
<<
"] error: "
<<
ex
.
what
());
}
}
ConstElementPtr
supersede_elem
=
option
->
get
(
"supersede"
);
if
(
supersede_elem
)
{
if
(
supersede_elem
->
getType
()
!=
Element
::
string
)
{
isc_throw
(
BadValue
,
"'supersede' must be a string: "
<<
supersede_elem
->
str
());
}
string
supersede
=
supersede_elem
->
stringValue
();
if
(
supersede
.
empty
())
{
isc_throw
(
BadValue
,
"'supersede' must not be empty"
);
}
if
(
opt_cfg
->
getAction
()
!=
NONE
)
{
isc_throw
(
BadValue
,
"multiple actions: "
<<
option
->
str
());
}
opt_cfg
->
setAction
(
SUPERSEDE
);
opt_cfg
->
setText
(
supersede
);
try
{
EvalContext
eval_ctx
(
universe
);
eval_ctx
.
parseString
(
supersede
,
EvalContext
::
PARSER_STRING
);
ExpressionPtr
expr
(
new
Expression
(
eval_ctx
.
expression
));
opt_cfg
->
setExpr
(
expr
);
}
catch
(
const
std
::
exception
&
ex
)
{
isc_throw
(
BadValue
,
"can't parse supersede expression ["
<<
supersede
<<
"] error: "
<<
ex
.
what
());
}
}
ConstElementPtr
remove_elem
=
option
->
get
(
"remove"
);
if
(
remove_elem
)
{
if
(
remove_elem
->
getType
()
!=
Element
::
string
)
{
isc_throw
(
BadValue
,
"'remove' must be a string: "
<<
remove_elem
->
str
());
}
string
remove
=
remove_elem
->
stringValue
();
if
(
remove
.
empty
())
{
isc_throw
(
BadValue
,
"'remove' must not be empty"
);
}
if
(
opt_cfg
->
getAction
()
!=
NONE
)
{
isc_throw
(
BadValue
,
"multiple actions: "
<<
option
->
str
());
}
opt_cfg
->
setAction
(
REMOVE
);
opt_cfg
->
setText
(
remove
);
try
{
EvalContext
eval_ctx
(
universe
);
eval_ctx
.
parseString
(
remove
,
EvalContext
::
PARSER_BOOL
);
ExpressionPtr
expr
(
new
Expression
(
eval_ctx
.
expression
));
opt_cfg
->
setExpr
(
expr
);
}
catch
(
const
std
::
exception
&
ex
)
{
isc_throw
(
BadValue
,
"can't parse remove expression ["
<<
remove
<<
"] error: "
<<
ex
.
what
());
}
}
if
(
opt_cfg
->
getAction
()
==
NONE
)
{
if
(
opt_cfg
->
getAction
()
==
NONE
)
{
isc_throw
(
BadValue
,
"no action: "
<<
option
->
str
());
isc_throw
(
BadValue
,
"no action: "
<<
option
->
str
());
...
...
src/hooks/dhcp/flex_option/flex_option.dox
View file @
2bf854c0
...
@@ -75,7 +75,7 @@ To configure it for kea-dhcp6, the commands are simply as shown below:
...
@@ -75,7 +75,7 @@ To configure it for kea-dhcp6, the commands are simply as shown below:
}
}
@endcode
@endcode
The parameter is
rge
options list of option
configuration map
s with:
The
sole
parameter is
a
options list of options with:
- @b code - Specifies the option code.
- @b code - Specifies the option code.
- @b name - Speficies the option name, at least the option code or name
- @b name - Speficies the option name, at least the option code or name
must be given.
must be given.
...
@@ -93,7 +93,8 @@ The parameter is rge options list of option configuration maps with:
...
@@ -93,7 +93,8 @@ The parameter is rge options list of option configuration maps with:
response. Only one action can be specified.
response. Only one action can be specified.
Note for the rare options which can be empty this mechanism does not work.
Note for the rare options which can be empty this mechanism does not work.
The proposed solution in this case is to use a client class.
The proposed solution in this case is to use a client class to set the
empty value to the option in a option-data clause.
## Internal operation
## Internal operation
...
...
src/hooks/dhcp/flex_option/flex_option.h
View file @
2bf854c0
...
@@ -17,6 +17,11 @@ namespace isc {
...
@@ -17,6 +17,11 @@ namespace isc {
namespace
flex_option
{
namespace
flex_option
{
/// @brief Flex Option implementation.
/// @brief Flex Option implementation.
///
/// The implementation can be divided into two parts:
/// - the configuration parsed and stored by load()
/// - the response packet processing performed by the process method
///
class
FlexOptionImpl
{
class
FlexOptionImpl
{
public:
public:
...
@@ -152,13 +157,16 @@ public:
...
@@ -152,13 +157,16 @@ public:
case
NONE
:
case
NONE
:
break
;
break
;
case
ADD
:
case
ADD
:
// Don't add if option is already there.
if
(
opt
)
{
if
(
opt
)
{
break
;
break
;
}
}
value
=
isc
::
dhcp
::
evaluateString
(
*
opt_cfg
->
getExpr
(),
*
query
);
value
=
isc
::
dhcp
::
evaluateString
(
*
opt_cfg
->
getExpr
(),
*
query
);
// Do nothing is the expression evaluates to empty.
if
(
value
.
empty
())
{
if
(
value
.
empty
())
{
break
;
break
;
}
}
// Add the option.
buffer
.
assign
(
value
.
begin
(),
value
.
end
());
buffer
.
assign
(
value
.
begin
(),
value
.
end
());
opt
.
reset
(
new
isc
::
dhcp
::
Option
(
universe
,
opt_cfg
->
getCode
(),
opt
.
reset
(
new
isc
::
dhcp
::
Option
(
universe
,
opt_cfg
->
getCode
(),
buffer
));
buffer
));
...
@@ -166,14 +174,17 @@ public:
...
@@ -166,14 +174,17 @@ public:
logAction
(
ADD
,
opt_cfg
->
getCode
(),
value
);
logAction
(
ADD
,
opt_cfg
->
getCode
(),
value
);
break
;
break
;
case
SUPERSEDE
:
case
SUPERSEDE
:
// Do nothing is the expression evaluates to empty.
value
=
isc
::
dhcp
::
evaluateString
(
*
opt_cfg
->
getExpr
(),
*
query
);
value
=
isc
::
dhcp
::
evaluateString
(
*
opt_cfg
->
getExpr
(),
*
query
);
if
(
value
.
empty
())
{
if
(
value
.
empty
())
{
break
;
break
;
}
}
// Remove the option if already there.
while
(
opt
)
{
while
(
opt
)
{
response
->
delOption
(
opt_cfg
->
getCode
());
response
->
delOption
(
opt_cfg
->
getCode
());
opt
=
response
->
getOption
(
opt_cfg
->
getCode
());
opt
=
response
->
getOption
(
opt_cfg
->
getCode
());
}
}
// Add the option.
buffer
.
assign
(
value
.
begin
(),
value
.
end
());
buffer
.
assign
(
value
.
begin
(),
value
.
end
());
opt
.
reset
(
new
isc
::
dhcp
::
Option
(
universe
,
opt_cfg
->
getCode
(),
opt
.
reset
(
new
isc
::
dhcp
::
Option
(
universe
,
opt_cfg
->
getCode
(),
buffer
));
buffer
));
...
@@ -181,12 +192,15 @@ public:
...
@@ -181,12 +192,15 @@ public:
logAction
(
SUPERSEDE
,
opt_cfg
->
getCode
(),
value
);
logAction
(
SUPERSEDE
,
opt_cfg
->
getCode
(),
value
);
break
;
break
;
case
REMOVE
:
case
REMOVE
:
// Nothing to remove if option is not present.
if
(
!
opt
)
{
if
(
!
opt
)
{
break
;
break
;
}
}
// Do nothing is the expression evaluates to false.
if
(
!
isc
::
dhcp
::
evaluateBool
(
*
opt_cfg
->
getExpr
(),
*
query
))
{
if
(
!
isc
::
dhcp
::
evaluateBool
(
*
opt_cfg
->
getExpr
(),
*
query
))
{
break
;
break
;
}
}
// Remove the option.
while
(
opt
)
{
while
(
opt
)
{
response
->
delOption
(
opt_cfg
->
getCode
());
response
->
delOption
(
opt_cfg
->
getCode
());
opt
=
response
->
getOption
(
opt_cfg
->
getCode
());
opt
=
response
->
getOption
(
opt_cfg
->
getCode
());
...
...
src/hooks/dhcp/flex_option/tests/flex_option_unittests.cc
View file @
2bf854c0
...
@@ -305,7 +305,9 @@ TEST_F(FlexOptionTest, optionConfigCodeNameMismatch) {
...
@@ -305,7 +305,9 @@ TEST_F(FlexOptionTest, optionConfigCodeNameMismatch) {
ElementPtr
name
=
Element
::
create
(
string
(
"host-name"
));
ElementPtr
name
=
Element
::
create
(
string
(
"host-name"
));
option
->
set
(
"name"
,
name
);
option
->
set
(
"name"
,
name
);
EXPECT_THROW
(
impl_
->
testConfigure
(
options
),
BadValue
);
EXPECT_THROW
(
impl_
->
testConfigure
(
options
),
BadValue
);
EXPECT_EQ
(
"option 'host-name' has code 12 but 'code' is 13"
,
impl_
->
getErrMsg
());
string
expected
=
"option 'host-name' is defined as code: 12, "
;
expected
+=
"not the specified code: 13"
;
EXPECT_EQ
(
expected
,
impl_
->
getErrMsg
());
}
}
// Verify that an option can be configured only once.
// Verify that an option can be configured only once.
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment