linux ebtables tool

ebtables

ebtables就是以太网桥防火墙,以太网桥工作在数据链路层,ebtables主要用来过滤数据链路层数据包。
linux用户空间ebtables tool用于配置各种netfilter功能,配置到内核,由内核来实现各种过滤规则。
本文主要介绍ebtables tool框架。
源代码:https://ebtables.netfilter.org/downloads/latest.html
ebtables包含几种编译配置,包括c/s架构,动态命令行输入,单独命令行输入等。
ebtablesd.c和ebtablesu.c 为一组,ebtablesd.c编译作为守护程序,ebtablesu.c 编译作物用户接口程序,两者之间通过管道方式通信。
ebtables-restore.c 编译为动态命令行输入程序,等待用户输入ebtables配置命令
ebtables-standalone.c编译为单独命令行程序,用户运行程序时必须带ebtables配置参数。

extensions\目录下各个文件实现不同的过滤配置,注册到ebtables 程序管理的链表中。各个文件编译为动态库。

以ebtables-standalone.c为例分析。

ebtables-standalone.c

ebtables -t broute -A BROUTING -p ipv4 -i eth0 --ip-dst 172.16.1.1 -j DROP
在BROUTING 点broute 添加过滤规则

static struct ebt_u_replace replace;
void ebt_early_init_once();

int main(int argc, char *argv[])
{
	ebt_silent = 0;
	ebt_early_init_once(); 初始化各个过滤配置,extensions\目录下各个配置
	strcpy(replace.name, "filter");//默认设置为filter表
	do_command(argc, argv, EXEC_STYLE_PRG, &replace);
	return 0;
}
do_command(int argc, char *argv[], int exec_style,
               struct ebt_u_replace *replace_)
{
     
    #def 需要先从kernel获取支持的过滤配置
	replace->flags &= OPT_KERNELDATA; /* ebtablesd needs OPT_KERNELDATA */
	replace->selected_chain = -1;
	replace->command = 'h';
	#def 申请new_entry 用于存储配置参数
	if (!new_entry) {
		new_entry = (struct ebt_u_entry *)malloc(sizeof(struct ebt_u_entry));
		if (!new_entry)
			ebt_print_memory();
	}
	/* Put some sane values in our new entry */
	ebt_initialize_entry(new_entry);
	new_entry->replace = replace;

#def 解析配置参数,存储到new_entry中,包括-A:D:C:I:N:E:X::L::Z::F::P:Vhi:o:j:c:p:s:d:t:M: 和ebt_options,ebt_options后续讲解构成
while ((c = getopt_long(argc, argv,
	   "-A:D:C:I:N:E:X::L::Z::F::P:Vhi:o:j:c:p:s:d:t:M:", ebt_options, NULL)) != -1) {
		switch (c) {
		case “A”
		
			replace->command = c;
			replace->flags |= OPT_COMMAND;
			#def 先从kernel获取支持的过滤配置,包括支持的链,表,规则等
			if (!(replace->flags & OPT_KERNELDATA))
				ebt_get_kernel_table(replace, 0);
		 ......

else if (c == 'j') {
				ebt_check_option2(&(replace->flags), OPT_JUMP);
				for (i = 0; i < NUM_STANDARD_TARGETS; i++)
					if (!strcmp(optarg, ebt_standard_targets[i])) {
						t = ebt_find_target(EBT_STANDARD_TARGET);
						((struct ebt_standard_target *) t->t)->verdict = -i - 1;
						break;
					}


				if ((i = ebt_get_chainnr(replace, optarg)) != -1) {
					if (i < NF_BR_NUMHOOKS)
						ebt_print_error2("Don't jump to a standard chain");
					t = ebt_find_target(EBT_STANDARD_TARGET);
					((struct ebt_standard_target *) t->t)->verdict = i - NF_BR_NUMHOOKS;
					break;
				} else {
					/* Must be an extension then */
					struct ebt_u_target *t;

					t = ebt_find_target(optarg);
					/* -j standard not allowed either */
					if (!t || t == (struct ebt_u_target *)new_entry->t)
						ebt_print_error2("Illegal target name '%s'", optarg);
					#def -j 对应target 执行的动作 
					new_entry->t = (struct ebt_entry_target *)t;
					ebt_find_target(EBT_STANDARD_TARGET)->used = 0;
					t->used = 1;
				}
				break;
			} else if (c == 's') {
			..........
		case ‘p’:
					ebt_check_option2(&(replace->flags), OPT_PROTOCOL);
			if (ebt_check_inverse2(optarg))
				new_entry->invflags |= EBT_IPROTO;

			new_entry->bitmask &= ~((unsigned int)EBT_NOPROTO);
			#def 对于 -p ipv4   strtol计算后,i为0,buffer为ipv4
			i = strtol(optarg, &buffer, 16);
			if (*buffer == '\0' && (i < 0 || i > 0xFFFF))
				ebt_print_error2("Problem with the specified protocol");
			if (*buffer != '\0') {
				struct ethertypeent *ent;

				if (!strcasecmp(optarg, "LENGTH")) {
					new_entry->bitmask |= EBT_802_3;
					break;
				}
				#def 这里读取系统中/etc/ethertypes,查找支持的协议类型,比如:IPv4	 	0800  	ip ip4 		# Internet IP (IPv4)
				ent = getethertypebyname(optarg);
				if (!ent)
					ebt_print_error2("Problem with the specified Ethernet protocol '%s', perhaps "_PATH_ETHERTYPES " is missing", optarg);
				#def 这里ipv4为0800  	
				new_entry->ethproto = ent->e_ethertype;
			} else
				new_entry->ethproto = i;
				
        #def 不在A:D:C:I:N:E:X::L::Z::F::P:Vhi:o:j:c:p:s:d:t:M中时,走default
		default:
			/* Is it a target option? */
			t = (struct ebt_u_target *)new_entry->t;
			if ((t->parse(c - t->option_offset, argv, argc, new_entry, &t->flags, &t->t))) {

			}

			/* Is it a match_option? */
			for (m = ebt_matches; m; m = m->next)
			    #def 比如--ip-dst 172.16.1.1 在这里匹配解析
				if (m->parse(c - m->option_offset, argv, argc, new_entry, &m->flags, &m->m))
					break;
  			if (m != NULL) {

				if (m->used == 0) {
				    #def 添加匹配match
					ebt_add_match(new_entry, m);
					m->used = 1;
				}
				goto check_extension;
			}

			/* Is it a watcher option? */
			for (w = ebt_watchers; w; w = w->next)
				if (w->parse(c - w->option_offset, argv, argc, new_entry, &w->flags, &w->w))
					break;



			if (ebt_errormsg[0] != '\0')
				return -1;
			if (w->used == 0) {
			    #def 添加匹配watcher
				ebt_add_watcher(new_entry, w);
				w->used = 1;
			}
   }

    ......
	} else if (replace->command == 'A' || replace->command == 'I') {
	    #def 把规则添加到链中
		ebt_add_rule(replace, new_entry, rule_nr);
    .........
		#def 把配置传递到kernel
		ebt_deliver_table(replace);
}

/* The user will use the match, so put it in new_entry. The ebt_u_match
 * pointer is put in the ebt_entry_match pointer. ebt_add_rule will
 * fill in the final value for new->m. Unless the rule is added to a chain,
 * the pointer will keep pointing to the ebt_u_match (until the new_entry
 * is freed). I know, I should use a union for these 2 pointer types... */
void ebt_add_match(struct ebt_u_entry *new_entry, struct ebt_u_match *m)
{
	struct ebt_u_match_list **m_list, *new;

	for (m_list = &new_entry->m_list; *m_list; m_list = &(*m_list)->next);
	new = (struct ebt_u_match_list *)
	   malloc(sizeof(struct ebt_u_match_list));
	if (!new)
		ebt_print_memory();
	*m_list = new;
	new->next = NULL;
	#def 这里把ebt_u_match 强制转换为了ebt_entry_match ,包括name,kernel中通过name找对对应匹配选项
	new->m = (struct ebt_entry_match *)m;
}

#def 比如--ip-dst 172.16.1.1, ebt_u_match 把name和size		对应转换到了ebt_entry_match
static struct ebt_u_match ip_match =
{
	.name		= "ip",
	.size		= sizeof(struct ebt_ip_info),
	.help		= print_help,
	.init		= init,
	.parse		= parse,
	.final_check	= final_check,
	.print		= print,
	.compare	= compare,
	.extra_ops	= opts,
};

struct ebt_entry_match
{
	union {
		char name[EBT_FUNCTION_MAXNAMELEN];
		struct ebt_match *match;
	} u;
	/* size of data */
	unsigned int match_size;
#ifdef KERNEL_64_USERSPACE_32
	unsigned int pad;
#endif
	unsigned char data[0] __attribute__ ((aligned (__alignof__(struct ebt_replace))));
};
void ebt_deliver_table(struct ebt_u_replace *u_repl)
{
	socklen_t optlen;
	struct ebt_replace *repl;

	/* Translate the struct ebt_u_replace to a struct ebt_replace */
	#def 配置数据,由用户空间格式转换为kernel使用格式 
	repl = translate_user2kernel(u_repl);
	if (u_repl->filename != NULL) {
		store_table_in_file(u_repl->filename, repl);
		goto free_repl;
	}
	/* Give the data to the kernel */
	optlen = sizeof(struct ebt_replace) + repl->entries_size;
	if (get_sockfd())
		goto free_repl;
	#def 配置到kernel
	if (!setsockopt(sockfd, IPPROTO_IP, EBT_SO_SET_ENTRIES, repl, optlen))
		goto free_repl;
	if (u_repl->command == 8) { /* The ebtables module may not
	                             * yet be loaded with --atomic-commit */
		ebtables_insmod("ebtables");
		if (!setsockopt(sockfd, IPPROTO_IP, EBT_SO_SET_ENTRIES,
		    repl, optlen))
			goto free_repl;
	}


}

ebt_options

前面main函数调用了如下函数,进行了early init

void ebt_early_init_once()
{
	ebt_iterate_matches(merge_match);
	ebt_iterate_watchers(merge_watcher);
	ebt_iterate_targets(merge_target);
}

void ebt_iterate_matches(void (*f)(struct ebt_u_match *))
{
	struct ebt_u_match *i;
    #def 遍历ebt_matches中选项分别调用merge_match,ebt_matches链表中选项是来自extensions\目录下各个过滤文件注册
	for (i = ebt_matches; i; i = i->next)
		f(i);
}

static void merge_match(struct ebt_u_match *m)
{
	#def ebt_options 为全局变量,作为入参,返回值也复制给ebt_options 
	ebt_options = merge_options
	   (ebt_options, m->extra_ops, &(m->option_offset));
}

#def 函数就是把newopts选项合并到oldopts
static struct option *merge_options(struct option *oldopts,
   const struct option *newopts, unsigned int *options_offset)
{
	unsigned int num_old, num_new, i;
	struct option *merge;

	if (!newopts || !oldopts || !options_offset)
		ebt_print_bug("merge wrong");
	for (num_old = 0; oldopts[num_old].name; num_old++);
	for (num_new = 0; newopts[num_new].name; num_new++);

	global_option_offset += OPTION_OFFSET;
	*options_offset = global_option_offset;
    #def 为newopts申请空间
	merge = malloc(sizeof(struct option) * (num_new + num_old + 1));
	if (!merge)
		ebt_print_memory();
	#def 下面进行合并
	memcpy(merge, oldopts, num_old * sizeof(struct option));
	for (i = 0; i < num_new; i++) {
		merge[num_old + i] = newopts[i];
		merge[num_old + i].val += *options_offset;
	}
	memset(merge + num_old + num_new, 0, sizeof(struct option));
	/* Only free dynamically allocated stuff */
	if (oldopts != ebt_original_options)
		free(oldopts);

	return merge;
}

ebt_options 初始化为公共基础的ebt_original_options配置Default 选项

static struct option *ebt_options = ebt_original_options;

/* Default command line options. Do not mess around with the already
 * assigned numbers unless you know what you are doing */
static struct option ebt_original_options[] =
{
	{ "append"         , required_argument, 0, 'A' },
	{ "insert"         , required_argument, 0, 'I' },
	{ "delete"         , required_argument, 0, 'D' },
	{ "list"           , optional_argument, 0, 'L' },
	{ "Lc"             , no_argument      , 0, 4   },
	{ "Ln"             , no_argument      , 0, 5   },
	{ "Lx"             , no_argument      , 0, 6   },
	{ "Lmac2"          , no_argument      , 0, 12  },
	{ "zero"           , optional_argument, 0, 'Z' },
	{ "flush"          , optional_argument, 0, 'F' },
	{ "policy"         , required_argument, 0, 'P' },
	{ "in-interface"   , required_argument, 0, 'i' },
	{ "in-if"          , required_argument, 0, 'i' },
	{ "logical-in"     , required_argument, 0, 2   },
	{ "logical-out"    , required_argument, 0, 3   },
	{ "out-interface"  , required_argument, 0, 'o' },
	{ "out-if"         , required_argument, 0, 'o' },
	{ "version"        , no_argument      , 0, 'V' },
	{ "help"           , no_argument      , 0, 'h' },
	{ "jump"           , required_argument, 0, 'j' },
	{ "set-counters"   , required_argument, 0, 'c' },
	{ "change-counters", required_argument, 0, 'C' },
	{ "proto"          , required_argument, 0, 'p' },
	{ "protocol"       , required_argument, 0, 'p' },
	{ "db"             , required_argument, 0, 'b' },
	{ "source"         , required_argument, 0, 's' },
	{ "src"            , required_argument, 0, 's' },
	{ "destination"    , required_argument, 0, 'd' },
	{ "dst"            , required_argument, 0, 'd' },
	{ "table"          , required_argument, 0, 't' },
	{ "modprobe"       , required_argument, 0, 'M' },
	{ "new-chain"      , required_argument, 0, 'N' },
	{ "rename-chain"   , required_argument, 0, 'E' },
	{ "delete-chain"   , optional_argument, 0, 'X' },
	{ "atomic-init"    , no_argument      , 0, 7   },
	{ "atomic-commit"  , no_argument      , 0, 8   },
	{ "atomic-file"    , required_argument, 0, 9   },
	{ "atomic-save"    , no_argument      , 0, 10  },
	{ "init-table"     , no_argument      , 0, 11  },
	{ "concurrent"     , no_argument      , 0, 13  },
	{ 0 }
};

ebt_matches

三个全局变量存储动态过滤配置,extensions\目录下各个文件实现不同的过滤配置,注册到ebtables 程序管理的链表中,然后在上面的ebt_early_init_once中添加到ebt_options 中。

struct ebt_u_match *ebt_matches;
struct ebt_u_watcher *ebt_watchers;
struct ebt_u_target *ebt_targets;
例如 ebt_ip.c

#define IP_SOURCE ‘1’
#define IP_DEST ‘2’
#define IP_myTOS ‘3’ /* include/bits/in.h seems to already define IP_TOS */
#define IP_PROTO ‘4’
#define IP_SPORT ‘5’
#define IP_DPORT ‘6’


static struct ebt_u_match ip_match =
{
	.name		= "ip",
	.size		= sizeof(struct ebt_ip_info),
	.help		= print_help,
	.init		= init,
	.parse		= parse,
	.final_check	= final_check,
	.print		= print,
	.compare	= compare,
	.extra_ops	= opts, #def 用于解析opt
};

#def 这个函数非常特殊_init 在动态链接加载时,在main函数前先调用
void _init(void)
{
	#def 注册到ebt_matches链表,前面ebt_early_init_once中使用
	ebt_register_match(&ip_match);
}

/* Used in initialization code of modules */
void ebt_register_match(struct ebt_u_match *m)
{
	int size = EBT_ALIGN(m->size) + sizeof(struct ebt_entry_match); //大小为ebt_entry_match
	struct ebt_u_match **i;

	m->m = (struct ebt_entry_match *)malloc(size);
	if (!m->m)
		ebt_print_memory();
	strcpy(m->m->u.name, m->name);
	m->m->match_size = EBT_ALIGN(m->size);
	m->init(m->m);

	for (i = &ebt_matches; *i; i = &((*i)->next));
	m->next = NULL;
	*i = m;
}

static void init(struct ebt_entry_match *match)
{
	struct ebt_ip_info *ipinfo = (struct ebt_ip_info *)match->data;// data指向了ebt_ip_info 

	ipinfo->invflags = 0;
	ipinfo->bitmask = 0;
}

static struct option opts[] =
{
	{ "ip-source"           , required_argument, 0, IP_SOURCE },
	{ "ip-src"              , required_argument, 0, IP_SOURCE },
	{ "ip-destination"      , required_argument, 0, IP_DEST   },
	{ "ip-dst"              , required_argument, 0, IP_DEST   },
	{ "ip-tos"              , required_argument, 0, IP_myTOS  },
	{ "ip-protocol"         , required_argument, 0, IP_PROTO  },
	{ "ip-proto"            , required_argument, 0, IP_PROTO  },
	{ "ip-source-port"      , required_argument, 0, IP_SPORT  },
	{ "ip-sport"            , required_argument, 0, IP_SPORT  },
	{ "ip-destination-port" , required_argument, 0, IP_DPORT  },
	{ "ip-dport"            , required_argument, 0, IP_DPORT  },
	{ 0 }
};

数据结构

ebt_u_replace为用户空间使用的结构,解析用户输入命令过滤配置都存储在ebt_u_replace中,之后转换为ebt_replace结构,ebt_replace为配置到内核时使用的结构。
ebt_u_replace

struct ebt_u_replace
{
	char name[EBT_TABLE_MAXNAMELEN]; //过滤配置表名字,broute,nat,filter
	unsigned int valid_hooks;
	/* nr of rules in the table */
	unsigned int nentries;
	unsigned int num_chains; //支持的chain hooks,就是NF_BR_BROUTING,NF_BR_POST_ROUTING,NF_BR_LOCAL_OUT,NF_BR_FORWARD,NF_BR_PRE_ROUTINGNF_BR_LOCAL_IN
	unsigned int max_chains;
	struct ebt_u_entries **chains; //指向链上的规则
	/* nr of counters userspace expects back */
	unsigned int num_counters;
	/* where the kernel will put the old counters */
	struct ebt_counter *counters;
	/*
	 * can be used e.g. to know if a standard option
	 * has been specified twice
	 */
	unsigned int flags;
	/* we stick the specified command (e.g. -A) in here */
	char command;
	/*
	 * here we stick the chain to do our thing on (can be -1 if unspecified)
	 */
	int selected_chain; //当前命令要处理的链
	/* used for the atomic option */
	char *filename;
	/* tells what happened to the old rules (counter changes) */
	struct ebt_cntchanges *cc;
};
struct ebt_u_entries
{
	int policy; //默认规则配置
	unsigned int nentries; //规则个数
	/* counter offset for this chain */
	unsigned int counter_offset;
	/* used for udc */
	unsigned int hook_mask;
	char *kernel_start;
	char name[EBT_CHAIN_MAXNAMELEN]; 
	struct ebt_u_entry *entries; //规则链表
};
struct ebt_u_table
{
	char name[EBT_TABLE_MAXNAMELEN];
	void (*check)(struct ebt_u_replace *repl);
	void (*help)(const char **);
	struct ebt_u_table *next;
};

#def 描述规则
struct ebt_u_entry
{   
    //规则参数
	unsigned int bitmask;
	unsigned int invflags;
	uint16_t ethproto;
	char in[IFNAMSIZ];
	char logical_in[IFNAMSIZ];
	char out[IFNAMSIZ];
	char logical_out[IFNAMSIZ];
	unsigned char sourcemac[ETH_ALEN];
	unsigned char sourcemsk[ETH_ALEN];
	unsigned char destmac[ETH_ALEN];
	unsigned char destmsk[ETH_ALEN];
	struct ebt_u_match_list *m_list; //规则匹配
	struct ebt_u_watcher_list *w_list;
	struct ebt_entry_target *t;  //规则执行动作
	struct ebt_u_entry *prev; //执向前一个规则描述
	struct ebt_u_entry *next;
	struct ebt_counter cnt;
	struct ebt_counter cnt_surplus; /* for increasing/decreasing a counter and for option 'C' */
	struct ebt_cntchanges *cc;
	/* the standard target needs this to know the name of a udc when
	 * printing out rules. */
	struct ebt_u_replace *replace;
};
struct ebt_u_match_list
{
	struct ebt_u_match_list *next;
	struct ebt_entry_match *m;
};

struct ebt_u_watcher_list
{
	struct ebt_u_watcher_list *next;
	struct ebt_entry_watcher *w;
};



struct ebt_u_match
{
	char name[EBT_FUNCTION_MAXNAMELEN];
	/* size of the real match data */
	unsigned int size;
	void (*help)(void);
	void (*init)(struct ebt_entry_match *m);
	int (*parse)(int c, char **argv, int argc,
	        const struct ebt_u_entry *entry, unsigned int *flags,
	        struct ebt_entry_match **match);
	void (*final_check)(const struct ebt_u_entry *entry,
	   const struct ebt_entry_match *match,
	   const char *name, unsigned int hookmask, unsigned int time);
	void (*print)(const struct ebt_u_entry *entry,
	   const struct ebt_entry_match *match);
	int (*compare)(const struct ebt_entry_match *m1,
	   const struct ebt_entry_match *m2);
	const struct option *extra_ops;
	/*
	 * can be used e.g. to check for multiple occurance of the same option
	 */
	unsigned int flags;
	unsigned int option_offset;
	struct ebt_entry_match *m;
	/*
	 * if used == 1 we no longer have to add it to
	 * the match chain of the new entry
	 * be sure to put it back on 0 when finished
	 */
	unsigned int used;
	struct ebt_u_match *next;
};

struct ebt_u_watcher
{
	char name[EBT_FUNCTION_MAXNAMELEN];
	unsigned int size;
	void (*help)(void);
	void (*init)(struct ebt_entry_watcher *w);
	int (*parse)(int c, char **argv, int argc,
	   const struct ebt_u_entry *entry, unsigned int *flags,
	   struct ebt_entry_watcher **watcher);
	void (*final_check)(const struct ebt_u_entry *entry,
	   const struct ebt_entry_watcher *watch, const char *name,
	   unsigned int hookmask, unsigned int time);
	void (*print)(const struct ebt_u_entry *entry,
	   const struct ebt_entry_watcher *watcher);
	int (*compare)(const struct ebt_entry_watcher *w1,
	   const struct ebt_entry_watcher *w2);
	const struct option *extra_ops;
	unsigned int flags;
	unsigned int option_offset;
	struct ebt_entry_watcher *w;
	unsigned int used;
	struct ebt_u_watcher *next;
};

struct ebt_u_target
{
	char name[EBT_FUNCTION_MAXNAMELEN];
	unsigned int size;
	void (*help)(void);
	void (*init)(struct ebt_entry_target *t);
	int (*parse)(int c, char **argv, int argc,
	   const struct ebt_u_entry *entry, unsigned int *flags,
	   struct ebt_entry_target **target);
	void (*final_check)(const struct ebt_u_entry *entry,
	   const struct ebt_entry_target *target, const char *name,
	   unsigned int hookmask, unsigned int time);
	void (*print)(const struct ebt_u_entry *entry,
	   const struct ebt_entry_target *target);
	int (*compare)(const struct ebt_entry_target *t1,
	   const struct ebt_entry_target *t2);
	const struct option *extra_ops;
	unsigned int option_offset;
	unsigned int flags;
	struct ebt_entry_target *t;
	unsigned int used;
	struct ebt_u_target *next;
}

ebt_replace
ebt_replace为配置到内核时使用的结构

struct ebt_counter
{
	uint64_t pcnt;
	uint64_t bcnt;
};

struct ebt_replace
{
	char name[EBT_TABLE_MAXNAMELEN];
	unsigned int valid_hooks;
	/* nr of rules in the table */
	unsigned int nentries;
	/* total size of the entries */
	unsigned int entries_size;
	/* start of the chains */
#ifdef KERNEL_64_USERSPACE_32
	uint64_t hook_entry[NF_BR_NUMHOOKS];
#else
	struct ebt_entries *hook_entry[NF_BR_NUMHOOKS]; //指向每个链上规则首指针
#endif
	/* nr of counters userspace expects back */
	unsigned int num_counters;
	/* where the kernel will put the old counters */
#ifdef KERNEL_64_USERSPACE_32
	uint64_t counters;
	uint64_t entries;
#else
	struct ebt_counter *counters;
	char *entries;//每个链上规则,可以多个规则,上面hook_entry[]指向每个链上第一个规则
#endif
};

struct ebt_entries {
	/* this field is always set to zero
	 * See EBT_ENTRY_OR_ENTRIES.
	 * Must be same size as ebt_entry.bitmask */
	unsigned int distinguisher;
	/* the chain name */
	char name[EBT_CHAIN_MAXNAMELEN];
	/* counter offset for this chain */
	unsigned int counter_offset;
	/* one standard (accept, drop, return) per hook */
	int policy;
	/* nr. of entries */
	unsigned int nentries;
	/* entry list */
	char data[0] __attribute__ ((aligned (__alignof__(struct ebt_replace)))); //0数组,正好指向下一个ebt_entries 
};

总结

1,ebt_options = ebt_original_options;为默认配置选项
2,程序运行时动态加载可选性ebt_early_init_once,来自extensions\目录下各个文件实现不同的过滤配置
3,do_command 解析用户输入参数,配置到内核

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值