From 8b27b41c0c20b7d410d8151e892b4be35d9590c2 Mon Sep 17 00:00:00 2001
From: "Nicholas J. Kain" <nicholas@kain.us>
Date: Tue, 5 Jul 2011 13:03:55 -0400
Subject: [PATCH] Enable active defense of IP address / lease, as described in
 RFC5227.

---
 ndhc/arp.c  | 63 +++++++++++++++++++++++++++++++++++++++++++++++++----
 ndhc/arp.h  |  4 +++-
 ndhc/ndhc.c | 13 ++++++++---
 3 files changed, 72 insertions(+), 8 deletions(-)

diff --git a/ndhc/arp.c b/ndhc/arp.c
index ee8ca4c..e733589 100644
--- a/ndhc/arp.c
+++ b/ndhc/arp.c
@@ -1,5 +1,5 @@
 /* arp.c - arp ping checking
- * Time-stamp: <2011-07-05 11:06:00 njk>
+ * Time-stamp: <2011-07-05 12:49:09 njk>
  *
  * Copyright 2010-2011 Nicholas J. Kain <njkain@gmail.com>
  *
@@ -43,6 +43,18 @@
 #define ARP_MSG_SIZE 0x2a
 #define ARP_RETRANS_DELAY 5000 // ms
 
+// From RFC5227
+#define PROBE_WAIT 1000            // initial random delay
+#define PROBE_NUM 3                // number of probe packets
+#define PROBE_MIN 1000             // minimum delay until repeated probe
+#define PROBE_MAX 2000             // maximum delay until repeated probe
+#define ANNOUNCE_WAIT 2000         // delay before announcing
+#define ANNOUNCE_NUM 2             // number of Announcement packets
+#define ANNOUNCE_INTERVAL 2000     // time between Announcement packets
+#define MAX_CONFLICTS 10           // max conflicts before rate-limiting
+#define RATE_LIMIT_INTERVAL 60000  // delay between successive attempts
+#define DEFEND_INTERVAL 10000      // minimum interval between defensive ARPs
+
 typedef enum {
     AS_NONE = 0,        // Nothing to react to wrt ARP
     AS_COLLISION_CHECK, // Checking to see if another host has our IP before
@@ -61,6 +73,11 @@ struct arp_stats {
 };
 static struct arp_stats arp_stats[AS_MAX-1];
 static int using_arp_bpf; // Is a BPF installed on the ARP socket?
+int arp_relentless_def; // Don't give up defense no matter what.
+static long long last_conflict_ts; // TS of the last conflicting ARP seen.
+static long long pending_def_ts; // TS of the upcoming defense ARP send.
+static long long last_def_ts; // TS of the most recent defense ARP sent.
+static int def_old_oldTimeout; // Old value of cs->oldTimeout.
 
 static struct arpMsg arpreply;
 static int arpreply_offset;
@@ -384,6 +401,10 @@ void arp_success(struct client_state_t *cs)
     cs->clientAddr = arp_dhcp_packet.yiaddr;
     cs->dhcpState = DS_BOUND;
     cs->init = 0;
+    last_conflict_ts = 0;
+    pending_def_ts = 0;
+    last_def_ts = 0;
+    def_old_oldTimeout = 0;
     ifchange_bind(&arp_dhcp_packet);
     if (cs->arpPrevState == DS_RENEWING || cs->arpPrevState == DS_REBINDING) {
         arp_switch_state(cs, AS_DEFENSE);
@@ -468,6 +489,17 @@ static int arp_is_query_reply(struct arpMsg *am)
 // Perform retransmission if necessary.
 void arp_retransmit(struct client_state_t *cs)
 {
+    if (pending_def_ts) {
+        log_line("arp: Defending our lease IP.");
+        pending_def_ts = 0;
+        last_def_ts = curms();
+        arp_announcement(cs);
+        if (def_old_oldTimeout) {
+            cs->timeout = cs->oldTimeout;
+            cs->oldTimeout = def_old_oldTimeout;
+            def_old_oldTimeout = 0;
+        }
+    }
     if (curms() < arp_stats[arpState].ts + ARP_RETRANS_DELAY)
         return;
     log_line("DEBUG: retransmission timeout in arp state %u", arpState);
@@ -490,6 +522,31 @@ void arp_retransmit(struct client_state_t *cs)
     }
 }
 
+static void arp_do_defense(struct client_state_t *cs)
+{
+    log_line("arp: detected a peer attempting to use our IP!");
+    long long cur_conflict_ts = curms();
+    if (!last_conflict_ts ||
+        cur_conflict_ts - last_conflict_ts < DEFEND_INTERVAL) {
+        log_line("arp: Defending our lease IP.");
+        last_def_ts = cur_conflict_ts;
+        arp_announcement(cs);
+    } else if (!arp_relentless_def) {
+        log_line("arp: Conflicting peer is persistent.  Requesting new lease.");
+        send_release(cs);
+        reinit_selecting(cs, 0);
+    } else {
+        pending_def_ts = last_def_ts + DEFEND_INTERVAL;
+        long long def_xmit_ts = pending_def_ts - cur_conflict_ts;
+        if (!cs->timeout || cs->timeout > def_xmit_ts) {
+            def_old_oldTimeout = cs->oldTimeout;
+            cs->oldTimeout = cs->timeout;
+            cs->timeout = def_xmit_ts;
+        }
+    }
+    last_conflict_ts = cur_conflict_ts;
+}
+
 void handle_arp_response(struct client_state_t *cs)
 {
     int r = 0;
@@ -570,9 +627,7 @@ void handle_arp_response(struct client_state_t *cs)
         if (!arp_validate_bpf_defense(cs, &arpreply))
             break;
     case AS_DEFENSE:
-        log_line("arp: detected a peer attempting to use our IP!");
-        // XXX: actually do work...
-        log_line("DEBUG: TODO actually do work!");
+        arp_do_defense(cs);
         break;
     default:
         log_error("handle_arp_response: called in invalid state %u", arpState);
diff --git a/ndhc/arp.h b/ndhc/arp.h
index e6cdc6b..6e157a2 100644
--- a/ndhc/arp.h
+++ b/ndhc/arp.h
@@ -1,5 +1,5 @@
 /* arp.h - functions to call the interface change daemon
- * Time-stamp: <2011-07-05 10:49:41 njk>
+ * Time-stamp: <2011-07-05 12:54:21 njk>
  *
  * Copyright 2010-2011 Nicholas J. Kain <njkain@gmail.com>
  *
@@ -45,6 +45,8 @@ struct arpMsg {
     uint8_t  pad[18];       // 2a pad for min. ethernet payload (60 bytes)
 };
 
+extern int arp_relentless_def;
+
 int arp_close_fd(struct client_state_t *cs);
 int arp_check(struct client_state_t *cs, struct dhcpmsg *packet);
 int arp_gw_check(struct client_state_t *cs);
diff --git a/ndhc/ndhc.c b/ndhc/ndhc.c
index 6f77eae..0508c9d 100644
--- a/ndhc/ndhc.c
+++ b/ndhc/ndhc.c
@@ -1,5 +1,5 @@
 /* ndhc.c - DHCP client
- * Time-stamp: <2011-07-05 11:18:03 njk>
+ * Time-stamp: <2011-07-05 12:54:15 njk>
  *
  * (c) 2004-2011 Nicholas J. Kain <njkain at gmail dot com>
  *
@@ -94,6 +94,8 @@ static void show_usage(void)
 "  -r, --request=IP                IP address to request (default: none)\n"
 "  -u, --user=USER                 Change privileges to this user\n"
 "  -C, --chroot=DIR                Chroot to this directory\n"
+"  -d, --relentless-defense        Never back off in defending IP against\n"
+"                                  conflicting hosts (servers only)\n"
 "  -v, --version                   Display version\n"
            );
     exit(EXIT_SUCCESS);
@@ -220,17 +222,18 @@ int main(int argc, char **argv)
         {"now",         no_argument,        0, 'n'},
         {"quit",        no_argument,        0, 'q'},
         {"request",     required_argument,  0, 'r'},
-        {"version",     no_argument,        0, 'v'},
         {"vendorid",    required_argument,  0, 'V'},
         {"user",        required_argument,  0, 'u'},
         {"chroot",      required_argument,  0, 'C'},
+        {"relentless-defense", no_argument, 0, 'd'},
+        {"version",     no_argument,        0, 'v'},
         {"help",        no_argument,        0, '?'},
         {0, 0, 0, 0}
     };
 
     while (1) {
         int option_index = 0;
-        c = getopt_long(argc, argv, "c:fbp:h:i:np:l:qr:u:C:vV:", arg_options,
+        c = getopt_long(argc, argv, "c:fbp:h:i:np:l:qr:V:u:C:dv", arg_options,
                         &option_index);
         if (c == -1) break;
 
@@ -285,6 +288,10 @@ int main(int argc, char **argv)
             case 'C':
                 strlcpy(chroot_dir, optarg, sizeof chroot_dir);
                 break;
+            case 'd':
+                log_line("DEBUG: relentless defense enabled");
+                arp_relentless_def = 1;
+                break;
             case 'v':
                 printf("ndhc, version " VERSION "\n\n");
                 exit(EXIT_SUCCESS);