[Kea-users] DHCP4 MySQL Host Reservation Troubles

Bryan Perry bryan at sfcn.org
Fri May 13 16:14:06 UTC 2016


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);
};
}



More information about the Kea-users mailing list