Commit 58736370 authored by Marcin Siodelski's avatar Marcin Siodelski

[3688] Pass DHCPv4Exchange objects instead of query/resp in the dhcpv4_srv.

parent 6364a03c
This diff is collapsed.
......@@ -43,6 +43,104 @@ public:
isc::Exception(file, line, what) { };
};
/// @brief DHCPv4 message exchange.
///
/// This class represents the DHCPv4 message exchange. The message exchange
/// consists of the single client message, server response to this message
/// and the mechanisms to generate the server's response. The server creates
/// the instance of the @c DHCPv4Exchange for each inbound message that it
/// accepts for processing.
///
/// The use of the @c DHCPv4Exchange object as a central repository of
/// information about the message exchange simplifies the API of the
/// @c Dhcpv4Srv class.
///
/// Another benefit of using this class is that different methods of the
/// @c Dhcpv4Srv may share information. For example, the constructor of this
/// class selects the subnet and multiple methods of @c Dhcpv4Srv use this
/// subnet, without the need to select it again.
///
/// @todo This is the initial version of this class. In the future a lot of
/// code from the @c Dhcpv4Srv class will be migrated here.
class DHCPv4Exchange {
public:
/// @brief Constructor.
///
/// The constructor selects the subnet for the query and checks for the
/// static host reservations for the client which has sent the message.
/// The information about the reservations is stored in the
/// @c AllocEngine::ClientContext4 object, which can be obtained by
/// calling the @c getContext.
///
/// @param alloc_engine Pointer to the instance of the Allocation Engine
/// used by the server.
/// @param query Pointer to the client message.
DHCPv4Exchange(const AllocEnginePtr& alloc_engine, const Pkt4Ptr& query);
/// @brief Initializes the instance of the response message.
///
/// The type of the response depends on the type of the query message.
/// For the DHCPDISCOVER the DHCPOFFER is created. For the DHCPREQUEST
/// and DHCPINFORM the DHCPACK is created. For the DHCPRELEASE the
/// response is not initialized.
void initResponse();
/// @brief Selects the subnet for the message processing.
///
/// The pointer to the selected subnet is stored in the @c ClientContext4
/// structure.
void selectSubnet();
/// @brief Selects the subnet for the message processing.
///
/// @todo This variant of the @c selectSubnet method is static and public so
/// as it may be invoked by the @c Dhcpv4Srv object. This is temporary solution
/// and the function will go away once the server code fully supports the use
/// of this class and it obtains the subnet from the context returned by the
/// @c getContext method.
///
/// @param query Pointer to the client's message.
/// @return Pointer to the selected subnet or NULL if no suitable subnet
/// has been found.
static Subnet4Ptr selectSubnet(const Pkt4Ptr& query);
/// @brief Returns the pointer to the query from the client.
Pkt4Ptr getQuery() const {
return (query_);
}
/// @brief Returns the pointer to the server's response.
///
/// The returned pointer is NULL if the query type is DHCPRELEASE or DHCPDECLINE.
Pkt4Ptr getResponse() const {
return (resp_);
}
/// @brief Removes the response message by resetting the pointer to NULL.
void deleteResponse() {
resp_.reset();
}
/// @brief Returns the copy of the context for the Allocation engine.
AllocEngine::ClientContext4Ptr getContext() const {
return (context_);
}
private:
/// @brief Pointer to the allocation engine used by the server.
AllocEnginePtr alloc_engine_;
/// @brief Pointer to the DHCPv4 message sent by the client.
Pkt4Ptr query_;
/// @brief Pointer to the DHCPv4 message to be sent to the client.
Pkt4Ptr resp_;
/// @brief Context for use with allocation engine.
AllocEngine::ClientContext4Ptr context_;
};
/// @brief Type representing the pointer to the @c DHCPv4Exchange.
typedef boost::shared_ptr<DHCPv4Exchange> DHCPv4ExchangePtr;
/// @brief DHCPv4 server service.
///
/// This singleton class represents DHCPv4 server. It contains all
......@@ -269,15 +367,15 @@ protected:
bool acceptServerId(const Pkt4Ptr& pkt) const;
//@}
/// @brief verifies if specified packet meets RFC requirements
/// @brief Verifies if specified packet meets RFC requirements
///
/// Checks if mandatory option is really there, that forbidden option
/// is not there, and that client-id or server-id appears only once.
///
/// @param pkt packet to be checked
/// @param ex DHCPv4 exchange holding the client's message to be checked.
/// @param serverid expectation regarding server-id option
/// @throw RFCViolation if any issues are detected
static void sanityCheck(const Pkt4Ptr& pkt, RequirementLevel serverid);
static void sanityCheck(const DHCPv4Exchange& ex, RequirementLevel serverid);
/// @brief Processes incoming DISCOVER and returns response.
///
......@@ -327,18 +425,18 @@ protected:
/// Some fields are copied from client's message into server's response,
/// e.g. client HW address, number of hops, transaction-id etc.
///
/// @param question any message sent by client
/// @param answer any message server is going to send as response
void copyDefaultFields(const Pkt4Ptr& question, Pkt4Ptr& answer);
/// @param ex The exchange holding both the client's message and the
/// server's response.
void copyDefaultFields(DHCPv4Exchange& ex);
/// @brief Appends options requested by client.
///
/// This method assigns options that were requested by client
/// (sent in PRL) or are enforced by server.
///
/// @param question DISCOVER or REQUEST message from a client.
/// @param msg outgoing message (options will be added here)
void appendRequestedOptions(const Pkt4Ptr& question, Pkt4Ptr& msg);
/// @param ex The exchange holding both the client's message and the
/// server's response.
void appendRequestedOptions(DHCPv4Exchange& ex);
/// @brief Appends requested vendor options as requested by client.
///
......@@ -348,9 +446,9 @@ protected:
/// options, each with unique vendor-id). Vendor options are requested
/// using separate options within their respective vendor-option spaces.
///
/// @param question DISCOVER or REQUEST message from a client.
/// @param answer outgoing message (options will be added here)
void appendRequestedVendorOptions(const Pkt4Ptr& question, Pkt4Ptr& answer);
/// @param ex The exchange holding both the client's message and the
/// server's response.
void appendRequestedVendorOptions(DHCPv4Exchange& ex);
/// @brief Assigns a lease and appends corresponding options
///
......@@ -358,28 +456,27 @@ protected:
/// client and assigning it. Options corresponding to the lease
/// are added to specific message.
///
/// @param question DISCOVER or REQUEST message from client
/// @param answer OFFER or ACK/NAK message (lease options will be
/// added here)
///
/// This method may reset the @c answer shared pointer to indicate
/// that the response should not be sent to the client. The caller
/// must check if the @c answer is null after calling this method.
void assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer);
/// This method may reset the pointer to the response in the @c ex object
/// to indicate that the response should not be sent to the client.
/// The caller must check if the response is is null after calling
/// this method.
///
/// The response type in the @c ex object may be set to DHCPACK or DHCPNAK.
///
/// @param ex DHCPv4 exchange holding the client's message to be checked.
void assignLease(DHCPv4Exchange& ex);
/// @brief Append basic options if they are not present.
///
/// This function adds the following basic options if they
/// are not yet added to the message:
/// are not yet added to the response message:
/// - Subnet Mask,
/// - Router,
/// - Name Server,
/// - Domain Name.
///
/// @param question DISCOVER or REQUEST message from a client.
/// @param msg the message to add options to.
void appendBasicOptions(const Pkt4Ptr& question, Pkt4Ptr& msg);
/// @param ex DHCPv4 exchange holding the client's message to be checked.
void appendBasicOptions(DHCPv4Exchange& ex);
/// @brief Processes Client FQDN and Hostname Options sent by a client.
///
......@@ -416,9 +513,9 @@ protected:
/// This function does not throw. It simply logs the debug message if the
/// processing of the FQDN or Hostname failed.
///
/// @param query A DISCOVER or REQUEST message from a client.
/// @param [out] answer A response message to be sent to a client.
void processClientName(const Pkt4Ptr& query, Pkt4Ptr& answer);
/// @param ex The exchange holding both the client's message and the
/// server's response.
void processClientName(DHCPv4Exchange& ex);
/// @brief this is a prefix added to the contend of vendor-class option
///
......@@ -437,10 +534,9 @@ private:
/// the FQDN option to be sent back to the client in the server's
/// response.
///
/// @param fqdn An DHCPv4 Client FQDN %Option sent by a client.
/// @param [out] answer A response message to be sent to a client.
void processClientFqdnOption(const Option4ClientFqdnPtr& fqdn,
Pkt4Ptr& answer);
/// @param ex The exchange holding both the client's message and the
/// server's response.
void processClientFqdnOption(DHCPv4Exchange& ex);
/// @brief Process Hostname %Option sent by a client.
///
......@@ -450,11 +546,9 @@ private:
/// prepare the Hostname option to be sent back to the client in the
/// server's response.
///
/// @param opt_hostname An @c OptionString object encapsulating the Hostname
/// %Option.
/// @param [out] answer A response message to be sent to a client.
void processHostnameOption(const OptionStringPtr& opt_hostname,
Pkt4Ptr& answer);
/// @param ex The exchange holding both the client's message and the
/// server's response.
void processHostnameOption(DHCPv4Exchange& ex);
protected:
......@@ -499,16 +593,13 @@ protected:
/// @param reply server's response (ACK or NAK)
void renewLease(const Pkt4Ptr& renew, Pkt4Ptr& reply);
/// @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.
/// @brief Appends default options to a message.
///
/// This method is currently no-op.
///
/// @param msg message object (options will be added to it)
/// @param msg_type specifies message type
void appendDefaultOptions(Pkt4Ptr& msg, uint8_t msg_type);
/// @param ex The exchange holding both the client's message and the
/// server's response.
void appendDefaultOptions(DHCPv4Exchange& ex);
/// @brief Adds server identifier option to the server's response.
///
......@@ -527,9 +618,9 @@ protected:
/// @note This method is static because it is not dependent on the class
/// state.
///
/// @param [out] response DHCPv4 message to which the server identifier
/// option should be added.
static void appendServerID(const Pkt4Ptr& response);
/// @param ex The exchange holding both the client's message and the
/// server's response.
static void appendServerID(DHCPv4Exchange& ex);
/// @brief Set IP/UDP and interface parameters for the DHCPv4 response.
///
......@@ -561,7 +652,10 @@ protected:
///
/// @note This method is static because it is not dependent on the class
/// state.
static void adjustIfaceData(const Pkt4Ptr& query, const Pkt4Ptr& response);
///
/// @param ex The exchange holding both the client's message and the
/// server's response.
static void adjustIfaceData(DHCPv4Exchange& ex);
/// @brief Sets remote addresses for outgoing packet.
///
......@@ -579,11 +673,9 @@ protected:
/// @note This method is static because it is not dependent on the class
/// state.
///
/// @param question instance of a packet received by a server.
/// @param [out] response response packet which addresses are to be
/// adjusted.
static void adjustRemoteAddr(const Pkt4Ptr& question,
const Pkt4Ptr& response);
/// @param ex The exchange holding both the client's message and the
/// server's response.
static void adjustRemoteAddr(DHCPv4Exchange& ex);
/// @brief converts server-id to text
/// Converts content of server-id option to a text representation, e.g.
......@@ -668,6 +760,12 @@ protected:
/// @return true if successful, false otherwise (will prevent sending response)
bool classSpecificProcessing(const Pkt4Ptr& query, const Pkt4Ptr& rsp);
/// @brief Allocation Engine.
/// Pointer to the allocation engine that we are currently using
/// It must be a pointer, because we will support changing engines
/// during normal operation (e.g. to use different allocators)
boost::shared_ptr<AllocEngine> alloc_engine_;
private:
/// @brief Constructs netmask option based on subnet4
......@@ -685,12 +783,6 @@ private:
/// @param errmsg An error message containing a cause of the failure.
static void ifaceMgrSocket4ErrorHandler(const std::string& errmsg);
/// @brief Allocation Engine.
/// Pointer to the allocation engine that we are currently using
/// It must be a pointer, because we will support changing engines
/// during normal operation (e.g. to use different allocators)
boost::shared_ptr<AllocEngine> alloc_engine_;
uint16_t port_; ///< UDP port number on which server listens.
bool use_bcast_; ///< Should broadcast be enabled on sockets (if true).
......
......@@ -86,10 +86,10 @@ TEST_F(Dhcpv4SrvTest, adjustIfaceDataRelay) {
req->setIface("eth1");
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.
boost::shared_ptr<Pkt4> resp(new Pkt4(DHCPOFFER, 1234));
// Create the exchange using the req.
DHCPv4Exchange ex = createExchange(req);
Pkt4Ptr resp = ex.getResponse();
resp->setYiaddr(IOAddress("192.0.1.100"));
// Clear the remote address.
resp->setRemoteAddr(IOAddress("0.0.0.0"));
......@@ -97,7 +97,7 @@ TEST_F(Dhcpv4SrvTest, adjustIfaceDataRelay) {
resp->setHops(req->getHops());
// This function never throws.
ASSERT_NO_THROW(NakedDhcpv4Srv::adjustIfaceData(req, resp));
ASSERT_NO_THROW(NakedDhcpv4Srv::adjustIfaceData(ex));
// Now the destination address should be relay's address.
EXPECT_EQ("192.0.1.1", resp->getRemoteAddr().toText());
......@@ -123,7 +123,7 @@ TEST_F(Dhcpv4SrvTest, adjustIfaceDataRelay) {
// Clear remote address.
resp->setRemoteAddr(IOAddress("0.0.0.0"));
ASSERT_NO_THROW(NakedDhcpv4Srv::adjustIfaceData(req, resp));
ASSERT_NO_THROW(NakedDhcpv4Srv::adjustIfaceData(ex));
// Response should be sent back to the relay address.
EXPECT_EQ("192.0.1.50", resp->getRemoteAddr().toText());
......@@ -163,8 +163,10 @@ TEST_F(Dhcpv4SrvTest, adjustIfaceDataRenew) {
req->setIface("eth1");
req->setIndex(1);
// Create a response.
boost::shared_ptr<Pkt4> resp(new Pkt4(DHCPOFFER, 1234));
// Create the exchange using the req.
DHCPv4Exchange ex = createExchange(req);
Pkt4Ptr resp = ex.getResponse();
// Let's extend the lease for the client in such a way that
// it will actually get different address. The response
// should not be sent to this address but rather to ciaddr
......@@ -175,7 +177,7 @@ TEST_F(Dhcpv4SrvTest, adjustIfaceDataRenew) {
// Copy hops value from the query.
resp->setHops(req->getHops());
ASSERT_NO_THROW(NakedDhcpv4Srv::adjustIfaceData(req, resp));
ASSERT_NO_THROW(NakedDhcpv4Srv::adjustIfaceData(ex));
// Check that server responds to ciaddr
EXPECT_EQ("192.0.1.15", resp->getRemoteAddr().toText());
......@@ -225,8 +227,9 @@ TEST_F(Dhcpv4SrvTest, adjustIfaceDataSelect) {
req->setIface("eth1");
req->setIndex(1);
// Create a response.
boost::shared_ptr<Pkt4> resp(new Pkt4(DHCPOFFER, 1234));
// Create the exchange using the req.
DHCPv4Exchange ex = createExchange(req);
Pkt4Ptr resp = ex.getResponse();
// Assign some new address for this client.
resp->setYiaddr(IOAddress("192.0.1.13"));
// Clear the remote address.
......@@ -247,7 +250,7 @@ TEST_F(Dhcpv4SrvTest, adjustIfaceDataSelect) {
// 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(NakedDhcpv4Srv::adjustIfaceData(req, resp));
ASSERT_NO_THROW(NakedDhcpv4Srv::adjustIfaceData(ex));
// Check that the response is sent to broadcast address as the
// server doesn't have capability to respond directly.
......@@ -276,7 +279,7 @@ TEST_F(Dhcpv4SrvTest, adjustIfaceDataSelect) {
// Now we expect that the server will send its response to the
// address assigned for the client.
ASSERT_NO_THROW(NakedDhcpv4Srv::adjustIfaceData(req, resp));
ASSERT_NO_THROW(NakedDhcpv4Srv::adjustIfaceData(ex));
EXPECT_EQ("192.0.1.13", resp->getRemoteAddr().toText());
}
......@@ -308,15 +311,17 @@ TEST_F(Dhcpv4SrvTest, adjustIfaceDataBroadcast) {
// Let's set the broadcast flag.
req->setFlags(Pkt4::FLAG_BROADCAST_MASK);
// Create a response.
boost::shared_ptr<Pkt4> resp(new Pkt4(DHCPOFFER, 1234));
// Create the exchange using the req.
DHCPv4Exchange ex = createExchange(req);
Pkt4Ptr resp = ex.getResponse();
// Assign some new address for this client.
resp->setYiaddr(IOAddress("192.0.1.13"));
// Clear the remote address.
resp->setRemoteAddr(IOAddress("0.0.0.0"));
ASSERT_NO_THROW(NakedDhcpv4Srv::adjustIfaceData(req, resp));
ASSERT_NO_THROW(NakedDhcpv4Srv::adjustIfaceData(ex));
// Server must respond to broadcast address when client desired that
// by setting the broadcast flag in its request.
......@@ -340,6 +345,9 @@ TEST_F(Dhcpv4SrvTest, adjustIfaceDataBroadcast) {
// This test verifies that exception is thrown of the invalid combination
// of giaddr and hops is specified in a client's message.
TEST_F(Dhcpv4SrvTest, adjustIfaceDataInvalid) {
IfaceMgrTestConfig test_config(true);
IfaceMgr::instance().openSockets4();
boost::shared_ptr<Pkt4> req(new Pkt4(DHCPDISCOVER, 1234));
// The hops and giaddr values are used to determine if the client's
......@@ -360,27 +368,32 @@ TEST_F(Dhcpv4SrvTest, adjustIfaceDataInvalid) {
req->setIface("eth0");
req->setIndex(1);
// Create a response.
boost::shared_ptr<Pkt4> resp(new Pkt4(DHCPOFFER, 1234));
// Create the exchange using the req.
DHCPv4Exchange ex = createExchange(req);
Pkt4Ptr resp = ex.getResponse();
// 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"));
EXPECT_THROW(NakedDhcpv4Srv::adjustIfaceData(req, resp), isc::BadValue);
EXPECT_THROW(NakedDhcpv4Srv::adjustIfaceData(ex), isc::BadValue);
}
// 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));
Pkt4Ptr query(new Pkt4(DHCPDISCOVER, 1234));
DHCPv4Exchange ex = createExchange(query);
Pkt4Ptr response = ex.getResponse();
// 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));
ASSERT_NO_THROW(NakedDhcpv4Srv::appendServerID(ex));
// Make sure that the option has been added.
OptionPtr opt = response->getOption(DHO_DHCP_SERVER_IDENTIFIER);
......@@ -783,10 +796,10 @@ TEST_F(Dhcpv4SrvTest, requestEchoClientId) {
checkClientId(ack, clientid);
CfgMgr::instance().echoClientId(false);
ack = srv.processDiscover(dis);
ack = srv.processRequest(dis);
// Check if we get response at all
checkResponse(ack, DHCPOFFER, 1234);
checkResponse(ack, DHCPACK, 1234);
checkClientId(ack, clientid);
}
......@@ -947,25 +960,28 @@ TEST_F(Dhcpv4SrvTest, sanityCheck) {
pkt->setHWAddr(generateHWAddr(6));
// Server-id is optional for information-request, so
EXPECT_NO_THROW(NakedDhcpv4Srv::sanityCheck(pkt, Dhcpv4Srv::OPTIONAL));
EXPECT_NO_THROW(NakedDhcpv4Srv::sanityCheck(createExchange(pkt),
Dhcpv4Srv::OPTIONAL));
// Empty packet, no server-id
EXPECT_THROW(NakedDhcpv4Srv::sanityCheck(pkt, Dhcpv4Srv::MANDATORY),
EXPECT_THROW(NakedDhcpv4Srv::sanityCheck(createExchange(pkt), Dhcpv4Srv::MANDATORY),
RFCViolation);
pkt->addOption(srv->getServerID());
// Server-id is mandatory and present = no exception
EXPECT_NO_THROW(NakedDhcpv4Srv::sanityCheck(pkt, Dhcpv4Srv::MANDATORY));
EXPECT_NO_THROW(NakedDhcpv4Srv::sanityCheck(createExchange(pkt),
Dhcpv4Srv::MANDATORY));
// Server-id is forbidden, but present => exception
EXPECT_THROW(NakedDhcpv4Srv::sanityCheck(pkt, Dhcpv4Srv::FORBIDDEN),
EXPECT_THROW(NakedDhcpv4Srv::sanityCheck(createExchange(pkt), Dhcpv4Srv::FORBIDDEN),
RFCViolation);
// There's no client-id and no HWADDR. Server needs something to
// identify the client
pkt->setHWAddr(generateHWAddr(0));
EXPECT_THROW(NakedDhcpv4Srv::sanityCheck(pkt, Dhcpv4Srv::MANDATORY),
EXPECT_THROW(NakedDhcpv4Srv::sanityCheck(createExchange(pkt),
Dhcpv4Srv::MANDATORY),
RFCViolation);
}
......
......@@ -587,6 +587,10 @@ Dhcpv4SrvTest::configure(const std::string& config, NakedDhcpv4Srv& srv,
}
}
DHCPv4Exchange
Dhcpv4SrvTest::createExchange(const Pkt4Ptr& query) {
return (DHCPv4Exchange(srv_.alloc_engine_, query));
}
}; // end of isc::dhcp::test namespace
......
......@@ -205,6 +205,7 @@ public:
using Dhcpv4Srv::selectSubnet;
using Dhcpv4Srv::VENDOR_CLASS_PREFIX;
using Dhcpv4Srv::shutdown_;
using Dhcpv4Srv::alloc_engine_;
};
class Dhcpv4SrvTest : public ::testing::Test {
......@@ -390,6 +391,9 @@ public:
void configure(const std::string& config, NakedDhcpv4Srv& srv,
const bool commit = true);
/// @brief Create @c DHCPv4Exchange from client's query.
DHCPv4Exchange createExchange(const Pkt4Ptr& query);
/// @brief This function cleans up after the test.
virtual void TearDown();
......
......@@ -309,12 +309,13 @@ public:
answer.reset(new Pkt4(DHCPACK, 1234));
}
ASSERT_NO_THROW(srv_->processClientName(query, answer));
DHCPv4Exchange ex = createExchange(query);
ASSERT_NO_THROW(srv_->processClientName(ex));
Option4ClientFqdnPtr fqdn = getClientFqdnOption(answer);
Option4ClientFqdnPtr fqdn = getClientFqdnOption(ex.getResponse());
ASSERT_TRUE(fqdn);
checkFqdnFlags(answer, exp_flags);
checkFqdnFlags(ex.getResponse(), exp_flags);
EXPECT_EQ(exp_domain_name, fqdn->getDomainName());
EXPECT_EQ(exp_domain_type, fqdn->getDomainNameType());
......@@ -358,9 +359,10 @@ public:
answer.reset(new Pkt4(DHCPACK, 1234));
}
srv_->processClientName(query, answer);
DHCPv4Exchange ex = createExchange(query);
srv_->processClientName(ex);
OptionStringPtr hostname = getHostnameOption(answer);
OptionStringPtr hostname = getHostnameOption(ex.getResponse());
return (hostname);
}
......
......@@ -767,6 +767,9 @@ public:
};
/// @brief Pointer to the @c ClientContext4.
typedef boost::shared_ptr<ClientContext4> ClientContext4Ptr;
/// @brief Returns IPv4 lease.
///
/// This method finds a lease for a client using the following algorithm:
......
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