summary refs log tree commit diff
path: root/shared/n-dhcp4/src/n-dhcp4-c-connection.c
diff options
context:
space:
mode:
Diffstat (limited to 'shared/n-dhcp4/src/n-dhcp4-c-connection.c')
-rw-r--r--shared/n-dhcp4/src/n-dhcp4-c-connection.c1256
1 files changed, 0 insertions, 1256 deletions
diff --git a/shared/n-dhcp4/src/n-dhcp4-c-connection.c b/shared/n-dhcp4/src/n-dhcp4-c-connection.c
deleted file mode 100644
index 31235253..00000000
--- a/shared/n-dhcp4/src/n-dhcp4-c-connection.c
+++ /dev/null
@@ -1,1256 +0,0 @@
-/*
- * DHCPv4 Client Connection
- *
- * XXX
- */
-
-#include <assert.h>
-#include <c-stdaux.h>
-#include <errno.h>
-#include <limits.h>
-#include <sys/socket.h> /* needed by linux/netdevice.h */
-#include <linux/netdevice.h>
-#include <net/if_arp.h>
-#include <stdbool.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/epoll.h>
-#include "n-dhcp4-private.h"
-#include "util/packet.h"
-
-/**
- * n_dhcp4_c_connection_init() - initialize client connection
- * @connection:                 connection to operate on
- * @client_config:              client configuration to use
- * @probe_config:               client probe configuration to use
- * @log_queue:                  the log queue for logging events
- * @fd_epoll:                   epoll context to attach to, or -1
- *
- * This initializes a new client connection using the configuration given in
- * @client_config and @probe_config.
- *
- * The client-configuration given as @client_config must survive the lifetime
- * of @connection. It is pinned in the connection and used all over the place.
- * The caller must guarantee that the configuration is not deallocated in the
- * meantime. Same is true for @probe_config.
- *
- * The new connection automatically attaches to the epoll context given as
- * @fd_epoll. The epoll FD is retained in the connection and the caller must
- * guarantee that it lives as long as the connection.
- * The caller is explicitly allowed to pass -1 as @fd_epoll, in which case the
- * connection will initialize correctly, but will not be in a usable state.
- * That is, any call to n_dhcp4_c_connection_listen() will fail, since it will
- * be unable to attach to the epoll context. Such a connection can be used to
- * get a detached object that behaves sound, but provides no runtime.
- *
- * Return: 0 on success, negative error code on failure.
- */
-int n_dhcp4_c_connection_init(NDhcp4CConnection *connection,
-                              NDhcp4ClientConfig *client_config,
-                              NDhcp4ClientProbeConfig *probe_config,
-                              NDhcp4LogQueue *log_queue,
-                              int fd_epoll) {
-        *connection = (NDhcp4CConnection)N_DHCP4_C_CONNECTION_NULL(*connection);
-        connection->client_config = client_config;
-        connection->probe_config = probe_config;
-        connection->fd_epoll = fd_epoll;
-        connection->log_queue = log_queue;
-
-        /*
-         * We explicitly allow initializing connections with an invalid
-         * epoll-fd. The resulting connection immediately transitions into the
-         * CLOSED state. This allows the caller to create dummy connections
-         * useful to provide asynchronous constructor-feedback in the API.
-         *
-         * The effect of this is as if you immediately call
-         * n_dhcp4_c_connection_close() on the new connection. However, by
-         * directly passing -1 in the constructor, you are guaranteed not even
-         * the constructor can ever mess with your epoll-set.
-         */
-        if (connection->fd_epoll < 0)
-                connection->state = N_DHCP4_C_CONNECTION_STATE_CLOSED;
-
-        return 0;
-}
-
-/**
- * n_dhcp4_c_connection_deinit() - deinitialize client connection
- * @connection:                 connection to operate on
- *
- * This deinitializes a connection that was previously initialized via
- * n_dhcp4_c_connection_init(). It will tear down all allocated state and
- * release it.
- *
- * Once this function returns, @connection is re-initialized to
- * N_DHCP4_C_CONNECTION_NULL. If this function is called on a deinitialized
- * connection, it is a no-op.
- */
-void n_dhcp4_c_connection_deinit(NDhcp4CConnection *connection) {
-        n_dhcp4_c_connection_close(connection);
-        n_dhcp4_outgoing_free(connection->request);
-        *connection = (NDhcp4CConnection)N_DHCP4_C_CONNECTION_NULL(*connection);
-}
-
-static void n_dhcp4_c_connection_outgoing_set_secs(NDhcp4Outgoing *message) {
-        uint64_t secs;
-
-        /*
-         * This function sets the `secs` field for outgoing messages. It
-         * expects the base-time and start-time to be already set by the
-         * caller.
-         * For a given outgoing message, its `secs` field describes the time
-         * (in seconds) between the start of the transaction this message is
-         * part of and the start of the operational process (also called the
-         * base time here).
-         *
-         * The operational process in the DHCP sense describes the entire
-         * process of requesting a lease and acquiring it. That is, it starts
-         * with the caller's intent to request a lease, and it ends when we
-         * got granted a lease. The act of refreshing a lease is, in itself, a
-         * new operational process. The base-time describes the start-time
-         * recorded when such a process as initiated.
-         *
-         * A transaction in the DHCP sense describes a request+reply
-         * combination, in most cases. That is, the time a request is sent is
-         * the start-time of a transaction. In the ideal case, the start-time
-         * of the first transaction in an operational process matches the
-         * base-time. However, transactions are often delayed with a randomized
-         * offset to reduce traffic during network bursts.
-         * In some cases, however, transactions are composed out of multiple
-         * requests+reply combinations. This includes, for instance, the SELECT
-         * message following an OFFER. The specification clearly says that
-         * those must be considered a single transaction and thus share the
-         * transaction start-time.
-         *
-         * The `secs` field, thus, describes how long a client has been busy
-         * requesting a lease. DHCP servers and proxies do use it to prioritize
-         * clients.
-         *
-         * Note: Some DHCP relays reject a `secs` value of 0 (which might look
-         *       like it is uninitialized). Hence, we always clamp the value to
-         *       the range `[1, 65535]`.
-         */
-
-        secs = message->userdata.base_time - message->userdata.start_time;
-        secs /= 1000ULL * 1000ULL * 1000ULL; /* nsecs to secs */
-        secs = C_CLAMP(secs, 1, UINT16_MAX);
-
-        n_dhcp4_outgoing_set_secs(message, secs);
-}
-
-int n_dhcp4_c_connection_listen(NDhcp4CConnection *connection) {
-        _c_cleanup_(c_closep) int fd_packet = -1;
-        int r;
-
-        if (connection->state == N_DHCP4_C_CONNECTION_STATE_PACKET)
-                return 0;
-
-        c_assert(connection->state == N_DHCP4_C_CONNECTION_STATE_INIT ||
-                 connection->state == N_DHCP4_C_CONNECTION_STATE_DRAINING ||
-                 connection->state == N_DHCP4_C_CONNECTION_STATE_UDP);
-
-        if (connection->fd_packet >= 0) {
-                epoll_ctl(connection->fd_epoll, EPOLL_CTL_DEL, connection->fd_packet, NULL);
-                connection->fd_packet = c_close(connection->fd_packet);
-        }
-
-        if (connection->fd_udp >= 0) {
-                epoll_ctl(connection->fd_epoll, EPOLL_CTL_DEL, connection->fd_udp, NULL);
-                connection->fd_udp = c_close(connection->fd_udp);
-        }
-
-        r = n_dhcp4_c_socket_packet_new(&fd_packet, connection->client_config->ifindex);
-        if (r)
-                return r;
-
-        r = epoll_ctl(connection->fd_epoll,
-                      EPOLL_CTL_ADD,
-                      fd_packet,
-                      &(struct epoll_event){
-                              .events = EPOLLIN,
-                              .data = { .u32 = N_DHCP4_CLIENT_EPOLL_IO },
-                      });
-        if (r < 0)
-                return -errno;
-
-        connection->state = N_DHCP4_C_CONNECTION_STATE_PACKET;
-        connection->fd_packet = fd_packet;
-        fd_packet = -1;
-        return 0;
-}
-
-int n_dhcp4_c_connection_connect(NDhcp4CConnection *connection,
-                                 const struct in_addr *client,
-                                 const struct in_addr *server) {
-        _c_cleanup_(c_closep) int fd_udp = -1;
-        int r;
-
-        c_assert(connection->state == N_DHCP4_C_CONNECTION_STATE_PACKET);
-
-        r = n_dhcp4_c_socket_udp_new(&fd_udp,
-                                     connection->client_config->ifindex,
-                                     client,
-                                     server);
-        if (r)
-                return r;
-
-        r = epoll_ctl(connection->fd_epoll,
-                      EPOLL_CTL_ADD,
-                      fd_udp,
-                      &(struct epoll_event){
-                              .events = EPOLLIN,
-                              .data = { .u32 = N_DHCP4_CLIENT_EPOLL_IO },
-                      });
-        if (r < 0)
-                return -errno;
-
-        r = packet_shutdown(connection->fd_packet);
-        if (r < 0) {
-                epoll_ctl(connection->fd_epoll, EPOLL_CTL_DEL, fd_udp, NULL);
-                return r;
-        }
-
-        connection->state = N_DHCP4_C_CONNECTION_STATE_DRAINING;
-        connection->fd_udp = fd_udp;
-        fd_udp = -1;
-        connection->client_ip = client->s_addr;
-        connection->server_ip = server->s_addr;
-        return 0;
-}
-
-void n_dhcp4_c_connection_close(NDhcp4CConnection *connection) {
-        if (connection->fd_udp >= 0) {
-                epoll_ctl(connection->fd_epoll, EPOLL_CTL_DEL, connection->fd_udp, NULL);
-                connection->fd_udp = c_close(connection->fd_udp);
-        }
-
-        if (connection->fd_packet >= 0) {
-                epoll_ctl(connection->fd_epoll, EPOLL_CTL_DEL, connection->fd_packet, NULL);
-                connection->fd_packet = c_close(connection->fd_packet);
-        }
-
-        connection->fd_epoll = -1;
-        connection->state = N_DHCP4_C_CONNECTION_STATE_CLOSED;
-}
-
-static int n_dhcp4_c_connection_verify_incoming(NDhcp4CConnection *connection,
-                                                NDhcp4Incoming *message,
-                                                uint8_t *typep) {
-        NDhcp4Header *header = n_dhcp4_incoming_get_header(message);
-        uint8_t type;
-        uint32_t request_xid;
-        uint8_t *id;
-        size_t n_id;
-        int r;
-
-        r = n_dhcp4_incoming_query_message_type(message, &type);
-        if (r) {
-                if (r == N_DHCP4_E_UNSET)
-                        return N_DHCP4_E_MALFORMED;
-                else
-                        return r;
-        }
-
-        switch (type) {
-        case N_DHCP4_MESSAGE_OFFER:
-        case N_DHCP4_MESSAGE_ACK:
-        case N_DHCP4_MESSAGE_NAK:
-                /*
-                 * Only accept replies if there is a pending request, and it
-                 * has a matching transaction id.
-                 */
-                if (!connection->request)
-                        return N_DHCP4_E_UNEXPECTED;
-
-                n_dhcp4_outgoing_get_xid(connection->request, &request_xid);
-                if (header->xid != request_xid)
-                        return N_DHCP4_E_UNEXPECTED;
-
-                break;
-        case N_DHCP4_MESSAGE_FORCERENEW:
-                /*
-                 * Force renew messages are triggered by a server, and do not
-                 * match a pending request.
-                 */
-                break;
-        default:
-                return N_DHCP4_E_UNEXPECTED;
-        }
-
-        /*
-         * In case our transport makes use of the 'chaddr' field, make sure it
-         * matches exactly our address.
-         */
-        switch (connection->client_config->transport) {
-        case N_DHCP4_TRANSPORT_ETHERNET:
-                c_assert(connection->client_config->n_mac == ETH_ALEN);
-
-                if (header->hlen != ETH_ALEN)
-                        return N_DHCP4_E_UNEXPECTED;
-                if (memcmp(header->chaddr, connection->client_config->mac, ETH_ALEN) != 0)
-                        return N_DHCP4_E_UNEXPECTED;
-
-                break;
-        case N_DHCP4_TRANSPORT_INFINIBAND:
-                if (header->hlen != 0)
-                        return N_DHCP4_E_UNEXPECTED;
-
-                break;
-        }
-
-        /*
-         * If a server passes us back a client ID, it must be the one we
-         * provided. We ignore any packets that have mismatching client-ids.
-         */
-        id = NULL;
-        n_id = 0;
-        r = n_dhcp4_incoming_query(message, N_DHCP4_OPTION_CLIENT_IDENTIFIER, &id, &n_id);
-        if (r) {
-                if (r != N_DHCP4_E_UNSET)
-                        return r;
-        } else {
-                if (n_id != connection->client_config->n_client_id)
-                        return N_DHCP4_E_UNEXPECTED;
-                if (memcmp(id, connection->client_config->client_id, n_id) != 0)
-                        return N_DHCP4_E_UNEXPECTED;
-        }
-
-        *typep = type;
-        return 0;
-}
-
-void n_dhcp4_c_connection_get_timeout(NDhcp4CConnection *connection,
-                                      uint64_t *timeoutp) {
-        uint64_t timeout;
-        size_t n_send;
-
-        if (!connection->request) {
-                *timeoutp = 0;
-                return;
-        }
-
-        switch (connection->request->userdata.type) {
-        case N_DHCP4_C_MESSAGE_DISCOVER:
-        case N_DHCP4_C_MESSAGE_SELECT:
-        case N_DHCP4_C_MESSAGE_INFORM:
-                /*
-                 * Resend with an exponential backoff and a one second random
-                 * slack, from a minimum of two seconds to a maximum of sixty
-                 * four.
-                 *
-                 * Note that the RFC says to start at four rather than two
-                 * seconds, and use [-1,1] slack, rather than [0,1].
-                 */
-                n_send = connection->request->userdata.n_send;
-                if (n_send >= 6)
-                        n_send = 6;
-
-                timeout = connection->request->userdata.send_time + ((1ULL << n_send) * 1000000000ULL) + connection->request->userdata.send_jitter;
-
-                break;
-        case N_DHCP4_C_MESSAGE_REBIND:
-        case N_DHCP4_C_MESSAGE_RENEW:
-        case N_DHCP4_C_MESSAGE_REBOOT:
-                /*
-                 * Resend every sixty seconds with a one second random slack.
-                 *
-                 * Note that the RFC says to do this at most once, but we do
-                 * it until we are cancelled.
-                 */
-                timeout = connection->request->userdata.send_time + (60ULL * 1000000000ULL) + connection->request->userdata.send_jitter;
-
-                break;
-        case N_DHCP4_C_MESSAGE_DECLINE:
-        case N_DHCP4_C_MESSAGE_RELEASE:
-                /* XXX make sure these message types are never pinned? */
-                timeout = 0;
-                break;
-        default:
-                c_assert(0);
-        }
-
-        *timeoutp = timeout;
-}
-
-static int n_dhcp4_c_connection_packet_broadcast(NDhcp4CConnection *connection,
-                                                 NDhcp4Outgoing *message) {
-        int r;
-
-        c_assert(connection->state == N_DHCP4_C_CONNECTION_STATE_PACKET);
-
-        r = n_dhcp4_c_socket_packet_send(connection->fd_packet,
-                                         connection->client_config->ifindex,
-                                         connection->client_config->broadcast_mac,
-                                         connection->client_config->n_broadcast_mac,
-                                         message);
-        if (r)
-                return r;
-
-        return 0;
-}
-
-static int n_dhcp4_c_connection_udp_broadcast(NDhcp4CConnection *connection,
-                                              NDhcp4Outgoing *message) {
-        int r;
-
-        c_assert(connection->state == N_DHCP4_C_CONNECTION_STATE_DRAINING ||
-               connection->state == N_DHCP4_C_CONNECTION_STATE_UDP);
-
-        r = n_dhcp4_c_socket_udp_broadcast(connection->fd_udp, message);
-        if (r)
-                return r;
-
-        return 0;
-}
-
-static int n_dhcp4_c_connection_udp_send(NDhcp4CConnection *connection,
-                                         NDhcp4Outgoing *message) {
-        int r;
-
-        c_assert(connection->state == N_DHCP4_C_CONNECTION_STATE_DRAINING ||
-               connection->state == N_DHCP4_C_CONNECTION_STATE_UDP);
-
-        r = n_dhcp4_c_socket_udp_send(connection->fd_udp, message);
-        if (r)
-                return r;
-
-        return 0;
-}
-
-static void n_dhcp4_c_connection_init_header(NDhcp4CConnection *connection,
-                                             NDhcp4Header *header) {
-        bool broadcast = connection->client_config->request_broadcast;
-
-        header->op = N_DHCP4_OP_BOOTREQUEST;
-
-        switch (connection->client_config->transport) {
-        case N_DHCP4_TRANSPORT_ETHERNET:
-                c_assert(connection->client_config->n_mac == ETH_ALEN);
-
-                header->htype = ARPHRD_ETHER;
-                header->hlen = ETH_ALEN;
-                memcpy(header->chaddr, connection->client_config->mac, ETH_ALEN);
-                break;
-        case N_DHCP4_TRANSPORT_INFINIBAND:
-                header->htype = ARPHRD_INFINIBAND;
-                header->hlen = 0;
-
-                /* infiniband mandates to request broadcasts */
-                broadcast = true;
-                break;
-        default:
-                abort();
-                break;
-        }
-
-        if (connection->client_ip != INADDR_ANY) {
-                header->ciaddr = connection->client_ip;
-        } else {
-                /*
-                 * When the IP stack has not been configured, we may
-                 * not be able to receive unicast packets, depending
-                 * on the hardware. If that is the case we must request
-                 * replies from the server to be broadcast.
-                 *
-                 * Once the IP stack has been configured, receiving
-                 * unicast packets is never a problem, so the broadcast
-                 * flag should not be set.
-                 */
-                if (broadcast)
-                        header->flags |= N_DHCP4_MESSAGE_FLAG_BROADCAST;
-        }
-}
-
-static int n_dhcp4_c_connection_new_message(NDhcp4CConnection *connection,
-                                            NDhcp4Outgoing **messagep,
-                                            uint8_t type) {
-        _c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *message = NULL;
-        NDhcp4Header *header;
-        uint8_t message_type;
-        bool via_packet_socket = false;
-        int r;
-
-        switch (type) {
-        case N_DHCP4_C_MESSAGE_DISCOVER:
-                message_type = N_DHCP4_MESSAGE_DISCOVER;
-                via_packet_socket = true;
-                break;
-        case N_DHCP4_C_MESSAGE_INFORM:
-                message_type = N_DHCP4_MESSAGE_INFORM;
-                break;
-        case N_DHCP4_C_MESSAGE_SELECT:
-                message_type = N_DHCP4_MESSAGE_REQUEST;
-                via_packet_socket = true;
-                break;
-        case N_DHCP4_C_MESSAGE_RENEW:
-                message_type = N_DHCP4_MESSAGE_REQUEST;
-                break;
-        case N_DHCP4_C_MESSAGE_REBIND:
-                message_type = N_DHCP4_MESSAGE_REQUEST;
-                break;
-        case N_DHCP4_C_MESSAGE_REBOOT:
-                message_type = N_DHCP4_MESSAGE_REQUEST;
-                via_packet_socket = true;
-                break;
-        case N_DHCP4_C_MESSAGE_RELEASE:
-                message_type = N_DHCP4_MESSAGE_RELEASE;
-                break;
-        case N_DHCP4_C_MESSAGE_DECLINE:
-                message_type = N_DHCP4_MESSAGE_DECLINE;
-                via_packet_socket = true;
-                break;
-        default:
-                abort();
-                return -ENOTRECOVERABLE;
-        }
-
-        /*
-         * We explicitly pass 0 as maximum message size, which makes
-         * NDhcp4Outgoing use the mandated default value from the spec (see its
-         * implementation). While the transport and like layers might support
-         * bigger MTUs (and we very likely know about them through
-         * n_dhcp4_client_update_mtu()), we cannot assume the target DHCP
-         * server supports parsing packets bigger than the minimum (and it is
-         * allowed to refuse bigger IP packets, even if the network supports
-         * transmission of them).
-         *
-         * We could theoretically increase this for packets other than the
-         * initial discovery. However, clients are unlikely to ever send large
-         * packets, so we just keep the same default for all outgoing packets.
-         */
-        r = n_dhcp4_outgoing_new(&message, 0, N_DHCP4_OVERLOAD_FILE | N_DHCP4_OVERLOAD_SNAME);
-        if (r)
-                return r;
-
-        header = n_dhcp4_outgoing_get_header(message);
-        n_dhcp4_c_connection_init_header(connection, header);
-
-        message->userdata.type = type;
-        message->userdata.message_type = message_type;
-
-        /*
-         * Note that some implementations expect the MESSAGE_TYPE option to be
-         * the first option, and possibly even hard-code access to it. Hence,
-         * we really should make sure to pass it first as well.
-         */
-        r = n_dhcp4_outgoing_append(message, N_DHCP4_OPTION_MESSAGE_TYPE, &message_type, sizeof(message_type));
-        if (r)
-                return r;
-
-        r = n_dhcp4_outgoing_append(message,
-                                    N_DHCP4_OPTION_CLIENT_IDENTIFIER,
-                                    connection->client_config->client_id,
-                                    connection->client_config->n_client_id);
-        if (r)
-                return r;
-
-        switch (message_type) {
-        case N_DHCP4_MESSAGE_DISCOVER:
-        case N_DHCP4_MESSAGE_REQUEST:
-        case N_DHCP4_MESSAGE_INFORM: {
-                uint16_t mtu;
-
-                if (connection->probe_config->n_request_parameters > 0) {
-                        r = n_dhcp4_outgoing_append(message,
-                                                    N_DHCP4_OPTION_PARAMETER_REQUEST_LIST,
-                                                    connection->probe_config->request_parameters,
-                                                    connection->probe_config->n_request_parameters);
-                        if (r)
-                                return r;
-                }
-
-                if (via_packet_socket) {
-                        /*
-                         * In case of packet sockets, we do not support
-                         * fragmentation. Hence, our maximum message size
-                         * equals the transport MTU. In case no mtu is given,
-                         * we use the minimum size mandated by the IP spec. If
-                         * we omit the field, some implementations will
-                         * interpret this to mean any packet size is supported,
-                         * which we rather not want as default behavior (we can
-                         * always support suppressing this field, if that is
-                         * what the caller wants).
-                         */
-                        mtu = htons(connection->mtu ?: N_DHCP4_NETWORK_IP_MINIMUM_MAX_SIZE);
-                        r = n_dhcp4_outgoing_append(message, N_DHCP4_OPTION_MAXIMUM_MESSAGE_SIZE, &mtu, sizeof(mtu));
-                        if (r)
-                                return r;
-                } else {
-                        /*
-                         * Once we use UDP sockets, we support fragmentation
-                         * through the kernel IP stack. This means, the biggest
-                         * message we can receive is the maximum UDP size plus
-                         * the possible IP header. This would sum up to
-                         * 2^16-1 + 20 (or even 2^16-1 + 60 if pedantic) and
-                         * thus exceed the option field. Hence, we simply set
-                         * the option to the maximum possible value.
-                         */
-                        mtu = htons(UINT16_MAX);
-                        r = n_dhcp4_outgoing_append(message, N_DHCP4_OPTION_MAXIMUM_MESSAGE_SIZE, &mtu, sizeof(mtu));
-                        if (r)
-                                return r;
-                }
-
-                break;
-        }
-        default:
-                break;
-        }
-
-        *messagep = message;
-        message = NULL;
-        return 0;
-}
-
-/*
- *      RFC2131 3.1
- *
- *      The client broadcasts a DHCPDISCOVER message on its local physical
- *      subnet.  The DHCPDISCOVER message MAY include options that suggest
- *      values for the network address and lease duration.  BOOTP relay
- *      agents may pass the message on to DHCP servers not on the same
- *      physical subnet.
- *
- *      RFC2131 3.5
- *
- *      [...] in its initial DHCPDISCOVER or DHCPREQUEST message, a client
- *      may provide the server with a list of specific parameters the
- *      client is interested in.  If the client includes a list of
- *      parameters in a DHCPDISCOVER message, it MUST include that list in
- *      any subsequent DHCPREQUEST messages.
- *
- *      [...]
- *
- *      In addition, the client may suggest values for the network address
- *      and lease time in the DHCPDISCOVER message.  The client may include
- *      the 'requested IP address' option to suggest that a particular IP
- *      address be assigned, and may include the 'IP address lease time'
- *      option to suggest the lease time it would like.  Other options
- *      representing "hints" at configuration parameters are allowed in a
- *      DHCPDISCOVER or DHCPREQUEST message.
- *
- *      RFC2131 4.4.1
- *
- *      The client generates and records a random transaction identifier and
- *      inserts that identifier into the 'xid' field.  The client records its
- *      own local time for later use in computing the lease expiration.  The
- *      client then broadcasts the DHCPDISCOVER on the local hardware
- *      broadcast address to the 0xffffffff IP broadcast address and 'DHCP
- *      server' UDP port.
- *
- *      If the 'xid' of an arriving DHCPOFFER message does not match the
- *      'xid' of the most recent DHCPDISCOVER message, the DHCPOFFER message
- *      must be silently discarded.  Any arriving DHCPACK messages must be
- *      silently discarded.
- */
-int n_dhcp4_c_connection_discover_new(NDhcp4CConnection *connection,
-                                      NDhcp4Outgoing **requestp) {
-        _c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *message = NULL;
-        int r;
-
-        r = n_dhcp4_c_connection_new_message(connection, &message, N_DHCP4_C_MESSAGE_DISCOVER);
-        if (r)
-                return r;
-
-        *requestp = message;
-        message = NULL;
-        return 0;
-}
-
-/*
- *
- *      RFC2131 4.1.1
- *
- *      The DHCPREQUEST message contains the same 'xid' as the DHCPOFFER
- *      message.
- *
- *      RFC2131 4.3.2
- *
- *      Client inserts the address of the selected server in 'server
- *      identifier', 'ciaddr' MUST be zero, 'requested IP address' MUST be
- *      filled in with the yiaddr value from the chosen DHCPOFFER.
- */
-int n_dhcp4_c_connection_select_new(NDhcp4CConnection *connection,
-                                    NDhcp4Outgoing **requestp,
-                                    NDhcp4Incoming *offer) {
-        _c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *message = NULL;
-        struct in_addr client;
-        struct in_addr server;
-        uint32_t xid;
-        int r;
-
-        n_dhcp4_incoming_get_yiaddr(offer, &client);
-
-        r = n_dhcp4_incoming_query_server_identifier(offer, &server);
-        if (r)
-                return r;
-
-        r = n_dhcp4_c_connection_new_message(connection, &message, N_DHCP4_C_MESSAGE_SELECT);
-        if (r)
-                return r;
-
-        r = n_dhcp4_outgoing_append(message, N_DHCP4_OPTION_REQUESTED_IP_ADDRESS, &client, sizeof(client));
-        if (r)
-                return r;
-
-        r = n_dhcp4_outgoing_append(message, N_DHCP4_OPTION_SERVER_IDENTIFIER, &server, sizeof(server));
-        if (r)
-                return r;
-
-        /*
-         * SELECT continues the transaction started by DISCOVER, and as such
-         * keeps the same start time. We also have to preserve the base time
-         * of the selected lease as well as the transaction ID.
-         */
-        message->userdata.start_time = offer->userdata.start_time;
-        message->userdata.base_time = offer->userdata.base_time;
-        message->userdata.client_addr = client.s_addr;
-        n_dhcp4_incoming_get_xid(offer, &xid);
-        n_dhcp4_outgoing_set_xid(message, xid);
-
-        *requestp = message;
-        message = NULL;
-        return 0;
-}
-
-/*
- *      RFC2131 4.3.2
- *
- *      'server identifier' MUST NOT be filled in, 'requested IP address'
- *      option MUST be filled in with client's notion of its previously
- *      assigned address. 'ciaddr' MUST be zero. The client is seeking to
- *      verify a previously allocated, cached configuration. Server SHOULD
- *      send a DHCPNAK message to the client if the 'requested IP address'
- *      is incorrect, or is on the wrong network.
- */
-int n_dhcp4_c_connection_reboot_new(NDhcp4CConnection *connection,
-                                    NDhcp4Outgoing **requestp,
-                                    const struct in_addr *client) {
-        _c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *message = NULL;
-        int r;
-
-        r = n_dhcp4_c_connection_new_message(connection, &message, N_DHCP4_C_MESSAGE_REBOOT);
-        if (r)
-                return r;
-
-        r = n_dhcp4_outgoing_append(message, N_DHCP4_OPTION_REQUESTED_IP_ADDRESS, client, sizeof(*client));
-        if (r)
-                return r;
-
-        *requestp = message;
-        message = NULL;
-        return 0;
-}
-
-/*
- *      RFC2131 4.3.2
- *
- *      'server identifier' MUST NOT be filled in, 'requested IP address'
- *      option MUST NOT be filled in, 'ciaddr' MUST be filled in with
- *      client's IP address. In this situation, the client is completely
- *      configured, and is trying to extend its lease. This message will
- *      be unicast, so no relay agents will be involved in its
- *      transmission.  Because 'giaddr' is therefore not filled in, the
- *      DHCP server will trust the value in 'ciaddr', and use it when
- *      replying to the client.
- *
- *      A client MAY choose to renew or extend its lease prior to T1.  The
- *      server may choose not to extend the lease (as a policy decision by
- *      the network administrator), but should return a DHCPACK message
- *      regardless.
- *
- *      RFC2131 4.4.5
- *
- *      At time T1 the client moves to RENEWING state and sends (via unicast)
- *      a DHCPREQUEST message to the server to extend its lease.  The client
- *      sets the 'ciaddr' field in the DHCPREQUEST to its current network
- *      address. The client records the local time at which the DHCPREQUEST
- *      message is sent for computation of the lease expiration time.  The
- *      client MUST NOT include a 'server identifier' in the DHCPREQUEST
- *      message.
- */
-int n_dhcp4_c_connection_renew_new(NDhcp4CConnection *connection,
-                                   NDhcp4Outgoing **requestp) {
-        _c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *message = NULL;
-        int r;
-
-        r = n_dhcp4_c_connection_new_message(connection, &message, N_DHCP4_C_MESSAGE_RENEW);
-        if (r)
-                return r;
-
-        message->userdata.client_addr = connection->client_ip;
-        *requestp = message;
-        message = NULL;
-        return 0;
-}
-
-/*
- *      RFC2131 4.3.2
- *
- *      'server identifier' MUST NOT be filled in, 'requested IP address'
- *      option MUST NOT be filled in, 'ciaddr' MUST be filled in with
- *      client's IP address. In this situation, the client is completely
- *      configured, and is trying to extend its lease. This message MUST
- *      be broadcast to the 0xffffffff IP broadcast address.  The DHCP
- *      server SHOULD check 'ciaddr' for correctness before replying to
- *      the DHCPREQUEST.
- *
- *      RFC2131 4.4.5
- *
- *      If no DHCPACK arrives before time T2, the client moves to REBINDING
- *      state and sends (via broadcast) a DHCPREQUEST message to extend its
- *      lease.  The client sets the 'ciaddr' field in the DHCPREQUEST to its
- *      current network address.  The client MUST NOT include a 'server
- *      identifier' in the DHCPREQUEST message.
- */
-int n_dhcp4_c_connection_rebind_new(NDhcp4CConnection *connection,
-                                    NDhcp4Outgoing **requestp) {
-        _c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *message = NULL;
-        int r;
-
-        r = n_dhcp4_c_connection_new_message(connection, &message, N_DHCP4_C_MESSAGE_REBIND);
-        if (r)
-                return r;
-
-        message->userdata.client_addr = connection->client_ip;
-        *requestp = message;
-        message = NULL;
-        return 0;
-}
-
-/*
- *      RFC2131 3.2
- *
- *      If the client detects that the IP address in the DHCPACK message
- *      is already in use, the client MUST send a DHCPDECLINE message to the
- *      server and restarts the configuration process by requesting a
- *      new network address.
- *
- *      RFC2131 4.4.4
- *
- *      Because the client is declining the use of the IP address supplied by
- *      the server, the client broadcasts DHCPDECLINE messages.
- */
-int n_dhcp4_c_connection_decline_new(NDhcp4CConnection *connection,
-                                     NDhcp4Outgoing **requestp,
-                                     NDhcp4Incoming *ack,
-                                     const char *error) {
-        _c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *message = NULL;
-        struct in_addr client;
-        struct in_addr server;
-        int r;
-
-        n_dhcp4_incoming_get_yiaddr(ack, &client);
-
-        r = n_dhcp4_incoming_query_server_identifier(ack, &server);
-        if (r)
-                return r;
-
-        r = n_dhcp4_c_connection_new_message(connection, &message, N_DHCP4_C_MESSAGE_DECLINE);
-        if (r)
-                return r;
-
-        r = n_dhcp4_outgoing_append(message, N_DHCP4_OPTION_REQUESTED_IP_ADDRESS, &client, sizeof(client));
-        if (r)
-                return r;
-
-        r = n_dhcp4_outgoing_append(message, N_DHCP4_OPTION_SERVER_IDENTIFIER, &server, sizeof(server));
-        if (r)
-                return r;
-
-        if (error) {
-                r = n_dhcp4_outgoing_append(message, N_DHCP4_OPTION_ERROR_MESSAGE, error, strlen(error) + 1);
-                if (r)
-                        return r;
-        }
-
-        message->userdata.client_addr = client.s_addr;
-        *requestp = message;
-        message = NULL;
-        return 0;
-}
-
-/*
- *      RFC2131 3.4
- *
- *      If a client has obtained a network address through some other means
- *      (e.g., manual configuration), it may use a DHCPINFORM request message
- *      to obtain other local configuration parameters.
- *
- *      RFC2131 4.4
- *
- *      The DHCPINFORM message is not shown in figure 5.  A client simply
- *      sends the DHCPINFORM and waits for DHCPACK messages.  Once the client
- *      has selected its parameters, it has completed the configuration
- *      process.
- *
- *      RFC2131 4.4.3
- *
- *      The client sends a DHCPINFORM message. The client may request
- *      specific configuration parameters by including the 'parameter request
- *      list' option. The client generates and records a random transaction
- *      identifier and inserts that identifier into the 'xid' field. The
- *      client places its own network address in the 'ciaddr' field. The
- *      client SHOULD NOT request lease time parameters.
- *
- *      The client then unicasts the DHCPINFORM to the DHCP server if it
- *      knows the server's address, otherwise it broadcasts the message to
- *      the limited (all 1s) broadcast address.  DHCPINFORM messages MUST be
- *      directed to the 'DHCP server' UDP port.
- */
-int n_dhcp4_c_connection_inform_new(NDhcp4CConnection *connection,
-                                    NDhcp4Outgoing **requestp) {
-        _c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *message = NULL;
-        int r;
-
-        r = n_dhcp4_c_connection_new_message(connection, &message, N_DHCP4_C_MESSAGE_INFORM);
-        if (r)
-                return r;
-
-        message->userdata.client_addr = connection->client_ip;
-        *requestp = message;
-        message = NULL;
-        return 0;
-}
-
-/*
- *      RFC2131 3.1
- *
- *      The client may choose to relinquish its lease on a network address
- *      by sending a DHCPRELEASE message to the server.  The client
- *      identifies the lease to be released with its 'client identifier',
- *      or 'chaddr' and network address in the DHCPRELEASE message. If the
- *      client used a 'client identifier' when it obtained the lease, it
- *      MUST use the same 'client identifier' in the DHCPRELEASE message.
- *
- *      RFC2131 3.2
- *
- *      The client may choose to relinquish its lease on a network
- *      address by sending a DHCPRELEASE message to the server.  The
- *      client identifies the lease to be released with its
- *      'client identifier', or 'chaddr' and network address in the
- *      DHCPRELEASE message.
- *
- *      Note that in this case, where the client retains its network
- *      address locally, the client will not normally relinquish its
- *      lease during a graceful shutdown.  Only in the case where the
- *      client explicitly needs to relinquish its lease, e.g., the client
- *      is about to be moved to a different subnet, will the client send
- *      a DHCPRELEASE message.
- *
- *      RFC2131 4.4.4
- *
- *      The client unicasts DHCPRELEASE messages to the server.
- *
- *      RFC2131 4.4.6
- *
- *      If the client no longer requires use of its assigned network address
- *      (e.g., the client is gracefully shut down), the client sends a
- *      DHCPRELEASE message to the server.  Note that the correct operation
- *      of DHCP does not depend on the transmission of DHCPRELEASE messages.
- */
-int n_dhcp4_c_connection_release_new(NDhcp4CConnection *connection,
-                                     NDhcp4Outgoing **requestp,
-                                     const char *error) {
-        _c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *message = NULL;
-        int r;
-
-        r = n_dhcp4_c_connection_new_message(connection, &message, N_DHCP4_C_MESSAGE_RELEASE);
-        if (r)
-                return r;
-
-        r = n_dhcp4_outgoing_append(message, N_DHCP4_OPTION_SERVER_IDENTIFIER, &connection->server_ip, sizeof(connection->server_ip));
-        if (r)
-                return r;
-
-        if (error) {
-                r = n_dhcp4_outgoing_append(message, N_DHCP4_OPTION_ERROR_MESSAGE, error, strlen(error) + 1);
-                if (r)
-                        return r;
-        }
-
-        *requestp = message;
-        message = NULL;
-        return 0;
-}
-
-static const char *message_type_to_str(uint8_t type) {
-        switch (type) {
-        case N_DHCP4_MESSAGE_DISCOVER:
-                return "DISCOVER";
-        case N_DHCP4_MESSAGE_OFFER:
-                return "OFFER";
-        case N_DHCP4_MESSAGE_REQUEST:
-                return "REQUEST";
-        case N_DHCP4_MESSAGE_DECLINE:
-                return "DECLINE";
-        case N_DHCP4_MESSAGE_ACK:
-                return "ACK";
-        case N_DHCP4_MESSAGE_NAK:
-                return "NACK";
-        case N_DHCP4_MESSAGE_RELEASE:
-                return "RELEASE";
-        case N_DHCP4_MESSAGE_INFORM:
-                return "INFORM";
-        case N_DHCP4_MESSAGE_FORCERENEW:
-                return "FORCERENEW";
-        default:
-                return "UNKNOWN";
-        }
-}
-
-static int n_dhcp4_c_connection_send_request(NDhcp4CConnection *connection,
-                                             NDhcp4Outgoing *request,
-                                             uint64_t timestamp) {
-        char server_addr[INET_ADDRSTRLEN];
-        char client_addr[INET_ADDRSTRLEN];
-        char error_msg[128];
-        int r;
-        bool broadcast = false;
-
-        /*
-         * Increment the base time and reset the xid field,
-         * where applicable. We never alter the header on
-         * resends of SELECT, as it must always match the
-         * OFFER message they are in reply to.
-         */
-        switch (request->userdata.type) {
-        case N_DHCP4_C_MESSAGE_DISCOVER:
-        case N_DHCP4_C_MESSAGE_INFORM:
-        case N_DHCP4_C_MESSAGE_REBOOT:
-        case N_DHCP4_C_MESSAGE_REBIND:
-        case N_DHCP4_C_MESSAGE_RENEW:
-                request->userdata.base_time = timestamp;
-                n_dhcp4_outgoing_set_xid(request, n_dhcp4_client_probe_config_get_random(connection->probe_config));
-
-                break;
-        case N_DHCP4_C_MESSAGE_SELECT:
-        case N_DHCP4_C_MESSAGE_DECLINE:
-        case N_DHCP4_C_MESSAGE_RELEASE:
-                break;
-        default:
-                c_assert(0);
-        }
-
-        request->userdata.send_time = timestamp;
-        request->userdata.send_jitter = (n_dhcp4_client_probe_config_get_random(connection->probe_config) % 1000000000ULL);
-        n_dhcp4_c_connection_outgoing_set_secs(request);
-
-        switch (request->userdata.type) {
-        case N_DHCP4_C_MESSAGE_DISCOVER:
-        case N_DHCP4_C_MESSAGE_SELECT:
-        case N_DHCP4_C_MESSAGE_REBOOT:
-        case N_DHCP4_C_MESSAGE_DECLINE:
-        case N_DHCP4_C_MESSAGE_REBIND:
-                broadcast = true;
-                r = n_dhcp4_c_connection_packet_broadcast(connection, request);
-                break;
-        case N_DHCP4_C_MESSAGE_INFORM:
-                broadcast = true;
-                r = n_dhcp4_c_connection_udp_broadcast(connection, request);
-                break;
-        case N_DHCP4_C_MESSAGE_RENEW:
-        case N_DHCP4_C_MESSAGE_RELEASE:
-                r = n_dhcp4_c_connection_udp_send(connection, request);
-                break;
-        default:
-                c_assert(0);
-        }
-
-        if (r) {
-                snprintf(error_msg, sizeof(error_msg), ": error %d", r);
-        } else {
-                error_msg[0] = '\0';
-        }
-
-        if (request->userdata.client_addr == INADDR_ANY) {
-                n_dhcp4_log(connection->log_queue,
-                            LOG_INFO,
-                            "send %s to %s%s",
-                            message_type_to_str(request->userdata.message_type),
-                            broadcast ?
-                            "255.255.255.255" :
-                            inet_ntop(AF_INET, &connection->server_ip,
-                                      server_addr, sizeof(server_addr)),
-                            error_msg);
-        } else {
-                n_dhcp4_log(connection->log_queue,
-                            LOG_INFO,
-                            "send %s of %s to %s%s",
-                            message_type_to_str(request->userdata.message_type),
-                            inet_ntop(AF_INET, &request->userdata.client_addr,
-                                      client_addr, sizeof(client_addr)),
-                            broadcast ?
-                            "255.255.255.255" :
-                            inet_ntop(AF_INET, &connection->server_ip,
-                                      server_addr, sizeof(server_addr)),
-                            error_msg);
-        }
-
-        ++request->userdata.n_send;
-        return 0;
-}
-
-int n_dhcp4_c_connection_start_request(NDhcp4CConnection *connection,
-                                       NDhcp4Outgoing *request,
-                                       uint64_t timestamp) {
-        int r;
-
-        /*
-         * This function starts a request, but in the case of SELECT it
-         * continues a previous transaction, so we do not want to reset
-         * the start time. Only set the start time if it was not already
-         * set.
-         */
-        if (request->userdata.start_time == 0)
-                request->userdata.start_time = timestamp;
-
-        connection->request = n_dhcp4_outgoing_free(connection->request);
-
-        r = n_dhcp4_c_connection_send_request(connection, request, timestamp);
-        if (r)
-                return r;
-
-        connection->request = request;
-
-        return 0;
-}
-
-int n_dhcp4_c_connection_dispatch_timer(NDhcp4CConnection *connection,
-                                        uint64_t timestamp) {
-        uint64_t timeout;
-        int r;
-
-        if (!connection->request)
-                return 0;
-
-        n_dhcp4_c_connection_get_timeout(connection, &timeout);
-
-        if (timeout > timestamp)
-                return 0;
-
-        r = n_dhcp4_c_connection_send_request(connection, connection->request, timestamp);
-        if (r)
-                return r;
-
-        return 0;
-}
-
-/*
- * Returns:
- *  0                     on success
- *  N_DHCP4_E_MALFORMED   if a malformed packet was received
- *  N_DHCP4_E_UNEXPECTED  if the packet received contains unexpected data
- *  N_DHCP4_E_AGAIN       if there was another error (non fatal for the client)
- */
-int n_dhcp4_c_connection_dispatch_io(NDhcp4CConnection *connection,
-                                     NDhcp4Incoming **messagep) {
-        _c_cleanup_(n_dhcp4_incoming_freep) NDhcp4Incoming *message = NULL;
-        char serv_addr[INET_ADDRSTRLEN];
-        char client_addr[INET_ADDRSTRLEN];
-        uint8_t type;
-        int r;
-
-        switch (connection->state) {
-        case N_DHCP4_C_CONNECTION_STATE_PACKET:
-                r = n_dhcp4_c_socket_packet_recv(connection->fd_packet,
-                                                 connection->scratch_buffer,
-                                                 sizeof(connection->scratch_buffer),
-                                                 &message);
-                if (!r)
-                        break;
-                else if (r == N_DHCP4_E_MALFORMED)
-                        return r;
-                return N_DHCP4_E_AGAIN;
-        case N_DHCP4_C_CONNECTION_STATE_DRAINING:
-                r = n_dhcp4_c_socket_packet_recv(connection->fd_packet,
-                                                 connection->scratch_buffer,
-                                                 sizeof(connection->scratch_buffer),
-                                                 &message);
-                if (!r)
-                        break;
-                else if (r == N_DHCP4_E_MALFORMED)
-                        return r;
-                else if (r != N_DHCP4_E_AGAIN)
-                        return N_DHCP4_E_AGAIN;
-
-                /*
-                 * The UDP socket is open and the packet socket has been shut down
-                 * and drained, clean up the packet socket and fall through to
-                 * dispatching the UDP socket.
-                 */
-                r = epoll_ctl(connection->fd_epoll, EPOLL_CTL_DEL, connection->fd_packet, NULL);
-                c_assert(!r);
-                connection->fd_packet = c_close(connection->fd_packet);
-                connection->state = N_DHCP4_C_CONNECTION_STATE_UDP;
-
-                /* fall-through */
-        case N_DHCP4_C_CONNECTION_STATE_UDP:
-                r = n_dhcp4_c_socket_udp_recv(connection->fd_udp,
-                                              connection->scratch_buffer,
-                                              sizeof(connection->scratch_buffer),
-                                              &message);
-                if (!r)
-                        break;
-                else if (r == N_DHCP4_E_MALFORMED)
-                        return r;
-                return N_DHCP4_E_AGAIN;
-        default:
-                abort();
-                return -ENOTRECOVERABLE;
-        }
-
-        r = n_dhcp4_c_connection_verify_incoming(connection, message, &type);
-        if (r == N_DHCP4_E_MALFORMED || r == N_DHCP4_E_UNEXPECTED)
-                return r;
-        else if (r != 0)
-                return N_DHCP4_E_AGAIN;
-
-        if (type == N_DHCP4_MESSAGE_OFFER || type == N_DHCP4_MESSAGE_ACK) {
-                n_dhcp4_log(connection->log_queue,
-                            LOG_INFO,
-                            "received %s of %s from %s",
-                            message_type_to_str(type),
-                            inet_ntop(AF_INET, &message->message.header.yiaddr,
-                                      client_addr, sizeof(client_addr)),
-                            inet_ntop(AF_INET, &message->message.header.siaddr,
-                                      serv_addr, sizeof(serv_addr)));
-        } else {
-                n_dhcp4_log(connection->log_queue,
-                            LOG_INFO,
-                            "received %s from %s",
-                            message_type_to_str(type),
-                            inet_ntop(AF_INET, &message->message.header.siaddr,
-                                      serv_addr, sizeof(serv_addr)));
-        }
-
-        switch (type) {
-        case N_DHCP4_MESSAGE_OFFER:
-        case N_DHCP4_MESSAGE_ACK:
-        case N_DHCP4_MESSAGE_NAK:
-                /*
-                 * Remember the start time of the transaction, and the base
-                 * time of any relative timestamps from the pending request.
-                 * The same time applies to the response, and should be
-                 * copied over.
-                 */
-                message->userdata.start_time = connection->request->userdata.start_time;
-                message->userdata.base_time = connection->request->userdata.base_time;
-
-                if (type != N_DHCP4_MESSAGE_OFFER) {
-                        /*
-                         * We only allow one reply to ACK or NAK, but for OFFER we must
-                         * accept several, so we do not free the pinned request.
-                         */
-                        connection->request = n_dhcp4_outgoing_free(connection->request);
-                }
-
-                break;
-        default:
-                break;
-        }
-
-        *messagep = message;
-        message = NULL;
-        return 0;
-}