diff --git a/ChangeLog b/ChangeLog index 5053f20927..759f5bc3ba 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,11 @@ +1382. [func] fdupont + Added support for generalized UDP Source Port for DHCP Relay + (RFC 8357) for DHCPv4, DHCPv6 and DHCPv4-over-DHCPv6. Note + this required changes to the inter-server protocol used by + our 4o6 implementation, and is therefore not backwardly + compatible. + (Trac #5404, git xxx) + 1381. [bug] marcin Corrected a bug in the libkea-asiolink library which caused the DHCP servers to crash while processing commands over diff --git a/doc/guide/dhcp4-srv.xml b/doc/guide/dhcp4-srv.xml index 395b74b5be..b70ea6de01 100644 --- a/doc/guide/dhcp4-srv.xml +++ b/doc/guide/dhcp4-srv.xml @@ -2996,7 +2996,8 @@ It is merely echoed by the server DHCPv4-over-DHCPv6 support is experimental and the details of the inter-process communication can change: both the DHCPv4 and DHCPv6 sides should be running the same version - of Kea. + of Kea. For instance the support of port relay (RFC 8357) introduced + such incompatible change. The dhcp4o6-port global parameter specifies diff --git a/doc/guide/dhcp6-srv.xml b/doc/guide/dhcp6-srv.xml index 9ca2dd62dc..a6a584f3d2 100644 --- a/doc/guide/dhcp6-srv.xml +++ b/doc/guide/dhcp6-srv.xml @@ -2718,6 +2718,8 @@ should include options from the isc option space: DHCPv4-over-DHCPv6 support is experimental and the details of the inter-process communication can change: both the DHCPv4 and DHCPv6 sides should be running the same version of Kea. + For instance the support of port relay (RFC 8357) introduced such + such incompatible change. There is only one specific parameter for the DHCPv6 side: diff --git a/src/bin/dhcp4/dhcp4_messages.mes b/src/bin/dhcp4/dhcp4_messages.mes index 8083a621d9..2cf1a297ed 100644 --- a/src/bin/dhcp4/dhcp4_messages.mes +++ b/src/bin/dhcp4/dhcp4_messages.mes @@ -207,11 +207,11 @@ A malformed DHCPv4o6 packet was received. This debug message is printed when the server is receiving a DHCPv4o6 from the DHCPv4 server over inter-process communication. -% DHCP4_DHCP4O6_PACKET_SEND %1: trying to send packet %2 (type %3) to %4 on interface %5 encapsulating %6: %7 (type %8) +% DHCP4_DHCP4O6_PACKET_SEND %1: trying to send packet %2 (type %3) to %4 port %5 on interface %6 encapsulating %7: %8 (type %9) The arguments specify the client identification information (HW address and client identifier), DHCPv6 message name and type, source IPv6 -address and interface name, DHCPv4 client identification, message -name and type. +address and port, and interface name, DHCPv4 client identification, +message name and type. % DHCP4_DHCP4O6_PACKET_SEND_FAIL %1: failed to send DHCPv4o6 packet: %2 This error is output if the IPv4 DHCP server fails to send an @@ -755,3 +755,10 @@ will drop its message if the received message was DHCPDISCOVER, and will send DHCPNAK if the received message was DHCPREQUEST. The argument includes the client and the transaction identification information. +<<<<<<< HEAD +======= + +% DHCP6_DHCP4O6_PACKET_RECEIVED received DHCPv4o6 packet from DHCPv6 server (type %1) for %2 port %3 on interface %4 +This debug message is printed when the server is receiving a DHCPv4o6 +from the DHCPv6 server over inter-process communication. +>>>>>>> trac5404 diff --git a/src/bin/dhcp4/dhcp4_srv.cc b/src/bin/dhcp4/dhcp4_srv.cc index 7db295a483..dfa90d8bbe 100644 --- a/src/bin/dhcp4/dhcp4_srv.cc +++ b/src/bin/dhcp4/dhcp4_srv.cc @@ -211,10 +211,11 @@ Dhcpv4Exchange::initResponse4o6() { if (!query6->relay_info_.empty()) { resp6->copyRelayInfo(query6); } - // Copy interface and remote address + // Copy interface, and remote address and port resp6->setIface(query6->getIface()); resp6->setIndex(query6->getIndex()); resp6->setRemoteAddr(query6->getRemoteAddr()); + resp6->setRemotePort(query6->getRemotePort()); resp_.reset(new Pkt4o6(resp_, resp6)); } @@ -2206,6 +2207,19 @@ Dhcpv4Srv::assignLease(Dhcpv4Exchange& ex) { } } +uint16_t +Dhcpv4Srv::checkRelayPort(const Dhcpv4Exchange& ex) { + + // Look for a relay-port RAI sub-option in the query. + const Pkt4Ptr& query = ex.getQuery(); + const OptionPtr& rai = query->getOption(DHO_DHCP_AGENT_OPTIONS); + if (rai && rai->getOption(RAI_OPTION_RELAY_PORT)) { + // Got the sub-option so use the remote port set by the relay. + return (query->getRemotePort()); + } + return (0); +} + void Dhcpv4Srv::adjustIfaceData(Dhcpv4Exchange& ex) { adjustRemoteAddr(ex); @@ -2232,7 +2246,9 @@ Dhcpv4Srv::adjustIfaceData(Dhcpv4Exchange& ex) { response->setRemotePort(DHCP4_CLIENT_PORT); } else { - response->setRemotePort(DHCP4_SERVER_PORT); + // RFC 8357 section 5.1 + uint16_t relay_port = checkRelayPort(ex); + response->setRemotePort(relay_port ? relay_port : DHCP4_SERVER_PORT); } CfgIfacePtr cfg_iface = CfgMgr::instance().getCurrentCfg()->getCfgIface(); diff --git a/src/bin/dhcp4/dhcp4_srv.h b/src/bin/dhcp4/dhcp4_srv.h index 72169e6967..8922e5bf04 100644 --- a/src/bin/dhcp4/dhcp4_srv.h +++ b/src/bin/dhcp4/dhcp4_srv.h @@ -719,6 +719,12 @@ protected: /// server's response. static void appendServerID(Dhcpv4Exchange& ex); + /// @brief Check if the relay port RAI sub-option was set in the query. + /// + /// @param ex The exchange holding the client's message + /// @return the port to use to join the relay or 0 for the default + static uint16_t checkRelayPort(const Dhcpv4Exchange& ex); + /// @brief Set IP/UDP and interface parameters for the DHCPv4 response. /// /// This method sets the following parameters for the DHCPv4 message being diff --git a/src/bin/dhcp4/dhcp4to6_ipc.cc b/src/bin/dhcp4/dhcp4to6_ipc.cc index 72321b3619..60948951e7 100644 --- a/src/bin/dhcp4/dhcp4to6_ipc.cc +++ b/src/bin/dhcp4/dhcp4to6_ipc.cc @@ -66,6 +66,7 @@ void Dhcp4to6Ipc::handler() { LOG_DEBUG(packet4_logger, DBG_DHCP4_BASIC, DHCP4_DHCP4O6_PACKET_RECEIVED) .arg(static_cast(pkt->getType())) .arg(pkt->getRemoteAddr().toText()) + .arg(pkt->getRemotePort()) .arg(pkt->getIface()); } } catch (const std::exception& e) { @@ -155,6 +156,7 @@ void Dhcp4to6Ipc::handler() { .arg(rsp6->getName()) .arg(static_cast(rsp6->getType())) .arg(rsp6->getRemoteAddr()) + .arg(rsp6->getRemotePort()) .arg(rsp6->getIface()) .arg(rsp->getLabel()) .arg(rsp->getName()) diff --git a/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc b/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc index 34bb04b5d0..99789e1cf5 100644 --- a/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc +++ b/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc @@ -162,6 +162,9 @@ TEST_F(Dhcpv4SrvTest, adjustIfaceDataRelay) { req->setIface("eth1"); req->setIndex(1); + // Set remote port (it will be used in the next test). + req->setRemotePort(1234); + // Create the exchange using the req. Dhcpv4Exchange ex = createExchange(req); @@ -205,6 +208,75 @@ TEST_F(Dhcpv4SrvTest, adjustIfaceDataRelay) { EXPECT_EQ("192.0.1.50", resp->getRemoteAddr().toText()); } +// This test verifies that the remote port is adjusted when +// the query carries a relay port RAI sub-option. +TEST_F(Dhcpv4SrvTest, adjustIfaceDataRelayPort) { + IfaceMgrTestConfig test_config(true); + IfaceMgr::instance().openSockets4(); + + // Create the instance of the incoming packet. + boost::shared_ptr req(new Pkt4(DHCPDISCOVER, 1234)); + // Set the giaddr to non-zero address and hops to non-zero value + // as if it was relayed. + req->setGiaddr(IOAddress("192.0.1.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.2.5")); + req->setLocalPort(1001); + req->setIface("eth1"); + req->setIndex(1); + + // Set remote port. + req->setRemotePort(1234); + + // Add a RAI relay-port sub-option (the only difference with the previous test). + OptionDefinitionPtr rai_def = + LibDHCP::getOptionDef(DHCP4_OPTION_SPACE, DHO_DHCP_AGENT_OPTIONS); + ASSERT_TRUE(rai_def); + OptionCustomPtr rai(new OptionCustom(*rai_def, Option::V4)); + ASSERT_TRUE(rai); + req->addOption(rai); + OptionPtr relay_port(new Option(Option::V4, RAI_OPTION_RELAY_PORT)); + ASSERT_TRUE(relay_port); + rai->addOption(relay_port); + + // 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")); + // Set hops value for the response. + resp->setHops(req->getHops()); + + // Set the remote port to 67 as we know it will be updated. + resp->setRemotePort(67); + + // This function never throws. + ASSERT_NO_THROW(NakedDhcpv4Srv::adjustIfaceData(ex)); + + // Now the destination address should be relay's address. + EXPECT_EQ("192.0.1.1", resp->getRemoteAddr().toText()); + // The query has been relayed, so the response should be sent to the + // port 67, but here there is a relay port RAI so another value is used. + EXPECT_EQ(1234, resp->getRemotePort()); + // Local address should be the address assigned to interface eth1. + EXPECT_EQ("192.0.2.5", 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("eth1", resp->getIface()); + EXPECT_EQ(1, resp->getIndex()); +} + // This test verifies that it is possible to configure the server to use // routing information to determine the right outbound interface to sent // responses to a relayed client. diff --git a/src/bin/dhcp6/dhcp6_messages.mes b/src/bin/dhcp6/dhcp6_messages.mes index 204e0ddd02..f189c51a8e 100644 --- a/src/bin/dhcp6/dhcp6_messages.mes +++ b/src/bin/dhcp6/dhcp6_messages.mes @@ -232,7 +232,7 @@ received in Decline message. It's expected that the option will contain an address that is being declined. Specific information will be printed in a separate message. -% DHCP6_DHCP4O6_PACKET_RECEIVED received DHCPv4o6 packet from DHCPv4 server (type %1) for %2 on interface %3 +% DHCP6_DHCP4O6_PACKET_RECEIVED received DHCPv4o6 packet from DHCPv4 server (type %1) for %2 port %3 on interface %4 This debug message is printed when the server is receiving a DHCPv4o6 from the DHCPv4 server over inter-process communication. diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc index 83125bedb5..5b39edaad9 100644 --- a/src/bin/dhcp6/dhcp6_srv.cc +++ b/src/bin/dhcp6/dhcp6_srv.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2011-2017 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2011-2018 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -791,7 +791,8 @@ Dhcpv6Srv::processPacket(Pkt6Ptr& query, Pkt6Ptr& rsp) { rsp->setRemotePort(DHCP6_CLIENT_PORT); } else { // Relayed traffic, send back to the relay agent - rsp->setRemotePort(DHCP6_SERVER_PORT); + uint16_t relay_port = checkRelaySourcePort(query); + rsp->setRemotePort(relay_port ? relay_port : DHCP6_SERVER_PORT); } rsp->setLocalPort(DHCP6_SERVER_PORT); @@ -3464,6 +3465,22 @@ void Dhcpv6Srv::processRSOO(const Pkt6Ptr& query, const Pkt6Ptr& rsp) { } } +uint16_t Dhcpv6Srv::checkRelaySourcePort(const Pkt6Ptr& query) { + + if (query->relay_info_.empty()) { + // No relay agent + return (0); + } + + // Did the last relay agent add a relay-source-port? + if (query->getRelayOption(D6O_RELAY_SOURCE_PORT, 0)) { + // RFC 8357 section 5.2 + return (query->getRemotePort()); + } + + return (0); +} + void Dhcpv6Srv::processStatsReceived(const Pkt6Ptr& query) { // Note that we're not bumping pkt6-received statistic as it was // increased early in the packet reception code. diff --git a/src/bin/dhcp6/dhcp6_srv.h b/src/bin/dhcp6/dhcp6_srv.h index d1a67ddef4..96d4432e39 100644 --- a/src/bin/dhcp6/dhcp6_srv.h +++ b/src/bin/dhcp6/dhcp6_srv.h @@ -1,4 +1,4 @@ -// Copyright (C) 2011-2017 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2011-2018 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -758,6 +758,16 @@ protected: void setStatusCode(boost::shared_ptr& container, const OptionPtr& status); +public: + + /// Used for DHCPv4-over-DHCPv6 too. + + /// @brief Check if the last relay added a relay-source-port option. + /// + /// @param query DHCPv6 message to be checked. + /// @return the port to use to join the relay or 0 for the default. + static uint16_t checkRelaySourcePort(const Pkt6Ptr& query); + private: /// @public diff --git a/src/bin/dhcp6/dhcp6to4_ipc.cc b/src/bin/dhcp6/dhcp6to4_ipc.cc index 0e8356516c..454cbd19c6 100644 --- a/src/bin/dhcp6/dhcp6to4_ipc.cc +++ b/src/bin/dhcp6/dhcp6to4_ipc.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2015-2018 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -64,6 +64,7 @@ void Dhcp6to4Ipc::handler() { LOG_DEBUG(packet6_logger, DBG_DHCP6_BASIC, DHCP6_DHCP4O6_PACKET_RECEIVED) .arg(static_cast(pkt->getType())) .arg(pkt->getRemoteAddr().toText()) + .arg(pkt->getRemotePort()) .arg(pkt->getIface()); } } catch (const std::exception& e) { @@ -77,6 +78,9 @@ void Dhcp6to4Ipc::handler() { // Should we check it is a DHCPV6_DHCPV4_RESPONSE? + // Handle relay port + uint16_t relay_port = Dhcpv6Srv::checkRelaySourcePort(pkt); + // The received message has been unpacked by the receive() function. This // method could have modified the message so it's better to pack() it // again because we'll be forwarding it to a client. @@ -89,7 +93,7 @@ void Dhcp6to4Ipc::handler() { // 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)) { - pkt->setRemotePort(DHCP6_SERVER_PORT); + pkt->setRemotePort(relay_port ? relay_port : DHCP6_SERVER_PORT); } else { pkt->setRemotePort(DHCP6_CLIENT_PORT); } diff --git a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc index 5571a96a2f..b4d4a0dec8 100644 --- a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc +++ b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc @@ -1572,6 +1572,97 @@ TEST_F(Dhcpv6SrvTest, portsRelayedTraffic) { EXPECT_EQ(DHCP6_SERVER_PORT, adv->getRemotePort()); } +// Test that the server processes relay-source-port option correctly. +TEST_F(Dhcpv6SrvTest, relaySourcePort) { + + NakedDhcpv6Srv srv(0); + + string config = + "{" + " \"preferred-lifetime\": 3000," + " \"rebind-timer\": 2000, " + " \"renew-timer\": 1000, " + " \"subnet6\": [ { " + " \"pools\": [ { \"pool\": \"2001:db8::/64\" } ]," + " \"subnet\": \"2001:db8::/48\" " + " } ]," + " \"valid-lifetime\": 4000" + "}"; + + EXPECT_NO_THROW(configure(config, srv)); + + // Create a solicit + Pkt6Ptr sol(new Pkt6(DHCPV6_SOLICIT, 1234)); + sol->setRemoteAddr(IOAddress("fe80::abcd")); + sol->setIface("eth0"); + sol->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000)); + OptionPtr clientid = generateClientId(); + sol->addOption(clientid); + + // Pretend the packet came via one relay. + Pkt6::RelayInfo relay; + relay.msg_type_ = DHCPV6_RELAY_FORW; + relay.hop_count_ = 1; + relay.linkaddr_ = IOAddress("2001:db8::1"); + relay.peeraddr_ = IOAddress("fe80::1"); + + // Set the source port + sol->setRemotePort(1234); + + // Simulate that we have received that traffic + sol->pack(); + + // Add a relay-source-port option + OptionBuffer zero(2, 0); + OptionPtr opt(new Option(Option::V6, D6O_RELAY_SOURCE_PORT, zero)); + relay.options_.insert(make_pair(opt->getType(), opt)); + sol->relay_info_.push_back(relay); + + // Simulate that we have received that traffic + sol->pack(); + EXPECT_EQ(DHCPV6_RELAY_FORW, sol->getBuffer()[0]); + Pkt6Ptr query(new Pkt6(static_cast + (sol->getBuffer().getData()), + sol->getBuffer().getLength())); + query->setRemoteAddr(sol->getRemoteAddr()); + query->setRemotePort(sol->getRemotePort()); + query->setLocalAddr(sol->getLocalAddr()); + query->setLocalPort(sol->getLocalPort()); + query->setIface(sol->getIface()); + + srv.fakeReceive(query); + + // 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(); + + // Check trace of processing + EXPECT_EQ(1234, query->getRemotePort()); + ASSERT_EQ(1, query->relay_info_.size()); + EXPECT_TRUE(query->getRelayOption(D6O_RELAY_SOURCE_PORT, 0)); + + // Get Response... + ASSERT_FALSE(srv.fake_sent_.empty()); + Pkt6Ptr rsp = srv.fake_sent_.front(); + ASSERT_TRUE(rsp); + + // Check it + EXPECT_EQ(1234, rsp->getRemotePort()); + EXPECT_EQ(DHCPV6_RELAY_REPL, rsp->getBuffer()[0]); + + // Get Advertise + Pkt6Ptr adv(new Pkt6(static_cast + (rsp->getBuffer().getData()), + rsp->getBuffer().getLength())); + adv->unpack(); + + // Check it + EXPECT_EQ(DHCPV6_ADVERTISE, adv->getType()); + ASSERT_EQ(1, adv->relay_info_.size()); + EXPECT_TRUE(adv->getRelayOption(D6O_RELAY_SOURCE_PORT, 0)); +} + // Checks effect of persistency (aka always-true) flag on the ORO TEST_F(Dhcpv6SrvTest, prlPersistency) { IfaceMgrTestConfig test_config(true); diff --git a/src/lib/dhcp/dhcp4.h b/src/lib/dhcp/dhcp4.h index 56d2586f68..b72af11bac 100644 --- a/src/lib/dhcp/dhcp4.h +++ b/src/lib/dhcp/dhcp4.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2004-2017 Internet Systems Consortium, Inc. ("ISC") + * Copyright (C) 2004-2018 Internet Systems Consortium, Inc. ("ISC") * Copyright (c) 1995-2003 by Internet Software Consortium * * This Source Code Form is subject to the terms of the Mozilla Public @@ -273,6 +273,7 @@ static const uint16_t RAI_OPTION_ACCESS_POINT_NAME = 15; // RFC7839 static const uint16_t RAI_OPTION_ACCESS_POINT_BSSID = 16; // RFC7839 static const uint16_t RAI_OPTION_OPERATOR_ID = 17; // RFC7839 static const uint16_t RAI_OPTION_OPERATOR_REALM = 18; // RFC7839 +static const uint16_t RAI_OPTION_RELAY_PORT = 19; // RFC8357 static const uint16_t RAI_OPTION_VIRTUAL_SUBNET_SELECT = 151; //RFC6607 static const uint16_t RAI_OPTION_VIRTUAL_SUBNET_SELECT_CTRL = 152; //RFC6607 diff --git a/src/lib/dhcp/dhcp6.h b/src/lib/dhcp/dhcp6.h index cf28ba2bb2..f5a11b7aac 100644 --- a/src/lib/dhcp/dhcp6.h +++ b/src/lib/dhcp/dhcp6.h @@ -152,7 +152,8 @@ enum DHCPv6OptionType { // D6O_F_SERVER_STATE = 132, /* RFC8156 */ // D6O_F_START_TIME_OF_STATE = 133, /* RFC8156 */ // D6O_F_STATE_EXPIRATION_TIME = 134, /* RFC8156 */ - // 135-142 unassigned + D6O_RELAY_SOURCE_PORT = 135, /* RFC8357 */ + // 136-142 unassigned D6O_IPV6_ADDRESS_ANDSF = 143, /* RFC6153 */ // The following are EXPERIMENTAL and may change when IANA assigns official @@ -278,6 +279,7 @@ static const uint32_t ENTERPRISE_ID_ISC = 2495; codes for the ISC vendor specific options used in 4o6 */ static const uint16_t ISC_V6_4O6_INTERFACE = 60000; static const uint16_t ISC_V6_4O6_SRC_ADDRESS = 60001; +static const uint16_t ISC_V6_4O6_SRC_PORT = 60002; /* Offsets into IA_*'s where Option spaces commence. */ static const uint16_t IA_NA_OFFSET = 12; /* IAID, T1, T2, all 4 octets each */ diff --git a/src/lib/dhcp/pkt6.cc b/src/lib/dhcp/pkt6.cc index 19de2f7a76..f30e908831 100644 --- a/src/lib/dhcp/pkt6.cc +++ b/src/lib/dhcp/pkt6.cc @@ -794,6 +794,12 @@ void Pkt6::copyRelayInfo(const Pkt6Ptr& question) { info.options_.insert(make_pair(opt->getType(), opt)); } + // Same for relay-source-port option + opt = question->getNonCopiedRelayOption(D6O_RELAY_SOURCE_PORT, i); + if (opt) { + info.options_.insert(make_pair(opt->getType(), opt)); + } + /// @todo: Implement support for ERO (Echo Request Option, RFC4994) // Add this relay-forw info (client's message) to our relay-repl diff --git a/src/lib/dhcp/std_option_defs.h b/src/lib/dhcp/std_option_defs.h index 5b3e90bf20..c097d250db 100644 --- a/src/lib/dhcp/std_option_defs.h +++ b/src/lib/dhcp/std_option_defs.h @@ -444,6 +444,7 @@ const OptionDefParams STANDARD_V6_OPTION_DEFINITIONS[] = { NO_RECORD_DEF, "" }, { "v6-captive-portal", D6O_V6_CAPTIVE_PORTAL, OPT_STRING_TYPE, false, NO_RECORD_DEF, "" }, + { "relay-source-port", D6O_RELAY_SOURCE_PORT, OPT_UINT16_TYPE, false, NO_RECORD_DEF, "" }, { "ipv6-address-andsf", D6O_IPV6_ADDRESS_ANDSF, OPT_IPV6_ADDRESS_TYPE, true, NO_RECORD_DEF, "" }, { "public-key", D6O_PUBLIC_KEY, OPT_BINARY_TYPE, false, @@ -489,7 +490,9 @@ const OptionDefParams ISC_V6_OPTION_DEFINITIONS[] = { { "4o6-interface", ISC_V6_4O6_INTERFACE, OPT_STRING_TYPE, false, NO_RECORD_DEF, "" }, { "4o6-source-address", ISC_V6_4O6_SRC_ADDRESS, OPT_IPV6_ADDRESS_TYPE, - false, NO_RECORD_DEF, "" } + false, NO_RECORD_DEF, "" }, + { "4o6-source-port", ISC_V6_4O6_SRC_PORT, OPT_UINT16_TYPE, false, + NO_RECORD_DEF, "" } }; const int ISC_V6_OPTION_DEFINITIONS_SIZE = diff --git a/src/lib/dhcp/tests/libdhcp++_unittest.cc b/src/lib/dhcp/tests/libdhcp++_unittest.cc index 53164056d7..8462cf33ba 100644 --- a/src/lib/dhcp/tests/libdhcp++_unittest.cc +++ b/src/lib/dhcp/tests/libdhcp++_unittest.cc @@ -1736,6 +1736,9 @@ TEST_F(LibDhcpTest, stdOptionDefs6) { LibDhcpTest::testStdOptionDefs6(D6O_V6_CAPTIVE_PORTAL, begin, end, typeid(OptionString)); + LibDhcpTest::testStdOptionDefs6(D6O_RELAY_SOURCE_PORT, begin, begin + 2, + typeid(OptionInt)); + LibDhcpTest::testStdOptionDefs6(D6O_IPV6_ADDRESS_ANDSF, begin, end, typeid(Option6AddrLst)); diff --git a/src/lib/dhcpsrv/dhcp4o6_ipc.cc b/src/lib/dhcpsrv/dhcp4o6_ipc.cc index ac58fd11a5..3720447333 100644 --- a/src/lib/dhcpsrv/dhcp4o6_ipc.cc +++ b/src/lib/dhcpsrv/dhcp4o6_ipc.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -191,14 +192,27 @@ Pkt6Ptr Dhcp4o6IpcBase::receive() { "or has incorrect type)"); } + // Get the option holding source port. + OptionUint16Ptr sport = boost::dynamic_pointer_cast< + OptionUint16>(option_vendor->getOption(ISC_V6_4O6_SRC_PORT)); + if (!sport) { + LOG_WARN(dhcpsrv_logger, DHCPSRV_DHCP4O6_RECEIVED_BAD_PACKET) + .arg("no source port suboption"); + isc_throw(Dhcp4o6IpcError, + "malformed packet (source port suboption missing " + "or has incorrect type)"); + } + // Update the packet. pkt->setRemoteAddr(srcs->readAddress()); + pkt->setRemotePort(sport->getValue()); pkt->setIface(iface->getName()); pkt->setIndex(iface->getIndex()); // Remove options that have been added by the IPC sender. static_cast(option_vendor->delOption(ISC_V6_4O6_INTERFACE)); static_cast(option_vendor->delOption(ISC_V6_4O6_SRC_ADDRESS)); + static_cast(option_vendor->delOption(ISC_V6_4O6_SRC_PORT)); // If there are no more options, the IPC sender has probably created the // vendor option, in which case we should remove it here. @@ -243,6 +257,9 @@ void Dhcp4o6IpcBase::send(const Pkt6Ptr& pkt) { option_vendor->addOption(Option6AddrLstPtr(new Option6AddrLst( ISC_V6_4O6_SRC_ADDRESS, pkt->getRemoteAddr()))); + option_vendor->addOption(OptionUint16Ptr(new OptionUint16(Option::V6, + ISC_V6_4O6_SRC_PORT, + pkt->getRemotePort()))); // Get packet content OutputBuffer& buf = pkt->getBuffer(); buf.clear(); diff --git a/src/lib/dhcpsrv/dhcp4o6_ipc.h b/src/lib/dhcpsrv/dhcp4o6_ipc.h index fd7c4d8c16..d67f2d2176 100644 --- a/src/lib/dhcpsrv/dhcp4o6_ipc.h +++ b/src/lib/dhcpsrv/dhcp4o6_ipc.h @@ -1,4 +1,4 @@ -// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -54,10 +54,10 @@ public: /// the original DHCPv4 query message sent by the client. This /// information is known by the DHCPv6 server and needs to be conveyed /// to the DHCPv4 server. The IPC conveys it in the -/// @c ISC_V6_4O6_INTERFACE and @c ISC_V6_4O6_SRC_ADDRESS options -/// within the Vendor Specific Information option, with ISC -/// enterprise id. These options are added by the IPC sender and removed -/// by the IPC receiver. +/// @c ISC_V6_4O6_INTERFACE, @c ISC_V6_4O6_SRC_ADDRESS and @c +/// ISC_V6_4O6_SRC_PORT options within the Vendor Specific Information +/// option, with ISC enterprise id. These options are added by the IPC +/// sender and removed by the IPC receiver. class Dhcp4o6IpcBase : public boost::noncopyable { public: @@ -105,11 +105,11 @@ public: /// @brief Send message over IPC. /// - /// The IPC uses @c ISC_V6_4O6_INTERFACE and @c ISC_V6_4O6_SRC_ADDRESS - /// options conveyed within the Vendor Specific Information option, with - /// ISC enterprise id, to communicate the client remote address and the - /// interface on which the DHCPv4 query was received. These options will - /// be removed by the receiver. + /// The IPC uses @c ISC_V6_4O6_INTERFACE, @c ISC_V6_4O6_SRC_ADDRESS + /// and @c ISC_V6_4O6_SRC_PORT options conveyed within the Vendor + /// Specific Information option, with ISC enterprise id, to communicate + /// the client remote address and the interface on which the DHCPv4 query + /// was received. These options will be removed by the receiver. /// /// @param pkt Pointer to a DHCPv6 message with interface and remote /// address. diff --git a/src/lib/dhcpsrv/tests/dhcp4o6_ipc_unittest.cc b/src/lib/dhcpsrv/tests/dhcp4o6_ipc_unittest.cc index 07ac8bd326..84b6d10ae2 100644 --- a/src/lib/dhcpsrv/tests/dhcp4o6_ipc_unittest.cc +++ b/src/lib/dhcpsrv/tests/dhcp4o6_ipc_unittest.cc @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -173,6 +174,10 @@ Dhcp4o6IpcBaseTest::createDHCPv4o6Message(uint16_t msg_type, // right address. pkt->setRemoteAddr(IOAddress(concatenate("2001:db8:1::", postfix))); + // The remote port of the sender of the DHCPv6 packet is carried + // between the servers in the dedicated option. + pkt->setRemotePort(10000 + (postfix % 1000)); + // Determine the endpoint type using the message type. TestIpc::EndpointType src = (msg_type == DHCPV6_DHCPV4_QUERY) ? TestIpc::ENDPOINT_TYPE_V6 : TestIpc::ENDPOINT_TYPE_V4; @@ -286,6 +291,9 @@ Dhcp4o6IpcBaseTest::testSendReceive(uint16_t iterations_num, EXPECT_EQ(concatenate("2001:db8:1::", i), pkt_received->getRemoteAddr().toText()); + // Check that the port conveyed is correct. + EXPECT_EQ(10000 + (i % 1000), pkt_received->getRemotePort()); + // Check that encapsulated DHCPv4 message has been received. EXPECT_TRUE(pkt_received->getOption(D6O_DHCPV4_MSG)); @@ -320,6 +328,7 @@ Dhcp4o6IpcBaseTest::testReceiveError(const Pkt6Ptr& pkt) { pkt->setIface("eth0"); pkt->setRemoteAddr(IOAddress("2001:db8:1::1")); + pkt->setRemotePort(TEST_PORT); pkt->addOption(createDHCPv4MsgOption(TestIpc::ENDPOINT_TYPE_V6)); OutputBuffer& buf = pkt->getBuffer(); @@ -476,6 +485,10 @@ TEST_F(Dhcp4o6IpcBaseTest, receiveWithoutInterfaceOption) { Option6AddrLstPtr(new Option6AddrLst(ISC_V6_4O6_SRC_ADDRESS, IOAddress("2001:db8:1::1"))) ); + option_vendor->addOption( + OptionUint16Ptr(new OptionUint16(Option::V6, + ISC_V6_4O6_SRC_PORT, + TEST_PORT))); pkt->addOption(option_vendor); testReceiveError(pkt); @@ -495,6 +508,10 @@ TEST_F(Dhcp4o6IpcBaseTest, receiveWithInvalidInterface) { Option6AddrLstPtr(new Option6AddrLst(ISC_V6_4O6_SRC_ADDRESS, IOAddress("2001:db8:1::1"))) ); + option_vendor->addOption( + OptionUint16Ptr(new OptionUint16(Option::V6, + ISC_V6_4O6_SRC_PORT, + TEST_PORT))); pkt->addOption(option_vendor); testReceiveError(pkt); @@ -510,6 +527,28 @@ TEST_F(Dhcp4o6IpcBaseTest, receiveWithoutSourceAddressOption) { option_vendor->addOption( OptionStringPtr(new OptionString(Option::V6, ISC_V6_4O6_INTERFACE, "eth0"))); + option_vendor->addOption( + OptionUint16Ptr(new OptionUint16(Option::V6, + ISC_V6_4O6_SRC_PORT, + TEST_PORT))); + + pkt->addOption(option_vendor); + testReceiveError(pkt); +} + +// This test verifies that receiving packet over the IPC fails when the +// source port option is not present. +TEST_F(Dhcp4o6IpcBaseTest, receiveWithoutSourcePortOption) { + Pkt6Ptr pkt(new Pkt6(DHCPV6_DHCPV4_QUERY, 0)); + OptionVendorPtr option_vendor(new OptionVendor(Option::V6, + ENTERPRISE_ID_ISC)); + option_vendor->addOption( + OptionStringPtr(new OptionString(Option::V6, ISC_V6_4O6_INTERFACE, + "eth0"))); + option_vendor->addOption( + Option6AddrLstPtr(new Option6AddrLst(ISC_V6_4O6_SRC_ADDRESS, + IOAddress("2001:db8:1::1"))) + ); pkt->addOption(option_vendor); testReceiveError(pkt);