From 2d1da0d1c3d1b76fef672227047e29b8bc818e0b Mon Sep 17 00:00:00 2001 From: Christian Mauderer Date: Fri, 22 Jul 2016 14:50:09 +0200 Subject: testsuite/pf01: Test pfctl and pf. --- testsuite/pf01/test_main.c | 450 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 450 insertions(+) diff --git a/testsuite/pf01/test_main.c b/testsuite/pf01/test_main.c index f84dca6d..488884b2 100644 --- a/testsuite/pf01/test_main.c +++ b/testsuite/pf01/test_main.c @@ -29,13 +29,463 @@ * SUCH DAMAGE. */ +#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include #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(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); } -- cgit v1.2.3