Browse Source

[master] Merge branch 'trac3549' (extract MAC from link-local address)

Tomek Mrugalski 10 years ago
parent
commit
d92e76860e
6 changed files with 327 additions and 22 deletions
  1. 72 0
      src/lib/dhcp/pkt.cc
  2. 38 1
      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. 175 19
      src/lib/dhcp/tests/pkt6_unittest.cc

+ 72 - 0
src/lib/dhcp/pkt.cc

@@ -14,6 +14,8 @@
 
 #include <utility>
 #include <dhcp/pkt.h>
+#include <dhcp/iface_mgr.h>
+#include <vector>
 
 namespace isc {
 namespace dhcp {
@@ -125,6 +127,8 @@ Pkt::setHWAddrMember(const uint8_t htype, const uint8_t,
 HWAddrPtr
 Pkt::getMAC(uint32_t hw_addr_src) {
     HWAddrPtr mac;
+
+    // Method 1: from raw sockets.
     if (hw_addr_src & HWADDR_SOURCE_RAW) {
         mac = getRemoteHWAddr();
         if (mac) {
@@ -136,11 +140,79 @@ 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 & HWADDR_SOURCE_IPV6_LINK_LOCAL) {
+        mac = getMACFromSrcLinkLocalAddr();
+        if (mac) {
+            return (mac);
+        } 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());
+        }
+    }
+
+    // Method 4: From client link-layer address option inserted by a relay
+
+    // Method 5: From remote-id option inserted by a relay
+
+    // Method 6: From subscriber-id option inserted by a relay
+
+    // Method 7: From docsis options
+
     /// @todo: add other MAC acquisition methods here
 
     // Ok, none of the methods were suitable. Return NULL.
     return (HWAddrPtr());
 }
 
+HWAddrPtr
+Pkt::getMACFromIPv6(const isc::asiolink::IOAddress& addr) {
+
+    if (!addr.isV6LinkLocal()) {
+        return (HWAddrPtr());
+    }
+
+    std::vector<uint8_t> bin = addr.toBytes();
+
+    // Double check that it's of appropriate size
+    if ((bin.size() != isc::asiolink::V6ADDRESS_LEN) ||
+
+        // Check that it's link-local (starts with fe80).
+        (bin[0] != 0xfe) || (bin[1] != 0x80) ||
+
+        // Check that u bit is set and g is clear. See Section 2.5.1 of RFC2373
+        // for details.
+        ((bin[8] & 3) != 2) ||
+
+        // And that the IID is of EUI-64 type.
+        (bin[11] != 0xff) || (bin[12] != 0xfe)) {
+        return (HWAddrPtr());
+    }
+
+    // Remove 8 most significant bytes
+    bin.erase(bin.begin(), bin.begin() + 8);
+
+    // Ok, we're down to EUI-64 only now: XX:XX:XX:ff:fe:XX:XX:XX
+    bin.erase(bin.begin() + 3, bin.begin() + 5);
+
+    // MAC-48 to EUI-64 involves inverting u bit (see explanation in Section
+    // 2.5.1 of RFC2373). We need to revert that.
+    bin[0] = bin[0] ^ 2;
+
+    // Let's get the interface this packet was received on. We need it to get
+    // hardware type
+    Iface* iface = IfaceMgr::instance().getIface(iface_);
+    uint16_t hwtype = 0; // not specified
+    if (iface) {
+        hwtype = iface->getHWType();
+    }
+
+    return (HWAddrPtr(new HWAddr(bin, hwtype)));
+}
+
+
 };
 };

+ 38 - 1
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
@@ -503,6 +503,43 @@ public:
 
 protected:
 
+    /// @brief Attempts to obtain MAC address from source link-local
+    /// IPv6 address
+    ///
+    /// 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 example, Windows supports
+    /// RFC4941, which randomized IID part of the link-local address.
+    /// If this method fails, it will return NULL.
+    ///
+    /// For direct message, it attempts to use remote_addr_ field. For relayed
+    /// message, it uses peer-addr of the first relay.
+    ///
+    /// @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 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
+    /// @brief 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 outermost 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.

+ 175 - 19
src/lib/dhcp/tests/pkt6_unittest.cc

@@ -21,6 +21,7 @@
 #include <dhcp/option6_ia.h>
 #include <dhcp/option_int.h>
 #include <dhcp/option_int_array.h>
+#include <dhcp/iface_mgr.h>
 #include <dhcp/pkt6.h>
 #include <dhcp/hwaddr.h>
 #include <dhcp/docsis3_option_defs.h>
@@ -86,8 +87,8 @@ public:
         // callback has been actually called.
         executed_ = true;
         // Use default implementation of the unpack algorithm to parse options.
-        return (LibDHCP::unpackOptions6(buf, option_space, options, relay_msg_offset,
-                                        relay_msg_len));
+        return (LibDHCP::unpackOptions6(buf, option_space, options,
+                                        relay_msg_offset, relay_msg_len));
     }
 
     /// A flag which indicates if callback function has been called.
@@ -99,7 +100,8 @@ public:
     Pkt6Test() {
     }
 
-    /// @brief generates an option with given code (and length) and random content
+    /// @brief generates an option with given code (and length) and
+    /// random content
     ///
     /// @param code option code
     /// @param len data length (data will be randomized)
@@ -252,13 +254,13 @@ Pkt6* capture2() {
 
     // string exported from Wireshark
     string hex_string =
-        "0c01200108880db800010000000000000000fe80000000000000020021fffe5c18a900"
-        "09007d0c0000000000000000000000000000000000fe80000000000000020021fffe5c"
-        "18a9001200154953414d3134342065746820312f312f30352f30310025000400000de9"
-        "00090036016b4fe20001000e0001000118b033410000215c18a90003000c00000001ff"
-        "ffffffffffffff00080002000000060006001700f200f30012001c4953414d3134347c"
-        "3239397c697076367c6e743a76703a313a313130002500120000197f0001000118b033"
-        "410000215c18a9";
+        "0c01200108880db800010000000000000000fe80000000000000020021fffe5c"
+        "18a90009007d0c0000000000000000000000000000000000fe80000000000000"
+        "020021fffe5c18a9001200154953414d3134342065746820312f312f30352f30"
+        "310025000400000de900090036016b4fe20001000e0001000118b03341000021"
+        "5c18a90003000c00000001ffffffffffffffff00080002000000060006001700"
+        "f200f30012001c4953414d3134347c3239397c697076367c6e743a76703a313a"
+        "313130002500120000197f0001000118b033410000215c18a9";
 
     std::vector<uint8_t> bin;
 
@@ -319,7 +321,8 @@ TEST_F(Pkt6Test, packUnpack) {
 
 // Checks if the code is able to handle malformed packet
 TEST_F(Pkt6Test, unpackMalformed) {
-    // Get a packet. We're really interested in its on-wire representation only.
+    // Get a packet. We're really interested in its on-wire
+    // representation only.
     scoped_ptr<Pkt6> donor(capture1());
 
     // That's our original content. It should be sane.
@@ -586,8 +589,9 @@ TEST_F(Pkt6Test, relayUnpack) {
     uint32_t vendor_id = custom->readInteger<uint32_t>(0);
     EXPECT_EQ(6527, vendor_id); // 6527 = Panthera Networks
 
-    uint8_t expected_remote_id[] = { 0x00, 0x01, 0x00, 0x01, 0x18, 0xb0, 0x33, 0x41, 0x00,
-                                     0x00, 0x21, 0x5c, 0x18, 0xa9 };
+    uint8_t expected_remote_id[] = { 0x00, 0x01, 0x00, 0x01, 0x18, 0xb0,
+                                     0x33, 0x41, 0x00, 0x00, 0x21, 0x5c,
+                                     0x18, 0xa9 };
     OptionBuffer remote_id = custom->readBinary(1);
     ASSERT_EQ(sizeof(expected_remote_id), remote_id.size());
     ASSERT_EQ(0, memcmp(expected_remote_id, &remote_id[0], remote_id.size()));
@@ -623,8 +627,9 @@ TEST_F(Pkt6Test, relayUnpack) {
 
     ASSERT_TRUE(opt = msg->getOption(D6O_CLIENTID));
     EXPECT_EQ(18, opt->len()); // 14 bytes of data + 4 bytes of header
-    uint8_t expected_client_id[] = { 0x00, 0x01, 0x00, 0x01, 0x18, 0xb0, 0x33, 0x41, 0x00,
-                                     0x00, 0x21, 0x5c, 0x18, 0xa9 };
+    uint8_t expected_client_id[] = { 0x00, 0x01, 0x00, 0x01, 0x18, 0xb0,
+                                     0x33, 0x41, 0x00, 0x00, 0x21, 0x5c,
+                                     0x18, 0xa9 };
     data = opt->getData();
     ASSERT_EQ(data.size(), sizeof(expected_client_id));
     ASSERT_EQ(0, memcmp(&data[0], expected_client_id, data.size()));
@@ -689,7 +694,8 @@ TEST_F(Pkt6Test, relayPack) {
 
     EXPECT_NO_THROW(parent->pack());
 
-    EXPECT_EQ(Pkt6::DHCPV6_PKT_HDR_LEN + 3 * Option::OPTION6_HDR_LEN // ADVERTISE
+    EXPECT_EQ(Pkt6::DHCPV6_PKT_HDR_LEN
+              + 3 * Option::OPTION6_HDR_LEN // ADVERTISE
               + Pkt6::DHCPV6_RELAY_HDR_LEN // Relay header
               + Option::OPTION6_HDR_LEN // Relay-msg
               + optRelay1->len(),
@@ -727,7 +733,8 @@ TEST_F(Pkt6Test, relayPack) {
     EXPECT_EQ(opt->len(), optRelay1->len());
     OptionBuffer data = opt->getData();
     ASSERT_EQ(data.size(), sizeof(relay_opt_data));
-    EXPECT_EQ(0, memcmp(relay_opt_data, relay_opt_data, sizeof(relay_opt_data)));
+    EXPECT_EQ(0,
+              memcmp(relay_opt_data, relay_opt_data, sizeof(relay_opt_data)));
 }
 
 
@@ -756,7 +763,8 @@ TEST_F(Pkt6Test, getAnyRelayOption) {
     OptionPtr relay2_opt1(new Option(Option::V6, 100));
     OptionPtr relay2_opt2(new Option(Option::V6, 101));
     OptionPtr relay2_opt3(new Option(Option::V6, 102));
-    OptionPtr relay2_opt4(new Option(Option::V6, 200)); // the same code as relay1_opt3
+    OptionPtr relay2_opt4(new Option(Option::V6, 200));
+    // the same code as relay1_opt3
     relay2.options_.insert(make_pair(100, relay2_opt1));
     relay2.options_.insert(make_pair(101, relay2_opt2));
     relay2.options_.insert(make_pair(102, relay2_opt3));
@@ -775,7 +783,8 @@ TEST_F(Pkt6Test, getAnyRelayOption) {
     // First check that the getAnyRelayOption does not confuse client options
     // and relay options
     // 300 is a client option, present in the message itself.
-    OptionPtr opt = msg->getAnyRelayOption(300, Pkt6::RELAY_SEARCH_FROM_CLIENT);
+    OptionPtr opt =
+        msg->getAnyRelayOption(300, Pkt6::RELAY_SEARCH_FROM_CLIENT);
     EXPECT_FALSE(opt);
     opt = msg->getAnyRelayOption(300, Pkt6::RELAY_SEARCH_FROM_SERVER);
     EXPECT_FALSE(opt);
@@ -877,6 +886,19 @@ TEST_F(Pkt6Test, getMAC) {
     EXPECT_FALSE(pkt.getMAC(Pkt::HWADDR_SOURCE_ANY));
     EXPECT_FALSE(pkt.getMAC(Pkt::HWADDR_SOURCE_RAW));
 
+    // We haven't specified source IPv6 address, so this method should
+    // fail, too
+    EXPECT_FALSE(pkt.getMAC(Pkt::HWADDR_SOURCE_IPV6_LINK_LOCAL));
+
+    // Let's check if setting IPv6 address improves the situation.
+    IOAddress linklocal_eui64("fe80::204:06ff:fe08:0a0c");
+    pkt.setRemoteAddr(linklocal_eui64);
+    EXPECT_TRUE(pkt.getMAC(Pkt::HWADDR_SOURCE_ANY));
+    EXPECT_TRUE(pkt.getMAC(Pkt::HWADDR_SOURCE_IPV6_LINK_LOCAL));
+    EXPECT_TRUE(pkt.getMAC(Pkt::HWADDR_SOURCE_IPV6_LINK_LOCAL |
+                           Pkt::HWADDR_SOURCE_RAW));
+    pkt.setRemoteAddr(IOAddress("::"));
+
     // Let's invent a MAC
     const uint8_t hw[] = { 2, 4, 6, 8, 10, 12 }; // MAC
     const uint8_t hw_type = 123; // hardware type
@@ -888,10 +910,144 @@ TEST_F(Pkt6Test, getMAC) {
     // Now we should be able to get something
     ASSERT_TRUE(pkt.getMAC(Pkt::HWADDR_SOURCE_ANY));
     ASSERT_TRUE(pkt.getMAC(Pkt::HWADDR_SOURCE_RAW));
+    EXPECT_TRUE(pkt.getMAC(Pkt::HWADDR_SOURCE_IPV6_LINK_LOCAL |
+                           Pkt::HWADDR_SOURCE_RAW));
 
     // Check that the returned MAC is indeed the expected one
     ASSERT_TRUE(*dummy_hwaddr == *pkt.getMAC(Pkt::HWADDR_SOURCE_ANY));
     ASSERT_TRUE(*dummy_hwaddr == *pkt.getMAC(Pkt::HWADDR_SOURCE_RAW));
 }
 
+// Test checks whether getMACFromIPv6LinkLocal() returns the hardware (MAC)
+// address properly (for direct message).
+TEST_F(Pkt6Test, getMACFromIPv6LinkLocal_direct) {
+    Pkt6 pkt(DHCPV6_ADVERTISE, 1234);
+
+    // 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());
+
+    // Note that u and g bits (the least significant ones of the most
+    // significant byte) have special meaning and must not be set in MAC.
+    // u bit is always set in EUI-64. g is always cleared.
+    IOAddress global("2001:db8::204:06ff:fe08:0a:0c");
+    IOAddress linklocal_eui64("fe80::f204:06ff:fe08:0a0c");
+    IOAddress linklocal_noneui64("fe80::f204:0608:0a0c:0e10");
+
+    // If received from a global address, this method should fail
+    pkt.setRemoteAddr(global);
+    EXPECT_FALSE(pkt.getMAC(Pkt::HWADDR_SOURCE_IPV6_LINK_LOCAL));
+
+    // If received from link-local that is EUI-64 based, it should succeed
+    pkt.setRemoteAddr(linklocal_eui64);
+    HWAddrPtr found = pkt.getMAC(Pkt::HWADDR_SOURCE_IPV6_LINK_LOCAL);
+    ASSERT_TRUE(found);
+
+    stringstream tmp;
+    tmp << "hwtype=" << (int)iface->getHWType() << " f0:04:06:08:0a:0c";
+    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::f204:0608:0a0c:0e10"); // no fffe
+    IOAddress linklocal_eui64("fe80::f204: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 should 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() << " f0: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::200:ff:fe00:1"); // valid EUI-64
+    IOAddress linklocal2("fe80::200:ff:fe00:2"); // valid EUI-64
+    IOAddress linklocal3("fe80::200: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));
+}
+
 }