Browse Source

[3549] Pkt::getMAC(HWADDR_SOURCE_IPV6_LINK_LOCAL) implemented.

Tomek Mrugalski 10 years ago
parent
commit
0b5b2461ac
6 changed files with 170 additions and 14 deletions
  1. 6 5
      src/lib/dhcp/pkt.cc
  2. 23 5
      src/lib/dhcp/pkt.h
  3. 12 0
      src/lib/dhcp/pkt4.h
  4. 12 0
      src/lib/dhcp/pkt6.cc
  5. 18 2
      src/lib/dhcp/pkt6.h
  6. 99 2
      src/lib/dhcp/tests/pkt6_unittest.cc

+ 6 - 5
src/lib/dhcp/pkt.cc

@@ -143,11 +143,11 @@ Pkt::getMAC(uint32_t hw_addr_src) {
     // Method 2: Extracted from DUID-LLT or DUID-LL
 
     // Method 3: Extracted from source IPv6 link-local address
-    if (hw_addr_src & MAC_SOURCE_IPV6_LINK_LOCAL) {
+    if (hw_addr_src & HWADDR_SOURCE_IPV6_LINK_LOCAL) {
         mac = getMACFromSrcLinkLocalAddr();
         if (mac) {
             return (mac);
-        } else if (hw_addr_src ==  MAC_SOURCE_IPV6_LINK_LOCAL) {
+        } else if (hw_addr_src ==  HWADDR_SOURCE_IPV6_LINK_LOCAL) {
             // If we're interested only in link-local addr as source of that
             // info, there's no point in trying other options.
             return (HWAddrPtr());
@@ -169,12 +169,13 @@ Pkt::getMAC(uint32_t hw_addr_src) {
 }
 
 HWAddrPtr
-Pkt::getMACFromSrcLinkLocalAddr() {
-    if (!remote_addr_.isV6LinkLocal()) {
+Pkt::getMACfromIPv6(const isc::asiolink::IOAddress& addr) {
+
+    if (!addr.isV6LinkLocal()) {
         return (HWAddrPtr());
     }
 
-    std::vector<uint8_t> bin = remote_addr_.toBytes();
+    std::vector<uint8_t> bin = addr.toBytes();
 
     // Double check that it's of appropriate size
     if ((bin.size() != isc::asiolink::V6ADDRESS_LEN) ||

+ 23 - 5
src/lib/dhcp/pkt.h

@@ -62,7 +62,7 @@ public:
     /// Extracted from IPv6 link-local address. Not 100% reliable, as the
     /// client can use different IID other than EUI-64, e.g. Windows supports
     /// RFC4941 and uses random values instead of EUI-64.
-    //static const uint32_t HWADDR_SOURCE_IPV6_LINK_LOCAL = 0x0004;
+    static const uint32_t HWADDR_SOURCE_IPV6_LINK_LOCAL = 0x0004;
 
     /// Get it from RFC6939 option. (A relay agent can insert client link layer
     /// address option). Note that a skilled attacker can fake that by sending
@@ -505,7 +505,7 @@ protected:
 
     /// @brief Attempts to obtain MAC address from source link-local IPv6 address
     ///
-    /// This method is called from getMAC(MAC_SOURCE_IPV6_LINK_LOCAL) and should
+    /// This method is called from getMAC(HWADDR_SOURCE_IPV6_LINK_LOCAL) and should
     /// not be called directly. It is not 100% reliable. The source IPv6 address
     /// does not necessarily have to be link-local (may be global or ULA) and
     /// even if it's link-local, it doesn't necessarily be based on EUI-64. For
@@ -515,10 +515,28 @@ protected:
     /// For direct message, it attempts to use remote_addr_ field. For relayed
     /// message, it uses peer-addr of the first relay.
     ///
-    /// This method is not applicable to IPv4.
+    /// @note This is a pure virtual method and must be implemented in
+    /// the derived classes. The @c Pkt6 class have respective implementation.
+    /// This method is not applicable to DHCPv4.
+    ///
+    /// @return hardware address (or NULL)
+    virtual HWAddrPtr getMACFromSrcLinkLocalAddr() = 0;
+
+    /// @brief Attempts to convert IPv6 address into MAC.
+    ///
+    /// Utility method that attempts to convert link-local IPv6 address to the
+    /// MAC address. That works only for link-local IPv6 addresses that are
+    /// based on EUI-64.
+    ///
+    /// @note This method uses hardware type of the interface the packet was
+    /// received on. If you have multiple access technologies in your network
+    /// (e.g. client connected to WiFi that relayed the traffic to the server
+    /// over Ethernet), hardware type may be invalid.
     ///
-    /// @param hardware address (or NULL)
-    HWAddrPtr getMACFromSrcLinkLocalAddr();
+    /// @param addr IPv6 address to be converted
+    /// @return hardware address (or NULL)
+    HWAddrPtr
+    getMACfromIPv6(const isc::asiolink::IOAddress& addr);
 
     /// Transaction-id (32 bits for v4, 24 bits for v6)
     uint32_t transid_;

+ 12 - 0
src/lib/dhcp/pkt4.h

@@ -390,6 +390,18 @@ protected:
     uint8_t
     DHCPTypeToBootpType(uint8_t dhcpType);
 
+    /// @brief No-op
+    ///
+    /// This method returns hardware address generated from the IPv6 link-local
+    /// address. As there is no IPv4-equivalent, it always returns NULL.
+    /// We need this stub implementation here, to keep all the get hardware
+    /// address logic in the base class.
+    ///
+    /// @return always NULL
+    virtual HWAddrPtr getMACFromSrcLinkLocalAddr() {
+        return (HWAddrPtr());
+    }
+
     /// local HW address (dst if receiving packet, src if sending packet)
     HWAddrPtr local_hwaddr_;
 

+ 12 - 0
src/lib/dhcp/pkt6.cc

@@ -542,5 +542,17 @@ void Pkt6::copyRelayInfo(const Pkt6Ptr& question) {
     }
 }
 
+HWAddrPtr
+Pkt6::getMACFromSrcLinkLocalAddr() {
+    if (relay_info_.empty()) {
+        // This is a direct message, use source address
+        return (getMACfromIPv6(remote_addr_));
+    }
+
+    // This is a relayed message, get the peer-addr from the first relay-forw
+    return (getMACfromIPv6(relay_info_[relay_info_.size() - 1].peeraddr_));
+}
+
+
 } // end of isc::dhcp namespace
 } // end of isc namespace

+ 18 - 2
src/lib/dhcp/pkt6.h

@@ -268,17 +268,33 @@ public:
     /// @param question client's packet
     void copyRelayInfo(const Pkt6Ptr& question);
 
-    /// relay information
+    /// @biref Relay information.
     ///
-    /// this is a public field. Otherwise we hit one of the two problems:
+    /// This is a public field. Otherwise we hit one of the two problems:
     /// we return reference to an internal field (and that reference could
     /// be potentially used past Pkt6 object lifetime causing badness) or
     /// we return a copy (which is inefficient and also causes any updates
     /// to be impossible). Therefore public field is considered the best
     /// (or least bad) solution.
+    ///
+    /// This vector is arranged in the order packet is encapsulated, i.e.
+    /// relay[0] was the outermort encapsulation (relay closest to the server),
+    /// relay[last] was the innermost encapsulation (relay closest to the
+    /// client).
     std::vector<RelayInfo> relay_info_;
 
 protected:
+
+    /// @brief Attempts to generate MAC/Hardware address from IPv6 link-local
+    ///        address.
+    ///
+    /// This method uses source IPv6 address for direct messages and the
+    /// peeraddr or the first relay that saw that packet. It may fail if the
+    /// address is not link-local or does not use EUI-64 identifier.
+    ///
+    /// @return Hardware address (or NULL)
+    virtual HWAddrPtr getMACFromSrcLinkLocalAddr();
+
     /// @brief Builds on wire packet for TCP transmission.
     ///
     /// @todo This function is not implemented yet.

+ 99 - 2
src/lib/dhcp/tests/pkt6_unittest.cc

@@ -910,8 +910,8 @@ TEST_F(Pkt6Test, getMAC) {
 }
 
 // Test checks whether getMACFromIPv6LinkLocal() returns the hardware (MAC)
-// address properly.
-TEST_F(Pkt6Test, getMACFromIPv6LinkLocal) {
+// address properly (for direct message).
+TEST_F(Pkt6Test, getMACFromIPv6LinkLocal_direct) {
     Pkt6 pkt(DHCPV6_ADVERTISE, 1234);
 
     // Let's get the first interface
@@ -941,4 +941,101 @@ TEST_F(Pkt6Test, getMACFromIPv6LinkLocal) {
     EXPECT_EQ(tmp.str(), found->toText(true));
 }
 
+// Test checks whether getMACFromIPv6LinkLocal() returns the hardware (MAC)
+// address properly (for relayed message).
+TEST_F(Pkt6Test, getMACFromIPv6LinkLocal_singleRelay) {
+
+    // Let's create a Solicit first...
+    Pkt6 pkt(DHCPV6_SOLICIT, 1234);
+
+    // ... and pretend it was relayed by a single relay.
+    Pkt6::RelayInfo info;
+    pkt.addRelayInfo(info);
+    ASSERT_EQ(1, pkt.relay_info_.size());
+
+    // Let's get the first interface
+    Iface* iface = IfaceMgr::instance().getIface(1);
+    ASSERT_TRUE(iface);
+
+    // and set source interface data properly. getMACFromIPv6LinkLocal attempts
+    // to use source interface to obtain hardware type
+    pkt.setIface(iface->getName());
+    pkt.setIndex(iface->getIndex());
+
+    IOAddress global("2001:db8::204:06ff:fe08:0a:0c"); // global address
+    IOAddress linklocal_noneui64("fe80::0204:0608:0a0c:0e10"); // no fffe
+    IOAddress linklocal_eui64("fe80::204:06ff:fe08:0a0c"); // valid EUI-64
+
+    // If received from a global address, this method should fail
+    pkt.relay_info_[0].peeraddr_ = global;
+    EXPECT_FALSE(pkt.getMAC(Pkt::HWADDR_SOURCE_IPV6_LINK_LOCAL));
+
+    // If received from a link-local that does not use EUI-64, it shoul fail
+    pkt.relay_info_[0].peeraddr_ = linklocal_noneui64;
+    EXPECT_FALSE(pkt.getMAC(Pkt::HWADDR_SOURCE_IPV6_LINK_LOCAL));
+
+    // If received from link-local that is EUI-64 based, it should succeed
+    pkt.relay_info_[0].peeraddr_ = linklocal_eui64;
+    HWAddrPtr found = pkt.getMAC(Pkt::HWADDR_SOURCE_IPV6_LINK_LOCAL);
+    ASSERT_TRUE(found);
+
+    stringstream tmp;
+    tmp << "hwtype=" << (int)iface->getHWType() << " 02:04:06:08:0a:0c";
+    EXPECT_EQ(tmp.str(), found->toText(true));
+}
+
+// Test checks whether getMACFromIPv6LinkLocal() returns the hardware (MAC)
+// address properly (for a message relayed multiple times).
+TEST_F(Pkt6Test, getMACFromIPv6LinkLocal_multiRelay) {
+
+    // Let's create a Solicit first...
+    Pkt6 pkt(DHCPV6_SOLICIT, 1234);
+
+    // ... and pretend it was relayed via 3 relays. Keep in mind that the relays
+    // are stored in relay_info_ in the encapsulation order rather than in
+    // traverse order. The following simulates:
+    // client --- relay1 --- relay2 --- relay3 --- server
+    IOAddress linklocal1("fe80::ff:fe00:1"); // valid EUI-64
+    IOAddress linklocal2("fe80::ff:fe00:2"); // valid EUI-64
+    IOAddress linklocal3("fe80::ff:fe00:3"); // valid EUI-64
+
+    // Let's add info about relay3. This was the last relay, so it added the
+    // outermost encapsulation layer, so it was parsed first during reception.
+    // Its peer-addr field contains an address of relay2, so it's useless for
+    // this method.
+    Pkt6::RelayInfo info;
+    info.peeraddr_ = linklocal3;
+    pkt.addRelayInfo(info);
+
+    // Now add info about relay2. Its peer-addr contains an address of the
+    // previous relay (relay1). Still useless for us.
+    info.peeraddr_ = linklocal2;
+    pkt.addRelayInfo(info);
+
+    // Finally add the first relay. This is the relay that received the packet
+    // from the client directly, so its peer-addr field contains an address of
+    // the client. The method should get that address and build MAC from it.
+    info.peeraddr_ = linklocal1;
+    pkt.addRelayInfo(info);
+    ASSERT_EQ(3, pkt.relay_info_.size());
+
+    // Let's get the first interface
+    Iface* iface = IfaceMgr::instance().getIface(1);
+    ASSERT_TRUE(iface);
+
+    // and set source interface data properly. getMACFromIPv6LinkLocal attempts
+    // to use source interface to obtain hardware type
+    pkt.setIface(iface->getName());
+    pkt.setIndex(iface->getIndex());
+
+    // The method should return MAC based on the first relay that was closest
+    HWAddrPtr found = pkt.getMAC(Pkt::HWADDR_SOURCE_IPV6_LINK_LOCAL);
+    ASSERT_TRUE(found);
+
+    // Let's check the info now.
+    stringstream tmp;
+    tmp << "hwtype=" << iface->getHWType() << " 00:00:00:00:00:01";
+    EXPECT_EQ(tmp.str(), found->toText(true));
+}
+
 }