2014 m. gegužės 14 d., trečiadienis

ISC DHCP option82

First we need to patch ISC DHCP server

Patch1. For ISP. Provides high performance using CLASS spawn

http://www.miquels.cistron.nl/isc-dhcpd/patch-server::01-subclass

diff -ruN ../../dhcp-3.0.5.orig/includes/dhcpd.h ./includes/dhcpd.h
--- ../../dhcp-3.0.5.orig/includes/dhcpd.h Wed May 17 22:16:59 2006
+++ ./includes/dhcpd.h Tue Jan 22 21:28:24 2008
@@ -551,6 +551,7 @@
   permit_class
  } type;
  struct class *class;
+ struct data_string hash_string;
 };
 
 struct pool {
diff -ruN ../../dhcp-3.0.5.orig/server/confpars.c ./server/confpars.c
--- ../../dhcp-3.0.5.orig/server/confpars.c Thu Jul 20 18:02:52 2006
+++ ./server/confpars.c Tue Jan 22 21:28:24 2008
@@ -1231,6 +1231,21 @@
 }
 #endif /* defined (FAILOVER_PROTOCOL) */
 
+
+/* Permit_same_class returns 1 if both permit structs contain
+ * the same class and the same hash_string aka subclass. */
+
+int permit_same_class(struct permit *plp, struct permit *prp)
+{
+ if (plp -> class != prp -> class)
+  return 0;
+ if (plp -> hash_string.len == 0 ||
+     plp -> hash_string.len != prp -> hash_string.len)
+  return 0;
+ return memcmp(plp -> hash_string.data,
+        prp -> hash_string.data, prp -> hash_string.len) == 0;
+}
+
 /* Permit_list_match returns 1 if every element of the permit list in lhs
    also appears in rhs.   Note that this doesn't by itself mean that the
    two lists are equal - to check for equality, permit_list_match has to
@@ -1250,7 +1265,7 @@
   for (prp = rhs; prp; prp = prp -> next) {
    if (prp -> type == plp -> type &&
        (prp -> type != permit_class ||
-        prp -> class == plp -> class)) {
+        permit_same_class(prp, plp))) {
     matched = 1;
     break;
    }
@@ -1275,6 +1290,7 @@
  int declaration = 0;
  isc_result_t status;
  struct lease *lpchain = (struct lease *)0, *lp;
+ struct data_string *dp;
 
  pool = (struct pool *)0;
  status = pool_allocate (&pool, MDL);
@@ -1440,10 +1456,39 @@
     }
     permit -> type = permit_class;
     permit -> class = (struct class *)0;
+
     find_class (&permit -> class, val, MDL);
-    if (!permit -> class)
+    if (!permit -> class) {
      parse_warn (cfile,
           "no such class: %s", val);
+     break;
+    }
+
+    dp = &permit -> hash_string;
+
+    token = peek_token (&val, (unsigned *)0, cfile);
+    if (token == STRING) {
+     token = next_token(&val,
+       &dp -> len, cfile);
+     dp -> buffer = (struct buffer *)0;
+     if (!buffer_allocate (&dp -> buffer,
+         dp -> len + 1, MDL)) {
+      free_permit(permit, MDL);
+      continue;
+     }
+     dp -> terminated = 1;
+     dp -> data = &dp -> buffer -> data [0];
+     memcpy ((char *)dp -> buffer -> data,
+      val, dp -> len + 1);
+    } else if (token == NUMBER_OR_NAME ||
+        token == NUMBER) {
+     memset (dp, 0, sizeof *dp);
+     if (!parse_cshl (dp, cfile)) {
+      free_permit(permit, MDL);
+      continue;
+     }
+    }
+
     break;
 
          default:
diff -ruN ../../dhcp-3.0.5.orig/server/dhcp.c ./server/dhcp.c
--- ../../dhcp-3.0.5.orig/server/dhcp.c Tue Aug 22 19:15:56 2006
+++ ./server/dhcp.c Tue Jan 22 21:28:24 2008
@@ -3807,12 +3807,28 @@
    
         case permit_class:
    for (i = 0; i < packet -> class_count; i++) {
-    if (p -> class == packet -> classes [i])
-     return 1;
-    if (packet -> classes [i] &&
-        packet -> classes [i] -> superclass &&
-        (packet -> classes [i] -> superclass ==
-         p -> class))
+    int r = 0;
+    struct class *pc = packet -> classes[i];
+
+    /* Does packet class or superclass match ? */
+    if (!p -> class || !pc)
+     continue;
+    if (p -> class == pc)
+     r = 1;
+    if (p -> class == pc -> superclass)
+     r = 1;
+
+    /* Does the subclass match as well ? */
+    if (r && p -> hash_string.len) {
+
+     if (p -> hash_string.len !=
+         pc -> hash_string.len ||
+         memcmp(p -> hash_string.data,
+         pc -> hash_string.data,
+         pc -> hash_string.len))
+       r = 0;
+    }
+    if (r)
      return 1;
    }
    break;
diff -ruN ../../dhcp-3.0.5.orig/server/salloc.c ./server/salloc.c
--- ../../dhcp-3.0.5.orig/server/salloc.c Wed Feb 22 23:43:27 2006
+++ ./server/salloc.c Tue Jan 22 21:28:24 2008
@@ -252,7 +252,10 @@
  const char *file;
  int line;
 {
- if (permit -> type == permit_class)
+ if (permit -> type == permit_class) {
   class_dereference (&permit -> class, MDL);
+  if (permit -> hash_string.buffer)
+   buffer_dereference(&permit -> hash_string.buffer, MDL);
+ }
  dfree (permit, file, line);
 }
 
 
After this DHCP dhcpd.conf will look like this: 

class "OPT82" {spawn with concat(substring(option agent.remote-id, 2,6),substring (option agent.circuit-id, 5, 1));} 

...
shared-network NET-192.168.33.0 { 

  subnet 192.168.33.0 netmask 255.255.255.0 {   option routers 192.168.33.254;    option subnet-mask 255.255.255.0; option broadcast-address 192.168.33.255 ; }

  pool { range 192.168.33.9; allow members of "OPT82" 00:1a:30:ad:60:00:23; deny dynamic bootp clients; }

 
After applying this patch we getting awesome performance: CPU dual-core Xeon, 30000 pools, lease-time 15min, DHCP rate about 80-100 PPS, CPU load: 2-3% 
Now client will be able to receive IP address based on only OPTION82. Everything look good, but:
As soon as you put into network new device you will receive "no free lease" log 
 
DHCPDISCOVER from 00:26:11:22:33:44 via 213.129.33.254: network NET-213.129.33.0: no free leases
 
There are few options:
  • Wait for the old lease expires 
  • Connect old device and RELEASE IP address
  • Clone MAC from old device (but wait where is the Option82 sharm?! )
      
Is it all? Nope! we have OMAPI isc-dhcp-server functionality, which give us ability to moderate dhcp lease file. Fine, all we need is free our DHCP lease. Everything is simple, BUT (one more BUT) how would you determine which lease to free? remember you have only log: 
DHCPDISCOVER from 00:26:11:22:33:44 via 213.129.33.254: network NET-213.129.33.0: no free leases


There only new MAC is avaliable, no old MAC, no IP address no Option82. 
What we gonna do? Patch, patch and patch. There is it PATCH number 2:

Patch2. Prints Option82 agent params in DHCPDISCOVER packet, if any exists

diff -ruN dhcp-4.2.3-P2+01/server/dhcp.c dhcp-4.2.3-P2/server/dhcp.c
--- dhcp-4.2.3-P2+01/server/dhcp.c 2012-04-02 22:47:00.838775000 +0200
+++ dhcp-4.2.3-P2/server/dhcp.c 2012-04-02 22:55:40.159751000 +0200
@@ -41,6 +41,12 @@
static void maybe_return_agent_options(struct packet *packet,
struct option_state *options);

+/*
+ * This setting makes the server log the agent.circuit-id
+ * and agent.remote-id options on DHCPDISCOVER.
+ */
+#define LOG_AGENT_OPTIONS 1
+
int outstanding_pings;

struct leasequeue *ackqueue_head, *ackqueue_tail;
@@ -262,6 +268,60 @@
lease_dereference (&lease, MDL);
}

+#if LOG_AGENT_OPTIONS
+static int get_agent_option(struct packet *packet, int opt,
+ struct data_string *data)
+{
+ struct data_string new;
+ struct option_cache *oc;
+ char *b;
+ int i;
+
+ oc = lookup_option (&agent_universe, packet -> options, opt);
+ if (!oc)
+ return 0;
+ memset (data, 0, sizeof(*data));
+ if (!evaluate_option_cache (data, packet, (struct lease *)0,
+ (struct client_state *)0,
+ packet -> options, (struct option_state *)0,
+ &global_scope, oc, MDL))
+ return 0;
+
+ if (data -> len == 0) {
+ data_string_forget (data, MDL);
+ return 0;
+ }
+
+ /* Printable ? */
+ for (i = 0; i < data -> len; i++)
+ if (data -> data[i] < 32 || data -> data[i] > 127)
+ break;
+ if (i == data -> len)
+ return 1;
+
+ /* Non-printable, return hex string */
+ memset (&new, 0, sizeof(new));
+ if (!buffer_allocate (&new.buffer, 3 * data -> len, MDL)) {
+ log_error ("get_agent_option: No memory");
+ data_string_forget (data, MDL);
+ return 0;
+ }
+ new.data = new.buffer -> data;
+ b = (char *)new.data;
+ for (i = 0; i < data -> len; i++) {
+ /* This is Safe. */
+ b += sprintf (b, "%x:", data -> data[i]);
+ }
+ b--;
+ new.len = b - (char *)new.data;
+ data_string_forget (data, MDL);
+ data_string_copy (data, &new, MDL);
+ data_string_forget (&new, MDL);
+
+ return 1;
+}
+#endif
+
void dhcpdiscover (packet, ms_nulltp)
struct packet *packet;
int ms_nulltp;
@@ -271,9 +331,14 @@
TIME when;
const char *s;
int peer_has_leases = 0;
+ char agentbuf[1024];
#if defined (FAILOVER_PROTOCOL)
dhcp_failover_state_t *peer;
#endif
+#if LOG_AGENT_OPTIONS
+ struct data_string data;
+ int l;
+#endif

find_lease (&lease, packet, packet -> shared_network,
0, &peer_has_leases, (struct lease *)0, MDL);
@@ -287,10 +352,25 @@
} else
s = (char *)0;

+ agentbuf[0] = 0;
+#if LOG_AGENT_OPTIONS
+ l = 0;
+ if (get_agent_option(packet, RAI_CIRCUIT_ID, &data)) {
+ l += snprintf (agentbuf + l, sizeof(agentbuf) - l,
+ "circuit-id %.*s ", data.len, data.data);
+ data_string_forget (&data, MDL);
+ }
+ if (get_agent_option(packet, RAI_REMOTE_ID, &data)) {
+ l += snprintf (agentbuf + l, sizeof(agentbuf) - l,
+ "remote-id %.*s ", data.len, data.data);
+ data_string_forget (&data, MDL);
+ }
+#endif
+
/* %Audit% This is log output. %2004.06.17,Safe%
* If we truncate we hope the user can get a hint from the log.
*/
- snprintf (msgbuf, sizeof msgbuf, "DHCPDISCOVER from %s %s%s%svia %s",
+ snprintf (msgbuf, sizeof msgbuf, "DHCPDISCOVER from %s %s%s%s%svia %s",
(packet -> raw -> htype
? print_hw_addr (packet -> raw -> htype,
packet -> raw -> hlen,
@@ -299,6 +379,7 @@
? print_hex_1(lease->uid_len, lease->uid, 60)
: "")),
s ? "(" : "", s ? s : "", s ? ") " : "",
+ agentbuf,
packet -> raw -> giaddr.s_addr
? inet_ntoa (packet -> raw -> giaddr)
: packet -> interface -> name);


after applying this patch DHCPDISCOVER will looks like this 

DHCPDISCOVER from 00:26:11:22:33:44 c-id 0:4:6:13:1:23 r-id 0:6:0:1a:30:ad:60:0 via 213.129.33.254: network NET-213.129.33.0: no free leases

you can see additional info: c-id 0:4:6:13:1:23 r-id 0:6:0:1a:30:ad:60:0, where c-id is circuit-id, r-id is remote-id, cute!


Log, log, log 

DHCP should put logs locally or on to the sepatare server, it is up to you
Next step - log analisys. We need to make some changes to syslog-ng.conf



source remote { udp( port(514) ); };
filter free_lease { program("^dhcpd$") and  match("no free leases" value("MESSAGE")) };
destination perl_dhcp { program("/usr/local/scripts/parse_log2.pl"); };
log { source(remote); filter( free_lease ); destination(perl_dhcp); };
So syslog-ng will catch dhcpd logs which contains "no free leases" string and put them intp /usr/local/scripts/parse_log2.pl analyzer



Parsing the log

Almost there. Few steps left.There is some sample PERL script which do following:
  • analyse, parse DHCP log, getting circuit-id, remote-id
  • Connects to MySQL DB and verify what is the current lease obtained for common Option82
  • Using OMAPI connect to DHCP server, and free current lease
#!/usr/bin/perl -n

s/^<\d{1,2}>//;
my $mac =     $1 if /from ((?:[0-9A-Fa-f]{2}[:-]){5}[0-9A-Fa-f]{2})/;
my $remote_id = $1 if /r-id (([^;]+))via/;
my $circuit_id =     $1 if /c-id (([^;]+))r-id/;

Some script for getting IP address fro MySQL
my $query = "SELECT ip FROM `dhcp` WHERE `switch`='$switch' and port=$port and sw_ven <> 'HP' order by ts desc";
    my $query_handle = $dbstore->prepare($query);
    $query_handle->execute() or die  "Unable to EXECUTE: $DBI::errstr\n ===\n$query \n ===\n";
    while (my @row = $query_handle->fetchrow_array()) {
         ($ip ) = @row;
  }






Use OMAPI for free current lease




open (OMSHELL, "|omshell") || die ("Unable to open omshell\n");
        print OMSHELL "server 192.168.95.105\n";
        print OMSHELL "port 7911\n";
        print OMSHELL "key dhcp_key \"KYS4h5dWtgv25rEWJhRTqw2U7kJvJZ/eHkuWSSF7rw8Z5adysqbChM/4z0bZxxIn0l5U30QXyfF+0mnMi0Y6FQ==\"\n";
        print OMSHELL "connect\n";
        print OMSHELL "new lease\n";
        #print OMSHELL "set hardware-address = $mac\n";
        print OMSHELL "set ip-address = ".$ip."\n";
        print OMSHELL "open\n";
        print OMSHELL "set ends = 0\n";
        print OMSHELL "update \n";
        close (OMSHELL) || die "Unable to close omshell.\n";

THE RESULT



============== getting IP address with MAC 00:26:9e:d9:34:ff ==============

May 14 09:15:42 dhcpserver dhcpd: DHCPREQUEST for 192.168.33.9 from 00:26:9e:d9:34:ff via 192.168.33.254
May 14 09:15:42 dhcpserver dhcpd: DHCPACK on 192.168.33.9 to 00:26:9e:d9:34:ff (PC23) via 192.168.33.254

============== Connect other device with new MAC 00:26:99:88:77:66 ==============

May 14 09:20:17 dhcpserver dhcpd: DHCPREQUEST for 192.168.33.9 from 00:26:99:88:77:66 via 192.168.33.254: lease 192.168.33.9 unavailable.
May 14 09:20:17 dhcpserver dhcpd: DHCPNAK on 192.168.33.9 to 00:26:99:88:77:66 via 192.168.33.254
May 14 09:20:17 dhcpserver dhcpd: DHCPDISCOVER from 00:26:99:88:77:66 c-id 0:4:6:13:1:23 r-id 0:6:0:1a:30:ad:60:0 via 192.168.33.254: networkET-192.168.33.0: no free leases

============== LEASE FREE's automatically ==============

May 14 09:20:17 dhcpserver dhcpd: lease 192.168.33.9 end changed from 1400049042 to 0
============== Device with MAC 00:26:99:88:77:66 get IP ==============

May 14 09:20:21 dhcpserver dhcpd: Lease for ==0:6:0:1a:30:ad:60:0==192.168.33.9 , is connected to interface 1/35 , VLAN 1555 , on switch 0:1a0:ad:60:0 , client MAC 0:26:99:88:77:66 , Vendor MSFT 5.0 Len: 0601a30ad600
May 14 09:20:21 dhcpserver dhcpd: DHCPDISCOVER from 00:26:99:88:77:66 c-id 0:4:6:13:1:23 r-id 0:6:0:1a:30:ad:60:0 via 192.168.33.254
May 14 09:20:21 dhcpserver dhcpd: DHCPOFFER on 192.168.33.9 to 00:26:99:88:77:66 (PC23) via 192.168.33.254
May 14 09:20:21 dhcpserver dhcpd: Lease for ==0:6:0:1a:30:ad:60:0==192.168.33.9 , is connected to interface 1/35 , VLAN 1555 , on switch 0:1a0:ad:60:0 , client MAC 0:26:99:88:77:66 , Vendor MSFT 5.0 Len: 0601a30ad600
May 14 09:20:21 dhcpserver dhcpd: DHCPREQUEST for 192.168.33.9 (192.168.95.171) from 00:26:99:88:77:66 (PC23) via 192.168.33.254
May 14 09:20:21 dhcpserver dhcpd: DHCPACK on 192.168.33.9 to 00:26:99:88:77:66 (PC23) via 192.168.33.254



MAGIC HAPPENS!