Added DHCP request construction, ACK processing
authorMartin Willi <martin@revosec.ch>
Wed, 24 Mar 2010 10:08:59 +0000 (11:08 +0100)
committerMartin Willi <martin@revosec.ch>
Thu, 25 Mar 2010 13:28:29 +0000 (14:28 +0100)
src/libcharon/plugins/dhcp/dhcp_socket.c
src/libcharon/plugins/dhcp/dhcp_transaction.c
src/libcharon/plugins/dhcp/dhcp_transaction.h

index 24f9d43..2d0e63c 100644 (file)
@@ -36,6 +36,7 @@
 
 #define DHCP_SERVER_PORT 67
 #define DHCP_CLIENT_PORT 68
+#define DHCP_TRIES 5
 
 typedef struct private_dhcp_socket_t private_dhcp_socket_t;
 
@@ -105,18 +106,29 @@ struct private_dhcp_socket_t {
        callback_job_t *job;
 };
 
+/**
+ * DHCP opcode (or BOOTP actually)
+ */
 typedef enum {
        BOOTREQUEST = 1,
        BOOTREPLY = 2,
 } dhcp_opcode_t;
 
+/**
+ * Some DHCP options used
+ */
 typedef enum {
        DHCP_HOST_NAME = 12,
+       DHCP_REQUESTED_IP = 50,
        DHCP_MESSAGE_TYPE = 53,
+       DHCP_SERVER_ID = 54,
        DHCP_PARAM_REQ_LIST = 55,
        DHCP_OPTEND = 255,
 } dhcp_option_type_t;
 
+/**
+ * DHCP messages types in the DHCP_MESSAGE_TYPE option
+ */
 typedef enum {
        DHCP_DISCOVER = 1,
        DHCP_OFFER = 2,
@@ -128,17 +140,26 @@ typedef enum {
        DHCP_INFORM = 8,
 } dhcp_message_type_t;
 
+/**
+ * DHCP parameters in the DHCP_PARAM_REQ_LIST option
+ */
 typedef enum {
        DHCP_ROUTER = 3,
        DHCP_DNS_SERVER = 6,
 } dhcp_parameter_t;
 
+/**
+ * DHCP option encoding, a TLV
+ */
 typedef struct __attribute__((packed)) {
        u_int8_t type;
        u_int8_t len;
        char data[];
 } dhcp_option_t;
 
+/**
+ * DHCP message format, with a maximum size options buffer
+ */
 typedef struct __attribute__((packed)) {
        u_int8_t opcode;
        u_int8_t hw_type;
@@ -163,7 +184,8 @@ typedef struct __attribute__((packed)) {
  * Prepare a DHCP message for a given transaction
  */
 static int prepare_dhcp(private_dhcp_socket_t *this,
-                                               dhcp_transaction_t *transaction, dhcp_t *dhcp)
+                                               dhcp_transaction_t *transaction,
+                                               dhcp_message_type_t type, dhcp_t *dhcp)
 {
        chunk_t chunk, broadcast = chunk_from_chars(0xFF,0xFF,0xFF,0xFF);
        identification_t *identity;
@@ -208,7 +230,7 @@ static int prepare_dhcp(private_dhcp_socket_t *this,
        option = (dhcp_option_t*)&dhcp->options[optlen];
        option->type = DHCP_MESSAGE_TYPE;
        option->len = 1;
-       option->data[0] = DHCP_DISCOVER;
+       option->data[0] = type;
        optlen += sizeof(dhcp_option_t) + option->len;
 
        option = (dhcp_option_t*)&dhcp->options[optlen];
@@ -217,36 +239,51 @@ static int prepare_dhcp(private_dhcp_socket_t *this,
        memcpy(option->data, chunk.ptr, option->len);
        optlen += sizeof(dhcp_option_t) + option->len;
 
+       option = (dhcp_option_t*)&dhcp->options[optlen];
+       option->type = DHCP_PARAM_REQ_LIST;
+       option->len = 2;
+       option->data[0] = DHCP_ROUTER;
+       option->data[1] = DHCP_DNS_SERVER;
+       optlen += sizeof(dhcp_option_t) + option->len;
+
        return optlen;
 }
 
 /**
+ * Send a DHCP message with given options length
+ */
+static bool send_dhcp(private_dhcp_socket_t *this,
+                                         dhcp_transaction_t *transaction, dhcp_t *dhcp, int optlen)
+{
+       host_t *dst;
+       ssize_t len;
+
+       dst = transaction->get_server(transaction);
+       if (!dst)
+       {
+               dst = this->dst;
+       }
+       len = offsetof(dhcp_t, magic_cookie) + ((optlen + 4) / 64 * 64 + 64);
+       return sendto(this->send, dhcp, len, 0, dst->get_sockaddr(dst),
+                                 *dst->get_sockaddr_len(dst)) == len;
+}
+
+/**
  * Send DHCP discover using a given transaction
  */
 static bool discover(private_dhcp_socket_t *this,
                                         dhcp_transaction_t *transaction)
 {
-       dhcp_option_t *option;
        dhcp_t dhcp;
-       ssize_t len;
        int optlen;
 
-       optlen = prepare_dhcp(this, transaction, &dhcp);
+       optlen = prepare_dhcp(this, transaction, DHCP_DISCOVER, &dhcp);
 
        DBG1(DBG_CFG, "sending DHCP DISCOVER to %H", this->dst);
 
-       option = (dhcp_option_t*)&dhcp.options[optlen];
-       option->type = DHCP_PARAM_REQ_LIST;
-       option->len = 2;
-       option->data[0] = DHCP_ROUTER;
-       option->data[1] = DHCP_DNS_SERVER;
-       optlen += sizeof(dhcp_option_t) + option->len;
-
        dhcp.options[optlen++] = DHCP_OPTEND;
 
-       len = offsetof(dhcp_t, magic_cookie) + ((optlen + 4) / 64 * 64 + 64);
-       if (sendto(this->send, &dhcp, len, 0, this->dst->get_sockaddr(this->dst),
-                          *this->dst->get_sockaddr_len(this->dst)) != len)
+       if (!send_dhcp(this, transaction, &dhcp, optlen))
        {
                DBG1(DBG_CFG, "sending DHCP DISCOVER failed: %s", strerror(errno));
                return FALSE;
@@ -258,9 +295,46 @@ static bool discover(private_dhcp_socket_t *this,
  * Send DHCP request using a given transaction
  */
 static bool request(private_dhcp_socket_t *this,
-                                        dhcp_transaction_t *transaction)
+                                       dhcp_transaction_t *transaction)
 {
-       return FALSE;
+       dhcp_option_t *option;
+       dhcp_t dhcp;
+       host_t *offer, *server;
+       chunk_t chunk;
+       int optlen;
+
+       optlen = prepare_dhcp(this, transaction, DHCP_REQUEST, &dhcp);
+
+       offer = transaction->get_address(transaction);
+       server = transaction->get_server(transaction);
+       if (!offer || !server)
+       {
+               return FALSE;
+       }
+       DBG1(DBG_CFG, "sending DHCP REQUEST for %H to %H", offer, server);
+
+       option = (dhcp_option_t*)&dhcp.options[optlen];
+       option->type = DHCP_REQUESTED_IP;
+       option->len = 4;
+       chunk = offer->get_address(offer);
+       memcpy(option->data, chunk.ptr, min(chunk.len, option->len));
+       optlen += sizeof(dhcp_option_t) + option->len;
+
+       option = (dhcp_option_t*)&dhcp.options[optlen];
+       option->type = DHCP_SERVER_ID;
+       option->len = 4;
+       chunk = server->get_address(server);
+       memcpy(option->data, chunk.ptr, min(chunk.len, option->len));
+       optlen += sizeof(dhcp_option_t) + option->len;
+
+       dhcp.options[optlen++] = DHCP_OPTEND;
+
+       if (!send_dhcp(this, transaction, &dhcp, optlen))
+       {
+               DBG1(DBG_CFG, "sending DHCP REQUEST failed: %s", strerror(errno));
+               return FALSE;
+       }
+       return TRUE;
 }
 
 METHOD(dhcp_socket_t, enroll, dhcp_transaction_t*,
@@ -268,66 +342,68 @@ METHOD(dhcp_socket_t, enroll, dhcp_transaction_t*,
 {
        dhcp_transaction_t *transaction;
        u_int32_t id;
-       int tries;
+       int try;
 
        this->rng->get_bytes(this->rng, sizeof(id), (u_int8_t*)&id);
        transaction = dhcp_transaction_create(id, identity);
 
        this->mutex->lock(this->mutex);
        this->discover->insert_last(this->discover, transaction);
-       tries = 3;
-       while (tries && discover(this, transaction))
+       try = 1;
+       while (try <= DHCP_TRIES && discover(this, transaction))
        {
-               if (!this->condvar->timed_wait(this->condvar, this->mutex, 1000))
+               if (!this->condvar->timed_wait(this->condvar, this->mutex, 1000 * try) &&
+                       this->request->find_first(this->request, NULL,
+                                                                         (void**)&transaction) == SUCCESS)
                {
                        break;
                }
-               tries--;
+               try++;
        }
        if (this->discover->remove(this->discover, transaction, NULL))
        {       /* no OFFER received */
                this->mutex->unlock(this->mutex);
                transaction->destroy(transaction);
+               DBG1(DBG_CFG, "DHCP disover timed out");
                return NULL;
        }
 
-       tries = 3;
-       while (tries && request(this, transaction))
+       try = 1;
+       while (try <= DHCP_TRIES && request(this, transaction))
        {
-               if (!this->condvar->timed_wait(this->condvar, this->mutex, 1000))
+               if (!this->condvar->timed_wait(this->condvar, this->mutex, 1000 * try) &&
+                       this->completed->remove(this->completed, transaction, NULL))
                {
                        break;
                }
-               tries--;
+               try++;
        }
        if (this->request->remove(this->request, transaction, NULL))
        {       /* no ACK received */
                this->mutex->unlock(this->mutex);
                transaction->destroy(transaction);
+               DBG1(DBG_CFG, "DHCP request timed out");
                return NULL;
        }
        this->mutex->unlock(this->mutex);
 
-       transaction->destroy(transaction);
-       return NULL;
+       return transaction;
 }
 
 /**
- * Handle a DHCP offer
+ * Handle a DHCP OFFER
  */
 static void handle_offer(private_dhcp_socket_t *this, dhcp_t *dhcp, int optlen)
 {
        dhcp_transaction_t *transaction;
        enumerator_t *enumerator;
-       host_t *offer;
+       host_t *offer, *server;
 
        offer = host_create_from_chunk(AF_INET,
-                                                                  chunk_from_thing(dhcp->your_address), 0);
-       if (!offer)
-       {
-               return;
-       }
-       DBG1(DBG_CFG, "received DHCP OFFER %H", offer);
+                                       chunk_from_thing(dhcp->your_address), 0);
+       server = host_create_from_chunk(AF_INET,
+                                       chunk_from_thing(dhcp->server_address), DHCP_SERVER_PORT);
+       DBG1(DBG_CFG, "received DHCP OFFER %H from %H", offer, server);
 
        this->mutex->lock(this->mutex);
        enumerator = this->discover->create_enumerator(this->discover);
@@ -338,6 +414,38 @@ static void handle_offer(private_dhcp_socket_t *this, dhcp_t *dhcp, int optlen)
                        this->discover->remove_at(this->discover, enumerator);
                        this->request->insert_last(this->request, transaction);
                        transaction->set_address(transaction, offer->clone(offer));
+                       transaction->set_server(transaction, server->clone(server));
+                       break;
+               }
+       }
+       enumerator->destroy(enumerator);
+       this->mutex->unlock(this->mutex);
+       this->condvar->broadcast(this->condvar);
+       offer->destroy(offer);
+       server->destroy(server);
+}
+
+/**
+ * Handle a DHCP ACK
+ */
+static void handle_ack(private_dhcp_socket_t *this, dhcp_t *dhcp, int optlen)
+{
+       dhcp_transaction_t *transaction;
+       enumerator_t *enumerator;
+       host_t *offer;
+
+       offer = host_create_from_chunk(AF_INET,
+                                               chunk_from_thing(dhcp->your_address), 0);
+       DBG1(DBG_CFG, "received DHCP ACK for %H", offer);
+
+       this->mutex->lock(this->mutex);
+       enumerator = this->request->create_enumerator(this->request);
+       while (enumerator->enumerate(enumerator, &transaction))
+       {
+               if (transaction->get_id(transaction) == dhcp->transaction_id)
+               {
+                       this->request->remove_at(this->request, enumerator);
+                       this->completed->insert_last(this->completed, transaction);
                        break;
                }
        }
@@ -388,6 +496,8 @@ static job_requeue_t receive_dhcp(private_dhcp_socket_t *this)
                                        case DHCP_OFFER:
                                                handle_offer(this, &packet.dhcp, origoptlen);
                                                break;
+                                       case DHCP_ACK:
+                                               handle_ack(this, &packet.dhcp, origoptlen);
                                        default:
                                                break;
                                }
index 14e6540..27235ff 100644 (file)
@@ -41,6 +41,11 @@ struct private_dhcp_transaction_t {
         * received DHCP address
         */
        host_t *address;
+
+       /**
+        * discovered DHCP server address
+        */
+       host_t *server;
 };
 
 METHOD(dhcp_transaction_t, get_id, u_int32_t,
@@ -68,11 +73,25 @@ METHOD(dhcp_transaction_t, get_address, host_t*,
        return this->address;
 }
 
+METHOD(dhcp_transaction_t, set_server, void,
+       private_dhcp_transaction_t *this, host_t *server)
+{
+       DESTROY_IF(this->server);
+       this->server = server;
+}
+
+METHOD(dhcp_transaction_t, get_server, host_t*,
+       private_dhcp_transaction_t *this)
+{
+       return this->server;
+}
+
 METHOD(dhcp_transaction_t, destroy, void,
        private_dhcp_transaction_t *this)
 {
        this->identity->destroy(this->identity);
        DESTROY_IF(this->address);
+       DESTROY_IF(this->server);
        free(this);
 }
 
@@ -90,6 +109,8 @@ dhcp_transaction_t *dhcp_transaction_create(u_int32_t id,
                        .get_identity = _get_identity,
                        .set_address = _set_address,
                        .get_address = _get_address,
+                       .set_server = _set_server,
+                       .get_server = _get_server,
                        .destroy = _destroy,
                },
                .id = id,
index e8215e9..e205720 100644 (file)
@@ -60,6 +60,20 @@ struct dhcp_transaction_t {
        host_t* (*get_address)(dhcp_transaction_t *this);
 
        /**
+        * Set the DCHP server address discovered.
+        *
+        * @param server        DHCP server address
+        */
+       void (*set_server)(dhcp_transaction_t *this, host_t *server);
+
+       /**
+        * Get the DHCP server address.
+        *
+        * @return                      DHCP server address
+        */
+       host_t* (*get_server)(dhcp_transaction_t *this);
+
+       /**
         * Destroy a dhcp_transaction_t.
         */
        void (*destroy)(dhcp_transaction_t *this);