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 解析用户输入参数,配置到内核