iptables-saveのコードリーディング

はじめに

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

iptables-save でルールを出力する部分のコードリーディングのメモです。

iptables_save_main 関数からの流れ

iptables/iptables-save.c#L120-#L168

120
121
122
123
124
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
/* Format:
 * :Chain name POLICY packets bytes
 * rule
 */
int
iptables_save_main(int argc, char *argv[])
{
    const char *tablename = NULL;
    int c;

    iptables_globals.program_name = "iptables-save";
    c = xtables_init_all(&iptables_globals, NFPROTO_IPV4);
    if (c < 0) {
            fprintf(stderr, "%s/%s Failed to initialize xtables\n",
                            iptables_globals.program_name,
                            iptables_globals.program_version);
            exit(1);
    }
#if defined(ALL_INCLUSIVE) || defined(NO_SHARED_LIBS)
    init_extensions();
    init_extensions4();
#endif

    while ((c = getopt_long(argc, argv, "bcdt:", options, NULL)) != -1) {
            switch (c) {
            case 'c':
                    show_counters = 1;
                    break;

            case 't':
                    /* Select specific table. */
                    tablename = optarg;
                    break;
            case 'M':
                    xtables_modprobe_program = optarg;
                    break;
            case 'd':
                    do_output(tablename);
                    exit(0);
            }
    }

    if (optind < argc) {
            fprintf(stderr, "Unknown arguments found on commandline\n");
            exit(1);
    }

    return !do_output(tablename);
}

-t オプションを指定しない場合は tablename = NULLdo_output 関数を呼び出します。

iptables/ip6tables-save.c#L25

25
static int show_counters = 0;

iptables/iptables-save.c#L59-#L118

 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 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
115
116
117
118
static int do_output(const char *tablename)
{
    struct xtc_handle *h;
    const char *chain = NULL;

    if (!tablename)
            return for_each_table(&do_output);

    h = iptc_init(tablename);
    if (h == NULL) {
            xtables_load_ko(xtables_modprobe_program, false);
            h = iptc_init(tablename);
    }
    if (!h)
            xtables_error(OTHER_PROBLEM, "Cannot initialize: %s\n",
                       iptc_strerror(errno));

    time_t now = time(NULL);

    printf("# Generated by iptables-save v%s on %s",
           IPTABLES_VERSION, ctime(&now));
    printf("*%s\n", tablename);

    /* Dump out chain names first,
     * thereby preventing dependency conflicts */
    for (chain = iptc_first_chain(h);
         chain;
         chain = iptc_next_chain(h)) {

            printf(":%s ", chain);
            if (iptc_builtin(chain, h)) {
                    struct xt_counters count;
                    printf("%s ",
                           iptc_get_policy(chain, &count, h));
                    printf("[%llu:%llu]\n", (unsigned long long)count.pcnt, (unsigned long long)count.bcnt);
            } else {
                    printf("- [0:0]\n");
            }
    }

    for (chain = iptc_first_chain(h);
         chain;
         chain = iptc_next_chain(h)) {
            const struct ipt_entry *e;

            /* Dump out rules */
            e = iptc_first_rule(chain, h);
            while(e) {
                    print_rule4(e, h, chain, show_counters);
                    e = iptc_next_rule(e, h);
            }
    }

    now = time(NULL);
    printf("COMMIT\n");
    printf("# Completed on %s", ctime(&now));
    iptc_free(h);

    return 1;
}

tablenameNULL で呼び出された場合 for_each_table 関数で各テーブルごとに do_output 関数を呼び出します。

iptables/iptables-save.c#L34-#L56

34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
/* Debugging prototype. */
static int for_each_table(int (*func)(const char *tablename))
{
    int ret = 1;
    FILE *procfile = NULL;
    char tablename[XT_TABLE_MAXNAMELEN+1];

    procfile = fopen("/proc/net/ip_tables_names", "re");
    if (!procfile)
            return ret;

    while (fgets(tablename, sizeof(tablename), procfile)) {
            if (tablename[strlen(tablename) - 1] != '\n')
                    xtables_error(OTHER_PROBLEM,
                               "Badly formed tablename `%s'\n",
                               tablename);
            tablename[strlen(tablename) - 1] = '\0';
            ret &= func(tablename);
    }

    fclose(procfile);
    return ret;
}

/proc/net/ip_tables_names の出力結果の各行がテーブル名になっていて、各テーブルごとに引数 func で指定された関数を実行します。

iptables/iptables.c#L1069-#L1148

1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
/* We want this to be readable, so only print out neccessary fields.
 * Because that's the kind of world I want to live in.  */
void print_rule4(const struct ipt_entry *e,
            struct xtc_handle *h, const char *chain, int counters)
{
    const struct xt_entry_target *t;
    const char *target_name;

    /* print counters for iptables-save */
    if (counters > 0)
            printf("[%llu:%llu] ", (unsigned long long)e->counters.pcnt, (unsigned long long)e->counters.bcnt);

    /* print chain name */
    printf("-A %s", chain);

    /* Print IP part. */
    print_ip("-s", e->ip.src.s_addr,e->ip.smsk.s_addr,
                    e->ip.invflags & IPT_INV_SRCIP);

    print_ip("-d", e->ip.dst.s_addr, e->ip.dmsk.s_addr,
                    e->ip.invflags & IPT_INV_DSTIP);

    print_iface('i', e->ip.iniface, e->ip.iniface_mask,
                e->ip.invflags & IPT_INV_VIA_IN);

    print_iface('o', e->ip.outiface, e->ip.outiface_mask,
                e->ip.invflags & IPT_INV_VIA_OUT);

    print_proto(e->ip.proto, e->ip.invflags & XT_INV_PROTO);

    if (e->ip.flags & IPT_F_FRAG)
            printf("%s -f",
                   e->ip.invflags & IPT_INV_FRAG ? " !" : "");

    /* Print matchinfo part */
    if (e->target_offset) {
            IPT_MATCH_ITERATE(e, print_match_save, &e->ip);
    }

    /* print counters for iptables -R */
    if (counters < 0)
            printf(" -c %llu %llu", (unsigned long long)e->counters.pcnt, (unsigned long long)e->counters.bcnt);

    /* Print target name and targinfo part */
    target_name = iptc_get_target(e, h);
    t = ipt_get_target((struct ipt_entry *)e);
    if (t->u.user.name[0]) {
            const struct xtables_target *target =
                    xtables_find_target(t->u.user.name, XTF_TRY_LOAD);

            if (!target) {
                    fprintf(stderr, "Can't find library for target `%s'\n",
                            t->u.user.name);
                    exit(1);
            }

            printf(" -j %s", target->alias ? target->alias(t) : target_name);
            if (target->save)
                    target->save(&e->ip, t);
            else {
                    /* If the target size is greater than xt_entry_target
                     * there is something to be saved, we just don't know
                     * how to print it */
                    if (t->u.target_size !=
                        sizeof(struct xt_entry_target)) {
                            fprintf(stderr, "Target `%s' is missing "
                                            "save function\n",
                                    t->u.user.name);
                            exit(1);
                    }
            }
    } else if (target_name && (*target_name != '\0'))
#ifdef IPT_F_GOTO
            printf(" -%c %s", e->ip.flags & IPT_F_GOTO ? 'g' : 'j', target_name);
#else
            printf(" -j %s", target_name);
#endif

    printf("\n");
}

ルールの出力は以下のようになっています。

  • 1082行目: -A チェイン名 を出力します。
  • 1088行目: -s ソースアドレス を出力します。ただし場合によっては出力しません(下記 print_ip 関数参照)。
  • 1088行目: -d デスティネーションアドレス を出力します。ただし場合によっては出力しません(下記 print_ip 関数参照)。
  • 1091行目: 入力インターフェースの情報を出力します。ただし場合によっては出力しません(下記 print_iface 関数参照)。
  • 1094行目: 出力インターフェースの情報を出力します。ただし場合によっては出力しません(下記 print_iface 関数参照)。
  • 1097行目: -p オプションでのプロトコルの指定を出力します。ただし場合によっては出力しません(下記 print_proto 関数参照)。
  • 1099行目: ルールがフラグメントルールの場合は -f オプションを出力します。
  • 1104行目: e->target_offset0 以外の場合は print_match_save 関数を IPT_MATCH_ITERATE マクロでループして -m オプションを出力します。
  • 1109行目: counters が負の場合は -c オプションでカウンタの数値を出力します。
  • 1115~1145行目: いろいろ分岐はありますが場合によって -j-g オプションでターゲット名と場合によってその後に targinfo のオプションを出力します。

include/linux/netfilter_ipv4/ip_tables.h#L217-#L222

217
218
219
220
221
222
/* Helper functions */
static __inline__ struct xt_entry_target *
ipt_get_target(struct ipt_entry *e)
{
    return (void *)e + e->target_offset;
}

include/linux/netfilter/x_tables.h#L33-#L54

33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
struct xt_entry_target {
    union {
            struct {
                    __u16 target_size;

                    /* Used by userspace */
                    char name[XT_EXTENSION_MAXNAMELEN];
                    __u8 revision;
            } user;
            struct {
                    __u16 target_size;

                    /* Used inside the kernel */
                    struct xt_target *target;
            } kernel;

            /* Total length */
            __u16 target_size;
    } u;

    unsigned char data[0];
};

include/xtables.h#L283-#L358

283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
struct xtables_target
{
    /*
     * ABI/API version this module requires. Must be first member,
     * as the rest of this struct may be subject to ABI changes.
     */
    const char *version;

    struct xtables_target *next;


    const char *name;

    /* Real target behind this, if any. */
    const char *real_name;

    /* Revision of target (0 by default). */
    u_int8_t revision;

    /* Extension flags */
    u_int8_t ext_flags;

    u_int16_t family;


    /* Size of target data. */
    size_t size;

    /* Size of target data relevant for userspace comparison purposes */
    size_t userspacesize;

    /* Function which prints out usage message. */
    void (*help)(void);

    /* Initialize the target. */
    void (*init)(struct xt_entry_target *t);

    /* Function which parses command options; returns true if it
           ate an option */
    /* entry is struct ipt_entry for example */
    int (*parse)(int c, char **argv, int invert, unsigned int *flags,
                 const void *entry,
                 struct xt_entry_target **targetinfo);

    /* Final check; exit if not ok. */
    void (*final_check)(unsigned int flags);

    /* Prints out the target iff non-NULL: put space at end */
    void (*print)(const void *ip,
                  const struct xt_entry_target *target, int numeric);

    /* Saves the targinfo in parsable form to stdout. */
    void (*save)(const void *ip,
                 const struct xt_entry_target *target);

    /* Print target name or alias */
    const char *(*alias)(const struct xt_entry_target *target);

    /* Pointer to list of extra command-line options */
    const struct option *extra_opts;

    /* New parser */
    void (*x6_parse)(struct xt_option_call *);
    void (*x6_fcheck)(struct xt_fcheck_call *);
    const struct xt_option_entry *x6_options;

    size_t udata_size;

    /* Ignore these men behind the curtain: */
    void *udata;
    unsigned int option_offset;
    struct xt_entry_target *t;
    unsigned int tflags;
    unsigned int used;
    unsigned int loaded; /* simulate loading so options are merged properly */
};

include/linux/netfilter_ipv4/ip_tables.h#L99-#L121

 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
/* This structure defines each of the firewall rules.  Consists of 3
   parts which are 1) general IP header stuff 2) match specific
   stuff 3) the target to perform if the rule matches */
struct ipt_entry {
    struct ipt_ip ip;

    /* Mark with fields that we care about. */
    unsigned int nfcache;

    /* Size of ipt_entry + matches */
    u_int16_t target_offset;
    /* Size of ipt_entry + matches + target */
    u_int16_t next_offset;

    /* Back pointer */
    unsigned int comefrom;

    /* Packet and byte counters. */
    struct xt_counters counters;

    /* The matches (if any), then the target. */
    unsigned char elems[0];
};

target->save の例。拡張ごとに struct xtables_target の配列が定義されています。

extensions/libxt_MARK.c#L248-#L262

248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
static struct xtables_target mark_tg_reg[] = {
    {
            .family        = NFPROTO_UNSPEC,
            .name          = "MARK",
            .version       = XTABLES_VERSION,
            .revision      = 0,
            .size          = XT_ALIGN(sizeof(struct xt_mark_target_info)),
            .userspacesize = XT_ALIGN(sizeof(struct xt_mark_target_info)),
            .help          = MARK_help,
            .print         = MARK_print_v0,
            .save          = MARK_save_v0,
            .x6_parse      = MARK_parse_v0,
            .x6_fcheck     = MARK_check,
            .x6_options    = MARK_opts,
    },

extensions/libxt_MARK.c#L176-#L183

176
177
178
179
180
181
182
183
static void MARK_save_v0(const void *ip, const struct xt_entry_target *target)
{
    const struct xt_mark_target_info *markinfo =
            (const struct xt_mark_target_info *)target->data;

    printf(" --set-mark");
    print_mark(markinfo->mark);
}

この拡張の場合は --set-mark というオプションが出力されるようです。

include/linux/netfilter_ipv4/ip_tables.h#L66-#L82

66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
/* Yes, Virginia, you have to zero the padding. */
struct ipt_ip {
    /* Source and destination IP addr */
    struct in_addr src, dst;
    /* Mask for src and dest IP addr */
    struct in_addr smsk, dmsk;
    char iniface[IFNAMSIZ], outiface[IFNAMSIZ];
    unsigned char iniface_mask[IFNAMSIZ], outiface_mask[IFNAMSIZ];

    /* Protocol, 0 = ANY */
    u_int16_t proto;

    /* Flags word */
    u_int8_t flags;
    /* Inverse flags */
    u_int8_t invflags;
};

include/linux/netfilter_ipv4/ip_tables.h#L83-#L87

83
84
85
86
/* Values for "flag" field in struct ipt_ip (general ip structure). */
#define IPT_F_FRAG          0x01    /* Set if rule is a fragment rule */
#define IPT_F_GOTO          0x02    /* Set if jump is a goto */
#define IPT_F_MASK          0x03    /* All possible flag bits mask. */

iptables/iptables.c#L1039-#L1067

1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
/* print a given ip including mask if neccessary */
static void print_ip(const char *prefix, uint32_t ip,
                 uint32_t mask, int invert)
{
    uint32_t bits, hmask = ntohl(mask);
    int i;

    if (!mask && !ip && !invert)
            return;

    printf("%s %s %u.%u.%u.%u",
            invert ? " !" : "",
            prefix,
            IP_PARTS(ip));

    if (mask == 0xFFFFFFFFU) {
            printf("/32");
            return;
    }

    i    = 32;
    bits = 0xFFFFFFFEU;
    while (--i >= 0 && hmask != bits)
            bits <<= 1;
    if (i >= 0)
            printf("/%u", i);
    else
            printf("/%u.%u.%u.%u", IP_PARTS(mask));
}

iptables/iptables.c#L989-#L1013

 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
/* This assumes that mask is contiguous, and byte-bounded. */
static void
print_iface(char letter, const char *iface, const unsigned char *mask,
        int invert)
{
    unsigned int i;

    if (mask[0] == 0)
            return;

    printf("%s -%c ", invert ? " !" : "", letter);

    for (i = 0; i < IFNAMSIZ; i++) {
            if (mask[i] != 0) {
                    if (iface[i] != '\0')
                            printf("%c", iface[i]);
            } else {
                    /* we can access iface[i-1] here, because
                     * a few lines above we make sure that mask[0] != 0 */
                    if (iface[i-1] != '\0')
                            printf("+");
                    break;
            }
    }
}

iptables/iptables.c#L958-#L979

958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
static void print_proto(uint16_t proto, int invert)
{
    if (proto) {
            unsigned int i;
            const char *invertstr = invert ? " !" : "";

            const struct protoent *pent = getprotobynumber(proto);
            if (pent) {
                    printf("%s -p %s", invertstr, pent->p_name);
                    return;
            }

            for (i = 0; xtables_chain_protos[i].name != NULL; ++i)
                    if (xtables_chain_protos[i].num == proto) {
                            printf("%s -p %s",
                                   invertstr, xtables_chain_protos[i].name);
                            return;
                    }

            printf("%s -p %u", invertstr, proto);
    }
}

include/linux/netfilter_ipv4/ip_tables.h#L58-#L60

58
59
60
/* fn returns 0 to continue iteration */
#define IPT_MATCH_ITERATE(e, fn, args...) \
    XT_MATCH_ITERATE(struct ipt_entry, e, fn, ## args)

include/linux/netfilter/x_tables.h#L126-#L143

126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
/* fn returns 0 to continue iteration */
#define XT_MATCH_ITERATE(type, e, fn, args...)                      \
({                                                          \
    unsigned int __i;                                       \
    int __ret = 0;                                          \
    struct xt_entry_match *__m;                             \
                                                            \
    for (__i = sizeof(type);                                \
         __i < (e)->target_offset;                          \
         __i += __m->u.match_size) {                        \
            __m = (void *)e + __i;                          \
                                                            \
            __ret = fn(__m , ## args);                      \
            if (__ret != 0)                                 \
                    break;                                  \
    }                                                       \
    __ret;                                                  \
})

iptables/iptables.c#L1015-#L1037

1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
static int print_match_save(const struct xt_entry_match *e,
                    const struct ipt_ip *ip)
{
    const struct xtables_match *match =
            xtables_find_match(e->u.user.name, XTF_TRY_LOAD, NULL);

    if (match) {
            printf(" -m %s",
                    match->alias ? match->alias(e) : e->u.user.name);

            /* some matches don't provide a save function */
            if (match->save)
                    match->save(ip, e);
    } else {
            if (e->u.match_size) {
                    fprintf(stderr,
                            "Can't find library for match `%s'\n",
                            e->u.user.name);
                    exit(1);
            }
    }
    return 0;
}