hook point set the next step status to SKIP. For this particular hook point, the
value set by a callout instructs the server to not release a lease.
+% DHCP4_HOOK_LEASES4_COMMITTED_DROP %1: packet is dropped, because a callout set the next step tp DROP
+This debug message is printed when a callout installed on the leases4_committed
+hook point sets the next step to DROP.
+
% DHCP4_HOOK_PACKET_RCVD_SKIP %1: packet is dropped, because a callout set the next step to SKIP
This debug message is printed when a callout installed on the pkt4_receive
hook point sets the next step to SKIP. For this particular hook point, the
return;
}
- try {
- // Now all fields and options are constructed into output wire buffer.
- // Option objects modification does not make sense anymore. Hooks
- // can only manipulate wire buffer at this stage.
- // Let's execute all callouts registered for buffer4_send
- if (HooksManager::calloutsPresent(Hooks.hook_index_buffer4_send_)) {
- CalloutHandlePtr callout_handle = getCalloutHandle(query);
-
- // Delete previously set arguments
- callout_handle->deleteAllArguments();
-
- // Enable copying options from the packet within hook library.
- ScopedEnableOptionsCopy<Pkt4> resp4_options_copy(rsp);
-
- // Pass incoming packet as argument
- callout_handle->setArgument("response4", rsp);
-
- // Call callouts
- HooksManager::callCallouts(Hooks.hook_index_buffer4_send_,
- *callout_handle);
-
- // Callouts decided to skip the next processing step. The next
- // processing step would to parse the packet, so skip at this
- // stage means drop.
- if ((callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) ||
- (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_DROP)) {
- LOG_DEBUG(hooks_logger, DBG_DHCP4_HOOKS,
- DHCP4_HOOK_BUFFER_SEND_SKIP)
- .arg(rsp->getLabel());
- return;
- }
-
- callout_handle->getArgument("response4", rsp);
- }
-
- LOG_DEBUG(packet4_logger, DBG_DHCP4_BASIC, DHCP4_PACKET_SEND)
- .arg(rsp->getLabel())
- .arg(rsp->getName())
- .arg(static_cast<int>(rsp->getType()))
- .arg(rsp->getLocalAddr().isV4Zero() ? "*" : rsp->getLocalAddr().toText())
- .arg(rsp->getLocalPort())
- .arg(rsp->getRemoteAddr())
- .arg(rsp->getRemotePort())
- .arg(rsp->getIface().empty() ? "to be determined from routing" :
- rsp->getIface());
-
- LOG_DEBUG(packet4_logger, DBG_DHCP4_DETAIL_DATA,
- DHCP4_RESPONSE_DATA)
- .arg(rsp->getLabel())
- .arg(rsp->getName())
- .arg(static_cast<int>(rsp->getType()))
- .arg(rsp->toText());
- sendPacket(rsp);
-
- // Update statistics accordingly for sent packet.
- processStatsSent(rsp);
-
- } catch (const std::exception& e) {
- LOG_ERROR(packet4_logger, DHCP4_PACKET_SEND_FAIL)
- .arg(rsp->getLabel())
- .arg(e.what());
- }
+ CalloutHandlePtr callout_handle = getCalloutHandle(query);
+ processPacketBufferSend(callout_handle, rsp);
}
void
-Dhcpv4Srv::processPacket(Pkt4Ptr& query, Pkt4Ptr& rsp) {
+Dhcpv4Srv::processPacket(Pkt4Ptr& query, Pkt4Ptr& rsp, bool allow_packet_park) {
// Log reception of the packet. We need to increase it early, as any
// failures in unpacking will cause the packet to be dropped. We
// will increase type specific statistic further down the road.
}
}
callout_handle->setArgument("deleted_leases4", deleted_leases);
-
callout_handle->setArgument("io_service", getIOService());
// Call all installed callouts
HooksManager::callCallouts(Hooks.hook_index_leases4_committed_,
*callout_handle);
+ if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_DROP) {
+ LOG_DEBUG(hooks_logger, DBG_DHCP4_HOOKS,
+ DHCP4_HOOK_LEASES4_COMMITTED_DROP)
+ .arg(query->getLabel());
+ rsp.reset();
- switch (callout_handle->getStatus()) {
- // If next step is set to "skip" we simply terminate here, because the
- // next step would be to send the packet.
- case CalloutHandle::NEXT_STEP_SKIP:
- return;
-
- case CalloutHandle::NEXT_STEP_PARK:
+ } else if ((callout_handle->getStatus() == CalloutHandle::NEXT_STEP_PARK)
+ && allow_packet_park) {
packet_park = true;
- break;
- default:
- ;
}
}
return;
}
+ // PARKING SPOT after leases4_committed hook point.
+ CalloutHandlePtr callout_handle = getCalloutHandle(query);
if (packet_park) {
// Park the packet. The function we bind here will be executed when the hook
// library unparks the packet.
HooksManager::park("leases4_committed", query,
- std::bind(&Dhcpv4Srv::leases4CommittedContinue, this,
- getCalloutHandle(query), query, rsp));
+ [this, callout_handle, query, rsp]() mutable {
+ processPacketPktSend(callout_handle, query, rsp);
+ processPacketBufferSend(callout_handle, rsp);
+ });
+
+ // If we have parked the packet, let's reset the pointer to the
+ // response to indicate to the caller that it should return, as
+ // the packet processing will continue via the callback.
+ rsp.reset();
} else {
- // Packet is not to be parked, so let's continue processing.
- leases4CommittedContinue(getCalloutHandle(query), query, rsp);
+ processPacketPktSend(callout_handle, query, rsp);
}
}
void
-Dhcpv4Srv::leases4CommittedContinue(const CalloutHandlePtr& callout_handle,
- Pkt4Ptr& query, Pkt4Ptr& rsp) {
+Dhcpv4Srv::processPacketPktSend(hooks::CalloutHandlePtr& callout_handle,
+ Pkt4Ptr& query, Pkt4Ptr& rsp) {
+ if (!rsp) {
+ return;
+ }
+
// Specifies if server should do the packing
bool skip_pack = false;
}
}
+void
+Dhcpv4Srv::processPacketBufferSend(CalloutHandlePtr& callout_handle,
+ Pkt4Ptr& rsp) {
+ if (!rsp) {
+ return;
+ }
+
+ try {
+ // Now all fields and options are constructed into output wire buffer.
+ // Option objects modification does not make sense anymore. Hooks
+ // can only manipulate wire buffer at this stage.
+ // Let's execute all callouts registered for buffer4_send
+ if (HooksManager::calloutsPresent(Hooks.hook_index_buffer4_send_)) {
+
+ // Delete previously set arguments
+ callout_handle->deleteAllArguments();
+
+ // Enable copying options from the packet within hook library.
+ ScopedEnableOptionsCopy<Pkt4> resp4_options_copy(rsp);
+
+ // Pass incoming packet as argument
+ callout_handle->setArgument("response4", rsp);
+
+ // Call callouts
+ HooksManager::callCallouts(Hooks.hook_index_buffer4_send_,
+ *callout_handle);
+
+ // Callouts decided to skip the next processing step. The next
+ // processing step would to parse the packet, so skip at this
+ // stage means drop.
+ if ((callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) ||
+ (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_DROP)) {
+ LOG_DEBUG(hooks_logger, DBG_DHCP4_HOOKS,
+ DHCP4_HOOK_BUFFER_SEND_SKIP)
+ .arg(rsp->getLabel());
+ return;
+ }
+
+ callout_handle->getArgument("response4", rsp);
+ }
+
+ LOG_DEBUG(packet4_logger, DBG_DHCP4_BASIC, DHCP4_PACKET_SEND)
+ .arg(rsp->getLabel())
+ .arg(rsp->getName())
+ .arg(static_cast<int>(rsp->getType()))
+ .arg(rsp->getLocalAddr().isV4Zero() ? "*" : rsp->getLocalAddr().toText())
+ .arg(rsp->getLocalPort())
+ .arg(rsp->getRemoteAddr())
+ .arg(rsp->getRemotePort())
+ .arg(rsp->getIface().empty() ? "to be determined from routing" :
+ rsp->getIface());
+
+ LOG_DEBUG(packet4_logger, DBG_DHCP4_DETAIL_DATA,
+ DHCP4_RESPONSE_DATA)
+ .arg(rsp->getLabel())
+ .arg(rsp->getName())
+ .arg(static_cast<int>(rsp->getType()))
+ .arg(rsp->toText());
+ sendPacket(rsp);
+
+ // Update statistics accordingly for sent packet.
+ processStatsSent(rsp);
+
+ } catch (const std::exception& e) {
+ LOG_ERROR(packet4_logger, DHCP4_PACKET_SEND_FAIL)
+ .arg(rsp->getLabel())
+ .arg(e.what());
+ }
+}
+
string
Dhcpv4Srv::srvidToString(const OptionPtr& srvid) {
if (!srvid) {
#include <boost/noncopyable.hpp>
+#include <functional>
#include <iostream>
#include <queue>
///
/// @param query A pointer to the packet to be processed.
/// @param rsp A pointer to the response
- void processPacket(Pkt4Ptr& query, Pkt4Ptr& rsp);
+ /// @param allow_packet_park Indicates if parking a packet is allowed.
+ void processPacket(Pkt4Ptr& query, Pkt4Ptr& rsp,
+ bool allow_packet_park = true);
- /// @brief Continues DHCPv4 packet processing after leases4_committed hook
- /// point.
- ///
- /// @param callout_handle Callout handle to be used.
- /// @param query A pointer to the packet to be processed.
- /// @param rsp A pointer to the response
- void leases4CommittedContinue(const hooks::CalloutHandlePtr& callout_handle,
- Pkt4Ptr& query, Pkt4Ptr& rsp);
-
/// @brief Instructs the server to shut down.
void shutdown();
/// @param query Pointer to the client message.
void deferredUnpack(Pkt4Ptr& query);
+ /// @brief Executes pkt4_send callout.
+ ///
+ /// @param callout_handle pointer to the callout handle.
+ /// @param query Pointer to a query.
+ /// @param rsp Pointer to a response.
+ void processPacketPktSend(hooks::CalloutHandlePtr& callout_handle,
+ Pkt4Ptr& query, Pkt4Ptr& rsp);
+
+ /// @brief Executes buffer4_send callout and sends the response.
+ ///
+ /// @param callout_handle pointer to the callout handle.
+ /// @param pkt Pointer to a response.
+ void processPacketBufferSend(hooks::CalloutHandlePtr& callout_handle,
+ 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
// From Dhcpv4Srv::run_one() processing and after
Pkt4Ptr rsp;
- ControlledDhcpv4Srv::getInstance()->processPacket(query, rsp);
+ ControlledDhcpv4Srv::getInstance()->processPacket(query, rsp, false);
if (!rsp) {
return;
}
}
+void
+Dhcp4Client::receiveResponse() {
+ context_.response_ = receiveOneMsg();
+ // If the server has responded, store the configuration received.
+ if (context_.response_) {
+ applyConfiguration();
+ }
+}
+
void
Dhcp4Client::includeClientId(const std::string& clientid) {
if (clientid.empty()) {
msg_copy->setLocalAddr(dest_addr_);
msg_copy->setIface(iface_name_);
srv_->fakeReceive(msg_copy);
- srv_->run();
+
+ try {
+ // Invoke run_one instead of run, because we want to avoid triggering
+ // IO service.
+ srv_->run_one();
+ } catch (...) {
+ // Suppress errors, as the DHCPv4 server does.
+ }
}
void
/// DHCPACK message is applied and can be obtained from the @c config_.
void doRequest();
+ /// @brief Receives a response from the server.
+ ///
+ /// This method is useful to receive response from the server after
+ /// parking a packet. In this case, the packet is not received as a
+ /// result of initial exchange, e.g. @c doRequest. The test can call
+ /// this method to complete the transaction when it expects that the
+ /// packet has been unparked.
+ void receiveResponse();
+
/// @brief Generates a hardware address used by the client.
///
/// It assigns random values to the bytes of the hardware address.
static void
leases4_committed_unpark(ParkingLotHandlePtr parking_lot, Pkt4Ptr query) {
- std::cout << "unparking" << std::endl;
- std::cout << parking_lot->unpark(query) << std::endl;
+ parking_lot->unpark(query);
}
/// Test callback which asks the server to park the packet.
EXPECT_FALSE(callback_deleted_lease4_);
}
-TEST_F(HooksDhcpv4SrvTest, leases4CommittedParkRequest) {
+// This test verifies that it is possible to park a packet as a result of
+// the leases4_committed callouts.
+TEST_F(HooksDhcpv4SrvTest, leases4CommittedParkRequests) {
IfaceMgrTestConfig test_config(true);
IfaceMgr::instance().openSockets4();
+ // This callout uses provided IO service object to post a function
+ // that unparks the packet. The packet is pared and can be unparked
+ // by simply calling IOService::poll.
ASSERT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
"leases4_committed", leases4_committed_park_callout));
- Dhcp4Client client(Dhcp4Client::SELECTING);
- client.setIfaceName("eth1");
- ASSERT_NO_THROW(client.doDORA(boost::shared_ptr<IOAddress>(new IOAddress("192.0.2.100"))));
+ // Create first client and perform DORA.
+ Dhcp4Client client1(Dhcp4Client::SELECTING);
+ client1.setIfaceName("eth1");
+ ASSERT_NO_THROW(client1.doDORA(boost::shared_ptr<IOAddress>(new IOAddress("192.0.2.100"))));
- // Make sure that we received a response
- ASSERT_FALSE(client.getContext().response_);
+ // We should be offerred an address but the DHCPACK should not arrive
+ // at this point, because the packet is parked.
+ ASSERT_FALSE(client1.getContext().response_);
// Check that the callback called is indeed the one we installed
EXPECT_EQ("leases4_committed", callback_name_);
sort(expected_argument_names.begin(), expected_argument_names.end());
EXPECT_TRUE(callback_argument_names_ == expected_argument_names);
- // Newly allocated lease should be returned.
+ // Newly allocated lease should be passed to the callout.
ASSERT_TRUE(callback_lease4_);
EXPECT_EQ("192.0.2.100", callback_lease4_->addr_.toText());
// Pkt passed to a callout must be configured to copy retrieved options.
EXPECT_TRUE(callback_qry_options_copy_);
-// client.getServer()->getIOService()->run_one();
+ // Reset all indicators because we'll be now creating a second client.
+ resetCalloutBuffers();
+
+ // Create the second client to test that it may communicate with the
+ // server while the previous packet is parked.
+ Dhcp4Client client2(client1.getServer(), Dhcp4Client::SELECTING);
+ client2.setIfaceName("eth1");
+ ASSERT_NO_THROW(client2.doDORA(boost::shared_ptr<IOAddress>(new IOAddress("192.0.2.101"))));
+
+ // The DHCPOFFER should have been returned but not DHCPACK, as this
+ // packet got parked too.
+ ASSERT_FALSE(client2.getContext().response_);
+
+ // Check that the callback called is indeed the one we installed.
+ EXPECT_EQ("leases4_committed", callback_name_);
+
+ // There should be now two actions schedulede on our IO service
+ // by the invoked callouts. They unpark both DHCPACK messages.
+ ASSERT_NO_THROW(client1.getServer()->getIOService()->poll());
+
+ // Receive and check the first response.
+ ASSERT_NO_THROW(client1.receiveResponse());
+ ASSERT_TRUE(client1.getContext().response_);
+ Pkt4Ptr rsp = client1.getContext().response_;
+ EXPECT_EQ(DHCPACK, rsp->getType());
+ EXPECT_EQ("192.0.2.100", rsp->getYiaddr().toText());
+
+ // Receive and check the second response.
+ ASSERT_NO_THROW(client2.receiveResponse());
+ ASSERT_TRUE(client2.getContext().response_);
+ rsp = client2.getContext().response_;
+ EXPECT_EQ(DHCPACK, rsp->getType());
+ EXPECT_EQ("192.0.2.101", rsp->getYiaddr().toText());
}
// This test verifies that valid RELEASE triggers lease4_release callouts