Commit 230e6813 authored by Marcin Siodelski's avatar Marcin Siodelski
Browse files

[3512] Implemented support for unicast sockets selection in IfaceCfg class.

parent a50b89a0
......@@ -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) {
......
......@@ -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
......
......@@ -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) {
......
......@@ -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
......
......@@ -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_;
......
......@@ -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);
EXPECT_THROW(cfg.use(""), InvalidIfaceName);
EXPECT_THROW(cfg.use(" "), InvalidIfaceName);
EXPECT_THROW(cfg.use("bogus"), NoSuchIface);
ASSERT_THROW(cfg.use(""), InvalidIfaceName);
ASSERT_THROW(cfg.use(" "), InvalidIfaceName);
ASSERT_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));
EXPECT_THROW(cfg.use(IfaceCfg::ALL_IFACES_KEYWORD), DuplicateIfaceName);
ASSERT_THROW(cfg.use(IfaceCfg::ALL_IFACES_KEYWORD), DuplicateIfaceName);
}
} // end of anonymous namespace
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment