]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[5404] Implemented DHCPv6 port relay
authorFrancis Dupont <fdupont@isc.org>
Wed, 20 Dec 2017 01:42:11 +0000 (02:42 +0100)
committerFrancis Dupont <fdupont@isc.org>
Wed, 20 Dec 2017 01:42:11 +0000 (02:42 +0100)
src/bin/dhcp6/dhcp6_srv.cc
src/bin/dhcp6/dhcp6_srv.h
src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
src/lib/dhcp/dhcp6.h
src/lib/dhcp/pkt6.cc
src/lib/dhcp/std_option_defs.h
src/lib/dhcp/tests/libdhcp++_unittest.cc

index 94fd68d13745fb0cf439feb7f259009f7e05ce07..cbba992675acaed4ff3dca2531a47f77ea617a0d 100644 (file)
@@ -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.
index 53d0d25a3c284a387b3eeb0e44949349065b47bd..6b6b3bde186b96d4f6b80ac97566034d41bb2d8d 100644 (file)
@@ -754,6 +754,12 @@ protected:
     void setStatusCode(boost::shared_ptr<Option6IA>& 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
index cb408c4b340cc2c403240a046b078aa109fbdffe..534b7b2371ed8e8faf67d805e804f6f41d0c72fe 100644 (file)
@@ -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<const uint8_t*>
+                           (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<const uint8_t*>
+                         (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);
index 5783b15e1de7f371ab4841cff343b7a7e15c630c..78c8c07380e0091e4bda6d2d0e267f552b742a66 100644 (file)
@@ -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 */
 
index 19de2f7a766645cd4b6e409f22ba8eade5f9292e..f30e9088317f5dd8b64ece94955cd86de6da9e21 100644 (file)
@@ -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
index 69cd4cc8b7dbd133510c5d994a020876b026e2c0..47d9c2256a00dedf761aa9adce9b08ca5d16c866 100644 (file)
@@ -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,
index fc0f38378613313bca2dd42826ad5b3517b3dbb5..3503a4e8a65125aef3c29423fd4a9ac7fd0cd856 100644 (file)
@@ -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<uint16_t>));
+
     LibDhcpTest::testStdOptionDefs6(D6O_IPV6_ADDRESS_ANDSF, begin, end,
                                     typeid(Option6AddrLst));