[Kea-users] Select subnet based on reservation?

James Sumners JamesSumners at clayton.edu
Wed Feb 8 15:20:05 UTC 2017


Hmm. CentOS 6 or 7? If 7, systemd (ugh) should be respawning it when it dies.



From: Bryan Perry <bryan at sfcn.org><mailto:bryan at sfcn.org>
Date: February 8, 2017 at 10:18:20 AM
To: kea-users at lists.isc.org <kea-users at lists.isc.org><mailto:kea-users at lists.isc.org>
Subject:  Re: [Kea-users] Select subnet based on reservation?

James,

Since getting this library functional in May I have had roughly 6 to 8
instances where the Kea process just dies and goes away. I am not sure
why or if it is an issue with my library, kea itself or the CentOS
machine it's running on. After the first two instances of this happening
I wrote a simple script that is run as a cron job that checks Kea every
minute and if it has died restarts it. That was the stability problem.

On the shared subnet handling I have another scalability challenge in my
network. The upstream router that is acting as my DHCP relay will always
send the client request from its primary IP address. This even killed
regular DHCP lease responses from a second subnet since the request came
from the first subnet ID. In order to get around this I had to enable a
feature of that router that would try sending the DHCP requests from
each secondary IP address after a DHCP request failure on the primary
address. This works, but takes several seconds for the failure on the
primary and then the request on the secondary. The time delay gets
longer and longer the more subnets it has to get failures on while its
going down the list. That's a painful delay trying to get an IP address
on the network if you are in a subnet further down the list.

The last requirement I have that has also been handled via the library I
wrote for Kea has been logging lease assignments to a database for
historical purposes (summons, subpoenas, etc.).

Basically, until the shared subnet functionality works in Kea the way it
does in DHCPD I don't really have a choice but to use DHCPD and put
lease reservations for my static customers in the config file. Once upon
a time I was also able to use DHCPD's 'on commit' functionality to write
lease information to a database for historical tracking. I'll have to
resurrect that as well.

Good luck,
Bryan

On 2/8/2017 7:46 AM, James Sumners wrote:
> 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
>
_______________________________________________
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/a86f7bd9/attachment.htm>


More information about the Kea-users mailing list