X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=blobdiff_plain;f=src%2Flibsystemd-network%2Fsd-dhcp-client.c;h=2f94c160783279abf5b05cc2522eaf05127fa54b;hp=854c6711a8f82cd35ee82e53fad85edd79c6630c;hb=6ff8806e1d20a4d3cbc4b360326ea3786768f0ca;hpb=6317519517210f703e20dc846b31d018845244cd diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index 854c6711a..2f94c1607 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -30,6 +30,7 @@ #include "util.h" #include "list.h" #include "refcnt.h" +#include "async.h" #include "dhcp-protocol.h" #include "dhcp-internal.h" @@ -47,6 +48,7 @@ struct sd_dhcp_client { int fd; union sockaddr_union link; sd_event_source *receive_message; + bool request_broadcast; uint8_t *req_opts; size_t req_opts_allocated; size_t req_opts_size; @@ -55,6 +57,9 @@ struct sd_dhcp_client { uint8_t type; struct ether_addr mac_addr; } _packed_ client_id; + char *hostname; + char *vendor_class_identifier; + uint32_t mtu; uint32_t xid; usec_t start_time; uint16_t secs; @@ -81,7 +86,7 @@ static int client_receive_message_raw(sd_event_source *s, int fd, uint32_t revents, void *userdata); static int client_receive_message_udp(sd_event_source *s, int fd, uint32_t revents, void *userdata); -static sd_dhcp_client *client_stop(sd_dhcp_client *client, int error); +static void client_stop(sd_dhcp_client *client, int error); int sd_dhcp_client_set_callback(sd_dhcp_client *client, sd_dhcp_client_cb_t cb, void *userdata) { @@ -93,6 +98,14 @@ int sd_dhcp_client_set_callback(sd_dhcp_client *client, sd_dhcp_client_cb_t cb, return 0; } +int sd_dhcp_client_set_request_broadcast(sd_dhcp_client *client, int broadcast) { + assert_return(client, -EINVAL); + + client->request_broadcast = !!broadcast; + + return 0; +} + int sd_dhcp_client_set_request_option(sd_dhcp_client *client, uint8_t option) { size_t i; @@ -143,7 +156,7 @@ int sd_dhcp_client_set_index(sd_dhcp_client *client, int interface_index) { assert_return(client, -EINVAL); assert_return (IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_STOPPED), -EBUSY); - assert_return(interface_index >= -1, -EINVAL); + assert_return(interface_index > 0, -EINVAL); client->index = interface_index; @@ -152,6 +165,7 @@ int sd_dhcp_client_set_index(sd_dhcp_client *client, int interface_index) { int sd_dhcp_client_set_mac(sd_dhcp_client *client, const struct ether_addr *addr) { + DHCP_CLIENT_DONT_DESTROY(client); bool need_restart = false; assert_return(client, -EINVAL); @@ -164,12 +178,9 @@ int sd_dhcp_client_set_mac(sd_dhcp_client *client, log_dhcp_client(client, "Changing MAC address on running DHCP " "client, restarting"); need_restart = true; - client = client_stop(client, DHCP_EVENT_STOP); + client_stop(client, DHCP_EVENT_STOP); } - if (!client) - return 0; - memcpy(&client->client_id.mac_addr, addr, ETH_ALEN); client->client_id.type = 0x01; @@ -179,6 +190,53 @@ int sd_dhcp_client_set_mac(sd_dhcp_client *client, return 0; } +int sd_dhcp_client_set_hostname(sd_dhcp_client *client, + const char *hostname) { + char *new_hostname = NULL; + + assert_return(client, -EINVAL); + + if (streq_ptr(client->hostname, hostname)) + return 0; + + if (hostname) { + new_hostname = strdup(hostname); + if (!new_hostname) + return -ENOMEM; + } + + free(client->hostname); + client->hostname = new_hostname; + + return 0; +} + +int sd_dhcp_client_set_vendor_class_identifier(sd_dhcp_client *client, + const char *vci) { + char *new_vci = NULL; + + assert_return(client, -EINVAL); + + new_vci = strdup(vci); + if (!new_vci) + return -ENOMEM; + + free(client->vendor_class_identifier); + + client->vendor_class_identifier = new_vci; + + return 0; +} + +int sd_dhcp_client_set_mtu(sd_dhcp_client *client, uint32_t mtu) { + assert_return(client, -EINVAL); + assert_return(mtu >= DHCP_DEFAULT_MIN_SIZE, -ERANGE); + + client->mtu = mtu; + + return 0; +} + int sd_dhcp_client_get_lease(sd_dhcp_client *client, sd_dhcp_lease **ret) { assert_return(client, -EINVAL); assert_return(ret, -EINVAL); @@ -193,14 +251,9 @@ int sd_dhcp_client_get_lease(sd_dhcp_client *client, sd_dhcp_lease **ret) { return 0; } -static sd_dhcp_client *client_notify(sd_dhcp_client *client, int event) { - if (client->cb) { - client = sd_dhcp_client_ref(client); +static void client_notify(sd_dhcp_client *client, int event) { + if (client->cb) client->cb(client, event, client->userdata); - client = sd_dhcp_client_unref(client); - } - - return client; } static int client_initialize(sd_dhcp_client *client) { @@ -209,7 +262,7 @@ static int client_initialize(sd_dhcp_client *client) { client->receive_message = sd_event_source_unref(client->receive_message); - client->fd = safe_close(client->fd); + client->fd = asynchronous_close(client->fd); client->timeout_resend = sd_event_source_unref(client->timeout_resend); @@ -228,49 +281,75 @@ static int client_initialize(sd_dhcp_client *client) { return 0; } -static sd_dhcp_client *client_stop(sd_dhcp_client *client, int error) { - assert_return(client, NULL); +static void client_stop(sd_dhcp_client *client, int error) { + assert(client); - log_dhcp_client(client, "STOPPED: %s", strerror(-error)); + if (error < 0) + log_dhcp_client(client, "STOPPED: %s", strerror(-error)); + else if (error == DHCP_EVENT_STOP) + log_dhcp_client(client, "STOPPED"); + else + log_dhcp_client(client, "STOPPED: Unknown event"); - client = client_notify(client, error); + client_notify(client, error); - if (client) - client_initialize(client); - - return client; + client_initialize(client); } -static int client_message_init(sd_dhcp_client *client, DHCPMessage *message, - uint8_t type, uint8_t **opt, size_t *optlen) { +static int client_message_init(sd_dhcp_client *client, DHCPPacket **ret, + uint8_t type, size_t *_optlen, size_t *_optoffset) { + _cleanup_free_ DHCPPacket *packet; + size_t optlen, optoffset, size; be16_t max_size; int r; assert(client); assert(client->secs); - assert(message); - assert(opt); - assert(optlen); + assert(ret); + assert(_optlen); + assert(_optoffset); assert(type == DHCP_DISCOVER || type == DHCP_REQUEST); - r = dhcp_message_init(message, BOOTREQUEST, client->xid, type, opt, - optlen); + optlen = DHCP_MIN_OPTIONS_SIZE; + size = sizeof(DHCPPacket) + optlen; + + packet = malloc0(size); + if (!packet) + return -ENOMEM; + + r = dhcp_message_init(&packet->dhcp, BOOTREQUEST, client->xid, type, + optlen, &optoffset); if (r < 0) return r; /* Although 'secs' field is a SHOULD in RFC 2131, certain DHCP servers refuse to issue an DHCP lease if 'secs' is set to zero */ - message->secs = htobe16(client->secs); + packet->dhcp.secs = htobe16(client->secs); + + /* RFC2132 section 4.1 + A client that cannot receive unicast IP datagrams until its protocol + software has been configured with an IP address SHOULD set the + BROADCAST bit in the 'flags' field to 1 in any DHCPDISCOVER or + DHCPREQUEST messages that client sends. The BROADCAST bit will + provide a hint to the DHCP server and BOOTP relay agent to broadcast + any messages to the client on the client's subnet. + + Note: some interfaces needs this to be enabled, but some networks + needs this to be disabled as broadcasts are filteretd, so this + needs to be configurable */ + if (client->request_broadcast) + packet->dhcp.flags = htobe16(0x8000); /* RFC2132 section 4.1.1: The client MUST include its hardware address in the ’chaddr’ field, if necessary for delivery of DHCP reply messages. */ - memcpy(&message->chaddr, &client->client_id.mac_addr, ETH_ALEN); + memcpy(&packet->dhcp.chaddr, &client->client_id.mac_addr, ETH_ALEN); /* Some DHCP servers will refuse to issue an DHCP lease if the Client Identifier option is not set */ - r = dhcp_option_append(opt, optlen, DHCP_OPTION_CLIENT_IDENTIFIER, + r = dhcp_option_append(&packet->dhcp, optlen, &optoffset, 0, + DHCP_OPTION_CLIENT_IDENTIFIER, sizeof(client->client_id), &client->client_id); if (r < 0) return r; @@ -284,10 +363,9 @@ static int client_message_init(sd_dhcp_client *client, DHCPMessage *message, it MUST include that list in any subsequent DHCPREQUEST messages. */ - r = dhcp_option_append(opt, optlen, + r = dhcp_option_append(&packet->dhcp, optlen, &optoffset, 0, DHCP_OPTION_PARAMETER_REQUEST_LIST, - client->req_opts_size, - client->req_opts); + client->req_opts_size, client->req_opts); if (r < 0) return r; @@ -298,15 +376,33 @@ static int client_message_init(sd_dhcp_client *client, DHCPMessage *message, Note (from ConnMan): Some DHCP servers will send bigger DHCP packets than the defined default size unless the Maximum Messge Size option is explicitely set + + RFC3442 "Requirements to Avoid Sizing Constraints": + Because a full routing table can be quite large, the standard 576 + octet maximum size for a DHCP message may be too short to contain + some legitimate Classless Static Route options. Because of this, + clients implementing the Classless Static Route option SHOULD send a + Maximum DHCP Message Size [4] option if the DHCP client's TCP/IP + stack is capable of receiving larger IP datagrams. In this case, the + client SHOULD set the value of this option to at least the MTU of the + interface that the client is configuring. The client MAY set the + value of this option higher, up to the size of the largest UDP packet + it is prepared to accept. (Note that the value specified in the + Maximum DHCP Message Size option is the total maximum packet size, + including IP and UDP headers.) */ - max_size = htobe16(DHCP_IP_UDP_SIZE + DHCP_MESSAGE_SIZE + - DHCP_MIN_OPTIONS_SIZE); - r = dhcp_option_append(opt, optlen, + max_size = htobe16(size); + r = dhcp_option_append(&packet->dhcp, client->mtu, &optoffset, 0, DHCP_OPTION_MAXIMUM_MESSAGE_SIZE, 2, &max_size); if (r < 0) return r; + *_optlen = optlen; + *_optoffset = optoffset; + *ret = packet; + packet = NULL; + return 0; } @@ -321,8 +417,7 @@ static int dhcp_client_send_raw(sd_dhcp_client *client, DHCPPacket *packet, static int client_send_discover(sd_dhcp_client *client) { _cleanup_free_ DHCPPacket *discover = NULL; - size_t optlen, len; - uint8_t *opt; + size_t optoffset, optlen; usec_t time_now; int r; @@ -332,7 +427,7 @@ static int client_send_discover(sd_dhcp_client *client) { /* See RFC2131 section 4.4.1 */ - r = sd_event_now(client->event, CLOCK_MONOTONIC, &time_now); + r = sd_event_now(client->event, clock_boottime_or_monotonic(), &time_now); if (r < 0) return r; assert(time_now >= client->start_time); @@ -341,15 +436,8 @@ static int client_send_discover(sd_dhcp_client *client) { * must always be strictly positive to deal with broken servers */ client->secs = ((time_now - client->start_time) / USEC_PER_SEC) ? : 1; - optlen = DHCP_MIN_OPTIONS_SIZE; - len = sizeof(DHCPPacket) + optlen; - - discover = malloc0(len); - if (!discover) - return -ENOMEM; - - r = client_message_init(client, &discover->dhcp, DHCP_DISCOVER, - &opt, &optlen); + r = client_message_init(client, &discover, DHCP_DISCOVER, + &optlen, &optoffset); if (r < 0) return r; @@ -360,14 +448,35 @@ static int client_send_discover(sd_dhcp_client *client) { option to suggest the lease time it would like. */ if (client->last_addr != INADDR_ANY) { - r = dhcp_option_append(&opt, &optlen, - DHCP_OPTION_REQUESTED_IP_ADDRESS, - 4, &client->last_addr); + r = dhcp_option_append(&discover->dhcp, optlen, &optoffset, 0, + DHCP_OPTION_REQUESTED_IP_ADDRESS, + 4, &client->last_addr); if (r < 0) return r; } - r = dhcp_option_append(&opt, &optlen, DHCP_OPTION_END, 0, NULL); + /* it is unclear from RFC 2131 if client should send hostname in + DHCPDISCOVER but dhclient does and so we do as well + */ + if (client->hostname) { + r = dhcp_option_append(&discover->dhcp, optlen, &optoffset, 0, + DHCP_OPTION_HOST_NAME, + strlen(client->hostname), client->hostname); + if (r < 0) + return r; + } + + if (client->vendor_class_identifier) { + r = dhcp_option_append(&discover->dhcp, optlen, &optoffset, 0, + DHCP_OPTION_VENDOR_CLASS_IDENTIFIER, + strlen(client->vendor_class_identifier), + client->vendor_class_identifier); + if (r < 0) + return r; + } + + r = dhcp_option_append(&discover->dhcp, optlen, &optoffset, 0, + DHCP_OPTION_END, 0, NULL); if (r < 0) return r; @@ -375,7 +484,7 @@ static int client_send_discover(sd_dhcp_client *client) { The client SHOULD wait a random time between one and ten seconds to desynchronize the use of DHCP at startup. */ - r = dhcp_client_send_raw(client, discover, len - optlen); + r = dhcp_client_send_raw(client, discover, sizeof(DHCPPacket) + optoffset); if (r < 0) return r; @@ -385,20 +494,12 @@ static int client_send_discover(sd_dhcp_client *client) { } static int client_send_request(sd_dhcp_client *client) { - _cleanup_free_ DHCPPacket *request; - size_t optlen, len; - uint8_t *opt; + _cleanup_free_ DHCPPacket *request = NULL; + size_t optoffset, optlen; int r; - optlen = DHCP_MIN_OPTIONS_SIZE; - len = sizeof(DHCPPacket) + optlen; - - request = malloc0(len); - if (!request) - return -ENOMEM; - - r = client_message_init(client, &request->dhcp, DHCP_REQUEST, &opt, - &optlen); + r = client_message_init(client, &request, DHCP_REQUEST, + &optlen, &optoffset); if (r < 0) return r; @@ -413,13 +514,13 @@ static int client_send_request(sd_dhcp_client *client) { filled in with the yiaddr value from the chosen DHCPOFFER. */ - r = dhcp_option_append(&opt, &optlen, + r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0, DHCP_OPTION_SERVER_IDENTIFIER, 4, &client->lease->server_address); if (r < 0) return r; - r = dhcp_option_append(&opt, &optlen, + r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0, DHCP_OPTION_REQUESTED_IP_ADDRESS, 4, &client->lease->address); if (r < 0) @@ -432,7 +533,7 @@ static int client_send_request(sd_dhcp_client *client) { option MUST be filled in with client’s notion of its previously assigned address. ’ciaddr’ MUST be zero. */ - r = dhcp_option_append(&opt, &optlen, + r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0, DHCP_OPTION_REQUESTED_IP_ADDRESS, 4, &client->last_addr); if (r < 0) @@ -465,7 +566,16 @@ static int client_send_request(sd_dhcp_client *client) { return -EINVAL; } - r = dhcp_option_append(&opt, &optlen, DHCP_OPTION_END, 0, NULL); + if (client->hostname) { + r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0, + DHCP_OPTION_HOST_NAME, + strlen(client->hostname), client->hostname); + if (r < 0) + return r; + } + + r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0, + DHCP_OPTION_END, 0, NULL); if (r < 0) return r; @@ -474,9 +584,9 @@ static int client_send_request(sd_dhcp_client *client) { client->lease->server_address, DHCP_PORT_SERVER, &request->dhcp, - len - optlen - DHCP_IP_UDP_SIZE); + sizeof(DHCPMessage) + optoffset); } else { - r = dhcp_client_send_raw(client, request, len - optlen); + r = dhcp_client_send_raw(client, request, sizeof(DHCPPacket) + optoffset); } if (r < 0) return r; @@ -507,6 +617,7 @@ static int client_start(sd_dhcp_client *client); static int client_timeout_resend(sd_event_source *s, uint64_t usec, void *userdata) { sd_dhcp_client *client = userdata; + DHCP_CLIENT_DONT_DESTROY(client); usec_t next_timeout = 0; uint64_t time_now; uint32_t time_left; @@ -516,7 +627,7 @@ static int client_timeout_resend(sd_event_source *s, uint64_t usec, assert(client); assert(client->event); - r = sd_event_now(client->event, CLOCK_MONOTONIC, &time_now); + r = sd_event_now(client->event, clock_boottime_or_monotonic(), &time_now); if (r < 0) goto error; @@ -578,7 +689,7 @@ static int client_timeout_resend(sd_event_source *s, uint64_t usec, r = sd_event_add_time(client->event, &client->timeout_resend, - CLOCK_MONOTONIC, + clock_boottime_or_monotonic(), next_timeout, 10 * USEC_PER_MSEC, client_timeout_resend, client); if (r < 0) @@ -589,6 +700,11 @@ static int client_timeout_resend(sd_event_source *s, uint64_t usec, if (r < 0) goto error; + r = sd_event_source_set_name(client->timeout_resend, + "dhcp4-resend-timer"); + if (r < 0) + goto error; + switch (client->state) { case DHCP_STATE_INIT: r = client_send_discover(client); @@ -644,8 +760,8 @@ error: return 0; } -static int client_initialize_events(sd_dhcp_client *client, - sd_event_io_handler_t io_callback) { +static int client_initialize_io_events(sd_dhcp_client *client, + sd_event_io_handler_t io_callback) { int r; assert(client); @@ -662,11 +778,29 @@ static int client_initialize_events(sd_dhcp_client *client, if (r < 0) goto error; + r = sd_event_source_set_name(client->receive_message, + "dhcp4-receive-message"); + if (r < 0) + goto error; + +error: + if (r < 0) + client_stop(client, r); + + return 0; +} + +static int client_initialize_time_events(sd_dhcp_client *client) { + int r; + + assert(client); + assert(client->event); + client->timeout_resend = sd_event_source_unref(client->timeout_resend); r = sd_event_add_time(client->event, &client->timeout_resend, - CLOCK_MONOTONIC, + clock_boottime_or_monotonic(), 0, 0, client_timeout_resend, client); if (r < 0) @@ -675,6 +809,11 @@ static int client_initialize_events(sd_dhcp_client *client, r = sd_event_source_set_priority(client->timeout_resend, client->event_priority); + r = sd_event_source_set_name(client->timeout_resend, + "dhcp4-resend-timer"); + if (r < 0) + goto error; + error: if (r < 0) client_stop(client, r); @@ -683,6 +822,14 @@ error: } +static int client_initialize_events(sd_dhcp_client *client, + sd_event_io_handler_t io_callback) { + client_initialize_io_events(client, io_callback); + client_initialize_time_events(client); + + return 0; +} + static int client_start(sd_dhcp_client *client) { int r; @@ -696,7 +843,7 @@ static int client_start(sd_dhcp_client *client) { client->xid = random_u32(); - r = dhcp_network_bind_raw_socket(client->index, &client->link, client->xid); + r = dhcp_network_bind_raw_socket(client->index, &client->link, client->xid, client->client_id.mac_addr); if (r < 0) { client_stop(client, r); return r; @@ -704,7 +851,7 @@ static int client_start(sd_dhcp_client *client) { client->fd = r; if (client->state == DHCP_STATE_INIT) { - client->start_time = now(CLOCK_MONOTONIC); + client->start_time = now(clock_boottime_or_monotonic()); client->secs = 0; } @@ -714,13 +861,14 @@ static int client_start(sd_dhcp_client *client) { static int client_timeout_expire(sd_event_source *s, uint64_t usec, void *userdata) { sd_dhcp_client *client = userdata; + DHCP_CLIENT_DONT_DESTROY(client); log_dhcp_client(client, "EXPIRED"); - client = client_notify(client, DHCP_EVENT_EXPIRED); + client_notify(client, DHCP_EVENT_EXPIRED); /* lease was lost, start over if not freed or stopped in callback */ - if (client && client->state != DHCP_STATE_STOPPED) { + if (client->state != DHCP_STATE_STOPPED) { client_initialize(client); client_start(client); } @@ -730,15 +878,16 @@ static int client_timeout_expire(sd_event_source *s, uint64_t usec, static int client_timeout_t2(sd_event_source *s, uint64_t usec, void *userdata) { sd_dhcp_client *client = userdata; + DHCP_CLIENT_DONT_DESTROY(client); int r; client->receive_message = sd_event_source_unref(client->receive_message); - client->fd = safe_close(client->fd); + client->fd = asynchronous_close(client->fd); client->state = DHCP_STATE_REBINDING; client->attempt = 1; - r = dhcp_network_bind_raw_socket(client->index, &client->link, client->xid); + r = dhcp_network_bind_raw_socket(client->index, &client->link, client->xid, client->client_id.mac_addr); if (r < 0) { client_stop(client, r); return 0; @@ -751,22 +900,12 @@ static int client_timeout_t2(sd_event_source *s, uint64_t usec, void *userdata) static int client_timeout_t1(sd_event_source *s, uint64_t usec, void *userdata) { sd_dhcp_client *client = userdata; - int r; + DHCP_CLIENT_DONT_DESTROY(client); client->state = DHCP_STATE_RENEWING; client->attempt = 1; - r = dhcp_network_bind_udp_socket(client->index, - client->lease->address, - DHCP_PORT_CLIENT); - if (r < 0) { - client_stop(client, r); - return 0; - } - - client->fd = r; - - return client_initialize_events(client, client_receive_message_udp); + return client_initialize_time_events(client); } static int client_handle_offer(sd_dhcp_client *client, DHCPMessage *offer, @@ -780,7 +919,7 @@ static int client_handle_offer(sd_dhcp_client *client, DHCPMessage *offer, r = dhcp_option_parse(offer, len, dhcp_lease_parse_options, lease); if (r != DHCP_OFFER) { - log_dhcp_client(client, "receieved message was not an OFFER, ignoring"); + log_dhcp_client(client, "received message was not an OFFER, ignoring"); return -ENOMSG; } @@ -791,7 +930,7 @@ static int client_handle_offer(sd_dhcp_client *client, DHCPMessage *offer, if (lease->address == INADDR_ANY || lease->server_address == INADDR_ANY || lease->lifetime == 0) { - log_dhcp_client(client, "receieved lease lacks address, server " + log_dhcp_client(client, "received lease lacks address, server " "address or lease lifetime, ignoring"); return -ENOMSG; } @@ -799,7 +938,7 @@ static int client_handle_offer(sd_dhcp_client *client, DHCPMessage *offer, if (lease->subnet_mask == INADDR_ANY) { r = dhcp_lease_set_default_subnet_mask(lease); if (r < 0) { - log_dhcp_client(client, "receieved lease lacks subnet " + log_dhcp_client(client, "received lease lacks subnet " "mask, and a fallback one can not be " "generated, ignoring"); return -ENOMSG; @@ -815,6 +954,19 @@ static int client_handle_offer(sd_dhcp_client *client, DHCPMessage *offer, return 0; } +static int client_handle_forcerenew(sd_dhcp_client *client, DHCPMessage *force, + size_t len) { + int r; + + r = dhcp_option_parse(force, len, NULL, NULL); + if (r != DHCP_FORCERENEW) + return -ENOMSG; + + log_dhcp_client(client, "FORCERENEW"); + + return 0; +} + static int client_handle_ack(sd_dhcp_client *client, DHCPMessage *ack, size_t len) { _cleanup_dhcp_lease_unref_ sd_dhcp_lease *lease = NULL; @@ -827,11 +979,11 @@ static int client_handle_ack(sd_dhcp_client *client, DHCPMessage *ack, r = dhcp_option_parse(ack, len, dhcp_lease_parse_options, lease); if (r == DHCP_NAK) { log_dhcp_client(client, "NAK"); - return DHCP_EVENT_NO_LEASE; + return -EADDRNOTAVAIL; } if (r != DHCP_ACK) { - log_dhcp_client(client, "receieved message was not an ACK, ignoring"); + log_dhcp_client(client, "received message was not an ACK, ignoring"); return -ENOMSG; } @@ -842,7 +994,7 @@ static int client_handle_ack(sd_dhcp_client *client, DHCPMessage *ack, if (lease->address == INADDR_ANY || lease->server_address == INADDR_ANY || lease->lifetime == 0) { - log_dhcp_client(client, "receieved lease lacks address, server " + log_dhcp_client(client, "received lease lacks address, server " "address or lease lifetime, ignoring"); return -ENOMSG; } @@ -850,7 +1002,7 @@ static int client_handle_ack(sd_dhcp_client *client, DHCPMessage *ack, if (lease->subnet_mask == INADDR_ANY) { r = dhcp_lease_set_default_subnet_mask(lease); if (r < 0) { - log_dhcp_client(client, "receieved lease lacks subnet " + log_dhcp_client(client, "received lease lacks subnet " "mask, and a fallback one can not be " "generated, ignoring"); return -ENOMSG; @@ -863,7 +1015,8 @@ static int client_handle_ack(sd_dhcp_client *client, DHCPMessage *ack, client->lease->subnet_mask != lease->subnet_mask || client->lease->router != lease->router) { r = DHCP_EVENT_IP_CHANGE; - } + } else + r = DHCP_EVENT_RENEW; client->lease = sd_dhcp_lease_unref(client->lease); } @@ -907,7 +1060,7 @@ static int client_set_lease_timeouts(sd_dhcp_client *client) { if (client->lease->lifetime == 0xffffffff) return 0; - r = sd_event_now(client->event, CLOCK_MONOTONIC, &time_now); + r = sd_event_now(client->event, clock_boottime_or_monotonic(), &time_now); if (r < 0) return r; assert(client->request_sent <= time_now); @@ -958,7 +1111,7 @@ static int client_set_lease_timeouts(sd_dhcp_client *client) { /* arm lifetime timeout */ r = sd_event_add_time(client->event, &client->timeout_expire, - CLOCK_MONOTONIC, + clock_boottime_or_monotonic(), lifetime_timeout, 10 * USEC_PER_MSEC, client_timeout_expire, client); if (r < 0) @@ -969,6 +1122,11 @@ static int client_set_lease_timeouts(sd_dhcp_client *client) { if (r < 0) return r; + r = sd_event_source_set_name(client->timeout_expire, + "dhcp4-lifetime"); + if (r < 0) + return r; + log_dhcp_client(client, "lease expires in %s", format_timespan(time_string, FORMAT_TIMESPAN_MAX, lifetime_timeout - time_now, 0)); @@ -980,7 +1138,7 @@ static int client_set_lease_timeouts(sd_dhcp_client *client) { /* arm T2 timeout */ r = sd_event_add_time(client->event, &client->timeout_t2, - CLOCK_MONOTONIC, + clock_boottime_or_monotonic(), t2_timeout, 10 * USEC_PER_MSEC, client_timeout_t2, client); @@ -992,6 +1150,11 @@ static int client_set_lease_timeouts(sd_dhcp_client *client) { if (r < 0) return r; + r = sd_event_source_set_name(client->timeout_t2, + "dhcp4-t2-timeout"); + if (r < 0) + return r; + log_dhcp_client(client, "T2 expires in %s", format_timespan(time_string, FORMAT_TIMESPAN_MAX, t2_timeout - time_now, 0)); @@ -1003,7 +1166,7 @@ static int client_set_lease_timeouts(sd_dhcp_client *client) { /* arm T1 timeout */ r = sd_event_add_time(client->event, &client->timeout_t1, - CLOCK_MONOTONIC, + clock_boottime_or_monotonic(), t1_timeout, 10 * USEC_PER_MSEC, client_timeout_t1, client); if (r < 0) @@ -1014,6 +1177,11 @@ static int client_set_lease_timeouts(sd_dhcp_client *client) { if (r < 0) return r; + r = sd_event_source_set_name(client->timeout_t1, + "dhcp4-t1-timer"); + if (r < 0) + return r; + log_dhcp_client(client, "T1 expires in %s", format_timespan(time_string, FORMAT_TIMESPAN_MAX, t1_timeout - time_now, 0)); @@ -1023,41 +1191,13 @@ static int client_set_lease_timeouts(sd_dhcp_client *client) { static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, int len) { + DHCP_CLIENT_DONT_DESTROY(client); int r = 0, notify_event = 0; assert(client); assert(client->event); assert(message); - if (be32toh(message->magic) != DHCP_MAGIC_COOKIE) { - log_dhcp_client(client, "not a DHCP message: ignoring"); - return 0; - } - - if (message->op != BOOTREPLY) { - log_dhcp_client(client, "not a BOOTREPLY message: ignoring"); - return 0; - } - - if (be32toh(message->xid) != client->xid) { - log_dhcp_client(client, "received xid (%u) does not match " - "expected (%u): ignoring", - be32toh(message->xid), client->xid); - return 0; - } - - if (message->htype != ARPHRD_ETHER || message->hlen != ETHER_ADDR_LEN) { - log_dhcp_client(client, "not an ethernet packet"); - return 0; - } - - if (memcmp(&message->chaddr[0], &client->client_id.mac_addr, - ETH_ALEN)) { - log_dhcp_client(client, "received chaddr does not match " - "expected: ignoring"); - return 0; - } - switch (client->state) { case DHCP_STATE_SELECTING: @@ -1072,7 +1212,7 @@ static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, r = sd_event_add_time(client->event, &client->timeout_resend, - CLOCK_MONOTONIC, + clock_boottime_or_monotonic(), 0, 0, client_timeout_resend, client); if (r < 0) @@ -1082,6 +1222,11 @@ static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, client->event_priority); if (r < 0) goto error; + + r = sd_event_source_set_name(client->timeout_resend, + "dhcp4-resend-timer"); + if (r < 0) + goto error; } else if (r == -ENOMSG) /* invalid message, let's ignore it */ return 0; @@ -1094,25 +1239,7 @@ static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, case DHCP_STATE_REBINDING: r = client_handle_ack(client, message, len); - if (r == DHCP_EVENT_NO_LEASE) { - - client->timeout_resend = - sd_event_source_unref(client->timeout_resend); - - if (client->state == DHCP_STATE_REBOOTING) { - r = client_initialize(client); - if (r < 0) - goto error; - - r = client_start(client); - if (r < 0) - goto error; - - log_dhcp_client(client, "REBOOTED"); - } - - goto error; - } else if (r >= 0) { + if (r >= 0) { client->timeout_resend = sd_event_source_unref(client->timeout_resend); @@ -1131,16 +1258,51 @@ static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, if (r < 0) goto error; + r = dhcp_network_bind_udp_socket(client->lease->address, + DHCP_PORT_CLIENT); + if (r < 0) { + log_dhcp_client(client, "could not bind UDP socket"); + goto error; + } + + client->fd = r; + + client_initialize_io_events(client, client_receive_message_udp); + if (notify_event) { - client = client_notify(client, notify_event); - if (!client || - client->state == DHCP_STATE_STOPPED) + client_notify(client, notify_event); + if (client->state == DHCP_STATE_STOPPED) return 0; } - client->receive_message = - sd_event_source_unref(client->receive_message); - client->fd = safe_close(client->fd); + } else if (r == -EADDRNOTAVAIL) { + /* got a NAK, let's restart the client */ + client->timeout_resend = + sd_event_source_unref(client->timeout_resend); + + r = client_initialize(client); + if (r < 0) + goto error; + + r = client_start(client); + if (r < 0) + goto error; + + log_dhcp_client(client, "REBOOTED"); + + return 0; + } else if (r == -ENOMSG) + /* invalid message, let's ignore it */ + return 0; + + break; + + case DHCP_STATE_BOUND: + r = client_handle_forcerenew(client, message, len); + if (r >= 0) { + r = client_timeout_t1(NULL, 0, client); + if (r < 0) + goto error; } else if (r == -ENOMSG) /* invalid message, let's ignore it */ return 0; @@ -1149,7 +1311,6 @@ static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, case DHCP_STATE_INIT: case DHCP_STATE_INIT_REBOOT: - case DHCP_STATE_BOUND: break; @@ -1159,7 +1320,7 @@ static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, } error: - if (r < 0 || r == DHCP_EVENT_NO_LEASE) + if (r < 0) client_stop(client, r); return r; @@ -1175,8 +1336,12 @@ static int client_receive_message_udp(sd_event_source *s, int fd, assert(client); r = ioctl(fd, FIONREAD, &buflen); - if (r < 0 || buflen <= 0) - buflen = sizeof(DHCPMessage) + DHCP_MIN_OPTIONS_SIZE; + if (r < 0) + return r; + + if (buflen < 0) + /* this can't be right */ + return -EIO; message = malloc0(buflen); if (!message) @@ -1185,10 +1350,44 @@ static int client_receive_message_udp(sd_event_source *s, int fd, len = read(fd, message, buflen); if (len < 0) { log_dhcp_client(client, "could not receive message from UDP " - "socket: %s", strerror(errno)); + "socket: %m"); + return 0; + } else if ((size_t)len < sizeof(DHCPMessage)) { + log_dhcp_client(client, "too small to be a DHCP message: ignoring"); + return 0; + } + + if (be32toh(message->magic) != DHCP_MAGIC_COOKIE) { + log_dhcp_client(client, "not a DHCP message: ignoring"); + return 0; + } + + if (message->op != BOOTREPLY) { + log_dhcp_client(client, "not a BOOTREPLY message: ignoring"); + return 0; + } + + if (message->htype != ARPHRD_ETHER || message->hlen != ETHER_ADDR_LEN) { + log_dhcp_client(client, "not an ethernet packet"); + return 0; + } + + if (memcmp(&message->chaddr[0], &client->client_id.mac_addr, + ETH_ALEN)) { + log_dhcp_client(client, "received chaddr does not match " + "expected: ignoring"); return 0; - } else if ((size_t)len < sizeof(DHCPMessage)) + } + + if (client->state != DHCP_STATE_BOUND && + be32toh(message->xid) != client->xid) { + /* in BOUND state, we may receive FORCERENEW with xid set by server, + so ignore the xid in this case */ + log_dhcp_client(client, "received xid (%u) does not match " + "expected (%u): ignoring", + be32toh(message->xid), client->xid); return 0; + } return client_handle_message(client, message, len); } @@ -1213,8 +1412,12 @@ static int client_receive_message_raw(sd_event_source *s, int fd, assert(client); r = ioctl(fd, FIONREAD, &buflen); - if (r < 0 || buflen <= 0) - buflen = sizeof(DHCPPacket) + DHCP_MIN_OPTIONS_SIZE; + if (r < 0) + return r; + + if (buflen < 0) + /* this can't be right */ + return -EIO; packet = malloc0(buflen); if (!packet) @@ -1226,7 +1429,7 @@ static int client_receive_message_raw(sd_event_source *s, int fd, len = recvmsg(fd, &msg, 0); if (len < 0) { log_dhcp_client(client, "could not receive message from raw " - "socket: %s", strerror(errno)); + "socket: %m"); return 0; } else if ((size_t)len < sizeof(DHCPPacket)) return 0; @@ -1252,6 +1455,7 @@ static int client_receive_message_raw(sd_event_source *s, int fd, } int sd_dhcp_client_start(sd_dhcp_client *client) { + char buffer[ETHER_ADDR_TO_STRING_MAX]; int r; assert_return(client, -EINVAL); @@ -1267,16 +1471,18 @@ int sd_dhcp_client_start(sd_dhcp_client *client) { if (r >= 0) log_dhcp_client(client, "STARTED on ifindex %u with address %s", client->index, - ether_ntoa(&client->client_id.mac_addr)); + ether_addr_to_string(&client->client_id.mac_addr, buffer)); return r; } int sd_dhcp_client_stop(sd_dhcp_client *client) { + DHCP_CLIENT_DONT_DESTROY(client); + assert_return(client, -EINVAL); - if (client_stop(client, DHCP_EVENT_STOP)) - client->state = DHCP_STATE_STOPPED; + client_stop(client, DHCP_EVENT_STOP); + client->state = DHCP_STATE_STOPPED; return 0; } @@ -1325,7 +1531,7 @@ sd_dhcp_client *sd_dhcp_client_ref(sd_dhcp_client *client) { sd_dhcp_client *sd_dhcp_client_unref(sd_dhcp_client *client) { if (client && REFCNT_DEC(client->n_ref) <= 0) { - log_dhcp_client(client, "UNREF"); + log_dhcp_client(client, "FREE"); client_initialize(client); @@ -1337,19 +1543,16 @@ sd_dhcp_client *sd_dhcp_client_unref(sd_dhcp_client *client) { sd_dhcp_lease_unref(client->lease); free(client->req_opts); + free(client->hostname); + free(client->vendor_class_identifier); free(client); - - return NULL; } - return client; + return NULL; } -DEFINE_TRIVIAL_CLEANUP_FUNC(sd_dhcp_client*, sd_dhcp_client_unref); -#define _cleanup_dhcp_client_free_ _cleanup_(sd_dhcp_client_unrefp) - int sd_dhcp_client_new(sd_dhcp_client **ret) { - _cleanup_dhcp_client_free_ sd_dhcp_client *client = NULL; + _cleanup_dhcp_client_unref_ sd_dhcp_client *client = NULL; assert_return(ret, -EINVAL); @@ -1362,6 +1565,7 @@ int sd_dhcp_client_new(sd_dhcp_client **ret) { client->index = -1; client->fd = -1; client->attempt = 1; + client->mtu = DHCP_DEFAULT_MIN_SIZE; client->req_opts_size = ELEMENTSOF(default_req_opts);