LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_OPEN_SOCKET).arg(port);
try {
- // First call to instance() will create IfaceMgr (it's a singleton)
- // it may throw something if things go wrong.
- // The 'true' value of the call to setMatchingPacketFilter imposes
- // that IfaceMgr will try to use the mechanism to respond directly
- // to the client which doesn't have address assigned. This capability
- // may be lacking on some OSes, so there is no guarantee that server
- // will be able to respond directly.
- IfaceMgr::instance().setMatchingPacketFilter(direct_response_desired);
-
- // Open sockets only if port is non-zero. Port 0 is used
- // for non-socket related testing.
+ // Open sockets only if port is non-zero. Port 0 is used for testing
+ // purposes in two cases:
+ // - when non-socket related testing is performed
+ // - when the particular test supplies its own packet filtering class.
if (port) {
+ // First call to instance() will create IfaceMgr (it's a singleton)
+ // it may throw something if things go wrong.
+ // The 'true' value of the call to setMatchingPacketFilter imposes
+ // that IfaceMgr will try to use the mechanism to respond directly
+ // to the client which doesn't have address assigned. This capability
+ // may be lacking on some OSes, so there is no guarantee that server
+ // will be able to respond directly.
+ IfaceMgr::instance().setMatchingPacketFilter(direct_response_desired);
+
// Create error handler. This handler will be called every time
// the socket opening operation fails. We use this handler to
// log a warning.
continue;
}
- adjustRemoteAddr(query, rsp);
-
- if (!rsp->getHops()) {
- rsp->setRemotePort(DHCP4_CLIENT_PORT);
- } else {
- rsp->setRemotePort(DHCP4_SERVER_PORT);
- }
-
- rsp->setLocalAddr(query->getLocalAddr());
- rsp->setLocalPort(DHCP4_SERVER_PORT);
- rsp->setIface(query->getIface());
- rsp->setIndex(query->getIndex());
-
// Specifies if server should do the packing
bool skip_pack = false;
// add Message Type Option (type 53)
msg->setType(msg_type);
- // DHCP Server Identifier (type 54)
- msg->addOption(getServerID());
-
// more options will be added here later
}
+void
+Dhcpv4Srv::appendServerID(const Pkt4Ptr& response) {
+ // The source address for the outbound message should have been set already.
+ // This is the address that to the best of the server's knowledge will be
+ // available from the client.
+ // @todo: perhaps we should consider some more sophisticated server id
+ // generation, but for the current use cases, it should be ok.
+ response->addOption(OptionPtr(new Option4AddrLst(DHO_DHCP_SERVER_IDENTIFIER,
+ response->getLocalAddr()))
+ );
+}
+
void
Dhcpv4Srv::appendRequestedOptions(const Pkt4Ptr& question, Pkt4Ptr& msg) {
}
void
-Dhcpv4Srv::adjustRemoteAddr(const Pkt4Ptr& question, Pkt4Ptr& msg) {
+Dhcpv4Srv::adjustIfaceData(const Pkt4Ptr& query, const Pkt4Ptr& response) {
+ adjustRemoteAddr(query, response);
+
+ // For the non-relayed message, the destination port is the client's port.
+ // For the relayed message, the server/relay port is a destination.
+ if (!response->getHops()) {
+ response->setRemotePort(DHCP4_CLIENT_PORT);
+ } else {
+ response->setRemotePort(DHCP4_SERVER_PORT);
+ }
+ // In many cases the query is sent to a broadcast address. This address
+ // appears as a local address in the query message. Therefore we can't
+ // simply copy local address from the query and use it as a source
+ // address for the response. Instead, we have to check what address our
+ // socket is bound to and use it as a source address. This operation
+ // may throw if for some reason the socket is closed.
+ // @todo Consider an optimization that we use local address from
+ // the query if this address is not broadcast.
+ SocketInfo sock_info = IfaceMgr::instance().getSocket(*query);
+ // Set local adddress, port and interface.
+ response->setLocalAddr(sock_info.addr_);
+ response->setLocalPort(DHCP4_SERVER_PORT);
+ response->setIface(query->getIface());
+ response->setIndex(query->getIndex());
+}
+
+void
+Dhcpv4Srv::adjustRemoteAddr(const Pkt4Ptr& question, const Pkt4Ptr& response) {
// Let's create static objects representing zeroed and broadcast
// addresses. We will use them further in this function to test
// other addresses against them. Since they are static, they will
// If received relayed message, server responds to the relay address.
if (question->getGiaddr() != zero_addr) {
- msg->setRemoteAddr(question->getGiaddr());
+ response->setRemoteAddr(question->getGiaddr());
// If giaddr is 0 but client set ciaddr, server should unicast the
// response to ciaddr.
} else if (question->getCiaddr() != zero_addr) {
- msg->setRemoteAddr(question->getCiaddr());
+ response->setRemoteAddr(question->getCiaddr());
// We can't unicast the response to the client when sending NAK,
// because we haven't allocated address for him. Therefore,
// NAK is broadcast.
- } else if (msg->getType() == DHCPNAK) {
- msg->setRemoteAddr(bcast_addr);
+ } else if (response->getType() == DHCPNAK) {
+ response->setRemoteAddr(bcast_addr);
// If yiaddr is set it means that we have created a lease for a client.
- } else if (msg->getYiaddr() != zero_addr) {
+ } else if (response->getYiaddr() != zero_addr) {
// If the broadcast bit is set in the flags field, we have to
// send the response to broadcast address. Client may have requested it
// because it doesn't support reception of messages on the interface
// directly to a client without address assigned.
const bool bcast_flag = ((question->getFlags() & Pkt4::FLAG_BROADCAST_MASK) != 0);
if (!IfaceMgr::instance().isDirectResponseSupported() || bcast_flag) {
- msg->setRemoteAddr(bcast_addr);
+ response->setRemoteAddr(bcast_addr);
// Client cleared the broadcast bit and we support direct responses
// so we should unicast the response to a newly allocated address -
// yiaddr.
} else {
- msg->setRemoteAddr(msg->getYiaddr());
+ response->setRemoteAddr(response ->getYiaddr());
}
// found ourselves at this point, the rational thing to do is to respond
// to the address we got the query from.
} else {
- msg->setRemoteAddr(question->getRemoteAddr());
+ response->setRemoteAddr(question->getRemoteAddr());
}
}
appendBasicOptions(discover, offer);
}
+ // Set the src/dest IP address, port and interface for the outgoing
+ // packet.
+ adjustIfaceData(discover, offer);
+
+ appendServerID(offer);
+
return (offer);
}
appendBasicOptions(request, ack);
}
+ // Set the src/dest IP address, port and interface for the outgoing
+ // packet.
+ adjustIfaceData(request, ack);
+
+ appendServerID(ack);
+
return (ack);
}
/// @brief Appends default options to a message
///
+ /// Currently it is only a Message Type option. This function does not add
+ /// the Server Identifier option as this option must be added using
+ /// @c Dhcpv4Srv::appendServerID.
+ ///
/// @param msg message object (options will be added to it)
/// @param msg_type specifies message type
void appendDefaultOptions(Pkt4Ptr& msg, uint8_t msg_type);
+ /// @brief Adds server identifier option to the server's response.
+ ///
+ /// This method adds a server identifier to the DHCPv4 message. It exects
+ /// that the local (source) address is set for this message. If address is
+ /// not set, it will throw an exception. This method also expects that the
+ /// server identifier option is not present in the specified message.
+ /// Otherwise, it will throw an exception on attempt to add a duplicate
+ /// server identifier option.
+ ///
+ /// @note This method doesn't throw exceptions by itself but the underlying
+ /// classes being used my throw. The reason for this method to not sanity
+ /// check the specified message is that it is meant to be called internally
+ /// by the @c Dhcpv4Srv class.
+ ///
+ /// @param [out] response DHCPv4 message to which the server identifier
+ /// option should be added.
+ static void appendServerID(const Pkt4Ptr& response);
+
+ /// @brief Set IP/UDP and interface parameters for the DHCPv4 response.
+ ///
+ /// This method sets the following parameters for the DHCPv4 message being
+ /// sent to a client:
+ /// - client unicast or a broadcast address,
+ /// - client or relay port,
+ /// - server address,
+ /// - server port,
+ /// - name and index of the interface which is to be used to send the
+ /// message.
+ ///
+ /// Internally it calls the @c Dhcpv4Srv::adjustRemoteAddr to figure
+ /// out the destination address (client unicast address or broadcast
+ /// address).
+ ///
+ /// The destination port is always DHCPv4 client (68) or relay (67) port,
+ /// depending if the response will be sent directly to a client (hops = 0),
+ /// or through a relay (hops > 0).
+ ///
+ /// The source port is always set to DHCPv4 server port (67).
+ ///
+ /// The interface selected for the response is always the same as the
+ /// one through which the query has been received.
+ ///
+ /// The source address for the response is the IPv4 address assigned to
+ /// the interface being used to send the response. This function uses
+ /// @c IfaceMgr to get the socket bound to the IPv4 address on the
+ /// particular interface.
+ static void adjustIfaceData(const Pkt4Ptr& query, const Pkt4Ptr& response);
+
/// @brief Sets remote addresses for outgoing packet.
///
/// This method sets the local and remote addresses on outgoing packet.
/// function.
///
/// @param question instance of a packet received by a server.
- /// @param [out] msg response packet which addresses are to be adjusted.
- void adjustRemoteAddr(const Pkt4Ptr& question, Pkt4Ptr& msg);
+ /// @param [out] response response packet which addresses are to be adjusted.
+ static void adjustRemoteAddr(const Pkt4Ptr& question, const Pkt4Ptr& response);
/// @brief Returns server-identifier option
///
// This test verifies that the destination address of the response
// message is set to giaddr, when giaddr is set to non-zero address
// in the received message.
-TEST_F(Dhcpv4SrvTest, adjustRemoteAddressRelay) {
- boost::scoped_ptr<NakedDhcpv4Srv> srv(new NakedDhcpv4Srv(0));
-
+TEST_F(Dhcpv4SrvFakeIfaceTest, adjustIfaceDataRelay) {
// Create the instance of the incoming packet.
boost::shared_ptr<Pkt4> req(new Pkt4(DHCPDISCOVER, 1234));
- // Set the giaddr to non-zero address as if it was relayed.
+ // Set the giaddr to non-zero address and hops to non-zero value
+ // as if it was relayed.
req->setGiaddr(IOAddress("192.0.2.1"));
+ req->setHops(2);
// Set ciaddr to zero. This simulates the client which applies
// for the new lease.
req->setCiaddr(IOAddress("0.0.0.0"));
// Clear broadcast flag.
req->setFlags(0x0000);
+ // Set local address, port and interface.
+ req->setLocalAddr(IOAddress("192.0.3.1"));
+ req->setLocalPort(1001);
+ req->setIface("eth0");
+ req->setIndex(1);
+
// Create a response packet. Assume that the new lease have
// been created and new address allocated. This address is
// stored in yiaddr field.
resp->setYiaddr(IOAddress("192.0.2.100"));
// Clear the remote address.
resp->setRemoteAddr(IOAddress("0.0.0.0"));
+ // Set hops value for the response.
+ resp->setHops(req->getHops());
// This function never throws.
- ASSERT_NO_THROW(srv->adjustRemoteAddr(req, resp));
+ ASSERT_NO_THROW(NakedDhcpv4Srv::adjustIfaceData(req, resp));
// Now the destination address should be relay's address.
EXPECT_EQ("192.0.2.1", resp->getRemoteAddr().toText());
+ // The query has been relayed, so the response must be sent to the port 67.
+ EXPECT_EQ(DHCP4_SERVER_PORT, resp->getRemotePort());
+ // Local address should be copied from the query message.
+ EXPECT_EQ("192.0.3.1", resp->getLocalAddr().toText());
+ // The local port is always DHCPv4 server port 67.
+ EXPECT_EQ(DHCP4_SERVER_PORT, resp->getLocalPort());
+ // We will send response over the same interface which was used to receive
+ // query.
+ EXPECT_EQ("eth0", resp->getIface());
+ EXPECT_EQ(1, resp->getIndex());
// Let's do another test and set other fields: ciaddr and
// flags. By doing it, we want to make sure that the relay
// Clear remote address.
resp->setRemoteAddr(IOAddress("0.0.0.0"));
- ASSERT_NO_THROW(srv->adjustRemoteAddr(req, resp));
+ ASSERT_NO_THROW(NakedDhcpv4Srv::adjustIfaceData(req, resp));
// Response should be sent back to the relay address.
EXPECT_EQ("192.0.2.50", resp->getRemoteAddr().toText());
// is set to ciaddr when giaddr is set to zero and the ciaddr is set to
// non-zero address in the received message. This is the case when the
// client is in Renew or Rebind state.
-TEST_F(Dhcpv4SrvTest, adjustRemoteAddressRenewRebind) {
- boost::scoped_ptr<NakedDhcpv4Srv> srv(new NakedDhcpv4Srv(0));
-
+TEST_F(Dhcpv4SrvFakeIfaceTest, adjustIfaceDataRenew) {
// Create instance of the incoming packet.
boost::shared_ptr<Pkt4> req(new Pkt4(DHCPDISCOVER, 1234));
// whether to unicast the response to the acquired address or
// broadcast it.
req->setFlags(Pkt4::FLAG_BROADCAST_MASK);
+ // This is a direct message, so the hops should be cleared.
+ req->setHops(0);
+ // Set local unicast address as if we are renewing a lease.
+ req->setLocalAddr(IOAddress("192.0.3.1"));
+ // Request is received on the DHCPv4 server port.
+ req->setLocalPort(DHCP4_SERVER_PORT);
+ // Set the interface. The response should be sent over the same interface.
+ req->setIface("eth0");
+ req->setIndex(1);
// Create a response.
boost::shared_ptr<Pkt4> resp(new Pkt4(DHCPOFFER, 1234));
resp->setYiaddr(IOAddress("192.0.2.13"));
// Clear the remote address.
resp->setRemoteAddr(IOAddress("0.0.0.0"));
+ // Copy hops value from the query.
+ resp->setHops(req->getHops());
- ASSERT_NO_THROW(srv->adjustRemoteAddr(req, resp));
+ ASSERT_NO_THROW(NakedDhcpv4Srv::adjustIfaceData(req, resp));
// Check that server responds to ciaddr
EXPECT_EQ("192.0.2.15", resp->getRemoteAddr().toText());
+ // The query was non-relayed, so the response should be sent to a DHCPv4
+ // client port 68.
+ EXPECT_EQ(DHCP4_CLIENT_PORT, resp->getRemotePort());
+ // The response should be sent from the unicast address on which the
+ // query has been received.
+ EXPECT_EQ("192.0.3.1", resp->getLocalAddr().toText());
+ // The response should be sent from the DHCPv4 server port.
+ EXPECT_EQ(DHCP4_SERVER_PORT, resp->getLocalPort());
+ // The interface data should match the data in the query.
+ EXPECT_EQ("eth0", resp->getIface());
+ EXPECT_EQ(1, resp->getIndex());
+
}
// This test verifies that the destination address of the response message
// of the response should be set to yiaddr if server supports direct responses
// to the client which doesn't have an address yet or broadcast if the server
// doesn't support direct responses.
-TEST_F(Dhcpv4SrvTest, adjustRemoteAddressSelect) {
- boost::scoped_ptr<NakedDhcpv4Srv> srv(new NakedDhcpv4Srv(0));
-
+TEST_F(Dhcpv4SrvFakeIfaceTest, adjustIfaceDataSelect) {
// Create instance of the incoming packet.
boost::shared_ptr<Pkt4> req(new Pkt4(DHCPDISCOVER, 1234));
// Let's clear the broadcast flag.
req->setFlags(0);
+ // This is a non-relayed message, so let's clear hops count.
+ req->setHops(0);
+ // The query is sent to the broadcast address in the Select state.
+ req->setLocalAddr(IOAddress("255.255.255.255"));
+ // The query has been received on the DHCPv4 server port 67.
+ req->setLocalPort(DHCP4_SERVER_PORT);
+ // Set the interface. The response should be sent via the same interface.
+ req->setIface("eth0");
+ req->setIndex(1);
+
// Create a response.
boost::shared_ptr<Pkt4> resp(new Pkt4(DHCPOFFER, 1234));
// Assign some new address for this client.
resp->setYiaddr(IOAddress("192.0.2.13"));
-
// Clear the remote address.
resp->setRemoteAddr(IOAddress("0.0.0.0"));
+ // Copy hops count.
+ resp->setHops(req->getHops());
+
+ // We want to test the case, when the server (packet filter) doesn't support
+ // ddirect responses to the client which doesn't have an address yet. In
+ // case, the server should send its response to the broadcast address.
+ // We can control whether the current packet filter returns that its support
+ // direct responses or not.
+ current_pkt_filter_->direct_resp_supported_ = false;
// When running unit tests, the IfaceMgr is using the default Packet
// Filtering class, PktFilterInet. This class does not support direct
// are zero and client has just got new lease, the assigned address is
// carried in yiaddr. In order to send this address to the client,
// server must broadcast its response.
- ASSERT_NO_THROW(srv->adjustRemoteAddr(req, resp));
+ ASSERT_NO_THROW(NakedDhcpv4Srv::adjustIfaceData(req, resp));
// Check that the response is sent to broadcast address as the
// server doesn't have capability to respond directly.
EXPECT_EQ("255.255.255.255", resp->getRemoteAddr().toText());
+ // Although the query has been sent to the broadcast address, the
+ // server should select a unicast address on the particular interface
+ // as a source address for the response.
+ EXPECT_EQ("192.0.3.1", resp->getLocalAddr().toText());
+
+ // The response should be sent from the DHCPv4 server port.
+ EXPECT_EQ(DHCP4_SERVER_PORT, resp->getLocalPort());
+
+ // The response should be sent via the same interface through which
+ // query has been received.
+ EXPECT_EQ("eth0", resp->getIface());
+ EXPECT_EQ(1, resp->getIndex());
+
// We also want to test the case when the server has capability to
// respond directly to the client which is not configured. Server
// makes decision whether it responds directly or broadcast its
- // response based on the capability reported by IfaceMgr. In order
- // to set this capability we have to provide a dummy Packet Filter
- // class which would report the support for direct responses.
- // This class is called PktFilterTest.
- IfaceMgr::instance().setPacketFilter(PktFilterPtr(new PktFilterTest()));
+ // response based on the capability reported by IfaceMgr. We can
+ // control whether the current packet filter returns that it supports
+ // direct responses or not.
+ current_pkt_filter_->direct_resp_supported_ = true;
// Now we expect that the server will send its response to the
// address assigned for the client.
- ASSERT_NO_THROW(srv->adjustRemoteAddr(req, resp));
+ ASSERT_NO_THROW(NakedDhcpv4Srv::adjustIfaceData(req, resp));
EXPECT_EQ("192.0.2.13", resp->getRemoteAddr().toText());
}
// query. Client sets this flag to indicate that it can't receive direct
// responses from the server when it doesn't have its interface configured.
// Server must respect broadcast flag.
-TEST_F(Dhcpv4SrvTest, adjustRemoteAddressBroadcast) {
- boost::scoped_ptr<NakedDhcpv4Srv> srv(new NakedDhcpv4Srv(0));
-
+TEST_F(Dhcpv4SrvFakeIfaceTest, adjustIfaceDataBroadcast) {
// Create instance of the incoming packet.
boost::shared_ptr<Pkt4> req(new Pkt4(DHCPDISCOVER, 1234));
req->setGiaddr(IOAddress("0.0.0.0"));
// Clear client address as it hasn't got any address configured yet.
req->setCiaddr(IOAddress("0.0.0.0"));
+ // The query is sent to the broadcast address in the Select state.
+ req->setLocalAddr(IOAddress("255.255.255.255"));
+ // The query has been received on the DHCPv4 server port 67.
+ req->setLocalPort(DHCP4_SERVER_PORT);
+ // Set the interface. The response should be sent via the same interface.
+ req->setIface("eth0");
+ req->setIndex(1);
// Let's set the broadcast flag.
req->setFlags(Pkt4::FLAG_BROADCAST_MASK);
// Clear the remote address.
resp->setRemoteAddr(IOAddress("0.0.0.0"));
- // When running unit tests, the IfaceMgr is using the default Packet
- // Filtering class, PktFilterInet. This class does not support direct
- // responses to the clients without address assigned. If giaddr and
- // ciaddr are zero and client has just got the new lease, the assigned
- // address is carried in yiaddr. In order to send this address to the
- // client, server must send the response to the broadcast address when
- // direct response is not supported. This conflicts with the purpose
- // of this test which is supposed to verify that responses are sent
- // to broadcast address only, when broadcast flag is set. Therefore,
- // in order to simulate that direct responses are supported we have
- // to replace the default packet filtering class with a dummy class
- // which reports direct response capability.
- IfaceMgr::instance().setPacketFilter(PktFilterPtr(new PktFilterTest()));
-
- ASSERT_NO_THROW(srv->adjustRemoteAddr(req, resp));
+ ASSERT_NO_THROW(NakedDhcpv4Srv::adjustIfaceData(req, resp));
// Server must repond to broadcast address when client desired that
// by setting the broadcast flag in its request.
EXPECT_EQ("255.255.255.255", resp->getRemoteAddr().toText());
+
+ // Although the query has been sent to the broadcast address, the
+ // server should select a unicast address on the particular interface
+ // as a source address for the response.
+ EXPECT_EQ("192.0.3.1", resp->getLocalAddr().toText());
+
+ // The response should be sent from the DHCPv4 server port.
+ EXPECT_EQ(DHCP4_SERVER_PORT, resp->getLocalPort());
+
+ // The response should be sent via the same interface through which
+ // query has been received.
+ EXPECT_EQ("eth0", resp->getIface());
+ EXPECT_EQ(1, resp->getIndex());
+
+}
+
+// This test verifies that the server identifier option is appended to
+// a specified DHCPv4 message and the server identifier is correct.
+TEST_F(Dhcpv4SrvTest, appendServerID) {
+ Pkt4Ptr response(new Pkt4(DHCPDISCOVER, 1234));
+ // Set a local address. It is required by the function under test
+ // to create the Server Identifier option.
+ response->setLocalAddr(IOAddress("192.0.3.1"));
+
+ // Append the Server Identifier.
+ ASSERT_NO_THROW(NakedDhcpv4Srv::appendServerID(response));
+
+ // Make sure that the option has been added.
+ OptionPtr opt = response->getOption(DHO_DHCP_SERVER_IDENTIFIER);
+ ASSERT_TRUE(opt);
+ Option4AddrLstPtr opt_server_id =
+ boost::dynamic_pointer_cast<Option4AddrLst>(opt);
+ ASSERT_TRUE(opt_server_id);
+
+ // The option is represented as a list of IPv4 addresses but with
+ // only one address added.
+ Option4AddrLst::AddressContainer addrs = opt_server_id->getAddresses();
+ ASSERT_EQ(1, addrs.size());
+ // This address should match the local address of the packet.
+ EXPECT_EQ("192.0.3.1", addrs[0].toText());
}
// Sanity check. Verifies that both Dhcpv4Srv and its derived
// @todo: Implement tests for rejecting renewals
// This test verifies if the sanityCheck() really checks options presence.
-TEST_F(Dhcpv4SrvFakeIfaceTest, sanityCheck) {
+TEST_F(Dhcpv4SrvTest, sanityCheck) {
boost::scoped_ptr<NakedDhcpv4Srv> srv;
ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));