Chinaunix首页 | 论坛 | 博客
  • 博客访问: 162341
  • 博文数量: 24
  • 博客积分: 2019
  • 博客等级: 大尉
  • 技术积分: 352
  • 用 户 组: 普通用户
  • 注册时间: 2010-01-22 17:36
文章分类
文章存档

2011年(2)

2010年(22)

我的朋友

分类: 网络与安全

2010-03-11 18:06:44

实例分析Iptables基本原理(Part 2 - 参数解析)

 本文源码分析基于Iptables1.4.7.
  本文档版权归hereitis所有,可以自由拷贝/转载,转载时请保持文档的完整性并且注明来源,禁止用于任何商业用途。
  hereitis.cu@gmail.com
  1. Iptables命令行参数
       根据我的理解,Iptables命令行参数分为两类,仍然以在该文第一部分中的命令为例:
          iptables -t nat -A PREROUTING -p tcp -d 172.20.9.80 -i eth0  --sport 1024:65535 --dport 21 -j DNAT --to-destination 192.168.1.100:21
  1. 独立参数:像-t, -A, -p, -d, -i, -j这样的参数都属于独立参数,可以直接被解析为具有明确意义的参数。
  2. 非独立参数:如--sport, --dport这样的参数依赖于-p tcp/udp,只有指定了tcp/udp这样的协议,--sport, --dport才有意义,当-p icmp时这些端口选项就没有意义了。类似地,--to-destination依赖于-j DNAT。在下文中,称-p tcp/udp,-j DNAT这样的参数为--sport,--dport,--to-destination这些参数的宿主参数。

        参数解析主要利用getopt_long()来进行解析。独立参数有相应的case:来进行处理,而对于非独立参数在被解析时,会按需加载相应的match/target,同时将该match/target的非独立参数都merge到global options里。当第一次遇到一个非独立参数时,如第一次遇到--sport,则在default:分支来进行解析:
        这时将会加载tcp_match,同时tcp_match中所有非独立参数都将全部merge到global options里, 回退optind,重新对该非独立参数进行一次解析。这一次的解析才真正地对该参数进行处理,第一次仅仅是加载相关match, 并将其中的全部非独立参数merge到global options.
        这个过程在下面的源码分析中会详细解释。
  1. 源码分析
    源码分析主要是根据上述Iptables示例命令来进行解析,并且假设该条命令是当前系统中第一条Iptables命令,之前所有的相关规则都是空的,主要是便于分析。命令解析在iptables.c中的do_command()函数中。
  1. -t nat
            case 't':
            if (invert) // 不支持-t !NAT
                xtables_error(PARAMETER_PROBLEM,
                       "unexpected ! flag before --table");
            *table = optarg;
            break;
            该选项指定了该条规则是用于nat table。
  1. -A PREROUTING
            case 'A':
            add_command(&command, CMD_APPEND, CMD_NONE,
                    invert);
            chain = optarg;
            break;
            该选项指定了该条规则的chain。
  1. -p tcp
  1. 选项处理
                        case 'p':
            xtables_check_inverse(optarg, &invert, &optind, argc, argv); // 分析是不是-p !tcp并设置invert标志
            set_option(&options, OPT_PROTOCOL, &fw.ip.invflags,
                   invert);

            /* Canonicalize into lower case */
            for (protocol = optarg; *protocol; protocol++)
                *protocol = tolower(*protocol);

            protocol = optarg;
            fw.ip.proto = xtables_parse_protocol(protocol); //协议号查找,如“tcp”将被转换成6

            if (fw.ip.proto == 0
                && (fw.ip.invflags & IPT_INV_PROTO))
                xtables_error(PARAMETER_PROBLEM,
                       "rule would never match protocol");
            break;
  1. 协议转换及查找函数xtables_parse_protocol(xtables.c)
                    const struct xtables_pprot xtables_chain_protos[] = {
    {"tcp",       IPPROTO_TCP},
    {"sctp",      IPPROTO_SCTP},
    {"udp",       IPPROTO_UDP},
    {"udplite",   IPPROTO_UDPLITE},
    {"icmp",      IPPROTO_ICMP},
    {"icmpv6",    IPPROTO_ICMPV6},
    {"ipv6-icmp", IPPROTO_ICMPV6},
    {"esp",       IPPROTO_ESP},
    {"ah",        IPPROTO_AH},
    {"ipv6-mh",   IPPROTO_MH},
    {"mh",        IPPROTO_MH},
    {"all",       0},
    {NULL},
};

u_int16_t
xtables_parse_protocol(const char *s)
{
    unsigned int proto;

    if (!xtables_strtoui(s, NULL, &proto, 0, UINT8_MAX)) {
        struct protoent *pent;

        /* first deal with the special case of 'all' to prevent
         * people from being able to redefine 'all' in nsswitch
         * and/or provoke expensive [not working] ldap/nis/...
         * lookups */
        if (!strcmp(s, "all"))
            return 0;

        if ((pent = getprotobyname(s))) // 根据/etc/protocols查找相应的协议号
            proto = pent->p_proto;
        else {
            unsigned int i;
            for (i = 0; i < ARRAY_SIZE(xtables_chain_protos); ++i) { //根据预先定义的协议数组来查找
                if (xtables_chain_protos[i].name == NULL)
                    continue;

                if (strcmp(s, xtables_chain_protos[i].name) == 0) {
                    proto = xtables_chain_protos[i].num;
                    break;
                }
            }
            if (i == ARRAY_SIZE(xtables_chain_protos))
                xt_params->exit_err(PARAMETER_PROBLEM,
                       "unknown protocol `%s' specified",
                       s);
        }
    }

    return proto;
}
  1. -d 172.20.9.80
    case 'd':
            xtables_check_inverse(optarg, &invert, &optind, argc, argv);
            set_option(&options, OPT_DESTINATION, &fw.ip.invflags,
                   invert);
            dhostnetworkmask = optarg;
            break;
        该选项指定了目的网络的信息。
  1. -i eth0
    1. 选项处理
        case 'i':
            xtables_check_inverse(optarg, &invert, &optind, argc, argv);
            set_option(&options, OPT_VIANAMEIN, &fw.ip.invflags,
                   invert);
            xtables_parse_interface(optarg,
                    fw.ip.iniface,
                    fw.ip.iniface_mask);
            break;
    1. 接口参数解析xtables_parse_interface(xtables.c)
void xtables_parse_interface(const char *arg, char *vianame,
                 unsigned char *mask)
{
    unsigned int vialen = strlen(arg);
    unsigned int i;

    memset(mask, 0, IFNAMSIZ);
    memset(vianame, 0, IFNAMSIZ);

    if (vialen + 1 > IFNAMSIZ)
        xt_params->exit_err(PARAMETER_PROBLEM,
               "interface name `%s' must be shorter than IFNAMSIZ"
               " (%i)", arg, IFNAMSIZ-1);

    strcpy(vianame, arg);
    if (vialen == 0)
        memset(mask, 0, IFNAMSIZ);
    else if (vianame[vialen - 1] == '+') {
        memset(mask, 0xFF, vialen - 1);
        memset(mask + vialen - 1, 0, IFNAMSIZ - vialen + 1);
        /* Don't remove `+' here! -HW */
    } else {
        /* Include nul-terminator in match */
        memset(mask, 0xFF, vialen + 1);
        memset(mask + vialen + 1, 0, IFNAMSIZ - vialen - 1);
        for (i = 0; vianame[i]; i++) {
            if (vianame[i] == '/' ||
                vianame[i] == ' ') {
                fprintf(stderr,
                    "Warning: weird character in interface"
                    " `%s' ('/' and ' ' are not allowed by the kernel).\n",
                    vianame);
                break;
            }
        }
    }
}
                需要解释的是接口名字最后含一个'+'的情况。eth+将表示所有的Ethernet网卡,注意上述函数中对于mask的设置。
  1. --sport 1024:65535
    1. 选项处理
        default:
            if (target == NULL || target->parse == NULL ||
                !target->parse(c - target->option_offset,
                           argv, invert,
                           &target->tflags,
                           &fw, &target->t)) {
                for (matchp = matches; matchp; matchp = matchp->next) { //第一条Iptables命令,所以目前matches为空
                    if (matchp->completed ||
                        matchp->match->parse == NULL)
                        continue;
                    if (matchp->match->parse(c - matchp->match->option_offset,
                             argv, invert,
                             &matchp->match->mflags,
                             &fw,
                             &matchp->match->m))
                        break;
                }
                m = matchp ? matchp->match : NULL;

                /* If you listen carefully, you can
                   actually hear this code suck. */

                /* some explanations (after four different bugs
                 * in 3 different releases): If we encounter a
                 * parameter, that has not been parsed yet,
                 * it's not an option of an explicitly loaded
                 * match or a target.  However, we support
                 * implicit loading of the protocol match
                 * extension.  '-p tcp' means 'l4 proto 6' and
                 * at the same time 'load tcp protocol match on
                 * demand if we specify --dport
'.
                 *
                 * To make this work, we need to make sure:
                 * - the parameter has not been parsed by
                 *   a match (m above)
                 * - a protocol has been specified
                 * - the protocol extension has not been
                 *   loaded yet, or is loaded and unused
                 *   [think of iptables-restore!]
                 * - the protocol extension can be successively
                 *   loaded
                 */
                if (m == NULL
                    && protocol
                    && (!find_proto(protocol, XTF_DONT_LOAD,
                           options&OPT_NUMERIC, NULL)
                    || (find_proto(protocol, XTF_DONT_LOAD,
                            options&OPT_NUMERIC, NULL)
 
                        && (proto_used == 0))
                       )
                    && (m = find_proto(protocol, XTF_TRY_LOAD,
                               options&OPT_NUMERIC, &matches))
) {//该函数将加载tcp_match
                    /* Try loading protocol */
                    size_t size;

                    proto_used = 1;

                    size = IPT_ALIGN(sizeof(struct ipt_entry_match))
                             + m->size;

                    m->m = xtables_calloc(1, size);
                    m->m->u.match_size = size;
                    strcpy(m->m->u.user.name, m->name);
                    xtables_set_revision(m->m->u.user.name,
                             m->revision);
                    if (m->init != NULL)
                        m->init(m->m); 

                    opts = xtables_merge_options(opts,  //这个函数将会把tcp_match相关的所有参数merge到global options
                                 m->extra_opts,
                                 &m->option_offset);
                    if (opts == NULL)
                        xtables_error(OTHER_PROBLEM,
                            "can't alloc memory!");

                    optind--;  //回退,重新解析--sport
                    continue;
                }
                if (!m) {
                    if (c == '?') {
                        if (optopt) {
                            xtables_error(
                               PARAMETER_PROBLEM,
                               "option `%s' "
                               "requires an "
                               "argument",
                               argv[optind-1]);
                        } else {
                            xtables_error(
                               PARAMETER_PROBLEM,
                               "unknown option "
                               "`%s'",
                               argv[optind-1]);
                        }
                    }
                    xtables_error(PARAMETER_PROBLEM,
                           "Unknown arg `%s'", optarg);
                }
            }
               程序中的注释非常重要,解释了什么情况下会按需加载,什么条件下按需加载会工作。
  1. find_proto(iptables.c)
                    static struct xtables_match *
find_proto(const char *pname, enum xtables_tryload tryload,
       int nolookup, struct xtables_rule_match **matches) //参数: ("tcp",  XTF_TRY_LOAD, options&OPT_NUMERIC, &matches)
{
    unsigned int proto;

    if (xtables_strtoui(pname, NULL, &proto, 0, UINT8_MAX)) {
        const char *protoname = proto_to_name(proto, nolookup);

        if (protoname) // "tcp"
            return xtables_find_match(protoname, tryload, matches);
    } else
        return xtables_find_match(pname, tryload, matches);

    return NULL;
}
根据相应的协议类型(tcp),寻找相应的match(tcp_match),按需加载。
  1. xtables_find_match(xtables.c)
struct xtables_match *
xtables_find_match(const char *name, enum xtables_tryload tryload,
           struct xtables_rule_match **matches)
{
    struct xtables_match *ptr;
    const char *icmp6 = "icmp6";

    /* This is ugly as hell. Nonetheless, there is no way of changing
     * this without hurting backwards compatibility */
    if ( (strcmp(name,"icmpv6") == 0) ||
         (strcmp(name,"ipv6-icmp") == 0) ||
         (strcmp(name,"icmp6") == 0) )
        name = icmp6;

    // Part 1: 这部分用于查找某一match是否已经在全局match list中,从而来判断match是否已经注册或者 进行一些兼容性检查
    for (ptr = xtables_matches; ptr; ptr = ptr->next) { // global match list
        if (strcmp(name, ptr->name) == 0) {
            struct xtables_match *clone;

            /* First match of this type: */
            if (ptr->m == NULL)
                break;

            /* Second and subsequent clones */
            clone = xtables_malloc(sizeof(struct xtables_match));
            memcpy(clone, ptr, sizeof(struct xtables_match));
            clone->mflags = 0;
            /* This is a clone: */
            clone->next = clone;

            ptr = clone;
            break;
        }
    }

//Part 2: 加载match相应的.so
#ifndef NO_SHARED_LIBS
    if (!ptr && tryload != XTF_DONT_LOAD && tryload != XTF_DURING_LOAD) {
        ptr = load_extension(xtables_libdir, afinfo->libprefix,
              name, false); //加载相应match的.so文件, libxt_tcp.so

        if (ptr == NULL && tryload == XTF_LOAD_MUST_SUCCEED)
            xt_params->exit_err(PARAMETER_PROBLEM,
                   "Couldn't load match `%s':%s\n",
                   name, dlerror());
    }
#else
    if (ptr && !ptr->loaded) {
        if (tryload != XTF_DONT_LOAD)
            ptr->loaded = 1;
        else
            ptr = NULL;
    }
    if(!ptr && (tryload == XTF_LOAD_MUST_SUCCEED)) {
        xt_params->exit_err(PARAMETER_PROBLEM,
               "Couldn't find match `%s'\n", name);
    }
#endif

    //Part 3: 将Part 2 中加载的match插入到match list中
    if (ptr && matches) {
        struct xtables_rule_match **i;
        struct xtables_rule_match *newentry;

        newentry = xtables_malloc(sizeof(struct xtables_rule_match));

        for (i = matches; *i; i = &(*i)->next) {
            if (strcmp(name, (*i)->match->name) == 0)
                (*i)->completed = true;  //match已经注册完成
        }
        newentry->match = ptr;
        newentry->completed = false;
        newentry->next = NULL;
        *i = newentry;  //将该match插入到match list中
    }

    return ptr;
}
      1. load_exention
        1. load_exention(xtables.c)  
static void *load_extension(const char *search_path, const char *prefix,
    const char *name, bool is_target)
{
    const char *dir = search_path, *next;
    void *ptr = NULL;
    struct stat sb;
    char path[256];

    do {
        next = strchr(dir, ':');
        if (next == NULL)
            next = dir + strlen(dir);
        snprintf(path, sizeof(path), "%.*s/libxt_%s.so",
                 (unsigned int)(next - dir), dir, name); //libxt_tcp.so

        if (dlopen(path, RTLD_NOW) != NULL) { //libxt_tcp.c 中的_init函数将会被调用
            /* Found library.  If it didn't register itself,
               maybe they specified target as match. */
            if (is_target)
                ptr = xtables_find_target(name, XTF_DONT_LOAD);
            else
                ptr = xtables_find_match(name,
                      XTF_DONT_LOAD, NULL);  //通过在全局match list中查找来判断match是否加载成功
        } else if (stat(path, &sb) == 0) {
            fprintf(stderr, "%s: %s\n", path, dlerror());
        }

        if (ptr != NULL)
            return ptr;

        snprintf(path, sizeof(path), "%.*s/%s%s.so",
                 (unsigned int)(next - dir), dir, prefix, name);
        if (dlopen(path, RTLD_NOW) != NULL) {
            if (is_target)
                ptr = xtables_find_target(name, XTF_DONT_LOAD);
            else
                ptr = xtables_find_match(name,
                      XTF_DONT_LOAD, NULL);
        } else if (stat(path, &sb) == 0) {
            fprintf(stderr, "%s: %s\n", path, dlerror());
        }

        if (ptr != NULL)
            return ptr;

        dir = next + 1;
    } while (*next != '\0');

    return NULL;
}
  1. During .so loading
    1.  _init(extentions/libxt_tcp.c)
            void
_init(void)
{
    xtables_register_match(&tcp_match);
}

        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,
};
  1. xtables_register_match(xtables.c)
            void xtables_register_match(struct xtables_match *me)
{
    struct xtables_match **i, *old;

    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);
    }

    /* Revision field stole a char from name. */
    if (strlen(me->name) >= XT_FUNCTION_MAXNAMELEN-1) {
        fprintf(stderr, "%s: target `%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);
    }

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

    old = xtables_find_match(me->name, XTF_DURING_LOAD, NULL); //查找match是否已经注册,以及相关兼容性
    if (old) {
        if (old->revision == me->revision &&
            old->family == me->family) {
            fprintf(stderr,
                "%s: match `%s' already registered.\n",
                xt_params->program_name, me->name);
            exit(1);
        }

        /* Now we have two (or more) options, check compatibility. */
        if (compatible_match_revision(old->name, old->revision)
            && old->revision > me->revision)
            return;

        /* See if new match can be used. */
        if (!compatible_match_revision(me->name, me->revision))
            return;

        /* Prefer !AF_UNSPEC over AF_UNSPEC for same revision. */
        if (old->revision == me->revision && me->family == AF_UNSPEC)
            return;

        /* Delete old one. */
        for (i = &xtables_matches; *i!=old; i = &(*i)->next);
        *i = old->next;
    }

    if (me->size != XT_ALIGN(me->size)) {
        fprintf(stderr, "%s: match `%s' has invalid size %u.\n",
                xt_params->program_name, me->name,
                (unsigned int)me->size);
        exit(1);
    }

    /* Append to list. */
    for (i = &xtables_matches; *i; i = &(*i)->next);  //将match插入到全局match list中
    me->next = NULL;
    *i = me;

    me->m = NULL;
    me->mflags = 0;
}
                      
  1. 回到选项处理
 /* Try loading protocol */
                    size_t size;

                    proto_used = 1;

                    size = IPT_ALIGN(sizeof(struct ipt_entry_match))
                             + m->size;

                    m->m = xtables_calloc(1, size); //为match分配空间
                    m->m->u.match_size = size;
                    strcpy(m->m->u.user.name, m->name);
                    xtables_set_revision(m->m->u.user.name,
                             m->revision);
                    if (m->init != NULL)
                        m->init(m->m); 

                    opts = xtables_merge_options(opts,  //这个函数将会把tcp_match相关的所有参数merge到global options
                                 m->extra_opts,  //tcp_match->extra_opts
                                 &m->option_offset);
                    if (opts == NULL)
                        xtables_error(OTHER_PROBLEM,
                            "can't alloc memory!");

                    optind--;  //回退,重新解析--sport
                    continue;
  1. tcp-match->init(extentions/libxt_tcp.c)
        static void tcp_init(struct xt_entry_match *m)
{
    struct xt_tcp *tcpinfo = (struct xt_tcp *)m->data;

    tcpinfo->spts[1] = tcpinfo->dpts[1] = 0xFFFF; //源端口,目的端口的初始化
}
  1. xtables_merge_options(xtables.c)
        static const struct option tcp_opts[] = { //tcp_match相关的非独立参数
    { "source-port", 1, NULL, '1' },
    { "sport", 1, NULL, '1' }, /* synonym */
    { "destination-port", 1, NULL, '2' },
    { "dport", 1, NULL, '2' }, /* synonym */
    { "syn", 0, NULL, '3' },
    { "tcp-flags", 1, NULL, '4' },
    { "tcp-option", 1, NULL, '5' },
    { .name = NULL }
};
                            struct option *xtables_merge_options(struct option *oldopts,
                     const struct option *newopts,
                     unsigned int *option_offset)
{
    unsigned int num_old, num_new, i;
    struct option *merge;

    if (newopts == NULL)
        return oldopts;

    for (num_old = 0; oldopts[num_old].name; num_old++) ;
    for (num_new = 0; newopts[num_new].name; num_new++) ;

    xt_params->option_offset += 256;
    *option_offset = xt_params->option_offset;

    merge = malloc(sizeof(struct option) * (num_new + num_old + 1));
    if (merge == NULL)
        return NULL;
    memcpy(merge, oldopts, num_old * sizeof(struct option));
    xtables_free_opts(0);    /* Release any old options merged  */
    for (i = 0; i < num_new; i++) {
        merge[num_old + i] = newopts[i];
        merge[num_old + i].val += *option_offset;
    }
    memset(merge + num_old + num_new, 0, sizeof(struct option));

    return merge;
}
  1. --sport 1024:65535(第二次解析)
  1. 选项处理
            default:
            if (target == NULL || target->parse == NULL ||
                !target->parse(c - target->option_offset,
                           argv, invert,
                           &target->tflags,
                           &fw, &target->t)) {
                for (matchp = matches; matchp; matchp = matchp->next) {
                    if (matchp->completed ||
                        matchp->match->parse == NULL)
                        continue;
                    if (matchp->match->parse(c - matchp->match->option_offset,
                             argv, invert,
                             &matchp->match->mflags,
                             &fw,
                             &matchp->match->m)) //调用tcp_match->parse
                        break;
                }
               ......
   忽略的部分本次处理中将不会执行,因为match已经成功加载。
  1. tcp_parse(extensions/libxt_tcp.c)
                    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': // --source-port
        if (*flags & TCP_SRC_PORTS)
            xtables_error(PARAMETER_PROBLEM,
                   "Only one `--source-port' allowed");
        xtables_check_inverse(optarg, &invert, &optind, 0, argv);
        parse_tcp_ports(optarg, tcpinfo->spts); //分析--source-port 参数值,将分析结果存储到match源端口中
        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");
        xtables_check_inverse(optarg, &invert, &optind, 0, argv);
        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");
        xtables_check_inverse(optarg, &invert, &optind, 0, argv);

        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");
        xtables_check_inverse(optarg, &invert, &optind, 0, argv);
        parse_tcp_option(optarg, &tcpinfo->option);
        if (invert)
            tcpinfo->invflags |= XT_TCP_INV_OPTION;
        *flags |= TCP_OPTION;
        break;

    default:
        return 0;
    }

    return 1;
}
  1. parce_tcp_ports(extension/libxt_tcp.c)
static void
parse_tcp_ports(const char *portstring, u_int16_t *ports)
{
    char *buffer;
    char *cp;

    buffer = strdup(portstring);
    if ((cp = strchr(buffer, ':')) == NULL)
        ports[0] = ports[1] = xtables_parse_port(buffer, "tcp");
    else { // 1024:65535
        *cp = '\0';
        cp++;

        ports[0] = buffer[0] ? xtables_parse_port(buffer, "tcp") : 0;
        ports[1] = cp[0] ? xtables_parse_port(cp, "tcp") : 0xFFFF;

        if (ports[0] > ports[1])
            xtables_error(PARAMETER_PROBLEM,
                   "invalid portrange (min > max)");
    }
    free(buffer);
}
  1. --dport 21
            解析过程类似于上述--sport的第二次解析,因为tcp_match已经成功加载。
  1. -j DNAT
    1. 参数处理
        case 'j':
            set_option(&options, OPT_JUMP, &fw.ip.invflags,
                   invert);
            jumpto = parse_target(optarg);
            /* TRY_LOAD (may be chain name) */
            target = xtables_find_target(jumpto, XTF_TRY_LOAD); // 加载target: dnat_tg_reg

            if (target) {
                size_t size;

                size = IPT_ALIGN(sizeof(struct ipt_entry_target))
                    + target->size;

                target->t = xtables_calloc(1, size); //为target分配内存
                target->t->u.target_size = size;
                strcpy(target->t->u.user.name, jumpto);
                xtables_set_revision(target->t->u.user.name,
                         target->revision);
                if (target->init != NULL)
                    target->init(target->t);
                opts = xtables_merge_options(opts,
                             target->extra_opts,
                             &target->option_offset);
                if (opts == NULL)
                    xtables_error(OTHER_PROBLEM,
                           "can't alloc memory!");
            }
            break;
  1. xtables_find_target(xtables.c)
        struct xtables_target *
xtables_find_target(const char *name, enum xtables_tryload tryload)
{
    struct xtables_target *ptr;

    /* Standard target? */
    if (strcmp(name, "") == 0
        || strcmp(name, XTC_LABEL_ACCEPT) == 0
        || strcmp(name, XTC_LABEL_DROP) == 0
        || strcmp(name, XTC_LABEL_QUEUE) == 0
        || strcmp(name, XTC_LABEL_RETURN) == 0)
        name = "standard";

    // Part 1: 查询global target list,看看target是否已经注册
    for (ptr = xtables_targets; ptr; ptr = ptr->next) {
        if (strcmp(name, ptr->name) == 0)
            break;
    }

     //Part 2: 如果target没有注册,则按需加载相应的target
#ifndef NO_SHARED_LIBS
    if (!ptr && tryload != XTF_DONT_LOAD && tryload != XTF_DURING_LOAD) {
        ptr = load_extension(xtables_libdir, afinfo->libprefix,              //libipt_DNAT.so
              name, true);

        if (ptr == NULL && tryload == XTF_LOAD_MUST_SUCCEED)
            xt_params->exit_err(PARAMETER_PROBLEM,
                   "Couldn't load target `%s':%s\n",
                   name, dlerror());
    }
#else
    if (ptr && !ptr->loaded) {
        if (tryload != XTF_DONT_LOAD)
            ptr->loaded = 1;
        else
            ptr = NULL;
    }
    if (ptr == NULL && tryload == XTF_LOAD_MUST_SUCCEED) {
        xt_params->exit_err(PARAMETER_PROBLEM,
               "Couldn't find target `%s'\n", name);
    }
#endif

    if (ptr)
        ptr->used = 1;

    return ptr;
}
                load_extension()在前面已经分析过了,接下来直接分析target的_init()。
  1. _init(extentions/libipt_DNAT.c)
static struct xtables_target dnat_tg_reg = {
    .name        = "DNAT",
    .version    = XTABLES_VERSION,
    .family        = NFPROTO_IPV4,
    .size        = XT_ALIGN(sizeof(struct nf_nat_multi_range)),
    .userspacesize    = XT_ALIGN(sizeof(struct nf_nat_multi_range)),
    .help        = DNAT_help,
    .parse        = DNAT_parse,
    .final_check    = DNAT_check,
    .print        = DNAT_print,
    .save        = DNAT_save,
    .extra_opts    = DNAT_opts,
};

void _init(void)
{
    xtables_register_target(&dnat_tg_reg);
}
  1. xtables_register_target(xtables.c)
void xtables_register_target(struct xtables_target *me)
{
    struct xtables_target *old;

    if (me->version == NULL) {
        fprintf(stderr, "%s: target %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: target \"%s\" has version \"%s\", "
                "but \"%s\" is required.\n",
            xt_params->program_name, me->name,
            me->version, XTABLES_VERSION);
        exit(1);
    }

    /* Revision field stole a char from name. */
    if (strlen(me->name) >= XT_FUNCTION_MAXNAMELEN-1) {
        fprintf(stderr, "%s: target `%s' has invalid name\n",
            xt_params->program_name, me->name);
        exit(1);
    }

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

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

    old = xtables_find_target(me->name, XTF_DURING_LOAD);
    if (old) {
        struct xtables_target **i;

        if (old->revision == me->revision &&
            old->family == me->family) {
            fprintf(stderr,
                "%s: target `%s' already registered.\n",
                xt_params->program_name, me->name);
            exit(1);
        }

        /* Now we have two (or more) options, check compatibility. */
        if (compatible_target_revision(old->name, old->revision)
            && old->revision > me->revision)
            return;

        /* See if new target can be used. */
        if (!compatible_target_revision(me->name, me->revision))
            return;

        /* Prefer !AF_UNSPEC over AF_UNSPEC for same revision. */
        if (old->revision == me->revision && me->family == AF_UNSPEC)
            return;

        /* Delete old one. */
        for (i = &xtables_targets; *i!=old; i = &(*i)->next);
        *i = old->next;
    }

    if (me->size != XT_ALIGN(me->size)) {
        fprintf(stderr, "%s: target `%s' has invalid size %u.\n",
                xt_params->program_name, me->name,
                (unsigned int)me->size);
        exit(1);
    }

    /* Prepend to list. */
    me->next = xtables_targets;
    xtables_targets = me;
    me->t = NULL;
    me->tflags = 0;
}
  1. --to-destination 192.168.1.100:21
    1. 选项处理
        default:
            if (target == NULL || target->parse == NULL ||
                !target->parse(c - target->option_offset,  //调用target的parse
                           argv, invert,
                           &target->tflags,
                           &fw, &target->t)) {
                for (matchp = matches; matchp; matchp = matchp->next) {
                    if (matchp->completed ||
                        matchp->match->parse == NULL)
                        continue;
                    if (matchp->match->parse(c - matchp->match->option_offset,
                             argv, invert,
                             &matchp->match->mflags,
                             &fw,
                             &matchp->match->m))
                        break;
                }
........
  1. DNAT_parse(extensions/libipt_DNAT.c)
    static int DNAT_parse(int c, char **argv, int invert, unsigned int *flags,
                          const void *e, struct xt_entry_target **target)
    {
        const struct ipt_entry *entry = e;
        struct ipt_natinfo *info = (void *)*target;
        int portok;

        if (entry->ip.proto == IPPROTO_TCP
            || entry->ip.proto == IPPROTO_UDP
            || entry->ip.proto == IPPROTO_SCTP
            || entry->ip.proto == IPPROTO_DCCP
            || entry->ip.proto == IPPROTO_ICMP)
            portok = 1;
        else
            portok = 0;

        switch (c) {
        case '1':  //--to-destination
            if (xtables_check_inverse(optarg, &invert, NULL, 0, argv))
                xtables_error(PARAMETER_PROBLEM,
                       "Unexpected `!' after --to-destination");

            if (*flags & IPT_DNAT_OPT_DEST) {
                if (!kernel_version)
                    get_kernel_version();
                if (kernel_version > LINUX_VERSION(2, 6, 10))
                    xtables_error(PARAMETER_PROBLEM,
                           "Multiple --to-destination not supported");
            }
            *target = parse_to(optarg, portok, info);
            /* WTF do we need this for?? */
            if (*flags & IPT_DNAT_OPT_RANDOM)
                info->mr.range[0].flags |= IP_NAT_RANGE_PROTO_RANDOM;
            *flags |= IPT_DNAT_OPT_DEST;
            return 1;

        case '2':
            if (*flags & IPT_DNAT_OPT_DEST) {
                info->mr.range[0].flags |= IP_NAT_RANGE_PROTO_RANDOM;
                *flags |= IPT_DNAT_OPT_RANDOM;
            } else
                *flags |= IPT_DNAT_OPT_RANDOM;
            return 1;

        case '3':
            info->mr.range[0].flags |= IP_NAT_RANGE_PERSISTENT;
            return 1;

        default:
            return 0;
        }
    }
  1. parse_to(extensions/libipt_DNAT.c)
        /* Ranges expected in network order. */
static struct xt_entry_target *
parse_to(char *arg, int portok, struct ipt_natinfo *info)
{
    struct nf_nat_range range;
    char *colon, *dash, *error;
    const struct in_addr *ip;

    memset(&range, 0, sizeof(range));
    colon = strchr(arg, ':');

    if (colon) {
        int port;

        if (!portok)
            xtables_error(PARAMETER_PROBLEM,
                   "Need TCP, UDP, SCTP or DCCP with port specification");

        range.flags |= IP_NAT_RANGE_PROTO_SPECIFIED;

        port = atoi(colon+1);
        if (port <= 0 || port > 65535)
            xtables_error(PARAMETER_PROBLEM,
                   "Port `%s' not valid\n", colon+1);

        error = strchr(colon+1, ':');
        if (error)
            xtables_error(PARAMETER_PROBLEM,
                   "Invalid port:port syntax - use dash\n");

        dash = strchr(colon, '-');
        if (!dash) {
            range.min.tcp.port
                = range.max.tcp.port
                = htons(port);
        } else {
            int maxport;

            maxport = atoi(dash + 1);
            if (maxport <= 0 || maxport > 65535)
                xtables_error(PARAMETER_PROBLEM,
                       "Port `%s' not valid\n", dash+1);
            if (maxport < port)
                /* People are stupid. */
                xtables_error(PARAMETER_PROBLEM,
                       "Port range `%s' funky\n", colon+1);
            range.min.tcp.port = htons(port);
            range.max.tcp.port = htons(maxport);
        }
        /* Starts with a colon? No IP info...*/
        if (colon == arg)
            return &(append_range(info, &range)->t);
        *colon = '\0';
    }

    range.flags |= IP_NAT_RANGE_MAP_IPS;
    dash = strchr(arg, '-');
    if (colon && dash && dash > colon)
        dash = NULL;

    if (dash)
        *dash = '\0';

    ip = xtables_numeric_to_ipaddr(arg);
    if (!ip)
        xtables_error(PARAMETER_PROBLEM, "Bad IP address \"%s\"\n",
               arg);
    range.min_ip = ip->s_addr;
    if (dash) {
        ip = xtables_numeric_to_ipaddr(dash+1);
        if (!ip)
            xtables_error(PARAMETER_PROBLEM, "Bad IP address \"%s\"\n",
                   dash+1);
        range.max_ip = ip->s_addr;
    } else
        range.max_ip = range.min_ip;

    return &(append_range(info, &range)->t);
}
这样将把DNAT相关info构造出来并且保存在target中。
        到此,所有的参数解析已经完成,相应的match,target也已经按需加载成功,相关的结构以及变量信息也已经成功构建,下一步的动作将是把这些信息写入内核中。
阅读(5571) | 评论(1) | 转发(2) |
给主人留下些什么吧!~~

chinaunix网友2011-05-05 13:51:58

太好了!