来源: DevOpSec公众号 作者: DevOpSec
作为技术人员tcpdump
这个工具还是有必要了解的
当你遇到网络协议问题一筹莫展的时候,这时候往往可以通过tcpdump
来看网络的通讯过程中发生了什么事,帮助快速定位问题。
本文只介绍工作用遇到的问题供大家参考,旨在给你工作中遇到类似问题提供解决灵感,具体tcpdump怎么使用google
吧。
下面通过三个案例进行介绍:
案例一:flume
写kafka
日志报错
案例二:LB
(负载均衡)增加请求header
后,nginx
日志获取不到header
key
client_ip
案例三:mysql
QPS
特别高,但mysql
并没有慢查询,想知道topK
mysql
语句
最后:场景的http协议抓包场景
案例一:flume
写kafka
日志报错
flume
写kafka
日志如下,没有其他报错。看下面报错,向kafka
push
数据TimeoutException
但在flume
机器上telnet
kafka
9092端口是通的
这是什么原因导致的呢?
看日志没有思路,tcpdump
抓包看一下
13 May 2023 16:01:28,367 ERROR [SinkRunner-PollingRunner-DefaultSinkProcessor] (org.apache.flume.sink.kafka.KafkaSink.process:240) - Failed to publish events
java.util.concurrent.ExecutionException: org.apache.kafka.common.errors.TimeoutException: Batch Expired
at org.apache.kafka.clients.producer.internals.FutureRecordMetadata.valueOrError(FutureRecordMetadata.java:56)
at org.apache.kafka.clients.producer.internals.FutureRecordMetadata.get(FutureRecordMetadata.java:43)
at org.apache.kafka.clients.producer.internals.FutureRecordMetadata.get(FutureRecordMetadata.java:25)
at org.apache.flume.sink.kafka.KafkaSink.process(KafkaSink.java:229)
at org.apache.flume.sink.DefaultSinkProcessor.process(DefaultSinkProcessor.java:67)
at org.apache.flume.SinkRunner$PollingRunner.run(SinkRunner.java:145)
at java.lang.Thread.run(Thread.java:748)
Caused by: org.apache.kafka.common.errors.TimeoutException: Batch Expired
13 May 2023 16:01:28,367 ERROR [SinkRunner-PollingRunner-DefaultSinkProcessor] (org.apache.flume.SinkRunner$PollingRunner.run:158) - Unable to deliver event. Exception follows.
org.apache.flume.EventDeliveryException: Failed to publish events
at org.apache.flume.sink.kafka.KafkaSink.process(KafkaSink.java:252)
at org.apache.flume.sink.DefaultSinkProcessor.process(DefaultSinkProcessor.java:67)
at org.apache.flume.SinkRunner$PollingRunner.run(SinkRunner.java:145)
at java.lang.Thread.run(Thread.java:748)
Caused by: java.util.concurrent.ExecutionException: org.apache.kafka.common.errors.TimeoutException: Batch Expired
at org.apache.kafka.clients.producer.internals.FutureRecordMetadata.valueOrError(FutureRecordMetadata.java:56)
at org.apache.kafka.clients.producer.internals.FutureRecordMetadata.get(FutureRecordMetadata.java:43)
at org.apache.kafka.clients.producer.internals.FutureRecordMetadata.get(FutureRecordMetadata.java:25)
at org.apache.flume.sink.kafka.KafkaSink.process(KafkaSink.java:229)
... 3 more
在flume
机器上抓包
tcpdump port 9092 -s 0 -A -e -vvv
16:46:31.324786 52:54:00:6f:bf:d2 (oui Unknown) > 98:f2:b3:2b:74:f0 (oui Unknown), ethertype IPv4 (0x0800), length 97: (tos 0x0, ttl 63, id 3722, offset 0,flags [DF], proto TCP (6), length 83)
flume-001.28230 > 192-168-160-10.kafka.release.svc.cluster.local.XmlIpcRegSvc: Flags [P.], cksum 0xc1b4 (incorrect -> 0x3b21), seq 14182:14225, ack 6634, win 31200, length 43
E..S..@.?.k........
nF#...d.q#..P.y........'.........
producer-1......log_flume_topic
16:46:31.325436 98:f2:b3:2b:74:f0 (oui Unknown) > 52:54:00:6f:bf:d2 (oui Unknown), ethertype IPv4 (0x0800), length 704: (tos 0x0, ttl 64, id 39463, offset 0, flags [DF], proto TCP (6), length 690)
192-168-160-10.kafka.release.svc.cluster.local.XmlIpcRegSvc > flume-001.28230: Flags [P.], cksum 0x4359 (correct), seq 6634:7284, ack 14225, win 50470, length 650
E....'@.@......
....#.nFq#....e.P..&CY....................kafka-002..#.......kafka-003..#.......kafka-001..#.........log_flume_topic.............................................................
................................................... ..........................................................................................................................................................................................................................................................................................................................................................................................................................
从上面抓包信息看kafka节点192-168-160-10.kafka.release.svc.cluster.local.XmlIpcRegSvc
回包内容有kafka-002..#.......kafka-003..#.......kafka-001..#.........log_flume_topic
kafka-002、kafka-003、kafka-001
是kafka
主机名,看到这里还是不是很直观,把数据包保存到文件通过whireshark
分析一下
执行tcpdump port 9092 -s 0 -w kafka_traffic.pcap
,然后把文件用whireshark
打开
可以看到kafka
协议有Kafka Metadata v0 request
和 Kafka Metadata v0 Response
点开Kafka Metadata v0 request
协议看下详细信息
点开Kafka Metadata v0 Response
协议看下详细信息
在flume
机器上ping kafka-002
ping kafka-002
ping: cannot resolve kafka-002: Unknown host
到这里出现TimeoutException
问题就清楚,flume client
在写kafka
之前,先从kafka
拿到kafka
的broker
信息,kafka
返回的broker
地址是主机名加端口
flume
拿到kafka-002
后通过dns
解析失败,导致push evet
失败
解决办法:
在flume
机器上配置上kafka-002
的hosts
后,报错消失,问题解决
这里flume
日志有些坑返回TimeoutException
而非kafka-002 name reslove failed
使定位问题增加难度
另一种解决办法:
为什么kakfa会返回kafka-002
主机名而不是ip呢?
我们看一下kafka的配置文件,发现advertised.listeners=PLAINTEXT://kafka-002:9092
advertised.listeners
参数的作用就是将Broker
的Listener
信息发布到Zookeeper
中
所以flume
从从kafka
那里拿到的是主机名而不是ip
,可以把kafka
配置advertised.listeners
改成ip
重启kafka
问题也能解决
案例二:LB
(负载均衡)增加请求header
后,nginx
日志获取不到header
key
client_ip
先说一下场景
通过LB
做七层负载均衡后remote_addr
看到的是LB的ip
,所以在负责均衡上把客户端的ip
给增加到请求header
client_ip
里。
在nginx
里增加日志打印$http_client_ip
,并没有获取到此header
的值
这是什么原因呢?
是不是负责LB运维的小伙伴没有增加client_ip
header
?
还是header
加上了但值是空?
这就需要通过tcpdump抓包来验证一下我们的猜测。
在nginx上执行如下命令:
tcpdump -s 0 -A 'tcp[((tcp[12:1] & 0xf0) >> 2):4] = 0x47455420'|grep client_ip
结果发现:
client_ip: 1.1.1.1
由抓包信息看客户端的ip
是设置到header
里的,说明LB
的配置没问题,那问题来到了nginx
这一侧。
为什么通过$http_client_ip
没有获取到header
的值?
通过nginx
官网找header
相关配置 http://nginx.org/en/docs/http/ngx_http_core_module.html 发现
Syntax: underscores_in_headers on | off;
Default:
underscores_in_headers off;
Context: http, server
到这里真相大白,nginx
默认会忽略用户自定义带下划线的header
避免和nginx
自带的header
key
冲突
修改nginx
配置设置underscores_in_headers no;
后问题解决
案例三:mysql
QPS
特别高,但mysql
并没有慢查询,想知道topK
mysql
语句
mysql
负载变高且qps
也变的特别高,但没有慢查询,也可能是sql
query time
设置的不合理导致慢查询没有暴露出来,担心长时间这样下去会影响数据库的性能,想知道是什么语句导致?
这里没有开启mysql
的审计,调用方没有记录日志,也不好排查
那怎么处理呢?有没有非侵入且不需要研发干涉的情况下拿到topK
sql
?
这时候我们的tcpdump
就闪亮登场了
抓取mysql通用脚本如下:
cat /tmp/mdump.sh
tcpdump -i eth0 -s 0 -l -w - port 3306 | strings | perl -e '
while(<>) { chomp; next if /^[^ ]+[ ]*$/;
if(/^(SELECT|UPDATE|DELETE|INSERT|SET|COMMIT|ROLLBACK|CREATE|DROP|ALTER|CALL)/i)
{
if (defined $q) { print "$q\n"; }
$q=$_;
} else {
$_ =~ s/^[ \t]+//; $q.=" $_";
}
}'
在qps高的mysql机器上执行
sh /tmp/mdump.sh > /tmp/m.sql
30s后ctrl + c
然后执行如下命令获取top 10 SQL
grep -i ' from ' /tmp/m.sql |grep -i ' where ' |awk -F'where|WHERE' '{print $1}'|sort|uniq -c |sort -rnk1|head -n 10
找到高频sql可以找开发沟通,是否有新业务功能上线,优化方案。
由此看类似的组件也能通过这种形式进行抓包定位问题
这里再推荐一个MySQL
、Redis
、MongoDB
、http
网络抓包工具
https://github.com/40t/go-sniffer
最后:场景的http协议抓包场景
抓取HTTP GET 请求
tcpdump -i enp0s8 -s 0 -A 'tcp[((tcp[12:1] & 0xf0) >> 2):4] = 0x47455420'
解释:
tcp\[((tcp\[12:1\] & 0xf0) >> 2):4\]
定义了我们所要截取的字符串的位置(http header的后面)的4 bytes。
0x47455420
是G E T
的ASCII码。
Character | ASCII Value |
---|---|
G | 47 |
E | 45 |
T | 54 |
Space | 20 |
抓取HTTP POST 请求
tcpdump -i enp0s8 -s 0 -A 'tcp[((tcp[12:1] & 0xf0) >> 2):4] = 0x504F5354
0x504F5354
代表的是 P O S T
的ASCII码.
目的端口为80的HTTP GET请求
tcpdump -i enp0s8 -s 0 -A 'tcp dst port 80 and tcp[((tcp[12:1] & 0xf0) >> 2):4] = 0x47455420'
目的端口为80或443的HTTP GET 和POST请求(来自10.10.10.10)
tcpdump -i enp0s8 -s 0 -A 'tcp dst port 80 or tcp dst port 443 and tcp[((tcp[12:1] & 0xf0) >> 2):4] = 0x47455420 or tcp[((tcp[12:1] & 0xf0) >> 2):4] = 0x504F5354' and host 10.10.10.10
抓取HTTP GET和POST request和response
tcpdump -i enp0s8 -s 0 -A 'tcp dst port 80 and tcp[((tcp[12:1] & 0xf0) >> 2):4] = 0x47455420 or tcp[((tcp[12:1] & 0xf0) >> 2):4] = 0x504F5354 or tcp[((tcp[12:1] & 0xf0) >> 2):4] = 0x48545450 or tcp[((tcp[12:1] & 0xf0) >> 2):4] = 0x3C21444F and host 10.10.10.10'
过滤目的端口为80,host为10.10.10.10,http get/post 的request和response
0x3C21444F
是'<' 'D' 'O' 'C'
的ASCII码,作为html文件的标识符
0x48545450
是'H' 'T' 'T' 'P'
的ASCII码,用来抓取HTTP response
监测所有的HTTP request URL(GET/POST)
tcpdump -i enp0s8 -s 0 -v -n -l | egrep -i "POST /|GET /|Host:"
抓取POST请求里的password
tcpdump -i enp0s8 -s 0 -A -n -l | egrep -i "POST /|pwd=|passwd=|password=|Host:"
抓取Request和response里的cookie
tcpdump -i enp0s8 -nn -A -s0 -l | egrep -i 'Set-Cookie|Host:|Cookie:'
过滤HTTP header
#从header里过滤出user-agent
tcpdump -vvAls0 | grep 'User-Agent:'