fail2ban 使用 iptables-xt_recent 代替 iptables-multiport

2014 年 8 月 19 日,mirrors 出现了连接数超过 connlimit 限制的问题。我们还以为是有人利用了 conntrack 的漏洞。22 日,查清问题原因是 fail2ban 干的,相当于一种防御措施干扰了另外一种防御措施。

Fail2ban 往 iptables filter 表插入规则的时候,会重置同样在 filter 表里的 connlimit 计数器。更深层的原因是,netfilter 用户态和内核态间的写接口只有两种操作,一种是替换整个表,一种是只替换表里的 counter。所有 iptables 操作,不管是插入、删除、替换,最后用户态的实现都是首先从内核态取出这张表(filter)里的所有规则及 counter(相当于 iptables-save),修改之后发给内核(相当于 iptables-restore),内核把这张表里的所有规则删掉,再逐一初始化新的规则。我做梦也想不到 netfilter 会使用如此低效的接口,不过实现上这确实是最简单的了。

在 netfilter 的模型下,现在的 connlimit 事实上是无法在 iptables 修改规则时保持状态的。首先,connlimit 作为一个 match 模块,既不知道其他的 match 条件(如 –tcp-flags syn,–dport 873),又不知道 action(-j),因此不可能实现一个全局的存储,否则不同的 connlimit 规则之间就可能存在冲突。其次,由于 netfilter 在修改表时会依次删除并重新插入所有规则,connlimit 在面临删除的时候,根本不知道是被“牵连”进去的,还是用户确实要删除这条规则。

今天 Zhang Cheng 提出用 iptables recent 规则也可以实现 fail2ban 的效果,修改 recent 规则的 IP 列表是通过 /proc/net/xt_recent/fail2ban-ssh,不需要动 iptables 规则。也就是只要我们不去修改 iptables 规则或者重启 fail2ban 之类服务,connlimit 就不会受到影响。我测试了 recent 规则,发现它对拒绝服务攻击不敏感(需要记忆状态的防火墙都要小心拒绝服务攻击)。

事实上 fail2ban 里本来就可以使用 iptables recent 来做封禁,只是默认使用的是 iptables-multiport 方式。只需要把 jail.conf 里的 banaction 修改成 iptables-xt_recent-echo

还发现发行版自带的 fail2ban 脚本里一处小问题,actionstop 没有删除 iptables 规则,这样 fail2ban 每重启一次,iptables 里就会多一条冗余的规则。强迫症不能忍啊,就自己改了改。正准备去发 issue 呢,却发现 上游在一个月前刚刚修复了

我们使用下列脚本来对各服务器启用新的 fail2ban 封禁方式(其中 actionstop 那行是修 fail2ban 的 bug):

ssh $HOSTNAME -t "
     sudo sed -i 's/^banaction = iptables-multiport$/banaction = iptables-xt_recent-echo/g' /etc/fail2ban/jail.conf;
     sudo sed -i '/actionstop =/a\             iptables -D INPUT -m recent --update --seconds 3600 --name fail2ban-<name> -j DROP' /etc/fail2ban/action.d/iptables-xt_recent-echo.conf;
     sudo service fail2ban restart;
     sudo iptables-save"

附修改后的 /etc/fail2ban/action.d/iptables-xt_recent-echo.conf 脚本,希望自己写封禁脚本的可以参考:

[Definition]
actionstart = iptables -I INPUT -m recent --update --seconds 3600 --name fail2ban-<name> -j DROP
actionstop = echo / > /proc/net/xt_recent/fail2ban-<name>
             iptables -D INPUT -m recent --update --seconds 3600 --name fail2ban-<name> -j DROP
actioncheck = test -e /proc/net/xt_recent/fail2ban-<name>
actionban = echo +<ip> > /proc/net/xt_recent/fail2ban-<name>
actionunban = echo -<ip> > /proc/net/xt_recent/fail2ban-<name>

[Init]
name = default
protocol = tcp

(其中 --seconds 3600 仅仅作为一种 fallback 机制,封禁的定时解除主要靠 fail2ban 内部的定时机制,到时执行 actionunban)

大赞 Zhang Cheng 的发现!

Freeshell 设置 1G swap

每个 freeshell 在 4G 内存限制基础上增加了 1G swap 空间。早先 freeshell 是没有 swap space 的,内存一旦达到限制进程分配就会失败,导致进程容易被杀死。有了 swap 之后内核会使用经典的 OOM kill 机制,给内存分配留一定的缓冲空间。

Blog、Freeshell HTTP Proxy 请求数限制

为保证服务器安全,在一定程度上抵抗拒绝服务攻击,Blog 和 Freeshell HTTP Proxy 的 HTTP(S) 服务器部署了下列限制,一般不会影响到正常使用。

并发连接数限制:

  • 每个客户端 IP 100 个并发连接;
  • 每个域名 1000 个并发连接。

HTTP/HTTPS 请求频率限制(采用令牌桶算法):

  • 每个客户端 IP 每秒 50 个请求,允许 250 个突发请求;
  • 每个域名每秒 200 个请求,允许 1000 个突发请求。

最新的配置参见 Nginx 配置文件

GitLab升级至7.9.0

本次维护历时30分钟,服务共中断27分钟。

7.9.0 主要特性:

  • 创建或删除branch、tag发邮件提醒
  • 支持新分支的在线编辑与保存
  • 支持与Bitbucket的项目对接
  • 通知邮件的内容更加具体
  • 支持订阅issue或merge请求
  • 数个UI改进
  • 数个API改进
  • 数个bug修复

VPN服务器多进程负载均衡方案

应boj大大的要求,将此次负载均衡的技术细节记录下来。本文供维护人员参考,请大家多多拍砖。

由于OpenVPN在处理数据包的校验、加解密、封装时都需要大量CPU资源,OpenVPN不支持多线程处理,导致服务器的CPU利用率率长期保持在30%以下(4核心服务器)。为了提升服务器CPU的利用率,增强数据处理能力、提升VPN总带宽,为服务器添加了4个独立的OpenVPN工作进程,使用ipvsadm做udp负载均衡。

1. 创建4个OpenVPN配置文件:/etc/openvpn/server-worker-{1..4}.conf(绑定10001~10004端口),并修改相应的路由表配置文件。

2. 启动4个工作线程
       service openvpn start server-worker-{1..4}

3. 设置ipvsadm超时参数:
       ipvsadm --set 0 0 35
三个参数代表:TCP sessions, TCP sessions after receiving a FIN packet, UDP packets 的超时时间;0表示不修改当前参数。

4. 绑定需要负载均衡的端口(以202.141.160.95:1194为例,其余类似):
       ipvsadm -A -u 202.141.160.95:1194 -s rr
其中 rr 表示采用轮询(Round Robin)策略。

5. 添加后端
       ipvsadm -a -u 202.141.160.95:1194 -r 202.141.160.95:10001 -m
类似的,可以添加端口号为10002~10004的后端。

其中 -m 表示采用地址伪装(masquerading)的方式访问后端。因为要保证从A网卡来的数据包仍然从A网卡返回,因此不能使用gatewaying和ipip的方式。(但masquerading效率相对较低)

到此,负载均衡已经部署完成。


经过观察,负载均衡的效果并不理想,依然存在某进程CPU占用率100%,而其他几个核心空闲的情况。以下是优化方式:

一、采用最小连接(Least-Connection)策略替换轮询(Round Robin)策略

由于用户可能随机断开连接,而轮询策略并不会感知这一因素,导致每个后端的连接数有一定差距。(注:使用ipvsadm -L命令可以查看分配到每个后端的连接数)

将连接策略改为Least-Connection:
       ipvsadm -E -u 202.141.160.95:1194 -s lc

P.S. 修改策略不会导致原有连接中断。

二、添加多个后端

由于每个用户的带宽使用情况并不一样,当有多个大流量用户被分配到同一个进程时,就会出现单线程CPU100%,其余空闲的情况。

假设有4个工作进程,160个用户,2个大流量用户,这两个大流量用户“撞车”的概率为:0.245。

假设有16个工作进程,其余条件不变,则撞车概率概率为 0.014。

为了减弱这类大流量用户“撞车”的影响,可以适当多添加一些工作进程。当前创建了16个工作进程,CPU利用率最高达到为83%(为什么不是100%呢?)。

以上

修复 LUG 官方邮箱转发邮件缓慢问题

近一段时间 LUG 官方邮箱(包括技术支持邮箱)向内部成员转发邮件的延迟很高,邮件服务器有大量类似下面的错误日志:

Mar 19 01:36:00 blog postfix/smtp[5702]: 1312CF821BD: to=<lugarchiver@gmail.com>, orig_to=<staff@lug.ustc.edu.cn>, relay=alt4.gmail-smtp-in.l.google.com[173.194.219.27]:25, delay=2144, delays=2141/0.01/3.1/0, dsn=4.7.0, status=deferred (host alt4.gmail-smtp-in.l.google.com[173.194.219.27] refused to talk to me: 421-4.7.0 [128.199.95.148] Our system has detected an unusual amount of 421-4.7.0 unsolicited mail originating from your IP address. To protect our 421-4.7.0 users from spam, mail sent from your IP address has been temporarily 421-4.7.0 blocked. Please visit 421-4.7.0 http://www.google.com/mail/help/bulk_mail.html to review our Bulk 421 4.7.0 Email Senders Guidelines. o61si8882013yhb.37 - gsmtp)

经查,是 DNS TXT 记录配置错误。只有 default view(对应 IPv6 和 IPv4 国外线路)的 TXT 记录正确:"v=spf1 mx a a:ip-list.vpn.ustclug.org ~all",而 chinanet view(对应国内 IPv4 除移动外的线路)和 cmcc view(对应国内移动线路)的 TXT 记录是过时的版本:"v=spf1 mx a a:do2.bojieli.com ~all"

可能是由于近期 Gmail 换用了中国服务器对 .cn 域名进行解析,Gmail 获取到的 TXT 记录错误,导致邮件服务器的 IP 地址无法通过 SPF 校验,转发给内部成员的邮件被 Gmail 服务器拒收。

感谢 高一凡 报 bug。

Freeshell 宕机通告【已恢复】

3 月 18 日晚 22 时 24 分,freeshell 1 号节点突然宕机,导致外部磁盘阵列服务中断。

我们尝试通过 IPMI 硬重启,重启后能 ping 通,但无法 SSH 登录,已弃疗。可能需要明天去机房修理。

首先通过 IPMI 关闭电源,过一会儿再启动,启动之后就能 SSH 了!现在各虚拟机正在启动中,今晚就能恢复服务了。
凌晨 0 时 53 分,经确认 1 号节点上的虚拟机启动完毕,于是恢复了 NFS 服务,凌晨 1 时确认各节点上的虚拟机恢复正常。

非常抱歉此问题给您带来的不便。

继续阅读Freeshell 宕机通告【已恢复】

Blog, freeshell 部署 Web 防火墙

今天 blog、freeshell 实验部署了 Web 防火墙。

Web 防火墙使用了 stephen 推荐的 ngx_lua_waf。当访问可能存在注入的页面时,将返回 403 错误页面。如果您正常的网页被阻止,出现下面这样的错误页,请联系我们。

为防 CC 攻击,限制每个 IP 地址每分钟访问相同 URL 不超过 200 次,若超过则返回 503 Service Temporarily Unavailable。下图使用 ab 进行压力测试,有 299 个请求被 WAF 阻止了;而每秒接近 50 个请求的速度是之前就有的 nginx 每 IP 每秒请求数限制所致。

多谢 stephen 师兄的建议。