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);