Commit d92e7686 authored by Tomek Mrugalski's avatar Tomek Mrugalski 🛰

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

parents 8b34dcb7 16b7fb6d
......@@ -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)));
}
};
};
......@@ -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_;
......
......@@ -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_;
......
......@@ -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
......@@ -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.
......
......@@ -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));
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment