Merge "Merge tag 'upstream/1.41' into tizen" into tizen
[platform/upstream/connman.git] / unit / test-iptables.c
1 /*
2  *  ConnMan firewall unit tests
3  *
4  *  Copyright (C) 2019 Jolla Ltd. All rights reserved.
5  *  Contact: jussi.laakkonen@jolla.com
6  *
7  *  This program is free software; you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License version 2 as
9  *  published by the Free Software Foundation.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  */
16
17 #ifdef HAVE_CONFIG_H
18 #include <config.h>
19 #endif
20
21 #include <glib.h>
22 #include <gdbus.h>
23 #include <unistd.h>
24 #include <stdlib.h>
25 #include <xtables.h>
26 #include <errno.h>
27 #include <sys/wait.h>
28
29 #include "src/connman.h"
30
31 enum configtype {
32         TEST_CONFIG_PASS =                      0x0001,
33         TEST_CONFIG_INIT_FAIL =                 0x0002,
34         TEST_CONFIG_FIND_MATCH_FAIL =           0x0004,
35         TEST_CONFIG_FIND_TARGET_FAIL =          0x0008,
36         TEST_CONFIG_PARSE_PROTOCOL_FAIL =       0x0010,
37         TEST_CONFIG_MFCALL_FAIL =               0x0020,
38         TEST_CONFIG_TFCALL_FAIL =               0x0040,
39         TEST_CONFIG_MPCALL_FAIL =               0x0080,
40         TEST_CONFIG_TPCALL_FAIL =               0x0100,
41         TEST_CONFIG_INSMOD_FAIL =               0x0200,
42         TEST_CONFIG_COMPATIBLE_REV_FAIL =       0x0400,
43         TEST_CONFIG_OPTIONS_XFRM_FAIL =         0x0800,
44         TEST_CONFIG_MERGE_OPTIONS_FAIL =        0x1000,
45 };
46
47 enum configtype test_config_type = TEST_CONFIG_PASS;
48
49 static void set_test_config(enum configtype type)
50 {
51         test_config_type = type;
52 }
53
54 /* Start of dummies */
55
56 /* xtables dummies */
57
58 /* From /usr/include/linux/netfilter_ipv4/ip_tables.h */
59 #define IPT_BASE_CTL                    64
60 #define IPT_SO_SET_REPLACE              (IPT_BASE_CTL)
61 #define IPT_SO_SET_ADD_COUNTERS         (IPT_BASE_CTL + 1)
62 #define IPT_SO_GET_INFO                 (IPT_BASE_CTL)
63 #define IPT_SO_GET_ENTRIES              (IPT_BASE_CTL + 1)
64
65 /* From /usr/include/linux/netfilter_ipv6/ip6_tables.h */
66 #define IP6T_BASE_CTL                   64
67 #define IP6T_SO_SET_REPLACE             (IP6T_BASE_CTL)
68 #define IP6T_SO_SET_ADD_COUNTERS        (IP6T_BASE_CTL + 1)
69 #define IP6T_SO_GET_INFO                (IP6T_BASE_CTL)
70 #define IP6T_SO_GET_ENTRIES             (IP6T_BASE_CTL + 1)
71
72 int static xt_match_parse(int c, char **argv, int invert, unsigned int *flags,
73                         const void *entry, struct xt_entry_match **match)
74 {
75         return 0;
76 }
77
78 int static xt_target_parse(int c, char **argv, int invert, unsigned int *flags,
79                         const void *entry, struct xt_entry_target **targetinfo)
80 {
81         return 0;
82 }
83
84 static void xt_x6_parse(struct xt_option_call *opt) {
85         return;
86 }
87
88 static void xt_x6_fcheck(struct xt_fcheck_call *call) {
89         return;
90 }
91
92 static struct xtables_match xt_match = {
93         .version = "1",
94         .next = NULL,
95         .name = "tcp",
96         .real_name = "tcp",
97         .revision = 1,
98         .ext_flags = 0,
99         .family = AF_INET,
100         .size = XT_ALIGN(sizeof(struct xtables_match)),
101         .userspacesize = XT_ALIGN(sizeof(struct xtables_match)),
102         .parse = xt_match_parse,
103         .extra_opts = NULL,
104         .x6_parse = xt_x6_parse,
105         .x6_fcheck = xt_x6_fcheck,
106         .x6_options = NULL,
107         .udata_size = XT_ALIGN(sizeof(struct xtables_match)),
108         .udata = NULL,
109         .option_offset = 32,
110         .m = NULL,
111         .mflags = 0,
112         .loaded = 1,
113 };
114
115 static struct xtables_target xt_target = {
116         .version = "1",
117         .next = NULL,
118         .name = "ACCEPT",
119         .real_name = "ACCEPT",
120         .revision = 1,
121         .ext_flags = 0,
122         .family = AF_INET,
123         .size = XT_ALIGN(sizeof(struct xtables_match)),
124         .userspacesize = XT_ALIGN(sizeof(struct xtables_match)),
125         .parse = xt_target_parse,
126         .extra_opts = NULL,
127         .x6_parse = xt_x6_parse,
128         .x6_fcheck = xt_x6_fcheck,
129         .x6_options = NULL,
130         .udata_size = XT_ALIGN(sizeof(struct xtables_match)),
131         .udata = NULL,
132         .option_offset = 32,
133         .t = NULL,
134         .tflags = 0,
135         .used = 0,
136         .loaded = 1,
137 };
138
139 struct xtables_globals *xt_params = NULL;
140
141 struct xtables_match *xtables_matches = NULL;
142 struct xtables_target *xtables_targets = NULL;
143
144 static void call_error(const char *src)
145 {
146         g_assert(xt_params);
147
148         DBG("%s", src);
149
150         xt_params->exit_err(PARAMETER_PROBLEM, "longjmp() %s", src);
151 }
152
153 int xtables_init_all(struct xtables_globals *xtp, uint8_t nfproto)
154 {
155         DBG("%d", nfproto);
156
157         if (test_config_type & TEST_CONFIG_INIT_FAIL)
158                 call_error("xtables_init_all");
159
160         xt_params = xtp;
161
162         return 0;
163 }
164
165 struct xtables_match *xtables_find_match(const char *name,
166                         enum xtables_tryload tryload,
167                         struct xtables_rule_match **matches)
168 {
169         DBG("name %s type %d", name, tryload);
170
171         if (test_config_type & TEST_CONFIG_FIND_MATCH_FAIL)
172                 call_error("xtables_find_match");
173
174         *matches = g_try_new0(struct xtables_rule_match, 1);
175         (*matches)->next = NULL;
176         (*matches)->match = &xt_match;
177         (*matches)->completed = 0;
178
179         return &xt_match;
180 }
181
182 struct xtables_target *xtables_find_target(const char *name,
183                         enum xtables_tryload tryload)
184 {
185         DBG("name %s type %d", name, tryload);
186
187         if (test_config_type & TEST_CONFIG_FIND_TARGET_FAIL)
188                 call_error("xtables_find_target");
189
190         return &xt_target;
191 }
192
193 uint16_t xtables_parse_protocol(const char *s)
194 {
195         DBG("protocol %s", s);
196
197         if (test_config_type & TEST_CONFIG_PARSE_PROTOCOL_FAIL)
198                 call_error("xtables_parse_protocol");
199
200         if (!g_strcmp0(s, "tcp"))
201                 return 6;
202
203         return 0;
204 }
205
206 void xtables_option_mfcall(struct xtables_match *m)
207 {
208         DBG("");
209
210         if (test_config_type & TEST_CONFIG_MFCALL_FAIL)
211                 call_error("xtables_option_mfcall");
212
213         m = &xt_match;
214
215         return;
216 }
217
218 void xtables_option_tfcall(struct xtables_target *t)
219 {
220         DBG("");
221
222         if (test_config_type & TEST_CONFIG_TFCALL_FAIL)
223                 call_error("xtables_option_tfcall");
224
225         t = &xt_target;
226
227         return;
228 }
229
230 void xtables_option_mpcall(unsigned int c, char **argv, bool invert,
231                         struct xtables_match *m, void *fw)
232 {
233         DBG("");
234
235         if (test_config_type & TEST_CONFIG_MPCALL_FAIL)
236                 call_error("xtables_option_mpcall");
237
238         m = &xt_match;
239
240         return;
241 }
242
243 void xtables_option_tpcall(unsigned int c, char **argv, bool invert,
244                         struct xtables_target *t, void *fw)
245 {
246         DBG("");
247
248         if (test_config_type & TEST_CONFIG_TPCALL_FAIL)
249                 call_error("xtables_option_tpcall");
250
251         t = &xt_target;
252
253         return;
254 }
255
256 int xtables_insmod(const char *modname, const char *modprobe, bool quiet)
257 {
258         DBG("mod %s modprobe %s quiet %s", modname, modprobe,
259                                 quiet ? "true" : "false");
260
261         if (test_config_type & TEST_CONFIG_INSMOD_FAIL)
262                 call_error("xtables_insmod");
263
264         return 0;
265 }
266
267 int xtables_compatible_revision(const char *name, uint8_t revision, int opt)
268 {
269         DBG("name %s rev %d opt %d", name, revision, opt);
270
271         if (test_config_type & TEST_CONFIG_COMPATIBLE_REV_FAIL)
272                 call_error("xtables_compatible_revision");
273
274         return 1;
275 }
276
277 struct option *xtables_options_xfrm(struct option *opt1, struct option *opt2,
278                                         const struct xt_option_entry *entry,
279                                         unsigned int *dummy)
280 {
281         if (test_config_type & TEST_CONFIG_OPTIONS_XFRM_FAIL)
282                 call_error("xtables_options_xfrm");
283
284         return opt1;
285 }
286
287 struct option *xtables_merge_options(struct option *orig_opts,
288                         struct option *oldopts, const struct option *newopts,
289                         unsigned int *option_offset)
290 {
291         if (test_config_type & TEST_CONFIG_MERGE_OPTIONS_FAIL)
292                 call_error("xtables_merge_options");
293
294         return orig_opts;
295 }
296
297 /* End of xtables dummies */
298
299 /* socket dummies */
300
301 int global_sockfd = 1000;
302
303 int socket(int domain, int type, int protocol)
304 {
305         DBG("domain %d type %d protocol %d", domain, type, protocol);
306
307         return global_sockfd;
308 }
309
310 int getsockopt(int sockfd, int level, int optname, void *optval,
311                         socklen_t *optlen)
312 {
313         struct ipt_getinfo *info = NULL;
314         struct ipt_get_entries *entries = NULL;
315         struct ip6t_getinfo *info6 = NULL;
316         struct ip6t_get_entries *entries6 = NULL;
317
318         DBG("");
319
320         g_assert_cmpint(global_sockfd, ==, sockfd);
321
322         switch (level) {
323         case IPPROTO_IP:
324                 DBG("IPPROTO_IP");
325
326                 switch (optname) {
327                 case IPT_SO_GET_ENTRIES:
328                         DBG("IPT_SO_GET_ENTRIES");
329                         optval = entries;
330                         break;
331                 case IPT_SO_GET_INFO:
332                         DBG("IPT_SO_GET_INFO");
333                         optval = info;
334                         break;
335                 default:
336                         DBG("optname %d", optname);
337                         return -1;
338                 }
339
340                 break;
341         case IPPROTO_IPV6:
342                 DBG("IPPROTO_IPV6");
343                 switch (optname) {
344                 case IP6T_SO_GET_ENTRIES:
345                         DBG("IP6T_SO_GET_ENTRIES");
346                         optval = entries6;
347                         break;
348                 case IP6T_SO_GET_INFO:
349                         DBG("IP6T_SO_GET_INFO");
350                         optval = info6;
351                         break;
352                 default:
353                         DBG("optname %d", optname);
354                         return -1;
355                 }
356
357                 break;
358         default:
359                 return -1;
360         }
361
362         *optlen = 0;
363         return 0;
364 }
365
366 int setsockopt(int sockfd, int level, int optname, const void *optval,
367                         socklen_t optlen)
368 {
369         DBG("");
370
371         g_assert_cmpint(global_sockfd, ==, sockfd);
372
373         switch (level) {
374         case IPPROTO_IP:
375                 DBG("IPPROTO_IP");
376                 switch (optname) {
377                 case IPT_SO_SET_REPLACE:
378                         DBG("IPT_SO_SET_REPLACE");
379                         return 0;
380                 case IPT_SO_SET_ADD_COUNTERS:
381                         DBG("IPT_SO_SET_ADD_COUNTERS");
382                         return 0;
383                 default:
384                         DBG("optname %d", optname);
385                         return -1;
386                 }
387
388                 break;
389         case IPPROTO_IPV6:
390                 DBG("IPPROTO_IPV6");
391
392                 switch (optname) {
393                 case IP6T_SO_SET_REPLACE:
394                         DBG("IP6T_SO_SET_REPLACE");
395                         return 0;
396                 case IP6T_SO_SET_ADD_COUNTERS:
397                         DBG("IP6T_SO_SET_ADD_COUNTERS");
398                         return 0;
399                 default:
400                         DBG("optname %d", optname);
401                         return -1;
402                 }
403
404                 break;
405         default:
406                 return -1;
407         }
408 }
409
410 /* End of socket dummies */
411
412 /* End of dummies */
413
414 static void iptables_test_basic0()
415 {
416         set_test_config(TEST_CONFIG_PASS);
417
418         __connman_iptables_init();
419
420         g_assert(!__connman_iptables_new_chain(AF_INET, "filter", "INPUT"));
421         g_assert_cmpint(__connman_iptables_insert(AF_INET, "filter", "INPUT",
422                                 "-p tcp -m tcp --dport 42 -j ACCEPT"), ==, 0);
423
424         __connman_iptables_cleanup();
425 }
426
427 /*
428  * These ok0...ok6 tests test the error handling. The setjmp() position is set
429  * properly for the functions that will trigger it and as a result, depending on
430  * iptables.c, there will be an error or no error at all. Each of these should
431  * return gracefully without calling exit().
432  */
433
434 static void iptables_test_jmp_ok0()
435 {
436         set_test_config(TEST_CONFIG_FIND_MATCH_FAIL);
437
438         __connman_iptables_init();
439
440         g_assert(!__connman_iptables_new_chain(AF_INET, "filter", "INPUT"));
441         g_assert_cmpint(__connman_iptables_insert(AF_INET, "filter", "INPUT",
442                                 "-p tcp -m tcp -j ACCEPT"), ==, -EINVAL);
443
444         __connman_iptables_cleanup();
445 }
446
447 static void iptables_test_jmp_ok1()
448 {
449         set_test_config(TEST_CONFIG_FIND_TARGET_FAIL);
450
451         __connman_iptables_init();
452
453         g_assert(!__connman_iptables_new_chain(AF_INET, "filter", "INPUT"));
454         g_assert_cmpint(__connman_iptables_insert(AF_INET, "filter", "INPUT",
455                                 "-p tcp -m tcp -j ACCEPT"), ==, -EINVAL);
456
457         __connman_iptables_cleanup();
458 }
459
460 static void iptables_test_jmp_ok2()
461 {
462         set_test_config(TEST_CONFIG_PARSE_PROTOCOL_FAIL);
463
464         __connman_iptables_init();
465
466         g_assert(!__connman_iptables_new_chain(AF_INET, "filter", "INPUT"));
467         g_assert_cmpint(__connman_iptables_insert(AF_INET, "filter", "INPUT",
468                                 "-p tcp -m tcp --dport 42 -j ACCEPT"), ==,
469                                 -EINVAL);
470
471         __connman_iptables_cleanup();
472 }
473
474 static void iptables_test_jmp_ok3()
475 {
476         set_test_config(TEST_CONFIG_TFCALL_FAIL);
477
478         __connman_iptables_init();
479
480         g_assert(!__connman_iptables_new_chain(AF_INET, "filter", "INPUT"));
481         g_assert_cmpint(__connman_iptables_insert(AF_INET, "filter", "INPUT",
482                                 "-p tcp -m tcp --dport 42 -j ACCEPT"), ==,
483                                 -EINVAL);
484
485         __connman_iptables_cleanup();
486 }
487
488 static void iptables_test_jmp_ok4()
489 {
490         set_test_config(TEST_CONFIG_MFCALL_FAIL);
491
492         __connman_iptables_init();
493
494         g_assert(!__connman_iptables_new_chain(AF_INET, "filter", "INPUT"));
495
496         g_assert_cmpint(__connman_iptables_insert(AF_INET, "filter", "INPUT",
497                                 "-p tcp -m tcp --dport 42 -j ACCEPT"), ==,
498                                 -EINVAL);
499
500         __connman_iptables_cleanup();
501 }
502
503 static void iptables_test_jmp_ok5()
504 {
505         set_test_config(TEST_CONFIG_TPCALL_FAIL);
506
507         __connman_iptables_init();
508
509         g_assert(!__connman_iptables_new_chain(AF_INET, "filter", "INPUT"));
510
511         g_assert_cmpint(__connman_iptables_insert(AF_INET, "filter", "INPUT",
512                                 "-p tcp -m tcp --dport 42 -j ACCEPT "
513                                 "--comment test"), ==, -EINVAL);
514
515         __connman_iptables_cleanup();
516 }
517
518 static void iptables_test_jmp_ok6()
519 {
520         set_test_config(TEST_CONFIG_MPCALL_FAIL);
521
522         __connman_iptables_init();
523
524         g_assert(!__connman_iptables_new_chain(AF_INET, "filter", "INPUT"));
525
526         g_assert_cmpint(__connman_iptables_insert(AF_INET, "filter", "INPUT",
527                                 "-p tcp -m tcp --dport 42 -j ACCEPT"), ==,
528                                 -EINVAL);
529
530         __connman_iptables_cleanup();
531 }
532
533 /*
534  * These exit0...exit2 tests invoke longjmp() via xtables exit_err() without
535  * having env saved with setjmp(). All of these will result calling exit(), thus
536  * forking is required.
537  */
538
539 static void iptables_test_jmp_exit0()
540 {
541         pid_t cpid = 0;
542         int cstatus = 0;
543
544         /*
545          * Should work as normal but fork() is needed as exit() is called
546          * when longjmp() is not allowed. At xtables_init_all() exit_err() is
547          * not normally called.
548          */
549         set_test_config(TEST_CONFIG_INIT_FAIL);
550
551         /* Child, run iptables test */
552         if (fork() == 0) {
553                 __connman_iptables_init();
554
555                 /*
556                  * Address family must be different from previous use because
557                  * otherwise xtables_init_all() is not called.
558                  */
559                 g_assert(!__connman_iptables_new_chain(AF_INET6, "filter",
560                                         "INPUT"));
561
562                 __connman_iptables_cleanup();
563                 exit(0);
564         } else {
565                 cpid = wait(&cstatus); /* Wait for child */
566         }
567
568         DBG("parent %d child %d exit %d", getpid(), cpid, WEXITSTATUS(cstatus));
569
570         g_assert(WIFEXITED(cstatus));
571         g_assert_cmpint(WEXITSTATUS(cstatus), ==, PARAMETER_PROBLEM);
572 }
573
574 static void iptables_test_jmp_exit1()
575 {
576         pid_t cpid = 0;
577         int cstatus = 0;
578
579         /*
580          * Should work as normal but fork() is needed as exit() is called
581          * when longjmp() is not allowed. At xtables_insmod() exit_err() is not
582          * normally called.
583          */
584         set_test_config(TEST_CONFIG_INSMOD_FAIL);
585
586         if (fork() == 0) {
587                 __connman_iptables_init();
588
589                 g_assert(!__connman_iptables_new_chain(AF_INET, "filter",
590                                         "INPUT"));
591
592                 __connman_iptables_cleanup();
593                 exit(0);
594         } else {
595                 cpid = wait(&cstatus);
596         }
597
598         DBG("parent %d child %d exit %d", getpid(), cpid, WEXITSTATUS(cstatus));
599
600         g_assert(WIFEXITED(cstatus));
601         g_assert_cmpint(WEXITSTATUS(cstatus), ==, PARAMETER_PROBLEM);
602 }
603
604 static void iptables_test_jmp_exit2()
605 {
606         pid_t cpid = 0;
607         int cstatus = 0;
608
609         set_test_config(TEST_CONFIG_OPTIONS_XFRM_FAIL|
610                                 TEST_CONFIG_MERGE_OPTIONS_FAIL|
611                                 TEST_CONFIG_COMPATIBLE_REV_FAIL);
612
613         if (fork() == 0) {
614                 __connman_iptables_init();
615
616                 g_assert(!__connman_iptables_new_chain(AF_INET, "filter",
617                                         "INPUT"));
618                 g_assert_cmpint(__connman_iptables_insert(AF_INET, "filter",
619                                         "INPUT", "-p tcp -m tcp --dport 42 "
620                                         "-j ACCEPT --comment test"), ==, 0);
621
622                 __connman_iptables_cleanup();
623                 exit(0);
624         } else {
625                 cpid = wait(&cstatus);
626         }
627
628         DBG("parent %d child %d exit %d", getpid(), cpid, WEXITSTATUS(cstatus));
629
630         g_assert(WIFEXITED(cstatus));
631         g_assert_cmpint(WEXITSTATUS(cstatus), ==, PARAMETER_PROBLEM);
632 }
633
634 static gchar *option_debug = NULL;
635
636 static bool parse_debug(const char *key, const char *value,
637                                         gpointer user_data, GError **error)
638 {
639         if (value)
640                 option_debug = g_strdup(value);
641         else
642                 option_debug = g_strdup("*");
643
644         return true;
645 }
646
647 static GOptionEntry options[] = {
648         { "debug", 'd', G_OPTION_FLAG_OPTIONAL_ARG,
649                                 G_OPTION_ARG_CALLBACK, parse_debug,
650                                 "Specify debug options to enable", "DEBUG" },
651         { NULL },
652 };
653
654 int main (int argc, char *argv[])
655 {
656         GOptionContext *context;
657         GError *error = NULL;
658
659         g_test_init(&argc, &argv, NULL);
660
661         context = g_option_context_new(NULL);
662         g_option_context_add_main_entries(context, options, NULL);
663
664         if (!g_option_context_parse(context, &argc, &argv, &error)) {
665                 if (error) {
666                         g_printerr("%s\n", error->message);
667                         g_error_free(error);
668                 } else
669                         g_printerr("An unknown error occurred\n");
670                 return 1;
671         }
672
673         g_option_context_free(context);
674
675         __connman_log_init(argv[0], option_debug, false, false,
676                         "Unit Tests Connection Manager", VERSION);
677
678         g_test_add_func("/iptables/test_basic0", iptables_test_basic0);
679         g_test_add_func("/iptables/test_jmp_ok0", iptables_test_jmp_ok0);
680         g_test_add_func("/iptables/test_jmp_ok1", iptables_test_jmp_ok1);
681         g_test_add_func("/iptables/test_jmp_ok2", iptables_test_jmp_ok2);
682         g_test_add_func("/iptables/test_jmp_ok3", iptables_test_jmp_ok3);
683         g_test_add_func("/iptables/test_jmp_ok4", iptables_test_jmp_ok4);
684         g_test_add_func("/iptables/test_jmp_ok5", iptables_test_jmp_ok5);
685         g_test_add_func("/iptables/test_jmp_ok6", iptables_test_jmp_ok6);
686         g_test_add_func("/iptables/test_jmp_exit0", iptables_test_jmp_exit0);
687         g_test_add_func("/iptables/test_jmp_exit1", iptables_test_jmp_exit1);
688         g_test_add_func("/iptables/test_jmp_exit2", iptables_test_jmp_exit2);
689
690         return g_test_run();
691 }
692
693 /*
694  * Local Variables:
695  * mode: C
696  * c-basic-offset: 8
697  * indent-tabs-mode: t
698  * End:
699  */