iptables: Replace iptables-test program
[framework/connectivity/connman.git] / tools / iptables-test.c
1 /*
2  *  Connection Manager
3  *
4  *  Copyright (C) 2007-2010  Intel Corporation. All rights reserved.
5  *
6  *  This program is free software; you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License version 2 as
8  *  published by the Free Software Foundation.
9  *
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with this program; if not, write to the Free Software
17  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18  *
19  */
20
21 #include <stdlib.h>
22 #include <stdio.h>
23 #include <string.h>
24 #include <unistd.h>
25 #include <sys/errno.h>
26 #include <sys/socket.h>
27 #include <xtables.h>
28
29 #include <linux/netfilter_ipv4/ip_tables.h>
30
31 #include <glib.h>
32
33 static const char *hooknames[] = {
34         [NF_IP_PRE_ROUTING]     = "PREROUTING",
35         [NF_IP_LOCAL_IN]        = "INPUT",
36         [NF_IP_FORWARD]         = "FORWARD",
37         [NF_IP_LOCAL_OUT]       = "OUTPUT",
38         [NF_IP_POST_ROUTING]    = "POSTROUTING",
39 };
40
41 #define LABEL_ACCEPT  "ACCEPT"
42 #define LABEL_DROP    "DROP"
43 #define LABEL_QUEUE   "QUEUE"
44 #define LABEL_RETURN  "RETURN"
45
46 /* fn returns 0 to continue iteration */
47 #define _XT_ENTRY_ITERATE_CONTINUE(type, entries, size, n, fn, args...) \
48 ({                                                              \
49         unsigned int __i;                                       \
50         int __n;                                                \
51         int __ret = 0;                                          \
52         type *__entry;                                          \
53                                                                 \
54         for (__i = 0, __n = 0; __i < (size);                    \
55              __i += __entry->next_offset, __n++) {              \
56                 __entry = (void *)(entries) + __i;              \
57                 if (__n < n)                                    \
58                         continue;                               \
59                                                                 \
60                 __ret = fn(__entry,  ## args);                  \
61                 if (__ret != 0)                                 \
62                         break;                                  \
63         }                                                       \
64         __ret;                                                  \
65 })
66
67 /* fn returns 0 to continue iteration */
68 #define _XT_ENTRY_ITERATE(type, entries, size, fn, args...) \
69         _XT_ENTRY_ITERATE_CONTINUE(type, entries, size, 0, fn, args)
70
71 #define ENTRY_ITERATE(entries, size, fn, args...) \
72         _XT_ENTRY_ITERATE(struct ipt_entry, entries, size, fn, ## args)
73
74 #define MIN_ALIGN (__alignof__(struct ipt_entry))
75
76 #define ALIGN(s) (((s) + ((MIN_ALIGN)-1)) & ~((MIN_ALIGN)-1))
77
78 struct ipt_error_target {
79         struct xt_entry_target t;
80         char error[IPT_TABLE_MAXNAMELEN];
81 };
82
83 struct connman_iptables_entry {
84         int builtin;
85         int std_target;
86         int jump_offset;
87
88         struct ipt_entry *entry;
89 };
90
91 struct connman_iptables {
92         int ipt_sock;
93
94         struct ipt_getinfo *info;
95         struct ipt_get_entries *blob_entries;
96
97         unsigned int num_entries;
98         unsigned int old_entries;
99         unsigned int size;
100
101         GList *entries;
102 };
103
104
105 static struct ipt_entry *get_entry(struct connman_iptables *table,
106                                         unsigned int offset)
107 {
108         return (struct ipt_entry *)((char *)table->blob_entries->entrytable +
109                                                                         offset);
110 }
111
112 static int is_hook_entry(struct connman_iptables *table,
113                                 struct ipt_entry *entry)
114 {
115         unsigned int i;
116
117         for (i = 0; i < NF_INET_NUMHOOKS; i++) {
118                 if ((table->info->valid_hooks & (1 << i))
119                 && get_entry(table, table->info->hook_entry[i]) == entry)
120                         return i;
121         }
122
123         return -1;
124 }
125
126 static unsigned long entry_to_offset(struct connman_iptables *table,
127                                         struct ipt_entry *entry)
128 {
129         return (void *)entry - (void *)table->blob_entries->entrytable;
130 }
131
132 static int target_to_verdict(char *target_name)
133 {
134         if (!strcmp(target_name, LABEL_ACCEPT))
135                 return -NF_ACCEPT - 1;
136
137         if (!strcmp(target_name, LABEL_DROP))
138                 return -NF_DROP - 1;
139
140         if (!strcmp(target_name, LABEL_QUEUE))
141                 return -NF_QUEUE - 1;
142
143         if (!strcmp(target_name, LABEL_RETURN))
144                 return XT_RETURN;
145
146         return 0;
147 }
148
149 static gboolean is_builtin_target(char *target_name)
150 {
151         if (!strcmp(target_name, LABEL_ACCEPT) ||
152                 !strcmp(target_name, LABEL_DROP) ||
153                 !strcmp(target_name, LABEL_QUEUE) ||
154                 !strcmp(target_name, LABEL_RETURN))
155                 return TRUE;
156
157         return FALSE;
158 }
159
160 static gboolean is_chain(struct connman_iptables *table,
161                                 struct connman_iptables_entry *e)
162 {
163         int builtin;
164         struct ipt_entry *entry;
165         struct xt_entry_target *target;
166
167         entry = e->entry;
168         builtin = is_hook_entry(table, entry);
169         if (builtin >= 0)
170                 return TRUE;
171
172         target = ipt_get_target(entry);
173         if (!strcmp(target->u.user.name, IPT_ERROR_TARGET))
174                 return TRUE;
175
176         return FALSE;
177 }
178
179 static GList *find_chain_tail(struct connman_iptables *table,
180                                 char *chain_name)
181 {
182         GList *chain_head, *list;
183         struct connman_iptables_entry *head, *tail;
184         struct ipt_entry *entry;
185         struct xt_entry_target *target;
186         int builtin;
187
188         /* First we look for the head */
189         for (list = table->entries; list; list = list->next) {
190                 head = list->data;
191                 entry = head->entry;
192
193                 /* Buit-in chain */
194                 builtin = is_hook_entry(table, entry);
195                 if (builtin >= 0 && !strcmp(hooknames[builtin], chain_name))
196                         break;
197
198                 /* User defined chain */
199                 target = ipt_get_target(entry);
200                 if (!strcmp(target->u.user.name, IPT_ERROR_TARGET) &&
201                     !strcmp((char *)target->data, chain_name))
202                         break;
203         }
204
205         if (list == NULL)
206                 return NULL;
207
208         chain_head = list;
209
210         /* Then we look for the next chain */
211         for (list = chain_head->next; list; list = list->next) {
212                 tail = list->data;
213                 entry = tail->entry;
214
215                 if (is_chain(table, tail))
216                         return list;
217         }
218
219         /* Nothing found, we return the table end */
220         return g_list_last(table->entries);
221 }
222
223 static int connman_add_entry(struct connman_iptables *table,
224                                 struct ipt_entry *entry, GList *before)
225 {
226         struct connman_iptables_entry *e;
227
228         if (table == NULL)
229                 return -1;
230
231         e = g_try_malloc0(sizeof(struct connman_iptables_entry));
232         if (e == NULL)
233                 return -1;
234
235         e->entry = entry;
236
237         table->entries = g_list_insert_before(table->entries, before, e);
238         table->num_entries++;
239         table->size += entry->next_offset;
240
241         return 0;
242 }
243
244 static int connman_iptables_add_chain(struct connman_iptables *table,
245                                         char *name)
246 {
247         GList *last;
248         struct ipt_entry *entry_head;
249         struct ipt_entry *entry_return;
250         struct ipt_error_target *error;
251         struct ipt_standard_target *standard;
252         u_int16_t entry_head_size, entry_return_size;
253
254         last = g_list_last(table->entries);
255
256         /*
257          * An empty chain is composed of:
258          * - A head entry, with no match and an error target.
259          *   The error target data is the chain name.
260          * - A tail entry, with no match and a standard target.
261          *   The standard target verdict is XT_RETURN (return to the
262          *   caller).
263          */
264
265         /* head entry */
266         entry_head_size = sizeof(struct ipt_entry) +
267                                 sizeof(struct ipt_error_target);
268         entry_head = g_try_malloc0(entry_head_size);
269         if (entry_head == NULL)
270                 goto err;
271
272         memset(entry_head, 0, entry_head_size);
273
274         entry_head->target_offset = sizeof(struct ipt_entry);
275         entry_head->next_offset = entry_head_size;
276
277         error = (struct ipt_error_target *) entry_head->elems;
278         strcpy(error->t.u.user.name, IPT_ERROR_TARGET);
279         error->t.u.user.target_size = ALIGN(sizeof(struct ipt_error_target));
280         strcpy(error->error, name);
281
282         if (connman_add_entry(table, entry_head, last) < 0)
283                 goto err;
284
285         /* tail entry */
286         entry_return_size = sizeof(struct ipt_entry) +
287                                 sizeof(struct ipt_standard_target);
288         entry_return = g_try_malloc0(entry_return_size);
289         if (entry_return == NULL)
290                 goto err;
291
292         memset(entry_return, 0, entry_return_size);
293
294         entry_return->target_offset = sizeof(struct ipt_entry);
295         entry_return->next_offset = entry_return_size;
296
297         standard = (struct ipt_standard_target *) entry_return->elems;
298         standard->target.u.user.target_size =
299                                 ALIGN(sizeof(struct ipt_standard_target));
300         standard->verdict = XT_RETURN;
301
302         if (connman_add_entry(table, entry_return, last) < 0)
303                 goto err;
304
305         return 0;
306
307 err:
308         g_free(entry_head);
309         g_free(entry_return);
310
311         return -ENOMEM;
312 }
313
314 static struct ipt_entry *
315 new_builtin_rule(char *target_name,
316                 char *match_name, int match_argc, char **match_argv)
317 {
318         struct ipt_entry *new_entry;
319         size_t match_size, target_size;
320         struct xtables_match *xt_m;
321         struct xt_standard_target *target;
322
323         xt_m = NULL;
324         match_size = 0;
325
326         if (match_name) {
327                 xt_m = xtables_find_match(match_name, XTF_TRY_LOAD, NULL);
328                 if (xt_m == NULL)
329                         return NULL;
330
331                 match_size = ALIGN(sizeof(struct xt_entry_match)) + xt_m->size;
332         }
333
334         target_size = ALIGN(sizeof(struct xt_standard_target));
335
336         new_entry = g_try_malloc0(sizeof(struct ipt_entry) + target_size +
337                                                                 match_size);
338         if (new_entry == NULL)
339                 return NULL;
340
341         new_entry->target_offset = sizeof(struct ipt_entry) + match_size;
342         new_entry->next_offset = sizeof(struct ipt_entry) + target_size +
343                                                                 match_size;
344
345         if (xt_m) {
346                 struct xt_entry_match *entry_match;
347
348                 entry_match = (struct xt_entry_match *)new_entry->elems;
349                 entry_match->u.match_size = match_size;
350                 strcpy(entry_match->u.user.name, xt_m->name);
351                 entry_match->u.user.revision = xt_m->revision;
352                 if (xt_m->init != NULL)
353                         xt_m->init(entry_match);
354         }
355
356         target = (struct xt_standard_target *)(new_entry->elems + match_size);
357         strcpy(target->target.u.user.name, IPT_STANDARD_TARGET);
358         target->target.u.user.target_size =
359                                 ALIGN(sizeof(struct ipt_standard_target));
360         target->verdict = target_to_verdict(target_name);
361
362         return new_entry;
363 }
364
365 static struct ipt_entry *
366 new_custom_rule(char *target_name, int target_argc, char **target_argv,
367                 char *match_name, int match_argc, char **match_argv)
368 {
369         return NULL;
370 }
371
372 static struct ipt_entry *
373 new_rule(char *target_name, int target_argc, char **target_argv,
374                 char *match_name, int match_argc, char **match_argv)
375 {
376         struct ipt_entry *new_entry;
377
378         if (is_builtin_target(target_name))
379                 new_entry = new_builtin_rule(target_name,
380                                         match_name, match_argc, match_argv);
381         else
382                 new_entry = new_custom_rule(target_name,
383                                         target_argc, target_argv,
384                                         match_name, match_argc, match_argv);
385
386         return new_entry;
387 }
388
389 static int
390 connman_iptables_add_rule(struct connman_iptables *table, char *chain_name,
391                         char *target_name, int target_argc, char **target_argv,
392                         char *match_name, int match_argc, char **match_argv)
393 {
394         GList *chain_tail;
395         struct ipt_entry *new_entry;
396
397         chain_tail = find_chain_tail(table, chain_name);
398         if (chain_tail == NULL)
399                 return -EINVAL;
400
401         printf("Chains found\n");
402
403         new_entry = new_rule(target_name, target_argc, target_argv,
404                                 match_name, match_argc, match_argv);
405         if (new_entry == NULL)
406                 return -EINVAL;
407
408         return connman_add_entry(table, new_entry, chain_tail->prev);
409 }
410
411 static struct ipt_replace *
412 connman_iptables_blob(struct connman_iptables *table)
413 {
414         struct ipt_replace *r;
415         GList *list;
416         struct connman_iptables_entry *e;
417         unsigned char *entry_index;
418
419         r = g_try_malloc0(sizeof(struct ipt_replace) + table->size);
420         if (r == NULL)
421                 return NULL;
422
423         memset(r, 0, sizeof(*r) + table->size);
424
425         r->counters = g_try_malloc0(sizeof(struct xt_counters)
426                                 * table->num_entries);
427         if (r->counters == NULL) {
428                 g_free(r);
429                 return NULL;
430         }
431
432         strcpy(r->name, table->info->name);
433         r->num_entries = table->num_entries;
434         r->size = table->size;
435
436         r->num_counters = table->old_entries;
437         r->valid_hooks  = table->info->valid_hooks;
438
439         memcpy(r->hook_entry, table->info->hook_entry,
440                                 sizeof(table->info->hook_entry));
441         memcpy(r->underflow, table->info->underflow,
442                                 sizeof(table->info->underflow));
443
444         entry_index = (unsigned char *)r->entries;
445         for (list = table->entries; list; list = list->next) {
446                 e = list->data;
447
448                 memcpy(entry_index, e->entry, e->entry->next_offset);
449                 entry_index += e->entry->next_offset;
450         }
451
452         return r;
453 }
454
455 static void dump_target(struct connman_iptables *table,
456                                 struct ipt_entry *entry)
457
458 {
459         struct xtables_target *xt_t;
460         struct xt_entry_target *target;
461
462         target = ipt_get_target(entry);
463
464         if (!strcmp(target->u.user.name, IPT_STANDARD_TARGET)) {
465                 struct xt_standard_target *t;
466
467                 t = (struct xt_standard_target *)target;
468
469                 switch (t->verdict) {
470                 case XT_RETURN:
471                         printf("\ttarget RETURN\n");
472                         break;
473
474                 case -NF_ACCEPT - 1:
475                         printf("\ttarget ACCEPT\n");
476                         break;
477
478                 case -NF_DROP - 1:
479                         printf("\ttarget DROP\n");
480                         break;
481
482                 case -NF_QUEUE - 1:
483                         printf("\ttarget QUEUE\n");
484                         break;
485
486                 case -NF_STOP - 1:
487                         printf("\ttarget STOP\n");
488                         break;
489
490                 default:
491                         printf("\tJUMP @%p (0x%x)\n",
492                                 (char*)table->blob_entries->entrytable +
493                                 t->verdict, t->verdict);
494                         break;
495                 }
496
497                 xt_t = xtables_find_target(IPT_STANDARD_TARGET,
498                                                 XTF_LOAD_MUST_SUCCEED);
499
500                 if(xt_t->print != NULL)
501                         xt_t->print(NULL, target, 1);
502         } else {
503                 printf("\ttarget %s\n", target->u.user.name);
504
505                 xt_t = xtables_find_target(target->u.user.name, XTF_TRY_LOAD);
506                 if (xt_t == NULL)
507                         return;
508
509                 if(xt_t->print != NULL)
510                         xt_t->print(NULL, target, 1);
511         }
512 }
513
514 static void dump_match(struct connman_iptables *table, struct ipt_entry *entry)
515 {
516         struct xtables_match *xt_m;
517         struct xt_entry_match *match;
518
519         match = (struct xt_entry_match *) entry->elems;
520
521         if (!strlen(match->u.user.name))
522                 return;
523
524         xt_m = xtables_find_match(match->u.user.name, XTF_TRY_LOAD, NULL);
525         if (xt_m == NULL)
526                 goto out;
527
528         if(xt_m->print != NULL) {
529                 printf("\tmatch ");
530                 xt_m->print(NULL, match, 1);
531                 printf("\n");
532
533                 return;
534         }
535
536 out:
537         printf("\tmatch %s\n", match->u.user.name);
538
539 }
540
541 static int connman_iptables_dump_entry(struct ipt_entry *entry,
542                                         struct connman_iptables *table)
543 {
544         struct xt_entry_target *target;
545         unsigned int offset;
546         int builtin;
547
548         offset = (char *)entry - (char *)table->blob_entries->entrytable;
549         target = ipt_get_target(entry);
550         builtin = is_hook_entry(table, entry);
551
552         if (entry_to_offset(table, entry) + entry->next_offset ==
553                                         table->blob_entries->size) {
554                 printf("End of CHAIN 0x%x\n", offset);
555                 return 0;
556         }
557
558         if (!strcmp(target->u.user.name, IPT_ERROR_TARGET)) {
559                 printf("USER CHAIN (%s) %p  match %p  target %p  size %d\n",
560                         target->data, entry, entry->elems,
561                         (char *)entry + entry->target_offset,
562                                 entry->next_offset);
563
564                 return 0;
565         } else if (builtin >= 0) {
566                 printf("CHAIN (%s) %p  match %p  target %p  size %d\n",
567                         hooknames[builtin], entry, entry->elems,
568                         (char *)entry + entry->target_offset,
569                                 entry->next_offset);
570         } else {
571                 printf("RULE %p  match %p  target %p  size %d\n", entry,
572                         entry->elems,
573                         (char *)entry + entry->target_offset,
574                                 entry->next_offset);
575         }
576
577         dump_match(table, entry);
578         dump_target(table, entry);
579
580         return 0;
581 }
582
583 static void connman_iptables_dump(struct connman_iptables *table)
584 {
585         printf("%s valid_hooks=0x%08x, num_entries=%u, size=%u\n",
586                 table->info->name,
587                 table->info->valid_hooks, table->info->num_entries,
588                 table->info->size);
589
590         ENTRY_ITERATE(table->blob_entries->entrytable,
591                         table->blob_entries->size,
592                         connman_iptables_dump_entry, table);
593
594 }
595
596 static int connman_iptables_get_entries(struct connman_iptables *table)
597 {
598         socklen_t entry_size;
599
600         entry_size = sizeof(struct ipt_get_entries) + table->info->size;
601
602         return getsockopt(table->ipt_sock, IPPROTO_IP, IPT_SO_GET_ENTRIES,
603                                 table->blob_entries, &entry_size);
604 }
605
606 static int connman_iptables_replace(struct connman_iptables *table,
607                                         struct ipt_replace *r)
608 {
609         return setsockopt(table->ipt_sock, IPPROTO_IP, IPT_SO_SET_REPLACE, r,
610                          sizeof(*r) + r->size);
611 }
612
613 static void connman_iptables_cleanup(struct connman_iptables *table)
614 {
615         close(table->ipt_sock);
616         g_free(table->info);
617         g_free(table->blob_entries);
618         g_free(table);
619 }
620
621 static int add_entry(struct ipt_entry *entry, struct connman_iptables *table)
622 {
623         return connman_add_entry(table, entry, NULL);
624 }
625
626 static struct connman_iptables *connman_iptables_init(const char *table_name)
627 {
628         struct connman_iptables *table;
629         socklen_t s;
630
631         table =  g_try_new0(struct connman_iptables, 1);
632         if (table == NULL)
633                 return NULL;
634
635         table->info =  g_try_new0(struct ipt_getinfo, 1);
636         if (table->info == NULL)
637                 goto err;
638
639         table->ipt_sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
640         if (table->ipt_sock < 0)
641                 goto err;
642
643         s = sizeof(*table->info);
644         strcpy(table->info->name, table_name);
645         if (getsockopt(table->ipt_sock, IPPROTO_IP, IPT_SO_GET_INFO,
646                                                 table->info, &s) < 0)
647                 goto err;
648
649         table->blob_entries = g_try_malloc0(sizeof(struct ipt_get_entries) +
650                                                 table->info->size);
651         if (table->blob_entries == NULL)
652                 goto err;
653
654         strcpy(table->blob_entries->name, table_name);
655         table->blob_entries->size = table->info->size;
656
657         if (connman_iptables_get_entries(table) < 0)
658                 goto err;
659
660         table->num_entries = 0;
661         table->old_entries = table->info->num_entries;
662         table->size = 0;
663
664         ENTRY_ITERATE(table->blob_entries->entrytable,
665                         table->blob_entries->size,
666                                 add_entry, table);
667
668
669         return table;
670
671 err:
672
673         connman_iptables_cleanup(table);
674
675         return NULL;
676 }
677
678 int main(int argc, char *argv[])
679 {
680         struct ipt_replace *repl;
681         struct connman_iptables *table;
682
683         xtables_init();
684         xtables_set_nfproto(NFPROTO_IPV4);
685
686         table = connman_iptables_init("filter");
687         if (table == NULL)
688                 return -1;
689
690         connman_iptables_dump(table);
691
692         if (argv[1]) {
693                 connman_iptables_add_chain(table, argv[1]);
694
695                 connman_iptables_add_rule(table, argv[1],
696                                         "ACCEPT", 0, NULL,
697                                         NULL, 0, NULL);
698
699                 repl = connman_iptables_blob(table);
700
701                 connman_iptables_replace(table, repl);
702         }
703
704         connman_iptables_cleanup(table);
705
706         return 0;
707 }