需求背景
目前公司部分业务使用阿里云环境,Linux 主机使用的是 ECS
- 部分 Linux 主机使用阿里云 SNAT 直接访问外网(没办法监控这部分主机对外访问的 http(s) 请求和流量)
- 部分 Linux 主机使用 Squid 正向代理,再通过 SNAT 访问外网(可以通过 Squid 监控这部分主机对外访问的 http(s) 请求和流量)
使用 Squid 正向代理,也有部分问题,一个是无法代理 TCP 协议,另一个是部分 Java 服务使用的 http(s) Client 无法直接使用 Squid 正向代理,要做代码修改。阿里云ECS也不支持修改主机网关IP,因此透明代理的方案也行不通
产生的问题
平时偶尔会收到 SNAT 的 EIP 入方向量告警,也就是对 Linux 主机对外请求的流量

解决过程:
如何实现监控 Linux 主机对外访问的 http(s) 和流量呢?(需求背景 1 的 Linux 主机)
思路1:
通过监控 SNAT 可以实现吗?
不能,SNAT 只能监控到哪台主机往外发的流量大小,并且还要提工单让阿里云工程师查询

思路2:可以通过阿里云 云防火墙 监控整个 VPC 对外访问的 HTTP(s) 和流量
可以,但是价格昂贵

思路3:有没有开源工具可以实现监控 Linux 主机对外访问的 http(s) 和流量
尝试过 iftop、httpry、jnettop 等工具,基本都无法监控 https 流量
思路4:自己编写 Python 脚本,实现监控 Linux 主机对外访问的 http(s) 和流量
可以使用 Python scapy 模块监控网络流量,但从 https 原理上来说, scapy 是不能获取到 https 的请求地址的

但可以做个变通,把 DNS 解析的记录也监控,就知道请求了哪个 https 地址
示例脚本如下:
# coding: utf-8
from scapy.all import *
import logging
import re
logging.basicConfig(filename='/data/scripts/tcp_sniffer/packet.log',
format='%(asctime)s - %(name)s - %(levelname)s - %(module)s: %(message)s',
datefmt='%Y-%m-%d %H:%M:%S',
level=10)
# 匹配阿里云SLB健康探测网段 100.64.0.0/10,参考:https://www.regextester.com/97320
pattern01 = re.compile(r'100\.(?:6[4-9]|[7-9]\d|1[0-1]\d|12[0-7])\.\d{1,3}\.\d{1,3}')
# 匹配一些阿里云内网DNS解析字段和NTP解析字段
pattern02 = re.compile(r'in-addr\.arpa|cnszsc|localdomain|aliyuncs\.com|aliyun\.com|NTP')
def packet_callback(pkt):
for x in pkt:
if x.haslayer(UDP):
# x.getlayer(IP).dst --> IP数据包里的目标IP
# 阿里云内网DNS ['100.100.2.136', '100.100.2.138'] 也在 100.64.0.0/10 网段范围内,所以这里先做个判断
if (x.getlayer(IP).dst in ['100.100.2.136', '100.100.2.138']):
msg = pkt.sprintf(pkt.summary() + " / packet_size: %IP.len%")
# 过滤掉 一些阿里云内网DNS解析字段和NTP解析字段
if not pattern02.search(msg):
# print '1' + msg
logging.info(msg)
# 过滤掉 阿里云SLB健康探测网段 100.64.0.0/10 的请求
if not pattern01.match(x.getlayer(IP).dst):
msg = pkt.sprintf(pkt.summary() + " / packet_size: %IP.len%")
if not pattern02.search(msg):
# print '2' + msg
logging.info(msg)
# 增加 store=0 参数,不然内存会持续升高,
# 参见:https://stackoverflow.com/questions/31116970/strange-python-memory-usage-with-scapy
sniff(filter="not (dst net 10.0.0.0/8)", prn=packet_callback, store=0)
// 运行 tcp_sniffer.py
# python tcp_sniffer.py
在另一个终端测试 http(s) 请求
# curl -I https://www.baidu.com
HTTP/1.1 200 OK
Accept-Ranges: bytes
Cache-Control: private, no-cache, no-store, proxy-revalidate, no-transform
Connection: Keep-Alive
Content-Length: 277
Content-Type: text/html
Date: Thu, 27 Jun 2019 12:02:55 GMT
Etag: "575e1f72-115"
Last-Modified: Mon, 13 Jun 2016 02:50:26 GMT
Pragma: no-cache
Server: bfe/1.0.8.18
实现效果:
2019-06-27 20:02:55 - root - INFO - tcp_sniffer: Ether / IP / UDP / DNS Qry "www.baidu.com." / packet_size: 59
2019-06-27 20:02:55 - root - INFO - tcp_sniffer: Ether / IP / UDP / DNS Qry "www.baidu.com." / packet_size: 59
2019-06-27 20:02:55 - root - INFO - tcp_sniffer: Ether / IP / TCP 10.1.18.250:37626 > 14.215.177.39:https S/ packet_size: 60
2019-06-27 20:02:55 - root - INFO - tcp_sniffer: Ether / IP / TCP 10.1.18.250:37626 > 14.215.177.39:https A/ packet_size: 40
2019-06-27 20:02:55 - root - INFO - tcp_sniffer: Ether / IP / TCP 10.1.18.250:37626 > 14.215.177.39:https PA / Raw/ packet_size: 227
2019-06-27 20:02:55 - root - INFO - tcp_sniffer: Ether / IP / TCP 10.1.18.250:37626 > 14.215.177.39:https A/ packet_size: 40
2019-06-27 20:02:55 - root - INFO - tcp_sniffer: Ether / IP / TCP 10.1.18.250:37626 > 14.215.177.39:https A/ packet_size: 40
2019-06-27 20:02:55 - root - INFO - tcp_sniffer: Ether / IP / TCP 10.1.18.250:37626 > 14.215.177.39:https A/ packet_size: 40
2019-06-27 20:02:55 - root - INFO - tcp_sniffer: Ether / IP / TCP 10.1.18.250:37626 > 14.215.177.39:https A/ packet_size: 40
2019-06-27 20:02:55 - root - INFO - tcp_sniffer: Ether / IP / TCP 10.1.18.250:37626 > 14.215.177.39:https PA / Raw/ packet_size: 166
2019-06-27 20:02:55 - root - INFO - tcp_sniffer: Ether / IP / TCP 10.1.18.250:37626 > 14.215.177.39:https A/ packet_size: 52
2019-06-27 20:02:55 - root - INFO - tcp_sniffer: Ether / IP / TCP 10.1.18.250:37626 > 14.215.177.39:https A/ packet_size: 40
2019-06-27 20:02:55 - root - INFO - tcp_sniffer: Ether / IP / TCP 10.1.18.250:37626 > 14.215.177.39:https A/ packet_size: 40
2019-06-27 20:02:55 - root - INFO - tcp_sniffer: Ether / IP / TCP 10.1.18.250:37626 > 14.215.177.39:https PA / Raw/ packet_size: 147
2019-06-27 20:02:55 - root - INFO - tcp_sniffer: Ether / IP / TCP 10.1.18.250:37626 > 14.215.177.39:https PA / Raw/ packet_size: 71
2019-06-27 20:02:55 - root - INFO - tcp_sniffer: Ether / IP / TCP 10.1.18.250:37626 > 14.215.177.39:https FA/ packet_size: 40
2019-06-27 20:02:55 - root - INFO - tcp_sniffer: Ether / IP / TCP 10.1.18.250:37626 > 14.215.177.39:https R/ packet_size: 40
2019-06-27 20:02:55 - root - INFO - tcp_sniffer: Ether / IP / TCP 10.1.18.250:37626 > 14.215.177.39:https R/ packet_size: 40
2019-06-27 20:03:05 - root - INFO - tcp_sniffer: Ether / IP / TCP 10.1.18.250:40646 > 116.31.68.142:http FA/ packet_size: 52
这样就可以把对外访问的 HTTP(s) 和流量,记录到日志文件里,或者收集到 ELK、Graylog 等日志系统中。
等到出现告警的时间点,就可以检查到是 对外访问的哪些 HTTP(s) URL 引起的告警。


其它问题
内存占用问题:
脚本启动后,发现内存持续升高,经过排查搜索发现,scapy 会保存所有捕获的数据包在内存中
加一个参数 scapy sniff store=0 即可解决

参见:
https://stackoverflow.com/questions/31116970/strange-python-memory-usage-with-scapy
https://bitbucket.org/secdev/scapy/issues/296/memory-leak-in-sniff-function-with-prn-set
网友评论