[Kea-users] Select subnet based on reservation?

Bryan Perry bryan at sfcn.org
Wed Feb 8 15:21:27 UTC 2017


CentOS Linux release 7.2.1511 (Core)

On 2/8/2017 8:20 AM, James Sumners wrote:
> 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
>



More information about the Kea-users mailing list