vpn: Make domain parameter optional
[platform/upstream/connman.git] / unit / test-iptables.c
index e378a90..cd261d0 100644 (file)
@@ -1,8 +1,8 @@
 /*
+ *  ConnMan firewall unit tests
  *
- *  Connection Manager
- *
- *  Copyright (C) 2013  BWM CarIT GmbH. All rights reserved.
+ *  Copyright (C) 2019 Jolla Ltd. All rights reserved.
+ *  Contact: jussi.laakkonen@jolla.com
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License version 2 as
  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program; if not, write to the Free Software
- *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
- *
  */
 
 #ifdef HAVE_CONFIG_H
 #endif
 
 #include <glib.h>
+#include <gdbus.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <xtables.h>
+#include <errno.h>
+#include <sys/wait.h>
+
+#include "src/connman.h"
+
+enum configtype {
+       TEST_CONFIG_PASS =                      0x0001,
+       TEST_CONFIG_INIT_FAIL =                 0x0002,
+       TEST_CONFIG_FIND_MATCH_FAIL =           0x0004,
+       TEST_CONFIG_FIND_TARGET_FAIL =          0x0008,
+       TEST_CONFIG_PARSE_PROTOCOL_FAIL =       0x0010,
+       TEST_CONFIG_MFCALL_FAIL =               0x0020,
+       TEST_CONFIG_TFCALL_FAIL =               0x0040,
+       TEST_CONFIG_MPCALL_FAIL =               0x0080,
+       TEST_CONFIG_TPCALL_FAIL =               0x0100,
+       TEST_CONFIG_INSMOD_FAIL =               0x0200,
+       TEST_CONFIG_COMPATIBLE_REV_FAIL =       0x0400,
+       TEST_CONFIG_OPTIONS_XFRM_FAIL =         0x0800,
+       TEST_CONFIG_MERGE_OPTIONS_FAIL =        0x1000,
+};
 
-#include "../src/connman.h"
+enum configtype test_config_type = TEST_CONFIG_PASS;
 
-static void test_iptables_chain0(void)
+static void set_test_config(enum configtype type)
 {
-       int err;
+       test_config_type = type;
+}
 
-       err = __connman_iptables_new_chain("filter", "foo");
-       g_assert(err == 0);
+/* Start of dummies */
 
-       err = __connman_iptables_commit("filter");
-       g_assert(err == 0);
+/* xtables dummies */
 
-       err = __connman_iptables_delete_chain("filter", "foo");
-       g_assert(err == 0);
+/* From /usr/include/linux/netfilter_ipv4/ip_tables.h */
+#define IPT_BASE_CTL                   64
+#define IPT_SO_SET_REPLACE             (IPT_BASE_CTL)
+#define IPT_SO_SET_ADD_COUNTERS                (IPT_BASE_CTL + 1)
+#define IPT_SO_GET_INFO                        (IPT_BASE_CTL)
+#define IPT_SO_GET_ENTRIES             (IPT_BASE_CTL + 1)
 
-       err = __connman_iptables_commit("filter");
-       g_assert(err == 0);
+/* From /usr/include/linux/netfilter_ipv6/ip6_tables.h */
+#define IP6T_BASE_CTL                  64
+#define IP6T_SO_SET_REPLACE            (IP6T_BASE_CTL)
+#define IP6T_SO_SET_ADD_COUNTERS       (IP6T_BASE_CTL + 1)
+#define IP6T_SO_GET_INFO               (IP6T_BASE_CTL)
+#define IP6T_SO_GET_ENTRIES            (IP6T_BASE_CTL + 1)
+
+int xt_match_parse(int c, char **argv, int invert, unsigned int *flags,
+                       const void *entry, struct xt_entry_match **match)
+{
+       return 0;
 }
 
-static void test_iptables_chain1(void)
+int xt_target_parse(int c, char **argv, int invert, unsigned int *flags,
+                       const void *entry, struct xt_entry_target **targetinfo)
 {
-       int err;
+       return 0;
+}
 
-       err = __connman_iptables_new_chain("filter", "foo");
-       g_assert(err == 0);
+static void xt_x6_parse(struct xt_option_call *opt) {
+       return;
+}
 
-       err = __connman_iptables_commit("filter");
-       g_assert(err == 0);
+static void xt_x6_fcheck(struct xt_fcheck_call *call) {
+       return;
+}
 
-       err = __connman_iptables_flush_chain("filter", "foo");
-       g_assert(err == 0);
+static struct xtables_match xt_match = {
+       .version = "1",
+       .next = NULL,
+       .name = "tcp",
+       .real_name = "tcp",
+       .revision = 1,
+       .ext_flags = 0,
+       .family = AF_INET,
+       .size = XT_ALIGN(sizeof(struct xtables_match)),
+       .userspacesize = XT_ALIGN(sizeof(struct xtables_match)),
+       .parse = xt_match_parse,
+       .extra_opts = NULL,
+       .x6_parse = xt_x6_parse,
+       .x6_fcheck = xt_x6_fcheck,
+       .x6_options = NULL,
+       .udata_size = XT_ALIGN(sizeof(struct xtables_match)),
+       .udata = NULL,
+       .option_offset = 32,
+       .m = NULL,
+       .mflags = 0,
+       .loaded = 1,
+};
 
-       err = __connman_iptables_commit("filter");
-       g_assert(err == 0);
+static struct xtables_target xt_target = {
+       .version = "1",
+       .next = NULL,
+       .name = "ACCEPT",
+       .real_name = "ACCEPT",
+       .revision = 1,
+       .ext_flags = 0,
+       .family = AF_INET,
+       .size = XT_ALIGN(sizeof(struct xtables_match)),
+       .userspacesize = XT_ALIGN(sizeof(struct xtables_match)),
+       .parse = xt_target_parse,
+       .extra_opts = NULL,
+       .x6_parse = xt_x6_parse,
+       .x6_fcheck = xt_x6_fcheck,
+       .x6_options = NULL,
+       .udata_size = XT_ALIGN(sizeof(struct xtables_match)),
+       .udata = NULL,
+       .option_offset = 32,
+       .t = NULL,
+       .tflags = 0,
+       .used = 0,
+       .loaded = 1,
+};
+
+struct xtables_globals *xt_params = NULL;
+
+struct xtables_match *xtables_matches = NULL;
+struct xtables_target *xtables_targets = NULL;
+
+static void call_error(const char *src)
+{
+       g_assert(xt_params);
 
-       err = __connman_iptables_delete_chain("filter", "foo");
-       g_assert(err == 0);
+       DBG("%s", src);
 
-       err = __connman_iptables_commit("filter");
-       g_assert(err == 0);
+       xt_params->exit_err(PARAMETER_PROBLEM, "longjmp() %s", src);
 }
 
-static void test_iptables_chain2(void)
+int xtables_init_all(struct xtables_globals *xtp, uint8_t nfproto)
 {
-       int err;
+       DBG("%d", nfproto);
 
-       err = __connman_iptables_change_policy("filter", "INPUT", "DROP");
-       g_assert(err == 0);
+       if (test_config_type & TEST_CONFIG_INIT_FAIL)
+               call_error("xtables_init_all");
 
-       err = __connman_iptables_commit("filter");
-       g_assert(err == 0);
+       xt_params = xtp;
 
-       err = __connman_iptables_change_policy("filter", "INPUT", "ACCEPT");
-       g_assert(err == 0);
+       return 0;
+}
+
+struct xtables_match *xtables_find_match(const char *name,
+                       enum xtables_tryload tryload,
+                       struct xtables_rule_match **matches)
+{
+       DBG("name %s type %d", name, tryload);
+
+       if (test_config_type & TEST_CONFIG_FIND_MATCH_FAIL)
+               call_error("xtables_find_match");
 
-       err = __connman_iptables_commit("filter");
-       g_assert(err == 0);
+       *matches = g_try_new0(struct xtables_rule_match, 1);
+       (*matches)->next = NULL;
+       (*matches)->match = &xt_match;
+       (*matches)->completed = 0;
+
+       return &xt_match;
 }
 
-static void test_iptables_rule0(void)
+struct xtables_target *xtables_find_target(const char *name,
+                       enum xtables_tryload tryload)
 {
-       int err;
+       DBG("name %s type %d", name, tryload);
+
+       if (test_config_type & TEST_CONFIG_FIND_TARGET_FAIL)
+               call_error("xtables_find_target");
 
-       /* Test simple appending and removing a rule */
+       return &xt_target;
+}
 
-       err = __connman_iptables_append("filter", "INPUT",
-                                       "-m mark --mark 1 -j LOG");
-       g_assert(err == 0);
+uint16_t xtables_parse_protocol(const char *s)
+{
+       DBG("protocol %s", s);
 
-       err = __connman_iptables_commit("filter");
-       g_assert(err == 0);
+       if (test_config_type & TEST_CONFIG_PARSE_PROTOCOL_FAIL)
+               call_error("xtables_parse_protocol");
 
-       err = __connman_iptables_delete("filter", "INPUT",
-                                       "-m mark --mark 1 -j LOG");
-       g_assert(err == 0);
+       if (!g_strcmp0(s, "tcp"))
+               return 6;
 
-       err = __connman_iptables_commit("filter");
-       g_assert(err == 0);
+       return 0;
 }
 
+void xtables_option_mfcall(struct xtables_match *m)
+{
+       DBG("");
+
+       if (test_config_type & TEST_CONFIG_MFCALL_FAIL)
+               call_error("xtables_option_mfcall");
 
-static void test_iptables_rule1(void)
+       m = &xt_match;
+
+       return;
+}
+
+void xtables_option_tfcall(struct xtables_target *t)
 {
-       int err;
+       DBG("");
+
+       if (test_config_type & TEST_CONFIG_TFCALL_FAIL)
+               call_error("xtables_option_tfcall");
 
-       /* Test if we can do NAT stuff */
+       t = &xt_target;
 
-       err = __connman_iptables_append("nat", "POSTROUTING",
-                               "-s 10.10.1.0/24 -o eth0 -j MASQUERADE");
+       return;
+}
 
-       err = __connman_iptables_commit("nat");
-       g_assert(err == 0);
+void xtables_option_mpcall(unsigned int c, char **argv, bool invert,
+                       struct xtables_match *m, void *fw)
+{
+       DBG("");
 
-       err = __connman_iptables_delete("nat", "POSTROUTING",
-                               "-s 10.10.1.0/24 -o eth0 -j MASQUERADE");
+       if (test_config_type & TEST_CONFIG_MPCALL_FAIL)
+               call_error("xtables_option_mpcall");
 
-       err = __connman_iptables_commit("nat");
-       g_assert(err == 0);
+       m = &xt_match;
+
+       return;
 }
 
-static void test_iptables_rule2(void)
+void xtables_option_tpcall(unsigned int c, char **argv, bool invert,
+                       struct xtables_target *t, void *fw)
 {
-       int err;
+       DBG("");
 
-       /* Test if the right rule is removed */
+       if (test_config_type & TEST_CONFIG_TPCALL_FAIL)
+               call_error("xtables_option_tpcall");
 
-       err = __connman_iptables_append("filter", "INPUT",
-                                       "-m mark --mark 1 -j LOG");
-       g_assert(err == 0);
+       t = &xt_target;
 
-       err = __connman_iptables_commit("filter");
-       g_assert(err == 0);
+       return;
+}
 
-       err = __connman_iptables_append("filter", "INPUT",
-                                       "-m mark --mark 2 -j LOG");
-       g_assert(err == 0);
+int xtables_insmod(const char *modname, const char *modprobe, bool quiet)
+{
+       DBG("mod %s modprobe %s quiet %s", modname, modprobe,
+                               quiet ? "true" : "false");
 
-       err = __connman_iptables_commit("filter");
-       g_assert(err == 0);
+       if (test_config_type & TEST_CONFIG_INSMOD_FAIL)
+               call_error("xtables_insmod");
 
-       err = __connman_iptables_delete("filter", "INPUT",
-                                       "-m mark --mark 2 -j LOG");
-       g_assert(err == 0);
+       return 0;
+}
 
-       err = __connman_iptables_commit("filter");
-       g_assert(err == 0);
+int xtables_compatible_revision(const char *name, uint8_t revision, int opt)
+{
+       DBG("name %s rev %d opt %d", name, revision, opt);
 
-       err = __connman_iptables_delete("filter", "INPUT",
-                                       "-m mark --mark 1 -j LOG");
-       g_assert(err == 0);
+       if (test_config_type & TEST_CONFIG_COMPATIBLE_REV_FAIL)
+               call_error("xtables_compatible_revision");
 
-       err = __connman_iptables_commit("filter");
-       g_assert(err == 0);
+       return 1;
 }
 
-struct connman_notifier *nat_notifier;
+struct option *xtables_options_xfrm(struct option *opt1, struct option *opt2,
+                                       const struct xt_option_entry *entry,
+                                       unsigned int *dummy)
+{
+       if (test_config_type & TEST_CONFIG_OPTIONS_XFRM_FAIL)
+               call_error("xtables_options_xfrm");
 
-struct connman_service {
-       char *dummy;
-};
+       return opt1;
+}
 
-char *connman_service_get_interface(struct connman_service *service)
+struct option *xtables_merge_options(struct option *orig_opts,
+                       struct option *oldopts, const struct option *newopts,
+                       unsigned int *option_offset)
 {
-       return "eth0";
+       if (test_config_type & TEST_CONFIG_MERGE_OPTIONS_FAIL)
+               call_error("xtables_merge_options");
+
+       return orig_opts;
 }
 
-int connman_notifier_register(struct connman_notifier *notifier)
+/* End of xtables dummies */
+
+/* socket dummies */
+
+int global_sockfd = 1000;
+
+int socket(int domain, int type, int protocol)
 {
-       nat_notifier = notifier;
+       DBG("domain %d type %d protocol %d", domain, type, protocol);
 
+       return global_sockfd;
+}
+
+int getsockopt(int sockfd, int level, int optname, void *optval,
+                       socklen_t *optlen)
+{
+       struct ipt_getinfo *info = NULL;
+       struct ipt_get_entries *entries = NULL;
+       struct ip6t_getinfo *info6 = NULL;
+       struct ip6t_get_entries *entries6 = NULL;
+
+       DBG("");
+
+       g_assert_cmpint(global_sockfd, ==, sockfd);
+
+       switch (level) {
+       case IPPROTO_IP:
+               DBG("IPPROTO_IP");
+
+               switch (optname) {
+               case IPT_SO_GET_ENTRIES:
+                       DBG("IPT_SO_GET_ENTRIES");
+                       optval = entries;
+                       break;
+               case IPT_SO_GET_INFO:
+                       DBG("IPT_SO_GET_INFO");
+                       optval = info;
+                       break;
+               default:
+                       DBG("optname %d", optname);
+                       return -1;
+               }
+
+               break;
+       case IPPROTO_IPV6:
+               DBG("IPPROTO_IPV6");
+               switch (optname) {
+               case IP6T_SO_GET_ENTRIES:
+                       DBG("IP6T_SO_GET_ENTRIES");
+                       optval = entries6;
+                       break;
+               case IP6T_SO_GET_INFO:
+                       DBG("IP6T_SO_GET_INFO");
+                       optval = info6;
+                       break;
+               default:
+                       DBG("optname %d", optname);
+                       return -1;
+               }
+
+               break;
+       default:
+               return -1;
+       }
+
+       *optlen = 0;
        return 0;
 }
 
-void connman_notifier_unregister(struct connman_notifier *notifier)
+int setsockopt(int sockfd, int level, int optname, const void *optval,
+                       socklen_t optlen)
 {
-       nat_notifier = NULL;
+       DBG("");
+
+       g_assert_cmpint(global_sockfd, ==, sockfd);
+
+       switch (level) {
+       case IPPROTO_IP:
+               DBG("IPPROTO_IP");
+               switch (optname) {
+               case IPT_SO_SET_REPLACE:
+                       DBG("IPT_SO_SET_REPLACE");
+                       return 0;
+               case IPT_SO_SET_ADD_COUNTERS:
+                       DBG("IPT_SO_SET_ADD_COUNTERS");
+                       return 0;
+               default:
+                       DBG("optname %d", optname);
+                       return -1;
+               }
+
+               break;
+       case IPPROTO_IPV6:
+               DBG("IPPROTO_IPV6");
+
+               switch (optname) {
+               case IP6T_SO_SET_REPLACE:
+                       DBG("IP6T_SO_SET_REPLACE");
+                       return 0;
+               case IP6T_SO_SET_ADD_COUNTERS:
+                       DBG("IP6T_SO_SET_ADD_COUNTERS");
+                       return 0;
+               default:
+                       DBG("optname %d", optname);
+                       return -1;
+               }
+
+               break;
+       default:
+               return -1;
+       }
+}
+
+/* End of socket dummies */
+
+/* End of dummies */
+
+static void iptables_test_basic0()
+{
+       set_test_config(TEST_CONFIG_PASS);
+
+       __connman_iptables_init();
+
+       g_assert(!__connman_iptables_new_chain(AF_INET, "filter", "INPUT"));
+       g_assert_cmpint(__connman_iptables_insert(AF_INET, "filter", "INPUT",
+                               "-p tcp -m tcp --dport 42 -j ACCEPT"), ==, 0);
+
+       __connman_iptables_cleanup();
+}
+
+/*
+ * These ok0...ok6 tests test the error handling. The setjmp() position is set
+ * properly for the functions that will trigger it and as a result, depending on
+ * iptables.c, there will be an error or no error at all. Each of these should
+ * return gracefully without calling exit().
+ */
+
+static void iptables_test_jmp_ok0()
+{
+       set_test_config(TEST_CONFIG_FIND_MATCH_FAIL);
+
+       __connman_iptables_init();
+
+       g_assert(!__connman_iptables_new_chain(AF_INET, "filter", "INPUT"));
+       g_assert_cmpint(__connman_iptables_insert(AF_INET, "filter", "INPUT",
+                               "-p tcp -m tcp -j ACCEPT"), ==, -EINVAL);
+
+       __connman_iptables_cleanup();
 }
 
-static void test_nat_basic0(void)
+static void iptables_test_jmp_ok1()
 {
-       int err;
+       set_test_config(TEST_CONFIG_FIND_TARGET_FAIL);
+
+       __connman_iptables_init();
+
+       g_assert(!__connman_iptables_new_chain(AF_INET, "filter", "INPUT"));
+       g_assert_cmpint(__connman_iptables_insert(AF_INET, "filter", "INPUT",
+                               "-p tcp -m tcp -j ACCEPT"), ==, -EINVAL);
 
-       err = __connman_nat_enable("bridge", "192.168.2.1", 24);
-       g_assert(err == 0);
+       __connman_iptables_cleanup();
+}
+
+static void iptables_test_jmp_ok2()
+{
+       set_test_config(TEST_CONFIG_PARSE_PROTOCOL_FAIL);
 
-       /* test that table is empty */
-       err = __connman_iptables_append("nat", "POSTROUTING",
-                                       "-s 192.168.2.1/24 -o eth0 -j MASQUERADE");
-       g_assert(err == 0);
+       __connman_iptables_init();
 
-       err = __connman_iptables_commit("nat");
-       g_assert(err == 0);
+       g_assert(!__connman_iptables_new_chain(AF_INET, "filter", "INPUT"));
+       g_assert_cmpint(__connman_iptables_insert(AF_INET, "filter", "INPUT",
+                               "-p tcp -m tcp --dport 42 -j ACCEPT"), ==,
+                               -EINVAL);
 
-       __connman_nat_disable("bridge");
+       __connman_iptables_cleanup();
 }
 
-static void test_nat_basic1(void)
+static void iptables_test_jmp_ok3()
 {
-       struct connman_service *service;
-       int err;
+       set_test_config(TEST_CONFIG_TFCALL_FAIL);
 
-       service = g_try_new0(struct connman_service, 1);
-       g_assert(service);
+       __connman_iptables_init();
 
-       nat_notifier->default_changed(service);
+       g_assert(!__connman_iptables_new_chain(AF_INET, "filter", "INPUT"));
+       g_assert_cmpint(__connman_iptables_insert(AF_INET, "filter", "INPUT",
+                               "-p tcp -m tcp --dport 42 -j ACCEPT"), ==,
+                               -EINVAL);
 
-       err = __connman_nat_enable("bridge", "192.168.2.1", 24);
-       g_assert(err == 0);
+       __connman_iptables_cleanup();
+}
 
-       /* test that table is not empty */
-       err = __connman_iptables_append("nat", "POSTROUTING",
-                                       "-s 192.168.2.1/24 -o eth0 -j MASQUERADE");
-       g_assert(err == 0);
+static void iptables_test_jmp_ok4()
+{
+       set_test_config(TEST_CONFIG_MFCALL_FAIL);
 
-       err = __connman_iptables_commit("nat");
-       g_assert(err == 0);
+       __connman_iptables_init();
 
-       __connman_nat_disable("bridge");
+       g_assert(!__connman_iptables_new_chain(AF_INET, "filter", "INPUT"));
 
-       /* test that table is empty again */
-       err = __connman_iptables_delete("nat", "POSTROUTING",
-                                       "-s 192.168.2.1/24 -o eth0 -j MASQUERADE");
-       g_assert(err == 0);
+       g_assert_cmpint(__connman_iptables_insert(AF_INET, "filter", "INPUT",
+                               "-p tcp -m tcp --dport 42 -j ACCEPT"), ==,
+                               -EINVAL);
 
-       err = __connman_iptables_commit("nat");
-       g_assert(err == 0);
+       __connman_iptables_cleanup();
 }
 
-int main(int argc, char *argv[])
+static void iptables_test_jmp_ok5()
 {
-       int err;
+       set_test_config(TEST_CONFIG_TPCALL_FAIL);
 
-       g_test_init(&argc, &argv, NULL);
+       __connman_iptables_init();
+
+       g_assert(!__connman_iptables_new_chain(AF_INET, "filter", "INPUT"));
+
+       g_assert_cmpint(__connman_iptables_insert(AF_INET, "filter", "INPUT",
+                               "-p tcp -m tcp --dport 42 -j ACCEPT "
+                               "--comment test"), ==, -EINVAL);
+
+       __connman_iptables_cleanup();
+}
+
+static void iptables_test_jmp_ok6()
+{
+       set_test_config(TEST_CONFIG_MPCALL_FAIL);
 
-       __connman_log_init(argv[0], "*", FALSE, FALSE,
-                       "Unit Tests Connection Manager", VERSION);
        __connman_iptables_init();
-       __connman_nat_init();
 
-       g_test_add_func("/iptables/chain0", test_iptables_chain0);
-       g_test_add_func("/iptables/chain1", test_iptables_chain1);
-       g_test_add_func("/iptables/chain2", test_iptables_chain2);
-       g_test_add_func("/iptables/rule0",  test_iptables_rule0);
-       g_test_add_func("/iptables/rule1",  test_iptables_rule1);
-       g_test_add_func("/iptables/rule2",  test_iptables_rule2);
-       g_test_add_func("/nat/basic0", test_nat_basic0);
-       g_test_add_func("/nat/basic1", test_nat_basic1);
+       g_assert(!__connman_iptables_new_chain(AF_INET, "filter", "INPUT"));
 
-       err = g_test_run();
+       g_assert_cmpint(__connman_iptables_insert(AF_INET, "filter", "INPUT",
+                               "-p tcp -m tcp --dport 42 -j ACCEPT"), ==,
+                               -EINVAL);
 
-       __connman_nat_cleanup();
        __connman_iptables_cleanup();
+}
 
-       return err;
+/*
+ * These exit0...exit2 tests invoke longjmp() via xtables exit_err() without
+ * having env saved with setjmp(). All of these will result calling exit(), thus
+ * forking is required.
+ */
+
+static void iptables_test_jmp_exit0()
+{
+       pid_t cpid = 0;
+       int cstatus = 0;
+
+       /*
+        * Should work as normal but fork() is needed as exit() is called
+        * when longjmp() is not allowed. At xtables_init_all() exit_err() is
+        * not normally called.
+        */
+       set_test_config(TEST_CONFIG_INIT_FAIL);
+
+       /* Child, run iptables test */
+       if (fork() == 0) {
+               __connman_iptables_init();
+
+               /*
+                * Address family must be different from previous use because
+                * otherwise xtables_init_all() is not called.
+                */
+               g_assert(!__connman_iptables_new_chain(AF_INET6, "filter",
+                                       "INPUT"));
+
+               __connman_iptables_cleanup();
+               exit(0);
+       } else {
+               cpid = wait(&cstatus); /* Wait for child */
+       }
+
+       DBG("parent %d child %d exit %d", getpid(), cpid, WEXITSTATUS(cstatus));
+
+       g_assert(WIFEXITED(cstatus));
+       g_assert_cmpint(WEXITSTATUS(cstatus), ==, PARAMETER_PROBLEM);
 }
+
+static void iptables_test_jmp_exit1()
+{
+       pid_t cpid = 0;
+       int cstatus = 0;
+
+       /*
+        * Should work as normal but fork() is needed as exit() is called
+        * when longjmp() is not allowed. At xtables_insmod() exit_err() is not
+        * normally called.
+        */
+       set_test_config(TEST_CONFIG_INSMOD_FAIL);
+
+       if (fork() == 0) {
+               __connman_iptables_init();
+
+               g_assert(!__connman_iptables_new_chain(AF_INET, "filter",
+                                       "INPUT"));
+
+               __connman_iptables_cleanup();
+               exit(0);
+       } else {
+               cpid = wait(&cstatus);
+       }
+
+       DBG("parent %d child %d exit %d", getpid(), cpid, WEXITSTATUS(cstatus));
+
+       g_assert(WIFEXITED(cstatus));
+       g_assert_cmpint(WEXITSTATUS(cstatus), ==, PARAMETER_PROBLEM);
+}
+
+static void iptables_test_jmp_exit2()
+{
+       pid_t cpid = 0;
+       int cstatus = 0;
+
+       set_test_config(TEST_CONFIG_OPTIONS_XFRM_FAIL|
+                               TEST_CONFIG_MERGE_OPTIONS_FAIL|
+                               TEST_CONFIG_COMPATIBLE_REV_FAIL);
+
+       if (fork() == 0) {
+               __connman_iptables_init();
+
+               g_assert(!__connman_iptables_new_chain(AF_INET, "filter",
+                                       "INPUT"));
+               g_assert_cmpint(__connman_iptables_insert(AF_INET, "filter",
+                                       "INPUT", "-p tcp -m tcp --dport 42 "
+                                       "-j ACCEPT --comment test"), ==, 0);
+
+               __connman_iptables_cleanup();
+               exit(0);
+       } else {
+               cpid = wait(&cstatus);
+       }
+
+       DBG("parent %d child %d exit %d", getpid(), cpid, WEXITSTATUS(cstatus));
+
+       g_assert(WIFEXITED(cstatus));
+       g_assert_cmpint(WEXITSTATUS(cstatus), ==, PARAMETER_PROBLEM);
+}
+
+static gchar *option_debug = NULL;
+
+static bool parse_debug(const char *key, const char *value,
+                                       gpointer user_data, GError **error)
+{
+       if (value)
+               option_debug = g_strdup(value);
+       else
+               option_debug = g_strdup("*");
+
+       return true;
+}
+
+static GOptionEntry options[] = {
+       { "debug", 'd', G_OPTION_FLAG_OPTIONAL_ARG,
+                               G_OPTION_ARG_CALLBACK, parse_debug,
+                               "Specify debug options to enable", "DEBUG" },
+       { NULL },
+};
+
+int main (int argc, char *argv[])
+{
+       GOptionContext *context;
+       GError *error = NULL;
+
+       g_test_init(&argc, &argv, NULL);
+
+       context = g_option_context_new(NULL);
+       g_option_context_add_main_entries(context, options, NULL);
+
+       if (!g_option_context_parse(context, &argc, &argv, &error)) {
+               if (error) {
+                       g_printerr("%s\n", error->message);
+                       g_error_free(error);
+               } else
+                       g_printerr("An unknown error occurred\n");
+               return 1;
+       }
+
+       g_option_context_free(context);
+
+       __connman_log_init(argv[0], option_debug, false, false,
+                       "Unit Tests Connection Manager", VERSION);
+
+       g_test_add_func("/iptables/test_basic0", iptables_test_basic0);
+       g_test_add_func("/iptables/test_jmp_ok0", iptables_test_jmp_ok0);
+       g_test_add_func("/iptables/test_jmp_ok1", iptables_test_jmp_ok1);
+       g_test_add_func("/iptables/test_jmp_ok2", iptables_test_jmp_ok2);
+       g_test_add_func("/iptables/test_jmp_ok3", iptables_test_jmp_ok3);
+       g_test_add_func("/iptables/test_jmp_ok4", iptables_test_jmp_ok4);
+       g_test_add_func("/iptables/test_jmp_ok5", iptables_test_jmp_ok5);
+       g_test_add_func("/iptables/test_jmp_ok6", iptables_test_jmp_ok6);
+       g_test_add_func("/iptables/test_jmp_exit0", iptables_test_jmp_exit0);
+       g_test_add_func("/iptables/test_jmp_exit1", iptables_test_jmp_exit1);
+       g_test_add_func("/iptables/test_jmp_exit2", iptables_test_jmp_exit2);
+
+       return g_test_run();
+}
+
+/*
+ * Local Variables:
+ * mode: C
+ * c-basic-offset: 8
+ * indent-tabs-mode: t
+ * End:
+ */