Commit be309bf0 authored by Tomek Mrugalski's avatar Tomek Mrugalski 🛰
Browse files

Merge branch 'trac1230'

Conflicts:
	ChangeLog
	src/lib/dhcp/iface_mgr.h
parents 55aa7ec8 aac05f56
363. [func] tomek
dhcp4: Support for DISCOVER and OFFER implemented. b10-dhcp4 is
now able to offer hardcoded leases to DHCPv4 clients.
dhcp6: Code refactored to use the same approach as dhcp4.
(Trac #1230, git aac05f566c49daad4d3de35550cfaff31c124513)
362. [func] tomek
libdhcp++: Interface detection in Linux implemented. libdhcp++
if now able to detect available network interfaces, its link-layer
......
......@@ -17,13 +17,22 @@
#include <dhcp/iface_mgr.h>
#include <dhcp4/dhcp4_srv.h>
#include <asiolink/io_address.h>
#include <dhcp/option4_addrlst.h>
using namespace std;
using namespace isc;
using namespace isc::dhcp;
using namespace isc::asiolink;
// #define ECHO_SERVER
// These are hardcoded parameters. Currently this is a skeleton server that only
// grants those options and a single, fixed, hardcoded lease.
const std::string HARDCODED_LEASE = "192.0.2.222"; // assigned lease
const std::string HARDCODED_NETMASK = "255.255.255.0";
const uint32_t HARDCODED_LEASE_TIME = 60; // in seconds
const std::string HARDCODED_GATEWAY = "192.0.2.1";
const std::string HARDCODED_DNS_SERVER = "192.0.2.2";
const std::string HARDCODED_DOMAIN_NAME = "isc.example.com";
const std::string HARDCODED_SERVER_ID = "192.0.2.1";
Dhcpv4Srv::Dhcpv4Srv(uint16_t port) {
cout << "Initialization: opening sockets on port " << port << endl;
......@@ -55,12 +64,6 @@ Dhcpv4Srv::run() {
query = IfaceMgr::instance().receive4();
#if defined(ECHO_SERVER)
query->repack();
IfaceMgr::instance().send(query);
continue;
#endif
if (query) {
try {
query->unpack();
......@@ -98,9 +101,16 @@ Dhcpv4Srv::run() {
cout << query->toText();
if (rsp) {
rsp->setRemoteAddr(query->getRemoteAddr());
if (rsp->getRemoteAddr().toText() == "0.0.0.0") {
rsp->setRemoteAddr(query->getRemoteAddr());
}
if (!rsp->getHops()) {
rsp->setRemotePort(DHCP4_CLIENT_PORT);
} else {
rsp->setRemotePort(DHCP4_SERVER_PORT);
}
rsp->setLocalAddr(query->getLocalAddr());
rsp->setRemotePort(DHCP4_CLIENT_PORT);
rsp->setLocalPort(DHCP4_SERVER_PORT);
rsp->setIface(query->getIface());
rsp->setIndex(query->getIndex());
......@@ -112,10 +122,7 @@ Dhcpv4Srv::run() {
if (rsp->pack()) {
cout << "Packet assembled correctly." << endl;
}
#if 1
// uncomment this once ticket 1240 is merged.
IfaceMgr::instance().send(rsp);
#endif
}
}
......@@ -139,16 +146,116 @@ Dhcpv4Srv::setServerID() {
#endif
}
void Dhcpv4Srv::copyDefaultFields(const boost::shared_ptr<Pkt4>& question,
boost::shared_ptr<Pkt4>& answer) {
answer->setIface(question->getIface());
answer->setIndex(question->getIndex());
answer->setCiaddr(question->getCiaddr());
answer->setSiaddr(IOAddress("0.0.0.0")); // explictly set this to 0
answer->setHops(question->getHops());
// copy MAC address
vector<uint8_t> mac(question->getChaddr(),
question->getChaddr() + Pkt4::MAX_CHADDR_LEN);
answer->setHWAddr(question->getHtype(), question->getHlen(), mac);
// relay address
answer->setGiaddr(question->getGiaddr());
if (question->getGiaddr().toText() != "0.0.0.0") {
// relayed traffic
answer->setRemoteAddr(question->getGiaddr());
} else {
// direct traffic
answer->setRemoteAddr(question->getRemoteAddr());
}
}
void Dhcpv4Srv::appendDefaultOptions(boost::shared_ptr<Pkt4>& msg, uint8_t msg_type) {
boost::shared_ptr<Option> opt;
// add Message Type Option (type 53)
std::vector<uint8_t> tmp;
tmp.push_back(static_cast<uint8_t>(msg_type));
opt = boost::shared_ptr<Option>(new Option(Option::V4, DHO_DHCP_MESSAGE_TYPE, tmp));
msg->addOption(opt);
// DHCP Server Identifier (type 54)
opt = boost::shared_ptr<Option>
(new Option4AddrLst(DHO_DHCP_SERVER_IDENTIFIER, IOAddress(HARDCODED_SERVER_ID)));
msg->addOption(opt);
// more options will be added here later
}
void Dhcpv4Srv::appendRequestedOptions(boost::shared_ptr<Pkt4>& msg) {
boost::shared_ptr<Option> opt;
// Domain name (type 15)
vector<uint8_t> domain(HARDCODED_DOMAIN_NAME.begin(), HARDCODED_DOMAIN_NAME.end());
opt = boost::shared_ptr<Option>(new Option(Option::V4, DHO_DOMAIN_NAME, domain));
msg->addOption(opt);
// TODO: Add Option_String class
// DNS servers (type 6)
opt = boost::shared_ptr<Option>
(new Option4AddrLst(DHO_DOMAIN_NAME_SERVERS, IOAddress(HARDCODED_DNS_SERVER)));
msg->addOption(opt);
}
void Dhcpv4Srv::tryAssignLease(boost::shared_ptr<Pkt4>& msg) {
boost::shared_ptr<Option> opt;
// TODO: Implement actual lease assignment here
msg->setYiaddr(IOAddress(HARDCODED_LEASE));
// IP Address Lease time (type 51)
opt = boost::shared_ptr<Option>(new Option(Option::V4, DHO_DHCP_LEASE_TIME));
opt->setUint32(HARDCODED_LEASE_TIME);
msg->addOption(opt);
// TODO: create Option_IntArray that holds list of integers, similar to Option4_AddrLst
// Subnet mask (type 1)
opt = boost::shared_ptr<Option>
(new Option4AddrLst(DHO_SUBNET_MASK, IOAddress(HARDCODED_NETMASK)));
msg->addOption(opt);
// Router (type 3)
opt = boost::shared_ptr<Option>
(new Option4AddrLst(DHO_ROUTERS, IOAddress(HARDCODED_GATEWAY)));
msg->addOption(opt);
}
boost::shared_ptr<Pkt4>
Dhcpv4Srv::processDiscover(boost::shared_ptr<Pkt4>& discover) {
/// TODO: Currently implemented echo mode. Implement this for real
return (discover);
boost::shared_ptr<Pkt4> offer = boost::shared_ptr<Pkt4>
(new Pkt4(DHCPOFFER, discover->getTransid()));
copyDefaultFields(discover, offer);
appendDefaultOptions(offer, DHCPOFFER);
appendRequestedOptions(offer);
tryAssignLease(offer);
return (offer);
}
boost::shared_ptr<Pkt4>
Dhcpv4Srv::processRequest(boost::shared_ptr<Pkt4>& request) {
/// TODO: Currently implemented echo mode. Implement this for real
return (request);
boost::shared_ptr<Pkt4> ack = boost::shared_ptr<Pkt4>
(new Pkt4(DHCPACK, request->getTransid()));
copyDefaultFields(request, ack);
appendDefaultOptions(ack, DHCPACK);
appendRequestedOptions(ack);
tryAssignLease(ack);
return (ack);
}
void Dhcpv4Srv::processRelease(boost::shared_ptr<Pkt4>& release) {
......
......@@ -106,6 +106,45 @@ protected:
/// @param infRequest message received from client
boost::shared_ptr<Pkt4> processInform(boost::shared_ptr<Pkt4>& inform);
/// @brief Copies default parameters from client's to server's message
///
/// Some fields are copied from client's message into server's response,
/// e.g. client HW address, number of hops, transaction-id etc.
///
/// @param question any message sent by client
/// @param answer any message server is going to send as response
void copyDefaultFields(const boost::shared_ptr<Pkt4>& question,
boost::shared_ptr<Pkt4>& answer);
/// @brief Appends options requested by client.
///
/// This method assigns options that were requested by client
/// (sent in PRL) or are enforced by server.
///
/// @param msg outgoing message (options will be added here)
void appendRequestedOptions(boost::shared_ptr<Pkt4>& msg);
/// @brief Assigns a lease and appends corresponding options
///
/// This method chooses the most appropriate lease for reqesting
/// client and assigning it. Options corresponding to the lease
/// are added to specific message.
///
/// Note: Lease manager is not implemented yet, so this method
/// used fixed, hardcoded lease.
///
/// @param msg OFFER or ACK message (lease options will be added here)
void tryAssignLease(boost::shared_ptr<Pkt4>& msg);
/// @brief Appends default options to a message
///
/// @param msg message object (options will be added to it)
/// @param msg_type specifies message type
void appendDefaultOptions(boost::shared_ptr<Pkt4>& msg, uint8_t msg_type);
/// @brief Returns server-intentifier option
///
/// @return server-id option
......
......@@ -23,10 +23,12 @@
#include <dhcp/dhcp4.h>
#include <dhcp4/dhcp4_srv.h>
#include <dhcp/option.h>
#include <asiolink/io_address.h>
using namespace std;
using namespace isc;
using namespace isc::dhcp;
using namespace isc::asiolink;
namespace {
const char* const INTERFACE_FILE = "interfaces.txt";
......@@ -66,6 +68,30 @@ public:
fakeifaces.close();
}
void MessageCheck(const boost::shared_ptr<Pkt4>& q,
const boost::shared_ptr<Pkt4>& a) {
ASSERT_TRUE(q);
ASSERT_TRUE(a);
EXPECT_EQ(q->getHops(), a->getHops());
EXPECT_EQ(q->getIface(), a->getIface());
EXPECT_EQ(q->getIndex(), a->getIndex());
EXPECT_EQ(q->getGiaddr(), a->getGiaddr());
// check that bare minimum of required options are there
EXPECT_TRUE(a->getOption(DHO_SUBNET_MASK));
EXPECT_TRUE(a->getOption(DHO_ROUTERS));
EXPECT_TRUE(a->getOption(DHO_DHCP_SERVER_IDENTIFIER));
EXPECT_TRUE(a->getOption(DHO_DHCP_LEASE_TIME));
EXPECT_TRUE(a->getOption(DHO_SUBNET_MASK));
EXPECT_TRUE(a->getOption(DHO_ROUTERS));
EXPECT_TRUE(a->getOption(DHO_DOMAIN_NAME));
EXPECT_TRUE(a->getOption(DHO_DOMAIN_NAME_SERVERS));
// check that something is offered
EXPECT_TRUE(a->getYiaddr().toText() != "0.0.0.0");
}
~Dhcpv4SrvTest() {
unlink(INTERFACE_FILE);
};
......@@ -85,37 +111,113 @@ TEST_F(Dhcpv4SrvTest, basic) {
TEST_F(Dhcpv4SrvTest, processDiscover) {
NakedDhcpv4Srv* srv = new NakedDhcpv4Srv();
vector<uint8_t> mac(6);
for (int i = 0; i < 6; i++) {
mac[i] = 255 - i;
}
boost::shared_ptr<Pkt4> pkt(new Pkt4(DHCPDISCOVER, 1234));
boost::shared_ptr<Pkt4> offer;
pkt->setIface("eth0");
pkt->setIndex(17);
pkt->setHWAddr(1, 6, mac);
pkt->setRemoteAddr(IOAddress("192.0.2.56"));
pkt->setGiaddr(IOAddress("192.0.2.67"));
// let's make it a relayed message
pkt->setHops(3);
pkt->setRemotePort(DHCP4_SERVER_PORT);
// should not throw
EXPECT_NO_THROW(
srv->processDiscover(pkt);
offer = srv->processDiscover(pkt);
);
// should return something
EXPECT_TRUE(srv->processDiscover(pkt));
ASSERT_TRUE(offer);
EXPECT_EQ(DHCPOFFER, offer->getType());
// this is relayed message. It should be sent back to relay address.
EXPECT_EQ(pkt->getGiaddr(), offer->getRemoteAddr());
MessageCheck(pkt, offer);
// now repeat the test for directly sent message
pkt->setHops(0);
pkt->setGiaddr(IOAddress("0.0.0.0"));
pkt->setRemotePort(DHCP4_CLIENT_PORT);
EXPECT_NO_THROW(
offer = srv->processDiscover(pkt);
);
// should return something
ASSERT_TRUE(offer);
EXPECT_EQ(DHCPOFFER, offer->getType());
// this is direct message. It should be sent back to origin, not
// to relay.
EXPECT_EQ(pkt->getRemoteAddr(), offer->getRemoteAddr());
MessageCheck(pkt, offer);
// TODO: Implement more reasonable tests before starting
// work on processSomething() method.
delete srv;
}
TEST_F(Dhcpv4SrvTest, processRequest) {
NakedDhcpv4Srv* srv = new NakedDhcpv4Srv();
vector<uint8_t> mac(6);
for (int i = 0; i < 6; i++) {
mac[i] = i*10;
}
boost::shared_ptr<Pkt4> pkt(new Pkt4(DHCPREQUEST, 1234));
boost::shared_ptr<Pkt4> req(new Pkt4(DHCPREQUEST, 1234));
boost::shared_ptr<Pkt4> ack;
req->setIface("eth0");
req->setIndex(17);
req->setHWAddr(1, 6, mac);
req->setRemoteAddr(IOAddress("192.0.2.56"));
req->setGiaddr(IOAddress("192.0.2.67"));
// should not throw
ASSERT_NO_THROW(
ack = srv->processRequest(req);
);
// should return something
ASSERT_TRUE(ack);
EXPECT_EQ(DHCPACK, ack->getType());
// this is relayed message. It should be sent back to relay address.
EXPECT_EQ(req->getGiaddr(), ack->getRemoteAddr());
MessageCheck(req, ack);
// now repeat the test for directly sent message
req->setHops(0);
req->setGiaddr(IOAddress("0.0.0.0"));
req->setRemotePort(DHCP4_CLIENT_PORT);
EXPECT_NO_THROW(
srv->processRequest(pkt);
ack = srv->processDiscover(req);
);
// should return something
EXPECT_TRUE(srv->processRequest(pkt));
ASSERT_TRUE(ack);
EXPECT_EQ(DHCPOFFER, ack->getType());
// this is direct message. It should be sent back to origin, not
// to relay.
EXPECT_EQ(ack->getRemoteAddr(), req->getRemoteAddr());
MessageCheck(req, ack);
// TODO: Implement more reasonable tests before starting
// work on processSomething() method.
delete srv;
}
......
......@@ -18,6 +18,7 @@
#include <dhcp6/dhcp6_srv.h>
#include <dhcp/option6_ia.h>
#include <dhcp/option6_iaaddr.h>
#include <dhcp/option6_addrlst.h>
#include <asiolink/io_address.h>
#include <exceptions/exceptions.h>
......@@ -26,21 +27,28 @@ using namespace isc;
using namespace isc::dhcp;
using namespace isc::asiolink;
const std::string HARDCODED_LEASE = "2001:db8:1::1234:abcd";
const uint32_t HARDCODED_T1 = 1500; // in seconds
const uint32_t HARDCODED_T2 = 2600; // in seconds
const uint32_t HARDCODED_PREFERRED_LIFETIME = 3600; // in seconds
const uint32_t HARDCODED_VALID_LIFETIME = 7200; // in seconds
const std::string HARDCODED_DNS_SERVER = "2001:db8:1::1";
Dhcpv6Srv::Dhcpv6Srv(uint16_t port) {
cout << "Initialization" << endl;
// first call to instance() will create IfaceMgr (it's a singleton)
// it may throw something if things go wrong
try {
IfaceMgr::instance();
IfaceMgr::instance();
} catch (const std::exception &e) {
cout << "Failed to instantiate InterfaceManager:" << e.what() << ". Aborting." << endl;
shutdown = true;
cout << "Failed to instantiate InterfaceManager:" << e.what() << ". Aborting." << endl;
shutdown = true;
}
if (IfaceMgr::instance().countIfaces() == 0) {
cout << "Failed to detect any network interfaces. Aborting." << endl;
shutdown = true;
cout << "Failed to detect any network interfaces. Aborting." << endl;
shutdown = true;
}
// Now try to open IPv6 sockets on detected interfaces.
......@@ -115,8 +123,9 @@ Dhcpv6Srv::run() {
cout << "Replying with:" << rsp->getType() << endl;
cout << rsp->toText();
cout << "----" << endl;
if (rsp->pack()) {
cout << "#### pack successful." << endl;
if (!rsp->pack()) {
cout << "Failed to assemble response packet." << endl;
continue;
}
IfaceMgr::instance().send(rsp);
}
......@@ -149,18 +158,45 @@ Dhcpv6Srv::setServerID() {
0, 14));
}
boost::shared_ptr<Pkt6>
Dhcpv6Srv::processSolicit(boost::shared_ptr<Pkt6> solicit) {
void Dhcpv6Srv::copyDefaultOptions(const boost::shared_ptr<Pkt6>& question,
boost::shared_ptr<Pkt6>& answer) {
// add client-id
boost::shared_ptr<Option> clientid = question->getOption(D6O_CLIENTID);
if (clientid) {
answer->addOption(clientid);
}
boost::shared_ptr<Pkt6> reply(new Pkt6(DHCPV6_ADVERTISE,
solicit->getTransid(),
Pkt6::UDP));
// TODO: Should throw if there is no client-id (except anonymous INF-REQUEST)
}
void Dhcpv6Srv::appendDefaultOptions(const boost::shared_ptr<Pkt6>& /*question*/,
boost::shared_ptr<Pkt6>& answer) {
// TODO: question is currently unused, but we need it at least to know
// message type we are answering
// add server-id
answer->addOption(getServerID());
}
void Dhcpv6Srv::appendRequestedOptions(const boost::shared_ptr<Pkt6>& /*question*/,
boost::shared_ptr<Pkt6>& answer) {
// TODO: question is currently unused, but we need to extract ORO from it
// and act on its content. Now we just send DNS-SERVERS option.
// add dns-servers option
boost::shared_ptr<Option> dnsservers(new Option6AddrLst(D6O_NAME_SERVERS,
IOAddress(HARDCODED_DNS_SERVER)));
answer->addOption(dnsservers);
}
void Dhcpv6Srv::assignLeases(const boost::shared_ptr<Pkt6>& question,
boost::shared_ptr<Pkt6>& answer) {
/// TODO Rewrite this once LeaseManager is implemented.
// answer client's IA (this is mostly a dummy,
// so let's answer only first IA and hope there is only one)
boost::shared_ptr<Option> ia_opt = solicit->getOption(D6O_IA_NA);
boost::shared_ptr<Option> ia_opt = question->getOption(D6O_IA_NA);
if (ia_opt) {
// found IA
Option* tmp = ia_opt.get();
......@@ -168,38 +204,51 @@ Dhcpv6Srv::processSolicit(boost::shared_ptr<Pkt6> solicit) {
if (ia_req) {
boost::shared_ptr<Option6IA>
ia_rsp(new Option6IA(D6O_IA_NA, ia_req->getIAID()));
ia_rsp->setT1(1500);
ia_rsp->setT2(2600);
ia_rsp->setT1(HARDCODED_T1);
ia_rsp->setT2(HARDCODED_T2);
boost::shared_ptr<Option6IAAddr>
addr(new Option6IAAddr(D6O_IAADDR,
IOAddress("2001:db8:1234:5678::abcd"),
5000, 7000));
IOAddress(HARDCODED_LEASE),
HARDCODED_PREFERRED_LIFETIME,
HARDCODED_VALID_LIFETIME));
ia_rsp->addOption(addr);
reply->addOption(ia_rsp);
answer->addOption(ia_rsp);
}
}
}
// add client-id
boost::shared_ptr<Option> clientid = solicit->getOption(D6O_CLIENTID);
if (clientid) {
reply->addOption(clientid);
}
boost::shared_ptr<Pkt6>
Dhcpv6Srv::processSolicit(const boost::shared_ptr<Pkt6>& solicit) {
// add server-id
reply->addOption(getServerID());
return reply;
boost::shared_ptr<Pkt6> advertise(new Pkt6(DHCPV6_ADVERTISE,
solicit->getTransid()));
copyDefaultOptions(solicit, advertise);
appendDefaultOptions(solicit, advertise);
appendRequestedOptions(solicit, advertise);
assignLeases(solicit, advertise);
return (advertise);
}
boost::shared_ptr<Pkt6>
Dhcpv6Srv::processRequest(boost::shared_ptr<Pkt6> request) {
/// TODO: Implement processRequest() for real
boost::shared_ptr<Pkt6> reply = processSolicit(request);
reply->setType(DHCPV6_REPLY);
return reply;
Dhcpv6Srv::processRequest(const boost::shared_ptr<Pkt6>& request) {
boost::shared_ptr<Pkt6> reply(new Pkt6(DHCPV6_REPLY,
request->getTransid()));
copyDefaultOptions(request, reply);
appendDefaultOptions(request, reply);
appendRequestedOptions(request, reply);
assignLeases(request, reply);
return (reply);
}
boost::shared_ptr<Pkt6>
Dhcpv6Srv::processRenew(boost::shared_ptr<Pkt6> renew) {
Dhcpv6Srv::processRenew(const boost::shared_ptr<Pkt6>& renew) {
boost::shared_ptr<Pkt6> reply(new Pkt6(DHCPV6_REPLY,
renew->getTransid(),
Pkt6::UDP));
......@@ -207,7 +256,7 @@ Dhcpv6Srv::processRenew(boost::shared_ptr<Pkt6> renew) {
}
boost::shared_ptr<Pkt6>
Dhcpv6Srv::processRebind(boost::shared_ptr<Pkt6> rebind) {
Dhcpv6Srv::processRebind(const boost::shared_ptr<Pkt6>& rebind) {
boost::shared_ptr<Pkt6> reply(new Pkt6(DHCPV6_REPLY,
rebind->getTransid(),
Pkt6::UDP));
......@@ -215,7 +264,7 @@ Dhcpv6Srv::processRebind(boost::shared_ptr<Pkt6> rebind) {
}
boost::shared_ptr<Pkt6>
Dhcpv6Srv::processConfirm(boost::shared_ptr<Pkt6> confirm) {
Dhcpv6Srv::processConfirm(const boost::shared_ptr<Pkt6>& confirm) {
boost::shared_ptr<Pkt6> reply(new Pkt6(DHCPV6_REPLY,
confirm->getTransid(),
Pkt6::UDP));
......@@ -223,7 +272,7 @@ Dhcpv6Srv::processConfirm(boost::shared_ptr<Pkt6> confirm) {
}
boost::shared_ptr<Pkt6>
Dhcpv6Srv::processRelease(boost::shared_ptr<Pkt6> release) {
Dhcpv6Srv::processRelease(const boost::shared_ptr<Pkt6>& release) {
boost::shared_ptr<Pkt6> reply(new Pkt6(DHCPV6_REPLY,
release->getTransid(),
Pkt6::UDP));