diff --git a/Dockerfile b/Dockerfile
index 7c000ba26f0422dd6dbcd7b702f819b9e9743850..6d0dddb7549efcd07fcbec70c3f2094b986a1e24 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -4,8 +4,6 @@ RUN echo "Hello from Docker"
 RUN mkdir -p /linux/tools/labs/skels/assignments/3-raid
 RUN mkdir -p /linux/tools/labs/skels/assignments/3-raid-checker
 
-COPY ./checker/3-raid-checker /linux/tools/labs/skels/assignments/3-raid-checker
-
 COPY ./checker/checker_daemons/so2_vm_checker_daemon.sh /linux/tools/labs/rootfs/etc/init.d
 RUN chmod +x /linux/tools/labs/rootfs/etc/init.d/so2_vm_checker_daemon.sh
 RUN chroot /linux/tools/labs/rootfs update-rc.d so2_vm_checker_daemon.sh defaults
diff --git a/checker/4-stp-checker/Makefile b/checker/4-stp-checker/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..e0c2b42807f22f2a94bf684002d7fa6a86aa8891
--- /dev/null
+++ b/checker/4-stp-checker/Makefile
@@ -0,0 +1,17 @@
+objects = _test/stp_test.o
+
+.PHONY: all clean  _test_subdir_all _test_subdir_clean
+
+all: stp_test
+
+stp_test: _test_subdir_all $(objects)
+	$(CC) -Wall -g -m32 -static $(objects) -Wl,--whole-archive -lpthread -Wl,--no-whole-archive -o $@
+
+_test_subdir_all:
+	make -C _test
+
+clean: _test_subdir_clean
+	-rm -f stp_test *~
+
+_test_subdir_clean:
+	make -C _test clean
diff --git a/checker/4-stp-checker/README b/checker/4-stp-checker/README
new file mode 100644
index 0000000000000000000000000000000000000000..a1d04d84f8653e98d3812f0a9ecbfa26d64c5956
--- /dev/null
+++ b/checker/4-stp-checker/README
@@ -0,0 +1,87 @@
+= STP TEST SUITE ==
+
+Test suite for SO2 Tranport Protocol
+
+== FILES ==
+
+README
+	* this file
+
+Makefile
+
+_checker
+	* script to run all tests defined in _test/stp_test.c
+
+_test/Makefile
+	* test suite internal Makefile (creates necessary object files)
+
+_test/stp_test.c
+	* test suite for SO2 Transport Protocol
+
+_test/stp_test.h
+	* test suite header file
+
+_test/stp.h
+	* SO2 Transport Protocol header file (macros and structures)
+
+_test/test.h
+	* useful macros for testing
+
+_test/debug.h
+	* debugging macros
+
+_test/util.h
+	* useful macros for generic use (error processing)
+
+== BUILDING ==
+
+
+== RUNNING ==
+
+Copy your af_stp.ko module and _checker and stp_test
+to fsimg/root directory on your QEMU/KVM virtual machine.
+
+In order to run the test suite you can either use the _checker
+script or run the stp_test executable.
+
+The _checker script runs all tests and computes assignment grade:
+
+	./_checker
+
+In order to run a specific test pass the test number (1 .. 32) to the
+stp_test executable.
+
+	./stp_test 5
+
+== TESTS ==
+
+Tests are basically unit tests. A single function in the test_fun_array (see
+stp_test.c) is called each time the stp_test executable is invoked,
+testing a single functionality (and assuming previous tests have been run and
+passed).
+
+The EXIT_IF_FAIL macro (see test.h) is unnecessary since after each test, the
+program completes.
+
+Each test function follows the unit test pattern: initialization, action,
+evaluation. The test macro (see test.h) is invoked at the end of each test
+for evaluating and grading the test.
+
+== DEBUGGING ==
+
+The debug.h header file consists of several macros useful for debugging
+(dprintf, dlog). There are multiple uses of these macros throughout the above
+files.
+
+In order to turn debug messages on, you must define the DEBUG macro, either in
+a header file, or, I suggest, in the Makefile. The LOG_LEVEL macro limits the
+log message types that are to be printed, by default LOG_WARNING (see enum in
+debug.h). You may redefine it in a header file or in the Makefile.
+
+Rapid enabling of debug messages is achieved by commenting out the CPPFLAGS
+line in the Makefile. It turns on debugging and enables all log messages
+(LOG_DEBUG).
+
+== OTHER ==
+
+srand48() and drand48() are used for generating random numbers.
diff --git a/checker/4-stp-checker/_checker b/checker/4-stp-checker/_checker
new file mode 100755
index 0000000000000000000000000000000000000000..810e77f5e98e5337f30ad6e54d707baacaca1f4f
--- /dev/null
+++ b/checker/4-stp-checker/_checker
@@ -0,0 +1,24 @@
+#!/bin/sh
+
+first_test=1
+last_test=32
+executable=stp_test
+
+for i in $(seq $first_test $last_test); do
+    ./"$executable" $i
+done | tee results.txt
+
+cat results.txt | grep '\[.*\]$' | awk -F '[] /[]+' '
+BEGIN {
+    sum=0
+}
+
+{
+    sum += $2;
+}
+
+END {
+    printf "\n%66s  [%d/100]\n", "Total:", sum;
+}'
+
+rm -f results.txt
diff --git a/checker/4-stp-checker/_test/Makefile b/checker/4-stp-checker/_test/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..d5074dd464a5f5fa80cf42324e336ebdb975f529
--- /dev/null
+++ b/checker/4-stp-checker/_test/Makefile
@@ -0,0 +1,11 @@
+#CPPFLAGS = -DDEBUG -DLOG_LEVEL=LOG_DEBUG
+CFLAGS = -Wall -g -m32
+
+.PHONY: all clean
+
+all: stp_test.o
+
+stp_test.o: stp_test.c stp_test.h stp.h test.h util.h debug.h
+
+clean:
+	-rm -f *~ *.o
diff --git a/checker/4-stp-checker/_test/debug.h b/checker/4-stp-checker/_test/debug.h
new file mode 100644
index 0000000000000000000000000000000000000000..a54e96226471814a6f8595bc0a3c59cce37c5210
--- /dev/null
+++ b/checker/4-stp-checker/_test/debug.h
@@ -0,0 +1,77 @@
+/*
+ * debugging macros
+ *    heavily inspired by previous work and Internet resources
+ *
+ * uses C99 variadic macros
+ * uses non-standard usage of the token-paste operator (##) for
+ *   removing the comma symbol (,) when not followed by a token
+ * uses non-standard __FUNCTION__ macro (MSVC doesn't support __func__)
+ * tested on gcc 4.4.5 and Visual Studio 2008 (9.0), compiler version 15.00
+ *
+ * Razvan Deaconescu, razvan.deaconescu@cs.pub.ro
+ */
+
+#ifndef DEBUG_H_
+#define DEBUG_H_	1
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdio.h>
+
+/* log levels */
+enum {
+	LOG_EMERG = 1,
+	LOG_ALERT,
+	LOG_CRIT,
+	LOG_ERR,
+	LOG_WARNING,
+	LOG_NOTICE,
+	LOG_INFO,
+	LOG_DEBUG
+};
+
+/*
+ * initialize default loglevel (for dlog)
+ * may be redefined in the including code
+ */
+
+#ifndef LOG_LEVEL
+#define LOG_LEVEL	LOG_WARNING
+#endif
+
+/*
+ * define DEBUG macro as a compiler option:
+ *    -DDEBUG for GCC
+ *    /DDEBUG for MSVC
+ */
+
+#if defined DEBUG
+#define dprintf(format, ...)					\
+	fprintf(stderr, " [%s(), %s:%u] " format,		\
+			__FUNCTION__, __FILE__, __LINE__,	\
+			##__VA_ARGS__)
+#else
+#define dprintf(format, ...)					\
+	do {							\
+	} while (0)
+#endif
+
+#if defined DEBUG
+#define dlog(level, format, ...)				\
+	do {							\
+		if (level <= LOG_LEVEL)				\
+			dprintf(format, ##__VA_ARGS__);		\
+	} while (0)
+#else
+#define dlog(level, format, ...)				\
+	do {							\
+	} while (0)
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/checker/4-stp-checker/_test/stp.h b/checker/4-stp-checker/_test/stp.h
new file mode 100644
index 0000000000000000000000000000000000000000..838f9936bf55c535d5e6ed640bb5e617effd1a26
--- /dev/null
+++ b/checker/4-stp-checker/_test/stp.h
@@ -0,0 +1,51 @@
+/*
+ * SO2 Transport Protocol
+ */
+
+#ifndef STP_H_
+#define STP_H_	1
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <linux/types.h>
+
+/* STP reuses the defines of ancient protocols like Econet and Xerox PUP
+ * because adding a new protocol would involve patching the kernel, which we
+ * don't want to do and besides that, they are probably not used anymore.
+ */
+#define AF_STP		19
+#define PF_STP		AF_STP
+#define ETH_P_STP	0x0a00
+
+struct stp_hdr {
+	__be16		dst;		/* Destination port */
+	__be16		src;		/* Source port */
+	__be16		len;		/* Total length, including header */
+	__u8		flags;		/* */
+	__u8		csum;		/* xor of all bytes, including header */
+};
+
+struct sockaddr_stp {
+	unsigned short	sas_family;	/* Always AF_STP */
+	int		sas_ifindex;	/* Interface index */
+	__be16		sas_port;	/* Port */
+	__u8		sas_addr[6];	/* MAC address */
+};
+
+/* STP protocol name; used as identifier in /proc/net/protocols */
+#define STP_PROTO_NAME			"STP"
+
+/*
+ * STP uses proc interface to communicate statistical information to
+ * user space (in /proc/net/).
+ */
+#define STP_PROC_NET_FILENAME		"stp_stats"
+#define STP_PROC_FULL_FILENAME		"/proc/net/" STP_PROC_NET_FILENAME
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* STP_H_ */
diff --git a/checker/4-stp-checker/_test/stp_test.c b/checker/4-stp-checker/_test/stp_test.c
new file mode 100644
index 0000000000000000000000000000000000000000..d6c729e344e634a0a90eced63918d7953cb8faf6
--- /dev/null
+++ b/checker/4-stp-checker/_test/stp_test.c
@@ -0,0 +1,1331 @@
+/*
+ * SO2 Transport Protocol - test suite
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <assert.h>
+#include <time.h>
+#include <signal.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/ether.h>
+#include <net/if.h>
+#include <assert.h>
+#include <sys/wait.h>
+#include <semaphore.h>
+#include <fcntl.h>
+
+#include "test.h"
+#include "debug.h"
+#include "util.h"
+
+#include "stp.h"
+#include "stp_test.h"
+
+#define SSA			struct sockaddr
+#define BUFLEN			32
+
+/* declared in test.h; used for printing information in test macro */
+int max_points = 100;
+
+/* values read from STP_PROC_FULL_FILENAME */
+static int rx_pkts, hdr_err, csum_err, no_sock, no_buffs, tx_pkts;
+
+enum socket_action {
+	ACTION_SENDTO,
+	ACTION_SENDMSG,
+	ACTION_SEND,
+	ACTION_SENDTO_PING_PONG,
+	ACTION_SENDMSG_PING_PONG,
+	ACTION_SEND_PING_PONG,
+};
+
+/*
+ * Do initialization for STP test functions.
+ */
+
+static void init_test(void)
+{
+	system("insmod " MODULE_FILENAME);
+}
+
+/*
+ * Do cleanup for STP test functions.
+ */
+
+static void cleanup_test(void)
+{
+	system("rmmod " MODULE_NAME);
+}
+
+/*
+ * Check for successful module insertion and removal from the kernel.
+ */
+
+static void test_insmod_rmmod(void)
+{
+	int rc;
+
+	rc = system("insmod " MODULE_FILENAME);
+	test("test_insmod", rc == 0, 1);
+
+	rc = system("rmmod " MODULE_NAME);
+	test("test_rmmod", rc == 0, 1);
+
+	rc = system("insmod " MODULE_FILENAME);
+	test(__FUNCTION__, rc == 0, 1);
+
+	system("rmmod " MODULE_NAME);
+}
+
+/*
+ * Check /proc/net/protocols for STP protocol. Grep for line starting with
+ * the string identified by STP_PROTO_NAME.
+ */
+
+static void test_proto_name_exists_after_insmod(void)
+{
+	int rc;
+
+	init_test();
+
+	rc = system("grep '^" STP_PROTO_NAME "' /proc/net/protocols > /dev/null 2>&1");
+	test(__FUNCTION__, rc == 0, 2);
+
+	cleanup_test();
+}
+
+/*
+ * STP entry in /proc/net/protocols is deleted when module is removed.
+ */
+
+static void test_proto_name_inexistent_after_rmmod(void)
+{
+	int rc;
+
+	init_test();
+	cleanup_test();
+
+	rc = system("grep '^" STP_PROTO_NAME "' /proc/net/protocols > /dev/null 2>&1");
+	test(__FUNCTION__, rc != 0, 2);
+}
+
+/*
+ * Check for proc entry for STP statistics.
+ */
+
+static void test_proc_entry_exists_after_insmod(void)
+{
+	int rc;
+
+	init_test();
+
+	rc = access(STP_PROC_FULL_FILENAME, F_OK);
+	test(__FUNCTION__, rc == 0, 2);
+
+	cleanup_test();
+}
+
+/*
+ * STP statistics file in /proc/net/ is deleted when module is removed.
+ */
+
+static void test_proc_entry_inexistent_after_rmmod(void)
+{
+	int rc;
+
+	init_test();
+	cleanup_test();
+
+	rc = system("file " STP_PROC_FULL_FILENAME " > /dev/null 2>&1");
+	test(__FUNCTION__, rc != 0, 2);
+}
+
+/*
+ * Call socket(2) with proper arguments for creating an AF_STP socket.
+ */
+
+static void test_socket(void)
+{
+	int s;
+
+	init_test();
+
+	s = socket(AF_STP, SOCK_DGRAM, 0);
+	test(__FUNCTION__, s > 0, 5);
+
+	close(s);
+	cleanup_test();
+}
+
+/*
+ * Create two AF_STP sockets using socket(2).
+ */
+
+static void test_two_sockets(void)
+{
+	int s1, s2;
+
+	init_test();
+
+	s1 = socket(AF_STP, SOCK_DGRAM, 0);
+	s2 = socket(AF_STP, SOCK_DGRAM, 0);
+	test(__FUNCTION__, s1 > 0 && s2 > 0 && s1 != s2, 2);
+
+	close(s1);
+	close(s2);
+	cleanup_test();
+}
+
+/*
+ * Pass bad socket type argument to socket(2) (second argument).
+ * Call should fail.
+ */
+
+static void test_socket_bad_socket_type(void)
+{
+	int s;
+
+	init_test();
+
+	s = socket(AF_STP, SOCK_STREAM, 0);
+	test(__FUNCTION__, s < 0, 1);
+
+	close(s);
+	cleanup_test();
+}
+
+/*
+ * Pass bad protocol argument to socket(2) (third argument).
+ * Call should fail.
+ */
+
+static void test_socket_bad_protocol(void)
+{
+	int s;
+
+	init_test();
+
+	s = socket(AF_STP, SOCK_DGRAM, IPPROTO_TCP);
+	test(__FUNCTION__, s < 0, 1);
+
+	close(s);
+	cleanup_test();
+}
+
+/*
+ * Close open socket using close(2).
+ */
+
+static void test_close(void)
+{
+	int s;
+	int rc;
+
+	init_test();
+
+	s = socket(AF_STP, SOCK_DGRAM, 0);
+
+	rc = close(s);
+	test(__FUNCTION__, rc == 0, 2);
+
+	cleanup_test();
+}
+
+/*
+ * Pass closed socket descriptor to close(2). Call should fail.
+ */
+
+static void test_close_closed_socket(void)
+{
+	int s;
+	int rc;
+
+	init_test();
+
+	s = socket(AF_STP, SOCK_DGRAM, 0);
+
+	close(s);
+	rc = close(s);
+
+	test(__FUNCTION__, rc < 0, 2);
+
+	cleanup_test();
+}
+
+/*
+ * Bind socket to proper address. Use "all" interface.
+ */
+
+static void test_bind(void)
+{
+	int s;
+	int rc;
+	struct sockaddr_stp sas;
+	const unsigned short port = 12345;
+
+	init_test();
+
+	s = socket(AF_STP, SOCK_DGRAM, 0);
+
+	sas.sas_family = AF_STP;
+	sas.sas_port = htons(port);
+	sas.sas_ifindex = 0;
+	rc = bind(s, (struct sockaddr *) &sas, sizeof(struct sockaddr_stp));
+
+	test(__FUNCTION__, rc == 0, 5);
+
+	close(s);
+	cleanup_test();
+}
+
+/*
+ * Bind socket to proper address. Use "eth0" interface.
+ */
+
+static void test_bind_eth0(void)
+{
+	int s;
+	int rc;
+	struct sockaddr_stp sas;
+	const unsigned short port = 12345;
+
+	init_test();
+
+	s = socket(AF_STP, SOCK_DGRAM, 0);
+
+	sas.sas_family = AF_STP;
+	sas.sas_port = htons(port);
+	sas.sas_ifindex = if_nametoindex("eth0");
+	rc = bind(s, (struct sockaddr *) &sas, sizeof(struct sockaddr_stp));
+
+	test(__FUNCTION__, rc == 0, 2);
+
+	close(s);
+	cleanup_test();
+}
+
+/*
+ * Use bind(2) on two AF_STP sockets.
+ */
+
+static void test_two_binds(void)
+{
+	int s1, s2;
+	int rc1, rc2;
+	struct sockaddr_stp sas1, sas2;
+	const unsigned short port1 = 12345, port2 = 54321;
+
+	init_test();
+
+	s1 = socket(AF_STP, SOCK_DGRAM, 0);
+
+	sas1.sas_family = AF_STP;
+	sas1.sas_port = htons(port1);
+	sas1.sas_ifindex = 0;
+	rc1 = bind(s1, (struct sockaddr *) &sas1, sizeof(struct sockaddr_stp));
+
+	s2 = socket(AF_STP, SOCK_DGRAM, 0);
+
+	sas2.sas_family = AF_STP;
+	sas2.sas_port = htons(port2);
+	sas2.sas_ifindex = 0;
+	rc2 = bind(s2, (struct sockaddr *) &sas2, sizeof(struct sockaddr_stp));
+
+	test(__FUNCTION__, rc1 == 0 && rc2 == 0, 2);
+
+	close(s1); close(s2);
+	cleanup_test();
+}
+
+/*
+ * Pass bad address to bind(2) (second argument).
+ * Call should fail.
+ */
+
+static void test_bind_bad_address(void)
+{
+	int s;
+	int rc;
+	struct sockaddr_stp sas;
+	const unsigned short port = 12345;
+
+	init_test();
+
+	s = socket(AF_STP, SOCK_DGRAM, 0);
+
+	sas.sas_family = AF_INET;	/* invalid */
+	sas.sas_port = htons(port);
+	sas.sas_ifindex = 0;
+	rc = bind(s, (struct sockaddr *) &sas, sizeof(struct sockaddr_stp));
+
+	test(__FUNCTION__, rc != 0, 1);
+
+	close(s);
+	cleanup_test();
+}
+
+/*
+ * Use bind(2) on two AF_STP sockets using same port and "all" interface.
+ * Call should fail.
+ */
+
+static void test_two_binds_same_if(void)
+{
+	int s1, s2;
+	int rc1, rc2;
+	struct sockaddr_stp sas1, sas2;
+	const unsigned short port = 12345;
+
+	init_test();
+
+	s1 = socket(AF_STP, SOCK_DGRAM, 0);
+
+	sas1.sas_family = AF_STP;
+	sas1.sas_port = htons(port);
+	sas1.sas_ifindex = 0;
+	rc1 = bind(s1, (struct sockaddr *) &sas1, sizeof(struct sockaddr_stp));
+
+	s2 = socket(AF_STP, SOCK_DGRAM, 0);
+
+	sas2.sas_family = AF_STP;
+	sas2.sas_port = htons(port);
+	sas2.sas_ifindex = 0;
+	rc2 = bind(s2, (struct sockaddr *) &sas2, sizeof(struct sockaddr_stp));
+
+	test(__FUNCTION__, rc1 == 0 && rc2 < 0, 2);
+
+	close(s1); close(s2);
+	cleanup_test();
+}
+
+/*
+ * Use bind(2) on two AF_STP sockets using same port and same interface.
+ * Call should fail.
+ */
+
+static void test_two_binds_same_if_eth0(void)
+{
+	int s1, s2;
+	int rc1, rc2;
+	struct sockaddr_stp sas1, sas2;
+	const unsigned short port = 12345;
+
+	init_test();
+
+	s1 = socket(AF_STP, SOCK_DGRAM, 0);
+
+	sas1.sas_family = AF_STP;
+	sas1.sas_port = htons(port);
+	sas1.sas_ifindex = if_nametoindex("eth0");
+	rc1 = bind(s1, (struct sockaddr *) &sas1, sizeof(struct sockaddr_stp));
+
+	s2 = socket(AF_STP, SOCK_DGRAM, 0);
+
+	sas2.sas_family = AF_STP;
+	sas2.sas_port = htons(port);
+	sas2.sas_ifindex = if_nametoindex("eth0");
+	rc2 = bind(s2, (struct sockaddr *) &sas2, sizeof(struct sockaddr_stp));
+
+	test(__FUNCTION__, rc1 == 0 && rc2 < 0, 2);
+
+	close(s1); close(s2);
+	cleanup_test();
+}
+
+/*
+ * Use bind(2) on two AF_STP sockets using same port and "all" interface and
+ * "eth0".
+ * Call should fail.
+ */
+
+static void test_two_binds_same_if_all_eth0(void)
+{
+	int s1, s2;
+	int rc1, rc2;
+	struct sockaddr_stp sas1, sas2;
+	const unsigned short port = 12345;
+
+	init_test();
+
+	s1 = socket(AF_STP, SOCK_DGRAM, 0);
+
+	sas1.sas_family = AF_STP;
+	sas1.sas_port = htons(port);
+	sas1.sas_ifindex = 0;
+	rc1 = bind(s1, (struct sockaddr *) &sas1, sizeof(struct sockaddr_stp));
+
+	s2 = socket(AF_STP, SOCK_DGRAM, 0);
+
+	sas2.sas_family = AF_STP;
+	sas2.sas_port = htons(port);
+	sas2.sas_ifindex = if_nametoindex("eth0");
+	rc2 = bind(s2, (struct sockaddr *) &sas2, sizeof(struct sockaddr_stp));
+
+	test(__FUNCTION__, rc1 == 0 && rc2 < 0, 2);
+
+	close(s1); close(s2);
+	cleanup_test();
+}
+
+/*
+ * Use bind(2) on two AF_STP sockets using same port and "eth0" interface and
+ * "all".
+ * Call should fail.
+ */
+
+static void test_two_binds_same_if_eth0_all(void)
+{
+	int s1, s2;
+	int rc1, rc2;
+	struct sockaddr_stp sas1, sas2;
+	const unsigned short port = 12345;
+
+	init_test();
+
+	s1 = socket(AF_STP, SOCK_DGRAM, 0);
+
+	sas1.sas_family = AF_STP;
+	sas1.sas_port = htons(port);
+	sas1.sas_ifindex = if_nametoindex("eth0");
+	rc1 = bind(s1, (struct sockaddr *) &sas1, sizeof(struct sockaddr_stp));
+
+	s2 = socket(AF_STP, SOCK_DGRAM, 0);
+
+	sas2.sas_family = AF_STP;
+	sas2.sas_port = htons(port);
+	sas2.sas_ifindex = 0;
+	rc2 = bind(s2, (struct sockaddr *) &sas2, sizeof(struct sockaddr_stp));
+
+	test(__FUNCTION__, rc1 == 0 && rc2 < 0, 2);
+
+	close(s1); close(s2);
+	cleanup_test();
+}
+
+static ssize_t sendto_message(int sockfd, struct sockaddr_stp *sas,
+	char *buf, size_t len)
+{
+	return sendto(sockfd, buf, len, 0, (SSA *) sas, sizeof(*sas));
+}
+
+static ssize_t sendmsg_message(int sockfd, struct sockaddr_stp *sas,
+	char *buf, size_t len)
+{
+	struct iovec iov;
+	struct msghdr msg;
+
+	iov.iov_base = buf;
+	iov.iov_len = len;
+	msg.msg_name = sas;
+	msg.msg_namelen = sizeof(*sas);
+	msg.msg_iov = &iov;
+	msg.msg_iovlen = 1;
+	msg.msg_control = NULL;
+	msg.msg_controllen = 0;
+	msg.msg_flags = 0;
+
+	return sendmsg(sockfd, &msg, 0);
+}
+
+static ssize_t send_message(int sockfd, char *buf, size_t len)
+{
+	return send(sockfd, buf, len, 0);
+}
+
+/*
+ * Use recvfrom(2) to receive message. We don't care what is the source
+ * address of the message.
+ */
+
+static ssize_t recvfrom_message(int sockfd, char *buf, size_t len)
+{
+	dprintf("ready to receive using recvfrom\n");
+	return recvfrom(sockfd, buf, len, 0, NULL, NULL);
+}
+
+/*
+ * Use recvmsg(2) to receive message. We don't care what is the source
+ * address of the message.
+ */
+
+static ssize_t recvmsg_message(int sockfd, char *buf, size_t len)
+{
+	struct iovec iov;
+	struct msghdr msg;
+
+	iov.iov_base = buf;
+	iov.iov_len = len;
+	msg.msg_name = NULL;
+	msg.msg_namelen = 0;
+	msg.msg_iov = &iov;
+	msg.msg_iovlen = 1;
+	msg.msg_control = NULL;
+	msg.msg_controllen = 0;
+	msg.msg_flags = 0;
+
+	return recvmsg(sockfd, &msg, 0);
+}
+
+/*
+ * Can not use recv(2) on datagram sockets. call recvfrom_message().
+ */
+
+static ssize_t recv_message(int sockfd, char *buf, size_t len)
+{
+	dprintf("ready to receive using recv\n");
+	return recv(sockfd, buf, len, 0);
+}
+
+/*
+ * Use sendto(2) on a socket.
+ */
+
+static void test_sendto(void)
+{
+	int s;
+	int rc;
+	struct sockaddr_stp sas, remote_sas;
+	const unsigned short port = 12345, remote_port = 54321;
+	char bufout[BUFLEN] = DEFAULT_SENDER_MESSAGE;
+
+	init_test();
+
+	s = socket(AF_STP, SOCK_DGRAM, 0);
+
+	sas.sas_family = AF_STP;
+	sas.sas_port = htons(port);
+	sas.sas_ifindex = if_nametoindex("lo");
+	bind(s, (struct sockaddr *) &sas, sizeof(struct sockaddr_stp));
+
+	remote_sas.sas_family = AF_STP;
+	remote_sas.sas_port = htons(remote_port);
+	remote_sas.sas_ifindex = 0;
+	memcpy(remote_sas.sas_addr, ether_aton("00:00:00:00:00:00"),
+		sizeof(remote_sas.sas_addr));
+
+	rc = sendto_message(s, &remote_sas, bufout, BUFLEN);
+
+	test(__FUNCTION__, rc >= 0, 5);
+
+	close(s);
+	cleanup_test();
+}
+
+/*
+ * Use sendmsg(2) on a socket.
+ */
+
+static void test_sendmsg(void)
+{
+	int s;
+	int rc;
+	struct sockaddr_stp sas, remote_sas;
+	const unsigned short port = 12345, remote_port = 54321;
+	char bufout[BUFLEN] = DEFAULT_SENDER_MESSAGE;
+
+	init_test();
+
+	s = socket(AF_STP, SOCK_DGRAM, 0);
+
+	sas.sas_family = AF_STP;
+	sas.sas_port = htons(port);
+	sas.sas_ifindex = if_nametoindex("lo");
+	bind(s, (struct sockaddr *) &sas, sizeof(struct sockaddr_stp));
+
+	remote_sas.sas_family = AF_STP;
+	remote_sas.sas_port = htons(remote_port);
+	remote_sas.sas_ifindex = 0;
+	memcpy(remote_sas.sas_addr, ether_aton("00:00:00:00:00:00"),
+		sizeof(remote_sas.sas_addr));
+
+	rc = sendmsg_message(s, &remote_sas, bufout, BUFLEN);
+
+	test(__FUNCTION__, rc >= 0, 3);
+
+	close(s);
+	cleanup_test();
+}
+
+/*
+ * Connect local socket to remote AF_STP socket.
+ */
+
+static void test_connect(void)
+{
+	int s;
+	int rc;
+	struct sockaddr_stp sas, remote_sas;
+	const unsigned short port = 12345, remote_port = 54321;
+
+	init_test();
+
+	s = socket(AF_STP, SOCK_DGRAM, 0);
+
+	sas.sas_family = AF_STP;
+	sas.sas_port = htons(port);
+	sas.sas_ifindex = if_nametoindex("lo");
+	bind(s, (struct sockaddr *) &sas, sizeof(struct sockaddr_stp));
+
+	remote_sas.sas_family = AF_STP;
+	remote_sas.sas_port = htons(remote_port);
+	remote_sas.sas_ifindex = 0;
+	memcpy(remote_sas.sas_addr, ether_aton("00:00:00:00:00:00"),
+		sizeof(remote_sas.sas_addr));
+
+	rc = connect(s, (struct sockaddr *) &remote_sas, sizeof(remote_sas));
+
+	test(__FUNCTION__, rc >= 0, 5);
+
+	close(s);
+	cleanup_test();
+}
+
+/*
+ * Use send(2) on a connected socket.
+ */
+
+static void test_send(void)
+{
+	int s;
+	int rc;
+	struct sockaddr_stp sas, remote_sas;
+	const unsigned short port = 12345, remote_port = 54321;
+	char bufout[BUFLEN] = DEFAULT_SENDER_MESSAGE;
+
+	init_test();
+
+	s = socket(AF_STP, SOCK_DGRAM, 0);
+
+	sas.sas_family = AF_STP;
+	sas.sas_port = htons(port);
+	sas.sas_ifindex = if_nametoindex("lo");
+	bind(s, (struct sockaddr *) &sas, sizeof(struct sockaddr_stp));
+
+	remote_sas.sas_family = AF_STP;
+	remote_sas.sas_port = htons(remote_port);
+	remote_sas.sas_ifindex = 0;
+	memcpy(remote_sas.sas_addr, ether_aton("00:00:00:00:00:00"),
+		sizeof(remote_sas.sas_addr));
+
+	rc = connect(s, (SSA *) &remote_sas, sizeof(remote_sas));
+	assert(rc == 0);
+
+	rc = send_message(s, bufout, BUFLEN);
+
+	test(__FUNCTION__, rc >= 0, 5);
+
+	close(s);
+	cleanup_test();
+}
+
+/*
+ * Read values from STP_PROC_FULL_FILENAME.
+ */
+
+static int stp_proc_read_values(void)
+{
+	char buffer[256];
+	FILE *f;
+
+	f = fopen(STP_PROC_FULL_FILENAME, "rt");
+	if (f == NULL)
+		return -1;
+
+	/* read column line */
+	fgets(buffer, 256, f);
+
+	/* read values line */
+	fscanf(f, "%d %d %d %d %d %d",
+		&rx_pkts, &hdr_err, &csum_err, &no_sock, &no_buffs, &tx_pkts);
+	dprintf("read: %d %d %d %d %d %d\n",
+		rx_pkts, hdr_err, csum_err, no_sock, no_buffs, tx_pkts);
+
+	fclose(f);
+
+	return 0;
+}
+
+/*
+ * Send packet updates RxPkts column in STP_PROC_FULL_FILENAME.
+ * Expected values are 1, 1.
+ */
+
+static void test_stat_tx(void)
+{
+	int s;
+	int rc;
+	struct sockaddr_stp sas, remote_sas;
+	const unsigned short port = 12345, remote_port = 54321;
+	char bufout[BUFLEN] = DEFAULT_SENDER_MESSAGE;
+
+	init_test();
+
+	s = socket(AF_STP, SOCK_DGRAM, 0);
+
+	sas.sas_family = AF_STP;
+	sas.sas_port = htons(port);
+	sas.sas_ifindex = if_nametoindex("lo");
+	bind(s, (struct sockaddr *) &sas, sizeof(struct sockaddr_stp));
+
+	remote_sas.sas_family = AF_STP;
+	remote_sas.sas_port = htons(remote_port);
+	remote_sas.sas_ifindex = 0;
+	memcpy(remote_sas.sas_addr, ether_aton("00:00:00:00:00:00"),
+		sizeof(remote_sas.sas_addr));
+
+	rc = connect(s, (SSA *) &remote_sas, sizeof(remote_sas));
+	assert(rc == 0);
+
+	send_message(s, bufout, BUFLEN);
+
+	close(s);
+
+	stp_proc_read_values();
+
+	test(__FUNCTION__, tx_pkts == 1, 3);
+
+	cleanup_test();
+}
+
+/*
+ * Start sender process.
+ *
+ * action switches between sendto(2), sendmsg(2), send(2) and whether
+ * to do ping_pong or not.
+ */
+
+static pid_t start_sender(enum socket_action action)
+{
+	pid_t pid;
+	int s;
+	struct sockaddr_stp sas, remote_sas;
+	const unsigned short port = 12345, remote_port = 54321;
+	char bufin[BUFLEN];
+	char bufout[BUFLEN] = DEFAULT_SENDER_MESSAGE;
+	ssize_t bytes_recv = 0, bytes_sent = 0;
+	sem_t *sem;
+
+	/* set bufin to 0 for testing purposes (it should be overwritten) */
+	memset(bufin, 0, BUFLEN);
+
+	pid = fork();
+	DIE(pid < 0, "fork");
+
+	switch (pid) {
+	case 0:		/* child process */
+		break;
+
+	default:	/* parent process */
+		return pid;
+	}
+
+	/* only child process (sender) is running */
+
+	sem = sem_open(SEM_NAME_SENDER, 0);
+	if (sem == SEM_FAILED)
+		exit(EXIT_FAILURE);
+
+	s = socket(AF_STP, SOCK_DGRAM, 0);
+
+	sas.sas_family = AF_STP;
+	sas.sas_port = htons(port);
+	sas.sas_ifindex = if_nametoindex("lo");
+	bind(s, (struct sockaddr *) &sas, sizeof(struct sockaddr_stp));
+
+	remote_sas.sas_family = AF_STP;
+	remote_sas.sas_port = htons(remote_port);
+	remote_sas.sas_ifindex = 0;
+	memcpy(remote_sas.sas_addr, ether_aton("00:00:00:00:00:00"),
+		sizeof(remote_sas.sas_addr));
+	if (action == ACTION_SEND || action == ACTION_SEND_PING_PONG) {
+		int rc;
+
+		rc = connect(s, (SSA *) &remote_sas, sizeof(remote_sas));
+		assert(rc == 0);
+	}
+
+	switch (action) {
+	case ACTION_SENDTO:
+	case ACTION_SENDTO_PING_PONG:
+		bytes_sent = sendto_message(s, &remote_sas, bufout, BUFLEN);
+		dprintf("sent %s\n", bufout);
+		break;
+
+	case ACTION_SENDMSG:
+	case ACTION_SENDMSG_PING_PONG:
+		bytes_sent = sendmsg_message(s, &remote_sas, bufout, BUFLEN);
+		dprintf("sent %s\n", bufout);
+		break;
+
+	case ACTION_SEND:
+	case ACTION_SEND_PING_PONG:
+		bytes_sent = send_message(s, bufout, BUFLEN);
+		dprintf("sent %s\n", bufout);
+		break;
+
+	default:
+		break;
+	}
+
+	switch (action) {
+	case ACTION_SENDTO_PING_PONG:
+		bytes_recv = recvfrom_message(s, bufin, BUFLEN);
+		dprintf("received %s\n", bufin);
+		break;
+	case ACTION_SENDMSG_PING_PONG:
+		bytes_recv = recvmsg_message(s, bufin, BUFLEN);
+		dprintf("received %s\n", bufin);
+		break;
+	case ACTION_SEND_PING_PONG:
+		bytes_recv = recv_message(s, bufin, BUFLEN);
+		dprintf("received %s\n", bufin);
+		break;
+	default:
+		break;
+	}
+
+	/* Let the parent know we're done. */
+	sem_post(sem);
+
+	/* exit with EXIT_SUCCESS in case of successful communication */
+	switch (action) {
+	case ACTION_SENDTO:
+	case ACTION_SEND:
+	case ACTION_SENDMSG:
+		if (bytes_sent > 0)
+			exit(EXIT_SUCCESS);
+		break;
+
+	case ACTION_SENDMSG_PING_PONG:
+	case ACTION_SENDTO_PING_PONG:
+	case ACTION_SEND_PING_PONG:
+		dprintf("(ping_pong) bytes_sent: %d, bytes_recv: %d, strcmp: %d\n",
+			bytes_sent, bytes_recv, strcmp(bufin, bufout));
+		dprintf("bufin: #%s#, bufout: #%s#\n", bufin, bufout);
+		if (bytes_sent > 0 && bytes_recv > 0 &&
+			strcmp(bufin, DEFAULT_RECEIVER_MESSAGE) == 0)
+			exit(EXIT_SUCCESS);
+		break;
+	}
+
+	exit(EXIT_FAILURE);
+
+	/* is not reached */
+	return 0;
+}
+
+/*
+ * Start receiver process.
+ *
+ * action switches between sendto(2), sendmsg(2), send(2) and whether
+ * to do ping_pong or not.
+ */
+
+static pid_t start_receiver(enum socket_action action)
+{
+	pid_t pid;
+	int s;
+	struct sockaddr_stp sas, remote_sas;
+	const unsigned short port = 54321, remote_port = 12345;
+	char bufin[BUFLEN];
+	char bufout[BUFLEN] = DEFAULT_RECEIVER_MESSAGE;
+	ssize_t bytes_recv = 0, bytes_sent = 0;
+	sem_t *sem;
+
+	/* set bufin to 0 for testing purposes (it should be overwritten) */
+	memset(bufin, 0, BUFLEN);
+
+	pid = fork();
+	DIE(pid < 0, "fork");
+
+	switch (pid) {
+	case 0:		/* child process */
+		break;
+
+	default:	/* parent process */
+		return pid;
+	}
+
+	/* only child process (receiver) is running */
+
+	sem = sem_open(SEM_NAME_RECEIVER, 0);
+	if (sem == SEM_FAILED)
+		exit(EXIT_FAILURE);
+
+	s = socket(AF_STP, SOCK_DGRAM, 0);
+
+	sas.sas_family = AF_STP;
+	sas.sas_port = htons(port);
+	sas.sas_ifindex = if_nametoindex("lo");
+	bind(s, (struct sockaddr *) &sas, sizeof(struct sockaddr_stp));
+
+	remote_sas.sas_family = AF_STP;
+	remote_sas.sas_port = htons(remote_port);
+	remote_sas.sas_ifindex = 0;
+	memcpy(remote_sas.sas_addr, ether_aton("00:00:00:00:00:00"),
+		sizeof(remote_sas.sas_addr));
+
+	if (action == ACTION_SEND || action == ACTION_SEND_PING_PONG) {
+		int rc;
+
+		rc = connect(s, (SSA *) &remote_sas, sizeof(remote_sas));
+		assert(rc == 0);
+		dprintf("connected\n");
+	}
+
+	/* We're set up, let the parent know. */
+	sem_post(sem);
+
+	switch (action) {
+	case ACTION_SENDTO:
+	case ACTION_SENDTO_PING_PONG:
+		bytes_recv = recvfrom_message(s, bufin, BUFLEN);
+		dprintf("received %s\n", bufin);
+		break;
+
+	case ACTION_SENDMSG:
+	case ACTION_SENDMSG_PING_PONG:
+		bytes_recv = recvmsg_message(s, bufin, BUFLEN);
+		dprintf("received %s\n", bufin);
+		break;
+
+	case ACTION_SEND:
+	case ACTION_SEND_PING_PONG:
+		bytes_recv = recv_message(s, bufin, BUFLEN);
+		dprintf("received %s\n", bufin);
+		break;
+
+	default:
+		break;
+	}
+
+	switch (action) {
+	case ACTION_SENDTO_PING_PONG:
+		bytes_sent = sendto_message(s, &remote_sas, bufout, BUFLEN);
+		dprintf("sent %s\n", bufout);
+		break;
+	case ACTION_SENDMSG_PING_PONG:
+		bytes_sent = sendmsg_message(s, &remote_sas, bufout, BUFLEN);
+		dprintf("sent %s\n", bufout);
+		break;
+	case ACTION_SEND_PING_PONG:
+		bytes_sent = send_message(s, bufout, BUFLEN);
+		dprintf("sent %s\n", bufout);
+		break;
+	default:
+		break;
+	}
+
+	/* Let the parent know we're done. */
+	sem_post(sem);
+
+	/* exit with EXIT_SUCCESS in case of successful communication */
+	switch (action) {
+	case ACTION_SENDTO:
+	case ACTION_SEND:
+	case ACTION_SENDMSG:
+		if (bytes_recv > 0)
+			exit(EXIT_SUCCESS);
+		break;
+
+	case ACTION_SENDMSG_PING_PONG:
+	case ACTION_SENDTO_PING_PONG:
+	case ACTION_SEND_PING_PONG:
+		dprintf("(ping_pong) bytes_sent: %d, bytes_recv: %d\n",
+				bytes_sent, bytes_recv);
+		dprintf("bufin: #%s#, bufout: #%s#\n", bufin, bufout);
+		if (bytes_recv > 0 && bytes_sent > 0 &&
+			strcmp(bufin, DEFAULT_SENDER_MESSAGE) == 0)
+			exit(EXIT_SUCCESS);
+		break;
+	}
+
+	exit(EXIT_FAILURE);
+
+	/* is not reached */
+	return 0;
+}
+
+int wait_for_semaphore(sem_t *sem, unsigned int secs)
+{
+	struct timespec ts;
+	int ret;
+
+	ret = clock_gettime(CLOCK_REALTIME, &ts);
+	assert(ret == 0);
+
+	ts.tv_sec += secs;
+
+	ret = sem_timedwait(sem, &ts);
+	return ret;
+}
+
+/*
+ * Wrapper call for running a sender and a receiver process.
+ *
+ * action switches between sendto(2), sendmsg(2), send(2) and whether
+ * to do ping_pong or not.
+ *
+ * Returns boolean value: 1 in case of successful run, 0 otherwise.
+ */
+
+static int run_sender_receiver(enum socket_action action)
+{
+	pid_t pid_r = 0, pid_s = 0;
+	int rc1, rc2, ret;
+	int status1, status2;
+	sem_t *sem_r, *sem_s;
+
+	/* Create two named semaphores used to communicate
+	 * with the child processes
+	 */
+	sem_r = sem_open(SEM_NAME_RECEIVER, O_CREAT, (mode_t)0644, 0);
+	assert(sem_r != SEM_FAILED);
+	sem_s = sem_open(SEM_NAME_SENDER, O_CREAT, (mode_t)0644, 0);
+	assert(sem_s != SEM_FAILED);
+
+	/* start the receiver */
+	pid_r = start_receiver(action);
+	assert(pid_r > 0);
+	/* wait for it to bind */
+	wait_for_semaphore(sem_r, RECV_TIMEOUT);
+
+	/* Receiver is set up, start the sender now. */
+	pid_s = start_sender(action);
+	assert(pid_s > 0);
+
+	/* Wait for both to finish. */
+	rc1 = wait_for_semaphore(sem_r, SENDRECV_TIMEOUT);
+	ret = waitpid(pid_r, &status1, rc1 ? WNOHANG : 0);
+	assert(ret >= 0);
+	kill(pid_r, SIGTERM); kill(pid_r, SIGKILL);
+
+	rc2 = wait_for_semaphore(sem_s, SENDRECV_TIMEOUT);
+	ret = waitpid(pid_s, &status2, rc2 ? WNOHANG : 0);
+	assert(ret >= 0);
+	kill(pid_s, SIGTERM); kill(pid_s, SIGKILL);
+
+	sem_close(sem_r); sem_unlink(SEM_NAME_RECEIVER);
+	sem_close(sem_s); sem_unlink(SEM_NAME_SENDER);
+
+	return !rc1 && !rc2 &&
+	       WIFEXITED(status1) && WEXITSTATUS(status1) == EXIT_SUCCESS &&
+	       WIFEXITED(status2) && WEXITSTATUS(status2) == EXIT_SUCCESS;
+}
+
+/*
+ * Send a datagram on one end and receive it on the other end.
+ * Use sendto(2) and recvfrom(2).
+ */
+
+static void test_sendto_recvfrom(void)
+{
+	int rc;
+
+	init_test();
+
+	rc = run_sender_receiver(ACTION_SENDTO);
+
+	test(__FUNCTION__, rc != 0, 10);
+
+	cleanup_test();
+}
+
+/*
+ * Send and receive packet updates RxPkts and TxPkts columns in
+ * STP_PROC_FULL_FILENAME. Expected values are 1, 1.
+ */
+
+static void test_stat_tx_rx(void)
+{
+	init_test();
+
+	run_sender_receiver(ACTION_SENDTO);
+
+	stp_proc_read_values();
+
+	test(__FUNCTION__, tx_pkts == 1 && rx_pkts == 1, 3);
+
+	cleanup_test();
+}
+
+/*
+ * Send a packet and then wait for a reply.
+ */
+
+static void test_sendto_recvfrom_ping_pong(void)
+{
+	int rc;
+
+	init_test();
+
+	rc = run_sender_receiver(ACTION_SENDTO_PING_PONG);
+
+	test(__FUNCTION__, rc != 0, 5);
+
+	cleanup_test();
+}
+
+/*
+ * Send and receive ping pong updates RxPkts and TxPkts column in
+ * STP_PROC_FULL_FILENAME. Expected values are 2, 2.
+ */
+
+static void test_stat_tx_rx_ping_pong(void)
+{
+	init_test();
+
+	run_sender_receiver(ACTION_SENDTO_PING_PONG);
+
+	stp_proc_read_values();
+	stp_proc_read_values();
+
+	test(__FUNCTION__, tx_pkts == 2 && rx_pkts == 2, 3);
+
+	cleanup_test();
+}
+
+/*
+ * Send a datagram on one end and receive it on the other end.
+ * Use sendmsg(2) and recvmsg(2).
+ */
+
+static void test_sendmsg_recvmsg(void)
+{
+	int rc;
+
+	init_test();
+
+	rc = run_sender_receiver(ACTION_SENDMSG);
+
+	test(__FUNCTION__, rc != 0, 5);
+
+	cleanup_test();
+}
+
+/*
+ * Send a packet and then wait for a reply.
+ */
+
+static void test_sendmsg_recvmsg_ping_pong(void)
+{
+	int rc;
+
+	init_test();
+
+	rc = run_sender_receiver(ACTION_SENDMSG_PING_PONG);
+
+	test(__FUNCTION__, rc != 0, 3);
+
+	cleanup_test();
+}
+
+/*
+ * Send a packet on one end and receive it on the other end.
+ * Use send(2) and recv(2).
+ */
+
+static void test_send_receive(void)
+{
+	int rc;
+
+	init_test();
+
+	rc = run_sender_receiver(ACTION_SEND);
+
+	test(__FUNCTION__, rc != 0, 5);
+
+	cleanup_test();
+}
+
+/*
+ * Send a packet and then wait for a reply.
+ */
+
+static void test_send_receive_ping_pong(void)
+{
+	int rc;
+
+	init_test();
+
+	rc = run_sender_receiver(ACTION_SEND_PING_PONG);
+
+	test(__FUNCTION__, rc != 0, 3);
+
+	cleanup_test();
+}
+
+static void (*test_fun_array[])(void) = {
+	NULL,
+	test_insmod_rmmod,
+	test_proto_name_exists_after_insmod,
+	test_proto_name_inexistent_after_rmmod,
+	test_proc_entry_exists_after_insmod,
+	test_proc_entry_inexistent_after_rmmod,
+	test_socket,
+	test_two_sockets,
+	test_socket_bad_socket_type,
+	test_socket_bad_protocol,
+	test_close,
+	test_close_closed_socket,
+	test_bind,
+	test_bind_eth0,
+	test_two_binds,
+	test_bind_bad_address,
+	test_two_binds_same_if,
+	test_two_binds_same_if_eth0,
+	test_two_binds_same_if_all_eth0,
+	test_two_binds_same_if_eth0_all,
+	test_sendto,
+	test_sendmsg,
+	test_connect,
+	test_send,
+	test_stat_tx,
+	test_sendto_recvfrom,
+	test_stat_tx_rx,
+	test_sendto_recvfrom_ping_pong,
+	test_stat_tx_rx_ping_pong,
+	test_sendmsg_recvmsg,
+	test_sendmsg_recvmsg_ping_pong,
+	test_send_receive,
+	test_send_receive_ping_pong,
+};
+
+/*
+ * Usage message for invalid executable call.
+ */
+
+static void usage(const char *argv0)
+{
+	fprintf(stderr, "Usage: %s test_no\n\n", argv0);
+	exit(EXIT_FAILURE);
+}
+
+int main(int argc, char **argv)
+{
+	int test_idx;
+
+	if (argc != 2)
+		usage(argv[0]);
+
+	test_idx = atoi(argv[1]);
+
+	if (test_idx < 1 ||
+		test_idx >= sizeof(test_fun_array)/sizeof(test_fun_array[0])) {
+		fprintf(stderr, "Error: test index %d is out of bounds\n",
+			test_idx);
+		exit(EXIT_FAILURE);
+	}
+
+	srand(time(NULL));
+	srand48(time(NULL));
+	test_fun_array[test_idx]();
+
+	return 0;
+}
diff --git a/checker/4-stp-checker/_test/stp_test.h b/checker/4-stp-checker/_test/stp_test.h
new file mode 100644
index 0000000000000000000000000000000000000000..fb708433c0269f38454ccb3d6903e2c8c410468a
--- /dev/null
+++ b/checker/4-stp-checker/_test/stp_test.h
@@ -0,0 +1,31 @@
+/*
+ * SO2 Transport Protocol - test suite specific header
+ */
+
+#ifndef STP_TEST_H_
+#define STP_TEST_H_		1
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* STP test suite macros and structures */
+#define MODULE_NAME		"af_stp"
+#define MODULE_FILENAME		MODULE_NAME ".ko"
+
+#define SEM_NAME_RECEIVER	"/receiver_sem"
+#define SEM_NAME_SENDER		"/sender_sem"
+
+/* timeouts waiting for receiver/sender */
+#define RECV_TIMEOUT			1
+#define SENDRECV_TIMEOUT		3
+
+/* messages used for "ping-pong" between sender and receiver */
+#define DEFAULT_SENDER_MESSAGE		"You called down the thunder"
+#define DEFAULT_RECEIVER_MESSAGE	"now reap the whirlwind"
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/checker/4-stp-checker/_test/test.h b/checker/4-stp-checker/_test/test.h
new file mode 100644
index 0000000000000000000000000000000000000000..4bcafad9c7d0f5b241a0241071301954c3bf9601
--- /dev/null
+++ b/checker/4-stp-checker/_test/test.h
@@ -0,0 +1,63 @@
+/*
+ * generic test suite
+ *
+ * test macros and headers
+ */
+
+#ifndef TEST_H_
+#define TEST_H_		1
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdio.h>
+
+/* to be defined by calling program */
+extern int max_points;
+
+/*
+ * uncommend EXIT_IF_FAIL macro in order to stop test execution
+ * at first failed test
+ */
+
+/*#define EXIT_IF_FAIL	1*/
+
+#if defined(EXIT_IF_FAIL)
+#define test_do_fail(points)		\
+	do {				\
+		printf("failed\n");	\
+		exit(EXIT_FAILURE);	\
+	} while (0)
+#else
+#define test_do_fail(points)		\
+	printf("failed  [  0/%3d]\n", max_points)
+#endif
+
+#define test_do_pass(points)		\
+	printf("passed  [%3d/%3d]\n", points, max_points)
+
+#define test(message, test, points)				\
+	do {							\
+		size_t i;					\
+		int t = (test);					\
+								\
+		printf("%s", message);				\
+		fflush(stdout);					\
+								\
+		for (i = 0; i < 60 - strlen(message); i++)	\
+			putchar('.');				\
+								\
+		if (!t)						\
+			test_do_fail(points);			\
+		else						\
+			test_do_pass(points);			\
+								\
+		fflush(stdout);					\
+	} while (0)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/checker/4-stp-checker/_test/util.h b/checker/4-stp-checker/_test/util.h
new file mode 100644
index 0000000000000000000000000000000000000000..f06cb833b99635e27c494498fd6b981e50564dbc
--- /dev/null
+++ b/checker/4-stp-checker/_test/util.h
@@ -0,0 +1,69 @@
+/*
+ * useful structures/macros
+ */
+
+#ifndef UTIL_H_
+#define UTIL_H_		1
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#if defined(_WIN32)
+
+#include <windows.h>
+
+static VOID PrintLastError(const PCHAR message)
+{
+	CHAR errBuff[1024];
+
+	FormatMessage(
+		FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_MAX_WIDTH_MASK,
+		NULL,
+		GetLastError(),
+		0,
+		errBuff,
+		sizeof(errBuff) - 1,
+		NULL);
+
+	fprintf(stderr, "%s: %s\n", message, errBuff);
+}
+
+#define ERR(call_description)					\
+	do {							\
+		fprintf(stderr, "(%s, %d): ",			\
+				__FILE__, __LINE__);		\
+			PrintLastError(call_description);	\
+	} while (0)
+
+#elif defined(__linux__)
+
+/* error printing macro */
+#define ERR(call_description)				\
+	do {						\
+		fprintf(stderr, "(%s, %d): ",		\
+				__FILE__, __LINE__);	\
+			perror(call_description);	\
+	} while (0)
+
+#else
+  #error "Unknown platform"
+#endif
+
+/* print error (call ERR) and exit */
+#define DIE(assertion, call_description)		\
+	do {						\
+		if (assertion) {			\
+			ERR(call_description);		\
+			exit(EXIT_FAILURE);		\
+		}					\
+	} while (0)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/checker/checker.sh b/checker/checker.sh
index 8c1cbb4ff602a247f3f3125d24ad8ae3fb424438..e461e54e6efef9aa1b426a88513fe0dcec5588da 100755
--- a/checker/checker.sh
+++ b/checker/checker.sh
@@ -1,7 +1,8 @@
 #!/bin/bash
 
 SO2_WORKSPACE=/linux/tools/labs
-
+SO2_VM_LOG=/tmp/so2_vm_log.txt
+DMESG_LOG=""
 
 ASSIGNMENT0_TIMEOUT=300 # 5 min
 ASSIGNMENT0_MOD=list.ko
@@ -41,6 +42,16 @@ ASSIGNMENT3_FINISHED=${SO2_WORKSPACE}/skels/3-raid-finished
 ASSIGNMENT3_HEADER_OVERWRITE=${SO2_WORKSPACE}/templates/assignments/3-raid/ssr.h
 ASSIGNMENT3_CHECKER_AUX_LIST="${ASSIGNMENT3_CHECKER_DIR}/_test/run-test"
 
+ASSIGNMENT4_TIMEOUT=300 # 5 min
+ASSIGNMENT4_MOD=af_stp.ko
+ASSIGNMENT4_DIR=${SO2_WORKSPACE}/skels/assignments/4-stp
+ASSIGNMENT4_CHECKER_LOCAL_DIR=checker/4-stp-checker
+ASSIGNMENT4_CHECKER_DIR=${SO2_WORKSPACE}/skels/assignments/4-stp-checker
+ASSIGNMENT4_OUTPUT=${SO2_WORKSPACE}/skels/4-stp-output
+ASSIGNMENT4_FINISHED=${SO2_WORKSPACE}/skels/4-stp-finished
+ASSIGNMENT4_HEADER_OVERWRITE=${SO2_WORKSPACE}/templates/assignments/4-stp/stp.h
+#ASSIGNMENT4_CHECKER_AUX_LIST="${ASSIGNMENT3_CHECKER_DIR}/_test/run-test"
+
 
 usage()
 {
@@ -49,6 +60,13 @@ usage()
 }
 
 
+configure_logger()
+{
+	DMESG_LOG="/linux/tools/labs/skels/log.txt"
+	cp ./checker/checker_daemons/so2_vm_checker_logger.sh /linux/tools/labs/rootfs/etc/init.d
+	chmod +x /linux/tools/labs/rootfs/etc/init.d/so2_vm_checker_logger.sh
+	chroot /linux/tools/labs/rootfs update-rc.d so2_vm_checker_logger.sh defaults 0 0
+}
 
 recover_grade_from_timeout()
 {
@@ -66,18 +84,32 @@ recover_grade_from_timeout()
 	fi
 }
 
+dmesg_log_dump()
+{
+	if [[ $DMESG_LOG != "" ]]; then
+		echo "dumping DMESG_LOG=${DMESG_LOG} output"
+		echo ">>>>---------------DMESG_LOG_STARTS_HERE------------------<<<<<"
+		cat $DMESG_LOG
+		echo ">>>>----------------DMESG_LOG_ENDS_HERE-------------------<<<<<"
+	fi
+}
+
 timeout_exceeded()
 {
 	local output=$1
 	pkill -SIGKILL qemu
 	echo ""
 	echo "TIMEOUT EXCEEDED !!! killing the process"
+
+	dmesg_log_dump
+
 	if [[ $RECOVER_GRADE_TIMEOUT == 0 ]]; then
 		if [ -f $output ]; then
 			echo "$output not available"
 		else
 			cat $output
 		fi
+
 		echo "The Recover Grade Timeout option is not set! Please contact a teaching assistant!"
 	else
 		recover_grade_from_timeout $output
@@ -195,7 +227,7 @@ run_checker()
 			done
 		fi
 
-		LINUX_ADD_CMDLINE="so2=$assignment" make checker &> /dev/null &
+		LINUX_ADD_CMDLINE="so2=$assignment" make checker &> ${SO2_VM_LOG} &
 
 		timeout=0
 		echo -n "CHECKER IS RUNNING"
@@ -204,7 +236,7 @@ run_checker()
 			if ((timeout >= TIMEOUT)); then
 				if [ -f $output ]; then
 					echo ""
-					dump_output $output
+					dump_output $output $timeout
 					compute_total $output
 				fi
 				timeout_exceeded $output
@@ -219,6 +251,8 @@ run_checker()
 	popd &> /dev/null
 }
 
+
+
 case $1 in
 	0-list)
 		TIMEOUT=$ASSIGNMENT0_TIMEOUT
@@ -233,6 +267,7 @@ case $1 in
 	2-uart)
 		TIMEOUT=$ASSIGNMENT2_TIMEOUT
 		RECOVER_GRADE_TIMEOUT=1 # If set to 1, in case of a timeout, will calculate the total grade based on the output directory
+		configure_logger
 		run_checker $ASSIGNMENT2_MOD $ASSIGNMENT2_DIR $ASSIGNMENT2_CHECKER_LOCAL_DIR $ASSIGNMENT2_CHECKER_DIR $ASSIGNMENT2_OUTPUT $ASSIGNMENT2_FINISHED $1 $ASSIGNMENT2_HEADER_OVERWRITE $ASSIGNMENT2_CHECKER_AUX_LIST
  		;;
 	3-raid)
@@ -240,6 +275,12 @@ case $1 in
 		RECOVER_GRADE_TIMEOUT=0 # If set to 1, in case of a timeout, will calculate the total grade based on the output directory
 		run_checker $ASSIGNMENT3_MOD $ASSIGNMENT3_DIR $ASSIGNMENT3_CHECKER_LOCAL_DIR $ASSIGNMENT3_CHECKER_DIR $ASSIGNMENT3_OUTPUT $ASSIGNMENT3_FINISHED $1 $ASSIGNMENT3_HEADER_OVERWRITE $ASSIGNMENT3_CHECKER_AUX_LIST
 		;;
+	4-stp)
+		TIMEOUT=$ASSIGNMENT4_TIMEOUT
+		RECOVER_GRADE_TIMEOUT=0 # If set to 1, in case of a timeout, will calculate the total grade based on the output file
+		run_checker $ASSIGNMENT4_MOD $ASSIGNMENT4_DIR $ASSIGNMENT4_CHECKER_LOCAL_DIR $ASSIGNMENT4_CHECKER_DIR $ASSIGNMENT4_OUTPUT $ASSIGNMENT4_FINISHED $1 $ASSIGNMENT4_HEADER_OVERWRITE
+		;;
+	
 	*)
 		usage
 		;;
diff --git a/checker/checker_daemons/so2_vm_checker_daemon.sh b/checker/checker_daemons/so2_vm_checker_daemon.sh
index d7e6b18c0b2a650c08f7a65c7b8e2520565d488a..546f236afdea29a8851f21106f3b317387856ee9 100644
--- a/checker/checker_daemons/so2_vm_checker_daemon.sh
+++ b/checker/checker_daemons/so2_vm_checker_daemon.sh
@@ -18,37 +18,49 @@ ASSIGNMENT3_CHECKER=/home/root/skels/assignments/3-raid-checker
 ASSIGNMENT3_OUTPUT=/home/root/skels/3-raid-output
 ASSIGNMENT3_FINISHED=/home/root/skels/3-raid-finished
 
+ASSIGNMENT4_CHECKER=/home/root/skels/assignments/4-stp-checker
+ASSIGNMENT4_OUTPUT=/home/root/skels/4-stp-output
+ASSIGNMENT4_FINISHED=/home/root/skels/4-stp-finished
+
 
 assign0_list()
 {
-        cd $ASSIGNMENT0_CHECKER
-     	   sh _checker &> $ASSIGNMENT0_OUTPUT
-       	   echo FINISHED &> $ASSIGNMENT0_FINISHED
-        cd -
+	cd $ASSIGNMENT0_CHECKER
+		sh _checker &> $ASSIGNMENT0_OUTPUT
+		echo FINISHED &> $ASSIGNMENT0_FINISHED
+	cd -
 }
 
 assign1_tracer()
 {
-        cd $ASSIGNMENT1_CHECKER
-     	   sh _checker &> $ASSIGNMENT1_OUTPUT
-       	   echo FINISHED &> $ASSIGNMENT1_FINISHED
-        cd -
+	cd $ASSIGNMENT1_CHECKER
+		sh _checker &> $ASSIGNMENT1_OUTPUT
+		echo FINISHED &> $ASSIGNMENT1_FINISHED
+	cd -
 }
 
 assign2_uart()
 {
-        cd $ASSIGNMENT2_CHECKER
-     	   sh _checker &> $ASSIGNMENT2_OUTPUT
-       	   echo FINISHED &> $ASSIGNMENT2_FINISHED
-        cd -
+	cd $ASSIGNMENT2_CHECKER
+		sh _checker &> $ASSIGNMENT2_OUTPUT
+		echo FINISHED &> $ASSIGNMENT2_FINISHED
+	cd -
 }
 
 assign3_raid()
 {
-        cd $ASSIGNMENT3_CHECKER
-     	   sh _checker &> $ASSIGNMENT3_OUTPUT
-       	   echo FINISHED &> $ASSIGNMENT3_FINISHED
-        cd -
+	cd $ASSIGNMENT3_CHECKER
+		sh _checker &> $ASSIGNMENT3_OUTPUT
+		echo FINISHED &> $ASSIGNMENT3_FINISHED
+	cd -
+}
+
+assign4_stp()
+{
+	cd $ASSIGNMENT4_CHECKER
+		sh _checker &> $ASSIGNMENT4_OUTPUT
+		echo FINISHED &> $ASSIGNMENT4_FINISHED
+	cd -
 }
 
 start()
@@ -67,6 +79,9 @@ start()
 		3-raid)
 			assign3_raid
 			;;
+		4-stp)
+			assign4_stp
+			;;
                 *)
                         echo "Unknown option"
                         exit 0
diff --git a/checker/checker_daemons/so2_vm_checker_logger.sh b/checker/checker_daemons/so2_vm_checker_logger.sh
new file mode 100644
index 0000000000000000000000000000000000000000..fe61fb61e4ac9e42e3f99ef9ced25f0f8312a3b8
--- /dev/null
+++ b/checker/checker_daemons/so2_vm_checker_logger.sh
@@ -0,0 +1,31 @@
+#!/bin/sh
+
+# THIS SCRIPT RUNS INSIDE THE SO2 VM
+
+LOG_FILE=/home/root/skels/log.txt
+
+start()
+{
+	set -x
+	echo "" > ${LOG_FILE}
+	while true
+	do
+		sleep 1
+		dmesg -c >> $LOG_FILE
+	done
+}
+
+# Carry out specific functions when asked to by the system
+case "$1" in
+        start)
+                echo "Starting so2_vm_checker_logger.sh..."
+                start & # start in background
+                ;;
+        *)
+                echo "Usage: /etc/init.d/foo {start|stop}"
+                exit 1
+                ;;
+        esac
+
+exit 0
+