mirror of
https://git.rtems.org/rtems-libbsd/
synced 2025-05-13 04:54:44 +08:00
505 lines
12 KiB
C
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>
|