2017-11-18 17:09:20 +01:00
|
|
|
/* SPDX-License-Identifier: LGPL-2.1+ */
|
2013-12-09 22:43:09 +01:00
|
|
|
/***
|
2018-06-12 17:15:23 +02:00
|
|
|
Copyright © 2013 Intel Corporation. All rights reserved.
|
2013-12-09 22:43:09 +01:00
|
|
|
***/
|
|
|
|
|
|
|
|
#include <errno.h>
|
2020-05-29 11:26:24 +02:00
|
|
|
#include <net/if.h>
|
|
|
|
#include <net/if_arp.h>
|
2013-12-09 22:43:16 +01:00
|
|
|
#include <stdio.h>
|
2013-12-09 22:43:26 +01:00
|
|
|
#include <sys/socket.h>
|
|
|
|
#include <unistd.h>
|
2013-12-09 22:43:09 +01:00
|
|
|
|
2015-10-24 22:58:24 +02:00
|
|
|
#include "sd-dhcp-client.h"
|
2014-04-11 05:45:46 +02:00
|
|
|
#include "sd-event.h"
|
2013-12-09 22:43:21 +01:00
|
|
|
|
2015-10-27 03:01:06 +01:00
|
|
|
#include "alloc-util.h"
|
2015-01-22 00:53:16 +01:00
|
|
|
#include "dhcp-identifier.h"
|
2013-12-09 22:43:21 +01:00
|
|
|
#include "dhcp-internal.h"
|
2015-10-24 22:58:24 +02:00
|
|
|
#include "dhcp-protocol.h"
|
2015-10-25 13:14:12 +01:00
|
|
|
#include "fd-util.h"
|
2018-11-01 14:46:27 +01:00
|
|
|
#include "random-util.h"
|
2018-09-13 14:31:13 +02:00
|
|
|
#include "tests.h"
|
2015-11-16 22:09:36 +01:00
|
|
|
#include "util.h"
|
2013-12-09 22:43:09 +01:00
|
|
|
|
2015-01-22 00:53:16 +01:00
|
|
|
static uint8_t mac_addr[] = {'A', 'B', 'C', '1', '2', '3'};
|
2013-12-09 22:43:21 +01:00
|
|
|
|
2014-02-18 15:07:39 +01:00
|
|
|
typedef int (*test_callback_recv_t)(size_t size, DHCPMessage *dhcp);
|
|
|
|
|
2015-01-22 00:53:16 +01:00
|
|
|
static bool verbose = true;
|
2013-12-09 22:43:26 +01:00
|
|
|
static int test_fd[2];
|
2014-02-18 15:07:39 +01:00
|
|
|
static test_callback_recv_t callback_recv;
|
2014-02-18 15:07:40 +01:00
|
|
|
static be32_t xid;
|
2014-03-12 11:52:00 +01:00
|
|
|
static sd_event_source *test_hangcheck;
|
|
|
|
|
2015-08-27 17:48:24 +02:00
|
|
|
static int test_dhcp_hangcheck(sd_event_source *s, uint64_t usec, void *userdata) {
|
2014-03-12 11:52:00 +01:00
|
|
|
assert_not_reached("Test case should have completed in 2 seconds");
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2013-12-09 22:43:26 +01:00
|
|
|
|
2015-08-27 17:48:24 +02:00
|
|
|
static void test_request_basic(sd_event *e) {
|
2014-01-18 19:32:45 +01:00
|
|
|
int r;
|
|
|
|
|
2013-12-09 22:43:09 +01:00
|
|
|
sd_dhcp_client *client;
|
|
|
|
|
2014-02-18 15:07:38 +01:00
|
|
|
if (verbose)
|
|
|
|
printf("* %s\n", __FUNCTION__);
|
|
|
|
|
2017-08-03 03:32:46 +02:00
|
|
|
/* Initialize client without Anonymize settings. */
|
|
|
|
r = sd_dhcp_client_new(&client, false);
|
2013-12-09 22:43:09 +01:00
|
|
|
|
2014-02-22 20:22:41 +01:00
|
|
|
assert_se(r >= 0);
|
|
|
|
assert_se(client);
|
2013-12-09 22:43:09 +01:00
|
|
|
|
2014-01-18 19:32:45 +01:00
|
|
|
r = sd_dhcp_client_attach_event(client, e, 0);
|
2014-02-22 20:22:41 +01:00
|
|
|
assert_se(r >= 0);
|
2014-01-18 19:32:45 +01:00
|
|
|
|
2014-02-22 20:22:41 +01:00
|
|
|
assert_se(sd_dhcp_client_set_request_option(NULL, 0) == -EINVAL);
|
|
|
|
assert_se(sd_dhcp_client_set_request_address(NULL, NULL) == -EINVAL);
|
2016-05-23 16:13:18 +02:00
|
|
|
assert_se(sd_dhcp_client_set_ifindex(NULL, 0) == -EINVAL);
|
2013-12-09 22:43:09 +01:00
|
|
|
|
2016-05-23 16:13:18 +02:00
|
|
|
assert_se(sd_dhcp_client_set_ifindex(client, 15) == 0);
|
|
|
|
assert_se(sd_dhcp_client_set_ifindex(client, -42) == -EINVAL);
|
|
|
|
assert_se(sd_dhcp_client_set_ifindex(client, -1) == -EINVAL);
|
|
|
|
assert_se(sd_dhcp_client_set_ifindex(client, 0) == -EINVAL);
|
|
|
|
assert_se(sd_dhcp_client_set_ifindex(client, 1) == 0);
|
2013-12-09 22:43:09 +01:00
|
|
|
|
2017-11-16 10:05:44 +01:00
|
|
|
assert_se(sd_dhcp_client_set_hostname(client, "host") == 1);
|
|
|
|
assert_se(sd_dhcp_client_set_hostname(client, "host.domain") == 1);
|
|
|
|
assert_se(sd_dhcp_client_set_hostname(client, NULL) == 1);
|
|
|
|
assert_se(sd_dhcp_client_set_hostname(client, "~host") == -EINVAL);
|
|
|
|
assert_se(sd_dhcp_client_set_hostname(client, "~host.domain") == -EINVAL);
|
|
|
|
|
2014-02-22 20:22:41 +01:00
|
|
|
assert_se(sd_dhcp_client_set_request_option(client,
|
2016-01-20 14:44:24 +01:00
|
|
|
SD_DHCP_OPTION_SUBNET_MASK) == -EEXIST);
|
2014-02-22 20:22:41 +01:00
|
|
|
assert_se(sd_dhcp_client_set_request_option(client,
|
2016-01-20 14:44:24 +01:00
|
|
|
SD_DHCP_OPTION_ROUTER) == -EEXIST);
|
2017-08-03 03:32:46 +02:00
|
|
|
/* This PRL option is not set when using Anonymize, but in this test
|
|
|
|
* Anonymize settings are not being used. */
|
2014-02-22 20:22:41 +01:00
|
|
|
assert_se(sd_dhcp_client_set_request_option(client,
|
2016-01-20 14:44:24 +01:00
|
|
|
SD_DHCP_OPTION_HOST_NAME) == -EEXIST);
|
2014-02-22 20:22:41 +01:00
|
|
|
assert_se(sd_dhcp_client_set_request_option(client,
|
2016-01-20 14:44:24 +01:00
|
|
|
SD_DHCP_OPTION_DOMAIN_NAME) == -EEXIST);
|
2014-02-22 20:22:41 +01:00
|
|
|
assert_se(sd_dhcp_client_set_request_option(client,
|
2016-01-20 14:44:24 +01:00
|
|
|
SD_DHCP_OPTION_DOMAIN_NAME_SERVER) == -EEXIST);
|
2013-12-09 22:43:09 +01:00
|
|
|
|
2014-02-22 20:22:41 +01:00
|
|
|
assert_se(sd_dhcp_client_set_request_option(client,
|
2016-01-20 14:44:24 +01:00
|
|
|
SD_DHCP_OPTION_PAD) == -EINVAL);
|
2014-02-22 20:22:41 +01:00
|
|
|
assert_se(sd_dhcp_client_set_request_option(client,
|
2016-01-20 14:44:24 +01:00
|
|
|
SD_DHCP_OPTION_END) == -EINVAL);
|
2014-02-22 20:22:41 +01:00
|
|
|
assert_se(sd_dhcp_client_set_request_option(client,
|
2016-01-20 14:44:24 +01:00
|
|
|
SD_DHCP_OPTION_MESSAGE_TYPE) == -EINVAL);
|
2014-02-22 20:22:41 +01:00
|
|
|
assert_se(sd_dhcp_client_set_request_option(client,
|
2016-01-20 14:44:24 +01:00
|
|
|
SD_DHCP_OPTION_OVERLOAD) == -EINVAL);
|
2014-02-22 20:22:41 +01:00
|
|
|
assert_se(sd_dhcp_client_set_request_option(client,
|
2016-01-20 14:44:24 +01:00
|
|
|
SD_DHCP_OPTION_PARAMETER_REQUEST_LIST)
|
2013-12-09 22:43:09 +01:00
|
|
|
== -EINVAL);
|
|
|
|
|
2017-08-03 03:32:46 +02:00
|
|
|
/* RFC7844: option 33 (SD_DHCP_OPTION_STATIC_ROUTE) is set in the
|
|
|
|
* default PRL when using Anonymize, so it is changed to other option
|
2017-10-11 14:45:48 +02:00
|
|
|
* that is not set by default, to check that it was set successfully.
|
|
|
|
* Options not set by default (using or not anonymize) are option 17
|
2017-08-03 03:32:46 +02:00
|
|
|
* (SD_DHCP_OPTION_ROOT_PATH) and 42 (SD_DHCP_OPTION_NTP_SERVER) */
|
|
|
|
assert_se(sd_dhcp_client_set_request_option(client, 17) == 0);
|
|
|
|
assert_se(sd_dhcp_client_set_request_option(client, 17) == -EEXIST);
|
|
|
|
assert_se(sd_dhcp_client_set_request_option(client, 42) == 0);
|
|
|
|
assert_se(sd_dhcp_client_set_request_option(client, 17) == -EEXIST);
|
2014-04-11 05:45:46 +02:00
|
|
|
|
|
|
|
sd_dhcp_client_unref(client);
|
2013-12-09 22:43:09 +01:00
|
|
|
}
|
|
|
|
|
2017-08-04 03:27:51 +02:00
|
|
|
static void test_request_anonymize(sd_event *e) {
|
|
|
|
int r;
|
|
|
|
|
|
|
|
sd_dhcp_client *client;
|
|
|
|
|
|
|
|
if (verbose)
|
|
|
|
printf("* %s\n", __FUNCTION__);
|
|
|
|
|
|
|
|
/* Initialize client with Anonymize settings. */
|
|
|
|
r = sd_dhcp_client_new(&client, true);
|
|
|
|
|
|
|
|
assert_se(r >= 0);
|
|
|
|
assert_se(client);
|
|
|
|
|
|
|
|
r = sd_dhcp_client_attach_event(client, e, 0);
|
|
|
|
assert_se(r >= 0);
|
|
|
|
|
|
|
|
assert_se(sd_dhcp_client_set_request_option(client,
|
|
|
|
SD_DHCP_OPTION_NETBIOS_NAMESERVER) == -EEXIST);
|
|
|
|
/* This PRL option is not set when using Anonymize */
|
|
|
|
assert_se(sd_dhcp_client_set_request_option(client,
|
|
|
|
SD_DHCP_OPTION_HOST_NAME) == 0);
|
|
|
|
assert_se(sd_dhcp_client_set_request_option(client,
|
|
|
|
SD_DHCP_OPTION_PARAMETER_REQUEST_LIST)
|
|
|
|
== -EINVAL);
|
|
|
|
|
|
|
|
/* RFC7844: option 101 (SD_DHCP_OPTION_NEW_TZDB_TIMEZONE) is not set in the
|
|
|
|
* default PRL when using Anonymize, */
|
|
|
|
assert_se(sd_dhcp_client_set_request_option(client, 101) == 0);
|
|
|
|
assert_se(sd_dhcp_client_set_request_option(client, 101) == -EEXIST);
|
|
|
|
|
|
|
|
sd_dhcp_client_unref(client);
|
|
|
|
}
|
|
|
|
|
2015-08-27 17:48:24 +02:00
|
|
|
static void test_checksum(void) {
|
2013-12-09 22:43:16 +01:00
|
|
|
uint8_t buf[20] = {
|
|
|
|
0x45, 0x00, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x40, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0xff, 0xff, 0xff, 0xff
|
|
|
|
};
|
|
|
|
|
2014-02-18 15:07:38 +01:00
|
|
|
if (verbose)
|
|
|
|
printf("* %s\n", __FUNCTION__);
|
|
|
|
|
2014-06-16 15:24:28 +02:00
|
|
|
assert_se(dhcp_packet_checksum((uint8_t*)&buf, 20) == be16toh(0x78ae));
|
2013-12-09 22:43:16 +01:00
|
|
|
}
|
|
|
|
|
2018-11-01 14:46:27 +01:00
|
|
|
static void test_dhcp_identifier_set_iaid(void) {
|
2018-11-01 14:43:11 +01:00
|
|
|
uint32_t iaid_legacy;
|
|
|
|
be32_t iaid;
|
2018-11-01 14:46:27 +01:00
|
|
|
int ifindex;
|
|
|
|
|
|
|
|
for (;;) {
|
|
|
|
char ifname[IFNAMSIZ];
|
|
|
|
|
|
|
|
/* try to find an ifindex which does not exist. I causes dhcp_identifier_set_iaid()
|
|
|
|
* to hash the MAC address. */
|
|
|
|
pseudo_random_bytes(&ifindex, sizeof(ifindex));
|
|
|
|
if (ifindex > 0 && !if_indextoname(ifindex, ifname))
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2018-11-01 14:43:11 +01:00
|
|
|
assert_se(dhcp_identifier_set_iaid(ifindex, mac_addr, sizeof(mac_addr), true, &iaid_legacy) >= 0);
|
|
|
|
assert_se(dhcp_identifier_set_iaid(ifindex, mac_addr, sizeof(mac_addr), false, &iaid) >= 0);
|
2018-11-01 14:46:27 +01:00
|
|
|
|
2018-11-01 14:43:11 +01:00
|
|
|
/* we expect, that the MAC address was hashed. The legacy value is in native
|
2018-11-01 14:46:27 +01:00
|
|
|
* endianness. */
|
2018-11-01 14:43:11 +01:00
|
|
|
assert_se(iaid_legacy == 0x8dde4ba8u);
|
|
|
|
assert_se(iaid == htole32(0x8dde4ba8u));
|
|
|
|
#if __BYTE_ORDER == __LITTLE_ENDIAN
|
|
|
|
assert_se(iaid == iaid_legacy);
|
|
|
|
#else
|
|
|
|
assert_se(iaid == __bswap_32(iaid_legacy));
|
|
|
|
#endif
|
2018-11-01 14:46:27 +01:00
|
|
|
}
|
|
|
|
|
2015-08-27 17:48:24 +02:00
|
|
|
static int check_options(uint8_t code, uint8_t len, const void *option, void *userdata) {
|
2014-03-20 09:31:49 +01:00
|
|
|
switch(code) {
|
2016-01-20 14:44:24 +01:00
|
|
|
case SD_DHCP_OPTION_CLIENT_IDENTIFIER:
|
2015-01-22 00:53:16 +01:00
|
|
|
{
|
|
|
|
uint32_t iaid;
|
|
|
|
struct duid duid;
|
|
|
|
size_t duid_len;
|
|
|
|
|
|
|
|
assert_se(dhcp_identifier_set_duid_en(&duid, &duid_len) >= 0);
|
2018-11-01 14:43:11 +01:00
|
|
|
assert_se(dhcp_identifier_set_iaid(42, mac_addr, ETH_ALEN, true, &iaid) >= 0);
|
2015-01-22 00:53:16 +01:00
|
|
|
|
|
|
|
assert_se(len == sizeof(uint8_t) + sizeof(uint32_t) + duid_len);
|
|
|
|
assert_se(len == 19);
|
2015-08-26 23:05:34 +02:00
|
|
|
assert_se(((uint8_t*) option)[0] == 0xff);
|
2015-01-22 00:53:16 +01:00
|
|
|
|
2015-08-26 23:05:34 +02:00
|
|
|
assert_se(memcmp((uint8_t*) option + 1, &iaid, sizeof(iaid)) == 0);
|
|
|
|
assert_se(memcmp((uint8_t*) option + 5, &duid, duid_len) == 0);
|
2014-03-20 09:31:49 +01:00
|
|
|
break;
|
2015-01-22 00:53:16 +01:00
|
|
|
}
|
2014-03-20 09:31:49 +01:00
|
|
|
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2013-12-09 22:43:21 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-08-27 17:48:24 +02:00
|
|
|
int dhcp_network_send_raw_socket(int s, const union sockaddr_union *link, const void *packet, size_t len) {
|
2013-12-09 22:43:21 +01:00
|
|
|
size_t size;
|
2014-03-12 01:25:05 +01:00
|
|
|
_cleanup_free_ DHCPPacket *discover;
|
2013-12-09 22:43:21 +01:00
|
|
|
uint16_t ip_check, udp_check;
|
|
|
|
|
2014-02-22 20:22:41 +01:00
|
|
|
assert_se(s >= 0);
|
|
|
|
assert_se(packet);
|
2013-12-09 22:43:21 +01:00
|
|
|
|
2014-04-10 16:37:47 +02:00
|
|
|
size = sizeof(DHCPPacket);
|
2014-02-22 20:22:41 +01:00
|
|
|
assert_se(len > size);
|
2013-12-09 22:43:21 +01:00
|
|
|
|
|
|
|
discover = memdup(packet, len);
|
|
|
|
|
2014-02-22 20:22:41 +01:00
|
|
|
assert_se(discover->ip.ttl == IPDEFTTL);
|
|
|
|
assert_se(discover->ip.protocol == IPPROTO_UDP);
|
|
|
|
assert_se(discover->ip.saddr == INADDR_ANY);
|
|
|
|
assert_se(discover->ip.daddr == INADDR_BROADCAST);
|
|
|
|
assert_se(discover->udp.source == be16toh(DHCP_PORT_CLIENT));
|
|
|
|
assert_se(discover->udp.dest == be16toh(DHCP_PORT_SERVER));
|
2013-12-09 22:43:21 +01:00
|
|
|
|
|
|
|
ip_check = discover->ip.check;
|
|
|
|
|
|
|
|
discover->ip.ttl = 0;
|
|
|
|
discover->ip.check = discover->udp.len;
|
|
|
|
|
2014-06-16 15:24:28 +02:00
|
|
|
udp_check = ~dhcp_packet_checksum((uint8_t*)&discover->ip.ttl, len - 8);
|
2014-02-22 20:22:41 +01:00
|
|
|
assert_se(udp_check == 0xffff);
|
2013-12-09 22:43:21 +01:00
|
|
|
|
|
|
|
discover->ip.ttl = IPDEFTTL;
|
|
|
|
discover->ip.check = ip_check;
|
|
|
|
|
2014-06-16 15:24:28 +02:00
|
|
|
ip_check = ~dhcp_packet_checksum((uint8_t*)&discover->ip, sizeof(discover->ip));
|
2014-02-22 20:22:41 +01:00
|
|
|
assert_se(ip_check == 0xffff);
|
2013-12-09 22:43:21 +01:00
|
|
|
|
2014-02-22 20:22:41 +01:00
|
|
|
assert_se(discover->dhcp.xid);
|
2015-01-22 00:53:16 +01:00
|
|
|
assert_se(memcmp(discover->dhcp.chaddr, &mac_addr, ETH_ALEN) == 0);
|
2014-02-18 15:07:39 +01:00
|
|
|
|
2013-12-09 22:43:21 +01:00
|
|
|
size = len - sizeof(struct iphdr) - sizeof(struct udphdr);
|
|
|
|
|
2014-02-22 20:22:41 +01:00
|
|
|
assert_se(callback_recv);
|
2014-02-18 15:07:39 +01:00
|
|
|
callback_recv(size, &discover->dhcp);
|
2013-12-09 22:43:21 +01:00
|
|
|
|
|
|
|
return 575;
|
|
|
|
}
|
|
|
|
|
2015-08-27 17:48:24 +02:00
|
|
|
int dhcp_network_bind_raw_socket(
|
2020-07-02 08:49:19 +02:00
|
|
|
int ifindex,
|
2015-08-27 17:48:24 +02:00
|
|
|
union sockaddr_union *link,
|
|
|
|
uint32_t id,
|
|
|
|
const uint8_t *addr, size_t addr_len,
|
2016-11-11 00:34:19 +01:00
|
|
|
uint16_t arp_type, uint16_t port) {
|
2015-08-27 17:48:24 +02:00
|
|
|
|
2018-11-11 18:03:22 +01:00
|
|
|
if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, test_fd) < 0)
|
2013-12-09 22:43:26 +01:00
|
|
|
return -errno;
|
|
|
|
|
|
|
|
return test_fd[0];
|
|
|
|
}
|
|
|
|
|
2019-09-23 13:25:21 +02:00
|
|
|
int dhcp_network_bind_udp_socket(int ifindex, be32_t address, uint16_t port, int ip_service_type) {
|
2015-02-02 20:46:40 +01:00
|
|
|
int fd;
|
|
|
|
|
2018-11-11 18:03:22 +01:00
|
|
|
fd = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0);
|
2015-02-02 20:46:40 +01:00
|
|
|
if (fd < 0)
|
|
|
|
return -errno;
|
|
|
|
|
|
|
|
return fd;
|
2013-12-20 16:16:17 +01:00
|
|
|
}
|
|
|
|
|
2015-08-27 17:48:24 +02:00
|
|
|
int dhcp_network_send_udp_socket(int s, be32_t address, uint16_t port, const void *packet, size_t len) {
|
2013-12-20 16:16:17 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-08-27 17:48:24 +02:00
|
|
|
static int test_discover_message_verify(size_t size, struct DHCPMessage *dhcp) {
|
2014-02-18 15:07:39 +01:00
|
|
|
int res;
|
|
|
|
|
2015-11-25 17:29:30 +01:00
|
|
|
res = dhcp_option_parse(dhcp, size, check_options, NULL, NULL);
|
2014-02-22 20:22:41 +01:00
|
|
|
assert_se(res == DHCP_DISCOVER);
|
2014-02-18 15:07:39 +01:00
|
|
|
|
|
|
|
if (verbose)
|
|
|
|
printf(" recv DHCP Discover 0x%08x\n", be32toh(dhcp->xid));
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-08-27 17:48:24 +02:00
|
|
|
static void test_discover_message(sd_event *e) {
|
2013-12-09 22:43:21 +01:00
|
|
|
sd_dhcp_client *client;
|
2014-01-18 19:32:45 +01:00
|
|
|
int res, r;
|
2013-12-09 22:43:21 +01:00
|
|
|
|
2014-02-18 15:07:38 +01:00
|
|
|
if (verbose)
|
|
|
|
printf("* %s\n", __FUNCTION__);
|
|
|
|
|
2017-08-03 03:32:46 +02:00
|
|
|
r = sd_dhcp_client_new(&client, false);
|
2014-02-22 20:22:41 +01:00
|
|
|
assert_se(r >= 0);
|
|
|
|
assert_se(client);
|
2013-12-09 22:43:21 +01:00
|
|
|
|
2014-01-18 19:32:45 +01:00
|
|
|
r = sd_dhcp_client_attach_event(client, e, 0);
|
2014-02-22 20:22:41 +01:00
|
|
|
assert_se(r >= 0);
|
2014-01-18 19:32:45 +01:00
|
|
|
|
2016-05-23 16:13:18 +02:00
|
|
|
assert_se(sd_dhcp_client_set_ifindex(client, 42) >= 0);
|
2015-01-22 00:53:16 +01:00
|
|
|
assert_se(sd_dhcp_client_set_mac(client, mac_addr, ETH_ALEN, ARPHRD_ETHER) >= 0);
|
2013-12-09 22:43:21 +01:00
|
|
|
|
2014-02-22 20:22:41 +01:00
|
|
|
assert_se(sd_dhcp_client_set_request_option(client, 248) >= 0);
|
2013-12-09 22:43:21 +01:00
|
|
|
|
2014-02-18 15:07:39 +01:00
|
|
|
callback_recv = test_discover_message_verify;
|
|
|
|
|
2013-12-09 22:43:21 +01:00
|
|
|
res = sd_dhcp_client_start(client);
|
|
|
|
|
2017-10-04 16:01:32 +02:00
|
|
|
assert_se(IN_SET(res, 0, -EINPROGRESS));
|
2013-12-09 22:43:26 +01:00
|
|
|
|
2014-02-18 15:07:39 +01:00
|
|
|
sd_event_run(e, (uint64_t) -1);
|
|
|
|
|
|
|
|
sd_dhcp_client_stop(client);
|
2014-04-09 12:12:07 +02:00
|
|
|
sd_dhcp_client_unref(client);
|
2014-02-18 15:07:39 +01:00
|
|
|
|
2014-03-22 10:52:49 +01:00
|
|
|
test_fd[1] = safe_close(test_fd[1]);
|
2014-02-18 15:07:39 +01:00
|
|
|
|
|
|
|
callback_recv = NULL;
|
2013-12-09 22:43:21 +01:00
|
|
|
}
|
|
|
|
|
2014-02-18 15:07:40 +01:00
|
|
|
static uint8_t test_addr_acq_offer[] = {
|
|
|
|
0x45, 0x10, 0x01, 0x48, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x80, 0x11, 0xb3, 0x84, 0xc0, 0xa8, 0x02, 0x01,
|
|
|
|
0xc0, 0xa8, 0x02, 0xbf, 0x00, 0x43, 0x00, 0x44,
|
|
|
|
0x01, 0x34, 0x00, 0x00, 0x02, 0x01, 0x06, 0x00,
|
|
|
|
0x6f, 0x95, 0x2f, 0x30, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0xc0, 0xa8, 0x02, 0xbf,
|
|
|
|
0xc0, 0xa8, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x63, 0x82, 0x53, 0x63, 0x35, 0x01, 0x02, 0x36,
|
|
|
|
0x04, 0xc0, 0xa8, 0x02, 0x01, 0x33, 0x04, 0x00,
|
|
|
|
0x00, 0x02, 0x58, 0x01, 0x04, 0xff, 0xff, 0xff,
|
|
|
|
0x00, 0x2a, 0x04, 0xc0, 0xa8, 0x02, 0x01, 0x0f,
|
|
|
|
0x09, 0x6c, 0x61, 0x62, 0x2e, 0x69, 0x6e, 0x74,
|
|
|
|
0x72, 0x61, 0x03, 0x04, 0xc0, 0xa8, 0x02, 0x01,
|
|
|
|
0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
};
|
|
|
|
|
|
|
|
static uint8_t test_addr_acq_ack[] = {
|
|
|
|
0x45, 0x10, 0x01, 0x48, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x80, 0x11, 0xb3, 0x84, 0xc0, 0xa8, 0x02, 0x01,
|
|
|
|
0xc0, 0xa8, 0x02, 0xbf, 0x00, 0x43, 0x00, 0x44,
|
|
|
|
0x01, 0x34, 0x00, 0x00, 0x02, 0x01, 0x06, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0xc0, 0xa8, 0x02, 0xbf,
|
|
|
|
0xc0, 0xa8, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x63, 0x82, 0x53, 0x63, 0x35, 0x01, 0x05, 0x36,
|
|
|
|
0x04, 0xc0, 0xa8, 0x02, 0x01, 0x33, 0x04, 0x00,
|
2014-04-10 16:37:47 +02:00
|
|
|
0x00, 0x02, 0x58, 0x01, 0x04, 0xff, 0xff, 0xff,
|
2014-02-18 15:07:40 +01:00
|
|
|
0x00, 0x2a, 0x04, 0xc0, 0xa8, 0x02, 0x01, 0x0f,
|
|
|
|
0x09, 0x6c, 0x61, 0x62, 0x2e, 0x69, 0x6e, 0x74,
|
|
|
|
0x72, 0x61, 0x03, 0x04, 0xc0, 0xa8, 0x02, 0x01,
|
|
|
|
0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
};
|
|
|
|
|
2019-05-01 00:47:41 +02:00
|
|
|
static int test_addr_acq_acquired(sd_dhcp_client *client, int event,
|
2014-04-08 02:58:36 +02:00
|
|
|
void *userdata) {
|
2014-02-18 15:07:40 +01:00
|
|
|
sd_event *e = userdata;
|
|
|
|
sd_dhcp_lease *lease;
|
|
|
|
struct in_addr addr;
|
2018-12-14 11:10:57 +01:00
|
|
|
const struct in_addr *addrs;
|
2014-02-18 15:07:40 +01:00
|
|
|
|
2014-02-22 20:22:41 +01:00
|
|
|
assert_se(client);
|
2019-05-01 00:47:41 +02:00
|
|
|
assert_se(IN_SET(event, SD_DHCP_CLIENT_EVENT_IP_ACQUIRE, SD_DHCP_CLIENT_EVENT_SELECTING));
|
2014-02-18 15:07:40 +01:00
|
|
|
|
2014-02-22 20:22:41 +01:00
|
|
|
assert_se(sd_dhcp_client_get_lease(client, &lease) >= 0);
|
|
|
|
assert_se(lease);
|
2014-02-18 15:07:40 +01:00
|
|
|
|
2014-02-22 20:22:41 +01:00
|
|
|
assert_se(sd_dhcp_lease_get_address(lease, &addr) >= 0);
|
|
|
|
assert_se(memcmp(&addr.s_addr, &test_addr_acq_ack[44],
|
2014-02-18 15:07:40 +01:00
|
|
|
sizeof(addr.s_addr)) == 0);
|
|
|
|
|
2014-02-22 20:22:41 +01:00
|
|
|
assert_se(sd_dhcp_lease_get_netmask(lease, &addr) >= 0);
|
|
|
|
assert_se(memcmp(&addr.s_addr, &test_addr_acq_ack[285],
|
2014-02-18 15:07:40 +01:00
|
|
|
sizeof(addr.s_addr)) == 0);
|
|
|
|
|
2018-12-14 11:10:57 +01:00
|
|
|
assert_se(sd_dhcp_lease_get_router(lease, &addrs) == 1);
|
|
|
|
assert_se(memcmp(&addrs[0].s_addr, &test_addr_acq_ack[308],
|
|
|
|
sizeof(addrs[0].s_addr)) == 0);
|
2014-02-18 15:07:40 +01:00
|
|
|
|
|
|
|
if (verbose)
|
|
|
|
printf(" DHCP address acquired\n");
|
|
|
|
|
|
|
|
sd_event_exit(e, 0);
|
2019-05-01 00:47:41 +02:00
|
|
|
|
|
|
|
return 0;
|
2014-02-18 15:07:40 +01:00
|
|
|
}
|
|
|
|
|
2014-04-08 02:58:36 +02:00
|
|
|
static int test_addr_acq_recv_request(size_t size, DHCPMessage *request) {
|
2014-02-18 15:07:40 +01:00
|
|
|
uint16_t udp_check = 0;
|
2014-03-20 09:31:49 +01:00
|
|
|
uint8_t *msg_bytes = (uint8_t *)request;
|
2014-02-18 15:07:40 +01:00
|
|
|
int res;
|
|
|
|
|
2015-11-25 17:29:30 +01:00
|
|
|
res = dhcp_option_parse(request, size, check_options, NULL, NULL);
|
2014-02-22 20:22:41 +01:00
|
|
|
assert_se(res == DHCP_REQUEST);
|
|
|
|
assert_se(xid == request->xid);
|
2014-02-18 15:07:40 +01:00
|
|
|
|
2016-01-20 14:44:24 +01:00
|
|
|
assert_se(msg_bytes[size - 1] == SD_DHCP_OPTION_END);
|
2014-03-20 09:31:49 +01:00
|
|
|
|
2014-02-18 15:07:40 +01:00
|
|
|
if (verbose)
|
|
|
|
printf(" recv DHCP Request 0x%08x\n", be32toh(xid));
|
|
|
|
|
|
|
|
memcpy(&test_addr_acq_ack[26], &udp_check, sizeof(udp_check));
|
|
|
|
memcpy(&test_addr_acq_ack[32], &xid, sizeof(xid));
|
2015-01-22 00:53:16 +01:00
|
|
|
memcpy(&test_addr_acq_ack[56], &mac_addr, ETHER_ADDR_LEN);
|
2014-02-18 15:07:40 +01:00
|
|
|
|
|
|
|
callback_recv = NULL;
|
|
|
|
|
|
|
|
res = write(test_fd[1], test_addr_acq_ack,
|
|
|
|
sizeof(test_addr_acq_ack));
|
2014-02-22 20:22:41 +01:00
|
|
|
assert_se(res == sizeof(test_addr_acq_ack));
|
2014-02-18 15:07:40 +01:00
|
|
|
|
|
|
|
if (verbose)
|
|
|
|
printf(" send DHCP Ack\n");
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
};
|
|
|
|
|
2014-04-08 02:58:36 +02:00
|
|
|
static int test_addr_acq_recv_discover(size_t size, DHCPMessage *discover) {
|
2014-02-18 15:07:40 +01:00
|
|
|
uint16_t udp_check = 0;
|
2014-03-20 09:31:49 +01:00
|
|
|
uint8_t *msg_bytes = (uint8_t *)discover;
|
2014-02-18 15:07:40 +01:00
|
|
|
int res;
|
|
|
|
|
2015-11-25 17:29:30 +01:00
|
|
|
res = dhcp_option_parse(discover, size, check_options, NULL, NULL);
|
2014-02-22 20:22:41 +01:00
|
|
|
assert_se(res == DHCP_DISCOVER);
|
2014-02-18 15:07:40 +01:00
|
|
|
|
2016-01-20 14:44:24 +01:00
|
|
|
assert_se(msg_bytes[size - 1] == SD_DHCP_OPTION_END);
|
2014-03-20 09:31:49 +01:00
|
|
|
|
2014-02-18 15:07:40 +01:00
|
|
|
xid = discover->xid;
|
|
|
|
|
|
|
|
if (verbose)
|
|
|
|
printf(" recv DHCP Discover 0x%08x\n", be32toh(xid));
|
|
|
|
|
|
|
|
memcpy(&test_addr_acq_offer[26], &udp_check, sizeof(udp_check));
|
|
|
|
memcpy(&test_addr_acq_offer[32], &xid, sizeof(xid));
|
2015-01-22 00:53:16 +01:00
|
|
|
memcpy(&test_addr_acq_offer[56], &mac_addr, ETHER_ADDR_LEN);
|
2014-02-18 15:07:40 +01:00
|
|
|
|
|
|
|
callback_recv = test_addr_acq_recv_request;
|
|
|
|
|
|
|
|
res = write(test_fd[1], test_addr_acq_offer,
|
|
|
|
sizeof(test_addr_acq_offer));
|
2014-02-22 20:22:41 +01:00
|
|
|
assert_se(res == sizeof(test_addr_acq_offer));
|
2014-02-18 15:07:40 +01:00
|
|
|
|
|
|
|
if (verbose)
|
2014-04-08 02:58:36 +02:00
|
|
|
printf(" sent DHCP Offer\n");
|
2014-02-18 15:07:40 +01:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-04-08 02:58:36 +02:00
|
|
|
static void test_addr_acq(sd_event *e) {
|
2014-02-18 15:07:40 +01:00
|
|
|
sd_dhcp_client *client;
|
|
|
|
int res, r;
|
|
|
|
|
|
|
|
if (verbose)
|
|
|
|
printf("* %s\n", __FUNCTION__);
|
|
|
|
|
2017-08-03 03:32:46 +02:00
|
|
|
r = sd_dhcp_client_new(&client, false);
|
2014-02-22 20:22:41 +01:00
|
|
|
assert_se(r >= 0);
|
|
|
|
assert_se(client);
|
2014-02-18 15:07:40 +01:00
|
|
|
|
|
|
|
r = sd_dhcp_client_attach_event(client, e, 0);
|
2014-02-22 20:22:41 +01:00
|
|
|
assert_se(r >= 0);
|
2014-02-18 15:07:40 +01:00
|
|
|
|
2016-05-23 16:13:18 +02:00
|
|
|
assert_se(sd_dhcp_client_set_ifindex(client, 42) >= 0);
|
2015-01-22 00:53:16 +01:00
|
|
|
assert_se(sd_dhcp_client_set_mac(client, mac_addr, ETH_ALEN, ARPHRD_ETHER) >= 0);
|
2014-02-18 15:07:40 +01:00
|
|
|
|
2015-01-31 18:25:12 +01:00
|
|
|
assert_se(sd_dhcp_client_set_callback(client, test_addr_acq_acquired, e) >= 0);
|
2014-02-18 15:07:40 +01:00
|
|
|
|
|
|
|
callback_recv = test_addr_acq_recv_discover;
|
|
|
|
|
2020-07-28 11:18:26 +02:00
|
|
|
assert_se(sd_event_add_time_relative(
|
|
|
|
e, &test_hangcheck,
|
|
|
|
clock_boottime_or_monotonic(),
|
|
|
|
2 * USEC_PER_SEC, 0,
|
|
|
|
test_dhcp_hangcheck, NULL) >= 0);
|
2014-03-12 11:52:00 +01:00
|
|
|
|
2014-02-18 15:07:40 +01:00
|
|
|
res = sd_dhcp_client_start(client);
|
2017-10-04 16:01:32 +02:00
|
|
|
assert_se(IN_SET(res, 0, -EINPROGRESS));
|
2014-02-18 15:07:40 +01:00
|
|
|
|
2014-10-13 10:07:05 +02:00
|
|
|
assert_se(sd_event_loop(e) >= 0);
|
2014-02-18 15:07:40 +01:00
|
|
|
|
2014-03-12 11:52:00 +01:00
|
|
|
test_hangcheck = sd_event_source_unref(test_hangcheck);
|
|
|
|
|
2014-10-13 10:07:05 +02:00
|
|
|
assert_se(sd_dhcp_client_set_callback(client, NULL, NULL) >= 0);
|
|
|
|
assert_se(sd_dhcp_client_stop(client) >= 0);
|
2014-04-09 12:12:07 +02:00
|
|
|
sd_dhcp_client_unref(client);
|
2014-02-18 15:07:40 +01:00
|
|
|
|
2014-03-22 10:52:49 +01:00
|
|
|
test_fd[1] = safe_close(test_fd[1]);
|
2014-02-18 15:07:40 +01:00
|
|
|
|
|
|
|
callback_recv = NULL;
|
|
|
|
xid = 0;
|
|
|
|
}
|
|
|
|
|
2014-04-08 02:58:36 +02:00
|
|
|
int main(int argc, char *argv[]) {
|
tree-wide: expose "p"-suffix unref calls in public APIs to make gcc cleanup easy
GLIB has recently started to officially support the gcc cleanup
attribute in its public API, hence let's do the same for our APIs.
With this patch we'll define an xyz_unrefp() call for each public
xyz_unref() call, to make it easy to use inside a
__attribute__((cleanup())) expression. Then, all code is ported over to
make use of this.
The new calls are also documented in the man pages, with examples how to
use them (well, I only added docs where the _unref() call itself already
had docs, and the examples, only cover sd_bus_unrefp() and
sd_event_unrefp()).
This also renames sd_lldp_free() to sd_lldp_unref(), since that's how we
tend to call our destructors these days.
Note that this defines no public macro that wraps gcc's attribute and
makes it easier to use. While I think it's our duty in the library to
make our stuff easy to use, I figure it's not our duty to make gcc's own
features easy to use on its own. Most likely, client code which wants to
make use of this should define its own:
#define _cleanup_(function) __attribute__((cleanup(function)))
Or similar, to make the gcc feature easier to use.
Making this logic public has the benefit that we can remove three header
files whose only purpose was to define these functions internally.
See #2008.
2015-11-27 19:13:45 +01:00
|
|
|
_cleanup_(sd_event_unrefp) sd_event *e;
|
2014-04-11 05:45:46 +02:00
|
|
|
|
2018-09-13 14:31:13 +02:00
|
|
|
test_setup_logging(LOG_DEBUG);
|
2013-12-09 22:43:25 +01:00
|
|
|
|
2014-02-22 20:22:41 +01:00
|
|
|
assert_se(sd_event_new(&e) >= 0);
|
2013-12-09 22:43:25 +01:00
|
|
|
|
|
|
|
test_request_basic(e);
|
2017-08-04 03:27:51 +02:00
|
|
|
test_request_anonymize(e);
|
2013-12-09 22:43:16 +01:00
|
|
|
test_checksum();
|
2018-11-01 14:46:27 +01:00
|
|
|
test_dhcp_identifier_set_iaid();
|
2013-12-09 22:43:09 +01:00
|
|
|
|
2013-12-09 22:43:25 +01:00
|
|
|
test_discover_message(e);
|
2014-02-18 15:07:40 +01:00
|
|
|
test_addr_acq(e);
|
2013-12-09 22:43:21 +01:00
|
|
|
|
2018-05-13 22:28:24 +02:00
|
|
|
#if VALGRIND
|
2015-02-02 20:50:56 +01:00
|
|
|
/* Make sure the async_close thread has finished.
|
|
|
|
* valgrind would report some of the phread_* structures
|
|
|
|
* as not cleaned up properly. */
|
|
|
|
sleep(1);
|
|
|
|
#endif
|
|
|
|
|
2013-12-09 22:43:09 +01:00
|
|
|
return 0;
|
|
|
|
}
|