<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<style>
body {
        font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
        padding:1em;
        margin:auto;
        background:#fefefe;
}

h1, h2, h3, h4, h5, h6 {
        font-weight: bold;
}

h1 {
        color: #000000;
        font-size: 28pt;
}

h2 {
        border-bottom: 1px solid #CCCCCC;
        color: #000000;
        font-size: 24px;
}

h3 {
        font-size: 18px;
}

h4 {
        font-size: 16px;
}

h5 {
        font-size: 14px;
}

h6 {
        color: #777777;
        background-color: inherit;
        font-size: 14px;
}

hr {
        height: 0.2em;
        border: 0;
        color: #CCCCCC;
        background-color: #CCCCCC;
    display: inherit;
}

p, blockquote, ul, ol, dl, li, table, pre {
        margin: 15px 0;
}

a, a:visited {
        color: #4183C4;
        background-color: inherit;
        text-decoration: none;
}

#message {
        border-radius: 6px;
        border: 1px solid #ccc;
        display:block;
        width:100%;
        height:60px;
        margin:6px 0px;
}

button, #ws {
        font-size: 12 pt;
        padding: 4px 6px;
        border-radius: 5px;
        border: 1px solid #bbb;
        background-color: #eee;
}

code, pre, #ws, #message {
        font-family: Monaco;
        font-size: 10pt;
        border-radius: 3px;
        background-color: #F8F8F8;
        color: inherit;
}

code {
        border: 1px solid #EAEAEA;
        margin: 0 2px;
        padding: 0 5px;
}

pre {
        border: 1px solid #CCCCCC;
        overflow: auto;
        padding: 4px 8px;
}

pre > code {
        border: 0;
        margin: 0;
        padding: 0;
}

#ws { background-color: #f8f8f8; }


.bloop_markdown table {
border-collapse: collapse;  
font-family: Helvetica, arial, freesans, clean, sans-serif;  
color: rgb(51, 51, 51);  
font-size: 15px; line-height: 25px;
padding: 0; }

.bloop_markdown table tr {
border-top: 1px solid #cccccc;
background-color: white;
margin: 0;
padding: 0; }
     
.bloop_markdown table tr:nth-child(2n) {
background-color: #f8f8f8; }

.bloop_markdown table tr th {
font-weight: bold;
border: 1px solid #cccccc;
margin: 0;
padding: 6px 13px; }

.bloop_markdown table tr td {
border: 1px solid #cccccc;
margin: 0;
padding: 6px 13px; }

.bloop_markdown table tr th :first-child, table tr td :first-child {
margin-top: 0; }

.bloop_markdown table tr th :last-child, table tr td :last-child {
margin-bottom: 0; }

.bloop_markdown blockquote{
  border-left: 4px solid #dddddd;
  padding: 0 15px;
  color: #777777; }
  blockquote > :first-child {
    margin-top: 0; }
  blockquote > :last-child {
    margin-bottom: 0; }

code, pre, #ws, #message {
    word-break: normal;
    word-wrap: normal;
}

hr {
    display: inherit;
}

.bloop_markdown :first-child {
    -webkit-margin-before: 0;
}

code, pre, #ws, #message {
    font-family: Menlo, Consolas, Liberation Mono, Courier, monospace;
}


.send { color:#77bb77; }
.server { color:#7799bb; }
.error { color:#AA0000; }</style>
</head>
<body style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;">
<div class="bloop_markdown">
<p>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.</p>
<p>What issues have you found that have lead you to conclude it is too unstable?</p>
<p></p>
</div>
<div class="bloop_original_html"><style>body{font-family:Helvetica,Arial;font-size:13px}</style>
<div id="bloop_customfont" style="font-family:Helvetica,Arial;font-size:13px; color: rgba(0,0,0,1.0); margin: 0px; line-height: auto;">
<br>
</div>
<br>
<div id="bloop_sign_1486565123860503808" class="bloop_sign"></div>
<br>
<p class="airmail_on">On February 8, 2017 at 9:38:18 AM, Bryan Perry (<a href="mailto:bryan@sfcn.org">bryan@sfcn.org</a>) wrote:</p>
<blockquote type="cite" class="clean_bq"><span>
<div>
<div></div>
<div>Hi James, <br>
<br>
At the risk of re-hashing this topic for everyone again... What you are <br>
facing is a very unfortunate shortcoming of Kea's ability to deal with <br>
shared subnets that I hope they solve in the future. I faced the same <br>
issue and took the approach of writing a custom library that uses Kea's <br>
hooks and intercepted the DHCP Discover, queried the database for lease <br>
reservations matching the MAC address and if found, changed the subnet <br>
ID to the correct ID and handed it back to Kea for processing. <br>
<br>
So yes, it can be done, but it is not trivial and very specific to your <br>
own implementation. I am running Kea this way now, but this hasn't been <br>
the most stable approach. This along with a few other issues related to <br>
shared subnet scenarios are unfortunately going to require that I move <br>
back to the standard ISC DHCPD. <br>
<br>
I am pasting below some of my final correspondence with a couple of the <br>
more knowledgeable Kea folks back in May when I got this running. The <br>
code I used to compile my library is also pasted in case it helps you <br>
see just how cumbersome of a task this was, or otherwise helps you in <br>
your project. It is certainly not anything I am an expert on or can <br>
provide technical support for, sorry, but I hope it helps. <br>
<br>
-Bryan <br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
Thomas, Marcin, <br>
<br>
With your help I have been able to get this working exactly as I needed <br>
it to. The subnet4_select callout checks my hosts table for a static <br>
reservation and, if found, updates the assigned subnet ID. Kea then <br>
picks up processing with the new subnet ID and can successfully assign <br>
the correct static IP address to the client. <br>
<br>
Although it's surely not the most elegant, I will paste in my code below <br>
in case it is helpful for anyone else dealing with shared subnets and <br>
static reservations. If you see anything wrong with the code or problems <br>
I may cause, I'm always open to feedback, but it seems to be working <br>
great right now. <br>
<br>
I also had the requirements to write some basic info out to a more human <br>
readable log as well as writing all IP address assignments out to a <br>
completely different database for required lease history tracking. I <br>
used the lease4_select callout for the lease history storage. I'll paste <br>
that code as well in case it's of any use to anyone else. <br>
<br>
Hopefully later versions of Kea will have some of this functionality <br>
built in. <br>
<br>
Thanks again for all your help. I couldn't have got this working without <br>
you guys. <br>
<br>
-Bryan <br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
// subnet4_select.cc <br>
<br>
#include <hooks/hooks.h> <br>
#include <dhcp/pkt4.h> <br>
#include <dhcpsrv/subnet.h> <br>
#include <dhcpsrv/mysql_connection.h> <br>
#include <algorithm/string.hpp> <br>
#include "library_common.h" <br>
#include <string> <br>
<br>
using namespace isc::dhcp; <br>
using namespace isc::hooks; <br>
using namespace std; <br>
<br>
// Define a structure for the mysql connection instance <br>
struct connection_details { <br>
const char *server; <br>
const char *user; <br>
const char *password; <br>
const char *database; <br>
}; <br>
<br>
MYSQL_RES *Result; // The mysql query results set variable <br>
<br>
// A function to establish the mysql connection <br>
MYSQL* mysql_connection_setup2(struct connection_details <br>
mysql_details) { <br>
// Create a mysql instance and initialize the variables <br>
MYSQL *connection = mysql_init(NULL); <br>
<br>
// Connect to the database with the details in the connection <br>
instance <br>
if (!mysql_real_connect(connection,mysql_details.server, <br>
mysql_details.user, mysql_details.password, mysql_details.database, 0, <br>
NULL, 0)) { <br>
printf("Conection error : %s\n", mysql_error(connection)); <br>
//exit(1); <br>
} <br>
return connection; <br>
} <br>
<br>
// A function to run the query and return the results set <br>
MYSQL_RES* mysql_perform_query2(MYSQL *connection, const char <br>
*sql_query) { <br>
// send the query to the database <br>
// cout << sql_query << " in function mysql_perform_query2" << endl; <br>
if (mysql_query(connection, sql_query)) <br>
{ <br>
printf("MySQL query error : %s\n", mysql_error(connection)); <br>
// exit(1); <br>
} <br>
Result = mysql_store_result(connection); <br>
int RowsReturned = mysql_num_rows( Result ); <br>
// cout << "NumRows in the function: " << RowsReturned << endl; <br>
// return mysql_use_result(connection); <br>
return Result; <br>
} <br>
<br>
extern "C" { <br>
<br>
// This callout is called at the "subnet4_select" hook. <br>
// We will intercept the request, check the MySQL hosts table <br>
// for reservations, and if found, set the correct subnet ID <br>
<br>
int subnet4_select(CalloutHandle& handle) { <br>
<br>
// A pointer to the packet is passed to the callout via a "boost" smart <br>
// pointer. The include file "pkt4.h" typedefs a pointer to the Pkt4 <br>
// object as Pkt4Ptr. Retrieve a pointer to the object. <br>
Pkt4Ptr query4_ptr; <br>
handle.getArgument("query4", query4_ptr); <br>
<br>
// Get a pointer to the subnet4 object <br>
Subnet4Ptr subnet4_ptr; <br>
handle.getArgument("subnet4", subnet4_ptr); <br>
<br>
// Get the collection of subnets from the callout argument set <br>
const isc::dhcp::Subnet4Collection* subnets; <br>
handle.getArgument("subnet4collection", subnets); <br>
<br>
// Get a pointer to the hardware address. <br>
HWAddrPtr hwaddr_ptr = query4_ptr->getHWAddr(); <br>
<br>
// Get the subnet ID from the initial request <br>
SubnetID subnetId = subnet4_ptr->getID(); <br>
<br>
// Exclude hardware type from hardware address string <br>
bool include_htype=false; <br>
string colonized_hwaddr = hwaddr_ptr->toText(include_htype); <br>
<br>
// Strip colons from colonized_hwaddr to get hwaddr <br>
string hwaddr = colonized_hwaddr; <br>
boost::erase_all(hwaddr, ":"); <br>
<br>
// Define some variables we will use <br>
int newSubnetId = 0; <br>
string ipaddr = "BLANK"; <br>
long RowsReturned; <br>
<br>
// cout << hwaddr << " wants subnet " << subnetId << "\n"; <br>
// Use a try...catch here to help prevent MySQL errors from killing <br>
the server. <br>
try { <br>
// Here is where we query the database and set new subnet ID if <br>
necessary <br>
MYSQL *conn2; // the connection <br>
MYSQL_RES *res2; // the results <br>
MYSQL_ROW row2; // the results row (line by line) <br>
<br>
struct connection_details mysqlD2; <br>
mysqlD2.server = "localhost"; // where the mysql database is <br>
mysqlD2.user = "********"; // the root user of mysql <br>
mysqlD2.password = "********"; // the password of the root user <br>
in mysql <br>
mysqlD2.database = "********"; // the databse to pick <br>
<br>
// Build the query string <br>
ostringstream ss2; <br>
ss2 << "select hex(dhcp_identifier), dhcp4_subnet_id, <br>
INET_NTOA(ipv4_address), host_id from hosts where hex(dhcp_identifier) = <br>
'" << hwaddr << "' limit 1"; <br>
string ss2_str = ss2.str(); <br>
const char *full_query2 = ss2_str.c_str(); <br>
<br>
// Connect to the mysql database <br>
conn2 = mysql_connection_setup2(mysqlD2); <br>
<br>
// Assign the results returned to res2 if valid <br>
if (res2 = mysql_perform_query2(conn2, full_query2)) { <br>
// Get the number of rows in the results set. Should be 0 or 1 <br>
int rCount = mysql_num_rows(res2); <br>
// If we got more than 0 rows in the results then we found a <br>
reservation for that client <br>
if (rCount > 0) { <br>
while ((row2 = mysql_fetch_row(res2)) !=NULL) { <br>
// Convert the subnet ID to an integer for use further on <br>
string input(row2[1]); stringstream SS(input); SS >> <br>
newSubnetId; <br>
// Handle a NULL ipv4_address field <br>
if (row2[2]) { <br>
ipaddr = row2[2]; <br>
} // end if <br>
// Log that we found a reservation <br>
leaselog << "Static reservation " << ipaddr << " found for <br>
" << row2[0] << " on row " << row2[3] << ", subnet " << row2[1] << endl; <br>
} <br>
} // end if <br>
// cout << "New subnet ID: " << newSubnetId << endl; <br>
<br>
// Clean up the database result set <br>
mysql_free_result(res2); <br>
<br>
// Clean up the database connectio <br>
mysql_close(conn2); <br>
} // end if <br>
else { <br>
// Throw an error if something went wrong in the mysql lookup <br>
leaselog << "MySQL query failed in lookup for " << hwaddr << " -- <br>
Results processing skipped." << endl; <br>
} // end else <br>
<br>
} catch (...) { <br>
// Throw an error if something more serious happened <br>
leaselog << "# ERR: Caught an error in subnet4_select.cc" << endl; <br>
} <br>
<br>
<br>
<br>
// Next, if we have to change the subnet ID, we iterate through the <br>
collection <br>
// of subnets, looking for the ID we want and then set it. <br>
if ((newSubnetId != 0) && (subnetId != newSubnetId)) { <br>
for (int i = 0; i < subnets->size(); ++i) { <br>
Subnet4Ptr new_subnet = (*subnets)[i]; <br>
if (new_subnet->getID() == newSubnetId) { <br>
// id matched so replace the selected subnet by <br>
// setting the "subnet4" callout argument with <br>
// the new subnet <br>
handle.setArgument("subnet4", new_subnet); <br>
break; <br>
} <br>
} <br>
} <br>
<br>
flush(leaselog); <br>
return (0); <br>
<br>
}; <br>
<br>
} <br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
// lease4_select.cc <br>
<br>
#include <hooks/hooks.h> <br>
#include <dhcp/pkt4.h> <br>
#include <dhcpsrv/lease.h> <br>
#include <dhcpsrv/mysql_connection.h> <br>
#include "library_common.h" <br>
#include <string> <br>
<br>
using namespace isc::dhcp; <br>
using namespace isc::hooks; <br>
using namespace std; <br>
<br>
// Define a structure for the mysql connection instance <br>
struct connection_details { <br>
const char *server; <br>
const char *user; <br>
const char *password; <br>
const char *database; <br>
}; <br>
<br>
// A function to establish the mysql connection <br>
MYSQL* mysql_connection_setup(struct connection_details <br>
mysql_details) { <br>
// first of all create a mysql instance and initialize the <br>
variables within <br>
MYSQL *connection = mysql_init(NULL); <br>
<br>
// connect to the database with the details attached. <br>
if (!mysql_real_connect(connection,mysql_details.server, <br>
mysql_details.user, mysql_details.password, mysql_details.database, 0, <br>
NULL, 0)) { <br>
printf("Conection error : %s\n", mysql_error(connection)); <br>
// exit(1); <br>
} <br>
return connection; <br>
} <br>
<br>
// A function to run the query and return the results set <br>
MYSQL_RES* mysql_perform_query(MYSQL *connection, const char <br>
*sql_query) { <br>
// send the query to the database <br>
// cout << sql_query << " in function mysql_perform_query" << endl; <br>
if (mysql_query(connection, sql_query)) <br>
{ <br>
printf("MySQL query error : %s\n", mysql_error(connection)); <br>
// exit(1); <br>
} <br>
return mysql_use_result(connection); <br>
} <br>
<br>
<br>
extern "C" { <br>
<br>
// This callout is called at the "lease4_select" hook. <br>
int lease4_select(CalloutHandle& handle) { <br>
<br>
// A pointer to the object is passed to the callout via a "boost" smart <br>
// pointer. The include file "lease.h" typedefs a pointer to the Lease4 <br>
// object as Lease4Ptr. Retrieve a pointer to the object. <br>
Lease4Ptr lease4_ptr; <br>
handle.getArgument("lease4", lease4_ptr); <br>
bool isFake; <br>
handle.getArgument("fake_allocation", isFake); <br>
<br>
// Get a pointer to the hardware address. <br>
HWAddrPtr hwaddr_ptr = lease4_ptr->hwaddr_; <br>
<br>
string ipaddr = lease4_ptr->addr_.toText(); <br>
int subnetId = lease4_ptr->subnet_id_; <br>
<br>
bool include_htype=false; <br>
string hwaddr = hwaddr_ptr->toText(include_htype); <br>
<br>
// If it is DHCPACK and not just DHCPOFFER, write to log and database <br>
if (!isFake) { <br>
<br>
leaselog << "ASSIGNED: " << ipaddr << " to " << hwaddr << " <br>
subnetID " << subnetId << "\n"; <br>
<br>
// Use a try...catch here to help prevent MySQL errors from killing <br>
the server. <br>
try { <br>
// Here is where we query the database and set new subnet ID if <br>
necessary <br>
MYSQL *conn; // the connection <br>
MYSQL_RES *res; // the results <br>
MYSQL_ROW row; // the results row (line by line) <br>
<br>
struct connection_details mysqlD; <br>
mysqlD.server = "localhost"; // where the mysql database is <br>
mysqlD.user = "********"; // the root user of mysql <br>
mysqlD.password = "********"; // the password of the root user in <br>
mysql <br>
mysqlD.database = "********"; // the databse to pick <br>
<br>
// connect to the mysql database <br>
conn = mysql_connection_setup(mysqlD); <br>
<br>
// Build the query <br>
std::stringstream ss; <br>
ss << "INSERT INTO lease_history VALUES ('" << ipaddr << "', '" << <br>
hwaddr << "', '" << subnetId << "')"; <br>
const char *full_query = ss.str().c_str(); <br>
// leaselog << full_query << endl; <br>
<br>
// assign the results return to the MYSQL_RES pointer <br>
res = mysql_perform_query(conn, full_query); <br>
<br>
// printf("MySQL Tables in mysql database:\n"); <br>
// while ((row = mysql_fetch_row(res)) !=NULL) <br>
// printf("%s\n", row[0]); <br>
<br>
/* clean up the database result set */ <br>
mysql_free_result(res); <br>
/* clean up the database link */ <br>
mysql_close(conn); <br>
} catch (...) { <br>
// Throw an error if something bad happened <br>
leaselog << "# ERR: Caught an error in lease4_select.cc" << endl; <br>
} <br>
<br>
<br>
} //End if (!isFake) <br>
<br>
flush(leaselog); <br>
return (0); <br>
}; <br>
} <br>
<br>
<br>
<br>
On 2/8/2017 6:22 AM, James Sumners wrote: <br>
> Before I move on, would the following be available to a hook?: <br>
> <br>
> 1. The subnet identifiers for each configured subnet <br>
> 2. The database of host reservations <br>
> <br>
> Get Outlook for iOS <https://aka.ms/o0ukef> <br>
> <br>
> ------------------------------------------------------------------------ <br>
> *From:* Tomek Mrugalski <tomasz@isc.org> <br>
> *Sent:* Tuesday, February 7, 2017 3:39:01 PM <br>
> *To:* James Sumners; kea-users@lists.isc.org <br>
> *Subject:* Re: [Kea-users] Select subnet based on reservation? <br>
> <br>
> W dniu 07.02.2017 o 21:31, James Sumners pisze: <br>
>> 1.3? 1.2 is too far off. Sadly, I’m just going to have to go back to <br>
>> looking for alternatives. That’s a shame, <br>
> Sorry to hear that. We already have our plans for 1.2 and we can't add <br>
> new features less than 3 months before the release. The shared subnets <br>
> scenario is not a trivial feature to implement, as it affects the logic <br>
> in many places. <br>
> <br>
>> because I was really liking Kea. <br>
> Thanks for your kind words. I hope you'll take a look at Kea again some <br>
> time in the future. <br>
> <br>
> Tomek <br>
> <br>
> <br>
> <br>
> <br>
> <br>
> _______________________________________________ <br>
> Kea-users mailing list <br>
> Kea-users@lists.isc.org <br>
> https://lists.isc.org/mailman/listinfo/kea-users <br>
> <br>
_______________________________________________ <br>
Kea-users mailing list <br>
Kea-users@lists.isc.org <br>
https://lists.isc.org/mailman/listinfo/kea-users <br>
</div>
</div>
</span></blockquote>
</div>
<div class="bloop_markdown">
<p></p>
</div>
</body>
</html>