[Kea-users] Select subnet based on reservation?

Bryan Perry bryan at sfcn.org
Wed Feb 8 14:38:05 UTC 2017


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
>



More information about the Kea-users mailing list