#include <sys/types.h>
#include <sys/param.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <spawn.h>
#include <sys/module.h>
#include <sys/sbuf.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <atf-c.h>
extern char **environ;
static struct sbuf *
read_fd(int fd, size_t sizehint)
{
struct sbuf *sb;
ssize_t count;
char buffer[MAXBSIZE];
sb = sbuf_new(NULL, NULL, sizehint, SBUF_AUTOEXTEND);
errno = 0;
while ((count = read(fd, buffer, sizeof(buffer) - 1)) > 0) {
sbuf_bcat(sb, buffer, count);
}
ATF_REQUIRE_ERRNO(0, count == 0 && "Should have reached EOF");
sbuf_finish(sb);
return (sb);
}
static struct sbuf *
read_file(const char *filename)
{
struct stat s;
struct sbuf *result;
int fd;
errno = 0;
ATF_REQUIRE_EQ_MSG(stat(filename, &s), 0, "cannot stat %s", filename);
fd = open(filename, O_RDONLY);
ATF_REQUIRE_ERRNO(0, fd > 0);
result = read_fd(fd, s.st_size);
ATF_REQUIRE_ERRNO(0, close(fd) == 0);
return (result);
}
static void
run_command_pipe(const char *argv[], struct sbuf **output)
{
posix_spawn_file_actions_t action;
pid_t pid;
int pipefds[2];
int status;
ATF_REQUIRE_ERRNO(0, pipe(pipefds) == 0);
posix_spawn_file_actions_init(&action);
posix_spawn_file_actions_addclose(&action, STDIN_FILENO);
posix_spawn_file_actions_addclose(&action, pipefds[1]);
posix_spawn_file_actions_adddup2(&action, pipefds[0], STDOUT_FILENO);
posix_spawn_file_actions_adddup2(&action, pipefds[0], STDERR_FILENO);
printf("Running ");
for (int i=0; argv[i] != NULL; i++)
printf("%s ", argv[i]);
printf("\n");
status = posix_spawnp(
&pid, argv[0], &action, NULL, __DECONST(char **, argv), environ);
ATF_REQUIRE_EQ_MSG(
status, 0, "posix_spawn failed: %s", strerror(errno));
posix_spawn_file_actions_destroy(&action);
close(pipefds[0]);
(*output) = read_fd(pipefds[1], 0);
printf("---\n%s---\n", sbuf_data(*output));
ATF_REQUIRE_EQ(waitpid(pid, &status, 0), pid);
ATF_REQUIRE_MSG(WIFEXITED(status),
"%s returned non-zero! Output:\n %s", argv[0], sbuf_data(*output));
close(pipefds[1]);
}
static void
run_command(const char *argv[])
{
posix_spawn_file_actions_t action;
pid_t pid;
int status;
posix_spawn_file_actions_init(&action);
posix_spawn_file_actions_addopen(&action, STDOUT_FILENO, "/dev/null", O_WRONLY, 0);
posix_spawn_file_actions_addopen(&action, STDERR_FILENO, "/dev/null", O_WRONLY, 0);
posix_spawn_file_actions_addopen(&action, STDIN_FILENO, "/dev/zero", O_RDONLY, 0);
printf("Running ");
for (int i=0; argv[i] != NULL; i++)
printf("%s ", argv[i]);
printf("\n");
status = posix_spawnp(
&pid, argv[0], &action, NULL, __DECONST(char **, argv), environ);
posix_spawn_file_actions_destroy(&action);
waitpid(pid, &status, 0);
}
static void
run_pfctl_test(const char *input_path, const char *output_path,
const atf_tc_t *tc, bool test_failure)
{
char input_files_path[PATH_MAX];
struct sbuf *expected_output;
struct sbuf *real_output;
snprintf(input_files_path, sizeof(input_files_path), "%s/files",
atf_tc_get_config_var(tc, "srcdir"));
ATF_REQUIRE_ERRNO(0, chdir(input_files_path) == 0);
expected_output = read_file(output_path);
const char *argv[] = { "pfctl", "-o", "none", "-nvf", input_path,
NULL };
run_command_pipe(argv, &real_output);
if (test_failure) {
ATF_CHECK_MATCH(sbuf_data(expected_output), sbuf_data(real_output));
sbuf_delete(expected_output);
} else {
ATF_CHECK_STREQ(sbuf_data(expected_output), sbuf_data(real_output));
sbuf_delete(expected_output);
}
sbuf_delete(real_output);
}
static void
do_pf_test_iface_create(const char *number)
{
struct sbuf *ifconfig_output;
char ifname[16] = {0};
snprintf(ifname, sizeof(ifname), "vlan%s", number);
const char *argv[] = { "ifconfig", ifname, "create", NULL};
run_command_pipe(argv, &ifconfig_output);
sbuf_delete(ifconfig_output);
const char *argv_inet[] = { "ifconfig", ifname, "inet", "203.0.113.5/30", NULL};
run_command_pipe(argv_inet, &ifconfig_output);
sbuf_delete(ifconfig_output);
const char *argv_inet6[] = { "ifconfig", ifname, "inet6", "2001:db8::203.0.113.5/126", NULL};
run_command_pipe(argv_inet6, &ifconfig_output);
sbuf_delete(ifconfig_output);
const char *argv_show[] = { "ifconfig", ifname, NULL};
run_command_pipe(argv_show, &ifconfig_output);
sbuf_delete(ifconfig_output);
}
static void
do_pf_test_iface_remove(const char *number)
{
char ifname[16] = {0};
snprintf(ifname, sizeof(ifname), "vlan%s", number);
const char *argv[] = { "ifconfig", ifname, "destroy", NULL};
run_command(argv);
}
static void
do_pf_test(const char *number, const atf_tc_t *tc)
{
char *input_path;
char *expected_path;
asprintf(&input_path, "%s/files/pf%s.in",
atf_tc_get_config_var(tc, "srcdir"), number);
asprintf(&expected_path, "%s/files/pf%s.ok",
atf_tc_get_config_var(tc, "srcdir"), number);
run_pfctl_test(input_path, expected_path, tc, false);
free(input_path);
free(expected_path);
}
static void
do_pf_test_fail(const char *number, const atf_tc_t *tc)
{
char *input_path;
char *expected_path;
asprintf(&input_path, "%s/files/pf%s.in",
atf_tc_get_config_var(tc, "srcdir"), number);
asprintf(&expected_path, "%s/files/pf%s.fail",
atf_tc_get_config_var(tc, "srcdir"), number);
run_pfctl_test(input_path, expected_path, tc, true);
free(input_path);
free(expected_path);
}
static void
do_selfpf_test(const char *number, const atf_tc_t *tc)
{
char *expected_path;
asprintf(&expected_path, "%s/files/pf%s.ok",
atf_tc_get_config_var(tc, "srcdir"), number);
run_pfctl_test(expected_path, expected_path, tc, false);
free(expected_path);
}
#define PFCTL_TEST(number, descr) \
ATF_TC(pf##number); \
ATF_TC_HEAD(pf##number, tc) \
{ \
atf_tc_set_md_var(tc, "descr", descr); \
atf_tc_set_md_var(tc, "require.kmods", "pf"); \
} \
ATF_TC_BODY(pf##number, tc) \
{ \
do_pf_test(#number, tc); \
} \
ATF_TC(selfpf##number); \
ATF_TC_HEAD(selfpf##number, tc) \
{ \
atf_tc_set_md_var(tc, "descr", "Self " descr); \
atf_tc_set_md_var(tc, "require.kmods", "pf"); \
} \
ATF_TC_BODY(selfpf##number, tc) \
{ \
do_selfpf_test(#number, tc); \
}
#define PFCTL_TEST_FAIL(number, descr) \
ATF_TC(pf##number); \
ATF_TC_HEAD(pf##number, tc) \
{ \
atf_tc_set_md_var(tc, "descr", descr); \
atf_tc_set_md_var(tc, "require.kmods", "pf"); \
} \
ATF_TC_BODY(pf##number, tc) \
{ \
do_pf_test_fail(#number, tc); \
}
#define PFCTL_TEST_IFACE(number, descr) \
ATF_TC_WITH_CLEANUP(pf##number); \
ATF_TC_HEAD(pf##number, tc) \
{ \
atf_tc_set_md_var(tc, "descr", descr); \
atf_tc_set_md_var(tc, "execenv", "jail"); \
atf_tc_set_md_var(tc, "execenv.jail.params", "vnet"); \
atf_tc_set_md_var(tc, "require.kmods", "pf"); \
} \
ATF_TC_BODY(pf##number, tc) \
{ \
do_pf_test_iface_create(#number); \
do_pf_test(#number, tc); \
} \
ATF_TC_CLEANUP(pf##number, tc) \
{ \
do_pf_test_iface_remove(#number); \
}
#include "pfctl_test_list.inc"
#undef PFCTL_TEST
#undef PFCTL_TEST_FAIL
#undef PFCTL_TEST_IFACE
ATF_TP_ADD_TCS(tp)
{
#define PFCTL_TEST(number, descr) \
ATF_TP_ADD_TC(tp, pf##number); \
ATF_TP_ADD_TC(tp, selfpf##number);
#define PFCTL_TEST_FAIL(number, descr) \
ATF_TP_ADD_TC(tp, pf##number);
#define PFCTL_TEST_IFACE(number, descr) \
ATF_TP_ADD_TC(tp, pf##number);
#include "pfctl_test_list.inc"
#undef PFCTL_TEST
#undef PFCTL_TEST_FAIL
#undef PFCTL_TEST_IFACE
return atf_no_error();
}