通过这段时间的学习,基本上熟悉了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的认识。