aboutsummaryrefslogtreecommitdiff
path: root/sbin/dhclient/tests
diff options
context:
space:
mode:
Diffstat (limited to 'sbin/dhclient/tests')
-rw-r--r--sbin/dhclient/tests/Makefile17
-rw-r--r--sbin/dhclient/tests/Makefile.depend18
-rw-r--r--sbin/dhclient/tests/fake.c77
-rw-r--r--sbin/dhclient/tests/option-domain-search.c374
-rw-r--r--sbin/dhclient/tests/pcp.sh196
5 files changed, 682 insertions, 0 deletions
diff --git a/sbin/dhclient/tests/Makefile b/sbin/dhclient/tests/Makefile
new file mode 100644
index 000000000000..2cca8635fa13
--- /dev/null
+++ b/sbin/dhclient/tests/Makefile
@@ -0,0 +1,17 @@
+.PATH: ${.CURDIR:H}
+
+ATF_TESTS_SH= pcp
+
+# Tests assign a common IP address.
+TEST_METADATA.pcp+= is_exclusive=true
+
+PLAIN_TESTS_C= option-domain-search_test
+SRCS.option-domain-search_test= alloc.c convert.c hash.c options.c \
+ tables.c parse.c conflex.c tree.c fake.c \
+ option-domain-search.c
+CFLAGS.option-domain-search_test+= -I${.CURDIR:H}
+LIBADD.option-domain-search_test= util
+
+WARNS?= 2
+
+.include <bsd.test.mk>
diff --git a/sbin/dhclient/tests/Makefile.depend b/sbin/dhclient/tests/Makefile.depend
new file mode 100644
index 000000000000..28acae7b0d55
--- /dev/null
+++ b/sbin/dhclient/tests/Makefile.depend
@@ -0,0 +1,18 @@
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libutil \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/sbin/dhclient/tests/fake.c b/sbin/dhclient/tests/fake.c
new file mode 100644
index 000000000000..15ed04dde077
--- /dev/null
+++ b/sbin/dhclient/tests/fake.c
@@ -0,0 +1,77 @@
+
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stdio.h>
+
+#include "dhcpd.h"
+
+extern jmp_buf env;
+int warnings_occurred;
+
+void
+error(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ (void)vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ fprintf(stderr, "\n");
+
+ longjmp(env, 1);
+}
+
+int
+warning(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ (void)vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ fprintf(stderr, "\n");
+
+ /*
+ * The original warning() would return "ret" here. We do this to
+ * check warnings explicitly.
+ */
+ longjmp(env, 1);
+}
+
+int
+note(const char *fmt, ...)
+{
+ int ret;
+ va_list ap;
+
+ va_start(ap, fmt);
+ ret = vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ fprintf(stderr, "\n");
+
+ return ret;
+}
+
+int
+parse_warn(const char *fmt, ...)
+{
+ int ret;
+ va_list ap;
+
+ va_start(ap, fmt);
+ ret = vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ fprintf(stderr, "\n");
+
+ return ret;
+}
+
+void
+bootp(struct packet *packet)
+{
+}
+
+void
+dhcp(struct packet *packet)
+{
+}
diff --git a/sbin/dhclient/tests/option-domain-search.c b/sbin/dhclient/tests/option-domain-search.c
new file mode 100644
index 000000000000..6a7f92c8775d
--- /dev/null
+++ b/sbin/dhclient/tests/option-domain-search.c
@@ -0,0 +1,374 @@
+
+#include <setjmp.h>
+#include <stdlib.h>
+
+#include "dhcpd.h"
+
+jmp_buf env;
+
+void expand_domain_search(struct packet *packet);
+
+void
+no_option_present()
+{
+ int ret;
+ struct option_data option;
+ struct packet p;
+
+ option.data = NULL;
+ option.len = 0;
+ p.options[DHO_DOMAIN_SEARCH] = option;
+
+ ret = setjmp(env);
+ if (ret == 0)
+ expand_domain_search(&p);
+
+ if (p.options[DHO_DOMAIN_SEARCH].len != 0 ||
+ p.options[DHO_DOMAIN_SEARCH].data != NULL)
+ abort();
+}
+
+void
+one_domain_valid()
+{
+ int ret;
+ struct packet p;
+ struct option_data *option;
+
+ char *data = "\007example\003org\0";
+ char *expected = "example.org.";
+
+ option = &p.options[DHO_DOMAIN_SEARCH];
+ option->len = 13;
+ option->data = malloc(option->len);
+ memcpy(option->data, data, option->len);
+
+ ret = setjmp(env);
+ if (ret == 0)
+ expand_domain_search(&p);
+
+ if (option->len != strlen(expected) ||
+ strcmp(option->data, expected) != 0)
+ abort();
+
+ free(option->data);
+}
+
+void
+one_domain_truncated1()
+{
+ int ret;
+ struct option_data *option;
+ struct packet p;
+
+ char *data = "\007example\003org";
+
+ option = &p.options[DHO_DOMAIN_SEARCH];
+ option->len = 12;
+ option->data = malloc(option->len);
+ memcpy(option->data, data, option->len);
+
+ ret = setjmp(env);
+ if (ret == 0)
+ expand_domain_search(&p);
+
+ if (ret != 1)
+ abort();
+
+ free(option->data);
+}
+
+void
+one_domain_truncated2()
+{
+ int ret;
+ struct option_data *option;
+ struct packet p;
+
+ char *data = "\007ex";
+
+ option = &p.options[DHO_DOMAIN_SEARCH];
+ option->len = 3;
+ option->data = malloc(option->len);
+ memcpy(option->data, data, option->len);
+
+ ret = setjmp(env);
+ if (ret == 0)
+ expand_domain_search(&p);
+
+ if (ret != 1)
+ abort();
+
+ free(option->data);
+}
+
+void
+two_domains_valid()
+{
+ int ret;
+ struct packet p;
+ struct option_data *option;
+
+ char *data = "\007example\003org\0\007example\003com\0";
+ char *expected = "example.org. example.com.";
+
+ option = &p.options[DHO_DOMAIN_SEARCH];
+ option->len = 26;
+ option->data = malloc(option->len);
+ memcpy(option->data, data, option->len);
+
+ ret = setjmp(env);
+ if (ret == 0)
+ expand_domain_search(&p);
+
+ if (option->len != strlen(expected) ||
+ strcmp(option->data, expected) != 0)
+ abort();
+
+ free(option->data);
+}
+
+void
+two_domains_truncated1()
+{
+ int ret;
+ struct option_data *option;
+ struct packet p;
+
+ char *data = "\007example\003org\0\007example\003com";
+
+ option = &p.options[DHO_DOMAIN_SEARCH];
+ option->len = 25;
+ option->data = malloc(option->len);
+ memcpy(option->data, data, option->len);
+
+ ret = setjmp(env);
+ if (ret == 0)
+ expand_domain_search(&p);
+
+ if (ret != 1)
+ abort();
+
+ free(option->data);
+}
+
+void
+two_domains_truncated2()
+{
+ int ret;
+ struct option_data *option;
+ struct packet p;
+
+ char *data = "\007example\003org\0\007ex";
+
+ option = &p.options[DHO_DOMAIN_SEARCH];
+ option->len = 16;
+ option->data = malloc(option->len);
+ memcpy(option->data, data, option->len);
+
+ ret = setjmp(env);
+ if (ret == 0)
+ expand_domain_search(&p);
+
+ if (ret != 1)
+ abort();
+
+ free(option->data);
+}
+
+void
+two_domains_compressed()
+{
+ int ret;
+ struct packet p;
+ struct option_data *option;
+
+ char *data = "\007example\003org\0\006foobar\xc0\x08";
+ char *expected = "example.org. foobar.org.";
+
+ option = &p.options[DHO_DOMAIN_SEARCH];
+ option->len = 22;
+ option->data = malloc(option->len);
+ memcpy(option->data, data, option->len);
+
+ ret = setjmp(env);
+ if (ret == 0)
+ expand_domain_search(&p);
+
+ if (option->len != strlen(expected) ||
+ strcmp(option->data, expected) != 0)
+ abort();
+
+ free(option->data);
+}
+
+void
+two_domains_infloop()
+{
+ int ret;
+ struct packet p;
+ struct option_data *option;
+
+ char *data = "\007example\003org\0\006foobar\xc0\x0d";
+
+ option = &p.options[DHO_DOMAIN_SEARCH];
+ option->len = 22;
+ option->data = malloc(option->len);
+ memcpy(option->data, data, option->len);
+
+ ret = setjmp(env);
+ if (ret == 0)
+ expand_domain_search(&p);
+
+ if (ret != 1)
+ abort();
+
+ free(option->data);
+}
+
+void
+two_domains_forwardptr()
+{
+ int ret;
+ struct packet p;
+ struct option_data *option;
+
+ char *data = "\007example\003org\xc0\x0d\006foobar\0";
+
+ option = &p.options[DHO_DOMAIN_SEARCH];
+ option->len = 22;
+ option->data = malloc(option->len);
+ memcpy(option->data, data, option->len);
+
+ ret = setjmp(env);
+ if (ret == 0)
+ expand_domain_search(&p);
+
+ if (ret != 1)
+ abort();
+
+ free(option->data);
+}
+
+void
+two_domains_truncatedptr()
+{
+ int ret;
+ struct packet p;
+ struct option_data *option;
+
+ char *data = "\007example\003org\0\006foobar\xc0";
+
+ option = &p.options[DHO_DOMAIN_SEARCH];
+ option->len = 21;
+ option->data = malloc(option->len);
+ memcpy(option->data, data, option->len);
+
+ ret = setjmp(env);
+ if (ret == 0)
+ expand_domain_search(&p);
+
+ if (ret != 1)
+ abort();
+
+ free(option->data);
+}
+
+void
+multiple_domains_valid()
+{
+ int ret;
+ struct packet p;
+ struct option_data *option;
+
+ char *data =
+ "\007example\003org\0\002cl\006foobar\003com\0\002fr\xc0\x10";
+
+ char *expected = "example.org. cl.foobar.com. fr.foobar.com.";
+
+ option = &p.options[DHO_DOMAIN_SEARCH];
+ option->len = 33;
+ option->data = malloc(option->len);
+ memcpy(option->data, data, option->len);
+
+ ret = setjmp(env);
+ if (ret == 0)
+ expand_domain_search(&p);
+
+ if (option->len != strlen(expected) ||
+ strcmp(option->data, expected) != 0)
+ abort();
+
+ free(option->data);
+}
+
+static
+void
+parse_date_helper(const char *string, time_t timestamp)
+{
+ int ret = 0;
+ FILE *file = NULL;
+ time_t ts;
+
+ file = fopen("/tmp/dhclient.test", "w");
+ if (!file)
+ abort();
+
+ ret = fwrite(string, strlen(string), 1, file);
+ if (ret <= 0)
+ abort();
+
+ fclose(file);
+
+ file = fopen("/tmp/dhclient.test", "r");
+ if (!file)
+ abort();
+
+ new_parse("test");
+ ts = parse_date(file);
+ if (ts != timestamp)
+ abort();
+
+ fclose(file);
+}
+
+void
+parse_date_valid(void)
+{
+ int ret;
+
+ ret = setjmp(env);
+ if (ret != 0)
+ abort();
+
+ parse_date_helper(" 2 2024/7/2 20:25:50;\n", 1719951950);
+#ifndef __i386__
+ parse_date_helper(" 1 2091/7/2 20:25:50;\n", 3834246350);
+#endif
+}
+
+int
+main(int argc, char *argv[])
+{
+
+ no_option_present();
+
+ one_domain_valid();
+ one_domain_truncated1();
+ one_domain_truncated2();
+
+ two_domains_valid();
+ two_domains_truncated1();
+ two_domains_truncated2();
+
+ two_domains_compressed();
+ two_domains_infloop();
+ two_domains_forwardptr();
+ two_domains_truncatedptr();
+
+ multiple_domains_valid();
+
+ parse_date_valid();
+
+ return (0);
+}
diff --git a/sbin/dhclient/tests/pcp.sh b/sbin/dhclient/tests/pcp.sh
new file mode 100644
index 000000000000..4875a10a10ce
--- /dev/null
+++ b/sbin/dhclient/tests/pcp.sh
@@ -0,0 +1,196 @@
+#!/usr/bin/env atf-sh
+#
+# SPDX-License-Identifier: BSD-2-Clause
+#
+# Copyright 2022 John-Mark Gurney
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#
+# Run the tests:
+# make WITH_TESTS=yes -j 4 all install && kyua test -k /usr/tests/Kyuafile sbin/dhclient/pcp
+#
+# Output last run:
+# kyua report --verbose -r $(ls -tr ~/.kyua/store/results.*.db | tail -n 1)
+
+. $(atf_get_srcdir)/../../sys/common/vnet.subr
+
+generic_dhcp_cleanup()
+{
+
+ # clean up programs
+ kill $(cat dhclient.test.pid) $(cat dhcpd.pid)
+
+ # clean up files
+ rm -f dhclient.dhcpd.conf lease.dhclient.test dhclient.test.pid
+
+ vnet_cleanup
+}
+
+atf_test_case normal cleanup
+normal_head()
+{
+ atf_set descr 'test dhclient against a server'
+ atf_set require.user root
+}
+
+normal_body()
+{
+ dhcpd=$(which dhcpd)
+
+ if ! [ -x "$dhcpd" ]; then
+ atf_skip "ISC dhcp server (isc-dhcp44-server) not installed"
+ fi
+
+ vnet_init
+
+ epair=$(vnet_mkepair)
+
+ vnet_mkjail dhclient_normal_test ${epair}b
+
+ # Set IP on server iface
+ ifconfig ${epair}a 192.0.2.2/24 up
+
+ # Create dhcp server config
+ cat > dhclient.dhcpd.conf << EOF
+default-lease-time 36000;
+max-lease-time 86400;
+authoritative;
+subnet 192.0.2.0 netmask 255.255.255.0 {
+ range 192.0.2.10 192.0.2.10;
+ option routers 192.0.2.2;
+ option domain-name-servers 192.0.2.2;
+}
+EOF
+
+ # Start dhcp server
+ touch dhcpd.leases.conf
+ atf_check -e ignore ${dhcpd} -cf ./dhclient.dhcpd.conf -lf ./dhcpd.leases.conf -pf ./dhcpd.pid ${epair}a
+
+ # Expect that we get an IP assigned
+ atf_check -e match:'DHCPACK from 192.0.2.2' jexec dhclient_normal_test dhclient -c /dev/null -l ./lease.dhclient.test -p ./dhclient.test.pid ${epair}b
+
+ # And it's the correct one
+ atf_check -o match:'inet 192.0.2.10' jexec dhclient_normal_test ifconfig ${epair}b
+
+}
+
+normal_cleanup()
+{
+
+ generic_dhcp_cleanup
+}
+
+atf_test_case pcp cleanup
+pcp_head()
+{
+ atf_set descr 'test dhclient on pcp interface'
+ atf_set require.user root
+}
+
+pcp_body()
+{
+ dhcpd=$(which dhcpd)
+
+ if ! [ -x "$dhcpd" ]; then
+ atf_skip "ISC dhcp server (isc-dhcp44-server) not installed"
+ fi
+
+ vnet_init
+
+ epair=$(vnet_mkepair)
+
+ # Server side needs to be up to pass packets
+ ifconfig ${epair}a up
+
+ # Make sure necessary netgraph modules are loaded
+ kldstat -q -n ng_ether || kldload ng_ether
+ kldstat -q -n ng_iface || kldload ng_iface
+ kldstat -q -n ng_vlan || kldload ng_vlan
+
+ # create vlan, and attach epair to it (has incoming/outgoing vlan
+ # 0 tagged frames)
+ ngctl mkpeer ${epair}a: vlan lower downstream
+
+ # create new interface on other side of vlan (untagged/pcp)
+ ngctl mkpeer ${epair}a:lower. eiface vlan0 ether
+
+ # get the interface created
+ ngiface=$(ngctl show ${epair}a:lower.vlan0 | head -n 1 | awk '{ print $2}')
+
+ # schedule it for clean up
+ echo ${ngiface} >> ngctl.shutdown
+
+ # set the filter on it
+ ngctl msg ${epair}a:lower. 'addfilter { vlan=0 hook="vlan0" }'
+
+ vnet_mkjail dhclient_pcp_test ${epair}b
+
+ # Set IP on server iface
+ ifconfig ${ngiface} up 192.0.2.2/24
+
+ # Set pcp in jail
+ jexec dhclient_pcp_test ifconfig ${epair}b pcp 0 up
+
+ # Create dhcp server config
+ cat > dhclient.dhcpd.conf << EOF
+default-lease-time 36000;
+max-lease-time 86400;
+authoritative;
+subnet 192.0.2.0 netmask 255.255.255.0 {
+ range 192.0.2.10 192.0.2.10;
+ option routers 192.0.2.2;
+ option domain-name-servers 192.0.2.2;
+}
+EOF
+
+ # Start dhcp server
+ touch dhcpd.leases.conf
+ atf_check -e ignore ${dhcpd} -cf ./dhclient.dhcpd.conf -lf ./dhcpd.leases.conf -pf ./dhcpd.pid ${ngiface}
+
+ # Expect that we get an IP assigned
+ atf_check -e match:'DHCPACK from 192.0.2.2' jexec dhclient_pcp_test dhclient -c /dev/null -l ./lease.dhclient.test -p ./dhclient.test.pid ${epair}b
+
+ # And it's the correct one
+ atf_check -o match:'inet 192.0.2.10' jexec dhclient_pcp_test ifconfig ${epair}b
+}
+
+pcp_cleanup()
+{
+
+ generic_dhcp_cleanup
+
+ # Clean up netgraph nodes
+ for i in $(cat ngctl.shutdown); do
+ ngctl shutdown ${i}:
+ done
+ rm -f ngctl.shutdown
+}
+
+atf_init_test_cases()
+{
+ atf_add_test_case normal
+ atf_add_test_case pcp
+}
+