diff options
Diffstat (limited to 'shared/n-dhcp4/src/n-dhcp4-c-connection.c')
| -rw-r--r-- | shared/n-dhcp4/src/n-dhcp4-c-connection.c | 1256 |
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; -} |