/** * @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 * * * 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 #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(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 #define RTEMS_BSD_CONFIG_FIREWALL_PF #include