diff --git a/doc/Makefile.am b/doc/Makefile.am index 8ad633cb4c..173f563648 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -9,6 +9,7 @@ nobase_dist_doc_DATA += examples/kea4/reservations.json nobase_dist_doc_DATA += examples/kea6/simple.json nobase_dist_doc_DATA += examples/kea6/several-subnets.json nobase_dist_doc_DATA += examples/kea6/multiple-options.json +nobase_dist_doc_DATA += examples/kea6/advanced.json nobase_dist_doc_DATA += examples/ddns/sample1.json nobase_dist_doc_DATA += examples/ddns/template.json diff --git a/doc/examples/kea4/multiple-options.json b/doc/examples/kea4/multiple-options.json index 9b125dc1b9..4130e4c5e6 100644 --- a/doc/examples/kea4/multiple-options.json +++ b/doc/examples/kea4/multiple-options.json @@ -4,8 +4,8 @@ { "Dhcp4": { -# Kea is told to listen on eth0 interface only. - "interfaces": [ "eth0" ], +# Kea is told to listen on ethX interface only. + "interfaces": [ "ethX" ], # We need to specify lease type. As of May 2014, three backends are supported: # memfile, mysql and pgsql. We'll just use memfile, because it doesn't require @@ -36,7 +36,7 @@ { "pools": [ { "pool": "192.0.2.10 - 192.0.2.200" } ], "subnet": "192.0.2.0/24", - "interface": "eth0", + "interface": "ethX", "option-data": [ { "name": "domain-name-servers", diff --git a/doc/examples/kea4/several-subnets.json b/doc/examples/kea4/several-subnets.json index aff824b640..b8576a9f23 100644 --- a/doc/examples/kea4/several-subnets.json +++ b/doc/examples/kea4/several-subnets.json @@ -5,8 +5,8 @@ { "Dhcp4": { -# Kea is told to listen on eth0 interface only. - "interfaces": [ "eth0" ], +# Kea is told to listen on ethX interface only. + "interfaces": [ "ethX" ], # We need to specify lease type. As of May 2014, three backends are supported: # memfile, mysql and pgsql. We'll just use memfile, because it doesn't require diff --git a/doc/examples/kea4/single-subnet.json b/doc/examples/kea4/single-subnet.json index 0c2e10ca07..28368939d1 100644 --- a/doc/examples/kea4/single-subnet.json +++ b/doc/examples/kea4/single-subnet.json @@ -5,8 +5,8 @@ { "Dhcp4": { -# Kea is told to listen on eth0 interface only. - "interfaces": [ "eth0" ], +# Kea is told to listen on ethX interface only. + "interfaces": [ "ethX" ], # We need to specify lease type. As of May 2014, three backends are supported: # memfile, mysql and pgsql. We'll just use memfile, because it doesn't require @@ -35,8 +35,8 @@ { "pools": [ { "pool": "192.0.2.1 - 192.0.2.200" } ], "subnet": "192.0.2.0/24", - "interface": "eth0" - } + "interface": "ethX" + } ] }, diff --git a/doc/examples/kea6/advanced.json b/doc/examples/kea6/advanced.json new file mode 100644 index 0000000000..1923064657 --- /dev/null +++ b/doc/examples/kea6/advanced.json @@ -0,0 +1,79 @@ +# This is an example configuration file for DHCPv6 server in Kea. +# It attempts to showcase some of the more advanced features. +# Topology wise, it's a basic scenario with one IPv6 subnet configured. +# It is assumed that one subnet (2001:db8:1::/64) is available directly +# over ethX interface. +# +# The following features are currently showcased here: +# 1. Configuration of MAC/hardware address sources in DHCPv6 + +{ "Dhcp6": + +{ +# Kea is told to listen on ethX network interface only. + "interfaces": [ "ethX" ], + +# We need to specify lease type. As of May 2014, three backends are supported: +# memfile, mysql and pgsql. We'll just use memfile, because it doesn't require +# any prior set up. + "lease-database": { + "type": "memfile" + }, + +# Kea 0.9.1 introduced MAC/hardware addresses support in DHCPv6. There is +# no single reliable method of getting MAC address information in DHCPv6. +# Kea supports several methods. Depending on your network set up, some +# methods may be more preferable than others, hence the configuration +# parameter. 'mac-sources' is a list of methods. Allowed parameters are: +# any, raw, duid, ipv6-link-local, client-link-addr-option, rfc6939 (which +# is an alias for client-link-addr-option), remote-id, rfc4649 (which is an +# alias for remote-id, subscriber-id, rfc4580 (which is an alias for +# subscriber-id) and docsis. +# +# Note that the order matters. Methods are attempted one by one in the order +# specified until hardware address is obtained. If you don't care which method +# is used, using 'any' is marginally faster than enumerating them all. +# +# If mac-sources are not specified, a default value of 'any' is used. + "mac-sources": [ "client-link-addr-option", "duid", "ipv6-link-local" ], + +# Addresses will be assigned with preferred and valid lifetimes +# being 3000 and 4000, respectively. Client is told to start +# renewing after 1000 seconds. If the server does not repond +# after 2000 seconds since the lease was granted, client is supposed +# to start REBIND procedure (emergency renewal that allows switching +# to a different server). + "preferred-lifetime": 3000, + "valid-lifetime": 4000, + "renew-timer": 1000, + "rebind-timer": 2000, + +# The following list defines subnets. Each subnet consists of at +# least subnet and pool entries. + "subnet6": [ + { + "pools": [ { "pool": "2001:db8:1::/80" } ], + "subnet": "2001:db8:1::/64", + "interface": "ethX" + } + ] +}, + +# The following configures logging. Kea will log all debug messages +# to /var/log/kea-debug.log file. +"Logging": { + "loggers": [ + { + "name": "kea-dhcp6", + "output_options": [ + { + "output": "/var/log/kea-debug.log" + } + ], + "debuglevel": 99, + "severity": "DEBUG" + } + ] +} + +} diff --git a/doc/examples/kea6/multiple-options.json b/doc/examples/kea6/multiple-options.json index 682b3b5102..e30cf9d303 100644 --- a/doc/examples/kea6/multiple-options.json +++ b/doc/examples/kea6/multiple-options.json @@ -4,8 +4,8 @@ { "Dhcp6": { -# Kea is told to listen on eth0 interface only. - "interfaces": [ "eth0" ], +# Kea is told to listen on ethX interface only. + "interfaces": [ "ethX" ], # We need to specify lease type. As of May 2014, three backends are supported: # memfile, mysql and pgsql. We'll just use memfile, because it doesn't require @@ -32,7 +32,7 @@ { "pools": [ { "pool": "2001:db8:1::/80" } ], "subnet": "2001:db8:1::/64", - "interface": "eth0", + "interface": "ethX", "option-data": [ { "name": "dns-servers", diff --git a/doc/examples/kea6/several-subnets.json b/doc/examples/kea6/several-subnets.json index f138c059ea..d9744053fe 100644 --- a/doc/examples/kea6/several-subnets.json +++ b/doc/examples/kea6/several-subnets.json @@ -5,8 +5,8 @@ { "Dhcp6": { -# Kea is told to listen on eth0 interface only. - "interfaces": [ "eth0" ], +# Kea is told to listen on ethX interface only. + "interfaces": [ "ethX" ], # We need to specify lease type. As of May 2014, three backends are supported: # memfile, mysql and pgsql. We'll just use memfile, because it doesn't require diff --git a/doc/examples/kea6/simple.json b/doc/examples/kea6/simple.json index e266d0ba10..cffec39f02 100644 --- a/doc/examples/kea6/simple.json +++ b/doc/examples/kea6/simple.json @@ -1,13 +1,13 @@ # This is an example configuration file for DHCPv6 server in Kea. -# It's a basic scenario with four IPv6 subnets configured. It is +# It's a basic scenario with one IPv6 subnet configured. It is # assumed that one subnet (2001:db8:1::/64 is available directly -# over eth0 interface. +# over ethX interface. { "Dhcp6": { -# Kea is told to listen on eth0 interface only. - "interfaces": [ "eth0" ], +# Kea is told to listen on ethX interface only. + "interfaces": [ "ethX" ], # We need to specify lease type. As of May 2014, three backends are supported: # memfile, mysql and pgsql. We'll just use memfile, because it doesn't require @@ -33,7 +33,7 @@ { "pools": [ { "pool": "2001:db8:1::/80" } ], "subnet": "2001:db8:1::/64", - "interface": "eth0" + "interface": "ethX" } ] }, diff --git a/doc/guide/dhcp6-srv.xml b/doc/guide/dhcp6-srv.xml index 8abb16c373..e1974a06c2 100644 --- a/doc/guide/dhcp6-srv.xml +++ b/doc/guide/dhcp6-srv.xml @@ -1838,6 +1838,117 @@ should include options from the isc option space: +
+ MAC/Hardware addresses in DHCPv6 + MAC/hardware addesses are available in DHCPv4 messages + from the clients and administrators + frequently use that information to perform certain tasks, like per host + configuration, address reserveration for specific MAC addresses and other. + Unfortunately, DHCPv6 protocol does not provide any completely reliable way + to retrieve that information. To mitigate that issue a number of mechanisms + have been implemented in Kea that attempt to gather that information. Each + of those mechanisms works in certain cases, but may fail in other cases. + Whether the mechanism works or not in the particular deployment is + somewhat dependent on the network topology and the technologies used. + + Kea allows for configuration which of the supported methods should be + used and in which order. This configuration may be considered a fine tuning + of the DHCP deployment. In a typical deployment the default + value of "any" is sufficient and there is no + need to select specific methods. Changing the value of this parameter + is the most useful in cases when an administrator wants to disable + certain method, e.g. if the administrator trusts the network infrastructure + more than the information provided by the clients themselves, the + administrator may prefer information provided by the relays over that + provided by the clients. The format of this parameter is as follows: + +"Dhcp6": { + "mac-sources": [ "method1", "method2", "method3", ... ], + + "subnet6": [ ... ], + + ... +} + + + When not specified, a special value of any is used, which + instructs the server to attempt to use all the methods in sequence and use + value returned by the first one that succeeds. + + Supported methods are: + + + any - not an actual method, just a keyword that + instructs Kea to try all other methods and use the first one that succeeds. + This is the default operation if no mac-sources are defined. + + + + raw In principle, a DHCPv6 server could use raw + sockets to receive incoming traffic and extract MAC/hardware address + information. This is currently not implemented and this value has no effect. + + + + duid - DHCPv6 uses DUID identifiers instead of + MAC addresses. There are currently four DUID types defined, with two of them + (DUID-LLT, which is the default one and DUID-LL) convey MAC address information. + Although RFC3315 forbids it, it is possible to parse those DUIDs and extract + necessary information from them. This method is not completely reliable, as + clients may use other DUID types, namely DUID-EN or DUID-UUID. + + + + ipv6-link-local - Another possible aquisition + method comes from the source IPv6 address. In typical usage, clients are + sending their packets from IPv6 link-local addresses. There's a good chance + that those addresses are based on EUI-64, which contains MAC address. This + method is not completely reliable, as clients may use other link-local address + types. In particular, privacy extensions, defined in RFC4941, do not use + MAC addresses. + + + + client-link-addr-option - One extension defined + to alleviate missing MAC issues is client link-layer address option, defined + in RFC 6939. This is + an option that is inserted by a relay and contains information about client's + MAC address. This method requires a relay agent that supports the option and + is configured to insert it. This method is useless for directly connected + clients. This parameter can also be specified as rfc6939, + which is an alias for client-link-addr-option. + + + + remote-id RFC 4649 + defines remote-id option that is inserted by a relay agent. Depending + on the relay agent configuration, the inserted option may convey client's + MAC address information. This parameter can also be specified as + rfc4649, which is an alias for remote-id. + + + + subscriber-id - Another option + that is somewhat similar to the previous one is subscriber-id, + defined in RFC + 4580. It is, too, inserted by a relay agent that is + configured to insert it. This parameter can also be specified + as rfc4580, which is an alias for + subscriber-id. This method is currently not + implemented. + + + + docsis - Yet another possible source of MAC + address information are DOCSIS options inserted by a CMTS that acts + as a DHCPv6 relay agent in cable networks. This method is + currently not implemented. + + + + +
Supported DHCPv6 Standards diff --git a/src/bin/dhcp6/dhcp6.spec b/src/bin/dhcp6/dhcp6.spec index f0fe861b90..ff2b308b42 100644 --- a/src/bin/dhcp6/dhcp6.spec +++ b/src/bin/dhcp6/dhcp6.spec @@ -422,6 +422,21 @@ } ] } }, + + { "item_name": "mac-sources", + "item_type": "list", + "item_optional": true, + "item_default": [ "any" ], + "item_description": "Lists MAC/hardware address acquisition sources", + "list_item_spec": + { + "item_name": "source", + "item_type": "string", + "item_optional": true, + "item_default": "any" + } + } , + { "item_name": "dhcp-ddns", "item_type": "map", "item_optional": false, diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc index 8b5336cde5..fc44347866 100644 --- a/src/bin/dhcp6/dhcp6_srv.cc +++ b/src/bin/dhcp6/dhcp6_srv.cc @@ -1184,6 +1184,21 @@ Dhcpv6Srv::createRemovalNameChangeRequest(const Lease6Ptr& lease) { CfgMgr::instance().getD2ClientMgr().sendRequest(ncr); } +HWAddrPtr +Dhcpv6Srv::getMAC(const Pkt6Ptr& pkt) { + CfgMACSources mac_sources = CfgMgr::instance().getCurrentCfg()-> + getMACSources().get(); + HWAddrPtr hwaddr; + for (CfgMACSources::const_iterator it = mac_sources.begin(); + it != mac_sources.end(); ++it) { + hwaddr = pkt->getMAC(*it); + if (hwaddr) { + return (hwaddr); + } + } + return (hwaddr); +} + OptionPtr Dhcpv6Srv::assignIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid, const Pkt6Ptr& query, const Pkt6Ptr& answer, @@ -1253,10 +1268,9 @@ Dhcpv6Srv::assignIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid, hostname = fqdn->getDomainName(); } - // Attempt to get MAC address using any of available mechanisms. - // It's ok if there response is NULL. Hardware address is optional in Lease6 - /// @todo: Make this configurable after trac 3554 is done. - HWAddrPtr hwaddr = query->getMAC(Pkt::HWADDR_SOURCE_ANY); + // Attempt to get MAC address using configured mechanisms. + // It's ok if there response is NULL. Hardware address is optional in Lease6. + HWAddrPtr hwaddr = getMAC(query); // Use allocation engine to pick a lease for this client. Allocation engine // will try to honour the hint, but it is just a hint - some other address @@ -1376,8 +1390,7 @@ Dhcpv6Srv::assignIA_PD(const Subnet6Ptr& subnet, const DuidPtr& duid, // Attempt to get MAC address using any of available mechanisms. // It's ok if there response is NULL. Hardware address is optional in Lease6 - /// @todo: Make this configurable after trac 3554 is done. - HWAddrPtr hwaddr = query->getMAC(Pkt::HWADDR_SOURCE_ANY); + HWAddrPtr hwaddr = getMAC(query); LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_PROCESS_IA_PD_REQUEST) .arg(duid ? duid->toText() : "(no-duid)").arg(ia->getIAID()) @@ -1569,6 +1582,9 @@ Dhcpv6Srv::extendIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid, lease->fqdn_fwd_ = do_fwd; lease->fqdn_rev_ = do_rev; + /// @todo: check if hardware address has changed since last update. + /// And modify lease->hwaddr_ if it did. + ia_rsp->setT1(subnet->getT1()); ia_rsp->setT2(subnet->getT2()); diff --git a/src/bin/dhcp6/dhcp6_srv.h b/src/bin/dhcp6/dhcp6_srv.h index af355c0d7a..ff3ff69581 100644 --- a/src/bin/dhcp6/dhcp6_srv.h +++ b/src/bin/dhcp6/dhcp6_srv.h @@ -596,6 +596,14 @@ protected: /// @param pkt packet to be classified void classifyPacket(const Pkt6Ptr& pkt); + /// @brief Attempts to get a MAC/hardware address using configred sources + /// + /// Tries to extract MAC/hardware address information from the packet + /// using MAC sources configured in 'mac-sources' configuration parameter. + /// + /// @param pkt will try to exact MAC address from this packet + /// @return HWaddr pointer (or NULL if configured methods fail) + static HWAddrPtr getMAC(const Pkt6Ptr& pkt); /// @brief this is a prefix added to the contend of vendor-class option /// diff --git a/src/bin/dhcp6/json_config_parser.cc b/src/bin/dhcp6/json_config_parser.cc index eaf278a199..1ea45bacd9 100644 --- a/src/bin/dhcp6/json_config_parser.cc +++ b/src/bin/dhcp6/json_config_parser.cc @@ -613,6 +613,9 @@ namespace dhcp { parser = new HooksLibrariesParser(config_id); } else if (config_id.compare("dhcp-ddns") == 0) { parser = new D2ClientConfigParser(config_id); + } else if (config_id.compare("mac-sources") == 0) { + parser = new MACSourcesListConfigParser(config_id, + globalContext()); } else { isc_throw(DhcpConfigError, "unsupported global configuration parameter: " diff --git a/src/bin/dhcp6/json_config_parser.h b/src/bin/dhcp6/json_config_parser.h index d3c1b27f3f..3970d68e94 100644 --- a/src/bin/dhcp6/json_config_parser.h +++ b/src/bin/dhcp6/json_config_parser.h @@ -15,9 +15,6 @@ #ifndef DHCP6_CONFIG_PARSER_H #define DHCP6_CONFIG_PARSER_H -/// @todo: This header file and its .cc counterpart are very similar between -/// DHCPv4 and DHCPv6. They should be merged. See ticket #2355. - #include #include #include diff --git a/src/bin/dhcp6/tests/config_parser_unittest.cc b/src/bin/dhcp6/tests/config_parser_unittest.cc index c3bf74ece6..8682d0b57c 100644 --- a/src/bin/dhcp6/tests/config_parser_unittest.cc +++ b/src/bin/dhcp6/tests/config_parser_unittest.cc @@ -3627,4 +3627,79 @@ TEST_F(Dhcp6ParserTest, reservationBogus) { } +/// The goal of this test is to verify that configuration can include +/// MAC/Hardware sources. This test also checks if the aliases are +/// handled properly (rfc6939 = client-addr-relay, rfc4649 = remote-id, +/// rfc4580 = subscriber-id). +TEST_F(Dhcp6ParserTest, macSources) { + + ConstElementPtr status; + + EXPECT_NO_THROW(status = configureDhcp6Server(srv_, + Element::fromJSON("{ \"interfaces\": [ \"*\" ]," + "\"mac-sources\": [ \"rfc6939\", \"rfc4649\", \"rfc4580\"," + "\"client-link-addr-option\", \"remote-id\", \"subscriber-id\"]," + "\"preferred-lifetime\": 3000," + "\"rebind-timer\": 2000, " + "\"renew-timer\": 1000, " + "\"subnet6\": [ ], " + "\"valid-lifetime\": 4000 }"))); + + // returned value should be 0 (success) + checkResult(status, 0); + + CfgMACSources mac_sources = CfgMgr::instance().getStagingCfg()->getMACSources().get(); + ASSERT_EQ(6, mac_sources.size()); + // Let's check the aliases. They should be recognized to their base methods. + EXPECT_EQ(HWAddr::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION, mac_sources[0]); + EXPECT_EQ(HWAddr::HWADDR_SOURCE_REMOTE_ID, mac_sources[1]); + EXPECT_EQ(HWAddr::HWADDR_SOURCE_SUBSCRIBER_ID, mac_sources[2]); + + // Let's check if the actual methods are recognized properly. + EXPECT_EQ(HWAddr::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION, mac_sources[3]); + EXPECT_EQ(HWAddr::HWADDR_SOURCE_REMOTE_ID, mac_sources[4]); + EXPECT_EQ(HWAddr::HWADDR_SOURCE_SUBSCRIBER_ID, mac_sources[5]); +} + +/// The goal of this test is to verify that MAC sources configuration can be +/// empty. +TEST_F(Dhcp6ParserTest, macSourcesEmpty) { + + ConstElementPtr status; + + EXPECT_NO_THROW(status = configureDhcp6Server(srv_, + Element::fromJSON("{ \"interfaces\": [ \"*\" ]," + "\"mac-sources\": [ ]," + "\"preferred-lifetime\": 3000," + "\"rebind-timer\": 2000, " + "\"renew-timer\": 1000, " + "\"subnet6\": [ ], " + "\"valid-lifetime\": 4000 }"))); + + // returned value should be 0 (success) + checkResult(status, 0); + + CfgMACSources mac_sources = CfgMgr::instance().getStagingCfg()->getMACSources().get(); + EXPECT_EQ(0, mac_sources.size()); +} + +/// The goal of this test is to verify that MAC sources configuration can +/// only use valid parameters. +TEST_F(Dhcp6ParserTest, macSourcesBogus) { + + ConstElementPtr status; + + EXPECT_NO_THROW(status = configureDhcp6Server(srv_, + Element::fromJSON("{ \"interfaces\": [ \"*\" ]," + "\"mac-sources\": [ \"from-wire\" ]," + "\"preferred-lifetime\": 3000," + "\"rebind-timer\": 2000, " + "\"renew-timer\": 1000, " + "\"subnet6\": [ ], " + "\"valid-lifetime\": 4000 }"))); + + // returned value should be 1 (failure) + checkResult(status, 1); +} + }; diff --git a/src/bin/dhcp6/tests/dhcp6_client.cc b/src/bin/dhcp6/tests/dhcp6_client.cc index b4feb3c199..4e5a9108a8 100644 --- a/src/bin/dhcp6/tests/dhcp6_client.cc +++ b/src/bin/dhcp6/tests/dhcp6_client.cc @@ -63,7 +63,7 @@ Dhcp6Client::applyRcvdConfiguration(const Pkt6Ptr& reply) { Opts opts = reply->options_; // Let's try to get a MAC - HWAddrPtr hwaddr = reply->getMAC(Pkt::HWADDR_SOURCE_ANY); + HWAddrPtr hwaddr = reply->getMAC(HWAddr::HWADDR_SOURCE_ANY); // Set the global status code to default: success and not received. config_.resetGlobalStatusCode(); diff --git a/src/lib/dhcp/hwaddr.cc b/src/lib/dhcp/hwaddr.cc index 6be98a6770..5d76ec723d 100644 --- a/src/lib/dhcp/hwaddr.cc +++ b/src/lib/dhcp/hwaddr.cc @@ -27,6 +27,16 @@ namespace isc { namespace dhcp { +const uint32_t HWAddr::HWADDR_SOURCE_ANY = 0xffffffff; +const uint32_t HWAddr::HWADDR_SOURCE_UNKNOWN = 0x00000000; +const uint32_t HWAddr::HWADDR_SOURCE_RAW = 0x00000001; +const uint32_t HWAddr::HWADDR_SOURCE_DUID = 0x00000002; +const uint32_t HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL = 0x00000004; +const uint32_t HWAddr::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION = 0x00000008; +const uint32_t HWAddr::HWADDR_SOURCE_REMOTE_ID = 0x00000010; +const uint32_t HWAddr::HWADDR_SOURCE_SUBSCRIBER_ID = 0x00000020; +const uint32_t HWAddr::HWADDR_SOURCE_DOCSIS = 0x00000040; + HWAddr::HWAddr() :htype_(HTYPE_ETHER), source_(0) { } diff --git a/src/lib/dhcp/hwaddr.h b/src/lib/dhcp/hwaddr.h index 0c7f21675a..3aa2046a1c 100644 --- a/src/lib/dhcp/hwaddr.h +++ b/src/lib/dhcp/hwaddr.h @@ -34,6 +34,53 @@ public: /// @brief Maximum size of a hardware address. static const size_t MAX_HWADDR_LEN = 20; + /// @defgroup hw_sources Specifies where a given MAC/hardware address was + /// obtained. + /// + /// @brief The list covers all possible MAC/hw address sources. + /// + /// @{ + + /// Not really a type, only used in getMAC() calls. + static const uint32_t HWADDR_SOURCE_ANY; + + /// Used when actual origin is not known, e.g. when reading from a + /// lease database that didn't store that information. + static const uint32_t HWADDR_SOURCE_UNKNOWN; + + /// Obtained first hand from raw socket (100% reliable). + static const uint32_t HWADDR_SOURCE_RAW; + + /// Extracted from DUID-LL or DUID-LLT (not 100% reliable as the client + /// can send fake DUID). + static const uint32_t HWADDR_SOURCE_DUID; + + /// 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; + + /// 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 + /// his request relayed, so the legitimate relay will think it's a second + /// relay. + static const uint32_t HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION; + + /// A relay can insert remote-id. In some deployments it contains a MAC + /// address (RFC4649). + static const uint32_t HWADDR_SOURCE_REMOTE_ID; + + /// A relay can insert a subscriber-id option. In some deployments it + /// contains a MAC address (RFC4580). + static const uint32_t HWADDR_SOURCE_SUBSCRIBER_ID; + + /// A CMTS (acting as DHCP relay agent) that supports DOCSIS standard + /// can insert DOCSIS options that contain client's MAC address. + /// Client in this context would be a cable modem. + static const uint32_t HWADDR_SOURCE_DOCSIS; + + /// @} + /// @brief default constructor HWAddr(); diff --git a/src/lib/dhcp/pkt.cc b/src/lib/dhcp/pkt.cc index 6bb0c8e386..72e0f86742 100644 --- a/src/lib/dhcp/pkt.cc +++ b/src/lib/dhcp/pkt.cc @@ -15,6 +15,7 @@ #include #include #include +#include #include namespace isc { @@ -129,11 +130,11 @@ Pkt::getMAC(uint32_t hw_addr_src) { HWAddrPtr mac; // Method 1: from raw sockets. - if (hw_addr_src & HWADDR_SOURCE_RAW) { + if (hw_addr_src & HWAddr::HWADDR_SOURCE_RAW) { mac = getRemoteHWAddr(); if (mac) { return (mac); - } else if (hw_addr_src == HWADDR_SOURCE_RAW) { + } else if (hw_addr_src == HWAddr::HWADDR_SOURCE_RAW) { // If we're interested only in RAW sockets as source of that info, // there's no point in trying other options. return (HWAddrPtr()); @@ -143,11 +144,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 & HWADDR_SOURCE_IPV6_LINK_LOCAL) { + if (hw_addr_src & HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL) { mac = getMACFromSrcLinkLocalAddr(); if (mac) { return (mac); - } else if (hw_addr_src == HWADDR_SOURCE_IPV6_LINK_LOCAL) { + } else if (hw_addr_src == HWAddr::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()); @@ -155,11 +156,11 @@ Pkt::getMAC(uint32_t hw_addr_src) { } // Method 4: From client link-layer address option inserted by a relay - if (hw_addr_src & HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION) { + if (hw_addr_src & HWAddr::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION) { mac = getMACFromIPv6RelayOpt(); if (mac) { return (mac); - } else if (hw_addr_src == HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION) { + } else if (hw_addr_src == HWAddr::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION) { // If we're interested only in RFC6939 link layer address as source // of that info, there's no point in trying other options. return (HWAddrPtr()); @@ -223,6 +224,5 @@ Pkt::getMACFromIPv6(const isc::asiolink::IOAddress& addr) { return (HWAddrPtr(new HWAddr(bin, hwtype))); } - }; }; diff --git a/src/lib/dhcp/pkt.h b/src/lib/dhcp/pkt.h index 21626b0d35..2b5e73f4bf 100644 --- a/src/lib/dhcp/pkt.h +++ b/src/lib/dhcp/pkt.h @@ -37,58 +37,6 @@ namespace dhcp { /// @note This is abstract class. Please instantiate derived classes /// such as @c Pkt4 or @c Pkt6. class Pkt { -public: - - /// @defgroup hw_sources Specifies where a given MAC/hardware address was - /// obtained. - /// - /// @brief The list covers all possible MAC/hw address sources. - /// - /// @note The uncommented ones are currently supported. When you implement - /// a new method, please uncomment appropriate line here. - /// - /// @{ - - /// Not really a type, only used in getMAC() calls. - static const uint32_t HWADDR_SOURCE_ANY = 0xffff; - - /// Used when actual origin is not known, e.g. when reading from a - /// lease database that didn't store that information. - static const uint32_t HWADDR_SOURCE_UNKNOWN = 0x0000; - - /// Obtained first hand from raw socket (100% reliable). - static const uint32_t HWADDR_SOURCE_RAW = 0x0001; - - /// Extracted from DUID-LL or DUID-LLT (not 100% reliable as the client - /// can send fake DUID). - //static const uint32_t HWADDR_SOURCE_DUID = 0x0002; - - /// 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; - - /// 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 - /// his request relayed, so the legitimate relay will think it's a second - /// relay. - static const uint32_t HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION = 0x0008; - - /// A relay can insert remote-id. In some deployments it contains a MAC - /// address (RFC4649). - //static const uint32_t HWADDR_SOURCE_REMOTE_ID = 0x0010; - - /// A relay can insert a subscriber-id option. In some deployments it - /// contains a MAC address (RFC4580). - //static const uint32_t HWADDR_SOURCE_SUBSCRIBER_ID = 0x0020; - - /// A CMTS (acting as DHCP relay agent) that supports DOCSIS standard - /// can insert DOCSIS options that contain client's MAC address. - /// Client in this context would be a cable modem. - //static const uint32_t HWADDR_SOURCE_DOCSIS_OPTIONS = 0x0040; - - /// @} - protected: /// @brief Constructor. diff --git a/src/lib/dhcp/tests/pkt4_unittest.cc b/src/lib/dhcp/tests/pkt4_unittest.cc index ae39d8906b..1ebf77125a 100644 --- a/src/lib/dhcp/tests/pkt4_unittest.cc +++ b/src/lib/dhcp/tests/pkt4_unittest.cc @@ -857,8 +857,8 @@ TEST_F(Pkt4Test, getMAC) { Pkt4 pkt(DHCPOFFER, 1234); // DHCPv4 packet by default doens't have MAC address specified. - EXPECT_FALSE(pkt.getMAC(Pkt::HWADDR_SOURCE_ANY)); - EXPECT_FALSE(pkt.getMAC(Pkt::HWADDR_SOURCE_RAW)); + EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_ANY)); + EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_RAW)); // Let's invent a MAC const uint8_t hw[] = { 2, 4, 6, 8, 10, 12 }; // MAC @@ -869,12 +869,12 @@ TEST_F(Pkt4Test, getMAC) { pkt.setRemoteHWAddr(dummy_hwaddr); // Now we should be able to get something - ASSERT_TRUE(pkt.getMAC(Pkt::HWADDR_SOURCE_ANY)); - ASSERT_TRUE(pkt.getMAC(Pkt::HWADDR_SOURCE_RAW)); + ASSERT_TRUE(pkt.getMAC(HWAddr::HWADDR_SOURCE_ANY)); + ASSERT_TRUE(pkt.getMAC(HWAddr::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)); + ASSERT_TRUE(*dummy_hwaddr == *pkt.getMAC(HWAddr::HWADDR_SOURCE_ANY)); + ASSERT_TRUE(*dummy_hwaddr == *pkt.getMAC(HWAddr::HWADDR_SOURCE_RAW)); } } // end of anonymous namespace diff --git a/src/lib/dhcp/tests/pkt6_unittest.cc b/src/lib/dhcp/tests/pkt6_unittest.cc index a2c3615184..a015c04f3b 100644 --- a/src/lib/dhcp/tests/pkt6_unittest.cc +++ b/src/lib/dhcp/tests/pkt6_unittest.cc @@ -883,20 +883,20 @@ TEST_F(Pkt6Test, getMAC) { Pkt6 pkt(DHCPV6_ADVERTISE, 1234); // DHCPv6 packet by default doens't have MAC address specified. - EXPECT_FALSE(pkt.getMAC(Pkt::HWADDR_SOURCE_ANY)); - EXPECT_FALSE(pkt.getMAC(Pkt::HWADDR_SOURCE_RAW)); + EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_ANY)); + EXPECT_FALSE(pkt.getMAC(HWAddr::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)); + EXPECT_FALSE(pkt.getMAC(HWAddr::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)); + EXPECT_TRUE(pkt.getMAC(HWAddr::HWADDR_SOURCE_ANY)); + EXPECT_TRUE(pkt.getMAC(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL)); + EXPECT_TRUE(pkt.getMAC(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL | + HWAddr::HWADDR_SOURCE_RAW)); pkt.setRemoteAddr(IOAddress("::")); // Let's invent a MAC @@ -908,14 +908,14 @@ TEST_F(Pkt6Test, getMAC) { pkt.setRemoteHWAddr(dummy_hwaddr); // 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)); + ASSERT_TRUE(pkt.getMAC(HWAddr::HWADDR_SOURCE_ANY)); + ASSERT_TRUE(pkt.getMAC(HWAddr::HWADDR_SOURCE_RAW)); + EXPECT_TRUE(pkt.getMAC(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL | + HWAddr::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)); + ASSERT_TRUE(*dummy_hwaddr == *pkt.getMAC(HWAddr::HWADDR_SOURCE_ANY)); + ASSERT_TRUE(*dummy_hwaddr == *pkt.getMAC(HWAddr::HWADDR_SOURCE_RAW)); } // Test checks whether getMACFromIPv6LinkLocal() returns the hardware (MAC) @@ -941,11 +941,11 @@ TEST_F(Pkt6Test, getMACFromIPv6LinkLocal_direct) { // If received from a global address, this method should fail pkt.setRemoteAddr(global); - EXPECT_FALSE(pkt.getMAC(Pkt::HWADDR_SOURCE_IPV6_LINK_LOCAL)); + EXPECT_FALSE(pkt.getMAC(HWAddr::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); + HWAddrPtr found = pkt.getMAC(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL); ASSERT_TRUE(found); stringstream tmp; @@ -980,15 +980,15 @@ TEST_F(Pkt6Test, getMACFromIPv6LinkLocal_singleRelay) { // 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)); + EXPECT_FALSE(pkt.getMAC(HWAddr::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)); + EXPECT_FALSE(pkt.getMAC(HWAddr::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); + HWAddrPtr found = pkt.getMAC(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL); ASSERT_TRUE(found); stringstream tmp; @@ -1041,7 +1041,7 @@ TEST_F(Pkt6Test, getMACFromIPv6LinkLocal_multiRelay) { 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); + HWAddrPtr found = pkt.getMAC(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL); ASSERT_TRUE(found); // Let's check the info now. @@ -1058,7 +1058,7 @@ TEST_F(Pkt6Test, getMACFromIPv6RelayOpt_singleRelay) { Pkt6 pkt(DHCPV6_SOLICIT, 1234); // Packets that are not relayed should fail - EXPECT_FALSE(pkt.getMAC(Pkt::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION)); + EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION)); // Now pretend it was relayed by a single relay. Pkt6::RelayInfo info; @@ -1075,7 +1075,7 @@ TEST_F(Pkt6Test, getMACFromIPv6RelayOpt_singleRelay) { pkt.addRelayInfo(info); ASSERT_EQ(1, pkt.relay_info_.size()); - HWAddrPtr found = pkt.getMAC(Pkt::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION); + HWAddrPtr found = pkt.getMAC(HWAddr::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION); ASSERT_TRUE(found); stringstream tmp; @@ -1108,7 +1108,7 @@ TEST_F(Pkt6Test, getMACFromIPv6RelayOpt_multipleRelay) { pkt.addRelayInfo(info2); ASSERT_EQ(2, pkt.relay_info_.size()); - EXPECT_FALSE(pkt.getMAC(Pkt::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION)); + EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION)); // Let's envolve the packet with a third relay (now the closest to the client) // that inserts the correct client_linklayer_addr option. @@ -1123,11 +1123,12 @@ TEST_F(Pkt6Test, getMACFromIPv6RelayOpt_multipleRelay) { ASSERT_EQ(3, pkt.relay_info_.size()); // Now extract the MAC address from the relayed option - HWAddrPtr found = pkt.getMAC(Pkt::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION); + HWAddrPtr found = pkt.getMAC(HWAddr::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION); ASSERT_TRUE(found); stringstream tmp; tmp << "hwtype=1 fa:30:0b:fa:c0:fe"; EXPECT_EQ(tmp.str(), found->toText(true)); } + } diff --git a/src/lib/dhcpsrv/Makefile.am b/src/lib/dhcpsrv/Makefile.am index 062ef8ef57..7e1cb28120 100644 --- a/src/lib/dhcpsrv/Makefile.am +++ b/src/lib/dhcpsrv/Makefile.am @@ -69,6 +69,7 @@ libkea_dhcpsrv_la_SOURCES += cfg_option.cc cfg_option.h libkea_dhcpsrv_la_SOURCES += cfg_option_def.cc cfg_option_def.h libkea_dhcpsrv_la_SOURCES += cfg_subnets4.cc cfg_subnets4.h libkea_dhcpsrv_la_SOURCES += cfg_subnets6.cc cfg_subnets6.h +libkea_dhcpsrv_la_SOURCES += cfg_mac_source.cc cfg_mac_source.h libkea_dhcpsrv_la_SOURCES += cfgmgr.cc cfgmgr.h libkea_dhcpsrv_la_SOURCES += csv_lease_file4.cc csv_lease_file4.h libkea_dhcpsrv_la_SOURCES += csv_lease_file6.cc csv_lease_file6.h diff --git a/src/lib/dhcpsrv/cfg_mac_source.cc b/src/lib/dhcpsrv/cfg_mac_source.cc new file mode 100644 index 0000000000..715820bd07 --- /dev/null +++ b/src/lib/dhcpsrv/cfg_mac_source.cc @@ -0,0 +1,59 @@ +// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC") +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +#include +#include +#include +#include + +namespace isc { +namespace dhcp { + +CfgMACSource::CfgMACSource() { + + // By default, use any hardware source that is available. + mac_sources_.push_back(HWAddr::HWADDR_SOURCE_ANY); +} + +uint32_t CfgMACSource::MACSourceFromText(const std::string& name) { + + struct { + const char * name; + uint32_t type; + } sources[] = { + { "any", HWAddr::HWADDR_SOURCE_ANY }, + { "raw", HWAddr::HWADDR_SOURCE_RAW }, + { "duid", HWAddr::HWADDR_SOURCE_DUID }, + { "ipv6-link-local", HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL }, + { "client-link-addr-option", HWAddr::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION }, + { "rfc6939", HWAddr::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION }, + { "remote-id", HWAddr::HWADDR_SOURCE_REMOTE_ID }, + { "rfc4649", HWAddr::HWADDR_SOURCE_REMOTE_ID }, + { "subscriber-id", HWAddr::HWADDR_SOURCE_SUBSCRIBER_ID }, + { "rfc4580", HWAddr::HWADDR_SOURCE_SUBSCRIBER_ID }, + { "docsis", HWAddr::HWADDR_SOURCE_DOCSIS } + }; + + for (int i=0; i < sizeof(sources)/sizeof(sources[0]); ++i) { + if (name.compare(sources[i].name) == 0) { + return (sources[i].type); + } + } + + isc_throw(BadValue, "Can't convert '" << name << "' to any known MAC source."); +} + + +}; +}; diff --git a/src/lib/dhcpsrv/cfg_mac_source.h b/src/lib/dhcpsrv/cfg_mac_source.h new file mode 100644 index 0000000000..3b3a098a86 --- /dev/null +++ b/src/lib/dhcpsrv/cfg_mac_source.h @@ -0,0 +1,88 @@ +// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC") +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +#ifndef CFG_MAC_SOURCE_H +#define CFG_MAC_SOURCE_H + +#include + +namespace isc { +namespace dhcp { + +/// @brief Container for defined MAC/hardware address sources +typedef std::vector CfgMACSources; + +/// @brief Wrapper class that holds MAC/hardware address sources +/// +/// It's a simple wrapper around a vector of uint32_t, with each entry +/// holding one MAC source. +class CfgMACSource { + + public: + /// @brief Default constructor. + /// + /// Sets source to 'any'. + CfgMACSource(); + + /// @brief Attempts to convert known hardware address sources to uint32_t + /// + /// Supported strings are: \li any => 0xffffffff + /// \li raw => 0x00000001 + /// \li duid => 0x00000002 + /// \li ipv6-link-local 0x00000004 + /// \li client-link-addr-option, rfc6939 => 0x00000008 + /// \li remote-id, rfc4649 => 0x00000010 + /// \li subscriber-id, rfc4580 => 0x00000020 + /// \li docsis => 0x00000040 + /// + /// For specific constants, see @ref isc::dhcp::HWAddr class. + /// + /// @throw BadValue if specified string is unknown + /// @return bitmask version of a given method + static uint32_t MACSourceFromText(const std::string& name); + + + /// @brief Adds additional MAC/hardware address aquisition. + /// + /// @param source MAC source (see constants in Pkt::HWADDR_SOURCE_*) + /// + /// Specified source is being added to the mac_sources_ array. + /// @todo implement add(string) version of this method. + void add(uint32_t source) { + mac_sources_.push_back(source); + } + + /// @brief Provides access to the configure MAC/Hardware address sources. + /// + /// @note The const reference returned is only valid as long as the + /// object that returned it. + const CfgMACSources& get() const { + return mac_sources_; + } + + /// @brief Removes any configured MAC/Hardware address sources. + void clear() { + mac_sources_.clear(); + } + + protected: + /// @brief Actual MAC sources storage + CfgMACSources mac_sources_; + +}; + +}; +}; + +#endif diff --git a/src/lib/dhcpsrv/parsers/dhcp_parsers.cc b/src/lib/dhcpsrv/parsers/dhcp_parsers.cc index 18043635c1..bf9a47731f 100644 --- a/src/lib/dhcpsrv/parsers/dhcp_parsers.cc +++ b/src/lib/dhcpsrv/parsers/dhcp_parsers.cc @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -187,6 +188,7 @@ InterfaceListConfigParser(const std::string& param_name, void InterfaceListConfigParser::build(ConstElementPtr value) { CfgIface cfg_iface; + BOOST_FOREACH(ConstElementPtr iface, value->listValue()) { std::string iface_name = iface->stringValue(); try { @@ -206,6 +208,45 @@ InterfaceListConfigParser::commit() { // Nothing to do. } +// ******************** MACSourcesListConfigParser ************************* + +MACSourcesListConfigParser:: +MACSourcesListConfigParser(const std::string& param_name, + ParserContextPtr global_context) + : param_name_(param_name), global_context_(global_context) { + if (param_name_ != "mac-sources") { + isc_throw(BadValue, "Internal error. MAC sources configuration " + "parser called for the wrong parameter: " << param_name); + } +} + +void +MACSourcesListConfigParser::build(ConstElementPtr value) { + CfgIface cfg_iface; + uint32_t source = 0; + + // By default, there's only one source defined: ANY. + // If user specified anything, we need to get rid of that default. + CfgMgr::instance().getStagingCfg()->getMACSources().clear(); + + BOOST_FOREACH(ConstElementPtr source_elem, value->listValue()) { + std::string source_str = source_elem->stringValue(); + try { + source = CfgMACSource::MACSourceFromText(source_str); + CfgMgr::instance().getStagingCfg()->getMACSources().add(source); + } catch (const std::exception& ex) { + isc_throw(DhcpConfigError, "Failed to convert '" + << source_str << "' to any recognized MAC source:" + << ex.what() << " (" << value->getPosition() << ")"); + } + } +} + +void +MACSourcesListConfigParser::commit() { + // Nothing to do. +} + // ******************** HooksLibrariesParser ************************* HooksLibrariesParser::HooksLibrariesParser(const std::string& param_name) diff --git a/src/lib/dhcpsrv/parsers/dhcp_parsers.h b/src/lib/dhcpsrv/parsers/dhcp_parsers.h index 984633f20b..46e1f7f865 100644 --- a/src/lib/dhcpsrv/parsers/dhcp_parsers.h +++ b/src/lib/dhcpsrv/parsers/dhcp_parsers.h @@ -382,12 +382,10 @@ private: /// @brief parser for interface list definition /// -/// This parser handles Dhcp4/interface entry. +/// This parser handles Dhcp4/interfaces and Dhcp6/interfaces entries. /// It contains a list of network interfaces that the server listens on. /// In particular, it can contain an entry called "all" or "any" that /// designates all interfaces. -/// -/// It is useful for parsing Dhcp4/interface parameter. class InterfaceListConfigParser : public DhcpConfigParser { public: @@ -422,6 +420,48 @@ private: ParserContextPtr global_context_; }; + +/// @brief parser for MAC/hardware aquisition sources +/// +/// This parser handles Dhcp6/mac-sources entry. +/// It contains a list of MAC/hardware aquisition source, i.e. methods how +/// MAC address can possibly by obtained in DHCPv6. For a currently supported +/// methods, see @ref isc::dhcp::Pkt::getMAC. +class MACSourcesListConfigParser : public DhcpConfigParser { +public: + + /// @brief constructor + /// + /// As this is a dedicated parser, it must be used to parse + /// "mac-sources" parameter only. All other types will throw exception. + /// + /// @param param_name name of the configuration parameter being parsed + /// @param global_context Global parser context. + /// @throw BadValue if supplied parameter name is not "mac-sources" + MACSourcesListConfigParser(const std::string& param_name, + ParserContextPtr global_context); + + /// @brief parses parameters value + /// + /// Parses configuration entry (list of sources) and adds each element + /// to the sources list. + /// + /// @param value pointer to the content of parsed values + virtual void build(isc::data::ConstElementPtr value); + + /// @brief Does nothing. + virtual void commit(); + +private: + + // Parsed parameter name + std::string param_name_; + + /// Global parser context. + ParserContextPtr global_context_; +}; + + /// @brief Parser for hooks library list /// /// This parser handles the list of hooks libraries. This is an optional list, @@ -1172,4 +1212,3 @@ typedef boost::shared_ptr Uint32ParserPtr; }; // end of isc namespace #endif // DHCP_PARSERS_H - diff --git a/src/lib/dhcpsrv/srv_config.cc b/src/lib/dhcpsrv/srv_config.cc index a48268e927..ba2384db0f 100644 --- a/src/lib/dhcpsrv/srv_config.cc +++ b/src/lib/dhcpsrv/srv_config.cc @@ -16,6 +16,7 @@ #include #include #include +#include // Needed for HWADDR_SOURCE_* #include #include diff --git a/src/lib/dhcpsrv/srv_config.h b/src/lib/dhcpsrv/srv_config.h index 8ffe29de87..b7403f4590 100644 --- a/src/lib/dhcpsrv/srv_config.h +++ b/src/lib/dhcpsrv/srv_config.h @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -237,6 +238,22 @@ public: //@} + /// @brief Returns non-const reference to an array that stores + /// MAC/hardware address sources. + /// + /// @return non-const reference to MAC/hardware address sources + CfgMACSource& getMACSources() { + return (cfg_mac_source_); + } + + /// @brief Returns const reference to an array that stores + /// MAC/hardware address sources. + /// + /// @return const reference to MAC/hardware address sources + const CfgMACSource& getMACSources() const { + return (cfg_mac_source_); + } + /// @brief Copies the currnet configuration to a new configuration. /// /// This method copies the parameters stored in the configuration to @@ -310,6 +327,7 @@ public: //@} + private: /// @brief Sequence number identifying the configuration. @@ -348,6 +366,8 @@ private: /// reservations for different IPv4 and IPv6 subnets. CfgHostsPtr cfg_hosts_; + /// @brief A list of configured MAC sources. + CfgMACSource cfg_mac_source_; }; /// @name Pointers to the @c SrvConfig object. diff --git a/src/lib/dhcpsrv/tests/Makefile.am b/src/lib/dhcpsrv/tests/Makefile.am index a57545791c..8b7d5e212c 100644 --- a/src/lib/dhcpsrv/tests/Makefile.am +++ b/src/lib/dhcpsrv/tests/Makefile.am @@ -57,6 +57,7 @@ libdhcpsrv_unittests_SOURCES += alloc_engine_unittest.cc libdhcpsrv_unittests_SOURCES += callout_handle_store_unittest.cc libdhcpsrv_unittests_SOURCES += cfg_hosts_unittest.cc libdhcpsrv_unittests_SOURCES += cfg_iface_unittest.cc +libdhcpsrv_unittests_SOURCES += cfg_mac_source_unittest.cc libdhcpsrv_unittests_SOURCES += cfg_option_unittest.cc libdhcpsrv_unittests_SOURCES += cfg_option_def_unittest.cc libdhcpsrv_unittests_SOURCES += cfg_subnets4_unittest.cc @@ -120,7 +121,8 @@ if USE_CLANGPP libdhcpsrv_unittests_CXXFLAGS += -Wno-unused-variable -Wno-unused-parameter endif -libdhcpsrv_unittests_LDADD = $(top_builddir)/src/lib/dhcpsrv/libkea-dhcpsrv.la +libdhcpsrv_unittests_LDADD = $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la +libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/libkea-dhcpsrv.la libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/testutils/libdhcpsrvtest.la libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/dhcp/tests/libdhcptest.la libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la diff --git a/src/lib/dhcpsrv/tests/cfg_mac_source_unittest.cc b/src/lib/dhcpsrv/tests/cfg_mac_source_unittest.cc new file mode 100644 index 0000000000..ad684aff13 --- /dev/null +++ b/src/lib/dhcpsrv/tests/cfg_mac_source_unittest.cc @@ -0,0 +1,54 @@ +// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC") +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +#include +#include +#include +#include + +namespace { + +using namespace isc; +using namespace isc::dhcp; + +// Checks whether CfgMACSource::MACSourceFromText is working correctly. +// Technically, this is a Pkt, not Pkt6 test, but since there is no separate +// unit-tests for Pkt and it is abstract, so it would be tricky to test it +// directly. Hence test is being run in Pkt6. +TEST(CfgMACSourceTest, MACSourceFromText) { + EXPECT_THROW(CfgMACSource::MACSourceFromText("unknown"), BadValue); + + EXPECT_EQ(HWAddr::HWADDR_SOURCE_ANY, CfgMACSource::MACSourceFromText("any")); + EXPECT_EQ(HWAddr::HWADDR_SOURCE_RAW, CfgMACSource::MACSourceFromText("raw")); + EXPECT_EQ(HWAddr::HWADDR_SOURCE_DUID, CfgMACSource::MACSourceFromText("duid")); + EXPECT_EQ(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL, + CfgMACSource::MACSourceFromText("ipv6-link-local")); + EXPECT_EQ(HWAddr::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION, + CfgMACSource::MACSourceFromText("client-link-addr-option")); + EXPECT_EQ(HWAddr::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION, + CfgMACSource::MACSourceFromText("rfc6939")); + EXPECT_EQ(HWAddr::HWADDR_SOURCE_REMOTE_ID, + CfgMACSource::MACSourceFromText("remote-id")); + EXPECT_EQ(HWAddr::HWADDR_SOURCE_REMOTE_ID, + CfgMACSource::MACSourceFromText("rfc4649")); + EXPECT_EQ(HWAddr::HWADDR_SOURCE_SUBSCRIBER_ID, + CfgMACSource::MACSourceFromText("subscriber-id")); + EXPECT_EQ(HWAddr::HWADDR_SOURCE_SUBSCRIBER_ID, + CfgMACSource::MACSourceFromText("rfc4580")); + EXPECT_EQ(HWAddr::HWADDR_SOURCE_DOCSIS, + CfgMACSource::MACSourceFromText("docsis")); + +} + +}; diff --git a/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc b/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc index a16e8f8c22..61b8e9d080 100644 --- a/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc +++ b/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -212,7 +213,7 @@ TEST_F(DhcpParserTest, uint32ParserTest) { EXPECT_EQ(test_value, actual_value); } -/// @brief Check InterfaceListParser basic functionality +/// @brief Check InterfaceListConfigParser basic functionality /// /// Verifies that the parser: /// 1. Does not allow empty for storage. @@ -270,6 +271,49 @@ TEST_F(DhcpParserTest, interfaceListParserTest) { EXPECT_TRUE(test_config.socketOpen("eth1", AF_INET)); } + +/// @brief Check MACSourcesListConfigParser basic functionality +/// +/// Verifies that the parser: +/// 1. Does not allow empty for storage. +/// 2. Does not allow name other than "mac-sources" +/// 3. Parses list of mac sources and adds them to CfgMgr +TEST_F(DhcpParserTest, MacSourcesListConfigParserTest) { + + const std::string valid_name = "mac-sources"; + const std::string bogus_name = "bogus-name"; + + ParserContextPtr parser_context(new ParserContext(Option::V6)); + + // Verify that parser constructor fails if parameter name isn't "mac-sources" + EXPECT_THROW(MACSourcesListConfigParser(bogus_name, parser_context), + isc::BadValue); + + // That's an equivalent of the following snippet: + // "mac-sources: [ \"duid\", \"ipv6\" ]"; + ElementPtr config = Element::createList(); + config->add(Element::create("duid")); + config->add(Element::create("ipv6-link-local")); + + boost::scoped_ptr + parser(new MACSourcesListConfigParser(valid_name, parser_context)); + + // This should parse the configuration and add eth0 and eth1 to the list + // of interfaces that server should listen on. + EXPECT_NO_THROW(parser->build(config)); + EXPECT_NO_THROW(parser->commit()); + + // Use CfgMgr instance to check if eth0 and eth1 was added, and that + // eth2 was not added. + SrvConfigPtr cfg = CfgMgr::instance().getStagingCfg(); + ASSERT_TRUE(cfg); + CfgMACSources configured_sources = cfg->getMACSources().get(); + + ASSERT_EQ(2, configured_sources.size()); + EXPECT_EQ(HWAddr::HWADDR_SOURCE_DUID, configured_sources[0]); + EXPECT_EQ(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL, configured_sources[1]); +} + /// @brief Test Fixture class which provides basic structure for testing /// configuration parsing. This is essentially the same structure provided /// by dhcp servers.