Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
openeuler
Kernel
提交
39b46fc6
K
Kernel
项目概览
openeuler
/
Kernel
1 年多 前同步成功
通知
8
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
DevOps
流水线
流水线任务
计划
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
K
Kernel
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
DevOps
DevOps
流水线
流水线任务
计划
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
流水线任务
提交
Issue看板
提交
39b46fc6
编写于
11月 29, 2006
作者:
P
Patrick McHardy
提交者:
David S. Miller
12月 02, 2006
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
[NETFILTER]: x_tables: add port of hashlimit match for IPv4 and IPv6
Signed-off-by:
N
Patrick McHardy
<
kaber@trash.net
>
上级
d7a5c324
变更
8
显示空白变更内容
内联
并排
Showing
8 changed file
with
339 addition
and
285 deletion
+339
-285
include/linux/netfilter/Kbuild
include/linux/netfilter/Kbuild
+1
-0
include/linux/netfilter/xt_hashlimit.h
include/linux/netfilter/xt_hashlimit.h
+40
-0
include/linux/netfilter_ipv4/ipt_hashlimit.h
include/linux/netfilter_ipv4/ipt_hashlimit.h
+8
-34
net/ipv4/netfilter/Kconfig
net/ipv4/netfilter/Kconfig
+0
-14
net/ipv4/netfilter/Makefile
net/ipv4/netfilter/Makefile
+0
-1
net/netfilter/Kconfig
net/netfilter/Kconfig
+14
-0
net/netfilter/Makefile
net/netfilter/Makefile
+1
-0
net/netfilter/xt_hashlimit.c
net/netfilter/xt_hashlimit.c
+275
-236
未找到文件。
include/linux/netfilter/Kbuild
浏览文件 @
39b46fc6
...
...
@@ -14,6 +14,7 @@ header-y += xt_dscp.h
header-y += xt_DSCP.h
header-y += xt_esp.h
header-y += xt_helper.h
header-y += xt_hashlimit.h
header-y += xt_length.h
header-y += xt_limit.h
header-y += xt_mac.h
...
...
include/linux/netfilter/xt_hashlimit.h
0 → 100644
浏览文件 @
39b46fc6
#ifndef _XT_HASHLIMIT_H
#define _XT_HASHLIMIT_H
/* timings are in milliseconds. */
#define XT_HASHLIMIT_SCALE 10000
/* 1/10,000 sec period => max of 10,000/sec. Min rate is then 429490
seconds, or one every 59 hours. */
/* details of this structure hidden by the implementation */
struct
xt_hashlimit_htable
;
#define XT_HASHLIMIT_HASH_DIP 0x0001
#define XT_HASHLIMIT_HASH_DPT 0x0002
#define XT_HASHLIMIT_HASH_SIP 0x0004
#define XT_HASHLIMIT_HASH_SPT 0x0008
struct
hashlimit_cfg
{
u_int32_t
mode
;
/* bitmask of IPT_HASHLIMIT_HASH_* */
u_int32_t
avg
;
/* Average secs between packets * scale */
u_int32_t
burst
;
/* Period multiplier for upper limit. */
/* user specified */
u_int32_t
size
;
/* how many buckets */
u_int32_t
max
;
/* max number of entries */
u_int32_t
gc_interval
;
/* gc interval */
u_int32_t
expire
;
/* when do entries expire? */
};
struct
xt_hashlimit_info
{
char
name
[
IFNAMSIZ
];
/* name */
struct
hashlimit_cfg
cfg
;
struct
xt_hashlimit_htable
*
hinfo
;
/* Used internally by the kernel */
union
{
void
*
ptr
;
struct
xt_hashlimit_info
*
master
;
}
u
;
};
#endif
/*_XT_HASHLIMIT_H*/
include/linux/netfilter_ipv4/ipt_hashlimit.h
浏览文件 @
39b46fc6
#ifndef _IPT_HASHLIMIT_H
#define _IPT_HASHLIMIT_H
/* timings are in milliseconds. */
#define IPT_HASHLIMIT_SCALE 10000
/* 1/10,000 sec period => max of 10,000/sec. Min rate is then 429490
seconds, or one every 59 hours. */
#include <linux/netfilter/xt_hashlimit.h>
/* details of this structure hidden by the implementation */
struct
ipt_hashlimit_htable
;
#define IPT_HASHLIMIT_SCALE XT_HASHLIMIT_SCALE
#define IPT_HASHLIMIT_HASH_DIP XT_HASHLIMIT_HASH_DIP
#define IPT_HASHLIMIT_HASH_DPT XT_HASHLIMIT_HASH_DPT
#define IPT_HASHLIMIT_HASH_SIP XT_HASHLIMIT_HASH_SIP
#define IPT_HASHLIMIT_HASH_SPT XT_HASHLIMIT_HASH_SPT
#define IPT_HASHLIMIT_HASH_DIP 0x0001
#define IPT_HASHLIMIT_HASH_DPT 0x0002
#define IPT_HASHLIMIT_HASH_SIP 0x0004
#define IPT_HASHLIMIT_HASH_SPT 0x0008
#define ipt_hashlimit_info xt_hashlimit_info
struct
hashlimit_cfg
{
u_int32_t
mode
;
/* bitmask of IPT_HASHLIMIT_HASH_* */
u_int32_t
avg
;
/* Average secs between packets * scale */
u_int32_t
burst
;
/* Period multiplier for upper limit. */
/* user specified */
u_int32_t
size
;
/* how many buckets */
u_int32_t
max
;
/* max number of entries */
u_int32_t
gc_interval
;
/* gc interval */
u_int32_t
expire
;
/* when do entries expire? */
};
struct
ipt_hashlimit_info
{
char
name
[
IFNAMSIZ
];
/* name */
struct
hashlimit_cfg
cfg
;
struct
ipt_hashlimit_htable
*
hinfo
;
/* Used internally by the kernel */
union
{
void
*
ptr
;
struct
ipt_hashlimit_info
*
master
;
}
u
;
};
#endif
/*_IPT_HASHLIMIT_H*/
#endif
/* _IPT_HASHLIMIT_H */
net/ipv4/netfilter/Kconfig
浏览文件 @
39b46fc6
...
...
@@ -326,20 +326,6 @@ config IP_NF_MATCH_ADDRTYPE
If you want to compile it as a module, say M here and read
<file:Documentation/modules.txt>. If unsure, say `N'.
config IP_NF_MATCH_HASHLIMIT
tristate 'hashlimit match support'
depends on IP_NF_IPTABLES
help
This option adds a new iptables `hashlimit' match.
As opposed to `limit', this match dynamically creates a hash table
of limit buckets, based on your selection of source/destination
ip addresses and/or ports.
It enables you to express policies like `10kpps for any given
destination IP' or `500pps from any given source IP' with a single
IPtables rule.
# `filter', generic and specific targets
config IP_NF_FILTER
tristate "Packet filtering"
...
...
net/ipv4/netfilter/Makefile
浏览文件 @
39b46fc6
...
...
@@ -53,7 +53,6 @@ obj-$(CONFIG_IP_NF_NAT) += iptable_nat.o
obj-$(CONFIG_IP_NF_RAW)
+=
iptable_raw.o
# matches
obj-$(CONFIG_IP_NF_MATCH_HASHLIMIT)
+=
ipt_hashlimit.o
obj-$(CONFIG_IP_NF_MATCH_IPRANGE)
+=
ipt_iprange.o
obj-$(CONFIG_IP_NF_MATCH_OWNER)
+=
ipt_owner.o
obj-$(CONFIG_IP_NF_MATCH_TOS)
+=
ipt_tos.o
...
...
net/netfilter/Kconfig
浏览文件 @
39b46fc6
...
...
@@ -464,5 +464,19 @@ config NETFILTER_XT_MATCH_TCPMSS
To compile it as a module, choose M here. If unsure, say N.
config NETFILTER_XT_MATCH_HASHLIMIT
tristate '"hashlimit" match support'
depends on NETFILTER_XTABLES
help
This option adds a `hashlimit' match.
As opposed to `limit', this match dynamically creates a hash table
of limit buckets, based on your selection of source/destination
addresses and/or ports.
It enables you to express policies like `10kpps for any given
destination address' or `500pps from any given source address'
with a single rule.
endmenu
net/netfilter/Makefile
浏览文件 @
39b46fc6
...
...
@@ -59,3 +59,4 @@ obj-$(CONFIG_NETFILTER_XT_MATCH_STATISTIC) += xt_statistic.o
obj-$(CONFIG_NETFILTER_XT_MATCH_STRING)
+=
xt_string.o
obj-$(CONFIG_NETFILTER_XT_MATCH_TCPMSS)
+=
xt_tcpmss.o
obj-$(CONFIG_NETFILTER_XT_MATCH_PHYSDEV)
+=
xt_physdev.o
obj-$(CONFIG_NETFILTER_XT_MATCH_HASHLIMIT)
+=
xt_hashlimit.o
net/
ipv4/netfilter/ip
t_hashlimit.c
→
net/
netfilter/x
t_hashlimit.c
浏览文件 @
39b46fc6
...
...
@@ -6,23 +6,8 @@
* $Id: ipt_hashlimit.c 3244 2004-10-20 16:24:29Z laforge@netfilter.org $
*
* Development of this code was funded by Astaro AG, http://www.astaro.com/
*
* based on ipt_limit.c by:
* Jrme de Vivie <devivie@info.enserb.u-bordeaux.fr>
* Herv Eychenne <eychenne@info.enserb.u-bordeaux.fr>
* Rusty Russell <rusty@rustcorp.com.au>
*
* The general idea is to create a hash table for every dstip and have a
* seperate limit counter per tuple. This way you can do something like 'limit
* the number of syn packets for each of my internal addresses.
*
* Ideally this would just be implemented as a general 'hash' match, which would
* allow us to attach any iptables target to it's hash buckets. But this is
* not possible in the current iptables architecture. As always, pkttables for
* 2.7.x will help ;)
*/
#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/spinlock.h>
#include <linux/random.h>
#include <linux/jhash.h>
...
...
@@ -31,28 +16,40 @@
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/list.h>
#include <linux/skbuff.h>
#include <linux/in.h>
#include <linux/ip.h>
#include <linux/ipv6.h>
#include <linux/netfilter/x_tables.h>
#include <linux/netfilter_ipv4/ip_tables.h>
#include <linux/netfilter_ipv4/ipt_hashlimit.h>
/* FIXME: this is just for IP_NF_ASSERRT */
#include <linux/netfilter_ipv4/ip_conntrack.h>
#include <linux/netfilter_ipv6/ip6_tables.h>
#include <linux/netfilter/xt_hashlimit.h>
#include <linux/mutex.h>
MODULE_LICENSE
(
"GPL"
);
MODULE_AUTHOR
(
"Harald Welte <laforge@netfilter.org>"
);
MODULE_DESCRIPTION
(
"iptables match for limiting per hash-bucket"
);
MODULE_ALIAS
(
"ipt_hashlimit"
);
MODULE_ALIAS
(
"ip6t_hashlimit"
);
/* need to declare this at the top */
static
struct
proc_dir_entry
*
hashlimit_procdir
;
static
struct
proc_dir_entry
*
hashlimit_procdir4
;
static
struct
proc_dir_entry
*
hashlimit_procdir6
;
static
struct
file_operations
dl_file_ops
;
/* hash table crap */
struct
dsthash_dst
{
__be32
src_ip
;
__be32
dst_ip
;
/* ports have to be consecutive !!! */
union
{
struct
{
__be32
src
;
__be32
dst
;
}
ip
;
struct
{
__be32
src
[
4
];
__be32
dst
[
4
];
}
ip6
;
}
addr
;
__be16
src_port
;
__be16
dst_port
;
};
...
...
@@ -71,9 +68,10 @@ struct dsthash_ent {
}
rateinfo
;
};
struct
ip
t_hashlimit_htable
{
struct
x
t_hashlimit_htable
{
struct
hlist_node
node
;
/* global list of all htables */
atomic_t
use
;
int
family
;
struct
hashlimit_cfg
cfg
;
/* config */
...
...
@@ -81,8 +79,8 @@ struct ipt_hashlimit_htable {
spinlock_t
lock
;
/* lock for list_head */
u_int32_t
rnd
;
/* random seed for hash */
int
rnd_initialized
;
unsigned
int
count
;
/* number entries in table */
struct
timer_list
timer
;
/* timer for gc */
atomic_t
count
;
/* number entries in table */
/* seq_file stuff */
struct
proc_dir_entry
*
pde
;
...
...
@@ -97,41 +95,33 @@ static kmem_cache_t *hashlimit_cachep __read_mostly;
static
inline
int
dst_cmp
(
const
struct
dsthash_ent
*
ent
,
struct
dsthash_dst
*
b
)
{
return
(
ent
->
dst
.
dst_ip
==
b
->
dst_ip
&&
ent
->
dst
.
dst_port
==
b
->
dst_port
&&
ent
->
dst
.
src_port
==
b
->
src_port
&&
ent
->
dst
.
src_ip
==
b
->
src_ip
);
return
!
memcmp
(
&
ent
->
dst
,
b
,
sizeof
(
ent
->
dst
));
}
static
inline
u_int32_t
hash_dst
(
const
struct
ip
t_hashlimit_htable
*
ht
,
const
struct
dsthash_dst
*
dst
)
static
u_int32_t
hash_dst
(
const
struct
x
t_hashlimit_htable
*
ht
,
const
struct
dsthash_dst
*
dst
)
{
return
(
jhash_3words
((
__force
u32
)
dst
->
dst_ip
,
((
__force
u32
)
dst
->
dst_port
<<
16
|
(
__force
u32
)
dst
->
src_port
),
(
__force
u32
)
dst
->
src_ip
,
ht
->
rnd
)
%
ht
->
cfg
.
size
);
return
jhash
(
dst
,
sizeof
(
*
dst
),
ht
->
rnd
)
%
ht
->
cfg
.
size
;
}
static
inline
struct
dsthash_ent
*
__dsthash_find
(
const
struct
ip
t_hashlimit_htable
*
ht
,
struct
dsthash_dst
*
dst
)
static
struct
dsthash_ent
*
dsthash_find
(
const
struct
x
t_hashlimit_htable
*
ht
,
struct
dsthash_dst
*
dst
)
{
struct
dsthash_ent
*
ent
;
struct
hlist_node
*
pos
;
u_int32_t
hash
=
hash_dst
(
ht
,
dst
);
if
(
!
hlist_empty
(
&
ht
->
hash
[
hash
]))
hlist_for_each_entry
(
ent
,
pos
,
&
ht
->
hash
[
hash
],
node
)
{
if
(
dst_cmp
(
ent
,
dst
))
{
if
(
!
hlist_empty
(
&
ht
->
hash
[
hash
]))
{
hlist_for_each_entry
(
ent
,
pos
,
&
ht
->
hash
[
hash
],
node
)
if
(
dst_cmp
(
ent
,
dst
))
return
ent
;
}
}
return
NULL
;
}
/* allocate dsthash_ent, initialize dst, put in htable and lock it */
static
struct
dsthash_ent
*
__dsthash_alloc_init
(
struct
ip
t_hashlimit_htable
*
ht
,
struct
dsthash_dst
*
dst
)
dsthash_alloc_init
(
struct
x
t_hashlimit_htable
*
ht
,
struct
dsthash_dst
*
dst
)
{
struct
dsthash_ent
*
ent
;
...
...
@@ -142,12 +132,11 @@ __dsthash_alloc_init(struct ipt_hashlimit_htable *ht, struct dsthash_dst *dst)
ht
->
rnd_initialized
=
1
;
}
if
(
ht
->
cfg
.
max
&&
atomic_read
(
&
ht
->
count
)
>=
ht
->
cfg
.
max
)
{
if
(
ht
->
cfg
.
max
&&
ht
->
count
>=
ht
->
cfg
.
max
)
{
/* FIXME: do something. question is what.. */
if
(
net_ratelimit
())
printk
(
KERN_WARNING
"
ipt_hashlimit: max count of %u reached
\n
"
,
"
xt_hashlimit: max count of %u reached
\n
"
,
ht
->
cfg
.
max
);
return
NULL
;
}
...
...
@@ -156,52 +145,46 @@ __dsthash_alloc_init(struct ipt_hashlimit_htable *ht, struct dsthash_dst *dst)
if
(
!
ent
)
{
if
(
net_ratelimit
())
printk
(
KERN_ERR
"
ip
t_hashlimit: can't allocate dsthash_ent
\n
"
);
"
x
t_hashlimit: can't allocate dsthash_ent
\n
"
);
return
NULL
;
}
atomic_inc
(
&
ht
->
count
);
ent
->
dst
.
dst_ip
=
dst
->
dst_ip
;
ent
->
dst
.
dst_port
=
dst
->
dst_port
;
ent
->
dst
.
src_ip
=
dst
->
src_ip
;
ent
->
dst
.
src_port
=
dst
->
src_port
;
memcpy
(
&
ent
->
dst
,
dst
,
sizeof
(
ent
->
dst
));
hlist_add_head
(
&
ent
->
node
,
&
ht
->
hash
[
hash_dst
(
ht
,
dst
)]);
ht
->
count
++
;
return
ent
;
}
static
inline
void
__dsthash_free
(
struct
ip
t_hashlimit_htable
*
ht
,
struct
dsthash_ent
*
ent
)
dsthash_free
(
struct
x
t_hashlimit_htable
*
ht
,
struct
dsthash_ent
*
ent
)
{
hlist_del
(
&
ent
->
node
);
kmem_cache_free
(
hashlimit_cachep
,
ent
);
atomic_dec
(
&
ht
->
count
)
;
ht
->
count
--
;
}
static
void
htable_gc
(
unsigned
long
htlong
);
static
int
htable_create
(
struct
ipt_hashlimit_info
*
minfo
)
static
int
htable_create
(
struct
xt_hashlimit_info
*
minfo
,
int
family
)
{
int
i
;
struct
xt_hashlimit_htable
*
hinfo
;
unsigned
int
size
;
struct
ipt_hashlimit_htable
*
hinfo
;
unsigned
int
i
;
if
(
minfo
->
cfg
.
size
)
size
=
minfo
->
cfg
.
size
;
else
{
size
=
((
(
num_physpages
<<
PAGE_SHIFT
)
/
16384
)
/
sizeof
(
struct
list_head
)
);
size
=
((
num_physpages
<<
PAGE_SHIFT
)
/
16384
)
/
sizeof
(
struct
list_head
);
if
(
num_physpages
>
(
1024
*
1024
*
1024
/
PAGE_SIZE
))
size
=
8192
;
if
(
size
<
16
)
size
=
16
;
}
/* FIXME: don't use vmalloc() here or anywhere else -HW */
hinfo
=
vmalloc
(
sizeof
(
struct
ipt_hashlimit_htable
)
+
(
sizeof
(
struct
list_head
)
*
size
)
);
hinfo
=
vmalloc
(
sizeof
(
struct
xt_hashlimit_htable
)
+
sizeof
(
struct
list_head
)
*
size
);
if
(
!
hinfo
)
{
printk
(
KERN_ERR
"
ipt_hashlimit: U
nable to create hashtable
\n
"
);
printk
(
KERN_ERR
"
xt_hashlimit: u
nable to create hashtable
\n
"
);
return
-
1
;
}
minfo
->
hinfo
=
hinfo
;
...
...
@@ -217,11 +200,14 @@ static int htable_create(struct ipt_hashlimit_info *minfo)
for
(
i
=
0
;
i
<
hinfo
->
cfg
.
size
;
i
++
)
INIT_HLIST_HEAD
(
&
hinfo
->
hash
[
i
]);
atomic_set
(
&
hinfo
->
count
,
0
);
atomic_set
(
&
hinfo
->
use
,
1
);
hinfo
->
count
=
0
;
hinfo
->
family
=
family
;
hinfo
->
rnd_initialized
=
0
;
spin_lock_init
(
&
hinfo
->
lock
);
hinfo
->
pde
=
create_proc_entry
(
minfo
->
name
,
0
,
hashlimit_procdir
);
hinfo
->
pde
=
create_proc_entry
(
minfo
->
name
,
0
,
family
==
AF_INET
?
hashlimit_procdir4
:
hashlimit_procdir6
);
if
(
!
hinfo
->
pde
)
{
vfree
(
hinfo
);
return
-
1
;
...
...
@@ -242,23 +228,21 @@ static int htable_create(struct ipt_hashlimit_info *minfo)
return
0
;
}
static
int
select_all
(
struct
ip
t_hashlimit_htable
*
ht
,
struct
dsthash_ent
*
he
)
static
int
select_all
(
struct
x
t_hashlimit_htable
*
ht
,
struct
dsthash_ent
*
he
)
{
return
1
;
}
static
int
select_gc
(
struct
ip
t_hashlimit_htable
*
ht
,
struct
dsthash_ent
*
he
)
static
int
select_gc
(
struct
x
t_hashlimit_htable
*
ht
,
struct
dsthash_ent
*
he
)
{
return
(
jiffies
>=
he
->
expires
);
}
static
void
htable_selective_cleanup
(
struct
ip
t_hashlimit_htable
*
ht
,
int
(
*
select
)(
struct
ipt_hashlimit_htable
*
ht
,
static
void
htable_selective_cleanup
(
struct
x
t_hashlimit_htable
*
ht
,
int
(
*
select
)(
struct
xt_hashlimit_htable
*
ht
,
struct
dsthash_ent
*
he
))
{
int
i
;
IP_NF_ASSERT
(
ht
->
cfg
.
size
&&
ht
->
cfg
.
max
);
unsigned
int
i
;
/* lock hash table and iterate over it */
spin_lock_bh
(
&
ht
->
lock
);
...
...
@@ -267,7 +251,7 @@ static void htable_selective_cleanup(struct ipt_hashlimit_htable *ht,
struct
hlist_node
*
pos
,
*
n
;
hlist_for_each_entry_safe
(
dh
,
pos
,
n
,
&
ht
->
hash
[
i
],
node
)
{
if
((
*
select
)(
ht
,
dh
))
__
dsthash_free
(
ht
,
dh
);
dsthash_free
(
ht
,
dh
);
}
}
spin_unlock_bh
(
&
ht
->
lock
);
...
...
@@ -276,7 +260,7 @@ static void htable_selective_cleanup(struct ipt_hashlimit_htable *ht,
/* hash table garbage collector, run by timer */
static
void
htable_gc
(
unsigned
long
htlong
)
{
struct
ipt_hashlimit_htable
*
ht
=
(
struct
ip
t_hashlimit_htable
*
)
htlong
;
struct
xt_hashlimit_htable
*
ht
=
(
struct
x
t_hashlimit_htable
*
)
htlong
;
htable_selective_cleanup
(
ht
,
select_gc
);
...
...
@@ -285,38 +269,39 @@ static void htable_gc(unsigned long htlong)
add_timer
(
&
ht
->
timer
);
}
static
void
htable_destroy
(
struct
ip
t_hashlimit_htable
*
hinfo
)
static
void
htable_destroy
(
struct
x
t_hashlimit_htable
*
hinfo
)
{
/* remove timer, if it is pending */
if
(
timer_pending
(
&
hinfo
->
timer
))
del_timer
(
&
hinfo
->
timer
);
/* remove proc entry */
remove_proc_entry
(
hinfo
->
pde
->
name
,
hashlimit_procdir
);
remove_proc_entry
(
hinfo
->
pde
->
name
,
hinfo
->
family
==
AF_INET
?
hashlimit_procdir4
:
hashlimit_procdir6
);
htable_selective_cleanup
(
hinfo
,
select_all
);
vfree
(
hinfo
);
}
static
struct
ipt_hashlimit_htable
*
htable_find_get
(
char
*
name
)
static
struct
xt_hashlimit_htable
*
htable_find_get
(
char
*
name
,
int
family
)
{
struct
ip
t_hashlimit_htable
*
hinfo
;
struct
x
t_hashlimit_htable
*
hinfo
;
struct
hlist_node
*
pos
;
spin_lock_bh
(
&
hashlimit_lock
);
hlist_for_each_entry
(
hinfo
,
pos
,
&
hashlimit_htables
,
node
)
{
if
(
!
strcmp
(
name
,
hinfo
->
pde
->
name
))
{
if
(
!
strcmp
(
name
,
hinfo
->
pde
->
name
)
&&
hinfo
->
family
==
family
)
{
atomic_inc
(
&
hinfo
->
use
);
spin_unlock_bh
(
&
hashlimit_lock
);
return
hinfo
;
}
}
spin_unlock_bh
(
&
hashlimit_lock
);
return
NULL
;
}
static
void
htable_put
(
struct
ip
t_hashlimit_htable
*
hinfo
)
static
void
htable_put
(
struct
x
t_hashlimit_htable
*
hinfo
)
{
if
(
atomic_dec_and_test
(
&
hinfo
->
use
))
{
spin_lock_bh
(
&
hashlimit_lock
);
...
...
@@ -326,7 +311,6 @@ static void htable_put(struct ipt_hashlimit_htable *hinfo)
}
}
/* The algorithm used is the Simple Token Bucket Filter (TBF)
* see net/sched/sch_tbf.c in the linux source tree
*/
...
...
@@ -370,86 +354,114 @@ user2credits(u_int32_t user)
/* If multiplying would overflow... */
if
(
user
>
0xFFFFFFFF
/
(
HZ
*
CREDITS_PER_JIFFY
))
/* Divide first. */
return
(
user
/
IP
T_HASHLIMIT_SCALE
)
*
HZ
*
CREDITS_PER_JIFFY
;
return
(
user
/
X
T_HASHLIMIT_SCALE
)
*
HZ
*
CREDITS_PER_JIFFY
;
return
(
user
*
HZ
*
CREDITS_PER_JIFFY
)
/
IP
T_HASHLIMIT_SCALE
;
return
(
user
*
HZ
*
CREDITS_PER_JIFFY
)
/
X
T_HASHLIMIT_SCALE
;
}
static
inline
void
rateinfo_recalc
(
struct
dsthash_ent
*
dh
,
unsigned
long
now
)
{
dh
->
rateinfo
.
credit
+=
(
now
-
xchg
(
&
dh
->
rateinfo
.
prev
,
now
))
*
CREDITS_PER_JIFFY
;
dh
->
rateinfo
.
credit
+=
(
now
-
dh
->
rateinfo
.
prev
)
*
CREDITS_PER_JIFFY
;
if
(
dh
->
rateinfo
.
credit
>
dh
->
rateinfo
.
credit_cap
)
dh
->
rateinfo
.
credit
=
dh
->
rateinfo
.
credit_cap
;
dh
->
rateinfo
.
prev
=
now
;
}
static
int
hashlimit_match
(
const
struct
sk_buff
*
skb
,
const
struct
net_device
*
in
,
const
struct
net_device
*
out
,
const
struct
xt_match
*
match
,
const
void
*
matchinfo
,
int
offset
,
unsigned
int
protoff
,
int
*
hotdrop
)
hashlimit_init_dst
(
struct
xt_hashlimit_htable
*
hinfo
,
struct
dsthash_dst
*
dst
,
const
struct
sk_buff
*
skb
,
unsigned
int
protoff
)
{
struct
ipt_hashlimit_info
*
r
=
((
struct
ipt_hashlimit_info
*
)
matchinfo
)
->
u
.
master
;
struct
ipt_hashlimit_htable
*
hinfo
=
r
->
hinfo
;
unsigned
long
now
=
jiffies
;
struct
dsthash_ent
*
dh
;
struct
dsthash_dst
dst
;
/* build 'dst' according to hinfo->cfg and current packet */
memset
(
&
dst
,
0
,
sizeof
(
dst
));
if
(
hinfo
->
cfg
.
mode
&
IPT_HASHLIMIT_HASH_DIP
)
dst
.
dst_ip
=
skb
->
nh
.
iph
->
daddr
;
if
(
hinfo
->
cfg
.
mode
&
IPT_HASHLIMIT_HASH_SIP
)
dst
.
src_ip
=
skb
->
nh
.
iph
->
saddr
;
if
(
hinfo
->
cfg
.
mode
&
IPT_HASHLIMIT_HASH_DPT
||
hinfo
->
cfg
.
mode
&
IPT_HASHLIMIT_HASH_SPT
)
{
__be16
_ports
[
2
],
*
ports
;
int
nexthdr
;
switch
(
skb
->
nh
.
iph
->
protocol
)
{
memset
(
dst
,
0
,
sizeof
(
*
dst
));
switch
(
hinfo
->
family
)
{
case
AF_INET
:
if
(
hinfo
->
cfg
.
mode
&
XT_HASHLIMIT_HASH_DIP
)
dst
->
addr
.
ip
.
dst
=
skb
->
nh
.
iph
->
daddr
;
if
(
hinfo
->
cfg
.
mode
&
XT_HASHLIMIT_HASH_SIP
)
dst
->
addr
.
ip
.
src
=
skb
->
nh
.
iph
->
saddr
;
if
(
!
(
hinfo
->
cfg
.
mode
&
(
XT_HASHLIMIT_HASH_DPT
|
XT_HASHLIMIT_HASH_SPT
)))
return
0
;
nexthdr
=
skb
->
nh
.
iph
->
protocol
;
break
;
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
case
AF_INET6
:
if
(
hinfo
->
cfg
.
mode
&
XT_HASHLIMIT_HASH_DIP
)
memcpy
(
&
dst
->
addr
.
ip6
.
dst
,
&
skb
->
nh
.
ipv6h
->
daddr
,
sizeof
(
dst
->
addr
.
ip6
.
dst
));
if
(
hinfo
->
cfg
.
mode
&
XT_HASHLIMIT_HASH_SIP
)
memcpy
(
&
dst
->
addr
.
ip6
.
src
,
&
skb
->
nh
.
ipv6h
->
saddr
,
sizeof
(
dst
->
addr
.
ip6
.
src
));
if
(
!
(
hinfo
->
cfg
.
mode
&
(
XT_HASHLIMIT_HASH_DPT
|
XT_HASHLIMIT_HASH_SPT
)))
return
0
;
nexthdr
=
ipv6_find_hdr
(
skb
,
&
protoff
,
-
1
,
NULL
);
if
(
nexthdr
<
0
)
return
-
1
;
break
;
#endif
default:
BUG
();
return
0
;
}
switch
(
nexthdr
)
{
case
IPPROTO_TCP
:
case
IPPROTO_UDP
:
case
IPPROTO_SCTP
:
case
IPPROTO_DCCP
:
ports
=
skb_header_pointer
(
skb
,
skb
->
nh
.
iph
->
ihl
*
4
,
sizeof
(
_ports
),
&
_ports
);
ports
=
skb_header_pointer
(
skb
,
protoff
,
sizeof
(
_ports
)
,
&
_ports
);
break
;
default:
_ports
[
0
]
=
_ports
[
1
]
=
0
;
ports
=
_ports
;
break
;
}
if
(
!
ports
)
{
/* We've been asked to examine this packet, and we
can't. Hence, no choice but to drop. */
*
hotdrop
=
1
;
if
(
!
ports
)
return
-
1
;
if
(
hinfo
->
cfg
.
mode
&
XT_HASHLIMIT_HASH_SPT
)
dst
->
src_port
=
ports
[
0
];
if
(
hinfo
->
cfg
.
mode
&
XT_HASHLIMIT_HASH_DPT
)
dst
->
dst_port
=
ports
[
1
];
return
0
;
}
if
(
hinfo
->
cfg
.
mode
&
IPT_HASHLIMIT_HASH_SPT
)
dst
.
src_port
=
ports
[
0
];
if
(
hinfo
->
cfg
.
mode
&
IPT_HASHLIMIT_HASH_DPT
)
dst
.
dst_port
=
ports
[
1
];
}
}
static
int
hashlimit_match
(
const
struct
sk_buff
*
skb
,
const
struct
net_device
*
in
,
const
struct
net_device
*
out
,
const
struct
xt_match
*
match
,
const
void
*
matchinfo
,
int
offset
,
unsigned
int
protoff
,
int
*
hotdrop
)
{
struct
xt_hashlimit_info
*
r
=
((
struct
xt_hashlimit_info
*
)
matchinfo
)
->
u
.
master
;
struct
xt_hashlimit_htable
*
hinfo
=
r
->
hinfo
;
unsigned
long
now
=
jiffies
;
struct
dsthash_ent
*
dh
;
struct
dsthash_dst
dst
;
if
(
hashlimit_init_dst
(
hinfo
,
&
dst
,
skb
,
protoff
)
<
0
)
goto
hotdrop
;
spin_lock_bh
(
&
hinfo
->
lock
);
dh
=
__
dsthash_find
(
hinfo
,
&
dst
);
dh
=
dsthash_find
(
hinfo
,
&
dst
);
if
(
!
dh
)
{
dh
=
__dsthash_alloc_init
(
hinfo
,
&
dst
);
dh
=
dsthash_alloc_init
(
hinfo
,
&
dst
);
if
(
!
dh
)
{
/* enomem... don't match == DROP */
if
(
net_ratelimit
())
printk
(
KERN_ERR
"%s: ENOMEM
\n
"
,
__FUNCTION__
);
spin_unlock_bh
(
&
hinfo
->
lock
);
return
0
;
goto
hotdrop
;
}
dh
->
expires
=
jiffies
+
msecs_to_jiffies
(
hinfo
->
cfg
.
expire
);
dh
->
rateinfo
.
prev
=
jiffies
;
dh
->
rateinfo
.
credit
=
user2credits
(
hinfo
->
cfg
.
avg
*
hinfo
->
cfg
.
burst
);
...
...
@@ -473,6 +485,10 @@ hashlimit_match(const struct sk_buff *skb,
/* default case: we're overlimit, thus don't match */
return
0
;
hotdrop:
*
hotdrop
=
1
;
return
0
;
}
static
int
...
...
@@ -482,42 +498,37 @@ hashlimit_checkentry(const char *tablename,
void
*
matchinfo
,
unsigned
int
hook_mask
)
{
struct
ip
t_hashlimit_info
*
r
=
matchinfo
;
struct
x
t_hashlimit_info
*
r
=
matchinfo
;
/* Check for overflow. */
if
(
r
->
cfg
.
burst
==
0
||
user2credits
(
r
->
cfg
.
avg
*
r
->
cfg
.
burst
)
<
user2credits
(
r
->
cfg
.
avg
))
{
printk
(
KERN_ERR
"ipt_hashlimit: Overflow, try lower: %u/%u
\n
"
,
if
(
r
->
cfg
.
burst
==
0
||
user2credits
(
r
->
cfg
.
avg
*
r
->
cfg
.
burst
)
<
user2credits
(
r
->
cfg
.
avg
))
{
printk
(
KERN_ERR
"xt_hashlimit: overflow, try lower: %u/%u
\n
"
,
r
->
cfg
.
avg
,
r
->
cfg
.
burst
);
return
0
;
}
if
(
r
->
cfg
.
mode
==
0
||
r
->
cfg
.
mode
>
(
IPT_HASHLIMIT_HASH_DPT
|
IPT_HASHLIMIT_HASH_DIP
|
IPT_HASHLIMIT_HASH_SIP
|
IPT_HASHLIMIT_HASH_SPT
))
if
(
r
->
cfg
.
mode
==
0
||
r
->
cfg
.
mode
>
(
XT_HASHLIMIT_HASH_DPT
|
XT_HASHLIMIT_HASH_DIP
|
XT_HASHLIMIT_HASH_SIP
|
XT_HASHLIMIT_HASH_SPT
))
return
0
;
if
(
!
r
->
cfg
.
gc_interval
)
return
0
;
if
(
!
r
->
cfg
.
expire
)
return
0
;
if
(
r
->
name
[
sizeof
(
r
->
name
)
-
1
]
!=
'\0'
)
return
0
;
/* This is the best we've got: We cannot release and re-grab lock,
* since checkentry() is called before
ip_tables.c grabs ipt_mutex.
* since checkentry() is called before
x_tables.c grabs xt_mutex.
* We also cannot grab the hashtable spinlock, since htable_create will
* call vmalloc, and that can sleep. And we cannot just re-search
* the list of htable's in htable_create(), since then we would
* create duplicate proc files. -HW */
mutex_lock
(
&
hlimit_mutex
);
r
->
hinfo
=
htable_find_get
(
r
->
name
);
if
(
!
r
->
hinfo
&&
(
htable_create
(
r
)
!=
0
)
)
{
r
->
hinfo
=
htable_find_get
(
r
->
name
,
match
->
family
);
if
(
!
r
->
hinfo
&&
htable_create
(
r
,
match
->
family
)
!=
0
)
{
mutex_unlock
(
&
hlimit_mutex
);
return
0
;
}
...
...
@@ -525,20 +536,19 @@ hashlimit_checkentry(const char *tablename,
/* Ugly hack: For SMP, we only want to use one set */
r
->
u
.
master
=
r
;
return
1
;
}
static
void
hashlimit_destroy
(
const
struct
xt_match
*
match
,
void
*
matchinfo
)
{
struct
ip
t_hashlimit_info
*
r
=
matchinfo
;
struct
x
t_hashlimit_info
*
r
=
matchinfo
;
htable_put
(
r
->
hinfo
);
}
#ifdef CONFIG_COMPAT
struct
compat_
ip
t_hashlimit_info
{
struct
compat_
x
t_hashlimit_info
{
char
name
[
IFNAMSIZ
];
struct
hashlimit_cfg
cfg
;
compat_uptr_t
hinfo
;
...
...
@@ -547,40 +557,56 @@ struct compat_ipt_hashlimit_info {
static
void
compat_from_user
(
void
*
dst
,
void
*
src
)
{
int
off
=
offsetof
(
struct
compat_
ip
t_hashlimit_info
,
hinfo
);
int
off
=
offsetof
(
struct
compat_
x
t_hashlimit_info
,
hinfo
);
memcpy
(
dst
,
src
,
off
);
memset
(
dst
+
off
,
0
,
sizeof
(
struct
compat_
ip
t_hashlimit_info
)
-
off
);
memset
(
dst
+
off
,
0
,
sizeof
(
struct
compat_
x
t_hashlimit_info
)
-
off
);
}
static
int
compat_to_user
(
void
__user
*
dst
,
void
*
src
)
{
int
off
=
offsetof
(
struct
compat_
ip
t_hashlimit_info
,
hinfo
);
int
off
=
offsetof
(
struct
compat_
x
t_hashlimit_info
,
hinfo
);
return
copy_to_user
(
dst
,
src
,
off
)
?
-
EFAULT
:
0
;
}
#endif
static
struct
ipt_match
ipt_hashlimit
=
{
static
struct
xt_match
xt_hashlimit
[]
=
{
{
.
name
=
"hashlimit"
,
.
family
=
AF_INET
,
.
match
=
hashlimit_match
,
.
matchsize
=
sizeof
(
struct
xt_hashlimit_info
),
#ifdef CONFIG_COMPAT
.
compatsize
=
sizeof
(
struct
compat_xt_hashlimit_info
),
.
compat_from_user
=
compat_from_user
,
.
compat_to_user
=
compat_to_user
,
#endif
.
checkentry
=
hashlimit_checkentry
,
.
destroy
=
hashlimit_destroy
,
.
me
=
THIS_MODULE
},
{
.
name
=
"hashlimit"
,
.
family
=
AF_INET6
,
.
match
=
hashlimit_match
,
.
matchsize
=
sizeof
(
struct
ip
t_hashlimit_info
),
.
matchsize
=
sizeof
(
struct
x
t_hashlimit_info
),
#ifdef CONFIG_COMPAT
.
compatsize
=
sizeof
(
struct
compat_ip
t_hashlimit_info
),
.
compatsize
=
sizeof
(
struct
compat_x
t_hashlimit_info
),
.
compat_from_user
=
compat_from_user
,
.
compat_to_user
=
compat_to_user
,
#endif
.
checkentry
=
hashlimit_checkentry
,
.
destroy
=
hashlimit_destroy
,
.
me
=
THIS_MODULE
},
};
/* PROC stuff */
static
void
*
dl_seq_start
(
struct
seq_file
*
s
,
loff_t
*
pos
)
{
struct
proc_dir_entry
*
pde
=
s
->
private
;
struct
ip
t_hashlimit_htable
*
htable
=
pde
->
data
;
struct
x
t_hashlimit_htable
*
htable
=
pde
->
data
;
unsigned
int
*
bucket
;
spin_lock_bh
(
&
htable
->
lock
);
...
...
@@ -598,7 +624,7 @@ static void *dl_seq_start(struct seq_file *s, loff_t *pos)
static
void
*
dl_seq_next
(
struct
seq_file
*
s
,
void
*
v
,
loff_t
*
pos
)
{
struct
proc_dir_entry
*
pde
=
s
->
private
;
struct
ip
t_hashlimit_htable
*
htable
=
pde
->
data
;
struct
x
t_hashlimit_htable
*
htable
=
pde
->
data
;
unsigned
int
*
bucket
=
(
unsigned
int
*
)
v
;
*
pos
=
++
(
*
bucket
);
...
...
@@ -612,43 +638,59 @@ static void *dl_seq_next(struct seq_file *s, void *v, loff_t *pos)
static
void
dl_seq_stop
(
struct
seq_file
*
s
,
void
*
v
)
{
struct
proc_dir_entry
*
pde
=
s
->
private
;
struct
ip
t_hashlimit_htable
*
htable
=
pde
->
data
;
struct
x
t_hashlimit_htable
*
htable
=
pde
->
data
;
unsigned
int
*
bucket
=
(
unsigned
int
*
)
v
;
kfree
(
bucket
);
spin_unlock_bh
(
&
htable
->
lock
);
}
static
inline
int
dl_seq_real_show
(
struct
dsthash_ent
*
ent
,
struct
seq_file
*
s
)
static
int
dl_seq_real_show
(
struct
dsthash_ent
*
ent
,
int
family
,
struct
seq_file
*
s
)
{
/* recalculate to show accurate numbers */
rateinfo_recalc
(
ent
,
jiffies
);
return
seq_printf
(
s
,
"%ld %u.%u.%u.%u:%u->%u.%u.%u.%u:%u %u %u %u
\n
"
,
switch
(
family
)
{
case
AF_INET
:
return
seq_printf
(
s
,
"%ld %u.%u.%u.%u:%u->"
"%u.%u.%u.%u:%u %u %u %u
\n
"
,
(
long
)(
ent
->
expires
-
jiffies
)
/
HZ
,
NIPQUAD
(
ent
->
dst
.
addr
.
ip
.
src
),
ntohs
(
ent
->
dst
.
src_port
),
NIPQUAD
(
ent
->
dst
.
addr
.
ip
.
dst
),
ntohs
(
ent
->
dst
.
dst_port
),
ent
->
rateinfo
.
credit
,
ent
->
rateinfo
.
credit_cap
,
ent
->
rateinfo
.
cost
);
case
AF_INET6
:
return
seq_printf
(
s
,
"%ld "
NIP6_FMT
":%u->"
NIP6_FMT
":%u %u %u %u
\n
"
,
(
long
)(
ent
->
expires
-
jiffies
)
/
HZ
,
NIPQUAD
(
ent
->
dst
.
src_ip
),
ntohs
(
ent
->
dst
.
src_port
),
NIPQUAD
(
ent
->
dst
.
dst_ip
),
ntohs
(
ent
->
dst
.
dst_port
),
NIP6
(
*
(
struct
in6_addr
*
)
&
ent
->
dst
.
addr
.
ip6
.
src
),
ntohs
(
ent
->
dst
.
src_port
),
NIP6
(
*
(
struct
in6_addr
*
)
&
ent
->
dst
.
addr
.
ip6
.
dst
),
ntohs
(
ent
->
dst
.
dst_port
),
ent
->
rateinfo
.
credit
,
ent
->
rateinfo
.
credit_cap
,
ent
->
rateinfo
.
cost
);
default:
BUG
();
return
0
;
}
}
static
int
dl_seq_show
(
struct
seq_file
*
s
,
void
*
v
)
{
struct
proc_dir_entry
*
pde
=
s
->
private
;
struct
ip
t_hashlimit_htable
*
htable
=
pde
->
data
;
struct
x
t_hashlimit_htable
*
htable
=
pde
->
data
;
unsigned
int
*
bucket
=
(
unsigned
int
*
)
v
;
struct
dsthash_ent
*
ent
;
struct
hlist_node
*
pos
;
if
(
!
hlist_empty
(
&
htable
->
hash
[
*
bucket
]))
hlist_for_each_entry
(
ent
,
pos
,
&
htable
->
hash
[
*
bucket
],
node
)
{
if
(
dl_seq_real_show
(
ent
,
s
))
{
/* buffer was filled and unable to print that tuple */
if
(
!
hlist_empty
(
&
htable
->
hash
[
*
bucket
]))
{
hlist_for_each_entry
(
ent
,
pos
,
&
htable
->
hash
[
*
bucket
],
node
)
if
(
dl_seq_real_show
(
ent
,
htable
->
family
,
s
))
return
1
;
}
}
return
0
;
}
...
...
@@ -678,56 +720,53 @@ static struct file_operations dl_file_ops = {
.
release
=
seq_release
};
static
int
init_or_fini
(
int
fini
)
static
int
__init
xt_hashlimit_init
(
void
)
{
int
ret
=
0
;
int
err
;
if
(
fini
)
goto
cleanup
;
err
=
xt_register_matches
(
xt_hashlimit
,
ARRAY_SIZE
(
xt_hashlimit
));
if
(
err
<
0
)
goto
err1
;
if
(
ipt_register_match
(
&
ipt_hashlimit
))
{
ret
=
-
EINVAL
;
goto
cleanup_nothing
;
}
hashlimit_cachep
=
kmem_cache_create
(
"ipt_hashlimit"
,
sizeof
(
struct
dsthash_ent
),
0
,
0
,
NULL
,
NULL
);
err
=
-
ENOMEM
;
hashlimit_cachep
=
kmem_cache_create
(
"xt_hashlimit"
,
sizeof
(
struct
dsthash_ent
),
0
,
0
,
NULL
,
NULL
);
if
(
!
hashlimit_cachep
)
{
printk
(
KERN_ERR
"Unable to create ipt_hashlimit slab cache
\n
"
);
ret
=
-
ENOMEM
;
goto
cleanup_unreg_match
;
printk
(
KERN_ERR
"xt_hashlimit: unable to create slab cache
\n
"
);
goto
err2
;
}
hashlimit_procdir
=
proc_mkdir
(
"ipt_hashlimit"
,
proc_net
);
if
(
!
hashlimit_procdir
)
{
printk
(
KERN_ERR
"Unable to create proc dir entry
\n
"
);
ret
=
-
ENOMEM
;
goto
cleanup_free_slab
;
hashlimit_procdir4
=
proc_mkdir
(
"ipt_hashlimit"
,
proc_net
);
if
(
!
hashlimit_procdir4
)
{
printk
(
KERN_ERR
"xt_hashlimit: unable to create proc dir "
"entry
\n
"
);
goto
err3
;
}
return
ret
;
cleanup:
hashlimit_procdir6
=
proc_mkdir
(
"ip6t_hashlimit"
,
proc_net
);
if
(
!
hashlimit_procdir6
)
{
printk
(
KERN_ERR
"xt_hashlimit: tnable to create proc dir "
"entry
\n
"
);
goto
err4
;
}
return
0
;
err4:
remove_proc_entry
(
"ipt_hashlimit"
,
proc_net
);
cleanup_free_slab
:
err3
:
kmem_cache_destroy
(
hashlimit_cachep
);
cleanup_unreg_match:
ipt_unregister_match
(
&
ipt_hashlimit
);
cleanup_nothing:
return
ret
;
}
err2:
xt_unregister_matches
(
xt_hashlimit
,
ARRAY_SIZE
(
xt_hashlimit
));
err1:
return
err
;
static
int
__init
ipt_hashlimit_init
(
void
)
{
return
init_or_fini
(
0
);
}
static
void
__exit
ip
t_hashlimit_fini
(
void
)
static
void
__exit
x
t_hashlimit_fini
(
void
)
{
init_or_fini
(
1
);
remove_proc_entry
(
"ipt_hashlimit"
,
proc_net
);
remove_proc_entry
(
"ip6t_hashlimit"
,
proc_net
);
kmem_cache_destroy
(
hashlimit_cachep
);
xt_unregister_matches
(
xt_hashlimit
,
ARRAY_SIZE
(
xt_hashlimit
));
}
module_init
(
ip
t_hashlimit_init
);
module_exit
(
ip
t_hashlimit_fini
);
module_init
(
x
t_hashlimit_init
);
module_exit
(
x
t_hashlimit_fini
);
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录