Linux netfilter 学习笔记 之十五 netfilter模块添加一个match

时间:2021-02-03 18:23:24

通过这段时间的学习,基本上熟悉了netfilter模块,为了进一步加深对netfilter的认识以及理解iptablesnetfilter的联系,准备添加一个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_intervalsrc_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. 功能实现

maclimit0时,则不进行公网接入限制,此时就不对数据包进行学习,全部允许通过。

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的认识。