以下规则将每个IP的最大并发连接数量控制在5个,使用connlimit-above或者connlimit-upto都可实现。
# iptables -I INPUT -p tcp --syn --dport 22 -m connlimit --connlimit-above 5 -j DROP
#
# iptables -L -n -v
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
0 0 DROP tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:22 flags:0x17/0x02 #conn src/32 > 5
#
#
# iptables -I INPUT -p tcp --syn --dport 22 -m connlimit --connlimit-upto 5 -j ACCEPT
#
# iptables -L -n -v
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:22 flags:0x17/0x02 #conn src/32 <= 5
以下规则将每个C网段内的所有地址的连接总数量控制在5个。
# iptables -I INPUT -p tcp --syn --dport 22 -m connlimit --connlimit-above 5 --connlimit-mask 24 -j DROP
#
# iptables -L -n -v
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
0 0 DROP tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:22 flags:0x17/0x02 #conn src/24 > 5
默认情况下–connlimit-saddr是生效的,以下指定–connlimit-daddr,控制到每个目的地址的连接数量不超过5个。
# iptables -I INPUT -p tcp --syn --dport 22 -m connlimit --connlimit-above 5 --connlimit-daddr -j DROP
#
# iptables -L -n -v
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
0 0 DROP tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:22 flags:0x17/0x02 #conn dst/32 > 5
connlimit匹配
内核函数connlimit_mt_init注册了connlimit_mt_reg匹配结构。
static struct xt_match connlimit_mt_reg __read_mostly = {
.name = "connlimit",
.revision = 1,
.family = NFPROTO_UNSPEC,
.checkentry = connlimit_mt_check,
.match = connlimit_mt,
.matchsize = sizeof(struct xt_connlimit_info),
.usersize = offsetof(struct xt_connlimit_info, data),
.destroy = connlimit_mt_destroy,
.me = THIS_MODULE,
};
static int __init connlimit_mt_init(void)
{
return xt_register_match(&connlimit_mt_reg);
}
配置检查函数connlimit_mt_check如下,这里初始化conncount相关结构,keylen包括zone区ID和相应协议组的地址长度。
static int connlimit_mt_check(const struct xt_mtchk_param *par)
{
struct xt_connlimit_info *info = par->matchinfo;
unsigned int keylen;
keylen = sizeof(u32);
if (par->family == NFPROTO_IPV6)
keylen += sizeof(struct in6_addr);
else
keylen += sizeof(struct in_addr);
/* init private data */
info->data = nf_conncount_init(par->net, par->family, keylen);
return PTR_ERR_OR_ZERO(info->data);
匹配函数connlimit_mt,如果报文已经带有连接跟踪结构,取出其中的原始方向tuple,否则,解析报文的tuple,解析失败,返回false,未能匹配。
static bool
connlimit_mt(const struct sk_buff *skb, struct xt_action_param *par)
{
const struct xt_connlimit_info *info = par->matchinfo;
struct nf_conntrack_tuple tuple;
const struct nf_conntrack_tuple *tuple_ptr = &tuple;
const struct nf_conntrack_zone *zone = &nf_ct_zone_dflt;
const struct nf_conn *ct;
u32 key[5];
ct = nf_ct_get(skb, &ctinfo);
if (ct != NULL) {
tuple_ptr = &ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple;
zone = nf_ct_zone(ct);
} else if (!nf_ct_get_tuplepr(skb, skb_network_offset(skb),
xt_family(par), net, &tuple)) {
goto hotdrop;
}
否则,成功获取报文的tuple信息,对于IPv6,如果设置了XT_CONNLIMIT_DADDR标志(–connlimit-daddr),key值选取报文的目的地址,否则,选取源地址。
if (xt_family(par) == NFPROTO_IPV6) {
const struct ipv6hdr *iph = ipv6_hdr(skb);
union nf_inet_addr addr;
memcpy(&addr.ip6, (info->flags & XT_CONNLIMIT_DADDR) ?
&iph->daddr : &iph->saddr, sizeof(addr.ip6));
for (i = 0; i < ARRAY_SIZE(addr.ip6); ++i)
addr.ip6[i] &= info->mask.ip6[i];
memcpy(key, &addr, sizeof(addr.ip6));
key[4] = zone->id;
} else {
const struct iphdr *iph = ip_hdr(skb);
key[0] = (info->flags & XT_CONNLIMIT_DADDR) ?
iph->daddr : iph->saddr;
key[0] &= info->mask.ip;
key[1] = zone->id;
}
对于IPv4协议,处理与以上相同,另外,key值中的地址部分需要与配置的掩码进行与操作(&)。之后,由key值和tuple值来获取连接的数量,最后,检查是否满足限制条件。
connections = nf_conncount_count(net, info->data, key, tuple_ptr, zone);
if (connections == 0)
/* kmalloc failed, drop it entirely */
goto hotdrop;
return (connections > info->limit) ^ !!(info->flags & XT_CONNLIMIT_INVERT);
hotdrop:
par->hotdrop = true;
return false;
内核版本 5.10
|