Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
Sebastian Schrader
Kea
Commits
230e6813
Commit
230e6813
authored
Aug 20, 2014
by
Marcin Siodelski
Browse files
[3512] Implemented support for unicast sockets selection in IfaceCfg class.
parent
a50b89a0
Changes
6
Hide whitespace changes
Inline
Side-by-side
src/lib/dhcp/iface_mgr.cc
View file @
230e6813
...
...
@@ -242,6 +242,18 @@ Iface::getAddress4(isc::asiolink::IOAddress& address) const {
return
(
false
);
}
bool
Iface
::
hasAddress
(
const
isc
::
asiolink
::
IOAddress
&
address
)
const
{
const
AddressCollection
&
addrs
=
getAddresses
();
for
(
AddressCollection
::
const_iterator
addr
=
addrs
.
begin
();
addr
!=
addrs
.
end
();
++
addr
)
{
if
(
address
==
*
addr
)
{
return
(
true
);
}
}
return
(
false
);
}
void
IfaceMgr
::
closeSockets
()
{
for
(
IfaceCollection
::
iterator
iface
=
ifaces_
.
begin
();
iface
!=
ifaces_
.
end
();
++
iface
)
{
...
...
@@ -682,6 +694,14 @@ IfaceMgr::clearIfaces() {
ifaces_
.
clear
();
}
void
IfaceMgr
::
clearUnicasts
()
{
for
(
IfaceCollection
::
iterator
iface
=
ifaces_
.
begin
();
iface
!=
ifaces_
.
end
();
++
iface
)
{
iface
->
clearUnicasts
();
}
}
int
IfaceMgr
::
openSocket
(
const
std
::
string
&
ifname
,
const
IOAddress
&
addr
,
const
uint16_t
port
,
const
bool
receive_bcast
,
const
bool
send_bcast
)
{
...
...
src/lib/dhcp/iface_mgr.h
View file @
230e6813
...
...
@@ -279,6 +279,12 @@ public:
/// for the interface (if true), or not (false).
bool
getAddress4
(
isc
::
asiolink
::
IOAddress
&
address
)
const
;
/// @brief Check if the interface has the specified address assigned.
///
/// @param address Address to be checked.
/// @return true if address is assigned to the intefrace, false otherwise.
bool
hasAddress
(
const
isc
::
asiolink
::
IOAddress
&
address
)
const
;
/// @brief Adds an address to an interface.
///
/// This only adds an address to collection, it does not physically
...
...
@@ -549,6 +555,9 @@ public:
/// IPv6 address is read from interfaces.txt file.
void
detectIfaces
();
/// @brief Clears unicast addresses on all interfaces.
void
clearUnicasts
();
/// @brief Return most suitable socket for transmitting specified IPv6 packet.
///
/// This method takes Pkt6 (see overloaded implementation that takes
...
...
src/lib/dhcp/tests/iface_mgr_unittest.cc
View file @
230e6813
...
...
@@ -606,6 +606,19 @@ TEST_F(IfaceMgrTest, ifaceGetAddress) {
}
// This test checks if it is possible to check that the specific address is
// assigned to the interface.
TEST_F
(
IfaceMgrTest
,
ifaceHasAddress
)
{
IfaceMgrTestConfig
config
(
true
);
Iface
*
iface
=
IfaceMgr
::
instance
().
getIface
(
"eth0"
);
ASSERT_FALSE
(
iface
==
NULL
);
EXPECT_TRUE
(
iface
->
hasAddress
(
IOAddress
(
"10.0.0.1"
)));
EXPECT_TRUE
(
iface
->
hasAddress
(
IOAddress
(
"fe80::3a60:77ff:fed5:cdef"
)));
EXPECT_TRUE
(
iface
->
hasAddress
(
IOAddress
(
"2001:db8:1::1"
)));
EXPECT_FALSE
(
iface
->
hasAddress
(
IOAddress
(
"2001:db8:1::2"
)));
}
// TODO: Implement getPlainMac() test as soon as interface detection
// is implemented.
TEST_F
(
IfaceMgrTest
,
getIface
)
{
...
...
src/lib/dhcpsrv/iface_cfg.cc
View file @
230e6813
...
...
@@ -18,6 +18,8 @@
#include <util/strutil.h>
#include <boost/bind.hpp>
using
namespace
isc
::
asiolink
;
namespace
isc
{
namespace
dhcp
{
...
...
@@ -40,6 +42,8 @@ IfaceCfg::openSockets(const uint16_t port, const bool use_bcast) {
// interface names specified by the user. If wildcard interface was
// specified, mark all interfaces active.
setState
(
!
wildcard_used_
);
// Remove selection of unicast addresses from all interfaces.
IfaceMgr
::
instance
().
clearUnicasts
();
// If there is no wildcard interface specified, we will have to iterate
// over the names specified by the caller and enable them.
if
(
!
wildcard_used_
)
{
...
...
@@ -65,6 +69,21 @@ IfaceCfg::openSockets(const uint16_t port, const bool use_bcast) {
}
}
// Select unicast sockets. It works only for V6. Ignore for V4.
if
(
getFamily
()
==
V6
)
{
for
(
UnicastMap
::
const_iterator
unicast
=
unicast_map_
.
begin
();
unicast
!=
unicast_map_
.
end
();
++
unicast
)
{
Iface
*
iface
=
IfaceMgr
::
instance
().
getIface
(
unicast
->
first
);
if
(
iface
==
NULL
)
{
isc_throw
(
Unexpected
,
"fail to open unicast socket on interface '"
<<
unicast
->
first
<<
"' as this interface doesn't"
" exist"
);
}
iface
->
addUnicast
(
unicast
->
second
);
}
}
// Set the callback which is called when the socket fails to open
// for some specific interface. This callback will simply log a
// warning message.
...
...
@@ -113,34 +132,119 @@ IfaceCfg::socketOpenErrorHandler(const std::string& errmsg) {
void
IfaceCfg
::
use
(
const
std
::
string
&
iface_name
)
{
// In theory the configuration parser should strip extraneous spaces but
// since this is a common library it may be better to make sure that it
// is really the case.
std
::
string
name
=
util
::
str
::
trim
(
iface_name
);
if
(
name
.
empty
())
{
isc_throw
(
InvalidIfaceName
,
"empty interface name used in configuration"
);
}
else
if
(
name
!=
ALL_IFACES_KEYWORD
)
{
if
(
IfaceMgr
::
instance
().
getIface
(
name
)
==
NULL
)
{
// The interface name specified may have two formats, e.g.:
// - eth0
// - eth0/2001:db8:1::1.
// The latter format is used to open unicast socket on the specified
// interface. Here we are detecting which format was used and we strip
// all extraneous spaces.
size_t
pos
=
iface_name
.
find
(
"/"
);
std
::
string
name
;
std
::
string
addr_str
;
// There is no unicast address so the whole string is an interface name.
if
(
pos
==
std
::
string
::
npos
)
{
name
=
util
::
str
::
trim
(
iface_name
);
if
(
name
.
empty
())
{
isc_throw
(
InvalidIfaceName
,
"empty interface name used in configuration"
);
}
if
(
name
!=
ALL_IFACES_KEYWORD
)
{
if
(
IfaceMgr
::
instance
().
getIface
(
name
)
==
NULL
)
{
isc_throw
(
NoSuchIface
,
"interface '"
<<
name
<<
"' doesn't exist in the system"
);
}
std
::
pair
<
IfaceSet
::
iterator
,
bool
>
res
=
iface_set_
.
insert
(
name
);
if
(
!
res
.
second
)
{
isc_throw
(
DuplicateIfaceName
,
"interface '"
<<
name
<<
"' has already been specified"
);
}
}
else
if
(
wildcard_used_
)
{
isc_throw
(
DuplicateIfaceName
,
"the wildcard interface '"
<<
ALL_IFACES_KEYWORD
<<
"' can only be specified once"
);
}
else
{
wildcard_used_
=
true
;
}
}
else
if
(
getFamily
()
==
V4
)
{
isc_throw
(
InvalidIfaceName
,
"unicast addresses in the format of: "
"iface-name/unicast-addr_stress can only be specified for"
" IPv6 addr_stress family"
);
}
else
{
// The interface name includes the unicast addr_stress, so we split
// interface name and the unicast addr_stress to two variables.
name
=
util
::
str
::
trim
(
iface_name
.
substr
(
0
,
pos
));
addr_str
=
util
::
str
::
trim
(
iface_name
.
substr
(
pos
+
1
));
// Interface name must not be empty.
if
(
name
.
empty
())
{
isc_throw
(
InvalidIfaceName
,
"empty interface name specified in the"
" interface configuration"
);
}
// Unicast addr_stress following the interface name must not be empty.
if
(
addr_str
.
empty
())
{
isc_throw
(
InvalidIfaceName
,
"empty unicast addr_stress specified in the interface"
<<
" configuration"
);
}
// Interface name must not be the wildcard name.
if
(
name
==
ALL_IFACES_KEYWORD
)
{
isc_throw
(
InvalidIfaceName
,
"wildcard interface name '"
<<
ALL_IFACES_KEYWORD
<<
"' must not be used in conjunction with a"
" unicast addr_stress"
);
}
// Interface must exist.
Iface
*
iface
=
IfaceMgr
::
instance
().
getIface
(
name
);
if
(
iface
==
NULL
)
{
isc_throw
(
NoSuchIface
,
"interface '"
<<
name
<<
"' doesn't exist in the system"
);
}
std
::
pair
<
IfaceSet
::
iterator
,
bool
>
res
=
iface_set_
.
insert
(
name
);
if
(
!
res
.
second
)
{
isc_throw
(
DuplicateIfaceName
,
"interface '"
<<
name
<<
"' has already been specified"
);
// Convert address string. This may throw an exception if the address
// is invalid.
IOAddress
addr
(
addr_str
);
// Check that the address is a valid unicast address.
if
(
!
addr
.
isV6
()
||
addr
.
isV6LinkLocal
()
||
addr
.
isV6Multicast
())
{
isc_throw
(
InvalidIfaceName
,
"address '"
<<
addr
<<
"' is not"
" a valid IPv6 unicast address"
);
}
}
else
if
(
wildcard_used_
)
{
isc_throw
(
DuplicateIfaceName
,
"the wildcard interface '"
<<
ALL_IFACES_KEYWORD
<<
"' can only be specified once"
);
// Interface must have this address assigned.
if
(
!
iface
->
hasAddress
(
addr
))
{
isc_throw
(
NoSuchAddress
,
"interface '"
<<
name
<<
"' doesn't have address '"
<<
addr
<<
"' assigned"
);
}
}
else
{
wildcard_used_
=
true
;
// Insert address and the interface to the collection of unicast
// addresses.
std
::
pair
<
UnicastMap
::
iterator
,
bool
>
res
=
unicast_map_
.
insert
(
std
::
pair
<
std
::
string
,
IOAddress
>
(
name
,
addr
));
// If some other unicast address has been added for the interface
// return an error. The new address didn't override the existing one.
if
(
!
res
.
second
)
{
isc_throw
(
DuplicateIfaceName
,
"must not specify unicast address '"
<<
addr
<<
"' for interface '"
<<
name
<<
"' "
"because other unicast address has already been"
" specified for this interface"
);
}
}
}
}
// end of isc::dhcp namespace
...
...
src/lib/dhcpsrv/iface_cfg.h
View file @
230e6813
...
...
@@ -15,6 +15,8 @@
#ifndef IFACE_CFG_H
#define IFACE_CFG_H
#include <asiolink/io_address.h>
#include <map>
#include <set>
namespace
isc
{
...
...
@@ -41,17 +43,21 @@ public:
isc
::
Exception
(
file
,
line
,
what
)
{
};
};
/// @brief Exception thrown when specified unicast address is not assigned
/// to the interface specified.
class
NoSuchAddress
:
public
Exception
{
public:
NoSuchAddress
(
const
char
*
file
,
size_t
line
,
const
char
*
what
)
:
isc
::
Exception
(
file
,
line
,
what
)
{
};
};
/// @brief Represents selection of interfaces for DHCP server.
///
/// This class manages selection of interfaces on which the DHCP server is
/// listening to queries. The interfaces are selected in the server
/// configuration by their names. This class performs sanity checks on the
/// interface names specified in the configuration and reports errors in the
/// following conditions:
/// - user specifies the same interface more than once,
/// - user specifies the interface which doesn't exist,
/// - user specifies an empty interface.
/// configuration by their names or by the pairs of interface names and unicast
/// addresses (e.g. eth0/2001:db8:1::1). The latter format is only accepted when
/// IPv6 configuration is in use.
///
/// This class also accepts "wildcard" interface name which, if specified,
/// instructs the server to listen on all available interfaces.
...
...
@@ -76,8 +82,8 @@ public:
/// @brief Constructor.
///
/// @param family Protocol family.
IfaceCfg
(
Family
family
);
/// @param family Protocol family
(default is V4)
.
IfaceCfg
(
Family
family
=
V4
);
/// @brief Convenience function which closes all open sockets.
void
closeSockets
();
...
...
@@ -88,6 +94,16 @@ public:
}
/// @brief Tries to open sockets on selected interfaces.
///
/// This function opens sockets bound to link-local address as well as
/// sockets bound to unicast address. See @c IfaceCfg::use function
/// documentation for details how to specify interfaces and unicast
/// addresses to bind the sockets to.
///
/// @param port Port number to be used to bind sockets to.
/// @param use_bcast A boolean flag which indicates if the broadcast
/// traffic should be received through the socket. This parameter is
/// ignored for IPv6.
void
openSockets
(
const
uint16_t
port
,
const
bool
use_bcast
=
true
);
/// @brief Puts the interface configuration into default state.
...
...
@@ -95,14 +111,42 @@ public:
/// This function removes interface names from the set.
void
reset
();
/// @brief Sets protocol family.
///
/// @param family New family value (V4 or V6).
void
setFamily
(
Family
family
)
{
family_
=
family
;
}
/// @brief Select interface to be used to receive DHCP traffic.
///
/// @param iface_name Explicit interface name or a wildcard name (*) of
/// the interface(s) to be used to receive DHCP traffic.
/// This function controls the selection of the interface on which the
/// DHCP queries should be received by the server. The interface name
/// passed as the argument of this function may appear in one of the following
/// formats:
/// - interface-name, e.g. eth0
/// - interface-name/unicast-address, e.g. eth0/2001:db8:1::1 (V6 only)
///
/// Extraneous spaces surrounding the interface name and/or unicast address
/// are accepted. For example: eth0 / 2001:db8:1::1 will be accepted.
///
/// When only interface name is specified (without an address) it is allowed
/// to use the "wildcard" interface name (*) which indicates that the server
/// should open sockets on all interfaces. When IPv6 is in use, the sockets
/// will be bound to the link local addresses. Wildcard interface names are
/// not allowed when specifying a unicast address. For example:
/// */2001:db8:1::1 is not allowed.
///
/// @param iface_name Explicit interface name, a wildcard name (*) of
/// the interface(s) or the pair of iterface/unicast-address to be used
/// to receive DHCP traffic.
///
/// @throw InvalidIfaceName If the interface name is incorrect, e.g. empty.
/// @throw NoSuchIface If the specified interface is not present.
/// @throw NoSuchAddress If the specified unicast address is not assigned
/// to the interface.
/// @throw DuplicateIfaceName If the interface is already selected, i.e.
/// @throw IOError when specified unicast address is invalid.
/// @c IfaceCfg::use has been already called for this interface.
void
use
(
const
std
::
string
&
iface_name
);
...
...
@@ -133,6 +177,13 @@ private:
typedef
std
::
set
<
std
::
string
>
IfaceSet
;
/// @brief A set of interface names specified by the user.
IfaceSet
iface_set_
;
/// @brief A map of interfaces and unicast addresses.
typedef
std
::
map
<
std
::
string
,
asiolink
::
IOAddress
>
UnicastMap
;
/// @brief A map which holds the pairs of interface names and unicast
/// addresses for which the unicast sockets should be opened.
///
/// This is only used for V6 family.
UnicastMap
unicast_map_
;
/// @brief A booolean value which indicates that the wildcard interface name
/// has been specified (*).
bool
wildcard_used_
;
...
...
src/lib/dhcpsrv/tests/iface_cfg_unittest.cc
View file @
230e6813
...
...
@@ -42,6 +42,11 @@ public:
/// @param family One of: AF_INET or AF_INET6
bool
socketOpen
(
const
std
::
string
&
iface_name
,
const
int
family
)
const
;
/// @brief Checks if unicast socket is opened on interface.
///
/// @param iface_name Interface name.
bool
unicastOpen
(
const
std
::
string
&
iface_name
)
const
;
/// @brief Holds a fake configuration of the interfaces.
IfaceMgrTestConfig
iface_mgr_test_config_
;
...
...
@@ -66,6 +71,25 @@ IfaceCfgTest::socketOpen(const std::string& iface_name,
return
(
false
);
}
bool
IfaceCfgTest
::
unicastOpen
(
const
std
::
string
&
iface_name
)
const
{
Iface
*
iface
=
IfaceMgr
::
instance
().
getIface
(
iface_name
);
if
(
iface
==
NULL
)
{
ADD_FAILURE
()
<<
"No such interface '"
<<
iface_name
<<
"'"
;
return
(
false
);
}
const
Iface
::
SocketCollection
&
sockets
=
iface
->
getSockets
();
for
(
Iface
::
SocketCollection
::
const_iterator
sock
=
sockets
.
begin
();
sock
!=
sockets
.
end
();
++
sock
)
{
if
((
!
sock
->
addr_
.
isV6LinkLocal
())
&&
(
!
sock
->
addr_
.
isV6Multicast
()))
{
return
(
true
);
}
}
return
(
false
);
}
// This test checks that the interface names can be explicitly selected
// by their names and IPv4 sockets are opened on these interfaces.
TEST_F
(
IfaceCfgTest
,
explicitNamesV4
)
{
...
...
@@ -184,18 +208,47 @@ TEST_F(IfaceCfgTest, wildcardV6) {
EXPECT_FALSE
(
socketOpen
(
"lo"
,
AF_INET
));
}
// Test that unicast address can be specified for the socket to be opened on
// the interface on which the socket bound to link local address is also
// opened.
TEST_F
(
IfaceCfgTest
,
validUnicast
)
{
IfaceCfg
cfg
(
IfaceCfg
::
V6
);
// One socket will be opened on link-local address, one on unicast but
// on the same interface.
ASSERT_NO_THROW
(
cfg
.
use
(
"eth0"
));
ASSERT_NO_THROW
(
cfg
.
use
(
"eth0/2001:db8:1::1"
));
cfg
.
openSockets
(
DHCP6_SERVER_PORT
);
EXPECT_TRUE
(
socketOpen
(
"eth0"
,
AF_INET6
));
EXPECT_TRUE
(
unicastOpen
(
"eth0"
));
}
// Test that when invalid interface names are specified an exception is thrown.
TEST_F
(
IfaceCfgTest
,
invalidValues
)
{
IfaceCfg
cfg
(
IfaceCfg
::
V4
);
EXPEC
T_THROW
(
cfg
.
use
(
""
),
InvalidIfaceName
);
EXPEC
T_THROW
(
cfg
.
use
(
" "
),
InvalidIfaceName
);
EXPEC
T_THROW
(
cfg
.
use
(
"bogus"
),
NoSuchIface
);
ASSER
T_THROW
(
cfg
.
use
(
""
),
InvalidIfaceName
);
ASSER
T_THROW
(
cfg
.
use
(
" "
),
InvalidIfaceName
);
ASSER
T_THROW
(
cfg
.
use
(
"bogus"
),
NoSuchIface
);
ASSERT_NO_THROW
(
cfg
.
use
(
"eth0"
));
EXPECT_THROW
(
cfg
.
use
(
"eth0"
),
DuplicateIfaceName
);
ASSERT_THROW
(
cfg
.
use
(
"eth0"
),
DuplicateIfaceName
);
ASSERT_THROW
(
cfg
.
use
(
"eth0/2001:db8:1::1"
),
InvalidIfaceName
);
cfg
.
setFamily
(
IfaceCfg
::
V6
);
ASSERT_THROW
(
cfg
.
use
(
"eth0/"
),
InvalidIfaceName
);
ASSERT_THROW
(
cfg
.
use
(
"/2001:db8:1::1"
),
InvalidIfaceName
);
ASSERT_THROW
(
cfg
.
use
(
"*/2001:db8:1::1"
),
InvalidIfaceName
);
ASSERT_THROW
(
cfg
.
use
(
"bogus/2001:db8:1::1"
),
NoSuchIface
);
ASSERT_THROW
(
cfg
.
use
(
"eth0/fe80::3a60:77ff:fed5:cdef"
),
InvalidIfaceName
);
ASSERT_THROW
(
cfg
.
use
(
"eth0/fe80::3a60:77ff:fed5:cdef"
),
InvalidIfaceName
);
ASSERT_THROW
(
cfg
.
use
(
"eth0/2001:db8:1::2"
),
NoSuchAddress
);
ASSERT_NO_THROW
(
cfg
.
use
(
IfaceCfg
::
ALL_IFACES_KEYWORD
));
EXPEC
T_THROW
(
cfg
.
use
(
IfaceCfg
::
ALL_IFACES_KEYWORD
),
DuplicateIfaceName
);
ASSER
T_THROW
(
cfg
.
use
(
IfaceCfg
::
ALL_IFACES_KEYWORD
),
DuplicateIfaceName
);
}
}
// end of anonymous namespace
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