From: Francis Dupont Date: Wed, 20 Dec 2017 01:42:11 +0000 (+0100) Subject: [5404] Implemented DHCPv6 port relay X-Git-Tag: trac5458a_base~19^2~3 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=7d520c058883425d0d66ffdd06e1bfca684d7be8;p=thirdparty%2Fkea.git [5404] Implemented DHCPv6 port relay --- diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc index 94fd68d137..cbba992675 100644 --- a/src/bin/dhcp6/dhcp6_srv.cc +++ b/src/bin/dhcp6/dhcp6_srv.cc @@ -764,7 +764,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 = testRelaySourcePort(query); + rsp->setRemotePort(relay_port ? relay_port : DHCP6_SERVER_PORT); } rsp->setLocalPort(DHCP6_SERVER_PORT); @@ -3373,6 +3374,22 @@ void Dhcpv6Srv::processRSOO(const Pkt6Ptr& query, const Pkt6Ptr& rsp) { } } +uint16_t Dhcpv6Srv::testRelaySourcePort(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)) { + // draft-ietf-dhc-relay-port-10.txt 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 53d0d25a3c..6b6b3bde18 100644 --- a/src/bin/dhcp6/dhcp6_srv.h +++ b/src/bin/dhcp6/dhcp6_srv.h @@ -754,6 +754,12 @@ protected: void setStatusCode(boost::shared_ptr& container, const OptionPtr& status); + /// @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 testRelaySourcePort(const Pkt6Ptr& query); + private: /// @public diff --git a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc index cb408c4b34..534b7b2371 100644 --- a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc +++ b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc @@ -1548,6 +1548,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/dhcp6.h b/src/lib/dhcp/dhcp6.h index 5783b15e1d..78c8c07380 100644 --- a/src/lib/dhcp/dhcp6.h +++ b/src/lib/dhcp/dhcp6.h @@ -152,6 +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 */ +// not yet assigned but next free value + D6O_RELAY_SOURCE_PORT = 135, /* draft-ietf-dhc-relay-port-10.txt */ // 135-142 unassigned D6O_IPV6_ADDRESS_ANDSF = 143, /* RFC6153 */ 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 69cd4cc8b7..47d9c2256a 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, diff --git a/src/lib/dhcp/tests/libdhcp++_unittest.cc b/src/lib/dhcp/tests/libdhcp++_unittest.cc index fc0f383786..3503a4e8a6 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));