Systemd/src/libsystemd-network/test-dhcp-client.c
Lennart Poettering 4afd3348c7 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:19:36 +01:00

516 lines
18 KiB
C

/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
/***
This file is part of systemd.
Copyright (C) 2013 Intel Corporation. All rights reserved.
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include <errno.h>
#include <stdio.h>
#include <sys/socket.h>
#include <unistd.h>
#include "sd-dhcp-client.h"
#include "sd-event.h"
#include "alloc-util.h"
#include "dhcp-identifier.h"
#include "dhcp-internal.h"
#include "dhcp-protocol.h"
#include "fd-util.h"
#include "util.h"
static uint8_t mac_addr[] = {'A', 'B', 'C', '1', '2', '3'};
typedef int (*test_callback_recv_t)(size_t size, DHCPMessage *dhcp);
static bool verbose = true;
static int test_fd[2];
static test_callback_recv_t callback_recv;
static be32_t xid;
static sd_event_source *test_hangcheck;
static int test_dhcp_hangcheck(sd_event_source *s, uint64_t usec, void *userdata) {
assert_not_reached("Test case should have completed in 2 seconds");
return 0;
}
static void test_request_basic(sd_event *e) {
int r;
sd_dhcp_client *client;
if (verbose)
printf("* %s\n", __FUNCTION__);
r = sd_dhcp_client_new(&client);
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(NULL, 0) == -EINVAL);
assert_se(sd_dhcp_client_set_request_address(NULL, NULL) == -EINVAL);
assert_se(sd_dhcp_client_set_index(NULL, 0) == -EINVAL);
assert_se(sd_dhcp_client_set_index(client, 15) == 0);
assert_se(sd_dhcp_client_set_index(client, -42) == -EINVAL);
assert_se(sd_dhcp_client_set_index(client, -1) == -EINVAL);
assert_se(sd_dhcp_client_set_index(client, 0) == -EINVAL);
assert_se(sd_dhcp_client_set_index(client, 1) == 0);
assert_se(sd_dhcp_client_set_request_option(client,
DHCP_OPTION_SUBNET_MASK) == -EEXIST);
assert_se(sd_dhcp_client_set_request_option(client,
DHCP_OPTION_ROUTER) == -EEXIST);
assert_se(sd_dhcp_client_set_request_option(client,
DHCP_OPTION_HOST_NAME) == -EEXIST);
assert_se(sd_dhcp_client_set_request_option(client,
DHCP_OPTION_DOMAIN_NAME) == -EEXIST);
assert_se(sd_dhcp_client_set_request_option(client,
DHCP_OPTION_DOMAIN_NAME_SERVER) == -EEXIST);
assert_se(sd_dhcp_client_set_request_option(client,
DHCP_OPTION_PAD) == -EINVAL);
assert_se(sd_dhcp_client_set_request_option(client,
DHCP_OPTION_END) == -EINVAL);
assert_se(sd_dhcp_client_set_request_option(client,
DHCP_OPTION_MESSAGE_TYPE) == -EINVAL);
assert_se(sd_dhcp_client_set_request_option(client,
DHCP_OPTION_OVERLOAD) == -EINVAL);
assert_se(sd_dhcp_client_set_request_option(client,
DHCP_OPTION_PARAMETER_REQUEST_LIST)
== -EINVAL);
assert_se(sd_dhcp_client_set_request_option(client, 33) == 0);
assert_se(sd_dhcp_client_set_request_option(client, 33) == -EEXIST);
assert_se(sd_dhcp_client_set_request_option(client, 44) == 0);
assert_se(sd_dhcp_client_set_request_option(client, 33) == -EEXIST);
sd_dhcp_client_unref(client);
}
static void test_checksum(void) {
uint8_t buf[20] = {
0x45, 0x00, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00,
0x40, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xff, 0xff, 0xff, 0xff
};
if (verbose)
printf("* %s\n", __FUNCTION__);
assert_se(dhcp_packet_checksum((uint8_t*)&buf, 20) == be16toh(0x78ae));
}
static int check_options(uint8_t code, uint8_t len, const void *option, void *userdata) {
switch(code) {
case DHCP_OPTION_CLIENT_IDENTIFIER:
{
uint32_t iaid;
struct duid duid;
size_t duid_len;
assert_se(dhcp_identifier_set_duid_en(&duid, &duid_len) >= 0);
assert_se(dhcp_identifier_set_iaid(42, mac_addr, ETH_ALEN, &iaid) >= 0);
assert_se(len == sizeof(uint8_t) + sizeof(uint32_t) + duid_len);
assert_se(len == 19);
assert_se(((uint8_t*) option)[0] == 0xff);
assert_se(memcmp((uint8_t*) option + 1, &iaid, sizeof(iaid)) == 0);
assert_se(memcmp((uint8_t*) option + 5, &duid, duid_len) == 0);
break;
}
default:
break;
}
return 0;
}
int dhcp_network_send_raw_socket(int s, const union sockaddr_union *link, const void *packet, size_t len) {
size_t size;
_cleanup_free_ DHCPPacket *discover;
uint16_t ip_check, udp_check;
assert_se(s >= 0);
assert_se(packet);
size = sizeof(DHCPPacket);
assert_se(len > size);
discover = memdup(packet, len);
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));
ip_check = discover->ip.check;
discover->ip.ttl = 0;
discover->ip.check = discover->udp.len;
udp_check = ~dhcp_packet_checksum((uint8_t*)&discover->ip.ttl, len - 8);
assert_se(udp_check == 0xffff);
discover->ip.ttl = IPDEFTTL;
discover->ip.check = ip_check;
ip_check = ~dhcp_packet_checksum((uint8_t*)&discover->ip, sizeof(discover->ip));
assert_se(ip_check == 0xffff);
assert_se(discover->dhcp.xid);
assert_se(memcmp(discover->dhcp.chaddr, &mac_addr, ETH_ALEN) == 0);
size = len - sizeof(struct iphdr) - sizeof(struct udphdr);
assert_se(callback_recv);
callback_recv(size, &discover->dhcp);
return 575;
}
int dhcp_network_bind_raw_socket(
int index,
union sockaddr_union *link,
uint32_t id,
const uint8_t *addr, size_t addr_len,
uint16_t arp_type) {
if (socketpair(AF_UNIX, SOCK_STREAM, 0, test_fd) < 0)
return -errno;
return test_fd[0];
}
int dhcp_network_bind_udp_socket(be32_t address, uint16_t port) {
int fd;
fd = socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC, 0);
if (fd < 0)
return -errno;
return fd;
}
int dhcp_network_send_udp_socket(int s, be32_t address, uint16_t port, const void *packet, size_t len) {
return 0;
}
static int test_discover_message_verify(size_t size, struct DHCPMessage *dhcp) {
int res;
res = dhcp_option_parse(dhcp, size, check_options, NULL, NULL);
assert_se(res == DHCP_DISCOVER);
if (verbose)
printf(" recv DHCP Discover 0x%08x\n", be32toh(dhcp->xid));
return 0;
}
static void test_discover_message(sd_event *e) {
sd_dhcp_client *client;
int res, r;
if (verbose)
printf("* %s\n", __FUNCTION__);
r = sd_dhcp_client_new(&client);
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_index(client, 42) >= 0);
assert_se(sd_dhcp_client_set_mac(client, mac_addr, ETH_ALEN, ARPHRD_ETHER) >= 0);
assert_se(sd_dhcp_client_set_request_option(client, 248) >= 0);
callback_recv = test_discover_message_verify;
res = sd_dhcp_client_start(client);
assert_se(res == 0 || res == -EINPROGRESS);
sd_event_run(e, (uint64_t) -1);
sd_dhcp_client_stop(client);
sd_dhcp_client_unref(client);
test_fd[1] = safe_close(test_fd[1]);
callback_recv = NULL;
}
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,
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 void test_addr_acq_acquired(sd_dhcp_client *client, int event,
void *userdata) {
sd_event *e = userdata;
sd_dhcp_lease *lease;
struct in_addr addr;
assert_se(client);
assert_se(event == SD_DHCP_CLIENT_EVENT_IP_ACQUIRE);
assert_se(sd_dhcp_client_get_lease(client, &lease) >= 0);
assert_se(lease);
assert_se(sd_dhcp_lease_get_address(lease, &addr) >= 0);
assert_se(memcmp(&addr.s_addr, &test_addr_acq_ack[44],
sizeof(addr.s_addr)) == 0);
assert_se(sd_dhcp_lease_get_netmask(lease, &addr) >= 0);
assert_se(memcmp(&addr.s_addr, &test_addr_acq_ack[285],
sizeof(addr.s_addr)) == 0);
assert_se(sd_dhcp_lease_get_router(lease, &addr) >= 0);
assert_se(memcmp(&addr.s_addr, &test_addr_acq_ack[308],
sizeof(addr.s_addr)) == 0);
if (verbose)
printf(" DHCP address acquired\n");
sd_event_exit(e, 0);
}
static int test_addr_acq_recv_request(size_t size, DHCPMessage *request) {
uint16_t udp_check = 0;
uint8_t *msg_bytes = (uint8_t *)request;
int res;
res = dhcp_option_parse(request, size, check_options, NULL, NULL);
assert_se(res == DHCP_REQUEST);
assert_se(xid == request->xid);
assert_se(msg_bytes[size - 1] == DHCP_OPTION_END);
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));
memcpy(&test_addr_acq_ack[56], &mac_addr, ETHER_ADDR_LEN);
callback_recv = NULL;
res = write(test_fd[1], test_addr_acq_ack,
sizeof(test_addr_acq_ack));
assert_se(res == sizeof(test_addr_acq_ack));
if (verbose)
printf(" send DHCP Ack\n");
return 0;
};
static int test_addr_acq_recv_discover(size_t size, DHCPMessage *discover) {
uint16_t udp_check = 0;
uint8_t *msg_bytes = (uint8_t *)discover;
int res;
res = dhcp_option_parse(discover, size, check_options, NULL, NULL);
assert_se(res == DHCP_DISCOVER);
assert_se(msg_bytes[size - 1] == DHCP_OPTION_END);
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));
memcpy(&test_addr_acq_offer[56], &mac_addr, ETHER_ADDR_LEN);
callback_recv = test_addr_acq_recv_request;
res = write(test_fd[1], test_addr_acq_offer,
sizeof(test_addr_acq_offer));
assert_se(res == sizeof(test_addr_acq_offer));
if (verbose)
printf(" sent DHCP Offer\n");
return 0;
}
static void test_addr_acq(sd_event *e) {
usec_t time_now = now(clock_boottime_or_monotonic());
sd_dhcp_client *client;
int res, r;
if (verbose)
printf("* %s\n", __FUNCTION__);
r = sd_dhcp_client_new(&client);
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_index(client, 42) >= 0);
assert_se(sd_dhcp_client_set_mac(client, mac_addr, ETH_ALEN, ARPHRD_ETHER) >= 0);
assert_se(sd_dhcp_client_set_callback(client, test_addr_acq_acquired, e) >= 0);
callback_recv = test_addr_acq_recv_discover;
assert_se(sd_event_add_time(e, &test_hangcheck,
clock_boottime_or_monotonic(),
time_now + 2 * USEC_PER_SEC, 0,
test_dhcp_hangcheck, NULL) >= 0);
res = sd_dhcp_client_start(client);
assert_se(res == 0 || res == -EINPROGRESS);
assert_se(sd_event_loop(e) >= 0);
test_hangcheck = sd_event_source_unref(test_hangcheck);
assert_se(sd_dhcp_client_set_callback(client, NULL, NULL) >= 0);
assert_se(sd_dhcp_client_stop(client) >= 0);
sd_dhcp_client_unref(client);
test_fd[1] = safe_close(test_fd[1]);
callback_recv = NULL;
xid = 0;
}
int main(int argc, char *argv[]) {
_cleanup_(sd_event_unrefp) sd_event *e;
log_set_max_level(LOG_DEBUG);
log_parse_environment();
log_open();
assert_se(sd_event_new(&e) >= 0);
test_request_basic(e);
test_checksum();
test_discover_message(e);
test_addr_acq(e);
#ifdef VALGRIND
/* Make sure the async_close thread has finished.
* valgrind would report some of the phread_* structures
* as not cleaned up properly. */
sleep(1);
#endif
return 0;
}