zoukankan      html  css  js  c++  java
  • iptables 源码分析

    # modinfo ip_tables

    filename: /lib/modules/3.10.0-957.12.2.el7.x86_64/kernel/net/ipv4/netfilter/ip_tables.ko.xz

     一、规则的显示

    选择先来说明规则的显示,因为他涉及到的东东简单,而且又全面,了解了规则的显示,对于其它操作的了解就显得容易了。

    iptables version 1.2.7

    iptables有两条线:ipv4 和ipv6,这里只分析v4的,因为v6偶暂时还用不着,没有去看。

    iptables_standardone.c

    主函数:

    int main(int argc, char *argv[])

    {

    int ret;

    char *table = "filter"; /*默认的表是filter*/

    iptc_handle_t handle = NULL;

    program_name = "iptables";

    program_version = IPTABLES_VERSION;

    #ifdef NO_SHARED_LIBS

    init_extensions();

    #endif

    /*进入命令行处理函数*/

    ret = do_command(argc, argv, &table, &handle);

    if (ret)

    ret = iptc_commit(&handle);

    if (!ret)

    fprintf(stderr, "iptables: %s ",

    iptc_strerror(errno));

    exit(!ret);

    }

    table表示表的名称,就是iptables -t 后面跟的那个,默认是"filter"

    iptc_handle_t handle = NULL; 这个东东很重要,现在初始化NULL,后面他被用来存储一个表的所有规则的快照。

    program_name = "iptables";

    program_version = IPTABLES_VERSION;

    设置名称和版本。

    #ifdef NO_SHARED_LIBS

    init_extensions();

    #endif

    iptables很多东东,是用共享库*.so的形式(我们安装会,可以在诸如/lib/iptables下边看到),如果不采用共享库,则进行一个初始化操作。我们假设是采用共享库的,忽略它。

    然后就进入核心处理模块:

    do_command(argc, argv, &table, &handle);

    do_command 函数是整个系统的核心,负责处理整个用户的输入命令。函数首先对一些结构、变量进行初始化,初始化完毕后,进入while循环,分析用户输入的命令,设置相关的标志变量,然后根据相应标志,调用对应的处理函数。

    struct ipt_entry fw, *e = NULL;

    int invert = 0;

    unsigned int nsaddrs = 0, ndaddrs = 0;

    struct in_addr *saddrs = NULL, *daddrs = NULL;

    int c, verbose = 0;

    const char *chain = NULL;

    const char *shostnetworkmask = NULL, *dhostnetworkmask = NULL;

    const char *policy = NULL, *newname = NULL;

    unsigned int rulenum = 0, options = 0, command = 0;

    const char *pcnt = NULL, *bcnt = NULL;

    int ret = 1;

    struct iptables_match *m;

    struct iptables_target *target = NULL;

    struct iptables_target *t;

    const char *jumpto = "";

    char *protocol = NULL;

    const char *modprobe = NULL;

    /*初始化变量*/

    memset(&fw, 0, sizeof(fw));

    opts = original_opts;

    global_option_offset = 0;

    /* re-set optind to 0 in case do_command gets called

    * a second time */

    optind = 0;

    /*初始化两个全局变量*/

    /* clear mflags in case do_command gets called a second time

    * (we clear the global list of all matches for security)*/

    for (m = iptables_matches; m; m = m->next) {

    m->mflags = 0;

    m->used = 0;

    }

    for (t = iptables_targets; t; t = t->next) {

    t->tflags = 0;

    t->used = 0;

    }

    ps:开头一大堆的变量定义和初始化,可以在程序分析的时候看它们的作用,有两个全局结构变量很重要:iptables_matches和iptables_targets。现在来分析他们的作用会有一点困难,因为它们涉及到了太多方面的东东,这里,可以先把它们“想像成”用户空间用来读取内核规则的结构(当然,这有点错误)。

    /*开始化析命令行*/

    while ((c = getopt_long(argc, argv,

    "-A:C:D:R:I:L::M:F::Z::N:X::E:P:Vh::o:p:s:d:j:i:fbvnt:m:xc:",

    opts, NULL)) != -1)

    {

    }

    这个while循环处理所有的用户输入,对应规则输出-L,有:

    case 'L':

    add_command(&command, CMD_LIST, CMD_ZERO,

    invert);

    if (optarg) chain = optarg;

    else if (optind < argc && argv[optind][0] != '-'

    && argv[optind][0] != '!')

    chain = argv[optind++];

    break;

    add_command函数负责将命令标志变量command与令标志 CMD_LIST求&运算, CMD_ZERO只是一个附加的判断标志而已,invert);然后,从命令行中取得要显示的链名(如果有的话)。

    与此相关的还有用t参数指定了表名:

    case 't':

    if (invert)

    exit_error(PARAMETER_PROBLEM,

    "unexpected ! flag before --table");

    *table = argv[optind-1];

    break;

    即,如果有’t’参数,则取’t’后跟的表名:*table = argv[optind-1],否则,它应该是主函数中默认的filter表。

    命令处理完毕后,即进入执行模块:

    /*因为程序定义了共享库的话,iptables_matches/iptables_target这两个结构运行至此是NULL,并且target也是NULL,对于规则显示而言,这一部份的处理目前没有实际意义,回过头再来看这一段更易理解。final_check成员函数的作用是作最终的标志检查,如果检测失则,则退出*/

    for (m = iptables_matches; m; m = m->next) {

    if (!m->used)

    continue;

    m->final_check(m->mflags);

    }

    if (target)

    target->final_check(target->tflags);

    接着对参数作一些必要的合法性检查:

    /* Fix me: must put inverse options checking here --MN */

    if (optind < argc)

    exit_error(PARAMETER_PROBLEM,

    "unknown arguments found on commandline");

    if (!command)

    exit_error(PARAMETER_PROBLEM, "no command specified");

    if (invert)

    exit_error(PARAMETER_PROBLEM,

    "nothing appropriate following !");

    /*对于如果要进行(CMD_REPLACE | CMD_INSERT | CMD_DELETE | CMD_APPEND)处理来说,如果没有设置来源/目的地址及掩码,则给予它们一个默认值*/

    if (command & (CMD_REPLACE | CMD_INSERT | CMD_DELETE | CMD_APPEND)) {

    if (!(options & OPT_DESTINATION))

    dhostnetworkmask = "0.0.0.0/0";

    if (!(options & OPT_SOURCE))

    shostnetworkmask = "0.0.0.0/0";

    }

    /*对来源/目的地址及掩码进行拆分,它们总是以 addr/mask的形式来出现的,根据’/’前面的字符串取得地址值,根据’/’后面的掩码位数,求得正确的掩码值,值得注意的是,同时要处理主机地址和网络地址的情况*/

    if (shostnetworkmask)

    parse_hostnetworkmask(shostnetworkmask, &saddrs,

    &(fw.ip.smsk), &nsaddrs);

    if (dhostnetworkmask)

    parse_hostnetworkmask(dhostnetworkmask, &daddrs,

    &(fw.ip.dmsk), &ndaddrs);

    /*然后检查来源/目的网络地址的合法性*/

    if ((nsaddrs > 1 || ndaddrs > 1) &&

    (fw.ip.invflags & (IPT_INV_SRCIP | IPT_INV_DSTIP)))

    exit_error(PARAMETER_PROBLEM, "! not allowed with multiple"

    " source or destination IP addresses");

    /*对命令行格式进行合法性检查*/

    generic_opt_check(command, options);

    如果前面只是热身的话,那么从现在开始,就进入实质性阶段了:

    do_command函数最后一个参数handle,是一个指向了具体表,如filter、nat表的句柄,这里判断,如果handle为空,则调用iptc_init,根据table的名称,让handle指针指向相应的表的地址空间,也就是把对应表的所有信息从内核中取出来:

    /* only allocate handle if we weren't called with a handle */

    if (!*handle)

    *handle = iptc_init(*table);

    /*如果获取换败,将试着插入模块,再次获取*/

    if (!*handle) {

    /* try to insmod the module if iptc_init failed */

    iptables_insmod("ip_tables", modprobe);

    *handle = iptc_init(*table);

    /*仍然失败,则退出*/

    if (!*handle)

    exit_error(VERSION_PROBLEM,

    "can't initialize iptables table `%s': %s",

    *table, iptc_strerror(errno));

    /*继续进行一些简单的判断*/

    if (command == CMD_APPEND

    || command == CMD_DELETE

    || command == CMD_INSERT

    || command == CMD_REPLACE) {

    /*List命令不在判断之列,暂时不分析*/

    }

    /*判断命令标志,调用相关函数进行处理*/

    switch (command) {

    case CMD_LIST:

    ret = list_entries(chain,

    options&OPT_VERBOSE,

    options&OPT_NUMERIC,

    options&OPT_EXPANDED,

    options&OPT_LINENUMBERS,

    handle);

    }

    list_entries是规则显示的主要处理函数。

    Options是显示的标志变量:

    OPT_VERBOSE:对应-v

    OPT_NUMERIC:对应-n

    OPT_EXPANDED:对应-x

    OPT_LINENUMBERS: -l

    看来很简单,说了这么大一圈子,就是调用 iptc_init获取表的规则信息,调用list_entries函数显示规则。 

    1.1 表的查找

    再回到iptc_init 函数上来,它根据表名,从内核获取对应的表的相关信息,handle是一个iptc_handle_t类型的指针,在libiptc.c中,有如下定义:

    /* Transparent handle type. */

    typedef struct iptc_handle *iptc_handle_t;

    在Libip4tc中:

    #define STRUCT_TC_HANDLE struct iptc_handle

    在Libiptc.c中,可以找到STRUCT_TC_HANDLE的定义:

    STRUCT_TC_HANDLE

    {

    /* Have changes been made? */

    int changed;

    /* Size in here reflects original state. */

    STRUCT_GETINFO info;

    struct counter_map *counter_map;

    /* Array of hook names */

    const char **hooknames;

    /* Cached position of chain heads (NULL = no cache). */

    unsigned int cache_num_chains;

    unsigned int cache_num_builtins;

    /* Rule iterator: terminal rule */

    STRUCT_ENTRY *cache_rule_end;

    /* Number in here reflects current state. */

    unsigned int new_number;

    STRUCT_GET_ENTRIES entries;

    };

    再来看看iptc_init函数,同样在在Libip4tc中,有如下定义:

    #define TC_INIT iptc_init

    在Libiptc.c中,可以看到函数的实现,基本上iptables与内核的交互,都是使用setsockopt函数来实现的,对于获取取规是信息来说,标志位是SO_GET_INFO,而从内核返回回来的规则信息是一个STRUCT_GETINFO结构:

    TC_HANDLE_T TC_INIT(const char *tablename)

    {

    TC_HANDLE_T h;

    STRUCT_GETINFO info;

    unsigned int i;

    int tmp;

    socklen_t s;

    iptc_fn = TC_INIT;

    if (sockfd != -1)

    close(sockfd);

    /*为获取信息打开一个套接字接口*/

    sockfd = socket(TC_AF, SOCK_RAW, IPPROTO_RAW);

    if (sockfd < 0)

    return NULL;

    s = sizeof(info);

    if (strlen(tablename) >= TABLE_MAXNAMELEN) {

    errno = EINVAL;

    return NULL;

    }

    strcpy(info.name, tablename);

    /*获取规则信息*/

    if (getsockopt(sockfd, TC_IPPROTO, SO_GET_INFO, &info, &s) < 0)

    return NULL;

    if ((h = alloc_handle(info.name, info.size, info.num_entries))

    == NULL)

    return NULL;

    /* Too hard --RR */

    #if 0

    sprintf(pathname, "%s/%s", IPT_LIB_DIR, info.name);

    dynlib = dlopen(pathname, RTLD_NOW);

    if (!dynlib) {

    errno = ENOENT;

    return NULL;

    }

    h->hooknames = dlsym(dynlib, "hooknames");

    if (!h->hooknames) {

    errno = ENOENT;

    return NULL;

    }

    #else

    h->hooknames = hooknames;

    #endif

    /* Initialize current state */

    h->info = info;

    h->new_number = h->info.num_entries;

    for (i = 0; i < h->info.num_entries; i++)

    h->counter_map[i]

    = ((struct counter_map){COUNTER_MAP_NORMAL_MAP, i});

    h->entries.size = h->info.size;

    tmp = sizeof(STRUCT_GET_ENTRIES) + h->info.size;

    if (getsockopt(sockfd, TC_IPPROTO, SO_GET_ENTRIES, &h->entries,

    &tmp) < 0) {

    free(h);

    return NULL;

    }

    CHECK(h);

    return h;

    }

    函数为h分配空间,然后赋予相应的值。要理解这个函数,还需要了解STRUCT_GETINFO结构和分配内存空间的函数alloc_handle。

    #define STRUCT_GETINFO struct ipt_getinfo

    /* The argument to IPT_SO_GET_INFO */

    struct ipt_getinfo

    {

    /* Which table: caller fills this in. */

    char name[IPT_TABLE_MAXNAMELEN];

    /* Kernel fills these in. */

    /* Which hook entry points are valid: bitmask */

    unsigned int valid_hooks;

    /* Hook entry points: one per netfilter hook. */

    unsigned int hook_entry[NF_IP_NUMHOOKS];

    /* Underflow points. */

    unsigned int underflow[NF_IP_NUMHOOKS];

    /* Number of entries */

    unsigned int num_entries;

    /* Size of entries. */

    unsigned int size;

    };

    /* Allocate handle of given size */

    static TC_HANDLE_T

    alloc_handle(const char *tablename, unsigned int size, unsigned int num_rules)

    {

    size_t len;

    TC_HANDLE_T h;

    len = sizeof(STRUCT_TC_HANDLE)

    + size

    + num_rules * sizeof(struct counter_map);

    if ((h = malloc(len)) == NULL) {

    errno = ENOMEM;

    return NULL;

    }

    h->changed = 0;

    h->cache_num_chains = 0;

    h->cache_chain_heads = NULL;

    h->counter_map = (void *)h

    + sizeof(STRUCT_TC_HANDLE)

    + size;

    strcpy(h->info.name, tablename);

    strcpy(h->entries.name, tablename);

    return h;

    }

    函数list_entries用于显示表下边的链:

    /*显示某table下的chain*/

    static int

    list_entries(const ipt_chainlabel chain, int verbose, int numeric,

    int expanded, int linenumbers, iptc_handle_t *handle)

    {

    int found = 0;

    unsigned int format;

    const char *this;

    format = FMT_OPTIONS; /*设置输出格式*/

    if (!verbose) /*详细输出模式,,对应-v ,显示匹配的包的数目,包的大小等*/

    format |= FMT_NOCOUNTS;

    else

    format |= FMT_VIA;

    if (numeric) /*对应-n,以数字的形式输出地址和端口*/

    format |= FMT_NUMERIC;

    if (!expanded) /*对应-x,expand numbers (display exact values)*/

    format |= FMT_KILOMEGAGIGA;

    if (linenumbers) /*输出行的编号*/

    format |= FMT_LINENUMBERS;

    for (this = iptc_first_chain(handle); /*遍历当前table的所有chain*/

    this;

    this = iptc_next_chain(handle))

    {

    const struct ipt_entry *i;

    unsigned int num;

    if (chain && strcmp(chain, this) != 0) /*匹配指定chain名,这里用chain &&,即若不指定chain,输出所有chain*/

    continue;

    if (found) printf(" ");

    print_header(format, this, handle); /*输出标头*/

    i = iptc_first_rule(this, handle); /*移至当前chain的第一条规则*/

    num = 0;

    while (i) {

    print_firewall(i, /*输出当前规则*/

    iptc_get_target(i, handle),

    num++,

    format,

    *handle);

    i = iptc_next_rule(i, handle); /*移至下一条规则*/

    }

    found = 1;

    }

    errno = ENOENT;

    return found;

    }

    可见,在函数中,由iptc_first_chain和iptc_next_chain实现了遍历,iptc_first_rule和iptc_next_rule实现了链中规是的遍历,print_firewall函数在遍历到规则的时候,向终端输出防火墙规则,其第二个参数iptc_get_target又用于获取规则的target。

    前面提到过,在内核中,handler指针指向了从内核中返回的对应的表的信息,handler对应的结构中,涉及到链的结构成员主要有两个:

    struct chain_cache *cache_chain_heads;

    struct chain_cache *cache_chain_iteration;

    前者用于指向第一个链,后者指向当前链。而struct chain_cache的定义如下:

    struct chain_cache

    {

    char name[TABLE_MAXNAMELEN]; /*链名*/

    STRUCT_ENTRY *start; /*该链的第一条规则*/

    STRUCT_ENTRY *end; /*该链的最后一条规则*/

    };

    理解了这两个成员,和结构struct chain_cache,再来理解链的遍历函数就不难了。所谓链的遍历,就是将handler对应成员的值取出来。

    #define TC_FIRST_CHAIN iptc_first_chain

    #define TC_NEXT_CHAIN iptc_next_chain

    函数TC_FIRST_CHAIN用于返回第一个链:

    /* Iterator functions to run through the chains. */

    const char *

    TC_FIRST_CHAIN(TC_HANDLE_T *handle)

    {

    /*链首为空,则返回NULL*/

    if ((*handle)->cache_chain_heads == NULL

    && !populate_cache(*handle))

    return NULL;

    /*当前链的指针指向链表首部*/

    (*handle)->cache_chain_iteration

    = &(*handle)->cache_chain_heads[0];

    /*返回链的名称*/

    return (*handle)->cache_chain_iteration->name;

    }

    /* Iterator functions to run through the chains. Returns NULL at end. */

    const char *

    TC_NEXT_CHAIN(TC_HANDLE_T *handle)

    {

    /*很简单,用heads开始,用++就可以实现遍历了*/

    (*handle)->cache_chain_iteration++;

    if ((*handle)->cache_chain_iteration - (*handle)->cache_chain_heads

    == (*handle)->cache_num_chains)

    return NULL;

    return (*handle)->cache_chain_iteration->name;

    }

    规则的遍历

    当遍历到某个链的时候,接下来,就需要遍历当前链下的所有规则了,输出之了。前面叙述了链的遍历,那么规则的遍历,应该就是根据链的名称,找到对应的成员结构struct chain_cache ,这里面包含了当前链的第一条规则与最后一条规则的指针:

    #define TC_FIRST_RULE iptc_first_rule

    #define TC_NEXT_RULE iptc_next_rule

    /* Get first rule in the given chain: NULL for empty chain. */

    const STRUCT_ENTRY *

    TC_FIRST_RULE(const char *chain, TC_HANDLE_T *handle)

    {

    struct chain_cache *c;

    c = find_label(chain, *handle); /*根据链名,返回对应的struct chain_cache结构*/

    if (!c) { /*没有找到,返回NULL*/

    errno = ENOENT;

    return NULL;

    }

    /* Empty chain: single return/policy rule */

    if (c->start == c->end) /*如果是空链*/

    return NULL;

    (*handle)->cache_rule_end = c->end;

    return c->start; /*返回链的首条规则*/

    }

    /* Returns NULL when rules run out. */

    const STRUCT_ENTRY *

    TC_NEXT_RULE(const STRUCT_ENTRY *prev, TC_HANDLE_T *handle)

    {

    if ((void *)prev + prev->next_offset

    == (void *)(*handle)->cache_rule_end)

    return NULL;

    return (void *)prev + prev->next_offset;

    }

    要更解TC_NEXT_RULE函数是如何实现查找下一条规则的,需要首先理解STRUCT_ENTRY结构:

    #define STRUCT_ENTRY struct ipt_entry

    ipt_entry结构用于存储链的规则,每一个包过滤规则可以分成两部份:条件和动作。前者在Netfilter中,称为match,后者称之为target。Match又分为两部份,一部份为一些基本的元素,如来源/目的地址,进/出网口,协议等,对应了struct ipt_ip,我们常常将其称为标准的match,另一部份match则以插件的形式存在,是动态可选择,也允许第三方开发的,常常称为扩展的match,如字符串匹配,p2p匹配等。同样,规则的target也是可扩展的。这样,一条规则占用的空间,可以分为:struct ipt_ip+n*match+n*target,(n表示了其个数,这里的match指的是可扩展的match部份)。基于此,规则对应的结构如下:

    /* 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; /*标准的match部份*/

    /* Mark with fields that we care about. */

    unsigned int nfcache;

    /* Size of ipt_entry + matches */

    u_int16_t target_offset; /*target的开始位置,是sizeof(ipt_entry+n*match)*/

    /* Size of ipt_entry + matches + target */

    u_int16_t next_offset; /*下一条规则相对于本条规则的位置,是sizeof(ipt_entry)加上所有的match,以及所有的target*/

    /* Back pointer */

    unsigned int comefrom;

    /* Packet and byte counters. */

    struct ipt_counters counters;

    /* The matches (if any), then the target. */

    unsigned char elems[0];

    };

    有了这样的基础,就不难理解遍历规则中,寻找下一条规则语句:

    return (void *)prev + prev->next_offset;

    即是本条规则加上下一条规则的偏移值。

    输出规则

    print_firewall 函数用于规则的输出:

    print_firewall(i, iptc_get_target(i, handle), num++,format,*handle);

    i:当前的规则;

    iptc_get_target(i, handle):用于规则的target部份的处理;

    num:规则序号;

    format:输出格式;

    handler:表的信息;

    /* e is called `fw' here for hysterical raisins */

    static void

    print_firewall(const struct ipt_entry *fw,

    const char *targname,

    unsigned int num,

    unsigned int format,

    const iptc_handle_t handle)

    {

    struct iptables_target *target = NULL;

    const struct ipt_entry_target *t;

    u_int8_t flags;

    char buf[BUFSIZ];

    if (!iptc_is_chain(targname, handle))

    target = find_target(targname, TRY_LOAD);

    else

    target = find_target(IPT_STANDARD_TARGET, LOAD_MUST_SUCCEED);

    t = ipt_get_target((struct ipt_entry *)fw);

    flags = fw->ip.flags;

    if (format & FMT_LINENUMBERS) /*输出行号*/

    printf(FMT("%-4u ", "%u "), num+1);

    if (!(format & FMT_NOCOUNTS)) { /*详细模式,列出计数器*/

    print_num(fw->counters.pcnt, format); /*匹配当前规则的数据包个数*/

    print_num(fw->counters.bcnt, format); /*--------------------大小*/

    }

    /*输出目标名称*/

    if (!(format & FMT_NOTARGET)) /*目标名称,即拦截、通过等动作*/

    printf(FMT("%-9s ", "%s "), targname);

    /*输出协议名*/

    fputc(fw->ip.invflags & IPT_INV_PROTO ? '!' : ' ', stdout);

    {

    char *pname = proto_to_name(fw->ip.proto, format&FMT_NUMERIC);

    if (pname)

    printf(FMT("%-5s", "%s "), pname);

    else

    printf(FMT("%-5hu", "%hu "), fw->ip.proto);

    }

    /*输出选项字段*/

    if (format & FMT_OPTIONS) {

    if (format & FMT_NOTABLE)

    fputs("opt ", stdout);

    fputc(fw->ip.invflags & IPT_INV_FRAG ? '!' : '-', stdout); //#define IP_FW_INV_FRAG 0x0080 /* Invert the sense of IP_FW_F_FRAG. */

    fputc(flags & IPT_F_FRAG ? 'f' : '-', stdout); //#define IP_FW_F_FRAG 0x0004 /* Set if rule is a fragment rule */

    fputc(' ', stdout);

    }

    if (format & FMT_VIA) {

    char iface[IFNAMSIZ+2];

    if (fw->ip.invflags & IPT_INV_VIA_IN) { /*输入端口取反标志*/

    iface[0] = '!'; /*设置取反标志符*/

    iface[1] = '';

    }

    else iface[0] = '';

    if (fw->ip.iniface[0] != '') {

    strcat(iface, fw->ip.iniface);

    }

    else if (format & FMT_NUMERIC) strcat(iface, "*");

    else strcat(iface, "any");

    printf(FMT(" %-6s ","in %s "), iface); /*输出输入端口*/

    if (fw->ip.invflags & IPT_INV_VIA_OUT) { /*输出端口取反标志*/

    iface[0] = '!'; /*设置取反标志符*/

    iface[1] = '';

    }

    else iface[0] = '';

    if (fw->ip.outiface[0] != '') {

    strcat(iface, fw->ip.outiface);

    }

    else if (format & FMT_NUMERIC) strcat(iface, "*");

    else strcat(iface, "any");

    printf(FMT("%-6s ","out %s "), iface); /*输出输出端口*/

    } /*end print in/out interface */

    /*输出源地址及掩码*/

    fputc(fw->ip.invflags & IPT_INV_SRCIP ? '!' : ' ', stdout); /*源地址取反标志*/

    if (fw->ip.smsk.s_addr == 0L && !(format & FMT_NUMERIC)) /*源地址为任意*/

    printf(FMT("%-19s ","%s "), "anywhere");

    else {

    if (format & FMT_NUMERIC)

    sprintf(buf, "%s", addr_to_dotted(&(fw->ip.src)));

    else

    sprintf(buf, "%s", addr_to_anyname(&(fw->ip.src)));

    strcat(buf, mask_to_dotted(&(fw->ip.smsk)));

    printf(FMT("%-19s ","%s "), buf);

    }

    /*输出目的地址及掩码*/

    fputc(fw->ip.invflags & IPT_INV_DSTIP ? '!' : ' ', stdout);

    if (fw->ip.dmsk.s_addr == 0L && !(format & FMT_NUMERIC))

    printf(FMT("%-19s","-> %s"), "anywhere");

    else {

    if (format & FMT_NUMERIC)

    sprintf(buf, "%s", addr_to_dotted(&(fw->ip.dst)));

    else

    sprintf(buf, "%s", addr_to_anyname(&(fw->ip.dst)));

    strcat(buf, mask_to_dotted(&(fw->ip.dmsk)));

    printf(FMT("%-19s","-> %s"), buf);

    }

    if (format & FMT_NOTABLE)

    fputs(" ", stdout);

    /*输出扩展的MATCH*/

    IPT_MATCH_ITERATE(fw, print_match, &fw->ip, format & FMT_NUMERIC);

    /*输出扩展的TARGET*/

    if (target) {

    if (target->print)

    /* Print the target information. */

    target->print(&fw->ip, t, format & FMT_NUMERIC);

    } else if (t->u.target_size != sizeof(*t))

    printf("[%u bytes of unknown target data] ",

    t->u.target_size - sizeof(*t));

    if (!(format & FMT_NONEWLINE))

    fputc(' ', stdout);

    }

    函数分为三部份:

    输出标准的match部份;

    输出扩展的match部份,调用IPT_MATCH_ITERATE实现;

    调用对应的target的print函数输出target部份。

    match的输出

    IPT_MATCH_ITERATE 宏用于实现扩展match的遍历。这个宏定义在内核include/Linux/Netfilter-ipv4/Ip_tables.h中:

    #define IPT_MATCH_ITERATE(e, fn, args...)

    ({

    unsigned int __i;

    int __ret = 0;

    struct ipt_entry_match *__match;

    for (__i = sizeof(struct ipt_entry);

    __i < (e)->target_offset;

    __i += __match->u.match_size) {

    __match = (void *)(e) + __i;

    __ret = fn(__match , ## args); /*每找到一个match,就交由fn函数来处理,在print_firewall中,传递过来的是函数print_match*/

    if (__ret != 0)

    break;

    }

    __ret;

    })

    要理解这个宏,需要先了解规则的存储,前面提到过,因为match/target都是可变的,所以在内存中,采取了ip_entry+n*match+n*target,即在规则后,是连续的若干个match,而mathc后面,又是若干个target,在结构ip_entry中,成员u_int16_t target_offset;代表了target的偏移地址,即target的开始,match的结束。我们要查到当前规则对应的所有match,需要了解三个要素:

    1、match从哪里开始:起始地址应该是 [当前规则地址+sizeof(struct ipt_entry)];

    2、match从哪里结束:结束地址,应该是 [当前规则地址+target_offet];

    3、每一个match的大小,在内核中,match对应的结构是ipt_entry_match,其成员u.match_size指明了当前match的大小;

    这三点,对应了for循环:

    for (__i = sizeof(struct ipt_entry); __i < (e)->target_offset; __i += __match->u.match_size)

    这样,i就对应了某个match的偏移植,通过:

    __match = (void *)(e) + __i;

    就得到了match的地址。

    再通过

    __ret = fn(__match , ## args);

    输出之。

    fn函数是在print_firewall中,传递过来的是函数print_match。

    static int

    print_match(const struct ipt_entry_match *m,

    const struct ipt_ip *ip,

    int numeric)

    {

    /*根据match名称进行查找,返回一个iptables_match结构,然后调用其中封装的print函数输出该match的信息*/

    struct iptables_match *match = find_match(m->u.user.name, TRY_LOAD);

    if (match) {

    if (match->print)

    match->print(ip, m, numeric);

    else

    printf("%s ", match->name);

    } else {

    if (m->u.user.name[0])

    printf("UNKNOWN match `%s' ", m->u.user.name);

    }

    /* Don't stop iterating. */

    return 0;

    }

    这里涉及到两个重要的结构:

    struct ipt_entry_match:在内核中用于存储扩展match信息

    struct ipt_entry_match

    {

    union {

    struct {

    u_int16_t match_size;

    /* Used by userspace */

    char name[IPT_FUNCTION_MAXNAMELEN];

    } user;

    struct {

    u_int16_t match_size;

    /* Used inside the kernel */

    struct ipt_match *match;

    } kernel;

    /* Total length */

    u_int16_t match_size;

    } u;

    unsigned char data[0];

    };

    struct iptables_match:用于用户级的match存储:

    /* Include file for additions: new matches and targets. */

    struct iptables_match

    {

    /* Match链,初始为NULL */

    struct iptables_match *next;

    /* Match名,和核心模块加载类似,作为动态链接库存在的Iptables Extension的命名规则为libipt_'name'.so */

    ipt_chainlabel name;

    /*版本信息,一般设为NETFILTER_VERSION */

    const char *version;

    /* Match数据的大小,必须用IPT_ALIGN()宏指定对界*/

    size_t size;

    /*由于内核可能修改某些域,因此size可能与确切的用户数据不同,这时就应该把不会被改变的数据放在数据区的前面部分,而这里就应该填写被改变的数据区大小;一般来说,这个值和size相同*/

    size_t userspacesize;

    /*当iptables要求显示当前match的信息时(比如iptables-m ip_ext -h),就会调用这个函数,输出在iptables程序的通用信息之后. */

    void (*help)(void);

    /*初始化,在parse之前调用. */

    void (*init)(struct ipt_entry_match *m, unsigned int *nfcache);

    /*扫描并接收本match的命令行参数,正确接收时返回非0,flags用于保存状态信息*/

    int (*parse)(int c, char **argv, int invert, unsigned int *flags,

    const struct ipt_entry *entry,

    unsigned int *nfcache,

    struct ipt_entry_match **match);

    /* 前面提到过这个函数,当命令行参数全部处理完毕以后调用,如果不正确,应该

    退出(exit_error())*/

    void (*final_check)(unsigned int flags);

    /*当查询当前表中的规则时,显示使用了当前match的规则*/

    void (*print)(const struct ipt_ip *ip,

    const struct ipt_entry_match *match, int numeric);

    /*按照parse允许的格式将本match的命令行参数输出到标准输出,用于iptables-save命令. */

    void (*save)(const struct ipt_ip *ip,

    const struct ipt_entry_match *match);

    /* NULL结尾的参数列表,struct option与getopt(3)使用的结构相同*/

    const struct option *extra_opts;

    /* Ignore these men behind the curtain: */

    unsigned int option_offset;

    struct ipt_entry_match *m;

    unsigned int mflags;

    unsigned int used;

    #ifdef NO_SHARED_LIBS

    unsigned int loaded; /* simulate loading so options are merged properly */

    #endif

    };

    理解了这两个结构后,再来看find_match函数:

    然match是以可扩展的形式表现出来,那么,当然就需要find_match这样的函数将它们一一找出来了。

    前面说过,在输出规则的函数中:

    IPT_MATCH_ITERATE(fw, print_match, &fw->ip, format & FMT_NUMERIC);

    用来遍历每一个match,找到了后,就调用print_match来输出。print_match是调用find_match来查找的:

    struct iptables_match *

    find_match(const char *name, enum ipt_tryload tryload)

    {

    struct iptables_match *ptr;

    for (ptr = iptables_matches; ptr; ptr = ptr->next) {

    if (strcmp(name, ptr->name) == 0)

    break;

    }

    #ifndef NO_SHARED_LIBS

    if (!ptr && tryload != DONT_LOAD) {

    char path[sizeof(IPT_LIB_DIR) + sizeof("/libipt_.so")

    + strlen(name)];

    sprintf(path, IPT_LIB_DIR "/libipt_%s.so", name);

    if (dlopen(path, RTLD_NOW)) {

    /* Found library. If it didn't register itself,

    maybe they specified target as match. */

    ptr = find_match(name, DONT_LOAD);

    if (!ptr)

    exit_error(PARAMETER_PROBLEM,

    "Couldn't load match `%s' ",

    name);

    } else if (tryload == LOAD_MUST_SUCCEED)

    exit_error(PARAMETER_PROBLEM,

    "Couldn't load match `%s':%s ",

    name, dlerror());

    }

    #else

    if (ptr && !ptr->loaded) {

    if (tryload != DONT_LOAD)

    ptr->loaded = 1;

    else

    ptr = NULL;

    }

    if(!ptr && (tryload == LOAD_MUST_SUCCEED)) {

    exit_error(PARAMETER_PROBLEM,

    "Couldn't find match `%s' ", name);

    }

    #endif

    if (ptr)

    ptr->used = 1;

    return ptr;

    }

    分析这个函数,不从开头来看,先看这一段:

    if (!ptr && tryload != DONT_LOAD) {

    char path[sizeof(IPT_LIB_DIR) + sizeof("/libipt_.so")

    + strlen(name)];

    sprintf(path, IPT_LIB_DIR "/libipt_%s.so", name);

    if (dlopen(path, RTLD_NOW)) {

    /* Found library. If it didn't register itself,

    maybe they specified target as match. */

    ptr = find_match(name, DONT_LOAD);

    if (!ptr)

    exit_error(PARAMETER_PROBLEM,

    "Couldn't load match `%s' ",

    name);

    } else if (tryload == LOAD_MUST_SUCCEED)

    exit_error(PARAMETER_PROBLEM,

    "Couldn't load match `%s':%s ",

    name, dlerror());

    }

    函数根据传递过来的match名称,从指定位置,加载对应的共享库,呵呵,这些共享库的源码,全部在Extensions目录下边:

    如果加载它们,那么其_init函数就会被调用。这个初始化函数用来向iptables_match全局结构注册当前match的相关处理函数。(这样,我们可以写我们自己的用户空间的扩展match处理工具了)。注册好后,函数再来调用自己:

    ptr = find_match(name, DONT_LOAD);

    递归回来后,呵呵,就是开头那一段了,我们需要从已经注册好的全局结构中查找与当前match名称相同的iptables_match成员,因为该成员中封装了print函数,这样就可以顺利地输出来了:

    比如,加载了libptc_tcp.so,它用来处理tcp的扩展,我们来看Extensions/libiptc_tcp.c:

    static

    struct iptables_match tcp

    = { NULL,

    "tcp",

    IPTABLES_VERSION,

    IPT_ALIGN(sizeof(struct ipt_tcp)),

    IPT_ALIGN(sizeof(struct ipt_tcp)),

    &help,

    &init,

    &parse,

    &final_check,

    &print,

    &save,

    opts };

    void

    _init(void)

    {

    register_match(&tcp);

    }

    构建了一个

    iptables_match结构,其间有其对应的所有用户空间工具函数,如分析命令行、输出、保存……

    然后,就调用register_match函数将其插入至全局结构iptables_match当中:

    void

    register_match(struct iptables_match *me)

    {

    struct iptables_match **i;

    if (strcmp(me->version, program_version) != 0) {

    fprintf(stderr, "%s: match `%s' v%s (I'm v%s). ",

    program_name, me->name, me->version, program_version);

    exit(1);

    }

    if (find_match(me->name, DONT_LOAD)) {

    fprintf(stderr, "%s: match `%s' already registered. ",

    program_name, me->name);

    exit(1);

    }

    if (me->size != IPT_ALIGN(me->size)) {

    fprintf(stderr, "%s: match `%s' has invalid size %u. ",

    program_name, me->name, me->size);

    exit(1);

    }

    /* Append to list. */

    for (i = &iptables_matches; *i; i = &(*i)->next);

    me->next = NULL;

    *i = me;

    me->m = NULL;

    me->mflags = 0;

    }

    函数就是一个建立链表的过程。不进一步分析了。

  • 相关阅读:
    Java中Runnable和Thread的区别
    JAVA Swing 事件监听
    java 监听机制模拟(JButton按钮监听机制)
    java事件处理机制(自定义事件)
    oracle之检查点(Checkpoint)
    linux内核值shmmax问题
    如何在VMware虚拟机间建立共享磁盘?
    Mysql 不同版本 说明
    mysql 概念和逻辑架构
    mysql 在大型应用中的架构演变
  • 原文地址:https://www.cnblogs.com/liuhongru/p/11425688.html
Copyright © 2011-2022 走看看