通过这段时间的学习,基本上熟悉了netfilter模块,为了进一步加深对netfilter的认识以及理解iptables与netfilter的联系,准备添加一个match模块。
在看到网关产品会有一个公网限制的功能,就想着添加一个公网数目限制的功能。
该模块实现的功能, 通过该match我们可以设置能够通过网关上网的数目,要想进行公网限制,就需要根据mac地址进行限制操作,这个模块放到ebtables里或许更好,或者在linux桥接模块里借助CAM表更容易实现。为了最小限度的修改协议栈的代码,放在netfilter里更好,由于对ebtables的研究不深入,即放在iptables里实现。
最后的命令如下:
# iptables -N access_limit
# iptables -I FORWARD -j access_limit
# iptables -A access_limit -i br0 -m maclimit --maclimit 1 --expire 30000 -j ACCEPT
其中 --maclimit 后面跟的数据是公网限制的数目, --expire后面跟的是超时时间(毫秒级),即对于一个已记录的mac,若在超时时间以后,没有收到相应的数据包,则删除该项。
1.相应的数据结构:
该数据结构为学习的mac地址表,当限制的数目不为0时,即对连接跟踪数据流进行学习,学习到一个mac地址,则将一个__xt_mac_limit_entry添加到xt_mac_limit_table->head链表中,当学习到的数目大于设置的数目后,则对后续学习到的mac数据,直接丢掉。
其中max_count代表限制的数目,mac_count代表当前已学习到的mac数目,gc_interval代表垃圾回收时间,也就是 --expire 后面的值。
typedef struct __xt_mac_limit_table
{
struct list_head head;
u_int32_t max_count;
u_int32_t mac_count;
spinlock_t lock;
u_int32_t gc_interval; /* gc interval */
bool init_flag;/*主要用于初始化*/
}xt_mac_limit_table;
该结构体对应于一个mac项,对于一个新的数据流,当学习到以后,则创建一个该结构的变量。其中list用于与xt_mac_limit_table->head进行链接,timeout定时器用于垃圾回收,即当网关在一定时间内没有再收到该mac地址相关的数据后,则释放该变量占用的内存,其中超时时间即为gc_interval,src_addr为源mac地址。
typedef struct __xt_mac_limit_entry
{
struct list_head list;
unsigned char src_addr[ETH_ALEN];
struct timer_list timeout;
}xt_mac_limit_entry;
/*tablesͨҲipt_entry_match->data[]ָ*/
typedef struct __xt_mac_limit_info
{
u_int32_t max_count;
u_int32_t expire;
}xt_mac_limit_info;
2. 功能实现
当maclimit为0时,则不进行公网接入限制,此时就不对数据包进行学习,全部允许通过。
当maclimit不为0时,则启动公网限制,对每一个数据流都进行mac学习,当学习到的mac超过限制后,则丢弃新的数据流。
而对于已学习到的mac,我们也要进行一个垃圾回收机制,即当在一定时间内收不到该mac相关的数据后,则删除该mac项,这就是所谓的垃圾回收,对于网络协议栈相关的功能开发,很多时候都要考虑到垃圾回收机制。
闲话少说,下面就是代码实现。 Kernel: Linux3.X linux-3.x/net/netfilter/makefile: obj-m += xt_maclimit.o linux-3.x/net/netfilter/xt_maclimit.c #include <linux/netfilter/xt_maclimit.h> static xt_mac_limit_table *mac_list_table = NULL; static xt_mac_limit_entry *mac_limit_find_entry(unsigned char *macaddr) { xt_mac_limit_entry *pos, *n; if(macaddr == NULL) return NULL; list_for_each_entry_safe(pos, n,&mac_list_table->head, list) { if(memcmp(pos->src_addr, macaddr, ETH_ALEN) == 0) { return pos; } } return NULL; } static void mac_limit_delete_mac_entry(unsigned char *macaddr) { xt_mac_limit_entry *pos, *n; if(macaddr == NULL) return ; list_for_each_entry_safe(pos, n,&mac_list_table->head, list) { if(memcmp(pos->src_addr, macaddr, ETH_ALEN) == 0) { list_del(&pos->list); printk(KERN_INFO"%s: delete entry with mac addr is %x:%x:%x:%x:%x:%x\n", __FUNCTION__, pos->src_addr[0], pos->src_addr[1], pos->src_addr[2], pos->src_addr[3], pos->src_addr[4], pos->src_addr[5]); kfree(pos); mac_list_table->mac_count--; return; } } } static void death_by_timeout(unsigned long mac_ent) { xt_mac_limit_entry *mac_entry = (xt_mac_limit_entry *)mac_ent; printk(KERN_INFO "%s: macaddr is %x:%x:%x:%x:%x:%x\n", __FUNCTION__, mac_entry->src_addr[0] ,mac_entry->src_addr[1], mac_entry->src_addr[2], mac_entry->src_addr[3], mac_entry->src_addr[4], mac_entry->src_addr[5]); spin_lock(&mac_list_table->lock); if(!list_empty(&mac_list_table->head)) { mac_limit_delete_mac_entry(mac_entry->src_addr); } spin_unlock(&mac_list_table->lock); } static xt_mac_limit_entry * mac_limit_create_entry(unsigned char *macaddr) { xt_mac_limit_entry *mac_entry; if((macaddr == NULL)||(mac_list_table->mac_count >= mac_list_table->max_count)) return NULL; mac_entry = mac_limit_find_entry(macaddr); if(mac_entry) return mac_entry; mac_entry = kmalloc(sizeof(xt_mac_limit_entry), GFP_ATOMIC); if((mac_entry == NULL)||(mac_list_table->mac_count >= mac_list_table->max_count)) { printk(KERN_INFO "%s: can not create new mac entry macaddr is %x:%x:%x:%x:%x:%x\n", __FUNCTION__, macaddr[0] , macaddr[1], macaddr[2], macaddr[3], macaddr[4], macaddr[5]); return NULL; } memcpy(mac_entry->src_addr, macaddr, ETH_ALEN); init_timer(&mac_entry->timeout); mac_entry->timeout.data = (unsigned long)mac_entry; mac_entry->timeout.function = death_by_timeout; mac_entry->timeout.expires = jiffies + msecs_to_jiffies(mac_list_table->gc_interval); add_timer(&mac_entry->timeout); list_add(&mac_entry->list, &mac_list_table->head); mac_list_table->mac_count++; return mac_entry; } static bool mac_limit_mt(const struct sk_buff *skb, struct xt_action_param *par) { bool ret; char mac_addr[ETH_ALEN]; xt_mac_limit_entry *mac_entry; memset(mac_addr, 0, ETH_ALEN); if (skb->dev == NULL || skb->dev->type != ARPHRD_ETHER) return false; if (skb_mac_header(skb) < skb->head) return false; if (skb_mac_header(skb) + ETH_HLEN > skb->data) return false; spin_lock(&mac_list_table->lock); if(mac_list_table->max_count == 0) { printk(KERN_INFO"%s: NO LIMIT\n", __FUNCTION__); ret = true; } else { memcpy(mac_addr, eth_hdr(skb)->h_source, ETH_ALEN); mac_entry = mac_limit_find_entry(mac_addr); if(mac_entry == NULL) { if(mac_list_table->mac_count >= mac_list_table->max_count) { goto hotdrop; } else { mac_entry = mac_limit_create_entry(mac_addr); if(mac_entry == NULL) goto hotdrop; printk(KERN_INFO "%s: add entry success,macaddr is %x:%x:%x:%x:%x:%x\n", __FUNCTION__, mac_entry->src_addr[0] ,mac_entry->src_addr[1], mac_entry->src_addr[2], mac_entry->src_addr[3], mac_entry->src_addr[4], mac_entry->src_addr[5]); ret = true; } } else { printk(KERN_INFO "%s: find entry , macaddr is %x:%x:%x:%x:%x:%x\n", __FUNCTION__, mac_entry->src_addr[0] ,mac_entry->src_addr[1], mac_entry->src_addr[2], mac_entry->src_addr[3], mac_entry->src_addr[4], mac_entry->src_addr[5]); if(del_timer(&mac_entry->timeout)) { printk("%s: update timeout expire\n", __FUNCTION__); mac_entry->timeout.expires = jiffies + msecs_to_jiffies(mac_list_table->gc_interval); add_timer(&mac_entry->timeout); } ret = true; } } spin_unlock(&mac_list_table->lock); return ret; hotdrop: spin_unlock(&mac_list_table->lock); printk(KERN_INFO "%s: drop packets\n", __FUNCTION__); par->hotdrop = true; return false; } static int mac_limit_checkentry(const struct xt_mtchk_param *par) { const xt_mac_limit_info *info = par->matchinfo; spin_lock(&mac_list_table->lock); if(mac_list_table->init_flag == false) { printk("%s: mac list table init\n", __FUNCTION__); mac_list_table->init_flag = true; INIT_LIST_HEAD(&mac_list_table->head); mac_list_table->max_count = info->max_count; mac_list_table->mac_count = 0; mac_list_table->gc_interval = info->expire; } printk(KERN_INFO"%s: max num is %d\n", __FUNCTION__, info->max_count); spin_unlock(&mac_list_table->lock); return 0; } static void mac_limit_destroy(const struct xt_mtdtor_param *par) { xt_mac_limit_entry *pos, *n; spin_lock(&mac_list_table->lock); printk(KERN_INFO"%s: destory mac list table\n", __FUNCTION__); if(mac_list_table->max_count != 0) { if(!list_empty(&mac_list_table->head)) { list_for_each_entry_safe(pos, n,&mac_list_table->head, list) { if(timer_pending(&pos->timeout)) del_timer(&pos->timeout); list_del(&pos->list); kfree(pos); } INIT_LIST_HEAD(&mac_list_table->head); } } mac_list_table->init_flag = false; spin_unlock(&mac_list_table->lock); } static struct xt_match mac_limit_mt_reg __read_mostly = { .name = "maclimit", .revision = 0, .family = NFPROTO_UNSPEC, .match = mac_limit_mt, .checkentry = mac_limit_checkentry, .destroy = mac_limit_destroy, .matchsize = sizeof(xt_mac_limit_info), .hooks = (1 << NF_INET_PRE_ROUTING) | (1 << NF_INET_LOCAL_IN) | (1 << NF_INET_FORWARD), .me = THIS_MODULE, }; static int __init mac_limit_mt_init(void) { mac_list_table = kmalloc(sizeof(xt_mac_limit_table),GFP_ATOMIC); spin_lock_init(&mac_list_table->lock); mac_list_table->init_flag = false; return xt_register_match(&mac_limit_mt_reg); } static void __exit mac_limit_mt_exit(void) { kfree(mac_list_table); xt_unregister_match(&mac_limit_mt_reg); } module_init(mac_limit_mt_init); module_exit(mac_limit_mt_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("jerry_chg"); MODULE_DESCRIPTION("Xtables:access limit"); MODULE_ALIAS("ipt_maclimit"); include/linux/netfilter/xt_maclimit.h #ifndef _XT_MAC_LIMIT_H #define _XT_MAC_LIMIT_H #include <linux/module.h> #include <linux/if_arp.h> #include <linux/if_ether.h> #include <linux/etherdevice.h> #include <linux/netfilter_ipv4.h> #include <linux/netfilter_ipv6.h> #include <linux/netfilter/x_tables.h> #include <linux/spinlock.h> #include <linux/random.h> #include <linux/jhash.h> #include <linux/slab.h> #include <linux/vmalloc.h> #include <linux/proc_fs.h> #include <linux/seq_file.h> #include <linux/list.h> #include <linux/skbuff.h> #include <linux/mm.h> #include <linux/in.h> #include <linux/ip.h> #if IS_ENABLED(CONFIG_IP6_NF_IPTABLES) #include <linux/ipv6.h> #include <net/ipv6.h> #endif #include <net/net_namespace.h> #include <net/netns/generic.h> #include <linux/netfilter_ipv4/ip_tables.h> #include <linux/netfilter_ipv6/ip6_tables.h> #include <linux/mutex.h> /*mac count limit table */ typedef struct __xt_mac_limit_table { struct list_head head; u_int32_t max_count; u_int32_t mac_count; spinlock_t lock; u_int32_t gc_interval; /* gc interval */ bool init_flag; }xt_mac_limit_table; /*for a mac struct*/ typedef struct __xt_mac_limit_entry { struct list_head list; unsigned char src_addr[ETH_ALEN]; struct timer_list timeout; }xt_mac_limit_entry; /*tablesͨҲipt_entry_match->data[]ָ*/ typedef struct __xt_mac_limit_info { u_int32_t max_count; u_int32_t expire; }xt_mac_limit_info; #endif /*_XT_MAC_LIMIT_H*/ Iptables下添加一个match还是比较简单的,代码如下: iptables-1.4.20/extensions/libxt_maclimit.c iptables-1.4.20/include/linux/netfilter/xt_maclimit.h xt_maclimit.h: #ifndef _XT_MAC_LIMIT_H #define _XT_MAC_LIMIT_H struct xt_maclimit_info { uint32_t limit_count; uint32_t expire; }; #endif /*_XT_MAC_H*/ libxt_maclimit.c: #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #include <xtables.h> #include <linux/netfilter/x_tables.h> #include <linux/netfilter/xt_maclimit.h> enum { O_MAC_LIMIT = 0, O_MAC_EXPIRE = 1, }; static void maclimit_help(void) { printf( "mac match options:\n" "--maclimit max_count\n" "--expire after which time are idle entries expired?\n" " mac count limit module\n"); } #define s struct xt_maclimit_info static const struct xt_option_entry maclimit_opts[] = { {.name = "maclimit", .id = O_MAC_LIMIT, .type = XTTYPE_UINT32, .flags = XTOPT_MAND | XTOPT_PUT, XTOPT_POINTER(s, limit_count) }, {.name = "expire", .id = O_MAC_EXPIRE, .type = XTTYPE_UINT32, .flags = XTOPT_MAND | XTOPT_PUT, XTOPT_POINTER(s, expire) }, XTOPT_TABLEEND, }; #undef s static int maclimit_max_count_parse(const char *max, struct xt_maclimit_info *info) { uintmax_t v; char *end; if (!xtables_strtoul(max, &end, &v, 0, 0xffffffff)) { xtables_error(PARAMETER_PROBLEM, "bad value for option " "\"--maclimit\", or out of range (0-32)."); } return v; } static int maclimit_expire_parse(const char *expire, struct xt_maclimit_info *info) { uintmax_t v; char *end; if (!xtables_strtoul(expire, &end, &v, 0, 0xffffffff)) { xtables_error(PARAMETER_PROBLEM, "bad value for option " "\"--maclimit\", or out of range (0-32)."); } return v; } static void maclimit_parse(struct xt_option_call *cb) { struct xt_maclimit_info *maclimitinfo = cb->data; struct xt_option_entry *entry = cb->entry; xtables_option_parse(cb); switch(entry->id) { case O_MAC_LIMIT: maclimitinfo->limit_count = maclimit_max_count_parse(cb->arg, maclimitinfo); break; case O_MAC_EXPIRE: maclimitinfo->expire = maclimit_expire_parse(cb->arg, maclimitinfo); break; } printf("maclimit count is %d\n", maclimitinfo->limit_count); printf("expire is %d\n", maclimitinfo->expire); } static void print_maclimit(const uint32_t maclimit) { printf("mac limit count 0x%x", maclimit); } static void print_mac_expire(const uint32_t expire) { printf("mac entry expire 0x%x", expire); } static void maclimit_print(const void *ip, const struct xt_entry_match *match, int numeric) { const struct xt_maclimit_info *info = (void *)match->data; print_maclimit(info->limit_count); print_mac_expire(info->expire); } static void maclimit_save(const void *ip, const struct xt_entry_match *match) { const struct xt_maclimit_info *info = (void *)match->data; printf(" --maclimit"); print_maclimit(info->limit_count); print_mac_expire(info->expire); } static struct xtables_match maclimit_match = { .family = NFPROTO_IPV4, .name = "maclimit", .version = XTABLES_VERSION, .size = XT_ALIGN(sizeof(struct xt_maclimit_info)), .userspacesize = XT_ALIGN(sizeof(struct xt_maclimit_info)), .help = maclimit_help, .x6_parse = maclimit_parse, .print = maclimit_print, .save = maclimit_save, .x6_options = maclimit_opts, }; void _init(void) { xtables_register_match(&maclimit_match); }
上文只是简单的实现了公网接入限制,没有在iptables中增加对ipv6的支持,仅仅在iptables中增加了对ipv4的支持,通过以上代码能够简单实现公网接入限制,这也加深了我对netfilter模块中增加match的认识。