sd-dhcp6-client.c revision 7bd8e95d44977833d0de3fc4e893eb3bc84351d6
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye/***
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye This file is part of systemd.
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye Copyright (C) 2014-2015 Intel Corporation. All rights reserved.
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye systemd is free software; you can redistribute it and/or modify it
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye under the terms of the GNU Lesser General Public License as published by
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye the Free Software Foundation; either version 2.1 of the License, or
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye (at your option) any later version.
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye systemd is distributed in the hope that it will be useful, but
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye WITHOUT ANY WARRANTY; without even the implied warranty of
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye Lesser General Public License for more details.
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye You should have received a copy of the GNU Lesser General Public License
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye along with systemd; If not, see <http://www.gnu.org/licenses/>.
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye***/
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye#include <errno.h>
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye#include <string.h>
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye#include <sys/ioctl.h>
cf1f7b5e81583dfca30972cfef322266a6928e7fKnut Anders Hatlen#include <linux/if_infiniband.h>
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye#include "udev.h"
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye#include "udev-util.h"
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye#include "util.h"
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye#include "refcnt.h"
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye#include "random-util.h"
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye#include "network-internal.h"
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye#include "sd-dhcp6-client.h"
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye#include "dhcp6-protocol.h"
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye#include "dhcp6-internal.h"
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye#include "dhcp6-lease-internal.h"
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye#include "dhcp-identifier.h"
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye#define MAX_MAC_ADDR_LEN INFINIBAND_ALEN
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye
64b763950bf11e9357facbd2b5666631a895c085Trond Norbyestruct sd_dhcp6_client {
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye RefCount n_ref;
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye enum DHCP6State state;
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye sd_event *event;
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye int event_priority;
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye int index;
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye uint8_t mac_addr[MAX_MAC_ADDR_LEN];
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye size_t mac_addr_len;
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye uint16_t arp_type;
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye DHCP6IA ia_na;
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye be32_t transaction_id;
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye usec_t transaction_start;
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye struct sd_dhcp6_lease *lease;
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye int fd;
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye bool information_request;
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye be16_t *req_opts;
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye size_t req_opts_allocated;
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye size_t req_opts_len;
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye sd_event_source *receive_message;
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye usec_t retransmit_time;
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye uint8_t retransmit_count;
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye sd_event_source *timeout_resend;
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye sd_event_source *timeout_resend_expire;
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye sd_dhcp6_client_cb_t cb;
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye void *userdata;
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye struct duid duid;
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye size_t duid_len;
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye};
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye
64b763950bf11e9357facbd2b5666631a895c085Trond Norbyestatic const uint16_t default_req_opts[] = {
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye DHCP6_OPTION_DNS_SERVERS,
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye DHCP6_OPTION_DOMAIN_LIST,
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye DHCP6_OPTION_NTP_SERVER,
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye};
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye
64b763950bf11e9357facbd2b5666631a895c085Trond Norbyeconst char * dhcp6_message_type_table[_DHCP6_MESSAGE_MAX] = {
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye [DHCP6_SOLICIT] = "SOLICIT",
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye [DHCP6_ADVERTISE] = "ADVERTISE",
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye [DHCP6_REQUEST] = "REQUEST",
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye [DHCP6_CONFIRM] = "CONFIRM",
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye [DHCP6_RENEW] = "RENEW",
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye [DHCP6_REBIND] = "REBIND",
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye [DHCP6_REPLY] = "REPLY",
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye [DHCP6_RELEASE] = "RELEASE",
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye [DHCP6_DECLINE] = "DECLINE",
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye [DHCP6_RECONFIGURE] = "RECONFIGURE",
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye [DHCP6_INFORMATION_REQUEST] = "INFORMATION-REQUEST",
0a0811923cbbd2976425db6f4c78eed811c2825bKnut Anders Hatlen [DHCP6_RELAY_FORW] = "RELAY-FORW",
77e3bcb700604954082585e3d7107004769a9f48Trond Norbye [DHCP6_RELAY_REPL] = "RELAY-REPL",
a07b2874263e3c5f0cd2e83441719415d53059c2Knut Anders Hatlen};
c7eb123c8b2081a261deff3c401fbf92ddba1b58Jorgen Austvik
c7eb123c8b2081a261deff3c401fbf92ddba1b58Jorgen AustvikDEFINE_STRING_TABLE_LOOKUP(dhcp6_message_type, int);
49f592091468eac515dde6139fbc8efa26056b0aJorgen Austvik
1ed6b730409d4740e941142599767d5eac7e7d92Lubos Koscoconst char * dhcp6_message_status_table[_DHCP6_STATUS_MAX] = {
7ecd52b03dc1f0b03ff8f522b4891c8531896c3dJorgen Austvik [DHCP6_STATUS_SUCCESS] = "Success",
7ecd52b03dc1f0b03ff8f522b4891c8531896c3dJorgen Austvik [DHCP6_STATUS_UNSPEC_FAIL] = "Unspecified failure",
7ecd52b03dc1f0b03ff8f522b4891c8531896c3dJorgen Austvik [DHCP6_STATUS_NO_ADDRS_AVAIL] = "No addresses available",
7ecd52b03dc1f0b03ff8f522b4891c8531896c3dJorgen Austvik [DHCP6_STATUS_NO_BINDING] = "Binding unavailable",
7ecd52b03dc1f0b03ff8f522b4891c8531896c3dJorgen Austvik [DHCP6_STATUS_NOT_ON_LINK] = "Not on link",
7ecd52b03dc1f0b03ff8f522b4891c8531896c3dJorgen Austvik [DHCP6_STATUS_USE_MULTICAST] = "Use multicast",
7ecd52b03dc1f0b03ff8f522b4891c8531896c3dJorgen Austvik};
c7eb123c8b2081a261deff3c401fbf92ddba1b58Jorgen Austvik
c7eb123c8b2081a261deff3c401fbf92ddba1b58Jorgen AustvikDEFINE_STRING_TABLE_LOOKUP(dhcp6_message_status, int);
7ecd52b03dc1f0b03ff8f522b4891c8531896c3dJorgen Austvik
7ecd52b03dc1f0b03ff8f522b4891c8531896c3dJorgen AustvikDEFINE_TRIVIAL_CLEANUP_FUNC(sd_dhcp6_client*, sd_dhcp6_client_unref);
7ecd52b03dc1f0b03ff8f522b4891c8531896c3dJorgen Austvik#define _cleanup_dhcp6_client_unref_ _cleanup_(sd_dhcp6_client_unrefp)
49f592091468eac515dde6139fbc8efa26056b0aJorgen Austvik
49f592091468eac515dde6139fbc8efa26056b0aJorgen Austvik#define DHCP6_CLIENT_DONT_DESTROY(client) \
49f592091468eac515dde6139fbc8efa26056b0aJorgen Austvik _cleanup_dhcp6_client_unref_ _unused_ sd_dhcp6_client *_dont_destroy_##client = sd_dhcp6_client_ref(client)
49f592091468eac515dde6139fbc8efa26056b0aJorgen Austvik
49f592091468eac515dde6139fbc8efa26056b0aJorgen Austvikstatic int client_start(sd_dhcp6_client *client, enum DHCP6State state);
49f592091468eac515dde6139fbc8efa26056b0aJorgen Austvik
1ed6b730409d4740e941142599767d5eac7e7d92Lubos Koscoint sd_dhcp6_client_set_callback(sd_dhcp6_client *client,
7ecd52b03dc1f0b03ff8f522b4891c8531896c3dJorgen Austvik sd_dhcp6_client_cb_t cb, void *userdata)
1ed6b730409d4740e941142599767d5eac7e7d92Lubos Kosco{
7ecd52b03dc1f0b03ff8f522b4891c8531896c3dJorgen Austvik assert_return(client, -EINVAL);
1ed6b730409d4740e941142599767d5eac7e7d92Lubos Kosco
1ed6b730409d4740e941142599767d5eac7e7d92Lubos Kosco client->cb = cb;
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye client->userdata = userdata;
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye return 0;
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye}
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye
64b763950bf11e9357facbd2b5666631a895c085Trond Norbyeint sd_dhcp6_client_set_index(sd_dhcp6_client *client, int interface_index)
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye{
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye assert_return(client, -EINVAL);
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye assert_return(interface_index >= -1, -EINVAL);
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye
225d5411e0f1f0e690e3553aad7a97c648d566a1HemangLavana client->index = interface_index;
225d5411e0f1f0e690e3553aad7a97c648d566a1HemangLavana
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye return 0;
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye}
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye
64b763950bf11e9357facbd2b5666631a895c085Trond Norbyeint sd_dhcp6_client_set_mac(sd_dhcp6_client *client, const uint8_t *addr,
75640e2b0da81c240758d747e76d30acd1ed194dKnut Anders Hatlen size_t addr_len, uint16_t arp_type)
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye{
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye assert_return(client, -EINVAL);
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye assert_return(addr, -EINVAL);
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye assert_return(addr_len > 0 && addr_len <= MAX_MAC_ADDR_LEN, -EINVAL);
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye assert_return(arp_type > 0, -EINVAL);
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye if (arp_type == ARPHRD_ETHER)
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye assert_return(addr_len == ETH_ALEN, -EINVAL);
7b046969a1b2565787df8ae3a8126359e8cd6fafTrond Norbye else if (arp_type == ARPHRD_INFINIBAND)
7b046969a1b2565787df8ae3a8126359e8cd6fafTrond Norbye assert_return(addr_len == INFINIBAND_ALEN, -EINVAL);
7b046969a1b2565787df8ae3a8126359e8cd6fafTrond Norbye else
7b046969a1b2565787df8ae3a8126359e8cd6fafTrond Norbye return -EINVAL;
7b046969a1b2565787df8ae3a8126359e8cd6fafTrond Norbye
7b046969a1b2565787df8ae3a8126359e8cd6fafTrond Norbye if (client->mac_addr_len == addr_len &&
7b046969a1b2565787df8ae3a8126359e8cd6fafTrond Norbye memcmp(&client->mac_addr, addr, addr_len) == 0)
7b046969a1b2565787df8ae3a8126359e8cd6fafTrond Norbye return 0;
7b046969a1b2565787df8ae3a8126359e8cd6fafTrond Norbye
7b046969a1b2565787df8ae3a8126359e8cd6fafTrond Norbye memcpy(&client->mac_addr, addr, addr_len);
7b046969a1b2565787df8ae3a8126359e8cd6fafTrond Norbye client->mac_addr_len = addr_len;
7b046969a1b2565787df8ae3a8126359e8cd6fafTrond Norbye client->arp_type = arp_type;
7b046969a1b2565787df8ae3a8126359e8cd6fafTrond Norbye
7b046969a1b2565787df8ae3a8126359e8cd6fafTrond Norbye return 0;
7b046969a1b2565787df8ae3a8126359e8cd6fafTrond Norbye}
7b046969a1b2565787df8ae3a8126359e8cd6fafTrond Norbye
7b046969a1b2565787df8ae3a8126359e8cd6fafTrond Norbyestatic int client_ensure_duid(sd_dhcp6_client *client)
7b046969a1b2565787df8ae3a8126359e8cd6fafTrond Norbye{
7b046969a1b2565787df8ae3a8126359e8cd6fafTrond Norbye if (client->duid_len != 0)
7b046969a1b2565787df8ae3a8126359e8cd6fafTrond Norbye return 0;
7b046969a1b2565787df8ae3a8126359e8cd6fafTrond Norbye return dhcp_identifier_set_duid_en(&client->duid, &client->duid_len);
7b046969a1b2565787df8ae3a8126359e8cd6fafTrond Norbye}
7b046969a1b2565787df8ae3a8126359e8cd6fafTrond Norbye
7b046969a1b2565787df8ae3a8126359e8cd6fafTrond Norbyeint sd_dhcp6_client_set_duid(sd_dhcp6_client *client, uint16_t type, uint8_t *duid,
7b046969a1b2565787df8ae3a8126359e8cd6fafTrond Norbye size_t duid_len)
7b046969a1b2565787df8ae3a8126359e8cd6fafTrond Norbye{
7b046969a1b2565787df8ae3a8126359e8cd6fafTrond Norbye assert_return(client, -EINVAL);
7b046969a1b2565787df8ae3a8126359e8cd6fafTrond Norbye assert_return(duid, -EINVAL);
7b046969a1b2565787df8ae3a8126359e8cd6fafTrond Norbye assert_return(duid_len > 0 && duid_len <= MAX_DUID_LEN, -EINVAL);
7b046969a1b2565787df8ae3a8126359e8cd6fafTrond Norbye
2e3c025fdd5908a27cc82eb1d5346368a8be4e0dJorgen Austvik switch (type) {
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye case DHCP6_DUID_LLT:
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye if (duid_len <= sizeof(client->duid.llt))
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye return -EINVAL;
2dbc1835e0ae88ad102e2b9a85e5c7b5298b14b6Knut Anders Hatlen break;
2e3c025fdd5908a27cc82eb1d5346368a8be4e0dJorgen Austvik case DHCP6_DUID_EN:
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye if (duid_len != sizeof(client->duid.en))
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye return -EINVAL;
9cf297d9a579835e9336d587eaee187ca0954767Knut Anders Hatlen break;
2dbc1835e0ae88ad102e2b9a85e5c7b5298b14b6Knut Anders Hatlen case DHCP6_DUID_LL:
2dbc1835e0ae88ad102e2b9a85e5c7b5298b14b6Knut Anders Hatlen if (duid_len <= sizeof(client->duid.ll))
9cf297d9a579835e9336d587eaee187ca0954767Knut Anders Hatlen return -EINVAL;
2dbc1835e0ae88ad102e2b9a85e5c7b5298b14b6Knut Anders Hatlen break;
2dbc1835e0ae88ad102e2b9a85e5c7b5298b14b6Knut Anders Hatlen case DHCP6_DUID_UUID:
2dbc1835e0ae88ad102e2b9a85e5c7b5298b14b6Knut Anders Hatlen if (duid_len != sizeof(client->duid.uuid))
2dbc1835e0ae88ad102e2b9a85e5c7b5298b14b6Knut Anders Hatlen return -EINVAL;
2dbc1835e0ae88ad102e2b9a85e5c7b5298b14b6Knut Anders Hatlen break;
2dbc1835e0ae88ad102e2b9a85e5c7b5298b14b6Knut Anders Hatlen default:
2dbc1835e0ae88ad102e2b9a85e5c7b5298b14b6Knut Anders Hatlen /* accept unknown type in order to be forward compatible */
2dbc1835e0ae88ad102e2b9a85e5c7b5298b14b6Knut Anders Hatlen break;
2dbc1835e0ae88ad102e2b9a85e5c7b5298b14b6Knut Anders Hatlen }
2dbc1835e0ae88ad102e2b9a85e5c7b5298b14b6Knut Anders Hatlen
2dbc1835e0ae88ad102e2b9a85e5c7b5298b14b6Knut Anders Hatlen client->duid.type = htobe16(type);
2dbc1835e0ae88ad102e2b9a85e5c7b5298b14b6Knut Anders Hatlen memcpy(&client->duid.raw.data, duid, duid_len);
2dbc1835e0ae88ad102e2b9a85e5c7b5298b14b6Knut Anders Hatlen client->duid_len = duid_len + sizeof(client->duid.type);
9cf297d9a579835e9336d587eaee187ca0954767Knut Anders Hatlen
9cf297d9a579835e9336d587eaee187ca0954767Knut Anders Hatlen return 0;
9cf297d9a579835e9336d587eaee187ca0954767Knut Anders Hatlen}
9cf297d9a579835e9336d587eaee187ca0954767Knut Anders Hatlen
2dbc1835e0ae88ad102e2b9a85e5c7b5298b14b6Knut Anders Hatlenint sd_dhcp6_client_set_information_request(sd_dhcp6_client *client,
9cf297d9a579835e9336d587eaee187ca0954767Knut Anders Hatlen bool enabled) {
2dbc1835e0ae88ad102e2b9a85e5c7b5298b14b6Knut Anders Hatlen assert_return(client, -EINVAL);
2dbc1835e0ae88ad102e2b9a85e5c7b5298b14b6Knut Anders Hatlen
2dbc1835e0ae88ad102e2b9a85e5c7b5298b14b6Knut Anders Hatlen client->information_request = enabled;
2dbc1835e0ae88ad102e2b9a85e5c7b5298b14b6Knut Anders Hatlen
2dbc1835e0ae88ad102e2b9a85e5c7b5298b14b6Knut Anders Hatlen return 0;
2dbc1835e0ae88ad102e2b9a85e5c7b5298b14b6Knut Anders Hatlen}
2dbc1835e0ae88ad102e2b9a85e5c7b5298b14b6Knut Anders Hatlen
2dbc1835e0ae88ad102e2b9a85e5c7b5298b14b6Knut Anders Hatlenint sd_dhcp6_client_get_information_request(sd_dhcp6_client *client,
2dbc1835e0ae88ad102e2b9a85e5c7b5298b14b6Knut Anders Hatlen bool *enabled) {
7b046969a1b2565787df8ae3a8126359e8cd6fafTrond Norbye assert_return(client, -EINVAL);
2dbc1835e0ae88ad102e2b9a85e5c7b5298b14b6Knut Anders Hatlen assert_return(enabled, -EINVAL);
2dbc1835e0ae88ad102e2b9a85e5c7b5298b14b6Knut Anders Hatlen
2dbc1835e0ae88ad102e2b9a85e5c7b5298b14b6Knut Anders Hatlen *enabled = client->information_request;
2dbc1835e0ae88ad102e2b9a85e5c7b5298b14b6Knut Anders Hatlen
2dbc1835e0ae88ad102e2b9a85e5c7b5298b14b6Knut Anders Hatlen return 0;
2dbc1835e0ae88ad102e2b9a85e5c7b5298b14b6Knut Anders Hatlen}
2dbc1835e0ae88ad102e2b9a85e5c7b5298b14b6Knut Anders Hatlen
9661674ed58ba62a40e43d1a4b38d5e77c3c6545Knut Anders Hatlenint sd_dhcp6_client_set_request_option(sd_dhcp6_client *client,
9661674ed58ba62a40e43d1a4b38d5e77c3c6545Knut Anders Hatlen uint16_t option) {
9661674ed58ba62a40e43d1a4b38d5e77c3c6545Knut Anders Hatlen size_t t;
2dbc1835e0ae88ad102e2b9a85e5c7b5298b14b6Knut Anders Hatlen
2dbc1835e0ae88ad102e2b9a85e5c7b5298b14b6Knut Anders Hatlen assert_return(client, -EINVAL);
9cf297d9a579835e9336d587eaee187ca0954767Knut Anders Hatlen assert_return(client->state == DHCP6_STATE_STOPPED, -EBUSY);
9cf297d9a579835e9336d587eaee187ca0954767Knut Anders Hatlen
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye switch(option) {
9cf297d9a579835e9336d587eaee187ca0954767Knut Anders Hatlen case DHCP6_OPTION_DNS_SERVERS:
c0550b01024b910b8c1468811c0ea663b10b1372Trond Norbye case DHCP6_OPTION_DOMAIN_LIST:
c0550b01024b910b8c1468811c0ea663b10b1372Trond Norbye case DHCP6_OPTION_SNTP_SERVERS:
c0550b01024b910b8c1468811c0ea663b10b1372Trond Norbye case DHCP6_OPTION_NTP_SERVER:
c0550b01024b910b8c1468811c0ea663b10b1372Trond Norbye break;
9794eb72d2b2da2812cdba67562851047cce4600Trond Norbye
c0550b01024b910b8c1468811c0ea663b10b1372Trond Norbye default:
523201f786f6b12b7cf54091c6e5be167878cbeeTrond Norbye return -EINVAL;
523201f786f6b12b7cf54091c6e5be167878cbeeTrond Norbye }
523201f786f6b12b7cf54091c6e5be167878cbeeTrond Norbye
523201f786f6b12b7cf54091c6e5be167878cbeeTrond Norbye for (t = 0; t < client->req_opts_len; t++)
523201f786f6b12b7cf54091c6e5be167878cbeeTrond Norbye if (client->req_opts[t] == htobe16(option))
523201f786f6b12b7cf54091c6e5be167878cbeeTrond Norbye return -EEXIST;
523201f786f6b12b7cf54091c6e5be167878cbeeTrond Norbye
523201f786f6b12b7cf54091c6e5be167878cbeeTrond Norbye if (!GREEDY_REALLOC(client->req_opts, client->req_opts_allocated,
c0550b01024b910b8c1468811c0ea663b10b1372Trond Norbye client->req_opts_len + 1))
0a0811923cbbd2976425db6f4c78eed811c2825bKnut Anders Hatlen return -ENOMEM;
0a0811923cbbd2976425db6f4c78eed811c2825bKnut Anders Hatlen
0a0811923cbbd2976425db6f4c78eed811c2825bKnut Anders Hatlen client->req_opts[client->req_opts_len++] = htobe16(option);
0a0811923cbbd2976425db6f4c78eed811c2825bKnut Anders Hatlen
0a0811923cbbd2976425db6f4c78eed811c2825bKnut Anders Hatlen return 0;
0a0811923cbbd2976425db6f4c78eed811c2825bKnut Anders Hatlen}
0a0811923cbbd2976425db6f4c78eed811c2825bKnut Anders Hatlen
0a0811923cbbd2976425db6f4c78eed811c2825bKnut Anders Hatlenint sd_dhcp6_client_get_lease(sd_dhcp6_client *client, sd_dhcp6_lease **ret) {
0a0811923cbbd2976425db6f4c78eed811c2825bKnut Anders Hatlen assert_return(client, -EINVAL);
a07b2874263e3c5f0cd2e83441719415d53059c2Knut Anders Hatlen assert_return(ret, -EINVAL);
a07b2874263e3c5f0cd2e83441719415d53059c2Knut Anders Hatlen
a07b2874263e3c5f0cd2e83441719415d53059c2Knut Anders Hatlen if (!client->lease)
a07b2874263e3c5f0cd2e83441719415d53059c2Knut Anders Hatlen return -ENOMSG;
a07b2874263e3c5f0cd2e83441719415d53059c2Knut Anders Hatlen
a07b2874263e3c5f0cd2e83441719415d53059c2Knut Anders Hatlen *ret = sd_dhcp6_lease_ref(client->lease);
a07b2874263e3c5f0cd2e83441719415d53059c2Knut Anders Hatlen
a07b2874263e3c5f0cd2e83441719415d53059c2Knut Anders Hatlen return 0;
a07b2874263e3c5f0cd2e83441719415d53059c2Knut Anders Hatlen}
a07b2874263e3c5f0cd2e83441719415d53059c2Knut Anders Hatlen
a07b2874263e3c5f0cd2e83441719415d53059c2Knut Anders Hatlenstatic void client_notify(sd_dhcp6_client *client, int event) {
cdf37a6f2e4e7ca653ef8e791cc8e720fa148a39Peter Bray if (client->cb)
a07b2874263e3c5f0cd2e83441719415d53059c2Knut Anders Hatlen client->cb(client, event, client->userdata);
a07b2874263e3c5f0cd2e83441719415d53059c2Knut Anders Hatlen}
a07b2874263e3c5f0cd2e83441719415d53059c2Knut Anders Hatlen
a07b2874263e3c5f0cd2e83441719415d53059c2Knut Anders Hatlenstatic int client_reset(sd_dhcp6_client *client) {
a07b2874263e3c5f0cd2e83441719415d53059c2Knut Anders Hatlen assert_return(client, -EINVAL);
a07b2874263e3c5f0cd2e83441719415d53059c2Knut Anders Hatlen
a07b2874263e3c5f0cd2e83441719415d53059c2Knut Anders Hatlen if (client->lease) {
a07b2874263e3c5f0cd2e83441719415d53059c2Knut Anders Hatlen dhcp6_lease_clear_timers(&client->lease->ia);
a07b2874263e3c5f0cd2e83441719415d53059c2Knut Anders Hatlen client->lease = sd_dhcp6_lease_unref(client->lease);
a07b2874263e3c5f0cd2e83441719415d53059c2Knut Anders Hatlen }
a07b2874263e3c5f0cd2e83441719415d53059c2Knut Anders Hatlen
a07b2874263e3c5f0cd2e83441719415d53059c2Knut Anders Hatlen client->receive_message =
a07b2874263e3c5f0cd2e83441719415d53059c2Knut Anders Hatlen sd_event_source_unref(client->receive_message);
a07b2874263e3c5f0cd2e83441719415d53059c2Knut Anders Hatlen
a07b2874263e3c5f0cd2e83441719415d53059c2Knut Anders Hatlen client->fd = safe_close(client->fd);
edcb01bf549171673fd0bb4239f2edfc7a810397Knut Anders Hatlen
edcb01bf549171673fd0bb4239f2edfc7a810397Knut Anders Hatlen client->transaction_id = 0;
edcb01bf549171673fd0bb4239f2edfc7a810397Knut Anders Hatlen client->transaction_start = 0;
edcb01bf549171673fd0bb4239f2edfc7a810397Knut Anders Hatlen
edcb01bf549171673fd0bb4239f2edfc7a810397Knut Anders Hatlen client->ia_na.timeout_t1 =
edcb01bf549171673fd0bb4239f2edfc7a810397Knut Anders Hatlen sd_event_source_unref(client->ia_na.timeout_t1);
edcb01bf549171673fd0bb4239f2edfc7a810397Knut Anders Hatlen client->ia_na.timeout_t2 =
edcb01bf549171673fd0bb4239f2edfc7a810397Knut Anders Hatlen sd_event_source_unref(client->ia_na.timeout_t2);
edcb01bf549171673fd0bb4239f2edfc7a810397Knut Anders Hatlen
85e0595857351c6e22f75b8928967d14cb679ac5Jorgen Austvik client->retransmit_time = 0;
7ecd52b03dc1f0b03ff8f522b4891c8531896c3dJorgen Austvik client->retransmit_count = 0;
7ecd52b03dc1f0b03ff8f522b4891c8531896c3dJorgen Austvik client->timeout_resend = sd_event_source_unref(client->timeout_resend);
7ecd52b03dc1f0b03ff8f522b4891c8531896c3dJorgen Austvik client->timeout_resend_expire =
7ecd52b03dc1f0b03ff8f522b4891c8531896c3dJorgen Austvik sd_event_source_unref(client->timeout_resend_expire);
c7eb123c8b2081a261deff3c401fbf92ddba1b58Jorgen Austvik
7ecd52b03dc1f0b03ff8f522b4891c8531896c3dJorgen Austvik client->state = DHCP6_STATE_STOPPED;
7ecd52b03dc1f0b03ff8f522b4891c8531896c3dJorgen Austvik
7ecd52b03dc1f0b03ff8f522b4891c8531896c3dJorgen Austvik return 0;
7ecd52b03dc1f0b03ff8f522b4891c8531896c3dJorgen Austvik}
c7eb123c8b2081a261deff3c401fbf92ddba1b58Jorgen Austvik
7ecd52b03dc1f0b03ff8f522b4891c8531896c3dJorgen Austvikstatic void client_stop(sd_dhcp6_client *client, int error) {
7ecd52b03dc1f0b03ff8f522b4891c8531896c3dJorgen Austvik DHCP6_CLIENT_DONT_DESTROY(client);
c7eb123c8b2081a261deff3c401fbf92ddba1b58Jorgen Austvik
7ecd52b03dc1f0b03ff8f522b4891c8531896c3dJorgen Austvik assert(client);
c7eb123c8b2081a261deff3c401fbf92ddba1b58Jorgen Austvik
c7eb123c8b2081a261deff3c401fbf92ddba1b58Jorgen Austvik client_notify(client, error);
7ecd52b03dc1f0b03ff8f522b4891c8531896c3dJorgen Austvik
7ecd52b03dc1f0b03ff8f522b4891c8531896c3dJorgen Austvik client_reset(client);
85e0595857351c6e22f75b8928967d14cb679ac5Jorgen Austvik}
c7eb123c8b2081a261deff3c401fbf92ddba1b58Jorgen Austvik
85e0595857351c6e22f75b8928967d14cb679ac5Jorgen Austvikstatic int client_send_message(sd_dhcp6_client *client, usec_t time_now) {
85e0595857351c6e22f75b8928967d14cb679ac5Jorgen Austvik _cleanup_free_ DHCP6Message *message = NULL;
85e0595857351c6e22f75b8928967d14cb679ac5Jorgen Austvik struct in6_addr all_servers =
85e0595857351c6e22f75b8928967d14cb679ac5Jorgen Austvik IN6ADDR_ALL_DHCP6_RELAY_AGENTS_AND_SERVERS_INIT;
85e0595857351c6e22f75b8928967d14cb679ac5Jorgen Austvik size_t len, optlen = 512;
85e0595857351c6e22f75b8928967d14cb679ac5Jorgen Austvik uint8_t *opt;
85e0595857351c6e22f75b8928967d14cb679ac5Jorgen Austvik int r;
85e0595857351c6e22f75b8928967d14cb679ac5Jorgen Austvik usec_t elapsed_usec;
85e0595857351c6e22f75b8928967d14cb679ac5Jorgen Austvik be16_t elapsed_time;
2e3c025fdd5908a27cc82eb1d5346368a8be4e0dJorgen Austvik
c7eb123c8b2081a261deff3c401fbf92ddba1b58Jorgen Austvik len = sizeof(DHCP6Message) + optlen;
1f17ba9e3c026d75f488227451416bd72a222afeTrond Norbye
c7eb123c8b2081a261deff3c401fbf92ddba1b58Jorgen Austvik message = malloc0(len);
c7eb123c8b2081a261deff3c401fbf92ddba1b58Jorgen Austvik if (!message)
1f17ba9e3c026d75f488227451416bd72a222afeTrond Norbye return -ENOMEM;
1f17ba9e3c026d75f488227451416bd72a222afeTrond Norbye
1f17ba9e3c026d75f488227451416bd72a222afeTrond Norbye opt = (uint8_t *)(message + 1);
c7eb123c8b2081a261deff3c401fbf92ddba1b58Jorgen Austvik
1f17ba9e3c026d75f488227451416bd72a222afeTrond Norbye message->transaction_id = client->transaction_id;
1f17ba9e3c026d75f488227451416bd72a222afeTrond Norbye
1f17ba9e3c026d75f488227451416bd72a222afeTrond Norbye switch(client->state) {
1f17ba9e3c026d75f488227451416bd72a222afeTrond Norbye case DHCP6_STATE_INFORMATION_REQUEST:
1f17ba9e3c026d75f488227451416bd72a222afeTrond Norbye message->type = DHCP6_INFORMATION_REQUEST;
1f17ba9e3c026d75f488227451416bd72a222afeTrond Norbye
1f17ba9e3c026d75f488227451416bd72a222afeTrond Norbye break;
1f17ba9e3c026d75f488227451416bd72a222afeTrond Norbye
1f17ba9e3c026d75f488227451416bd72a222afeTrond Norbye case DHCP6_STATE_SOLICITATION:
49f592091468eac515dde6139fbc8efa26056b0aJorgen Austvik message->type = DHCP6_SOLICIT;
49f592091468eac515dde6139fbc8efa26056b0aJorgen Austvik
49f592091468eac515dde6139fbc8efa26056b0aJorgen Austvik r = dhcp6_option_append(&opt, &optlen,
49f592091468eac515dde6139fbc8efa26056b0aJorgen Austvik DHCP6_OPTION_RAPID_COMMIT, 0, NULL);
49f592091468eac515dde6139fbc8efa26056b0aJorgen Austvik if (r < 0)
49f592091468eac515dde6139fbc8efa26056b0aJorgen Austvik return r;
49f592091468eac515dde6139fbc8efa26056b0aJorgen Austvik
49f592091468eac515dde6139fbc8efa26056b0aJorgen Austvik r = dhcp6_option_append_ia(&opt, &optlen, &client->ia_na);
49f592091468eac515dde6139fbc8efa26056b0aJorgen Austvik if (r < 0)
49f592091468eac515dde6139fbc8efa26056b0aJorgen Austvik return r;
49f592091468eac515dde6139fbc8efa26056b0aJorgen Austvik
49f592091468eac515dde6139fbc8efa26056b0aJorgen Austvik break;
49f592091468eac515dde6139fbc8efa26056b0aJorgen Austvik
49f592091468eac515dde6139fbc8efa26056b0aJorgen Austvik case DHCP6_STATE_REQUEST:
49f592091468eac515dde6139fbc8efa26056b0aJorgen Austvik case DHCP6_STATE_RENEW:
49f592091468eac515dde6139fbc8efa26056b0aJorgen Austvik
49f592091468eac515dde6139fbc8efa26056b0aJorgen Austvik if (client->state == DHCP6_STATE_REQUEST)
49f592091468eac515dde6139fbc8efa26056b0aJorgen Austvik message->type = DHCP6_REQUEST;
49f592091468eac515dde6139fbc8efa26056b0aJorgen Austvik else
49f592091468eac515dde6139fbc8efa26056b0aJorgen Austvik message->type = DHCP6_RENEW;
49f592091468eac515dde6139fbc8efa26056b0aJorgen Austvik
49f592091468eac515dde6139fbc8efa26056b0aJorgen Austvik r = dhcp6_option_append(&opt, &optlen, DHCP6_OPTION_SERVERID,
49f592091468eac515dde6139fbc8efa26056b0aJorgen Austvik client->lease->serverid_len,
1f17ba9e3c026d75f488227451416bd72a222afeTrond Norbye client->lease->serverid);
7ecd52b03dc1f0b03ff8f522b4891c8531896c3dJorgen Austvik if (r < 0)
f09d46eefeb5e4db6dc11e02e417b448fa9362a9Jorgen Austvik return r;
7ecd52b03dc1f0b03ff8f522b4891c8531896c3dJorgen Austvik
7ecd52b03dc1f0b03ff8f522b4891c8531896c3dJorgen Austvik r = dhcp6_option_append_ia(&opt, &optlen, &client->lease->ia);
7ecd52b03dc1f0b03ff8f522b4891c8531896c3dJorgen Austvik if (r < 0)
7ecd52b03dc1f0b03ff8f522b4891c8531896c3dJorgen Austvik return r;
7ecd52b03dc1f0b03ff8f522b4891c8531896c3dJorgen Austvik
7ecd52b03dc1f0b03ff8f522b4891c8531896c3dJorgen Austvik break;
7ecd52b03dc1f0b03ff8f522b4891c8531896c3dJorgen Austvik
7ecd52b03dc1f0b03ff8f522b4891c8531896c3dJorgen Austvik case DHCP6_STATE_REBIND:
7ecd52b03dc1f0b03ff8f522b4891c8531896c3dJorgen Austvik message->type = DHCP6_REBIND;
7ecd52b03dc1f0b03ff8f522b4891c8531896c3dJorgen Austvik
7ecd52b03dc1f0b03ff8f522b4891c8531896c3dJorgen Austvik r = dhcp6_option_append_ia(&opt, &optlen, &client->lease->ia);
2e3c025fdd5908a27cc82eb1d5346368a8be4e0dJorgen Austvik if (r < 0)
2e3c025fdd5908a27cc82eb1d5346368a8be4e0dJorgen Austvik return r;
2e3c025fdd5908a27cc82eb1d5346368a8be4e0dJorgen Austvik
2e3c025fdd5908a27cc82eb1d5346368a8be4e0dJorgen Austvik break;
2e3c025fdd5908a27cc82eb1d5346368a8be4e0dJorgen Austvik
2e3c025fdd5908a27cc82eb1d5346368a8be4e0dJorgen Austvik case DHCP6_STATE_STOPPED:
2e3c025fdd5908a27cc82eb1d5346368a8be4e0dJorgen Austvik case DHCP6_STATE_BOUND:
2e3c025fdd5908a27cc82eb1d5346368a8be4e0dJorgen Austvik return -EINVAL;
2e3c025fdd5908a27cc82eb1d5346368a8be4e0dJorgen Austvik }
2e3c025fdd5908a27cc82eb1d5346368a8be4e0dJorgen Austvik
2e3c025fdd5908a27cc82eb1d5346368a8be4e0dJorgen Austvik r = dhcp6_option_append(&opt, &optlen, DHCP6_OPTION_ORO,
2e3c025fdd5908a27cc82eb1d5346368a8be4e0dJorgen Austvik client->req_opts_len * sizeof(be16_t),
2e3c025fdd5908a27cc82eb1d5346368a8be4e0dJorgen Austvik client->req_opts);
2e3c025fdd5908a27cc82eb1d5346368a8be4e0dJorgen Austvik if (r < 0)
2e3c025fdd5908a27cc82eb1d5346368a8be4e0dJorgen Austvik return r;
2e3c025fdd5908a27cc82eb1d5346368a8be4e0dJorgen Austvik
2e3c025fdd5908a27cc82eb1d5346368a8be4e0dJorgen Austvik assert (client->duid_len);
2e3c025fdd5908a27cc82eb1d5346368a8be4e0dJorgen Austvik r = dhcp6_option_append(&opt, &optlen, DHCP6_OPTION_CLIENTID,
2e3c025fdd5908a27cc82eb1d5346368a8be4e0dJorgen Austvik client->duid_len, &client->duid);
7ecd52b03dc1f0b03ff8f522b4891c8531896c3dJorgen Austvik if (r < 0)
2e3c025fdd5908a27cc82eb1d5346368a8be4e0dJorgen Austvik return r;
2e3c025fdd5908a27cc82eb1d5346368a8be4e0dJorgen Austvik
2e3c025fdd5908a27cc82eb1d5346368a8be4e0dJorgen Austvik elapsed_usec = time_now - client->transaction_start;
2e3c025fdd5908a27cc82eb1d5346368a8be4e0dJorgen Austvik if (elapsed_usec < 0xffff * USEC_PER_MSEC * 10)
2e3c025fdd5908a27cc82eb1d5346368a8be4e0dJorgen Austvik elapsed_time = htobe16(elapsed_usec / USEC_PER_MSEC / 10);
2e3c025fdd5908a27cc82eb1d5346368a8be4e0dJorgen Austvik else
2e3c025fdd5908a27cc82eb1d5346368a8be4e0dJorgen Austvik elapsed_time = 0xffff;
2e3c025fdd5908a27cc82eb1d5346368a8be4e0dJorgen Austvik
2e3c025fdd5908a27cc82eb1d5346368a8be4e0dJorgen Austvik r = dhcp6_option_append(&opt, &optlen, DHCP6_OPTION_ELAPSED_TIME,
2e3c025fdd5908a27cc82eb1d5346368a8be4e0dJorgen Austvik sizeof(elapsed_time), &elapsed_time);
2e3c025fdd5908a27cc82eb1d5346368a8be4e0dJorgen Austvik if (r < 0)
2e3c025fdd5908a27cc82eb1d5346368a8be4e0dJorgen Austvik return r;
64b763950bf11e9357facbd2b5666631a895c085Trond Norbye
r = dhcp6_network_send_udp_socket(client->fd, &all_servers, message,
len - optlen);
if (r < 0)
return r;
log_dhcp6_client(client, "Sent %s",
dhcp6_message_type_to_string(message->type));
return 0;
}
static int client_timeout_t2(sd_event_source *s, uint64_t usec,
void *userdata) {
sd_dhcp6_client *client = userdata;
assert_return(s, -EINVAL);
assert_return(client, -EINVAL);
assert_return(client->lease, -EINVAL);
client->lease->ia.timeout_t2 =
sd_event_source_unref(client->lease->ia.timeout_t2);
log_dhcp6_client(client, "Timeout T2");
client_start(client, DHCP6_STATE_REBIND);
return 0;
}
static int client_timeout_t1(sd_event_source *s, uint64_t usec,
void *userdata) {
sd_dhcp6_client *client = userdata;
assert_return(s, -EINVAL);
assert_return(client, -EINVAL);
assert_return(client->lease, -EINVAL);
client->lease->ia.timeout_t1 =
sd_event_source_unref(client->lease->ia.timeout_t1);
log_dhcp6_client(client, "Timeout T1");
client_start(client, DHCP6_STATE_RENEW);
return 0;
}
static int client_timeout_resend_expire(sd_event_source *s, uint64_t usec,
void *userdata) {
sd_dhcp6_client *client = userdata;
DHCP6_CLIENT_DONT_DESTROY(client);
enum DHCP6State state;
assert(s);
assert(client);
assert(client->event);
state = client->state;
client_stop(client, DHCP6_EVENT_RESEND_EXPIRE);
/* RFC 3315, section 18.1.4., says that "...the client may choose to
use a Solicit message to locate a new DHCP server..." */
if (state == DHCP6_STATE_REBIND)
client_start(client, DHCP6_STATE_SOLICITATION);
return 0;
}
static usec_t client_timeout_compute_random(usec_t val) {
return val - val / 10 +
(random_u32() % (2 * USEC_PER_SEC)) * val / 10 / USEC_PER_SEC;
}
static int client_timeout_resend(sd_event_source *s, uint64_t usec,
void *userdata) {
int r = 0;
sd_dhcp6_client *client = userdata;
usec_t time_now, init_retransmit_time = 0, max_retransmit_time = 0;
usec_t max_retransmit_duration = 0;
uint8_t max_retransmit_count = 0;
char time_string[FORMAT_TIMESPAN_MAX];
uint32_t expire = 0;
assert(s);
assert(client);
assert(client->event);
client->timeout_resend = sd_event_source_unref(client->timeout_resend);
switch (client->state) {
case DHCP6_STATE_INFORMATION_REQUEST:
init_retransmit_time = DHCP6_INF_TIMEOUT;
max_retransmit_time = DHCP6_INF_MAX_RT;
break;
case DHCP6_STATE_SOLICITATION:
if (client->retransmit_count && client->lease) {
client_start(client, DHCP6_STATE_REQUEST);
return 0;
}
init_retransmit_time = DHCP6_SOL_TIMEOUT;
max_retransmit_time = DHCP6_SOL_MAX_RT;
break;
case DHCP6_STATE_REQUEST:
init_retransmit_time = DHCP6_REQ_TIMEOUT;
max_retransmit_time = DHCP6_REQ_MAX_RT;
max_retransmit_count = DHCP6_REQ_MAX_RC;
break;
case DHCP6_STATE_RENEW:
init_retransmit_time = DHCP6_REN_TIMEOUT;
max_retransmit_time = DHCP6_REN_MAX_RT;
/* RFC 3315, section 18.1.3. says max retransmit duration will
be the remaining time until T2. Instead of setting MRD,
wait for T2 to trigger with the same end result */
break;
case DHCP6_STATE_REBIND:
init_retransmit_time = DHCP6_REB_TIMEOUT;
max_retransmit_time = DHCP6_REB_MAX_RT;
if (!client->timeout_resend_expire) {
r = dhcp6_lease_ia_rebind_expire(&client->lease->ia,
&expire);
if (r < 0) {
client_stop(client, r);
return 0;
}
max_retransmit_duration = expire * USEC_PER_SEC;
}
break;
case DHCP6_STATE_STOPPED:
case DHCP6_STATE_BOUND:
return 0;
}
if (max_retransmit_count &&
client->retransmit_count >= max_retransmit_count) {
client_stop(client, DHCP6_EVENT_RETRANS_MAX);
return 0;
}
r = sd_event_now(client->event, clock_boottime_or_monotonic(), &time_now);
if (r < 0)
goto error;
r = client_send_message(client, time_now);
if (r >= 0)
client->retransmit_count++;
if (!client->retransmit_time) {
client->retransmit_time =
client_timeout_compute_random(init_retransmit_time);
if (client->state == DHCP6_STATE_SOLICITATION)
client->retransmit_time += init_retransmit_time / 10;
} else {
if (max_retransmit_time &&
client->retransmit_time > max_retransmit_time / 2)
client->retransmit_time = client_timeout_compute_random(max_retransmit_time);
else
client->retransmit_time += client_timeout_compute_random(client->retransmit_time);
}
log_dhcp6_client(client, "Next retransmission in %s",
format_timespan(time_string, FORMAT_TIMESPAN_MAX,
client->retransmit_time, 0));
r = sd_event_add_time(client->event, &client->timeout_resend,
clock_boottime_or_monotonic(),
time_now + client->retransmit_time,
10 * USEC_PER_MSEC, client_timeout_resend,
client);
if (r < 0)
goto error;
r = sd_event_source_set_priority(client->timeout_resend,
client->event_priority);
if (r < 0)
goto error;
r = sd_event_source_set_description(client->timeout_resend, "dhcp6-resend-timer");
if (r < 0)
goto error;
if (max_retransmit_duration && !client->timeout_resend_expire) {
log_dhcp6_client(client, "Max retransmission duration %"PRIu64" secs",
max_retransmit_duration / USEC_PER_SEC);
r = sd_event_add_time(client->event,
&client->timeout_resend_expire,
clock_boottime_or_monotonic(),
time_now + max_retransmit_duration,
USEC_PER_SEC,
client_timeout_resend_expire, client);
if (r < 0)
goto error;
r = sd_event_source_set_priority(client->timeout_resend_expire,
client->event_priority);
if (r < 0)
goto error;
r = sd_event_source_set_description(client->timeout_resend_expire, "dhcp6-resend-expire-timer");
if (r < 0)
goto error;
}
error:
if (r < 0)
client_stop(client, r);
return 0;
}
static int client_ensure_iaid(sd_dhcp6_client *client) {
int r;
assert(client);
if (client->ia_na.id)
return 0;
r = dhcp_identifier_set_iaid(client->index, client->mac_addr, client->mac_addr_len, &client->ia_na.id);
if (r < 0)
return r;
return 0;
}
static int client_parse_message(sd_dhcp6_client *client,
DHCP6Message *message, size_t len,
sd_dhcp6_lease *lease) {
int r;
uint8_t *optval, *option, *id = NULL;
uint16_t optcode, status;
size_t optlen, id_len;
bool clientid = false;
be32_t iaid_lease;
option = (uint8_t *)message + sizeof(DHCP6Message);
len -= sizeof(DHCP6Message);
while ((r = dhcp6_option_parse(&option, &len, &optcode, &optlen,
&optval)) >= 0) {
switch (optcode) {
case DHCP6_OPTION_CLIENTID:
if (clientid) {
log_dhcp6_client(client, "%s contains multiple clientids",
dhcp6_message_type_to_string(message->type));
return -EINVAL;
}
if (optlen != client->duid_len ||
memcmp(&client->duid, optval, optlen) != 0) {
log_dhcp6_client(client, "%s DUID does not match",
dhcp6_message_type_to_string(message->type));
return -EINVAL;
}
clientid = true;
break;
case DHCP6_OPTION_SERVERID:
r = dhcp6_lease_get_serverid(lease, &id, &id_len);
if (r >= 0 && id) {
log_dhcp6_client(client, "%s contains multiple serverids",
dhcp6_message_type_to_string(message->type));
return -EINVAL;
}
r = dhcp6_lease_set_serverid(lease, optval, optlen);
if (r < 0)
return r;
break;
case DHCP6_OPTION_PREFERENCE:
if (optlen != 1)
return -EINVAL;
r = dhcp6_lease_set_preference(lease, *optval);
if (r < 0)
return r;
break;
case DHCP6_OPTION_STATUS_CODE:
if (optlen < 2)
return -EINVAL;
status = optval[0] << 8 | optval[1];
if (status) {
log_dhcp6_client(client, "%s Status %s",
dhcp6_message_type_to_string(message->type),
dhcp6_message_status_to_string(status));
return -EINVAL;
}
break;
case DHCP6_OPTION_IA_NA:
if (client->state == DHCP6_STATE_INFORMATION_REQUEST) {
log_dhcp6_client(client, "Information request ignoring IA NA option");
break;
}
r = dhcp6_option_parse_ia(&optval, &optlen, optcode,
&lease->ia);
if (r < 0 && r != -ENOMSG)
return r;
r = dhcp6_lease_get_iaid(lease, &iaid_lease);
if (r < 0)
return r;
if (client->ia_na.id != iaid_lease) {
log_dhcp6_client(client, "%s has wrong IAID",
dhcp6_message_type_to_string(message->type));
return -EINVAL;
}
break;
case DHCP6_OPTION_RAPID_COMMIT:
r = dhcp6_lease_set_rapid_commit(lease);
if (r < 0)
return r;
break;
case DHCP6_OPTION_DNS_SERVERS:
r = dhcp6_lease_set_dns(lease, optval, optlen);
if (r < 0)
return r;
break;
}
}
if (r == -ENOMSG)
r = 0;
if (r < 0 || !clientid) {
log_dhcp6_client(client, "%s has incomplete options",
dhcp6_message_type_to_string(message->type));
return -EINVAL;
}
if (client->state != DHCP6_STATE_INFORMATION_REQUEST) {
r = dhcp6_lease_get_serverid(lease, &id, &id_len);
if (r < 0)
log_dhcp6_client(client, "%s has no server id",
dhcp6_message_type_to_string(message->type));
}
return r;
}
static int client_receive_reply(sd_dhcp6_client *client, DHCP6Message *reply,
size_t len)
{
int r;
_cleanup_dhcp6_lease_free_ sd_dhcp6_lease *lease = NULL;
bool rapid_commit;
if (reply->type != DHCP6_REPLY)
return 0;
r = dhcp6_lease_new(&lease);
if (r < 0)
return -ENOMEM;
r = client_parse_message(client, reply, len, lease);
if (r < 0)
return r;
if (client->state == DHCP6_STATE_SOLICITATION) {
r = dhcp6_lease_get_rapid_commit(lease, &rapid_commit);
if (r < 0)
return r;
if (!rapid_commit)
return 0;
}
if (client->lease) {
dhcp6_lease_clear_timers(&client->lease->ia);
client->lease = sd_dhcp6_lease_unref(client->lease);
}
client->lease = lease;
lease = NULL;
return DHCP6_STATE_BOUND;
}
static int client_receive_advertise(sd_dhcp6_client *client,
DHCP6Message *advertise, size_t len) {
int r;
_cleanup_dhcp6_lease_free_ sd_dhcp6_lease *lease = NULL;
uint8_t pref_advertise = 0, pref_lease = 0;
if (advertise->type != DHCP6_ADVERTISE)
return 0;
r = dhcp6_lease_new(&lease);
if (r < 0)
return r;
r = client_parse_message(client, advertise, len, lease);
if (r < 0)
return r;
r = dhcp6_lease_get_preference(lease, &pref_advertise);
if (r < 0)
return r;
r = dhcp6_lease_get_preference(client->lease, &pref_lease);
if (r < 0 || pref_advertise > pref_lease) {
sd_dhcp6_lease_unref(client->lease);
client->lease = lease;
lease = NULL;
r = 0;
}
if (pref_advertise == 255 || client->retransmit_count > 1)
r = DHCP6_STATE_REQUEST;
return r;
}
static int client_receive_message(sd_event_source *s, int fd, uint32_t revents,
void *userdata) {
sd_dhcp6_client *client = userdata;
DHCP6_CLIENT_DONT_DESTROY(client);
_cleanup_free_ DHCP6Message *message;
int r, buflen, len;
assert(s);
assert(client);
assert(client->event);
r = ioctl(fd, FIONREAD, &buflen);
if (r < 0 || buflen <= 0)
buflen = DHCP6_MIN_OPTIONS_SIZE;
message = malloc0(buflen);
if (!message)
return -ENOMEM;
len = read(fd, message, buflen);
if ((size_t)len < sizeof(DHCP6Message)) {
log_dhcp6_client(client, "could not receive message from UDP socket: %m");
return 0;
}
switch(message->type) {
case DHCP6_SOLICIT:
case DHCP6_REQUEST:
case DHCP6_CONFIRM:
case DHCP6_RENEW:
case DHCP6_REBIND:
case DHCP6_RELEASE:
case DHCP6_DECLINE:
case DHCP6_INFORMATION_REQUEST:
case DHCP6_RELAY_FORW:
case DHCP6_RELAY_REPL:
return 0;
case DHCP6_ADVERTISE:
case DHCP6_REPLY:
case DHCP6_RECONFIGURE:
break;
default:
log_dhcp6_client(client, "unknown message type %d",
message->type);
return 0;
}
if (client->transaction_id != (message->transaction_id &
htobe32(0x00ffffff)))
return 0;
switch (client->state) {
case DHCP6_STATE_INFORMATION_REQUEST:
r = client_receive_reply(client, message, len);
if (r < 0)
return 0;
client_notify(client, DHCP6_EVENT_INFORMATION_REQUEST);
client_start(client, DHCP6_STATE_STOPPED);
break;
case DHCP6_STATE_SOLICITATION:
r = client_receive_advertise(client, message, len);
if (r == DHCP6_STATE_REQUEST) {
client_start(client, r);
break;
}
/* fall through for Soliciation Rapid Commit option check */
case DHCP6_STATE_REQUEST:
case DHCP6_STATE_RENEW:
case DHCP6_STATE_REBIND:
r = client_receive_reply(client, message, len);
if (r < 0)
return 0;
if (r == DHCP6_STATE_BOUND) {
r = client_start(client, DHCP6_STATE_BOUND);
if (r < 0) {
client_stop(client, r);
return 0;
}
client_notify(client, DHCP6_EVENT_IP_ACQUIRE);
}
break;
case DHCP6_STATE_BOUND:
break;
case DHCP6_STATE_STOPPED:
return 0;
}
if (r >= 0) {
log_dhcp6_client(client, "Recv %s",
dhcp6_message_type_to_string(message->type));
}
return 0;
}
static int client_start(sd_dhcp6_client *client, enum DHCP6State state)
{
int r;
usec_t timeout, time_now;
char time_string[FORMAT_TIMESPAN_MAX];
assert_return(client, -EINVAL);
assert_return(client->event, -EINVAL);
assert_return(client->index > 0, -EINVAL);
assert_return(client->state != state, -EINVAL);
client->timeout_resend_expire =
sd_event_source_unref(client->timeout_resend_expire);
client->timeout_resend = sd_event_source_unref(client->timeout_resend);
client->retransmit_time = 0;
client->retransmit_count = 0;
r = sd_event_now(client->event, clock_boottime_or_monotonic(), &time_now);
if (r < 0)
return r;
switch (state) {
case DHCP6_STATE_STOPPED:
if (client->state == DHCP6_STATE_INFORMATION_REQUEST) {
client->state = DHCP6_STATE_STOPPED;
return 0;
}
/* fall through */
case DHCP6_STATE_SOLICITATION:
client->state = DHCP6_STATE_SOLICITATION;
break;
case DHCP6_STATE_INFORMATION_REQUEST:
case DHCP6_STATE_REQUEST:
case DHCP6_STATE_RENEW:
case DHCP6_STATE_REBIND:
client->state = state;
break;
case DHCP6_STATE_BOUND:
if (client->lease->ia.lifetime_t1 == 0xffffffff ||
client->lease->ia.lifetime_t2 == 0xffffffff) {
log_dhcp6_client(client, "infinite T1 0x%08x or T2 0x%08x",
be32toh(client->lease->ia.lifetime_t1),
be32toh(client->lease->ia.lifetime_t2));
return 0;
}
timeout = client_timeout_compute_random(be32toh(client->lease->ia.lifetime_t1) * USEC_PER_SEC);
log_dhcp6_client(client, "T1 expires in %s",
format_timespan(time_string,
FORMAT_TIMESPAN_MAX,
timeout, 0));
r = sd_event_add_time(client->event,
&client->lease->ia.timeout_t1,
clock_boottime_or_monotonic(), time_now + timeout,
10 * USEC_PER_SEC, client_timeout_t1,
client);
if (r < 0)
return r;
r = sd_event_source_set_priority(client->lease->ia.timeout_t1,
client->event_priority);
if (r < 0)
return r;
r = sd_event_source_set_description(client->lease->ia.timeout_t1, "dhcp6-t1-timeout");
if (r < 0)
return r;
timeout = client_timeout_compute_random(be32toh(client->lease->ia.lifetime_t2) * USEC_PER_SEC);
log_dhcp6_client(client, "T2 expires in %s",
format_timespan(time_string,
FORMAT_TIMESPAN_MAX,
timeout, 0));
r = sd_event_add_time(client->event,
&client->lease->ia.timeout_t2,
clock_boottime_or_monotonic(), time_now + timeout,
10 * USEC_PER_SEC, client_timeout_t2,
client);
if (r < 0)
return r;
r = sd_event_source_set_priority(client->lease->ia.timeout_t2,
client->event_priority);
if (r < 0)
return r;
r = sd_event_source_set_description(client->lease->ia.timeout_t2, "dhcp6-t2-timeout");
if (r < 0)
return r;
client->state = state;
return 0;
}
client->transaction_id = random_u32() & htobe32(0x00ffffff);
client->transaction_start = time_now;
r = sd_event_add_time(client->event, &client->timeout_resend,
clock_boottime_or_monotonic(), 0, 0, client_timeout_resend,
client);
if (r < 0)
return r;
r = sd_event_source_set_priority(client->timeout_resend,
client->event_priority);
if (r < 0)
return r;
r = sd_event_source_set_description(client->timeout_resend, "dhcp6-resend-timeout");
if (r < 0)
return r;
return 0;
}
int sd_dhcp6_client_stop(sd_dhcp6_client *client)
{
client_stop(client, DHCP6_EVENT_STOP);
return 0;
}
int sd_dhcp6_client_start(sd_dhcp6_client *client)
{
int r = 0;
enum DHCP6State state = DHCP6_STATE_SOLICITATION;
assert_return(client, -EINVAL);
assert_return(client->event, -EINVAL);
assert_return(client->index > 0, -EINVAL);
r = client_reset(client);
if (r < 0)
return r;
r = client_ensure_iaid(client);
if (r < 0)
return r;
r = client_ensure_duid(client);
if (r < 0)
return r;
r = dhcp6_network_bind_udp_socket(client->index, NULL);
if (r < 0)
return r;
client->fd = r;
r = sd_event_add_io(client->event, &client->receive_message,
client->fd, EPOLLIN, client_receive_message,
client);
if (r < 0)
goto error;
r = sd_event_source_set_priority(client->receive_message,
client->event_priority);
if (r < 0)
goto error;
r = sd_event_source_set_description(client->receive_message,
"dhcp6-receive-message");
if (r < 0)
goto error;
if (client->information_request)
state = DHCP6_STATE_INFORMATION_REQUEST;
log_dhcp6_client(client, "Started in %s mode",
client->information_request? "Information request":
"Managed");
return client_start(client, state);
error:
client_reset(client);
return r;
}
int sd_dhcp6_client_attach_event(sd_dhcp6_client *client, sd_event *event,
int priority)
{
int r;
assert_return(client, -EINVAL);
assert_return(!client->event, -EBUSY);
if (event)
client->event = sd_event_ref(event);
else {
r = sd_event_default(&client->event);
if (r < 0)
return 0;
}
client->event_priority = priority;
return 0;
}
int sd_dhcp6_client_detach_event(sd_dhcp6_client *client) {
assert_return(client, -EINVAL);
client->event = sd_event_unref(client->event);
return 0;
}
sd_event *sd_dhcp6_client_get_event(sd_dhcp6_client *client) {
if (!client)
return NULL;
return client->event;
}
sd_dhcp6_client *sd_dhcp6_client_ref(sd_dhcp6_client *client) {
if (client)
assert_se(REFCNT_INC(client->n_ref) >= 2);
return client;
}
sd_dhcp6_client *sd_dhcp6_client_unref(sd_dhcp6_client *client) {
if (client && REFCNT_DEC(client->n_ref) == 0) {
client_reset(client);
sd_dhcp6_client_detach_event(client);
sd_dhcp6_lease_unref(client->lease);
free(client->req_opts);
free(client);
return NULL;
}
return client;
}
int sd_dhcp6_client_new(sd_dhcp6_client **ret)
{
_cleanup_dhcp6_client_unref_ sd_dhcp6_client *client = NULL;
size_t t;
assert_return(ret, -EINVAL);
client = new0(sd_dhcp6_client, 1);
if (!client)
return -ENOMEM;
client->n_ref = REFCNT_INIT;
client->ia_na.type = DHCP6_OPTION_IA_NA;
client->index = -1;
client->fd = -1;
client->req_opts_len = ELEMENTSOF(default_req_opts);
client->req_opts = new0(be16_t, client->req_opts_len);
if (!client->req_opts)
return -ENOMEM;
for (t = 0; t < client->req_opts_len; t++)
client->req_opts[t] = htobe16(default_req_opts[t]);
*ret = client;
client = NULL;
return 0;
}