Commit ff71887c authored by Marcin Siodelski's avatar Marcin Siodelski
Browse files

[master] Merge branch 'trac3539'

parents 173fbd24 b0453ead
......@@ -348,9 +348,19 @@ url="http://jsonviewer.stack.hu/"/>.
with explicit interface names:
<screen>
"Dhcp4": { <userinput>"interfaces": [ "eth1", "eth3", "*" ]</userinput>, ... }</screen>
It is anticipated that this will form of usage only be used where it is desired to
It is anticipated that this form of usage will only be used when it is desired to
temporarily override a list of interface names and listen on all interfaces.
</para>
<para>Some deployments of the DHCP servers require that the servers listen
on the interfaces with multiple IPv4 addresses configured. In some cases,
multiple instances of the DHCP servers are running concurrently and each
instance should be bound to a different address on the particular interface.
In these situations, the address to use can be selected by
appending an IPv4 address to the interface name in the following manner:
<screen>
"Dhcp4": { <userinput>"interfaces": [ "eth1/10.0.0.1", "eth3/192.0.2.3" ]</userinput>, ... }</screen>
Note that only one address can be specified on each interface.
</para>
</section>
<section id="ipv4-subnet-id">
......
......@@ -3002,6 +3002,45 @@ TEST_F(Dhcp4ParserTest, allInterfaces) {
ASSERT_TRUE(test_config.socketOpen("eth1", AF_INET));
}
// This test verifies that it is possible to select subset of interfaces
// and addresses.
TEST_F(Dhcp4ParserTest, selectedInterfacesAndAddresses) {
IfaceMgrTestConfig test_config(true);
ConstElementPtr x;
string config = "{ \"interfaces\": [ \"eth0/10.0.0.1\", \"eth1/192.0.2.3\" ],"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"valid-lifetime\": 4000 }";
ElementPtr json = Element::fromJSON(config);
ConstElementPtr status;
// Make sure the config manager is clean and there is no hanging
// interface configuration.
ASSERT_FALSE(test_config.socketOpen("eth0", "10.0.0.1"));
ASSERT_FALSE(test_config.socketOpen("eth1", "192.0.2.3"));
ASSERT_FALSE(test_config.socketOpen("eth1", "192.0.2.5"));
// Apply configuration.
EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
ASSERT_TRUE(status);
checkResult(status, 0);
CfgMgr::instance().getStagingCfg()->
getCfgIface().openSockets(AF_INET, 10000);
// An address on eth0 was selected
EXPECT_TRUE(test_config.socketOpen("eth0", "10.0.0.1"));
// The 192.0.2.3 address on eth1 was selected.
EXPECT_TRUE(test_config.socketOpen("eth1", "192.0.2.3"));
// The 192.0.2.5 was not selected, thus the socket should not
// be bound to this address.
EXPECT_FALSE(test_config.socketOpen("eth1", "192.0.2.5"));
}
// This test checks the ability of the server to parse a configuration
// containing a full, valid dhcp-ddns (D2ClientConfig) entry.
TEST_F(Dhcp4ParserTest, d2ClientConfig) {
......
......@@ -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(true)));
}
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);
}
}
......@@ -487,8 +514,8 @@ IfaceMgr::openSockets4(const uint16_t port, const bool use_bcast,
addr != addrs.end();
++addr) {
// Skip all but V4 addresses.
if (!addr->isV4()) {
// Skip non-IPv4 addresses and those that weren't selected..
if (!addr->get().isV4() || !addr->isSpecified()) {
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.
///
......@@ -256,7 +258,15 @@ public:
/// @return hardware type
uint16_t getHWType() const { return hardware_type_; }
/// @brief Returns all interfaces available on an interface.
/// @brief Returns all addresses available on an interface.
///
/// The returned addresses are encapsulated in the @c util::OptionalValue
/// class to be able to selectively flag some of the addresses as active
/// (when optional value is specified) or inactive (when optional value
/// is specified). If the address is marked as active, the
/// @c IfaceMgr::openSockets4 method will open socket and bind to this
/// address. Otherwise, it will not bind any socket to this address.
/// This is useful when an interface has multiple IPv4 addresses assigned.
///
/// Care should be taken to not use this collection after Iface object
/// ceases to exist. That is easy in most cases as Iface objects are
......@@ -291,9 +301,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,20 @@ 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 user has specified an address to listen on, let's activate
// only this address.
if (addr != address_map_.end()) {
iface->setActive(addr->second, true);
// Otherwise, activate first one.
} else {
IOAddress address(0);
if (iface->getAddress4(address)) {
iface->setActive(address, true);
}
}
} else {
iface->inactive6_ = false;
......@@ -79,8 +93,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,
......@@ -121,6 +135,7 @@ void
CfgIface::reset() {
wildcard_used_ = false;
iface_set_.clear();
address_map_.clear();
}
void
......@@ -130,13 +145,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 +194,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 +205,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 +219,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 +231,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 +248,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.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 +279,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()) {
isc_throw(DuplicateIfaceName, "must not specify unicast address '"
if (address_map_.find(name) != address_map_.end()) {
isc_throw(DuplicateIfaceName, "must not select address '"
<< addr << "' for interface '" << name << "' "
"because other unicast address has already been"
"because another 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_USE_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
......
......@@ -12,8 +12,8 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#ifndef IFACE_CFG_H
#define IFACE_CFG_H
#ifndef CFG_IFACE_H
#define CFG_IFACE_H
#include <asiolink/io_address.h>
#include <map>
......@@ -193,14 +193,13 @@ private: