[Kea-users] Select subnet based on reservation?

James Sumners JamesSumners at clayton.edu
Wed Feb 8 14:46:43 UTC 2017


Thank you Bryan for chiming in. That is exactly what I am thinking of doing, and your code is a big help. I was just hoping to have a Kea API for accessing the database instead of doing it directly.

What issues have you found that have lead you to conclude it is too unstable?




On February 8, 2017 at 9:38:18 AM, Bryan Perry (bryan at sfcn.org<mailto:bryan at sfcn.org>) wrote:

Hi James,

At the risk of re-hashing this topic for everyone again... What you are
facing is a very unfortunate shortcoming of Kea's ability to deal with
shared subnets that I hope they solve in the future. I faced the same
issue and took the approach of writing a custom library that uses Kea's
hooks and intercepted the DHCP Discover, queried the database for lease
reservations matching the MAC address and if found, changed the subnet
ID to the correct ID and handed it back to Kea for processing.

So yes, it can be done, but it is not trivial and very specific to your
own implementation. I am running Kea this way now, but this hasn't been
the most stable approach. This along with a few other issues related to
shared subnet scenarios are unfortunately going to require that I move
back to the standard ISC DHCPD.

I am pasting below some of my final correspondence with a couple of the
more knowledgeable Kea folks back in May when I got this running. The
code I used to compile my library is also pasted in case it helps you
see just how cumbersome of a task this was, or otherwise helps you in
your project. It is certainly not anything I am an expert on or can
provide technical support for, sorry, but I hope it helps.

-Bryan








Thomas, Marcin,

With your help I have been able to get this working exactly as I needed
it to. The subnet4_select callout checks my hosts table for a static
reservation and, if found, updates the assigned subnet ID. Kea then
picks up processing with the new subnet ID and can successfully assign
the correct static IP address to the client.

Although it's surely not the most elegant, I will paste in my code below
in case it is helpful for anyone else dealing with shared subnets and
static reservations. If you see anything wrong with the code or problems
I may cause, I'm always open to feedback, but it seems to be working
great right now.

I also had the requirements to write some basic info out to a more human
readable log as well as writing all IP address assignments out to a
completely different database for required lease history tracking. I
used the lease4_select callout for the lease history storage. I'll paste
that code as well in case it's of any use to anyone else.

Hopefully later versions of Kea will have some of this functionality
built in.

Thanks again for all your help. I couldn't have got this working without
you guys.

-Bryan







// subnet4_select.cc

#include <hooks/hooks.h>
#include <dhcp/pkt4.h>
#include <dhcpsrv/subnet.h>
#include <dhcpsrv/mysql_connection.h>
#include <algorithm/string.hpp>
#include "library_common.h"
#include <string>

using namespace isc::dhcp;
using namespace isc::hooks;
using namespace std;

// Define a structure for the mysql connection instance
struct connection_details {
const char *server;
const char *user;
const char *password;
const char *database;
};

MYSQL_RES *Result; // The mysql query results set variable

// A function to establish the mysql connection
MYSQL* mysql_connection_setup2(struct connection_details
mysql_details) {
// Create a mysql instance and initialize the variables
MYSQL *connection = mysql_init(NULL);

// Connect to the database with the details in the connection
instance
if (!mysql_real_connect(connection,mysql_details.server,
mysql_details.user, mysql_details.password, mysql_details.database, 0,
NULL, 0)) {
printf("Conection error : %s\n", mysql_error(connection));
//exit(1);
}
return connection;
}

// A function to run the query and return the results set
MYSQL_RES* mysql_perform_query2(MYSQL *connection, const char
*sql_query) {
// send the query to the database
// cout << sql_query << " in function mysql_perform_query2" << endl;
if (mysql_query(connection, sql_query))
{
printf("MySQL query error : %s\n", mysql_error(connection));
// exit(1);
}
Result = mysql_store_result(connection);
int RowsReturned = mysql_num_rows( Result );
// cout << "NumRows in the function: " << RowsReturned << endl;
// return mysql_use_result(connection);
return Result;
}

extern "C" {

// This callout is called at the "subnet4_select" hook.
// We will intercept the request, check the MySQL hosts table
// for reservations, and if found, set the correct subnet ID

int subnet4_select(CalloutHandle& handle) {

// A pointer to the packet is passed to the callout via a "boost" smart
// pointer. The include file "pkt4.h" typedefs a pointer to the Pkt4
// object as Pkt4Ptr. Retrieve a pointer to the object.
Pkt4Ptr query4_ptr;
handle.getArgument("query4", query4_ptr);

// Get a pointer to the subnet4 object
Subnet4Ptr subnet4_ptr;
handle.getArgument("subnet4", subnet4_ptr);

// Get the collection of subnets from the callout argument set
const isc::dhcp::Subnet4Collection* subnets;
handle.getArgument("subnet4collection", subnets);

// Get a pointer to the hardware address.
HWAddrPtr hwaddr_ptr = query4_ptr->getHWAddr();

// Get the subnet ID from the initial request
SubnetID subnetId = subnet4_ptr->getID();

// Exclude hardware type from hardware address string
bool include_htype=false;
string colonized_hwaddr = hwaddr_ptr->toText(include_htype);

// Strip colons from colonized_hwaddr to get hwaddr
string hwaddr = colonized_hwaddr;
boost::erase_all(hwaddr, ":");

// Define some variables we will use
int newSubnetId = 0;
string ipaddr = "BLANK";
long RowsReturned;

// cout << hwaddr << " wants subnet " << subnetId << "\n";
// Use a try...catch here to help prevent MySQL errors from killing
the server.
try {
// Here is where we query the database and set new subnet ID if
necessary
MYSQL *conn2; // the connection
MYSQL_RES *res2; // the results
MYSQL_ROW row2; // the results row (line by line)

struct connection_details mysqlD2;
mysqlD2.server = "localhost"; // where the mysql database is
mysqlD2.user = "********"; // the root user of mysql
mysqlD2.password = "********"; // the password of the root user
in mysql
mysqlD2.database = "********"; // the databse to pick

// Build the query string
ostringstream ss2;
ss2 << "select hex(dhcp_identifier), dhcp4_subnet_id,
INET_NTOA(ipv4_address), host_id from hosts where hex(dhcp_identifier) =
'" << hwaddr << "' limit 1";
string ss2_str = ss2.str();
const char *full_query2 = ss2_str.c_str();

// Connect to the mysql database
conn2 = mysql_connection_setup2(mysqlD2);

// Assign the results returned to res2 if valid
if (res2 = mysql_perform_query2(conn2, full_query2)) {
// Get the number of rows in the results set. Should be 0 or 1
int rCount = mysql_num_rows(res2);
// If we got more than 0 rows in the results then we found a
reservation for that client
if (rCount > 0) {
while ((row2 = mysql_fetch_row(res2)) !=NULL) {
// Convert the subnet ID to an integer for use further on
string input(row2[1]); stringstream SS(input); SS >>
newSubnetId;
// Handle a NULL ipv4_address field
if (row2[2]) {
ipaddr = row2[2];
} // end if
// Log that we found a reservation
leaselog << "Static reservation " << ipaddr << " found for
" << row2[0] << " on row " << row2[3] << ", subnet " << row2[1] << endl;
}
} // end if
// cout << "New subnet ID: " << newSubnetId << endl;

// Clean up the database result set
mysql_free_result(res2);

// Clean up the database connectio
mysql_close(conn2);
} // end if
else {
// Throw an error if something went wrong in the mysql lookup
leaselog << "MySQL query failed in lookup for " << hwaddr << " --
Results processing skipped." << endl;
} // end else

} catch (...) {
// Throw an error if something more serious happened
leaselog << "# ERR: Caught an error in subnet4_select.cc" << endl;
}



// Next, if we have to change the subnet ID, we iterate through the
collection
// of subnets, looking for the ID we want and then set it.
if ((newSubnetId != 0) && (subnetId != newSubnetId)) {
for (int i = 0; i < subnets->size(); ++i) {
Subnet4Ptr new_subnet = (*subnets)[i];
if (new_subnet->getID() == newSubnetId) {
// id matched so replace the selected subnet by
// setting the "subnet4" callout argument with
// the new subnet
handle.setArgument("subnet4", new_subnet);
break;
}
}
}

flush(leaselog);
return (0);

};

}








// lease4_select.cc

#include <hooks/hooks.h>
#include <dhcp/pkt4.h>
#include <dhcpsrv/lease.h>
#include <dhcpsrv/mysql_connection.h>
#include "library_common.h"
#include <string>

using namespace isc::dhcp;
using namespace isc::hooks;
using namespace std;

// Define a structure for the mysql connection instance
struct connection_details {
const char *server;
const char *user;
const char *password;
const char *database;
};

// A function to establish the mysql connection
MYSQL* mysql_connection_setup(struct connection_details
mysql_details) {
// first of all create a mysql instance and initialize the
variables within
MYSQL *connection = mysql_init(NULL);

// connect to the database with the details attached.
if (!mysql_real_connect(connection,mysql_details.server,
mysql_details.user, mysql_details.password, mysql_details.database, 0,
NULL, 0)) {
printf("Conection error : %s\n", mysql_error(connection));
// exit(1);
}
return connection;
}

// A function to run the query and return the results set
MYSQL_RES* mysql_perform_query(MYSQL *connection, const char
*sql_query) {
// send the query to the database
// cout << sql_query << " in function mysql_perform_query" << endl;
if (mysql_query(connection, sql_query))
{
printf("MySQL query error : %s\n", mysql_error(connection));
// exit(1);
}
return mysql_use_result(connection);
}


extern "C" {

// This callout is called at the "lease4_select" hook.
int lease4_select(CalloutHandle& handle) {

// A pointer to the object is passed to the callout via a "boost" smart
// pointer. The include file "lease.h" typedefs a pointer to the Lease4
// object as Lease4Ptr. Retrieve a pointer to the object.
Lease4Ptr lease4_ptr;
handle.getArgument("lease4", lease4_ptr);
bool isFake;
handle.getArgument("fake_allocation", isFake);

// Get a pointer to the hardware address.
HWAddrPtr hwaddr_ptr = lease4_ptr->hwaddr_;

string ipaddr = lease4_ptr->addr_.toText();
int subnetId = lease4_ptr->subnet_id_;

bool include_htype=false;
string hwaddr = hwaddr_ptr->toText(include_htype);

// If it is DHCPACK and not just DHCPOFFER, write to log and database
if (!isFake) {

leaselog << "ASSIGNED: " << ipaddr << " to " << hwaddr << "
subnetID " << subnetId << "\n";

// Use a try...catch here to help prevent MySQL errors from killing
the server.
try {
// Here is where we query the database and set new subnet ID if
necessary
MYSQL *conn; // the connection
MYSQL_RES *res; // the results
MYSQL_ROW row; // the results row (line by line)

struct connection_details mysqlD;
mysqlD.server = "localhost"; // where the mysql database is
mysqlD.user = "********"; // the root user of mysql
mysqlD.password = "********"; // the password of the root user in
mysql
mysqlD.database = "********"; // the databse to pick

// connect to the mysql database
conn = mysql_connection_setup(mysqlD);

// Build the query
std::stringstream ss;
ss << "INSERT INTO lease_history VALUES ('" << ipaddr << "', '" <<
hwaddr << "', '" << subnetId << "')";
const char *full_query = ss.str().c_str();
// leaselog << full_query << endl;

// assign the results return to the MYSQL_RES pointer
res = mysql_perform_query(conn, full_query);

// printf("MySQL Tables in mysql database:\n");
// while ((row = mysql_fetch_row(res)) !=NULL)
// printf("%s\n", row[0]);

/* clean up the database result set */
mysql_free_result(res);
/* clean up the database link */
mysql_close(conn);
} catch (...) {
// Throw an error if something bad happened
leaselog << "# ERR: Caught an error in lease4_select.cc" << endl;
}


} //End if (!isFake)

flush(leaselog);
return (0);
};
}



On 2/8/2017 6:22 AM, James Sumners wrote:
> Before I move on, would the following be available to a hook?:
>
> 1. The subnet identifiers for each configured subnet
> 2. The database of host reservations
>
> Get Outlook for iOS <https://aka.ms/o0ukef>
>
> ------------------------------------------------------------------------
> *From:* Tomek Mrugalski <tomasz at isc.org>
> *Sent:* Tuesday, February 7, 2017 3:39:01 PM
> *To:* James Sumners; kea-users at lists.isc.org
> *Subject:* Re: [Kea-users] Select subnet based on reservation?
>
> W dniu 07.02.2017 o 21:31, James Sumners pisze:
>> 1.3? 1.2 is too far off. Sadly, I’m just going to have to go back to
>> looking for alternatives. That’s a shame,
> Sorry to hear that. We already have our plans for 1.2 and we can't add
> new features less than 3 months before the release. The shared subnets
> scenario is not a trivial feature to implement, as it affects the logic
> in many places.
>
>> because I was really liking Kea.
> Thanks for your kind words. I hope you'll take a look at Kea again some
> time in the future.
>
> Tomek
>
>
>
>
>
> _______________________________________________
> Kea-users mailing list
> Kea-users at lists.isc.org
> https://lists.isc.org/mailman/listinfo/kea-users
>
_______________________________________________
Kea-users mailing list
Kea-users at lists.isc.org
https://lists.isc.org/mailman/listinfo/kea-users
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.isc.org/pipermail/kea-users/attachments/20170208/e5dd820e/attachment.htm>


More information about the Kea-users mailing list