[rtems-libbsd commit] testsuite/pf01: Test pfctl and pf.
Sebastian Huber
sebh at rtems.org
Tue Aug 2 11:21:59 UTC 2016
Module: rtems-libbsd
Branch: master
Commit: df222eba728d169b808e87e16090edd8363ce80c
Changeset: http://git.rtems.org/rtems-libbsd/commit/?id=df222eba728d169b808e87e16090edd8363ce80c
Author: Christian Mauderer <Christian.Mauderer at embedded-brains.de>
Date: Fri Jul 22 14:50:09 2016 +0200
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 f84dca6..488884b 100644
--- a/testsuite/pf01/test_main.c
+++ b/testsuite/pf01/test_main.c
@@ -29,13 +29,463 @@
* SUCH DAMAGE.
*/
+#include <assert.h>
+#include <fcntl.h>
+#include <stdio.h>
#include <stdlib.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(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);
}
More information about the vc
mailing list