2020-04-09 08:35:41 +02:00

505 lines
12 KiB
C

/**
* @file
*
* @brief Packet filter rules are tested (see defines below).
*/
/*
* Copyright (c) 2016 embedded brains GmbH. All rights reserved.
*
* embedded brains GmbH
* Dornierstr. 4
* 82178 Puchheim
* Germany
* <rtems@embedded-brains.de>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. 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 AUTHOR 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 AUTHOR 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.
*/
#include <assert.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <netdb.h>
#include <unistd.h>
#include <errno.h>
#include <machine/rtems-bsd-commands.h>
#include <rtems/libcsupport.h>
#include <rtems/shell.h>
#include <rtems/telnetd.h>
#include <rtems/ftpd.h>
#define TEST_NAME "LIBBSD PF 1"
#define ARGC(x) RTEMS_BSD_ARGC(x)
#define LO1_IP "172.21.1.1"
#define LO2_IP "172.22.2.2"
#define TELNET_PORT 23
#define FTP_PORT 21
#define SERVER_TASK_PRIO 110
/*
* WARNING: The following rules are not made to be an good example for using PF.
* Check the online manuals for PF to find out how to write good rules.
*/
/* Config with invalid syntax */
#define TEST_CFG_INVALID "/etc/pf_invalid.conf"
#define TEST_CFG_INVALID_CONTENT \
"some\n" \
"garbage\n"
/* Allow only some services on output. */
#define TEST_CFG_BLOCK_MOST "/etc/pf_block_most.conf"
#define TEST_CFG_BLOCK_MOST_CONTENT \
"tcp_services = \"{ ssh, telnet, www }\"\n" \
"udp_services = \"{ telnet }\"\n" \
"block all\n" \
"pass out proto tcp to any port $tcp_services keep state\n" \
"pass proto udp to any port $udp_services keep state\n"
/* Allow all on one and only some on another interface. */
#define TEST_CFG_IF_DEPEND "/etc/pf_if_depend.conf"
#define TEST_CFG_IF_DEPEND_CONTENT \
"ext_if = \"cgem0\"\n" \
"int_if = \"lo0\"\n" \
"block in all\n" \
"pass in on $int_if all\n" \
"pass in on $ext_if inet proto { tcp, udp } from any port { telnet }\n"
/* Block all input except on lo1. */
#define TEST_CFG_ALLOW_LO1 "/etc/pf_allow_lo1.conf"
#define TEST_CFG_ALLOW_LO1_CONTENT \
"block all\n" \
"pass in on lo1 all keep state\n" \
"pass out all\n"
/* Block all input except telnet on lo1. */
#define TEST_CFG_TELNET_LO1 "/etc/pf_allow_telnet_lo1.conf"
#define TEST_CFG_TELNET_LO1_CONTENT \
"block all\n" \
"pass in on lo1 inet proto { tcp } from any to any port { telnet } keep state\n" \
"pass out all\n"
/* pf.os */
#define ETC_PF_OS "/etc/pf.os"
#define ETC_PF_OS_CONTENT "# empty"
/* protocols */
#define ETC_PROTOCOLS "/etc/protocols"
#define ETC_PROTOCOLS_CONTENT \
"ip 0 IP # internet protocol, pseudo protocol number\n" \
"tcp 6 TCP # transmission control protocol\n" \
"udp 17 UDP # user datagram protocol\n"
/* services */
#define ETC_SERVICES "/etc/services"
#define ETC_SERVICES_CONTENT \
"ftp-data 20/sctp #File Transfer [Default Data]\n" \
"ftp-data 20/tcp #File Transfer [Default Data]\n" \
"ftp-data 20/udp #File Transfer [Default Data]\n" \
"ftp 21/sctp #File Transfer [Control]\n" \
"ftp 21/tcp #File Transfer [Control]\n" \
"ftp 21/udp #File Transfer [Control]\n" \
"ssh 22/tcp #Secure Shell Login\n" \
"telnet 23/tcp\n" \
"telnet 23/udp\n" \
"http 80/tcp www www-http #World Wide Web HTTP\n"
static const struct {
const char *name;
const char *content;
} init_files[] = {
{.name = ETC_PF_OS, .content = ETC_PF_OS_CONTENT},
{.name = ETC_PROTOCOLS, .content = ETC_PROTOCOLS_CONTENT},
{.name = ETC_SERVICES, .content = ETC_SERVICES_CONTENT},
{.name = TEST_CFG_INVALID, .content = TEST_CFG_INVALID_CONTENT},
{.name = TEST_CFG_BLOCK_MOST, .content = TEST_CFG_BLOCK_MOST_CONTENT},
{.name = TEST_CFG_IF_DEPEND, .content = TEST_CFG_IF_DEPEND_CONTENT},
{.name = TEST_CFG_ALLOW_LO1, .content = TEST_CFG_ALLOW_LO1_CONTENT},
{.name = TEST_CFG_TELNET_LO1, .content = TEST_CFG_TELNET_LO1_CONTENT},
};
/* Create all necessary files */
static void
prepare_files()
{
size_t i;
struct stat sb;
int rv;
int fd;
size_t written;
/* Create /etc if necessary */
rv = mkdir("/etc", S_IRWXU | S_IRWXG | S_IRWXO);
/* ignore errors, check the dir after. */
assert(stat("/etc", &sb) == 0);
assert(S_ISDIR(sb.st_mode));
/* Create files */
for(i = 0; i < (sizeof(init_files)/sizeof(init_files[0])); ++i) {
const char *content;
size_t len;
content = init_files[i].content;
len = strlen(content);
fd = open(init_files[i].name, O_WRONLY | O_CREAT,
S_IRWXU | S_IRWXG | S_IRWXO);
assert(fd != -1);
written = write(fd, content, len);
assert(written == len);
rv = close(fd);
assert(rv == 0);
}
}
/* Generic server task */
static rtems_task
tcp_server_task(rtems_task_argument arg)
{
int sd, client;
socklen_t addrlen;
struct sockaddr_in addr, client_addr;
uint16_t port = arg;
int rv;
const char answer[] = "I am a dummy\n";
sd = socket(AF_INET, SOCK_STREAM, 0);
assert(sd > 0);
memset(&addr, 0, sizeof addr);
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(port);
rv = bind(sd, (struct sockaddr *)&addr, sizeof(addr));
assert(rv == 0);
rv = listen(sd, 10);
assert(rv == 0);
for(;;) {
addrlen = sizeof(client_addr);
client = accept(sd, (struct sockaddr *)&client_addr, &addrlen);
if(client > 0) {
write(client, answer, strlen(answer));
close(client);
}
}
}
static void
spawn_server(uint16_t port)
{
rtems_status_code sc;
rtems_id tid;
sc = rtems_task_create(rtems_build_name('s','r','v',' '),
SERVER_TASK_PRIO,
RTEMS_MINIMUM_STACK_SIZE,
RTEMS_DEFAULT_MODES,
RTEMS_DEFAULT_ATTRIBUTES,
&tid);
assert(sc == RTEMS_SUCCESSFUL);
sc = rtems_task_start(tid, tcp_server_task, port);
assert(sc == RTEMS_SUCCESSFUL);
}
/* Start a few services to test */
static void
prepare_services()
{
spawn_server(TELNET_PORT);
spawn_server(FTP_PORT);
}
/* Create some additional loopback interfaces for the test. */
static void
prepare_interfaces()
{
int exit_code;
char *lo1[] = {
"ifconfig", "lo1", "create", "inet", LO1_IP,
"netmask", "255.255.0.0", NULL
};
char *lo2[] = {
"ifconfig", "lo2", "create", "inet", LO2_IP,
"netmask", "255.255.0.0", NULL
};
puts("--- create lo1 and lo2");
exit_code = rtems_bsd_command_ifconfig(ARGC(lo1), lo1);
assert(exit_code == EXIT_SUCCESS);
exit_code = rtems_bsd_command_ifconfig(ARGC(lo2), lo2);
assert(exit_code == EXIT_SUCCESS);
}
static void
check_tcp_port(char *host, bool expect_open, uint16_t port)
{
int sd;
struct sockaddr_in addr;
struct hostent *server;
int rv;
struct timeval tv;
fd_set select_set;
sd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
assert(sd > 0);
/* make socket nonblocking */
rv = fcntl(sd, F_GETFL, NULL);
assert(rv >= 0);
rv |= O_NONBLOCK;
rv = fcntl(sd, F_SETFL, rv);
assert(rv == 0);
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
server = gethostbyname(host);
assert(server != NULL);
memcpy(&addr.sin_addr.s_addr, server->h_addr, server->h_length);
rv = connect(sd, (struct sockaddr *)&addr, sizeof(addr));
if(rv == 0) {
/* successfully connected */
assert(expect_open == true);
} else {
if(errno != EINPROGRESS) {
assert(expect_open == false);
} else {
/* wait for connection or timeout */
memset(&tv, 0, sizeof(tv));
tv.tv_usec = 100000;
FD_ZERO(&select_set);
FD_SET(sd, &select_set);
rv = select(sd+1, NULL, &select_set, NULL, &tv);
/* rv < 0 would be an error
* rv == 0 would be an timeout
* rv > 0 would be success */
if(expect_open == false) {
assert(rv == 0);
} else {
assert(rv > 0);
}
}
}
rv = close(sd);
assert(rv == 0);
}
static void
check_telnet(char *host, bool expect_open)
{
check_tcp_port(host, expect_open, TELNET_PORT);
}
static void
check_ftp(char *host, bool expect_open)
{
check_tcp_port(host, expect_open, FTP_PORT);
}
/* Check if services work like expected */
static void
check_services(char *host, bool exp_telnet, bool exp_ftp)
{
check_telnet(host, exp_telnet);
check_ftp(host, exp_ftp);
}
static void
disable_pf(bool ignore_not_enabled)
{
int exit_code;
char *pfctl[] = {"pfctl", "-d", "-q", NULL};
exit_code = rtems_bsd_command_pfctl(ARGC(pfctl), pfctl);
assert(ignore_not_enabled || (exit_code == EXIT_SUCCESS));
}
static void
enable_pf()
{
int exit_code;
char *pfctl[] = {"pfctl", "-e", "-q", NULL};
exit_code = rtems_bsd_command_pfctl(ARGC(pfctl), pfctl);
assert(exit_code == EXIT_SUCCESS);
}
/* Execute pfctl two times with the given arguments. Check the resources. */
static void
run_pfctl(int argc, char *argv[], int expected_result)
{
int exit_code;
rtems_resource_snapshot snapshot, snapshot2;
exit_code = rtems_bsd_command_pfctl(argc, argv);
assert(exit_code == expected_result);
rtems_resource_snapshot_take(&snapshot);
exit_code = rtems_bsd_command_pfctl(argc, argv);
assert(exit_code == expected_result);
rtems_resource_snapshot_take(&snapshot2);
/* heap fragmentation might change so remove it from compare */
snapshot.workspace_info.Free.largest = 0;
snapshot2.workspace_info.Free.largest = 0;
snapshot.heap_info.Free.largest = 0;
snapshot2.heap_info.Free.largest = 0;
assert(exit_code == EXIT_SUCCESS ||
rtems_resource_snapshot_equal(&snapshot, &snapshot2));
}
static void
test_pfctl(void)
{
char *no_params[] = {
"pfctl",
NULL
};
char *invalid[] = {
"pfctl",
"-f",
TEST_CFG_INVALID,
"-q",
NULL
};
char *block_most[] = {
"pfctl",
"-f",
TEST_CFG_BLOCK_MOST,
"-q",
NULL
};
char *if_depend[] = {
"pfctl",
"-f",
TEST_CFG_IF_DEPEND,
"-q",
NULL
};
puts("--- run pfctl with no paramters");
run_pfctl(ARGC(no_params), no_params, EXIT_FAILURE);
puts("--- load invalid rule");
run_pfctl(ARGC(invalid), invalid, EXIT_FAILURE);
puts("--- load rule to block input and most output");
run_pfctl(ARGC(block_most), block_most, EXIT_SUCCESS);
puts("--- load rule to block if dependent");
run_pfctl(ARGC(if_depend), if_depend, EXIT_SUCCESS);
}
static void
test_allow_lo1()
{
char *pfctl[] = {
"pfctl",
"-f",
TEST_CFG_ALLOW_LO1,
"-q",
NULL
};
disable_pf(true);
puts("--- check services");
check_services(LO1_IP, true, true);
check_services(LO2_IP, true, true);
puts("--- load and enable rule to allow only lo1");
run_pfctl(ARGC(pfctl), pfctl, EXIT_SUCCESS);
enable_pf();
puts("--- check services");
check_services(LO1_IP, true, true);
check_services(LO2_IP, false, false);
}
static void
test_telnet_lo1()
{
char *pfctl[] = {
"pfctl",
"-f",
TEST_CFG_TELNET_LO1,
"-q",
NULL
};
disable_pf(true);
puts("--- check services");
check_services(LO1_IP, true, true);
check_services(LO2_IP, true, true);
puts("--- load and enable rule to allow telnet on lo1");
run_pfctl(ARGC(pfctl), pfctl, EXIT_SUCCESS);
enable_pf();
puts("--- check services");
check_services(LO1_IP, true, false);
check_services(LO2_IP, false, false);
}
static void
test_main(void)
{
prepare_files();
prepare_interfaces();
prepare_services();
test_pfctl();
test_allow_lo1();
test_telnet_lo1();
exit(0);
}
#include <machine/rtems-bsd-sysinit.h>
#define RTEMS_BSD_CONFIG_FIREWALL_PF
#include <rtems/bsd/test/default-network-init.h>