Chinaunix

标题: iptables 源码分析 [打印本页]

作者: 独孤九贱    时间: 2005-12-07 09:17
标题: iptables 源码分析
这里只是一个笔记,我会将进一步修改、整理,补充后的版本发在我的个人主页上:
http://www.skynet.org.cn/viewthr ... 2&page=1#pid262

应用软件源码分析发在这里不知道是否合适:D

最近看了一下iptables的实现,现在想陆续把看的心得贴出来,和大家讨论一下,共同学习……

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

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\n",
                        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函数显示规则。

[ 本帖最后由 独孤九贱 于 2006-3-5 14:49 编辑 ]
作者: 独孤九贱    时间: 2005-12-07 09:18
标题: iptc_init
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;
}
作者: 独孤九贱    时间: 2005-12-07 09:20
标题: list_entries
函数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("\n");

                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;
即是本条规则加上下一条规则的偏移值。
作者: 独孤九贱    时间: 2005-12-07 09:21
输出规则
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] = '\0';
                }
                else iface[0] = '\0';

                if (fw->ip.iniface[0] != '\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] = '\0';
                }
                else iface[0] = '\0';

                if (fw->ip.outiface[0] != '\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('\n', stdout);
}

函数分为三部份:
输出标准的match部份;
输出扩展的match部份,调用IPT_MATCH_ITERATE实现;
调用对应的target的print函数输出target部份。
作者: 独孤九贱    时间: 2005-12-07 09:22
标题: match的输出
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函数:
作者: 独孤九贱    时间: 2005-12-07 09:35
标题: 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'\n",
                                           name);
                } else if (tryload == LOAD_MUST_SUCCEED)
                        exit_error(PARAMETER_PROBLEM,
                                   "Couldn't load match `%s':%s\n",
                                   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'\n", 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'\n",
                                           name);
                } else if (tryload == LOAD_MUST_SUCCEED)
                        exit_error(PARAMETER_PROBLEM,
                                   "Couldn't load match `%s':%s\n",
                                   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).\n",
                        program_name, me->name, me->version, program_version);
                exit(1);
        }

        if (find_match(me->name, DONT_LOAD)) {
                fprintf(stderr, "%s: match `%s' already registered.\n",
                        program_name, me->name);
                exit(1);
        }

        if (me->size != IPT_ALIGN(me->size)) {
                fprintf(stderr, "%s: match `%s' has invalid size %u.\n",
                        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;
}

函数就是一个建立链表的过程。不进一步分析了。
作者: 独孤九贱    时间: 2005-12-07 09:43
暂时写到这里吧,太累了,原以为两三句就可以写完的,target的查找与match是一模一样的,就不写下去了,添加/删除等规则操作,改开空了继续写。

因为看iptables时,没有找到参考资料,所以文章中有些地方肯定有错。另一方面,因为时间关系,没有什么条理。不过还是欢迎感兴趣的朋友,一起讨论。

总结一下显示流程:
int main(int argc, char *argv[])  //主函数

int do_command()                    //命令行处理

*handle = iptc_init(*table);     //从内核获取表的规则快照

list_entries                              //显示规则

        for (this = iptc_first_chain(handle);
             this;
             this = iptc_next_chain(handle)) {
                const struct ipt_entry *i;
                unsigned int num;

                if (chain && strcmp(chain, this) != 0)
                        continue;

                if (found) printf("\n");

                print_header(format, this, handle);
                i = iptc_first_rule(this, handle);

                num = 0;
                while (i) {
                        print_firewall(i,
                                       iptc_get_target(i, handle),
                                       num++,
                                       format,
                                       *handle);
                        i = iptc_next_rule(i, handle);
                }
                found = 1;
        }
这一段是遍历表的每个链,然后再遍历链的每条规则,然后调用print_filrewall输出规则;

输出函数在输出标准的match,如地址/协议/网口后,就调用
        IPT_MATCH_ITERATE(fw, print_match, &fw->ip, format & FMT_NUMERIC);
遍历规则的每一个match,然后调用print_match输出。
print_match中,调用find_match加载共享库,共享库又通过_init函数向主函数的全局结构iptables_match注册自己的处理函数。
这样,根据match的名称,我们在print_match中,就可以很容易地调用其print函数,输出其值了。

target也是一样的原理。

休息一下……
作者: 阿辉    时间: 2005-12-07 09:49
兄弟,不错,谢谢。
作者: albcamus    时间: 2005-12-07 10:25
去掉符号表情吧,要不太乱了
作者: 独孤九贱    时间: 2005-12-07 10:32
原帖由 albcamus 于 2005-12-7 10:25 发表
去掉符号表情吧,要不太乱了


我也没办法去啊,我把字打出来,一发表,它就成那样了
作者: albcamus    时间: 2005-12-07 10:35
原帖由 独孤九贱 于 2005-12-7 10:32 发表


我也没办法去啊,我把字打出来,一发表,它就成那样了


发表的时候有个选项,就在文字编辑框的下面:禁止Smilies,打上对号就好了
作者: 思一克    时间: 2005-12-07 10:53
iptables 仅仅是USER空间的一个改变(添加,删除等操作)KERNEL中的规则表的SHELL。

规则一旦建立,真正起防火墙作用的东西都是内核中的代码,和iptables程序就没有关系了。
作者: 独孤九贱    时间: 2005-12-07 10:56
原帖由 思一克 于 2005-12-7 10:53 发表
iptables 仅仅是USER空间的一个改变(添加,删除等操作)KERNEL中的规则表的SHELL。

规则一旦建立,真正起防火墙作用的东西都是内核中的代码,和iptables程序就没有关系了。


这个是当然啊……可是文章本来就是分析iptables的代码啊,而不是netfilter的代码。

回头写完这个,有时间我会写分析netfilter代码的文章的,不过他涉及到linux网络堆栈太多的东东,不好详述,涉及面太广了。
作者: mq110    时间: 2005-12-07 12:17
我最近正在研究 netfilter架构 提供的钩子函数。实现个简单的包过虑防火墙 感觉还是很简单的。。
现在有个问题。内核模块如何和用户进程通信? 我有个办法可以采取共享内存。不过共享内存方式要有互斥操作。内核有内核的信号量操作接口。用户态有用户态的信号量的操作方式。我有如下问题:
1/ 能否用一种公用的信号量的操作方式??
2/ iptables采取何种方式与内核模块通信??
作者: platinum    时间: 2005-12-07 12:23
个人感觉,iptables 仅仅是一个 userspace 而已,真正核心部分在 netfilter 中
分析一下 ip_conntrack_ftp.c、ip_nat_ftp.c、ipt_mac.c、ipt_state.c 才是正道 ^_^
作者: 独孤九贱    时间: 2005-12-07 12:32
原帖由 mq110 于 2005-12-7 12:17 发表
我最近正在研究 netfilter架构 提供的钩子函数。实现个简单的包过虑防火墙 感觉还是很简单的。。
现在有个问题。内核模块如何和用户进程通信? 我有个办法可以采取共享内存。不过共享内存方式要有互斥操作。内核 ...


内核有接口的:
1、在获取规则的部份,iptc_init中的getsockopt有这方面的代码;
2、设置的话,后面我说添加规则时会提到的;
作者: mq110    时间: 2005-12-07 12:34
原帖由 独孤九贱 于 2005-12-7 12:32 发表


内核有接口的:
1、在获取规则的部份,iptc_init中的getsockopt有这方面的代码;
2、设置的话,后面我说添加规则时会提到的;


其实我想得到的答案是一个 通用的方式。。内核模块与用户进程通信。我想还是共享内存最快。不过我现在不知道如何互斥。

[ 本帖最后由 mq110 于 2005-12-7 12:46 编辑 ]
作者: 独孤九贱    时间: 2005-12-07 12:35
原帖由 platinum 于 2005-12-7 12:23 发表
个人感觉,iptables 仅仅是一个 userspace 而已,真正核心部分在 netfilter 中
分析一下 ip_conntrack_ftp.c、ip_nat_ftp.c、ipt_mac.c、ipt_state.c 才是正道 ^_^


呵呵,理是如此了,不过呢,了解iptables的核心代码,对于明白netfilter的许多细节有很大的帮助。因为netfilter的架构是很容易了解的,但是要明白每一处细节,每一句代码,还是有很多工作要做的。所以,我打算先写一篇关于iptables的,然后再写一篇netfilter的……只是要过年了,业务多了,事情多了,时间少了……
作者: 独孤九贱    时间: 2005-12-07 12:39
原帖由 mq110 于 2005-12-7 12:34 发表


其实我想得到的答案是一个 通用的方式。。内核与用户模块通信。我想还是共享内存最快。不过我现在不知道如何互斥。


linux下的共享内存很简单呀,比win还好用。我现在CGI的身份认证就是用的它了。互斥的话,你可以参考《unix环境高级编译》
作者: mq110    时间: 2005-12-07 12:42
原帖由 独孤九贱 于 2005-12-7 12:39 发表


linux下的共享内存很简单呀,比win还好用。我现在CGI的身份认证就是用的它了。互斥的话,你可以参考《unix环境高级编译》


内核进程和用户进程 共享内存了 你如何实现互斥???

你说的<<Unix环境高级编程>>只是提到了System V 共享内存的方式。他是用户态的。。不是我说的内核进程和用户进程共享内存的方式。

[ 本帖最后由 mq110 于 2005-12-7 12:47 编辑 ]
作者: 独孤九贱    时间: 2005-12-07 13:10
原帖由 mq110 于 2005-12-7 12:42 发表


内核进程和用户进程 共享内存了 你如何实现互斥???

你说的<<Unix环境高级编程>>只是提到了System V 共享内存的方式。他是用户态的。。不是我说的内核进程和用户进程共享内存的方式。


可能我误解你的意思了!

但是偶认为,包过滤函数与用户空间操作函数之间共享规则:
1、可以通过共享空间来完成的,在Win下边做防火墙,偶就是这样子做的;至于linux下内核与用户进程共享内存的互斥,到是没有做过。实现不行,用最笨的方法,自己实现互斥锁应该是可以的吧,回头我做做测试先。

2、netfilter在实现的时候,应该是使用 find_table_lock来实现的,不过偶没有认真地跟下去看这个函数了。如果我没有分析错的话,iptables通过调用setsockopt函数,进过层层调用,最后是
nf_sockopts(),它事实上是操作了:
static struct nf_sockopt_ops ipt_sockopts
= { { NULL, NULL }, PF_INET, IPT_BASE_CTL, IPT_SO_SET_MAX+1, do_ipt_set_ctl,
    IPT_BASE_CTL, IPT_SO_GET_MAX+1, do_ipt_get_ctl, 0, NULL  };

因为在这个结构中,封装了set/get函数。比如get,它把内核空间的规则拷贝到用户这间:
static int
do_ipt_get_ctl(struct sock *sk, int cmd, void *user, int *len)
……
        case IPT_SO_GET_ENTRIES: {
                struct ipt_get_entries get;

                if (*len < sizeof(get)) {
                        duprintf("get_entries: %u < %u\n", *len, sizeof(get));
                        ret = -EINVAL;
                } else if (copy_from_user(&get, user, sizeof(get)) != 0) {
                        ret = -EFAULT;
                } else if (*len != sizeof(struct ipt_get_entries) + get.size) {
                        duprintf("get_entries: %u != %u\n", *len,
                                 sizeof(struct ipt_get_entries) + get.size);
                        ret = -EINVAL;
                } else
                        ret = get_entries(&get, user);
                break;
        }
进入get_entries
static int
get_entries(const struct ipt_get_entries *entries,
            struct ipt_get_entries *uptr)
{
        int ret;
        struct ipt_table *t;


        t = find_table_lock(entries->name, &ret, &ipt_mutex);
        if (t) {
                duprintf("t->private->number = %u\n",
                         t->private->number);
                if (entries->size == t->private->size)
                        ret = copy_entries_to_user(t->private->size,
                                                   t, uptr->entrytable);
                else {
                        duprintf("get_entries: I've got %u not %u!\n",
                                 t->private->size,
                                 entries->size);
                        ret = -EINVAL;
                }
                up(&ipt_mutex);
        } else
                duprintf("get_entries: Can't find %s!\n",
                         entries->name);

        return ret;
}

find_table_lock用于返回查找的表的元素,同时第三个参数应该即为互斥锁,应该就是你所关心的。不过前一遍看它时并没有仔细地分析它。我看完第二遍的时候,或许可以写个详尽的分析出来。

[ 本帖最后由 独孤九贱 于 2005-12-7 13:16 编辑 ]
作者: albcamus    时间: 2005-12-07 13:16
原帖由 mq110 于 2005-12-7 12:42 发表


内核进程和用户进程 共享内存了 你如何实现互斥???

你说的<<Unix环境高级编程>>只是提到了System V 共享内存的方式。他是用户态的。。不是我说的内核进程和用户进程共享内存的方式。



内核和用户可以实现内存共享、并且同步的,不过我也没思路,只记得linuxforum有个人在wheelz版主的指点下做出来了,wheelz版主还给了个小例子──可惜我没保存,这两天linuxforum又上不去。


一般常用的通讯方式有这么几种:1, 注册一个字符设备,实现read/write等---相当于驱动了一个物理上不存在的设备;2, proc文件系统;3, 系统调用,改写某个sys_ni_syscall或者添加新的syscal或者reuse某个已有的syscall;4, netlink,可以man 7 netlink查看,手册中说:netlink, PF_NETLINK - Communication between kernel and user,不过我没接触过,不知道好不好用。

我知道的就这么多了
作者: 独孤九贱    时间: 2005-12-07 13:19
原帖由 albcamus 于 2005-12-7 13:16 发表



内核和用户可以实现内存共享、并且同步的,不过我也没思路,只记得linuxforum有个人在wheelz版主的指点下做出来了,wheelz版主还给了个小例子──可惜我没保存,这两天linuxforum又上不去。


一般常用的 ...


OK,我也试试先……

至防火墙规则,其实把规则放在过滤函数那里,用户空间到时去拷贝也是可以的,而且简单,互斥可以自己简单地实现……
作者: mq110    时间: 2005-12-07 13:23
原帖由 albcamus 于 2005-12-7 13:16 发表



内核和用户可以实现内存共享、并且同步的,不过我也没思路,只记得linuxforum有个人在wheelz版主的指点下做出来了,wheelz版主还给了个小例子──可惜我没保存,这两天linuxforum又上不去。


一般常用的 ...


哪个例子我保存了 需要的话给你发上来。
我就是想改进 wheelz版主的例子 。来做一个通用的内核进程 和 用户进程共享内存的通用方法。
作者: mq110    时间: 2005-12-07 13:27
想法 是内核分配内存。然后存入/proc 文件系统 一个内存地址。用户空间的进程读这个地址。。然后就可以共享内存了。。现在只是不知道如何互斥的操作。。

ps:这几天linuxforum上不去 好是郁闷。。
我比较同意 白金兄的观点。。其实分析netfilter架构。以及tcp/ip协议栈 来实现自己的架构比较好。。
作者: 独孤九贱    时间: 2005-12-07 13:28
标题: 回复 24楼 mq110 的帖子
关于用netlink来做,这篇贴子写得非常好啊:

http://www-128.ibm.com/developer ... cn-newsletter-linux
作者: mq110    时间: 2005-12-07 13:28
或许我应该换个思路了。。
注册字符设备 驱动 这个方式比较好。实现起来也不麻烦。
作者: mq110    时间: 2005-12-07 13:30
原帖由 独孤九贱 于 2005-12-7 13:28 发表
关于用netlink来做,这篇贴子写得非常好啊:

http://www-128.ibm.com/developer ... cn-newsletter-linux


多谢多谢。
作者: 独孤九贱    时间: 2005-12-07 13:35
原帖由 mq110 于 2005-12-7 13:30 发表


多谢多谢。


不用谢了,好像那个很现成,正符合你要求。
我正在请教偶老大,内核态与用户空间用shm做怎么互斥。试验去了。

刚转到linux下边来一两个月,还是个新手,不明白的地方很多,我贴代码的本来目的,就是想多几个人一起讨论学习,上次贴《snort源码分析》就把各人打击惨了,可能是研究它的人太少了。呵呵,以后多交流了……
作者: albcamus    时间: 2005-12-07 13:35
TCP/IP协议栈我在网上找了一些资料,仍然未惬人意。 特别是由于缺少印刷的图书, 进展很慢。
大家可以在emule上找一本叫做the Linux Networking Architectures: Design and Implementation of Network Protocols in the Linux Kernel的书,德国人写的,可惜英文译本不是很好。
作者: 思一克    时间: 2005-12-07 13:37
对于iptables来说,USER mode的IPTABLES和KERNEL的NETFILTER不存在内存共享和LOCK问题。
USER 和 KERNEL 通信就是通过 SET(GET)SOCKOPT实现的。
USER 得到的TABLES是在USER 空间的一个COPY,而不是内核那个TABLE,连格式也是不一样的。
作者: 独孤九贱    时间: 2005-12-07 13:40
原帖由 思一克 于 2005-12-7 13:37 发表
对于iptables来说,USER mode的IPTABLES和KERNEL的NETFILTER不存在内存共享和LOCK问题。
USER 和 KERNEL 通信就是通过 SET(GET)SOCKOPT实现的。
USER 得到的TABLES是在USER 空间的一个COPY,而不是内核那个TABL ...


呵呵,说对了一半吧,刚才这个问题已经讨论过了,不过,不可否认,内核态是用copy_to_user这样的方式,效率也太低了点……要是有几万条规则的话,er……没有尝试过
作者: platinum    时间: 2005-12-07 14:16
独孤九贱、mq110、albcamus、思一克几位老大都太牛了,看得我云山雾绕插不上话,不知道何时才能到达各位老大的境界
作者: 思一克    时间: 2005-12-07 15:38
To platinum, 我恰好研究过iptalbes, 所以略知道一点。看你明白的其他那么多我一点也不知道的问题,自己也感觉惭愧。

To JiuJian,
是的。所以规则太多了(比如 大于大于1000)就十分慢了。这个问题有人贴过。
作者: 思一克    时间: 2005-12-07 15:43
我说的“不存在LOCK问题”是指在iptables user 程序中不用lock,
在setsockopt 的内核代码中要修改 kernel中的table必须lock.
作者: albcamus    时间: 2005-12-07 15:56
术业有专攻嘛! 俺还常去shell版问菜到天荒地老的菜鸟问题呢
作者: youngy411    时间: 2005-12-07 16:02
原帖由 思一克 于 2005-12-7 13:37 发表
对于iptables来说,USER mode的IPTABLES和KERNEL的NETFILTER不存在内存共享和LOCK问题。
USER 和 KERNEL 通信就是通过 SET(GET)SOCKOPT实现的。
USER 得到的TABLES是在USER 空间的一个COPY,而不是内核那个TABL ...


各位老大,我是刚学linux的菜鸟,前几天碰到一个问题,在网络与硬件版上求助过,见帖子:
http://bbs.chinaunix.net/viewthr ... &extra=page%3D3
只是问题一直没有解决,刚刚看到各位的讨论,虽然看不懂,但我觉得我碰到的问题可能跟这个有关系,因为表现就是能查看到的规则与实际运行的规则不一致。如果不麻烦的话,有劳大侠们也帮忙看看吧
作者: 独孤九贱    时间: 2005-12-07 16:09
原帖由 思一克 于 2005-12-7 15:43 发表
我说的“不存在LOCK问题”是指在iptables user 程序中不用lock,
在setsockopt 的内核代码中要修改 kernel中的table必须lock.


哈哈,我就是这个意思!
作者: 思一克    时间: 2005-12-07 16:12
To Jiu3Jian4,

我没有研究过IPTALBES code, 仅仅看过KERNEL的部分。
作者: mq110    时间: 2005-12-07 16:13
原帖由 思一克 于 2005-12-7 16:12 发表
To Jiu3Jian4,

我没有研究过IPTALBES code, 仅仅看过KERNEL的部分。


有空交流交流。
作者: mq110    时间: 2005-12-07 16:17
原帖由 platinum 于 2005-12-7 14:16 发表
独孤九贱、mq110、albcamus、思一克几位老大都太牛了,看得我云山雾绕插不上话,不知道何时才能到达各位老大的境界


白金兄实在是太谦虚了。像al*兄说的那样术业有专攻。

我也是内核刚刚起步。 还靠大家提携。
作者: 独孤九贱    时间: 2005-12-07 16:19
原帖由 mq110 于 2005-12-7 16:17 发表


白金兄实在是太谦虚了。像al*兄说的那样术业有专攻。

我也是内核刚刚起步。 还靠大家提携。


怎么偶发这个贴子,越往后越没有人讨论技术了,越看越像在互相吹捧
作者: 七剑    时间: 2005-12-07 18:13
名字真特别啊.独孤九贱 .不是九剑.
作者: platinum    时间: 2005-12-08 00:22
大家一起研究 netfilter 吧,那个更有意思! ^_^
作者: 独孤九贱    时间: 2005-12-08 08:12
原帖由 七剑 于 2005-12-7 18:13 发表
名字真特别啊.独孤九贱 .不是九剑.


如果是九剑,那比起七剑就成小师弟了。

九贱者,贪嗔、淫荡、嗜赌……
作者: 思一克    时间: 2005-12-08 13:53
内核的netfilter table基本是一个空间上连续的分为几个区域(TABLES)的ARRAY结构,每个区域是一个table, table的每一个项是一个规则。这些在空间上是连续的。ARRAY的大小不等,table的每一个项大小也不同,因为规则复杂程度不同。
次项的开始地址 + 长度 == 下一个项的开始。

规则的比较匹配是用注册函数指针在项中。所以匹配就是调用相应的函数。有一些built-in函数,比如比较ip地址port等。其他的靠register.

内核的table和用户getsockopt得到的表的格式不完全相同。

netfilter匹配规则时,这种数据结构的速度还可以。但设置时就十分慢。因为插入,删除等很费力。

我看代码后的理解。不十分精确。
作者: 独孤九贱    时间: 2005-12-08 14:50
原帖由 思一克 于 2005-12-8 13:53 发表
内核的netfilter table基本是一个空间上连续的分为几个区域(TABLES)的ARRAY结构,每个区域是一个table, table的每一个项是一个规则。这些在空间上是连续的。ARRAY的大小不等,table的每一个项大小也不同,因为规 ...


正解!

所以他们说我分析iptables代码没有多大意思,其实看明白iptables如何操作规则,对Netfilter的规则存储也就基本了解了!
作者: tianrenly    时间: 2005-12-09 09:23
标题: 向楼上的高手学习
向你们学习,向你们致敬!
作者: liyanux    时间: 2005-12-15 13:13
我现在相对linux的tcp/ip实现以及netfilter进行代码学习,各位有什么好的建议
作者: albcamus    时间: 2005-12-15 13:17
原帖由 liyanux 于 2005-12-15 13:13 发表
我现在相对linux的tcp/ip实现以及netfilter进行代码学习,各位有什么好的建议


linuxforum.net的daemeon兄给我的建议: 读《Linux Network Architecture》,2.6的协议栈也没有太大变化;看RFC,有时侯它比书籍更详细准确。

那本书上emule下载吧
作者: 独孤九贱    时间: 2005-12-15 13:22
原帖由 albcamus 于 2005-12-15 13:17 发表


linuxforum.net的daemeon兄给我的建议: 读《Linux Network Architecture》,2.6的协议栈也没有太大变化;看RFC,有时侯它比书籍更详细准确。

那本书上emule下载吧


你说这本,找了半天,没有找着,兄弟可否给个链接之类的……
作者: albcamus    时间: 2005-12-15 13:29
http://www.itpub.net/showthread. ... 117&pagenumber=

不保证好用。 emule下载是最可靠的方法了,我请人打印出来了,不到700页。原著是德文的,amazon上痛骂英文版翻译糟糕
作者: 独孤九贱    时间: 2005-12-15 13:33
原帖由 albcamus 于 2005-12-15 13:29 发表
http://www.itpub.net/showthread. ... 117&pagenumber=

不保证好用。 emule下载是最可靠的方法了,我请人打印出来了,不到700页。原著是德文的,amazon上痛骂英文版翻译糟糕


如果德文到英言语翻译损失了三成,我再变成中文再损失三成,那我只能接受40%的东东了。兄台还有没有好点的中文方面的东东推荐?
作者: albcamus    时间: 2005-12-15 13:50
没有中文的,协议栈我也刚刚开始,还没什么概念。 linuxforum.net内核版置顶贴有几篇opera前辈的分析文章,很精彩。此外,我就是从google找了一堆英文PDF文档,下载了所有的RFC文档。
慢慢来吧
作者: 独孤九贱    时间: 2005-12-16 09:06
原帖由 albcamus 于 2005-12-15 13:50 发表
没有中文的,协议栈我也刚刚开始,还没什么概念。 linuxforum.net内核版置顶贴有几篇opera前辈的分析文章,很精彩。此外,我就是从google找了一堆英文PDF文档,下载了所有的RFC文档。
慢慢来吧


那本书我已经下载下来了,谢谢,大概看了一下,挺好的,不是想像中翻译太烂的东东,至少比前段时间什么资料没有,一个人用Insight3一句句地跟,一句句地想要好得多了。呵呵,以后大家可以多交流交流!
作者: linuxpiao    时间: 2005-12-17 15:14
不錯,先收下.
作者: zhuty    时间: 2006-02-22 19:51
原帖由 mq110 于 2005-12-7 12:17 发表
我最近正在研究 netfilter架构 提供的钩子函数。实现个简单的包过虑防火墙 感觉还是很简单的。。
现在有个问题。


我也对这个感兴趣,但是刚开始,能简单说一下过程吗?谢谢
作者: tomorrow0530    时间: 2006-03-28 12:16
楼主的精神值得敬佩!!辛苦了

现在我只看到了一半,有个疑问
在print_firewall 函数中出现了很多类似这样的代码:
fputc(fw->ip.invflags & IPT_INV_FRAG ? '!' : '-', stdout);        
fw->ip.invflags 与上'!'或'-'会得到什么结果阿?有时还会是' '空格!
不明白,还请解惑!
作者: 独孤九贱    时间: 2006-03-29 09:22
原帖由 tomorrow0530 于 2006-3-28 12:16 发表
楼主的精神值得敬佩!!辛苦了

现在我只看到了一半,有个疑问
在print_firewall 函数中出现了很多类似这样的代码:
fputc(fw->ip.invflags & IPT_INV_FRAG ? '!' : '-', stdout);        
fw->ip ...


err……
很多地方允许取反标志'!',输出的时候要判断,就这样……
1.2.7的实现,特别是链的重组上面(我上面的代码没有贴出这块的分析),我觉得实现得不太好,上个周末看了一下1.3.3的,实现得好一些,个人推荐不要看1.2.7了,呵呵……
作者: tomorrow0530    时间: 2006-04-04 11:35
还有个疑问就是,set/getsockopt是在那里定义的?
我一直都没有找到,也就没法把用户和内核交互这一块弄明白!

我看之前21楼,楼主的回复中有提到最后会用到
static struct nf_sockopt_ops ipt_sockopts
= { { NULL, NULL }, PF_INET, IPT_BASE_CTL, IPT_SO_SET_MAX+1, do_ipt_set_ctl,
    IPT_BASE_CTL, IPT_SO_GET_MAX+1, do_ipt_get_ctl, 0, NULL  };
的do_ipt_get_ctl
这个我是找到了,但他们只怎么连起来了?
作者: 独孤九贱    时间: 2006-04-04 16:03
原帖由 tomorrow0530 于 2006-4-4 11:35 发表
还有个疑问就是,set/getsockopt是在那里定义的?
我一直都没有找到,也就没法把用户和内核交互这一块弄明白!

我看之前21楼,楼主的回复中有提到最后会用到
static struct nf_sockopt_ops ipt_sockopts
=  ...


this's system API.



作者: pengyg1023    时间: 2006-09-06 14:33
原帖由 platinum 于 2005-12-7 12:23 发表
个人感觉,iptables 仅仅是一个 userspace 而已,真正核心部分在 netfilter 中
分析一下 ip_conntrack_ftp.c、ip_nat_ftp.c、ipt_mac.c、ipt_state.c 才是正道 ^_^



不知道各位现在 ip_conntrack_ftp.c分析得怎样
我刚开始分析 ip_conntrack_ftp.c
承蒙各位多多指点
作者: pengyg1023    时间: 2006-09-12 18:07
我在分析MSN文件传输时

抓包出现:


CODE:[Copy to clipboard]Via: MSNSLP/1.0/TLP ;branch={30FE3BB0-83A7-4AFA-B46A-6F4B14FF8BBB} CSeq: 1  Call-ID: {0143A442-DADC-4B1D-9BBE-A50795FA9060} Max-Forwards: 0 Content-Type: application/x-msnmsgr-transrespbody Content-Length: 214  Bridge: TCPv1 Listening: true Hashed-Nonce: {50A7BBEE-CA68-B15C-727F-B959CAB786AD} IPv4Internal-Addrs: 192.168.113.1 192.168.203.1 192.168.0.83 IPv4Internal-Port: 1736
我(192.168.0.192)跟192.168.0.83传文件
为什么会出现3个IP(IPv4Internal-Addrs: 192.168.113.1 192.168.203.1 192.168.0.83 )
而在另一次抓包时只出现一个IP(192.168.0.83)
我的问题是:为什么会出现3个IP 什么情况出现3个IP
(只有一个IP(192.168.0.83)是我感兴趣的  )
作者: pengyg1023    时间: 2006-09-12 18:07
而且 192.168.113.1 192.168.203.1 我们内网根本没有(我们内网IP全部是192.168.0.xxx) 怎么出现的呢

也就是192.168.113.1 192.168.203.1 这两个IP是根本不在的IP
是怎么出现的呢

是MSN协议虚构出来的IP吗
作者: ftr    时间: 2006-09-13 15:22
标题: 能篇译吗
能篇译吗??????




欢迎光临 Chinaunix (http://bbs.chinaunix.net/) Powered by Discuz! X3.2