iptablesのコードリーディング

はじめに

iptables-restoreのコードリーディング の続きです。

サーバ上の iptables の現状の設定が、自分が意図した設定と一致しているか確認したいというニーズがあります。 シェルスクリプトで iptables コマンドを順次実行する方式は大変すぎるので、 iptables-restore に自分の設定ファイルを渡して設定を反映させ、現在の状態は iptables-save で出力して、設定ファイルとこの出力を比較したらどうかと考えました。

しかし、この方式でもいくつか気をつける必要があります (これで全部ではないかもしれません)。

  • iptables-restore への入力ファイルに書いていないテーブル (* テーブル名 の行から COMMIT の行まで)も iptables-save では出力される。
  • iptables-restore への入力ファイルで複数のテーブルを指定した場合、 iptables-save では特定の順序で出力される。
  • iptables-restore への入力ファイルにコメント行(# で始まる行)を含めていても、 iptables-save では出力されない。
  • iptables-save ではテーブルの前後に # Generated by iptables-save v1.4.21 on Sun Feb 26 03:40:03 2017# Completed on Sun Feb 26 03:40:03 2017 のようなコメント行が出力される。
  • チェーン (: で始まる行) のカウンタ ([整数:整数]) の値は iptables-restore への入力ファイルと iptables-save の出力ではほとんどのケースは一致しない。 iptables-restore 実行時から iptables-save 実行時までにそのチェーンでパケットを処理していなければ一致するが、設定直後に確認するケースを除いてたいていは一致しないことになる。
  • 既に追加したルールセットの途中に -I でルールを追加すると、 iptables-save ではソートされた -A のリストとして出力される。
  • 1つのコマンド内で複数の引数の順序は任意に指定可能だが、 iptables-save では特定の順番で出力される。
  • --destination-port-dport のように同一のオプションに2通りの書き方(シノニム)が用意されているものがある。
  • --tcp-flags の値に ALL を指定すると、 iptables-save では FIN,SYN,RST,PSH,ACK,URG に展開される。
  • --tcp-flags の値に複数の値を指定した場合、 iptables-save では特定の順番で出力される。
  • --syn を指定すると、 iptables-save では --tcp-flags FIN,SYN,RST,ACK SYN に展開される。
  • --src--destination0.0.0.0/0 を指定した場合、 iptables-save では出力が省略される。

以下に例を示します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[root@centos7 iptables-experiment]# cat iptables.conf
*filter
:INPUT DROP [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
# this comment will not be printed with iptables-save
-A INPUT -p tcp -m tcp --tcp-flags ALL NONE -j DROP
-A INPUT -p tcp -m tcp ! --syn -m state --state NEW -j DROP
-A INPUT -p tcp -m tcp --tcp-flags ALL ALL -j DROP
-A INPUT -i lo -j ACCEPT
-A INPUT -p icmp -j ACCEPT
-A INPUT -p tcp -m tcp --destination 0.0.0.0/0 --dport 443 -j ACCEPT
-I INPUT 7 -p tcp -m tcp --src 0.0.0.0/0 --destination-port 80 -j ACCEPT
-I INPUT 7 -p tcp -m tcp --src 192.168.0.0/24 --destination-port 22 -j ACCEPT
COMMIT
*nat
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
COMMIT
[root@centos7 iptables-experiment]# iptables-restore < iptables.conf
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
[root@centos7 iptables-experiment]# iptables-save
# Generated by iptables-save v1.4.21 on Sun Feb 26 03:40:03 2017
*mangle
:PREROUTING ACCEPT [2:668]
:INPUT ACCEPT [2:668]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [2:656]
:POSTROUTING ACCEPT [2:656]
COMMIT
# Completed on Sun Feb 26 03:40:03 2017
# Generated by iptables-save v1.4.21 on Sun Feb 26 03:40:03 2017
*nat
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
COMMIT
# Completed on Sun Feb 26 03:40:03 2017
# Generated by iptables-save v1.4.21 on Sun Feb 26 03:40:03 2017
*filter
:INPUT DROP [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
-A INPUT -p tcp -m tcp --tcp-flags FIN,SYN,RST,PSH,ACK,URG NONE -j DROP
-A INPUT -p tcp -m tcp ! --tcp-flags FIN,SYN,RST,ACK SYN -m state --state NEW -j DROP
-A INPUT -p tcp -m tcp --tcp-flags FIN,SYN,RST,PSH,ACK,URG FIN,SYN,RST,PSH,ACK,URG -j DROP
-A INPUT -i lo -j ACCEPT
-A INPUT -p icmp -j ACCEPT
-A INPUT -s 192.168.0.0/24 -p tcp -m tcp --dport 22 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 80 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 443 -j ACCEPT
COMMIT
# Completed on Sun Feb 26 03:40:03 2017

synonymで検索した結果

なお、 IPv6 関連は省略しています。

extensions/libxt_tcp.c#L29-#L38

29
30
31
32
33
34
35
36
37
38
static const struct option tcp_opts[] = {
    {.name = "source-port",      .has_arg = true,  .val = '1'},
    {.name = "sport",            .has_arg = true,  .val = '1'}, /* synonym */
    {.name = "destination-port", .has_arg = true,  .val = '2'},
    {.name = "dport",            .has_arg = true,  .val = '2'}, /* synonym */
    {.name = "syn",              .has_arg = false, .val = '3'},
    {.name = "tcp-flags",        .has_arg = true,  .val = '4'},
    {.name = "tcp-option",       .has_arg = true,  .val = '5'},
    XT_GETOPT_TABLEEND,
};

iptables/iptables.c#L76-#L114

 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
static struct option original_opts[] = {
    {.name = "append",        .has_arg = 1, .val = 'A'},
    {.name = "delete",        .has_arg = 1, .val = 'D'},
    {.name = "check",         .has_arg = 1, .val = 'C'},
    {.name = "insert",        .has_arg = 1, .val = 'I'},
    {.name = "replace",       .has_arg = 1, .val = 'R'},
    {.name = "list",          .has_arg = 2, .val = 'L'},
    {.name = "list-rules",    .has_arg = 2, .val = 'S'},
    {.name = "flush",         .has_arg = 2, .val = 'F'},
    {.name = "zero",          .has_arg = 2, .val = 'Z'},
    {.name = "new-chain",     .has_arg = 1, .val = 'N'},
    {.name = "delete-chain",  .has_arg = 2, .val = 'X'},
    {.name = "rename-chain",  .has_arg = 1, .val = 'E'},
    {.name = "policy",        .has_arg = 1, .val = 'P'},
    {.name = "source",        .has_arg = 1, .val = 's'},
    {.name = "destination",   .has_arg = 1, .val = 'd'},
    {.name = "src",           .has_arg = 1, .val = 's'}, /* synonym */
    {.name = "dst",           .has_arg = 1, .val = 'd'}, /* synonym */
    {.name = "protocol",      .has_arg = 1, .val = 'p'},
    {.name = "in-interface",  .has_arg = 1, .val = 'i'},
    {.name = "jump",          .has_arg = 1, .val = 'j'},
    {.name = "table",         .has_arg = 1, .val = 't'},
    {.name = "match",         .has_arg = 1, .val = 'm'},
    {.name = "numeric",       .has_arg = 0, .val = 'n'},
    {.name = "out-interface", .has_arg = 1, .val = 'o'},
    {.name = "verbose",       .has_arg = 0, .val = 'v'},
    {.name = "wait",          .has_arg = 0, .val = 'w'},
    {.name = "exact",         .has_arg = 0, .val = 'x'},
    {.name = "fragments",     .has_arg = 0, .val = 'f'},
    {.name = "version",       .has_arg = 0, .val = 'V'},
    {.name = "help",          .has_arg = 2, .val = 'h'},
    {.name = "line-numbers",  .has_arg = 0, .val = '0'},
    {.name = "modprobe",      .has_arg = 1, .val = 'M'},
    {.name = "set-counters",  .has_arg = 1, .val = 'c'},
    {.name = "goto",          .has_arg = 1, .val = 'g'},
    {.name = "ipv4",          .has_arg = 0, .val = '4'},
    {.name = "ipv6",          .has_arg = 0, .val = '6'},
    {NULL},
};

tcp_opts の参照箇所

extensions/libxt_tcp.c#L365-#L383

365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
static struct xtables_match tcp_match = {
    .family         = NFPROTO_UNSPEC,
    .name           = "tcp",
    .version        = XTABLES_VERSION,
    .size           = XT_ALIGN(sizeof(struct xt_tcp)),
    .userspacesize  = XT_ALIGN(sizeof(struct xt_tcp)),
    .help           = tcp_help,
    .init           = tcp_init,
    .parse          = tcp_parse,
    .print          = tcp_print,
    .save           = tcp_save,
    .extra_opts     = tcp_opts,
};

void
_init(void)
{
    xtables_register_match(&tcp_match);
}

libxtables/xtables.c#L821-#L861

821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
void xtables_register_match(struct xtables_match *me)
{
    if (me->version == NULL) {
            fprintf(stderr, "%s: match %s<%u> is missing a version\n",
                    xt_params->program_name, me->name, me->revision);
            exit(1);
    }
    if (strcmp(me->version, XTABLES_VERSION) != 0) {
            fprintf(stderr, "%s: match \"%s\" has version \"%s\", "
                    "but \"%s\" is required.\n",
                    xt_params->program_name, me->name,
                    me->version, XTABLES_VERSION);
            exit(1);
    }

    if (strlen(me->name) >= XT_EXTENSION_MAXNAMELEN) {
            fprintf(stderr, "%s: match `%s' has invalid name\n",
                    xt_params->program_name, me->name);
            exit(1);
    }

    if (me->family >= NPROTO) {
            fprintf(stderr,
                    "%s: BUG: match %s has invalid protocol family\n",
                    xt_params->program_name, me->name);
            exit(1);
    }

    if (me->x6_options != NULL)
            xtables_option_metavalidate(me->name, me->x6_options);
    if (me->extra_opts != NULL)
            xtables_check_options(me->name, me->extra_opts);

    /* ignore not interested match */
    if (me->family != afinfo->family && me->family != AF_UNSPEC)
            return;

    /* place on linked list of matches pending full registration */
    me->next = xtables_pending_matches;
    xtables_pending_matches = me;
}

libxtables/xtables.c#L810-#L819

810
811
812
813
814
815
816
817
818
819
static void xtables_check_options(const char *name, const struct option *opt)
{
    for (; opt->name != NULL; ++opt)
            if (opt->val < 0 || opt->val >= XT_OPTION_OFFSET_SCALE) {
                    fprintf(stderr, "%s: Extension %s uses invalid "
                            "option value %d\n",xt_params->program_name,
                            name, opt->val);
                    exit(1);
            }
}

original_opts の参照箇所

iptables/iptables.c#L118-#L123

118
119
120
121
122
123
struct xtables_globals iptables_globals = {
    .option_offset = 0,
    .program_version = IPTABLES_VERSION,
    .orig_opts = original_opts,
    .exit_err = iptables_exit_error,
};

iptables_globals.orig_opts の参照箇所のうちの1つ。

iptables/iptables.c#L1300-#L1306

1300
1301
1302
1303
1304
1305
1306
    /* Merge options for non-cloned matches */
    if (m->x6_options != NULL)
            opts = xtables_options_xfrm(iptables_globals.orig_opts, opts,
                                        m->x6_options, &m->option_offset);
    else if (m->extra_opts != NULL)
            opts = xtables_merge_options(iptables_globals.orig_opts, opts,
                                         m->extra_opts, &m->option_offset);

iptables/iptables.c#L171

171
#define opts iptables_globals.opts

equivalentで検索した結果

extensions/libxt_tcp.c#L12-#L27

12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
static void tcp_help(void)
{
    printf(
"tcp match options:\n"
"[!] --tcp-flags mask comp  match when TCP flags & mask == comp\n"
"                           (Flags: SYN ACK FIN RST URG PSH ALL NONE)\n"
"[!] --syn                  match when only SYN flag set\n"
"                           (equivalent to --tcp-flags SYN,RST,ACK,FIN SYN)\n"
"[!] --source-port port[:port]\n"
" --sport ...\n"
"                           match source port(s)\n"
"[!] --destination-port port[:port]\n"
" --dport ...\n"
"                           match destination port(s)\n"
"[!] --tcp-option number        match if TCP option set\n");
}

コマンドとオプションの有効な組み合わせ

iptables/iptables.c#L125-#L169

125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
/* Table of legal combinations of commands and options.  If any of the
 * given commands make an option legal, that option is legal (applies to
 * CMD_LIST and CMD_ZERO only).
 * Key:
 *  +  compulsory
 *  x  illegal
 *     optional
 */

static const char commands_v_options[NUMBER_OF_CMD][NUMBER_OF_OPT] =
/* Well, it's better than "Re: Linux vs FreeBSD" */
{
    /*     -n  -s  -d  -p  -j  -v  -x  -i  -o --line -c -f */
/*INSERT*/    {'x',' ',' ',' ',' ',' ','x',' ',' ','x',' ',' '},
/*DELETE*/    {'x',' ',' ',' ',' ',' ','x',' ',' ','x','x',' '},
/*DELETE_NUM*/{'x','x','x','x','x',' ','x','x','x','x','x','x'},
/*REPLACE*/   {'x',' ',' ',' ',' ',' ','x',' ',' ','x',' ',' '},
/*APPEND*/    {'x',' ',' ',' ',' ',' ','x',' ',' ','x',' ',' '},
/*LIST*/      {' ','x','x','x','x',' ',' ','x','x',' ','x','x'},
/*FLUSH*/     {'x','x','x','x','x',' ','x','x','x','x','x','x'},
/*ZERO*/      {'x','x','x','x','x',' ','x','x','x','x','x','x'},
/*NEW_CHAIN*/ {'x','x','x','x','x',' ','x','x','x','x','x','x'},
/*DEL_CHAIN*/ {'x','x','x','x','x',' ','x','x','x','x','x','x'},
/*SET_POLICY*/{'x','x','x','x','x',' ','x','x','x','x',' ','x'},
/*RENAME*/    {'x','x','x','x','x',' ','x','x','x','x','x','x'},
/*LIST_RULES*/{'x','x','x','x','x',' ','x','x','x','x','x','x'},
/*ZERO_NUM*/  {'x','x','x','x','x',' ','x','x','x','x','x','x'},
/*CHECK*/     {'x',' ',' ',' ',' ',' ','x',' ',' ','x','x',' '},
};

static const int inverse_for_options[NUMBER_OF_OPT] =
{
/* -n */ 0,
/* -s */ IPT_INV_SRCIP,
/* -d */ IPT_INV_DSTIP,
/* -p */ XT_INV_PROTO,
/* -j */ 0,
/* -v */ 0,
/* -x */ 0,
/* -i */ IPT_INV_VIA_IN,
/* -o */ IPT_INV_VIA_OUT,
/*--line*/ 0,
/* -c */ 0,
/* -f */ IPT_INV_FRAG,
};

tcp-flags の値定義

extensions/libxt_tcp.c#L63-#L77

63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
struct tcp_flag_names {
    const char *name;
    unsigned int flag;
};

static const struct tcp_flag_names tcp_flag_names[]
= { { "FIN", 0x01 },
    { "SYN", 0x02 },
    { "RST", 0x04 },
    { "PSH", 0x08 },
    { "ACK", 0x10 },
    { "URG", 0x20 },
    { "ALL", 0x3F },
    { "NONE", 0 },
};

strcasecmp で大文字小文字無視で比較していました。 extensions/libxt_tcp.c#L79-#L102

 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
static unsigned int
parse_tcp_flag(const char *flags)
{
    unsigned int ret = 0;
    char *ptr;
    char *buffer;

    buffer = strdup(flags);

    for (ptr = strtok(buffer, ","); ptr; ptr = strtok(NULL, ",")) {
            unsigned int i;
            for (i = 0; i < ARRAY_SIZE(tcp_flag_names); ++i)
                    if (strcasecmp(tcp_flag_names[i].name, ptr) == 0) {
                            ret |= tcp_flag_names[i].flag;
                            break;
                    }
            if (i == ARRAY_SIZE(tcp_flag_names))
                    xtables_error(PARAMETER_PROBLEM,
                               "Unknown TCP flag `%s'", ptr);
    }

    free(buffer);
    return ret;
}

--dports などの値定義

extensions/libxt_tcp.c#L135-#L138

135
136
137
138
#define TCP_SRC_PORTS 0x01
#define TCP_DST_PORTS 0x02
#define TCP_FLAGS 0x04
#define TCP_OPTION  0x08

extensions/libxt_tcp.c#L140-#L204

140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
static int
tcp_parse(int c, char **argv, int invert, unsigned int *flags,
          const void *entry, struct xt_entry_match **match)
{
    struct xt_tcp *tcpinfo = (struct xt_tcp *)(*match)->data;

    switch (c) {
    case '1':
            if (*flags & TCP_SRC_PORTS)
                    xtables_error(PARAMETER_PROBLEM,
                               "Only one `--source-port' allowed");
            parse_tcp_ports(optarg, tcpinfo->spts);
            if (invert)
                    tcpinfo->invflags |= XT_TCP_INV_SRCPT;
            *flags |= TCP_SRC_PORTS;
            break;

    case '2':
            if (*flags & TCP_DST_PORTS)
                    xtables_error(PARAMETER_PROBLEM,
                               "Only one `--destination-port' allowed");
            parse_tcp_ports(optarg, tcpinfo->dpts);
            if (invert)
                    tcpinfo->invflags |= XT_TCP_INV_DSTPT;
            *flags |= TCP_DST_PORTS;
            break;

    case '3':
            if (*flags & TCP_FLAGS)
                    xtables_error(PARAMETER_PROBLEM,
                               "Only one of `--syn' or `--tcp-flags' "
                               " allowed");
            parse_tcp_flags(tcpinfo, "SYN,RST,ACK,FIN", "SYN", invert);
            *flags |= TCP_FLAGS;
            break;

    case '4':
            if (*flags & TCP_FLAGS)
                    xtables_error(PARAMETER_PROBLEM,
                               "Only one of `--syn' or `--tcp-flags' "
                               " allowed");
            if (!argv[optind]
                || argv[optind][0] == '-' || argv[optind][0] == '!')
                    xtables_error(PARAMETER_PROBLEM,
                               "--tcp-flags requires two args.");

            parse_tcp_flags(tcpinfo, optarg, argv[optind],
                            invert);
            optind++;
            *flags |= TCP_FLAGS;
            break;

    case '5':
            if (*flags & TCP_OPTION)
                    xtables_error(PARAMETER_PROBLEM,
                               "Only one `--tcp-option' allowed");
            parse_tcp_option(optarg, &tcpinfo->option);
            if (invert)
                    tcpinfo->invflags |= XT_TCP_INV_OPTION;
            *flags |= TCP_OPTION;
            break;
    }

    return 1;
}