From: Francis Dupont Date: Thu, 27 Dec 2018 18:12:10 +0000 (+0100) Subject: [82-improve-kea-test-capabilities] Added remote port to dhcp4/dhcp6/perfdhcp X-Git-Tag: 421-create-config-backend-for-dhcpv6-base_base~2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=c25bbf261615805f74007ce010a2e5bf510cf1e5;p=thirdparty%2Fkea.git [82-improve-kea-test-capabilities] Added remote port to dhcp4/dhcp6/perfdhcp --- diff --git a/src/bin/dhcp4/ctrl_dhcp4_srv.cc b/src/bin/dhcp4/ctrl_dhcp4_srv.cc index cacfe35d3d..0ab41233b4 100644 --- a/src/bin/dhcp4/ctrl_dhcp4_srv.cc +++ b/src/bin/dhcp4/ctrl_dhcp4_srv.cc @@ -716,8 +716,10 @@ ControlledDhcpv4Srv::checkConfig(isc::data::ConstElementPtr config) { return (configureDhcp4Server(*srv, config, true)); } -ControlledDhcpv4Srv::ControlledDhcpv4Srv(uint16_t server_port /*= DHCP4_SERVER_PORT*/) - : Dhcpv4Srv(server_port), io_service_(), timer_mgr_(TimerMgr::instance()) { +ControlledDhcpv4Srv::ControlledDhcpv4Srv(uint16_t server_port /*= DHCP4_SERVER_PORT*/, + uint16_t client_port /*= 0*/) + : Dhcpv4Srv(server_port, client_port), io_service_(), + timer_mgr_(TimerMgr::instance()) { if (getInstance()) { isc_throw(InvalidOperation, "There is another Dhcpv4Srv instance already."); diff --git a/src/bin/dhcp4/ctrl_dhcp4_srv.h b/src/bin/dhcp4/ctrl_dhcp4_srv.h index 34dda4329e..de7a02ef66 100644 --- a/src/bin/dhcp4/ctrl_dhcp4_srv.h +++ b/src/bin/dhcp4/ctrl_dhcp4_srv.h @@ -28,7 +28,9 @@ public: /// @brief Constructor /// /// @param server_port UDP port to be opened for DHCP traffic - ControlledDhcpv4Srv(uint16_t server_port = DHCP4_SERVER_PORT); + /// @param client_port UDP port where all responses are sent to. + ControlledDhcpv4Srv(uint16_t server_port = DHCP4_SERVER_PORT, + uint16_t client_port = 0); /// @brief Destructor. ~ControlledDhcpv4Srv(); diff --git a/src/bin/dhcp4/dhcp4_messages.mes b/src/bin/dhcp4/dhcp4_messages.mes index c66f719055..7612e2fa92 100644 --- a/src/bin/dhcp4/dhcp4_messages.mes +++ b/src/bin/dhcp4/dhcp4_messages.mes @@ -739,7 +739,7 @@ This informational message indicates that the DHCPv4 server has processed any command-line switches and is starting. The version is also printed. -% DHCP4_START_INFO pid: %1, server port: %2, verbose: %3 +% DHCP4_START_INFO pid: %1, server port: %2, client port: %3, verbose: %4 This is a debug message issued during the DHCPv4 server startup. It lists some information about the parameters with which the server is running. diff --git a/src/bin/dhcp4/dhcp4_srv.cc b/src/bin/dhcp4/dhcp4_srv.cc index 4a15e0ffd8..79b207c83b 100644 --- a/src/bin/dhcp4/dhcp4_srv.cc +++ b/src/bin/dhcp4/dhcp4_srv.cc @@ -441,10 +441,11 @@ Dhcpv4Exchange::setReservedMessageFields() { const std::string Dhcpv4Srv::VENDOR_CLASS_PREFIX("VENDOR_CLASS_"); -Dhcpv4Srv::Dhcpv4Srv(uint16_t server_port, const bool use_bcast, - const bool direct_response_desired) +Dhcpv4Srv::Dhcpv4Srv(uint16_t server_port, uint16_t client_port, + const bool use_bcast, const bool direct_response_desired) : io_service_(new IOService()), shutdown_(true), alloc_engine_(), server_port_(server_port), use_bcast_(use_bcast), + client_port_(client_port), network_state_(new NetworkState(NetworkState::DHCPv4)) { LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_OPEN_SOCKET) @@ -2296,11 +2297,15 @@ Dhcpv4Srv::adjustIfaceData(Dhcpv4Exchange& ex) { // server has to reply via relay agent. For other messages we send back // through relay if message is relayed, and unicast to the client if the // message is not relayed. + // If client port was set from the command line enforce all responses + // to it. Of course it is only for testing purposes. // Note that the call to this function may throw if invalid combination // of hops and giaddr is found (hops = 0 if giaddr = 0 and hops != 0 if // giaddr != 0). The exception will propagate down and eventually cause the // packet to be discarded. - if (((query->getType() == DHCPINFORM) && + if (client_port_) { + response->setRemotePort(client_port_); + } else if (((query->getType() == DHCPINFORM) && ((!query->getCiaddr().isV4Zero()) || (!query->isRelayed() && !query->getRemoteAddr().isV4Zero()))) || ((query->getType() != DHCPINFORM) && !query->isRelayed())) { diff --git a/src/bin/dhcp4/dhcp4_srv.h b/src/bin/dhcp4/dhcp4_srv.h index c7b838c813..ccc1ce88a8 100644 --- a/src/bin/dhcp4/dhcp4_srv.h +++ b/src/bin/dhcp4/dhcp4_srv.h @@ -212,7 +212,8 @@ public: /// In particular, creates IfaceMgr that will be responsible for /// network interaction. Will instantiate lease manager, and load /// old or create new DUID. It is possible to specify alternate - /// port on which DHCPv4 server will listen on. That is mostly useful + /// port on which DHCPv4 server will listen on and alternate port + /// where DHCPv4 server sends all responses to. Those are mostly useful /// for testing purposes. The Last two arguments of the constructor /// should be left at default values for normal server operation. /// They should be set to 'false' when creating an instance of this @@ -220,10 +221,12 @@ public: /// root privileges. /// /// @param server_port specifies port number to listen on + /// @param client_port specifies port number to send to /// @param use_bcast configure sockets to support broadcast messages. /// @param direct_response_desired specifies if it is desired to /// use direct V4 traffic. Dhcpv4Srv(uint16_t server_port = DHCP4_SERVER_PORT, + uint16_t client_port = 0, const bool use_bcast = true, const bool direct_response_desired = true); @@ -772,7 +775,8 @@ protected: /// address). /// /// The destination port is always DHCPv4 client (68) or relay (67) port, - /// depending if the response will be sent directly to a client. + /// depending if the response will be sent directly to a client, unless + /// a client port was enforced from the command line. /// /// The source port is always set to DHCPv4 server port (67). /// @@ -789,7 +793,7 @@ protected: /// /// @param ex The exchange holding both the client's message and the /// server's response. - static void adjustIfaceData(Dhcpv4Exchange& ex); + void adjustIfaceData(Dhcpv4Exchange& ex); /// @brief Sets remote addresses for outgoing packet. /// @@ -962,6 +966,9 @@ private: protected: + /// UDP port number to which server sends responses. + uint16_t client_port_; + /// @brief Holds information about disabled DHCP service and/or /// disabled subnet/network scopes. NetworkStatePtr network_state_; diff --git a/src/bin/dhcp4/kea-dhcp4.xml b/src/bin/dhcp4/kea-dhcp4.xml index 03a59336b4..720ae47bd1 100644 --- a/src/bin/dhcp4/kea-dhcp4.xml +++ b/src/bin/dhcp4/kea-dhcp4.xml @@ -50,6 +50,7 @@ + @@ -123,6 +124,14 @@ + + + + Client port number (1-65535) to which the server responds. + This is useful for testing purposes only. + + + diff --git a/src/bin/dhcp4/main.cc b/src/bin/dhcp4/main.cc index 15bc920f25..7e138ac00d 100644 --- a/src/bin/dhcp4/main.cc +++ b/src/bin/dhcp4/main.cc @@ -44,7 +44,7 @@ usage() { cerr << "Kea DHCPv4 server, version " << VERSION << endl; cerr << endl; cerr << "Usage: " << DHCP4_NAME - << " -[v|V|W] [-d] [-{c|t} cfgfile] [-p number]" << endl; + << " -[v|V|W] [-d] [-{c|t} cfgfile] [-p number] [-P number]" << endl; cerr << " -v: print version number and exit" << endl; cerr << " -V: print extended version and exit" << endl; cerr << " -W: display the configuration report and exit" << endl; @@ -53,6 +53,8 @@ usage() { cerr << " -t file: check the configuration file syntax and exit" << endl; cerr << " -p number: specify non-standard server port number 1-65535 " << "(useful for testing only)" << endl; + cerr << " -P number: specify non-standard client port number 1-65535 " + << "(useful for testing only)" << endl; exit(EXIT_FAILURE); } } // end of anonymous namespace @@ -60,15 +62,17 @@ usage() { int main(int argc, char* argv[]) { int ch; - // The default. any other values are useful for testing only. + // The default. Any other values are useful for testing only. int server_port_number = DHCP4_SERVER_PORT; + // Not zero values are useful for testing only. + int client_port_number = 0; bool verbose_mode = false; // Should server be verbose? bool check_mode = false; // Check syntax // The standard config file std::string config_file(""); - while ((ch = getopt(argc, argv, "dvVWc:p:t:")) != -1) { + while ((ch = getopt(argc, argv, "dvVWc:p:P:t:")) != -1) { switch (ch) { case 'd': verbose_mode = true; @@ -98,12 +102,27 @@ main(int argc, char* argv[]) { try { server_port_number = boost::lexical_cast(optarg); } catch (const boost::bad_lexical_cast &) { - cerr << "Failed to parse port number: [" << optarg + cerr << "Failed to parse server port number: [" << optarg << "], 1-65535 allowed." << endl; usage(); } if (server_port_number <= 0 || server_port_number > 65535) { - cerr << "Failed to parse port number: [" << optarg + cerr << "Failed to parse server port number: [" << optarg + << "], 1-65535 allowed." << endl; + usage(); + } + break; + + case 'P': + try { + client_port_number = boost::lexical_cast(optarg); + } catch (const boost::bad_lexical_cast &) { + cerr << "Failed to parse client port number: [" << optarg + << "], 1-65535 allowed." << endl; + usage(); + } + if (client_port_number <= 0 || client_port_number > 65535) { + cerr << "Failed to parse client port number: [" << optarg << "], 1-65535 allowed." << endl; usage(); } @@ -187,13 +206,15 @@ main(int argc, char* argv[]) { // Initialize logging. If verbose, we'll use maximum verbosity. Daemon::loggerInit(DHCP4_ROOT_LOGGER_NAME, verbose_mode); LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_START_INFO) - .arg(getpid()).arg(server_port_number) + .arg(getpid()) + .arg(server_port_number) + .arg(client_port_number) .arg(verbose_mode ? "yes" : "no"); LOG_INFO(dhcp4_logger, DHCP4_STARTING).arg(VERSION); // Create the server instance. - ControlledDhcpv4Srv server(server_port_number); + ControlledDhcpv4Srv server(server_port_number, client_port_number); // Remember verbose-mode server.setVerbose(verbose_mode); diff --git a/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc b/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc index f06dbb949b..412b48b9c4 100644 --- a/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc +++ b/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc @@ -176,7 +176,7 @@ TEST_F(Dhcpv4SrvTest, adjustIfaceDataRelay) { resp->setHops(req->getHops()); // This function never throws. - ASSERT_NO_THROW(NakedDhcpv4Srv::adjustIfaceData(ex)); + ASSERT_NO_THROW(srv_.adjustIfaceData(ex)); // Now the destination address should be relay's address. EXPECT_EQ("192.0.1.1", resp->getRemoteAddr().toText()); @@ -202,10 +202,16 @@ TEST_F(Dhcpv4SrvTest, adjustIfaceDataRelay) { // Clear remote address. resp->setRemoteAddr(IOAddress("0.0.0.0")); - ASSERT_NO_THROW(NakedDhcpv4Srv::adjustIfaceData(ex)); + // Set the client port. + srv_.client_port_ = 1234; + + ASSERT_NO_THROW(srv_.adjustIfaceData(ex)); // Response should be sent back to the relay address. EXPECT_EQ("192.0.1.50", resp->getRemoteAddr().toText()); + + // Remote port was enforced to the client port. + EXPECT_EQ(srv_.client_port_, resp->getRemotePort()); } // This test verifies that the remote port is adjusted when @@ -260,7 +266,7 @@ TEST_F(Dhcpv4SrvTest, adjustIfaceDataRelayPort) { resp->setRemotePort(67); // This function never throws. - ASSERT_NO_THROW(NakedDhcpv4Srv::adjustIfaceData(ex)); + ASSERT_NO_THROW(srv_.adjustIfaceData(ex)); // Now the destination address should be relay's address. EXPECT_EQ("192.0.1.1", resp->getRemoteAddr().toText()); @@ -325,7 +331,7 @@ TEST_F(Dhcpv4SrvTest, adjustIfaceDataUseRouting) { resp->setHops(req->getHops()); // This function never throws. - ASSERT_NO_THROW(NakedDhcpv4Srv::adjustIfaceData(ex)); + ASSERT_NO_THROW(srv_.adjustIfaceData(ex)); // Now the destination address should be relay's address. EXPECT_EQ("192.0.1.1", resp->getRemoteAddr().toText()); @@ -354,7 +360,7 @@ TEST_F(Dhcpv4SrvTest, adjustIfaceDataUseRouting) { cfg_iface->setOutboundIface(CfgIface::SAME_AS_INBOUND); CfgMgr::instance().commit(); - ASSERT_NO_THROW(NakedDhcpv4Srv::adjustIfaceData(ex)); + ASSERT_NO_THROW(srv_.adjustIfaceData(ex)); EXPECT_EQ("192.0.2.5", resp->getLocalAddr().toText()); EXPECT_EQ("eth1", resp->getIface()); @@ -409,7 +415,7 @@ TEST_F(Dhcpv4SrvTest, adjustIfaceDataRenew) { // Copy hops value from the query. resp->setHops(req->getHops()); - ASSERT_NO_THROW(NakedDhcpv4Srv::adjustIfaceData(ex)); + ASSERT_NO_THROW(srv_.adjustIfaceData(ex)); // Check that server responds to ciaddr EXPECT_EQ("192.0.1.15", resp->getRemoteAddr().toText()); @@ -482,7 +488,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(ex)); + ASSERT_NO_THROW(srv_.adjustIfaceData(ex)); // Check that the response is sent to broadcast address as the // server doesn't have capability to respond directly. @@ -511,7 +517,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(ex)); + ASSERT_NO_THROW(srv_.adjustIfaceData(ex)); EXPECT_EQ("192.0.1.13", resp->getRemoteAddr().toText()); } @@ -553,7 +559,7 @@ TEST_F(Dhcpv4SrvTest, adjustIfaceDataBroadcast) { // Clear the remote address. resp->setRemoteAddr(IOAddress("0.0.0.0")); - ASSERT_NO_THROW(NakedDhcpv4Srv::adjustIfaceData(ex)); + ASSERT_NO_THROW(srv_.adjustIfaceData(ex)); // Server must respond to broadcast address when client desired that // by setting the broadcast flag in its request. diff --git a/src/bin/dhcp4/tests/dhcp4_test_utils.h b/src/bin/dhcp4/tests/dhcp4_test_utils.h index cdd3bafe70..0ec1171f28 100644 --- a/src/bin/dhcp4/tests/dhcp4_test_utils.h +++ b/src/bin/dhcp4/tests/dhcp4_test_utils.h @@ -229,6 +229,7 @@ public: using Dhcpv4Srv::VENDOR_CLASS_PREFIX; using Dhcpv4Srv::shutdown_; using Dhcpv4Srv::alloc_engine_; + using Dhcpv4Srv::client_port_; }; // We need to pass one reference to the Dhcp4Client, which is defined in diff --git a/src/bin/dhcp6/ctrl_dhcp6_srv.cc b/src/bin/dhcp6/ctrl_dhcp6_srv.cc index 4a85b53032..84ba14ddc6 100644 --- a/src/bin/dhcp6/ctrl_dhcp6_srv.cc +++ b/src/bin/dhcp6/ctrl_dhcp6_srv.cc @@ -738,8 +738,10 @@ ControlledDhcpv6Srv::checkConfig(isc::data::ConstElementPtr config) { return (configureDhcp6Server(*srv, config, true)); } -ControlledDhcpv6Srv::ControlledDhcpv6Srv(uint16_t server_port) - : Dhcpv6Srv(server_port), io_service_(), timer_mgr_(TimerMgr::instance()) { +ControlledDhcpv6Srv::ControlledDhcpv6Srv(uint16_t server_port, + uint16_t client_port) + : Dhcpv6Srv(server_port, client_port), io_service_(), + timer_mgr_(TimerMgr::instance()) { if (server_) { isc_throw(InvalidOperation, "There is another Dhcpv6Srv instance already."); diff --git a/src/bin/dhcp6/ctrl_dhcp6_srv.h b/src/bin/dhcp6/ctrl_dhcp6_srv.h index f8fd27691e..ee81c36a53 100644 --- a/src/bin/dhcp6/ctrl_dhcp6_srv.h +++ b/src/bin/dhcp6/ctrl_dhcp6_srv.h @@ -28,7 +28,9 @@ public: /// @brief Constructor /// /// @param server_port UDP port to be opened for DHCP traffic - ControlledDhcpv6Srv(uint16_t server_port = DHCP6_SERVER_PORT); + /// @param client_port UDP port where all responses are sent to. + ControlledDhcpv6Srv(uint16_t server_port = DHCP6_SERVER_PORT, + uint16_t client_port = 0); /// @brief Destructor. virtual ~ControlledDhcpv6Srv(); diff --git a/src/bin/dhcp6/dhcp6_messages.mes b/src/bin/dhcp6/dhcp6_messages.mes index 6fa07ce28f..9079efb09e 100644 --- a/src/bin/dhcp6/dhcp6_messages.mes +++ b/src/bin/dhcp6/dhcp6_messages.mes @@ -782,7 +782,7 @@ This informational message indicates that the IPv6 DHCP server has processed any command-line switches and is starting. The version is also printed. -% DHCP6_START_INFO pid: %1, server port: %2, verbose: %3 +% DHCP6_START_INFO pid: %1, server port: %2, client port: %3, verbose: %4 This is a debug message issued during the IPv6 DHCP server startup. It lists some information about the parameters with which the server is running. diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc index e333e3be77..36da8047ca 100644 --- a/src/bin/dhcp6/dhcp6_srv.cc +++ b/src/bin/dhcp6/dhcp6_srv.cc @@ -179,15 +179,18 @@ namespace dhcp { const std::string Dhcpv6Srv::VENDOR_CLASS_PREFIX("VENDOR_CLASS_"); -Dhcpv6Srv::Dhcpv6Srv(uint16_t server_port) - : io_service_(new IOService()), server_port_(server_port), serverid_(), - shutdown_(true), alloc_engine_(), name_change_reqs_(), +Dhcpv6Srv::Dhcpv6Srv(uint16_t server_port, uint16_t client_port) + : io_service_(new IOService()), server_port_(server_port), + client_port_(client_port), serverid_(), shutdown_(true), + alloc_engine_(), name_change_reqs_(), network_state_(new NetworkState(NetworkState::DHCPv6)) { LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_OPEN_SOCKET) .arg(server_port); + Dhcp6to4Ipc::instance().client_port = client_port; + // Initialize objects required for DHCP server operation. try { // Port 0 is used for testing purposes where in most cases we don't @@ -775,7 +778,10 @@ Dhcpv6Srv::processPacket(Pkt6Ptr& query, Pkt6Ptr& rsp) { rsp->setRemoteAddr(query->getRemoteAddr()); rsp->setLocalAddr(query->getLocalAddr()); - if (rsp->relay_info_.empty()) { + if (client_port_) { + // A command line option enforces a specific client port + rsp->setRemotePort(client_port_); + } else if (rsp->relay_info_.empty()) { // Direct traffic, send back to the client directly rsp->setRemotePort(DHCP6_CLIENT_PORT); } else { diff --git a/src/bin/dhcp6/dhcp6_srv.h b/src/bin/dhcp6/dhcp6_srv.h index 3c04ac530d..36b54ee8c4 100644 --- a/src/bin/dhcp6/dhcp6_srv.h +++ b/src/bin/dhcp6/dhcp6_srv.h @@ -80,8 +80,10 @@ public: /// network interaction. Will instantiate lease manager, and load /// old or create new DUID. /// - /// @param server_port port on will all sockets will listen - Dhcpv6Srv(uint16_t server_port = DHCP6_SERVER_PORT); + /// @param server_port port on which all sockets will listen + /// @param client_port port to which all responses will be sent + Dhcpv6Srv(uint16_t server_port = DHCP6_SERVER_PORT, + uint16_t client_port = 0); /// @brief Destructor. Used during DHCPv6 service shutdown. virtual ~Dhcpv6Srv(); @@ -943,6 +945,10 @@ private: /// UDP port number on which server listens. uint16_t server_port_; +protected: + /// UDP port number to which server sends all responses. + uint16_t client_port_; + public: /// @note used by DHCPv4-over-DHCPv6 so must be public and static diff --git a/src/bin/dhcp6/dhcp6to4_ipc.cc b/src/bin/dhcp6/dhcp6to4_ipc.cc index 34873daaf1..a9dbfd6ad0 100644 --- a/src/bin/dhcp6/dhcp6to4_ipc.cc +++ b/src/bin/dhcp6/dhcp6to4_ipc.cc @@ -27,6 +27,8 @@ using namespace isc::hooks; namespace isc { namespace dhcp { +uint16_t Dhcp6to4Ipc::client_port = 0; + Dhcp6to4Ipc::Dhcp6to4Ipc() : Dhcp4o6IpcBase() {} Dhcp6to4Ipc& Dhcp6to4Ipc::instance() { @@ -93,7 +95,10 @@ void Dhcp6to4Ipc::handler() { // want to know if it is a relayed message (vs. internal message type). // getType() always returns the type of internal message. uint8_t msg_type = buf[0]; - if ((msg_type == DHCPV6_RELAY_FORW) || (msg_type == DHCPV6_RELAY_REPL)) { + if (client_port) { + pkt->setRemotePort(client_port); + } else if ((msg_type == DHCPV6_RELAY_FORW) || + (msg_type == DHCPV6_RELAY_REPL)) { pkt->setRemotePort(relay_port ? relay_port : DHCP6_SERVER_PORT); } else { pkt->setRemotePort(DHCP6_CLIENT_PORT); diff --git a/src/bin/dhcp6/dhcp6to4_ipc.h b/src/bin/dhcp6/dhcp6to4_ipc.h index 844033531b..fc025ceaea 100644 --- a/src/bin/dhcp6/dhcp6to4_ipc.h +++ b/src/bin/dhcp6/dhcp6to4_ipc.h @@ -45,6 +45,10 @@ public: /// /// The handler sends the DHCPv6 packet back to the remote address static void handler(); + + /// @param client_port UDP port where all responses are sent to. + /// Not zero is mostly useful for testing purposes. + static uint16_t client_port; }; } // namespace isc diff --git a/src/bin/dhcp6/main.cc b/src/bin/dhcp6/main.cc index c934dedbb7..6f44221f5e 100644 --- a/src/bin/dhcp6/main.cc +++ b/src/bin/dhcp6/main.cc @@ -49,7 +49,7 @@ usage() { cerr << "Kea DHCPv6 server, version " << VERSION << endl; cerr << endl; cerr << "Usage: " << DHCP6_NAME - << " -[v|V|W] [-d] [-{c|t} cfgfile] [-p server_port_number]" << endl; + << " -[v|V|W] [-d] [-{c|t} cfgfile] [-p number] [-P number]" << endl; cerr << " -v: print version number and exit." << endl; cerr << " -V: print extended version and exit" << endl; cerr << " -W: display the configuration report and exit" << endl; @@ -58,6 +58,8 @@ usage() { cerr << " -t file: check the configuration file syntax and exit" << endl; cerr << " -p number: specify non-standard server port number 1-65535 " << "(useful for testing only)" << endl; + cerr << " -P number: specify non-standard client port number 1-65535 " + << "(useful for testing only)" << endl; exit(EXIT_FAILURE); } } // end of anonymous namespace @@ -67,13 +69,15 @@ main(int argc, char* argv[]) { int ch; // The default. Any other values are useful for testing only. int server_port_number = DHCP6_SERVER_PORT; + // Not zero values are useful for testing only. + int client_port_number = 0; bool verbose_mode = false; // Should server be verbose? bool check_mode = false; // Check syntax // The standard config file std::string config_file(""); - while ((ch = getopt(argc, argv, "dvVWc:p:t:")) != -1) { + while ((ch = getopt(argc, argv, "dvVWc:p:P:t:")) != -1) { switch (ch) { case 'd': verbose_mode = true; @@ -99,16 +103,31 @@ main(int argc, char* argv[]) { config_file = optarg; break; - case 'p': // port number + case 'p': // server port number try { server_port_number = boost::lexical_cast(optarg); } catch (const boost::bad_lexical_cast &) { - cerr << "Failed to parse port number: [" << optarg + cerr << "Failed to parse server port number: [" << optarg << "], 1-65535 allowed." << endl; usage(); } if (server_port_number <= 0 || server_port_number > 65535) { - cerr << "Failed to parse port number: [" << optarg + cerr << "Failed to parse server port number: [" << optarg + << "], 1-65535 allowed." << endl; + usage(); + } + break; + + case 'P': // client port number + try { + client_port_number = boost::lexical_cast(optarg); + } catch (const boost::bad_lexical_cast &) { + cerr << "Failed to parse client port number: [" << optarg + << "], 1-65535 allowed." << endl; + usage(); + } + if (client_port_number <= 0 || client_port_number > 65535) { + cerr << "Failed to parse client port number: [" << optarg << "], 1-65535 allowed." << endl; usage(); } @@ -194,13 +213,15 @@ main(int argc, char* argv[]) { Daemon::loggerInit(DHCP6_LOGGER_NAME, verbose_mode); LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_START_INFO) - .arg(getpid()).arg(server_port_number) + .arg(getpid()) + .arg(server_port_number) + .arg(client_port_number) .arg(verbose_mode ? "yes" : "no"); LOG_INFO(dhcp6_logger, DHCP6_STARTING).arg(VERSION); // Create the server instance. - ControlledDhcpv6Srv server(server_port_number); + ControlledDhcpv6Srv server(server_port_number, client_port_number); // Remember verbose-mode server.setVerbose(verbose_mode); diff --git a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc index 2a2f505fb6..99d4f5d3c2 100644 --- a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc +++ b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc @@ -1609,6 +1609,35 @@ TEST_F(Dhcpv6SrvTest, selectSubnetRelayInterfaceId) { EXPECT_FALSE(drop); } +// Checks if server responses are sent to the proper port. +TEST_F(Dhcpv6SrvTest, portsClientPort) { + + NakedDhcpv6Srv srv(0); + + // Enforce a specific client port value. + EXPECT_EQ(0, srv.client_port_); + srv.client_port_ = 1234; + + // Let's create a simple SOLICIT + Pkt6Ptr sol = PktCaptures::captureSimpleSolicit(); + + // Simulate that we have received that traffic + srv.fakeReceive(sol); + + // Server will now process to run its normal loop, but instead of calling + // IfaceMgr::receive6(), it will read all packets from the list set by + // fakeReceive() + srv.run(); + + // Get Advertise... + ASSERT_FALSE(srv.fake_sent_.empty()); + Pkt6Ptr adv = srv.fake_sent_.front(); + ASSERT_TRUE(adv); + + // This is sent back to client directly, should be port 546 + EXPECT_EQ(srv.client_port_, adv->getRemotePort()); +} + // Checks if server responses are sent to the proper port. TEST_F(Dhcpv6SrvTest, portsDirectTraffic) { diff --git a/src/bin/dhcp6/tests/dhcp6_test_utils.h b/src/bin/dhcp6/tests/dhcp6_test_utils.h index 71a23d8db2..a0ba34a965 100644 --- a/src/bin/dhcp6/tests/dhcp6_test_utils.h +++ b/src/bin/dhcp6/tests/dhcp6_test_utils.h @@ -272,6 +272,7 @@ public: using Dhcpv6Srv::name_change_reqs_; using Dhcpv6Srv::VENDOR_CLASS_PREFIX; using Dhcpv6Srv::initContext; + using Dhcpv6Srv::client_port_; /// @brief packets we pretend to receive /// diff --git a/src/bin/dhcp6/tests/dhcp6to4_ipc_unittest.cc b/src/bin/dhcp6/tests/dhcp6to4_ipc_unittest.cc index 2e1a708e02..93f97f092d 100644 --- a/src/bin/dhcp6/tests/dhcp6to4_ipc_unittest.cc +++ b/src/bin/dhcp6/tests/dhcp6to4_ipc_unittest.cc @@ -139,6 +139,9 @@ TEST_F(Dhcp6to4IpcTest, receive) { // Create instance of the IPC endpoint being used as a source of messages. TestIpc src_ipc(TEST_PORT, TestIpc::ENDPOINT_TYPE_V4); + // Reset the IPC. + ASSERT_NO_THROW(ipc.close()); + // Open both endpoints. ASSERT_NO_THROW(ipc.open()); ASSERT_NO_THROW(src_ipc.open()); @@ -197,6 +200,9 @@ TEST_F(Dhcp6to4IpcTest, DISABLED_receiveRelayed) { // Create instance of the IPC endpoint being used as a source of messages. TestIpc src_ipc(TEST_PORT, TestIpc::ENDPOINT_TYPE_V4); + // Reset the IPC. + ASSERT_NO_THROW(ipc.close()); + // Open both endpoints. ASSERT_NO_THROW(ipc.open()); ASSERT_NO_THROW(src_ipc.open()); @@ -249,4 +255,49 @@ TEST_F(Dhcp6to4IpcTest, DISABLED_receiveRelayed) { EXPECT_EQ(1, d4_resp->getInteger().first); } +// This test verifies the client port is enforced also with DHCP4o6. +TEST_F(Dhcp6to4IpcTest, clientPort) { + // Create instance of the IPC endpoint under test. + Dhcp6to4Ipc& ipc = Dhcp6to4Ipc::instance(); + // Set the client port. + ipc.client_port = 1234; + // Create instance of the IPC endpoint being used as a source of messages. + TestIpc src_ipc(TEST_PORT, TestIpc::ENDPOINT_TYPE_V4); + + // Reset the IPC. + ASSERT_NO_THROW(ipc.close()); + + // Open both endpoints. + ASSERT_NO_THROW(ipc.open()); + ASSERT_NO_THROW(src_ipc.open()); + + // Create message to be sent over IPC. + Pkt6Ptr pkt(new Pkt6(DHCPV6_DHCPV4_RESPONSE, 1234)); + pkt->addOption(createDHCPv4MsgOption()); + pkt->setIface("eth0"); + pkt->setRemoteAddr(IOAddress("2001:db8:1::123")); + ASSERT_NO_THROW(pkt->pack()); + + // Reset the callout cached packet + Dhcp6to4IpcTest::callback_pkt_.reset(); + + // Send and wait up to 1 second to receive it. + ASSERT_NO_THROW(src_ipc.send(pkt)); + ASSERT_NO_THROW(IfaceMgr::instance().receive6(1, 0)); + + // Make sure that the received packet was configured to return copy of + // retrieved options within a callout. + EXPECT_TRUE(callback_pkt_options_copy_); + + // Get the forwarded packet from the callout + Pkt6Ptr forwarded = Dhcp6to4IpcTest::callback_pkt_; + ASSERT_TRUE(forwarded); + + // Verify the packet received. + EXPECT_EQ(ipc.client_port, forwarded->getRemotePort()); + + // Reset the value in case tests are not in order. + ipc.client_port = 0; +} + } // end of anonymous namespace diff --git a/src/bin/perfdhcp/command_options.cc b/src/bin/perfdhcp/command_options.cc index b5b7039701..eb562d97d2 100644 --- a/src/bin/perfdhcp/command_options.cc +++ b/src/bin/perfdhcp/command_options.cc @@ -138,6 +138,7 @@ CommandOptions::reset() { is_interface_ = false; preload_ = 0; local_port_ = 0; + remote_port_ = 0; seeded_ = false; seed_ = 0; broadcast_ = false; @@ -226,7 +227,7 @@ CommandOptions::initialize(int argc, char** argv, bool print_cmd_line) { // In this section we collect argument values from command line // they will be tuned and validated elsewhere - while((opt = getopt(argc, argv, "hv46A:r:t:R:b:n:p:d:D:l:P:L:M:" + while((opt = getopt(argc, argv, "hv46A:r:t:R:b:n:p:d:D:l:P:a:L:N:M:" "s:iBc1T:X:O:o:E:S:I:x:W:w:e:f:F:g:")) != -1) { stream << " -" << static_cast(opt); if (optarg) { @@ -373,8 +374,18 @@ CommandOptions::initialize(int argc, char** argv, bool print_cmd_line) { " negative integer"); check(local_port_ > static_cast(std::numeric_limits::max()), - "local-port must be lower than " + - boost::lexical_cast(std::numeric_limits::max())); + "local-port must be lower than " + + boost::lexical_cast(std::numeric_limits::max())); + break; + + case 'N': + remote_port_ = nonNegativeInteger("value of remote port:" + " -L must not be a" + " negative integer"); + check(remote_port_ > + static_cast(std::numeric_limits::max()), + "remote-port must be lower than " + + boost::lexical_cast(std::numeric_limits::max())); break; case 'M': @@ -992,6 +1003,9 @@ CommandOptions::printCommandLine() const { if (getLocalPort() != 0) { std::cout << "local-port=" << local_port_ << std::endl; } + if (getRemotePort() != 0) { + std::cout << "remote-port=" << remote_port_ << std::endl; + } if (seeded_) { std::cout << "seed=" << seed_ << std::endl; } @@ -1056,8 +1070,8 @@ CommandOptions::usage() const { " [-F] [-t] [-R] [-b]\n" " [-n] [-p] [-d]\n" " [-D] [-l] [-P]\n" - " [-L] [-s] [-i] [-B] [-g]\n" - " [-W]\n" + " [-L] [-N]\n" + " [-s] [-i] [-B] [-W]\n" " [-c] [-1] [-M] [-T]\n" " [-X] [-O]\n" " [-S] [-I] [-x]\n" @@ -1129,6 +1143,8 @@ CommandOptions::usage() const { " from this list for every new exchange. In the DHCPv6 case, MAC\n" " addresses are used to generate DUID-LLs. This parameter must not be\n" " used in conjunction with the -b parameter.\n" + "-N: Specify the remote port to use\n" + " (the value 0 means to use the default).\n" "-O: Offset of the last octet to randomize in the template.\n" "-P: Initiate first exchanges back to back at startup.\n" "-r: Initiate DORA/SARR (or if -i is given, DO/SA)\n" diff --git a/src/bin/perfdhcp/command_options.h b/src/bin/perfdhcp/command_options.h index f843d2484d..deb2808267 100644 --- a/src/bin/perfdhcp/command_options.h +++ b/src/bin/perfdhcp/command_options.h @@ -243,6 +243,11 @@ public: /// \return local port number. int getLocalPort() const { return local_port_; } + /// \brief Returns remote port number. + /// + /// \return remote port number. + int getRemotePort() const { return remote_port_; } + /// @brief Returns the time in microseconds to delay the program by. /// /// @return the time in microseconds to delay the program by. @@ -577,6 +582,9 @@ private: /// Local port number (host endian) int local_port_; + /// Remote port number (host endian) + int remote_port_; + /// Randomization seed. uint32_t seed_; diff --git a/src/bin/perfdhcp/perfdhcp.xml b/src/bin/perfdhcp/perfdhcp.xml index 3f85823f80..2fe4ac5d6c 100644 --- a/src/bin/perfdhcp/perfdhcp.xml +++ b/src/bin/perfdhcp/perfdhcp.xml @@ -64,6 +64,7 @@ + @@ -416,6 +417,18 @@ + + + + + Specify the remote port to use. This must be zero + or a positive integer up to 65535. A value of 0 + (the default) allows perfdhcp + to choose the standard service port. + + + + diff --git a/src/bin/perfdhcp/test_control.cc b/src/bin/perfdhcp/test_control.cc index 39a61251c5..55aaacbc2d 100644 --- a/src/bin/perfdhcp/test_control.cc +++ b/src/bin/perfdhcp/test_control.cc @@ -2190,7 +2190,11 @@ TestControl::setDefaults4(const PerfSocket& socket, // Local client's port (68) pkt->setLocalPort(DHCP4_CLIENT_PORT); // Server's port (67) - pkt->setRemotePort(DHCP4_SERVER_PORT); + if (options.getRemotePort()) { + pkt->setRemotePort(options.getRemotePort()); + } else { + pkt->setRemotePort(DHCP4_SERVER_PORT); + } // The remote server's name or IP. pkt->setRemoteAddr(IOAddress(options.getServerName())); // Set local address. @@ -2216,7 +2220,11 @@ TestControl::setDefaults6(const PerfSocket& socket, // Local client's port (547) pkt->setLocalPort(DHCP6_CLIENT_PORT); // Server's port (548) - pkt->setRemotePort(DHCP6_SERVER_PORT); + if (options.getRemotePort()) { + pkt->setRemotePort(options.getRemotePort()); + } else { + pkt->setRemotePort(DHCP6_SERVER_PORT); + } // Set local address. pkt->setLocalAddr(socket.addr_); // The remote server's name or IP.