Dynamische IPs mit DHCP

Immer wieder benötigt man im Privatkunden-Umfeld die Möglichkeit die Zuweisung der IP Adressen zu dynamisieren. Hier ist der Hauptgrund, daß die Kunden darauf konditioniert wurden und sich ohne regelmäßige Adreßwechsel unsicher fühlen. Erfolgreiches Marketing eben. Muß man halt haben.

Standardsoftware

Wir setzen auf den ISC-DHCP. Und der ist bezüglich der IP Vergabe sehr konservativ. Solange auch nur eine Anhaltspunkt gefunden werden kann, daß der Client die spezielle IP bekommen müßte, wird er die bekommen. Gründe sind obskure Historien, schon mal gesehenen MAC Adressen oder ganz einfach die Anfrage des Clients, diese IP zu erhalten.

Die Dynamisierung besteht im Prinzip aus zwei Schritten.

  • Zum einen darf ein Client seine vorher mal benutzte IP nicht erneut angeboten (DISCOVER) bekommen.
  • Zum anderen darf ein Client eine Lease mit einer zugewiesenen IP ab einem bestimmten Zeitpunkt nicht mehr verlängert (REQUEST) bekommen.

Konsequenterweise sind das zwei getrennte, poolspezifische Optionen:

  • avoid-reuse bool sorgt dafür, daß ein Client jedesmal eine andere IP angeboten bekommt, wenn er keine gültige IP hat. Das gestattet einen Adreßwechsel jedesmal, wenn der Client rebootet oder neue IPs anfordert.
  • force-nack hour minute dagegen sorgt dafür, daß ein Client zu einem bestimmten Zeitpunkt seine aktuell zugewiesene IP verliert. Überstreicht eine Lease den definierten Zeitpunkt, wird die niemals verlängert. Der Client muß dann nach einer neuen IP fragen.

Die Optionen können unabhängig voneinander agieren.

Es ist also möglich, nur den Adreßwechsel zu erzwingen, nicht aber die Zwangstrennung du8rchzuführen. Diese quasi-statische Zuordnung ist im Zusammenhang mit Triple-Play wichtig, wenn der ISP erhebliche Risiken eingehen, falls er ein wichtiges Telefonat (z.B. einen Notruf) oder eine Sportübertragung (Fußball, SuperBowl) unterbricht.

Es ist ebenso möglich, nur die Zwangstrennung zu aktivieren, falls das durch Beschränkungen der Abrechnungssoftware oder andere interne Mechanismen erforderlich ist.

Umsetzung

Zuerst einmal brauchtes ein paar Token, die beide Funktionen kennzeichnen sollen.

diff -pbBru ../ORIGINAL/includes/dhcpd.h includes/dhcpd.h
--- ../ORIGINAL/includes/dhcpd.h        2011-07-09 00:56:26.000000000 +0200
+++ includes/dhcpd.h    2012-03-06 15:54:40.000000000 +0100
@@ -713,6 +713,9 @@ struct lease_state {
 # define SV_LDAP_TLS_RANDFILE           77
 #endif
 #endif
+/* private options */
+#define SV_AVOID_REUSE                 200
+#define SV_FORCE_NACK                  201
 
 #if !defined (DEFAULT_PING_TIMEOUT)
 # define DEFAULT_PING_TIMEOUT 1
diff -pbBru ../ORIGINAL/includes/dhctoken.h includes/dhctoken.h
--- ../ORIGINAL/includes/dhctoken.h     2011-05-12 14:02:47.000000000 +0200
+++ includes/dhctoken.h 2012-03-06 15:54:06.000000000 +0100
@@ -362,6 +362,9 @@ enum dhcp_token {
        REWIND = 663,
        INITIAL_DELAY = 664,
        GETHOSTBYNAME = 665
+       /* Private usage. Prepend COMMA to keep patches context independant */
+     ,  AVOID_REUSE = 1000
+     ,  FORCE_NACK = 1001
 };
 
 #define is_identifier(x)       ((x) >= FIRST_TOKEN &&  \

Als nächstes sind diese Werte pro Pool aus der Konfiguration zu parsen:

diff -pbBru ../ORIGINAL/server/stables.c server/stables.c
--- ../ORIGINAL/server/stables.c        2011-05-20 16:21:11.000000000 +0200
+++ server/stables.c    2012-03-06 15:27:42.000000000 +0100
@@ -266,6 +266,8 @@ static struct option server_options[] = 
        { "ldap-tls-randfile", "t",             &server_universe,  77, 1 },
 #endif /* LDAP_USE_SSL */
 #endif /* LDAP_CONFIGURATION */
+       { "avoid-reuse", "f",                   &server_universe,  SV_AVOID_REUSE, 1 },
+       { "force-nack", "BB",                   &server_universe,  SV_FORCE_NACK, 1 },
        { NULL, NULL, NULL, 0, 0 }
 };
diff -pbBru ../ORIGINAL/common/conflex.c common/conflex.c
--- ../ORIGINAL/common/conflex.c        2011-05-11 16:20:59.000000000 +0200
+++ ./common/conflex.c  2012-03-06 15:56:22.000000000 +0100
@@ -782,6 +782,8 @@ intern(char *atom, enum dhcp_token dfv) 
                                return AUTO_PARTNER_DOWN;
                        break;
                }
+               if (!strcasecmp(atom + 1, "void-reuse"))
+                       return AVOID_REUSE;
                break;
              case 'b':
                if (!strcasecmp (atom + 1, "ackup"))
@@ -986,6 +988,8 @@ intern(char *atom, enum dhcp_token dfv) 
                        return FIXED_PREFIX6;
                if (!strcasecmp (atom + 1, "ddi"))
                        return TOKEN_FDDI;
+               if (!strcasecmp(atom + 1, "orce-nack"))
+                       return FORCE_NACK;
                if (!strcasecmp (atom + 1, "ormerr"))
                        return NS_FORMERR;
                if (!strcasecmp (atom + 1, "unction"))

Und dann braucht es noch Funktionalität. Dazu werden Hooks in die betreffenden Funktionen eingepaßt. Diese Hooks ermitteln, ob die betreffende Funktionalität jetzt im Moment für diese Anfrage aktiviert werden soll oder nicht.

diff -pbBru ../ORIGINAL/server/dhcp.c server/dhcp.c
--- ../ORIGINAL/server/dhcp.c   2011-07-20 00:22:49.000000000 +0200
+++ server/dhcp.c       2012-03-16 11:33:11.000000000 +0100
@@ -40,6 +40,8 @@
 static void commit_leases_ackout(void *foo);
 static void maybe_return_agent_options(struct packet *packet,
                                       struct option_state *options);
+static int avoid_reuse(struct packet *packet, struct lease * lease);
+static int force_nack(struct packet *packet, struct lease * lease);
 
 int outstanding_pings; 

Die Funktion von avoid-reuse besteht darin, jede zuvor gefundenen Lease während der DISCOVER Verarbeitung zu verwerfen. Deswegen steht sie am Ende aller Lease-Ermittlungen. Diese Platzierung gestattet es, alle anderen Funktionen unverändert zu belassen.

diff -pbBru ../ORIGINAL/server/dhcp.c server/dhcp.c
--- ../ORIGINAL/server/dhcp.c 2011-07-20 00:22:49.000000000 +0200
+++ server/dhcp.c 2012-03-16 11:33:11.000000000 +0100
@@ -341,6 +343,18 @@ void dhcpdiscover (packet, ms_nulltp)
                }
        }
 #endif
+       /*
+        * Special handling to insist on new IPs whenever possible
+        */
+       if (avoid_reuse(packet, lease)) {
+          log_info ("Avoid reuse of old lease %s", piaddr (lease -> ip_addr));
+          if(lease -> ends > cur_time)
+            dissociate_lease (lease);   /* Free lease to enable reuse. */
+          lease_dereference (&lease, MDL);
+          if(lease)
+            log_error ("Lease %s can't be avoided, it's still referenced.",
+                       piaddr (lease -> ip_addr));
+       }
 
        /* If we didn't find a lease, try to allocate one... */
        if (!lease) {

Die Funktion von force-nack dagegen besteht darin, die Verarbeitung des REQUEST zu unterbinden.

diff -pbBru ../ORIGINAL/server/dhcp.c server/dhcp.c
--- ../ORIGINAL/server/dhcp.c   2011-07-20 00:22:49.000000000 +0200
+++ server/dhcp.c       2012-03-16 11:33:11.000000000 +0100
@@ -671,6 +685,12 @@ void dhcprequest (packet, ms_nulltp, ip_
                goto out;
        }
 
+       if (force_nack (packet, lease)) {
+               log_info ("%s: force disconnect.", msgbuf, piaddr (cip));
+               nak_lease (packet, &cip);
+               goto out;
+       }
+       
        /* Otherwise, send the lease to the client if we found one. */
        if (lease) {
                ack_lease (packet, lease, DHCPACK, 0, msgbuf, ms_nulltp,

Bleibt also die Hooks auch zu aktivieren. Unglücklicherweise ist das Optionshandling nicht trivial. Es gilt nicht nur die Konfiguration zum jetzigen Zeitpunkt, sondern auch die Konfiguration, die bei der letzten Verlängerung der Lease galt. Dieses ist Suche habe ich in eine separate Funktion get_lease_state ausgelagert.

Mit dieser Hilfsfunktion ist es wesentlich leichter, die Funktionalität aufzubauen.

diff -pbBru ../ORIGINAL/server/dhcp.c server/dhcp.c
--- ../ORIGINAL/server/dhcp.c   2011-07-20 00:22:49.000000000 +0200
+++ server/dhcp.c       2012-03-16 11:33:11.000000000 +0100
@@ -4472,3 +4492,129 @@ maybe_return_agent_options(struct packet
                        options->universe_count = agent_universe.index + 1;
        }
 }
+
+/*
+ * 
+ * 
+ * 
+ */
+static int get_lease_state(struct lease_state * state,
+                          struct packet * packet, struct lease * lease) {
+   int i;
+
+   state -> got_requested_address = packet -> got_requested_address;
+   shared_network_reference (&state -> shared_network,
+                            packet -> interface -> shared_network, MDL);
+   
+   /* See if we got a server identifier option. */
+   if (lookup_option (&dhcp_universe,
+                     packet -> options, DHO_DHCP_SERVER_IDENTIFIER))
+     state -> got_server_identifier = 1;
+   
+   maybe_return_agent_options(packet, state->options);
+   
+   /* Execute statements in scope starting with the subnet scope. */
+   execute_statements_in_scope ((struct binding_value **)0,
+                               packet, lease, (struct client_state *)0,
+                               packet -> options,
+                               state -> options, &lease -> scope,
+                               lease -> subnet -> group,
+                               (struct group *)0);
+   
+   /* If the lease is from a pool, run the pool scope. */
+   if (lease -> pool)
+     (execute_statements_in_scope
+      ((struct binding_value **)0, packet, lease,
+       (struct client_state *)0, packet -> options,
+       state -> options, &lease -> scope, lease -> pool -> group,
+       lease -> pool -> shared_network -> group));
+   
+   /* Execute statements from class scopes. */
+   for (i = packet -> class_count; i > 0; i--) {
+      execute_statements_in_scope
+       ((struct binding_value **)0,
+        packet, lease, (struct client_state *)0,
+        packet -> options, state -> options,
+        &lease -> scope, packet -> classes [i - 1] -> group,
+        (lease -> pool
+         ? lease -> pool -> group
+         : lease -> subnet -> group));
+   }
+}
+
+static int avoid_reuse(struct packet *packet, struct lease * lease) {
+   int avoid_this_lease = 0;
+   struct option_cache * oc;
+   struct lease_state * state;
+   int ignorep;
+
+   /* Shortcut: Nothing to do. */
+   if (!packet || !lease || (lease -> flags & STATIC_LEASE))
+     return avoid_this_lease;
+   
+   state = new_lease_state (MDL);
+   if (!state)
+     return avoid_this_lease;         /* silently ignore the error */
+   else
+     get_lease_state(state, packet, lease);
+   
+   oc = lookup_option(&server_universe, state -> options, SV_AVOID_REUSE);
+   if (oc &&
+       evaluate_boolean_option_cache(&ignorep, packet, lease,
+                                    (struct client_state *)0,
+                                    packet -> options, state -> options,
+                                    &lease -> scope, oc, MDL)) {
+      /* 120 seconds is the typical hold time for temporary allocations */
+      if(cur_time - 120 > lease -> starts)
+       avoid_this_lease = 1;          /* do not OFFER an "old" lease */
+   }
+
+   free_lease_state (state, MDL);
+   
+   return avoid_this_lease;
+}
+   
+
+static int force_nack(struct packet *packet, struct lease * lease) {
+   int force_nack = 0;
+   struct option_cache * oc;
+   struct lease_state * state;
+   struct data_string data;
+
+   /* Shortcut: Nothing to do. */
+   if (!packet || !lease || (lease -> flags & STATIC_LEASE))
+     return force_nack;
+   
+   state = new_lease_state (MDL);
+   if (!state)
+     return force_nack;               /* silently ignore the error */
+   else
+     get_lease_state(state, packet, lease);
+   
+   memset(&data, 0, sizeof(data));
+   oc = lookup_option(&server_universe, state -> options, SV_FORCE_NACK);
+   if (oc &&
+       evaluate_option_cache(&data, packet, lease,
+                            (struct client_state *)0,
+                            packet -> options, state -> options,
+                            &lease -> scope, oc, MDL)) {
+      struct tm disconnect_tm;
+      TIME disconnect;
+      
+      if(localtime_r(&cur_time, &disconnect_tm)) {
+        disconnect_tm.tm_sec  = 0;
+        disconnect_tm.tm_min  = data.data[1];
+        disconnect_tm.tm_hour = data.data[0];   
+        disconnect = mktime(&disconnect_tm);
+        if(disconnect <= cur_time &&
+           disconnect >  lease -> starts) {
+           force_nack = 1;            /* NAK the lease if it spans the disconnect time */
+        }
+      }
+      data_string_forget (&data, MDL);
+   }
+
+   free_lease_state (state, MDL);
+   
+   return force_nack;
+}

Konfiguration

Wie sieht das nun aus, wenn man diese Optionen auch benutzen will?

# short lease times to allow quick changes
shared-network Internet_Vlan {
        default-lease-time 3000;
        max-lease-time 3600;

        # static and other subnets as usual

        subnet x.y.z.0 netmask 255.255.255.0 {
                option subnet-mask 255.255.255.0;
                option routers x.y.z.1;
                option domain-name-servers a.b.c.d;
                pool {
                        allow members of "dynamic";
                        range x.y.z.10 x.y.z.254;
                        avoid-reuse on;
                        force-nack 3 0;
                }
        }

        subnet 100.64.0.0 netmask 255.255.128.0 {
                option subnet-mask 255.255.128.0;
                option routers 100.64.0.1;
                option domain-name-servers 100.64.0.4, 100.64.0.5;
                pool {
                        allow members of "CGN";
                        range 100.64.0.10 100.64.127.254;
                        avoid-reuse on;
                }
        }
}

Es gibt mehrere statische Bereiche, die wie gewohnt konfiguriert werden.

Es gibt darüberhinaus mehrere dynamische Bereiche (einer ist hier dargestellt), bei denen nachts um 3:00 eine vertraglich vereinbarte Zwangstrennung erfolgt und die IP Adressen gewechselt werden.

Darüberhinaus gibt es mehrere dynamische Carrier Grade NAT Bereiche (einer ist hier dargestellt), bei denen keine Zwangstrennung, wohl aber ein Adreßwechsel erfolgt.

Der ganze Kram ist Failover fähig und läuft hier problemlos in einem DHCP-Cluster.

Post a comment

Verwandter Inhalt