Commit 3fb6d907 authored by Marcin Siodelski's avatar Marcin Siodelski
Browse files

[3539] Iface configuration allows for specifying IPv4 address to listen on.

parent 32f06c29
......@@ -40,6 +40,7 @@
using namespace std;
using namespace isc::asiolink;
using namespace isc::util;
using namespace isc::util::io::internal;
namespace isc {
......@@ -149,7 +150,7 @@ void Iface::setMac(const uint8_t* mac, size_t len) {
bool Iface::delAddress(const isc::asiolink::IOAddress& addr) {
for (AddressCollection::iterator a = addrs_.begin();
a!=addrs_.end(); ++a) {
if (*a==addr) {
if (a->get() == addr) {
addrs_.erase(a);
return (true);
}
......@@ -216,12 +217,12 @@ IfaceMgr::IfaceMgr()
void Iface::addUnicast(const isc::asiolink::IOAddress& addr) {
for (Iface::AddressCollection::const_iterator i = unicasts_.begin();
i != unicasts_.end(); ++i) {
if (*i == addr) {
if (i->get() == addr) {
isc_throw(BadValue, "Address " << addr
<< " already defined on the " << name_ << " interface.");
}
}
unicasts_.push_back(addr);
unicasts_.push_back(OptionalValue<IOAddress>(addr, true));
}
bool
......@@ -233,8 +234,8 @@ Iface::getAddress4(isc::asiolink::IOAddress& address) const {
addr != addrs.end(); ++addr) {
// If address is IPv4, we assign it to the function argument
// and return true.
if (addr->isV4()) {
address = *addr;
if (addr->get().isV4()) {
address = addr->get();
return (true);
}
}
......@@ -247,13 +248,39 @@ 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) {
if (address == addr->get()) {
return (true);
}
}
return (false);
}
void
Iface::addAddress(const isc::asiolink::IOAddress& addr) {
addrs_.push_back(OptionalValue<IOAddress>(addr, OptionalValueState(false)));
}
void
Iface::setActive(const IOAddress& address, const bool active) {
for (AddressCollection::iterator addr_it = addrs_.begin();
addr_it != addrs_.end(); ++addr_it) {
if (address == addr_it->get()) {
addr_it->specify(active);
return;
}
}
isc_throw(BadValue, "specified address " << address << " was not"
" found on the interface " << getName());
}
void
Iface::setActive(const bool active) {
for (AddressCollection::iterator addr_it = addrs_.begin();
addr_it != addrs_.end(); ++addr_it) {
addr_it->specify(active);
}
}
void IfaceMgr::closeSockets() {
for (IfaceCollection::iterator iface = ifaces_.begin();
iface != ifaces_.end(); ++iface) {
......@@ -393,7 +420,7 @@ IfaceMgr::hasOpenSocket(const IOAddress& addr) const {
iface->getAddresses().begin();
addr_it != iface->getAddresses().end();
++addr_it) {
if (addr == *addr_it) {
if (addr == addr_it->get()) {
return (true);
}
}
......@@ -488,7 +515,7 @@ IfaceMgr::openSockets4(const uint16_t port, const bool use_bcast,
++addr) {
// Skip all but V4 addresses.
if (!addr->isV4()) {
if (!addr->get().isV4()) {
continue;
}
......@@ -616,7 +643,7 @@ IfaceMgr::openSockets6(const uint16_t port,
++addr) {
// Skip all but V6 addresses.
if (!addr->isV6()) {
if (!addr->get().isV6()) {
continue;
}
......@@ -625,7 +652,7 @@ IfaceMgr::openSockets6(const uint16_t port,
// with interface with 2 global addresses, we would bind 3 sockets
// (one for link-local and two for global). That would result in
// getting each message 3 times.
if (!addr->isV6LinkLocal()){
if (!addr->get().isV6LinkLocal()){
continue;
}
......@@ -663,7 +690,7 @@ IfaceMgr::printIfaces(std::ostream& out /*= std::cout*/) {
for (Iface::AddressCollection::const_iterator addr = addrs.begin();
addr != addrs.end(); ++addr) {
out << " " << addr->toText();
out << " " << addr->get().toText();
}
out << endl;
}
......@@ -743,7 +770,7 @@ int IfaceMgr::openSocketFromIface(const std::string& ifname,
Iface::AddressCollection addrs = iface->getAddresses();
Iface::AddressCollection::iterator addr_it = addrs.begin();
while (addr_it != addrs.end()) {
if (addr_it->getFamily() == family) {
if (addr_it->get().getFamily() == family) {
// We have interface and address so let's open socket.
// This may cause isc::Unexpected exception.
return (openSocket(iface->getName(), *addr_it, port, false));
......@@ -787,7 +814,7 @@ int IfaceMgr::openSocketFromAddress(const IOAddress& addr,
// on detected interfaces. If it does, we have
// address and interface detected so we can open
// socket.
if (*addr_it == addr) {
if (addr_it->get() == addr) {
// Open socket using local interface, address and port.
// This may cause isc::Unexpected exception.
return (openSocket(iface->getName(), *addr_it, port, false));
......
......@@ -22,6 +22,7 @@
#include <dhcp/pkt6.h>
#include <dhcp/pkt_filter.h>
#include <dhcp/pkt_filter6.h>
#include <util/optional_value.h>
#include <boost/function.hpp>
#include <boost/noncopyable.hpp>
......@@ -150,7 +151,8 @@ public:
static const unsigned int MAX_MAC_LEN = 20;
/// Type that defines list of addresses
typedef std::vector<isc::asiolink::IOAddress> AddressCollection;
typedef
std::list<util::OptionalValue<asiolink::IOAddress> > AddressCollection;
/// @brief Type that holds a list of socket information.
///
......@@ -291,9 +293,30 @@ public:
/// configure address on actual network interface.
///
/// @param addr address to be added
void addAddress(const isc::asiolink::IOAddress& addr) {
addrs_.push_back(addr);
}
void addAddress(const isc::asiolink::IOAddress& addr);
/// @brief Activates or deactivates address for the interface.
///
/// This method marks a specified address on the interface active or
/// inactive. If the address is marked inactive, the
/// @c IfaceMgr::openSockets4 method will NOT open socket for this address.
///
/// @param address An address which should be activated, deactivated.
/// @param active A boolean flag which indicates that the specified address
/// should be active (if true) or inactive (if false).
///
/// @throw BadValue if specified address doesn't exist for the interface.
void setActive(const isc::asiolink::IOAddress& address, const bool active);
/// @brief Activates or deactivates all addresses for the interface.
///
/// This method marks all addresses on the interface active or inactive.
/// If the address is marked inactive, the @c IfaceMgr::openSockets4
/// method will NOT open socket for this address.
///
/// @param active A boolean flag which indicates that the addresses
/// should be active (if true) or inactive (if false).
void setActive(const bool active);
/// @brief Deletes an address from an interface.
///
......
......@@ -107,6 +107,7 @@ IfaceMgrTestConfig::createIfaces() {
// eth1
addIface("eth1", 2);
addAddress("eth1", IOAddress("192.0.2.3"));
addAddress("eth1", IOAddress("192.0.2.5"));
addAddress("eth1", IOAddress("fe80::3a60:77ff:fed5:abcd"));
}
......@@ -143,7 +144,7 @@ IfaceMgrTestConfig::setIfaceFlags(const std::string& name,
bool
IfaceMgrTestConfig::socketOpen(const std::string& iface_name,
const int family) const {
const int family) const {
Iface* iface = IfaceMgr::instance().getIface(iface_name);
if (iface == NULL) {
isc_throw(Unexpected, "No such interface '" << iface_name << "'");
......@@ -159,6 +160,25 @@ IfaceMgrTestConfig::socketOpen(const std::string& iface_name,
return (false);
}
bool
IfaceMgrTestConfig::socketOpen(const std::string& iface_name,
const std::string& address) const {
Iface* iface = IfaceMgr::instance().getIface(iface_name);
if (iface == NULL) {
isc_throw(Unexpected, "No such interface '" << iface_name << "'");
}
const Iface::SocketCollection& sockets = iface->getSockets();
for (Iface::SocketCollection::const_iterator sock = sockets.begin();
sock != sockets.end(); ++sock) {
if ((sock->family_ == AF_INET) &&
(sock->addr_ == IOAddress(address))) {
return (true);
}
}
return (false);
}
bool
IfaceMgrTestConfig::unicastOpen(const std::string& iface_name) const {
Iface* iface = IfaceMgr::instance().getIface(iface_name);
......
......@@ -235,6 +235,14 @@ public:
/// @param family One of: AF_INET or AF_INET6
bool socketOpen(const std::string& iface_name, const int family) const;
/// @brief Checks is socket is opened on the interface and bound to a
/// specified address.
///
/// @param iface_name Interface name.
/// @param address Address to which the socket is bound.
bool socketOpen(const std::string& iface_name,
const std::string & address) const;
/// @brief Checks if unicast socket is opened on interface.
///
/// @param iface_name Interface name.
......
......@@ -289,7 +289,7 @@ public:
iface->getAddresses().begin();
addr_it != iface->getAddresses().end();
++addr_it) {
if (*addr_it == IOAddress(addr)) {
if (addr_it->get() == IOAddress(addr)) {
return (true);
}
}
......@@ -1501,7 +1501,7 @@ TEST_F(IfaceMgrTest, openSockets4IfaceDown) {
// Expecting that the socket is open on eth1 because it was up, running
// and active.
EXPECT_EQ(1, IfaceMgr::instance().getIface("eth1")->getSockets().size());
EXPECT_EQ(2, IfaceMgr::instance().getIface("eth1")->getSockets().size());
// Never open socket on loopback interface.
EXPECT_TRUE(IfaceMgr::instance().getIface("lo")->getSockets().empty());
......@@ -2072,7 +2072,7 @@ TEST_F(IfaceMgrTest, iface) {
addrs = iface->getAddresses();
ASSERT_EQ(1, addrs.size());
EXPECT_EQ("192.0.2.6", addrs.at(0).toText());
EXPECT_EQ("192.0.2.6", addrs.begin()->get().toText());
// No such address, should return false.
EXPECT_FALSE(iface->delAddress(IOAddress("192.0.8.9")));
......@@ -2332,7 +2332,7 @@ checkIfAddrs(const Iface & iface, struct ifaddrs *& ifptr) {
for (Iface::AddressCollection::const_iterator a =
iface.getAddresses().begin();
a != iface.getAddresses().end(); ++ a) {
if(a->isV4() && (*a) == addrv4) {
if(a->get().isV4() && (a->get()) == addrv4) {
return (true);
}
}
......@@ -2349,7 +2349,7 @@ checkIfAddrs(const Iface & iface, struct ifaddrs *& ifptr) {
for(Iface::AddressCollection::const_iterator a =
iface.getAddresses().begin();
a != iface.getAddresses().end(); ++ a) {
if(a->isV6() && (*a) == addrv6) {
if (a->get().isV6() && (a->get() == addrv6)) {
return (true);
}
}
......
......@@ -37,7 +37,7 @@ CfgIface::closeSockets() const {
bool
CfgIface::equals(const CfgIface& other) const {
return (iface_set_ == other.iface_set_ &&
unicast_map_ == other.unicast_map_ &&
address_map_ == other.address_map_ &&
wildcard_used_ == other.wildcard_used_);
}
......@@ -70,6 +70,10 @@ CfgIface::openSockets(const uint16_t family, const uint16_t port,
} else if (family == AF_INET) {
iface->inactive4_ = false;
ExplicitAddressMap::const_iterator addr = address_map_.find(iface->getName());
if (addr != address_map_.end()) {
iface->setActive(addr->second, true);
}
} else {
iface->inactive6_ = false;
......@@ -79,8 +83,8 @@ CfgIface::openSockets(const uint16_t family, const uint16_t port,
// Select unicast sockets. It works only for V6. Ignore for V4.
if (family == AF_INET6) {
for (UnicastMap::const_iterator unicast = unicast_map_.begin();
unicast != unicast_map_.end(); ++unicast) {
for (ExplicitAddressMap::const_iterator unicast = address_map_.begin();
unicast != address_map_.end(); ++unicast) {
Iface* iface = IfaceMgr::instance().getIface(unicast->first);
if (iface == NULL) {
isc_throw(Unexpected,
......@@ -130,13 +134,23 @@ CfgIface::setState(const uint16_t family, const bool inactive,
for (IfaceMgr::IfaceCollection::iterator iface = ifaces.begin();
iface != ifaces.end(); ++iface) {
Iface* iface_ptr = IfaceMgr::instance().getIface(iface->getName());
bool iface_inactive = iface_ptr->flag_loopback_ ?
loopback_inactive : inactive;
if (family == AF_INET) {
iface_ptr->inactive4_ = iface_ptr->flag_loopback_ ?
loopback_inactive : inactive;
iface_ptr->inactive4_ = iface_inactive;
} else {
iface_ptr->inactive6_ = iface_ptr->flag_loopback_ ?
loopback_inactive : inactive;
iface_ptr->inactive6_ = iface_inactive;
}
// Activate/deactivate all addresses.
const Iface::AddressCollection addresses = iface_ptr->getAddresses();
for (Iface::AddressCollection::const_iterator addr_it =
addresses.begin(); addr_it != addresses.end(); ++addr_it) {
if (addr_it->get().getFamily() == family) {
iface_ptr->setActive(addr_it->get(), !iface_inactive);
}
}
}
}
......@@ -169,18 +183,6 @@ CfgIface::use(const uint16_t family, const std::string& iface_name) {
<< "' doesn't exist in the system");
}
// If interface has already been specified.
if (iface_set_.find(name) != iface_set_.end()) {
isc_throw(DuplicateIfaceName, "interface '" << name
<< "' has already been specified");
}
// All ok, add interface.
LOG_INFO(dhcpsrv_logger, DHCPSRV_CFGMGR_ADD_IFACE)
.arg(name);
iface_set_.insert(name);
} else if (wildcard_used_) {
isc_throw(DuplicateIfaceName, "the wildcard interface '"
<< ALL_IFACES_KEYWORD << "' can only be specified once");
......@@ -192,14 +194,10 @@ CfgIface::use(const uint16_t family, const std::string& iface_name) {
}
} else if (family == AF_INET) {
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.
// The interface name includes the address on which the socket should
// be opened, we we need to split interface name and the address to
// two variables.
name = util::str::trim(iface_name.substr(0, pos));
addr_str = util::str::trim(iface_name.substr(pos + 1));
......@@ -210,10 +208,10 @@ CfgIface::use(const uint16_t family, const std::string& iface_name) {
" interface configuration");
}
// Unicast addr_stress following the interface name must not be empty.
// An address following the interface name must not be empty.
if (addr_str.empty()) {
isc_throw(InvalidIfaceName,
"empty unicast addr_stress specified in the interface"
"empty address specified in the interface"
<< " configuration");
}
......@@ -222,8 +220,8 @@ CfgIface::use(const uint16_t family, const std::string& iface_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");
<< "' must not be used in conjunction with an"
" address");
}
......@@ -239,17 +237,26 @@ CfgIface::use(const uint16_t family, const std::string& iface_name) {
// 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");
}
// Validate V6 address.
if (family == AF_INET6) {
// 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");
}
// There are valid cases where link local address can be specified to
// receive unicast traffic, e.g. sent by relay agent.
if (addr.isV6LinkLocal()) {
LOG_WARN(dhcpsrv_logger, DHCPSRV_CFGMGR_UNICAST_LINK_LOCAL)
.arg(addr.toText()).arg(name);
// There are valid cases where link local address can be specified to
// receive unicast traffic, e.g. sent by relay agent.
if (addr.isV6LinkLocal()) {
LOG_WARN(dhcpsrv_logger, DHCPSRV_CFGMGR_UNICAST_LINK_LOCAL)
.arg(addr.toText()).arg(name);
}
} else {
if (!addr.isV4()) {
isc_throw(InvalidIfaceName, "address '" << addr << "' is not"
" a valid IPv4 address");
}
}
// Interface must have this address assigned.
......@@ -261,17 +268,41 @@ CfgIface::use(const uint16_t family, const std::string& iface_name) {
// Insert address and the interface to the collection of unicast
// addresses.
if (unicast_map_.find(name) != unicast_map_.end()) {
if (address_map_.find(name) != address_map_.end()) {
isc_throw(DuplicateIfaceName, "must not specify unicast address '"
<< addr << "' for interface '" << name << "' "
"because other unicast address has already been"
" specified for this interface");
}
LOG_INFO(dhcpsrv_logger, DHCPSRV_CFGMGR_ADD_UNICAST)
.arg(addr.toText()).arg(name);
unicast_map_.insert(std::pair<std::string, IOAddress>(name, addr));
if (family == AF_INET6) {
LOG_INFO(dhcpsrv_logger, DHCPSRV_CFGMGR_ADD_UNICAST)
.arg(addr.toText()).arg(name);
} else {
LOG_INFO(dhcpsrv_logger, DHCPSRV_CFGMGR_USE_ADDRESS)
.arg(addr.toText()).arg(name);
}
address_map_.insert(std::pair<std::string, IOAddress>(name, addr));
}
// If interface name was explicitly specified and we're not parsing
// a unicast IPv6 address, add the interface to the interface set.
if ((name != ALL_IFACES_KEYWORD) &&
((family == AF_INET) || ((family == AF_INET6) && addr_str.empty()))) {
// If interface has already been specified.
if (iface_set_.find(name) != iface_set_.end()) {
isc_throw(DuplicateIfaceName, "interface '" << name
<< "' has already been specified");
}
// Log that we're listening on the specific interface and that the
// address is not explicitly specified.
if (addr_str.empty()) {
LOG_INFO(dhcpsrv_logger, DHCPSRV_CFGMGR_ADD_IFACE).arg(name);
}
iface_set_.insert(name);
}
}
} // end of isc::dhcp namespace
......
......@@ -193,14 +193,13 @@ private:
/// @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 of interfaces and addresses to which the server
/// should bind sockets.
typedef std::map<std::string, asiolink::IOAddress> ExplicitAddressMap;
/// @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 map which holds the pairs of interface names and addresses
/// for which the sockets should be opened.
ExplicitAddressMap address_map_;
/// @brief A booolean value which indicates that the wildcard interface name
/// has been specified (*).
......
......@@ -142,6 +142,10 @@ This warning message is logged when user specified a link-local address to
receive unicast traffic. The warning message is issued because it is an
uncommon use.
% DHCPSRV_CFGMGR_USE_ADDRESS listening on address %1, on interface %2
A message issued when server is configured to listen on the explicitly specified
IP address on the given interface.
% DHCPSRV_CLOSE_DB closing currently open %1 database
This is a debug message, issued when the DHCP server closes the currently
open lease database. It is issued at program shutdown and whenever
......
......@@ -42,6 +42,14 @@ public:
/// @param family One of: AF_INET or AF_INET6
bool socketOpen(const std::string& iface_name, const int family) const;
/// @brief Checks if socket is opened on the specified interface and bound
/// to a specific IPv4 address.
///
/// @param iface_name Interface name.
/// @param address Address that the socket should be bound to.
bool socketOpen(const std::string& iface_name,
const std::string& address) const;
/// @brief Checks if unicast socket is opened on interface.
///
/// @param iface_name Interface name.
......@@ -57,6 +65,15 @@ CfgIfaceTest::socketOpen(const std::string& iface_name,
const int family) const {
return (iface_mgr_test_config_.socketOpen(iface_name, family));
}
bool
CfgIfaceTest::socketOpen(const std::string& iface_name,
const std::string& address) const {
return (iface_mgr_test_config_.socketOpen(iface_name, address));
}
bool
CfgIfaceTest::unicastOpen(const std::string& iface_name) const {
return (iface_mgr_test_config_.unicastOpen(iface_name));
......@@ -99,7 +116,38 @@ TEST_F(CfgIfaceTest, explicitNamesV4) {
EXPECT_FALSE(socketOpen("eth0", AF_INET));
EXPECT_TRUE(socketOpen("eth1", AF_INET));
EXPECT_FALSE(socketOpen("lo", AF_INET));
}
// This test checks that it is possible to specify an interface and address
// on this interface to which the socket should be bound. The sockets should
// not be opened on other addresses on this interface.
TEST_F(CfgIfaceTest, explicitNamesAndAddressesV4) {
CfgIface cfg;
ASSERT_NO_THROW(cfg.use(AF_INET, "eth0/10.0.0.1"));
ASSERT_NO_THROW(cfg.use(AF_INET, "eth1/192.0.2.3"));
ASSERT_THROW(cfg.use(AF_INET, "eth1/192.0.2.5"), DuplicateIfaceName);
// Open sockets on specified interfaces and addresses.
cfg.openSockets(AF_INET, DHCP4_SERVER_PORT);
EXPECT_TRUE(socketOpen("eth0", "10.0.0.1"));
EXPECT_TRUE(socketOpen("eth1", "192.0.2.3"));
EXPECT_FALSE(socketOpen("eth1", "192.0.2.5"));
// Close all sockets and make sure they are really closed.
cfg.closeSockets();
ASSERT_FALSE(socketOpen("eth0", "10.0.0.1"));
ASSERT_FALSE(socketOpen("eth1", "192.0.2.3"));
ASSERT_FALSE(socketOpen("eth1", "192.0.2.5"));
// Now check that the socket can be bound to a different address on
// eth1.
ASSERT_NO_THROW(cfg.use(AF_INET, "eth1/192.0.2.5"));
ASSERT_THROW(cfg.use(AF_INET, "eth1/192.0.2.3"), DuplicateIfaceName);
EXPECT_FALSE(socketOpen("eth0", "10.0.0.1"));
EXPECT_FALSE(socketOpen("eth1", "192.0.2.3"));
EXPECT_TRUE(socketOpen("eth1", "192.0.2.5"));
}
// This test checks that the interface names can be explicitly selected
......@@ -118,7 +166,7 @@ TEST_F(CfgIfaceTest, explicitNamesV6) {
EXPECT_TRUE(socketOpen("eth1", AF_INET6));
EXPECT_FALSE(socketOpen("lo", AF_INET6));
// No IPv4 sockets should be present because we wanted IPv4 sockets.
// No IPv4 sockets should be present because we wanted IPv6 sockets.
EXPECT_FALSE(socketOpen("eth0", AF_INET));
EXPECT_FALSE(socketOpen("eth1", AF_INET));
EXPECT_FALSE(socketOpen("lo", AF_INET));
......
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