return (true);
}
+DuidPtr Dhcpv6Srv::extractClientId(const Pkt6Ptr& pkt) {
+ // Let's find client's DUID. Client is supposed to include its client-id
+ // option almost all the time (the only exception is an anonymous inf-request,
+ // but that is mostly a theoretical case). Our allocation engine needs DUID
+ // and will refuse to allocate anything to anonymous clients.
+ OptionPtr opt_duid = pkt->getOption(D6O_CLIENTID);
+ if (opt_duid) {
+ return (DuidPtr(new DUID(opt_duid->getData())));
+ } else {
+ return (DuidPtr());
+ }
+}
+
+AllocEngine::ClientContext6 Dhcpv6Srv::createContext(const Pkt6Ptr& pkt) {
+ AllocEngine::ClientContext6 ctx;
+ ctx.subnet_ = selectSubnet(pkt);
+ ctx.duid_ = extractClientId(pkt);
+ ctx.hwaddr_ = getMAC(pkt);
+ alloc_engine_->findReservation(ctx);
+
+ return (ctx);
+}
+
bool Dhcpv6Srv::run() {
while (!shutdown_) {
// client's message and server's response
classifyPacket(query);
try {
- NameChangeRequestPtr ncr;
+ NameChangeRequestPtr ncr;
+
switch (query->getType()) {
case DHCPV6_SOLICIT:
rsp = processSolicit(query);
}
void
-Dhcpv6Srv::appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer) {
+Dhcpv6Srv::appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer,
+ const AllocEngine::ClientContext6 ctx) {
// Client requests some options using ORO option. Try to
// get this option from client's message.
ConstCfgOptionPtr global_opts = CfgMgr::instance().getCurrentCfg()->
getCfgOption();
- // Get the configured subnet suitable for the incoming packet.
- // It may be NULL (if server is misconfigured or the client was rejected
- // using client classes).
- Subnet6Ptr subnet = selectSubnet(question);
-
// Get the list of options that client requested.
const std::vector<uint16_t>& requested_opts = option_oro->getValues();
BOOST_FOREACH(uint16_t opt, requested_opts) {
// If we found a subnet for this client, try subnet first.
- if (subnet) {
- OptionDescriptor desc = subnet->getCfgOption()->get("dhcp6", opt);
+ if (ctx.subnet_) {
+ OptionDescriptor desc = ctx.subnet_->getCfgOption()->get("dhcp6", opt);
if (desc.option_) {
// Attempt to assign an option from subnet first.
answer->addOption(desc.option_);
}
void
-Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer) {
+Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer,
+ const AllocEngine::ClientContext6& ctx) {
// We need to allocate addresses for all IA_NA options in the client's
// question (i.e. SOLICIT or REQUEST) message.
// @todo add support for IA_TA
// We need to select a subnet the client is connected in.
- Subnet6Ptr subnet = selectSubnet(question);
- if (!subnet) {
+ if (!ctx.subnet_) {
// This particular client is out of luck today. We do not have
// information about the subnet he is connected to. This likely means
// misconfiguration of the server (or some relays). We will continue to
} else {
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA, DHCP6_SUBNET_SELECTED)
- .arg(subnet->toText());
+ .arg(ctx.subnet_->toText());
}
- // @todo: We should implement Option6Duid some day, but we can do without it
- // just fine for now
-
- // Let's find client's DUID. Client is supposed to include its client-id
- // option almost all the time (the only exception is an anonymous inf-request,
- // but that is mostly a theoretical case). Our allocation engine needs DUID
- // and will refuse to allocate anything to anonymous clients.
- DuidPtr duid;
- OptionPtr opt_duid = question->getOption(D6O_CLIENTID);
- if (opt_duid) {
- duid = DuidPtr(new DUID(opt_duid->getData()));
- } else {
- LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_CLIENTID_MISSING);
+ if (!ctx.duid_) {
+ LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_CLIENTID_MISSING)
+ .arg(question->getIface());
// Let's drop the message. This client is not sane.
isc_throw(RFCViolation, "Mandatory client-id is missing in received message");
}
opt != question->options_.end(); ++opt) {
switch (opt->second->getType()) {
case D6O_IA_NA: {
- OptionPtr answer_opt = assignIA_NA(subnet, duid, question, answer,
+ OptionPtr answer_opt = assignIA_NA(ctx.subnet_, ctx.duid_, question,
+ answer,
boost::dynamic_pointer_cast<
Option6IA>(opt->second));
if (answer_opt) {
break;
}
case D6O_IA_PD: {
- OptionPtr answer_opt = assignIA_PD(subnet, duid, question,
+ OptionPtr answer_opt = assignIA_PD(ctx.subnet_, ctx.duid_, question,
boost::dynamic_pointer_cast<
Option6IA>(opt->second));
if (answer_opt) {
}
void
-Dhcpv6Srv::processClientFqdn(const Pkt6Ptr& question, const Pkt6Ptr& answer) {
+Dhcpv6Srv::processClientFqdn(const Pkt6Ptr& question, const Pkt6Ptr& answer,
+ const AllocEngine::ClientContext6 ctx) {
// Get Client FQDN Option from the client's message. If this option hasn't
// been included, do nothing.
Option6ClientFqdnPtr fqdn = boost::dynamic_pointer_cast<
// response to a client.
Option6ClientFqdnPtr fqdn_resp(new Option6ClientFqdn(*fqdn));
+ // If there's a reservation and it has a hostname specified, use it!
+ if (ctx.host_ && !ctx.host_->getHostname().empty()) {
+ /// @todo: We don't support partial domain names in HR yet.
+ fqdn_resp->setDomainName(ctx.host_->getHostname(), Option6ClientFqdn::FULL);
+ }
+
// Set the server S, N, and O flags based on client's flags and
// current configuration.
D2ClientMgr& d2_mgr = CfgMgr::instance().getD2ClientMgr();
}
void
-Dhcpv6Srv::releaseLeases(const Pkt6Ptr& release, Pkt6Ptr& reply) {
+Dhcpv6Srv::releaseLeases(const Pkt6Ptr& release, Pkt6Ptr& reply,
+ AllocEngine::ClientContext6& ctx) {
// We need to release addresses for all IA_NA options in the client's
// RELEASE message.
// option almost all the time (the only exception is an anonymous inf-request,
// but that is mostly a theoretical case). Our allocation engine needs DUID
// and will refuse to allocate anything to anonymous clients.
- OptionPtr opt_duid = release->getOption(D6O_CLIENTID);
- if (!opt_duid) {
+ if (!ctx.duid_) {
// This should not happen. We have checked this before.
// see sanityCheck() called from processRelease()
LOG_WARN(dhcp6_logger, DHCP6_RELEASE_MISSING_CLIENTID)
"You did not include mandatory client-id"));
return;
}
- DuidPtr duid(new DUID(opt_duid->getData()));
// Let's set the status to be success by default. We can override it with
// error status if needed. The important thing to understand here is that
opt != release->options_.end(); ++opt) {
switch (opt->second->getType()) {
case D6O_IA_NA: {
- OptionPtr answer_opt = releaseIA_NA(duid, release, general_status,
+ OptionPtr answer_opt = releaseIA_NA(ctx.duid_, release, general_status,
boost::dynamic_pointer_cast<Option6IA>(opt->second));
if (answer_opt) {
reply->addOption(answer_opt);
break;
}
case D6O_IA_PD: {
- OptionPtr answer_opt = releaseIA_PD(duid, release, general_status,
+ OptionPtr answer_opt = releaseIA_PD(ctx.duid_, release, general_status,
boost::dynamic_pointer_cast<Option6IA>(opt->second));
if (answer_opt) {
reply->addOption(answer_opt);
return (ia_rsp);
}
+
+
Pkt6Ptr
Dhcpv6Srv::processSolicit(const Pkt6Ptr& solicit) {
sanityCheck(solicit, MANDATORY, FORBIDDEN);
+ // Let's create a simplified client context here.
+ AllocEngine::ClientContext6 ctx = createContext(solicit);
+
Pkt6Ptr advertise(new Pkt6(DHCPV6_ADVERTISE, solicit->getTransid()));
copyClientOptions(solicit, advertise);
appendDefaultOptions(solicit, advertise);
- appendRequestedOptions(solicit, advertise);
+ appendRequestedOptions(solicit, advertise, ctx);
appendRequestedVendorOptions(solicit, advertise);
- processClientFqdn(solicit, advertise);
- assignLeases(solicit, advertise);
+ processClientFqdn(solicit, advertise, ctx);
+ assignLeases(solicit, advertise, ctx);
// Note, that we don't create NameChangeRequests here because we don't
// perform DNS Updates for Solicit. Client must send Request to update
// DNS.
sanityCheck(request, MANDATORY, MANDATORY);
+ // Let's create a simplified client context here.
+ AllocEngine::ClientContext6 ctx = createContext(request);
+
Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, request->getTransid()));
copyClientOptions(request, reply);
appendDefaultOptions(request, reply);
- appendRequestedOptions(request, reply);
+ appendRequestedOptions(request, reply, ctx);
appendRequestedVendorOptions(request, reply);
- processClientFqdn(request, reply);
- assignLeases(request, reply);
+ processClientFqdn(request, reply, ctx);
+ assignLeases(request, reply, ctx);
generateFqdn(reply);
createNameChangeRequests(reply);
sanityCheck(renew, MANDATORY, MANDATORY);
+ // Let's create a simplified client context here.
+ AllocEngine::ClientContext6 ctx = createContext(renew);
+
Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, renew->getTransid()));
copyClientOptions(renew, reply);
appendDefaultOptions(renew, reply);
- appendRequestedOptions(renew, reply);
+ appendRequestedOptions(renew, reply, ctx);
- processClientFqdn(renew, reply);
+ processClientFqdn(renew, reply, ctx);
extendLeases(renew, reply);
generateFqdn(reply);
createNameChangeRequests(reply);
Pkt6Ptr
Dhcpv6Srv::processRebind(const Pkt6Ptr& rebind) {
+ // Let's create a simplified client context here.
+ AllocEngine::ClientContext6 ctx = createContext(rebind);
+
Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, rebind->getTransid()));
copyClientOptions(rebind, reply);
appendDefaultOptions(rebind, reply);
- appendRequestedOptions(rebind, reply);
+ appendRequestedOptions(rebind, reply, ctx);
- processClientFqdn(rebind, reply);
+ processClientFqdn(rebind, reply, ctx);
extendLeases(rebind, reply);
generateFqdn(reply);
createNameChangeRequests(rebind);
Pkt6Ptr
Dhcpv6Srv::processConfirm(const Pkt6Ptr& confirm) {
+
+ // Let's create a simplified client context here.
+ AllocEngine::ClientContext6 ctx = createContext(confirm);
+
// Get IA_NAs from the Confirm. If there are none, the message is
// invalid and must be discarded. There is nothing more to do.
OptionCollection ias = confirm->getOptions(D6O_IA_NA);
// are verified it means that the client has sent no IA_NA options
// or no IAAddr options and that client's message has to be discarded.
bool verified = false;
- // Check if subnet can be selected for the message. If no subnet
+ // Check if subnet was selected for the message. If no subnet
// has been selected, the client is not on link.
- SubnetPtr subnet = selectSubnet(confirm);
+ SubnetPtr subnet = ctx.subnet_;
+
// Regardless if the subnet has been selected or not, we will iterate
// over the IA_NA options to check if they hold any addresses. If there
// are no, the Confirm is discarded.
sanityCheck(release, MANDATORY, MANDATORY);
+ // Let's create a simplified client context here.
+ AllocEngine::ClientContext6 ctx = createContext(release);
+
Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, release->getTransid()));
copyClientOptions(release, reply);
appendDefaultOptions(release, reply);
- releaseLeases(release, reply);
+ releaseLeases(release, reply, ctx);
// @todo If client sent a release and we should remove outstanding
// DNS records.
Pkt6Ptr
Dhcpv6Srv::processInfRequest(const Pkt6Ptr& infRequest) {
+ // Let's create a simplified client context here.
+ AllocEngine::ClientContext6 ctx = createContext(infRequest);
+
// Create a Reply packet, with the same trans-id as the client's.
Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, infRequest->getTransid()));
appendDefaultOptions(infRequest, reply);
// Try to assign options that were requested by the client.
- appendRequestedOptions(infRequest, reply);
+ appendRequestedOptions(infRequest, reply, ctx);
return (reply);
}
///
/// @param question client's message
/// @param answer server's message (options will be added here)
- void appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer);
+ /// @param ctx client context (contains subnet, duid and other parameters)
+ void appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer,
+ const AllocEngine::ClientContext6 ctx);
/// @brief Appends requested vendor options to server's answer.
///
/// @brief Assigns leases.
///
- /// It supports addresses (IA_NA) only. It does NOT support temporary
- /// addresses (IA_TA) nor prefixes (IA_PD).
- /// @todo: Extend this method once TA and PD becomes supported
+ /// It supports non-temporary addresses (IA_NA) and prefixes (IA_PD). It
+ /// does NOT support temporary addresses (IA_TA).
///
- /// @param question client's message (with requested IA_NA)
- /// @param answer server's message (IA_NA options will be added here).
- /// This message should contain Client FQDN option being sent by the server
- /// to the client (if the client sent this option to the server).
- void assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer);
+ /// @param question client's message (with requested IA options)
+ /// @param answer server's message (IA options will be added here).
+ /// This message should contain Client FQDN option being sent by the server
+ /// to the client (if the client sent this option to the server).
+ /// @param ctx client context (contains subnet, duid and other parameters)
+ void assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer,
+ const AllocEngine::ClientContext6& ctx);
/// @brief Processes Client FQDN Option.
///
/// @param answer Server's response to a client. If server generated
/// Client FQDN option for the client, this option is stored in this
/// object.
- void processClientFqdn(const Pkt6Ptr& question, const Pkt6Ptr& answer);
+ /// @param ctx client context (includes subnet, client-id, hw-addr etc.)
+ void processClientFqdn(const Pkt6Ptr& question, const Pkt6Ptr& answer,
+ const AllocEngine::ClientContext6 ctx);
/// @brief Creates a number of @c isc::dhcp_ddns::NameChangeRequest objects
/// based on the DHCPv6 Client FQDN %Option.
/// to REPLY packet, just its IA_NA containers.
/// @param release client's message asking to release
/// @param reply server's response
- void releaseLeases(const Pkt6Ptr& release, Pkt6Ptr& reply);
+ /// @param ctx client context (includes subnet, client-id, hw-addr etc.)
+ void releaseLeases(const Pkt6Ptr& release, Pkt6Ptr& reply,
+ AllocEngine::ClientContext6& ctx);
/// @brief Sets server-identifier.
///
/// @return HWaddr pointer (or NULL if configured methods fail)
static HWAddrPtr getMAC(const Pkt6Ptr& pkt);
+ /// @brief Creates client context for specified packet
+ ///
+ /// Creates context that includes subnet, client-id, hw address and
+ /// possibly other parameters.
+ /// @return client context
+ AllocEngine::ClientContext6 createContext(const Pkt6Ptr& pkt);
+
/// @brief this is a prefix added to the contend of vendor-class option
///
/// If incoming packet has a vendor class option, its content is
const std::string& hostname,
bool do_fwd, bool do_rev);
+ /// @brief Utility method that extracts DUID from client-id option
+ ///
+ /// @param pkt the message that contains client-id option
+ /// @return extracted DUID (or NULL if client-id is missing)
+ DuidPtr extractClientId(const Pkt6Ptr& pkt);
+
/// @brief Allocation Engine.
/// Pointer to the allocation engine that we are currently using
/// It must be a pointer, because we will support changing engines
// Create three IAs, each having different address.
addIA(1234, IOAddress("2001:db8:1::1"), answer);
- ASSERT_NO_THROW(srv_->processClientFqdn(question, answer));
+ AllocEngine::ClientContext6 ctx;
+ ASSERT_NO_THROW(srv_->processClientFqdn(question, answer, ctx));
Option6ClientFqdnPtr answ_fqdn = boost::dynamic_pointer_cast<
Option6ClientFqdn>(answer->getOption(D6O_CLIENT_FQDN));
ASSERT_TRUE(answ_fqdn);
const std::string& exp_hostname,
const uint8_t client_flags =
Option6ClientFqdn::FLAG_S,
+ const IOAddress expected_address = IOAddress("2001:db8:1:1::dead:beef"),
const bool include_oro = true) {
// Create a message of a specified type, add server id and
// FQDN option.
ASSERT_TRUE(addr);
// Check that we have got the address we requested.
- checkIAAddr(addr, IOAddress("2001:db8:1:1::dead:beef"),
+ checkIAAddr(addr, expected_address,
Lease::TYPE_NA);
if (msg_type != DHCPV6_SOLICIT) {
ASSERT_NO_THROW(d2_mgr_.runReadyIO());
}
+ /// @brief Utility function that creates a host reservation (duid)
+ ///
+ /// @param add_to_host_mgr true if the reservation should be added
+ /// @param type specifies reservation type (NA or PD)
+ /// @param addr specifies reserved address
+ /// @param hostname specifies hostname to be used in reservation
+ /// @return created Host object.
+ HostPtr
+ createHost6(bool add_to_host_mgr, IPv6Resrv::Type type,
+ const asiolink::IOAddress& addr, const std::string& hostname) {
+ HostPtr host(new Host(&duid_->getDuid()[0], duid_->getDuid().size(),
+ Host::IDENT_DUID, SubnetID(0), subnet_->getID(),
+ asiolink::IOAddress("0.0.0.0"),
+ hostname));
+
+ // Prefix length doesn't matter here, let's assume address is /128 and
+ // prefix is /64
+ IPv6Resrv resv(type, addr, type == IPv6Resrv::TYPE_NA? 128 : 64);
+ host->addReservation(resv);
+
+ if (add_to_host_mgr) {
+
+ // Let's add the host.
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+
+ // We also need to add existing subnet
+ CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet_);
+
+ // Commit this configuration.
+ CfgMgr::instance().commit();
+ }
+ return (host);
+ }
+
// Holds a lease used by a test.
Lease6Ptr lease_;
// In this case, we expect that the FQDN option will not be included
// in the server's response. The testProcessMessage will check that.
testProcessMessage(DHCPV6_REQUEST, "myhost.example.com",
- "myhost.example.com.", Option6ClientFqdn::FLAG_S, false);
+ "myhost.example.com.", Option6ClientFqdn::FLAG_S,
+ IOAddress("2001:db8:1:1::dead:beef"), false);
ASSERT_EQ(1, d2_mgr_.getQueueSize());
verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
"2001:db8:1:1::dead:beef",
TEST_F(FqdnDhcpv6SrvTest, processRequestEmptyFqdn) {
testProcessMessage(DHCPV6_REQUEST, "",
"myhost-2001-db8-1-1--dead-beef.example.com.",
- Option6ClientFqdn::FLAG_S, false);
+ Option6ClientFqdn::FLAG_S,
+ IOAddress("2001:db8:1:1::dead:beef"), false);
ASSERT_EQ(1, d2_mgr_.getQueueSize());
verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
"2001:db8:1:1::dead:beef",
0, 4000);
}
+TEST_F(FqdnDhcpv6SrvTest, hostnameReservation) {
+
+ createHost6(true, IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1:1::babe"),
+ "alice.example.org.");
+
+ testProcessMessage(DHCPV6_REQUEST, "myhost.example.com",
+ "alice.example.org.", 0, IOAddress("2001:db8:1:1::babe"));
+ ASSERT_EQ(1, d2_mgr_.getQueueSize());
+}
+
} // end of anonymous namespace