[rtems-libbsd commit] testsuite/pf01: Test pfctl and pf.

Sebastian Huber sebh at rtems.org
Wed Aug 3 12:44:14 UTC 2016


Module:    rtems-libbsd
Branch:    4.11
Commit:    2d1da0d1c3d1b76fef672227047e29b8bc818e0b
Changeset: http://git.rtems.org/rtems-libbsd/commit/?id=2d1da0d1c3d1b76fef672227047e29b8bc818e0b

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