问题背景:
服务端:openfire4.0.2,底层nio采用mina2.0.7,
客户端:windows端exe应用
安全协议:tls1.0
服务端捕获到如下异常,并且此问题是随机出现的,尤其是im(即时通信)用户较多时较为频繁出现:
[2016-07-28 18:29:48] [org.jivesoftware.openfire.nio.ConnectionHandler : exceptionCaught(146)] >Closing connection due to exception in session: (0x0000000D: nio socket, server, /10.3.17.171:53641 => /10.3.17.171:5222)
javax.net.ssl.SSLException: bad record MAC
at sun.security.ssl.Alerts.getSSLException(Alerts.java:208)
at sun.security.ssl.SSLEngineImpl.fatal(SSLEngineImpl.java:1703)
at sun.security.ssl.SSLEngineImpl.readRecord(SSLEngineImpl.java:965)
at sun.security.ssl.SSLEngineImpl.readNetRecord(SSLEngineImpl.java:890)
at sun.security.ssl.SSLEngineImpl.unwrap(SSLEngineImpl.java:764)
at javax.net.ssl.SSLEngine.unwrap(SSLEngine.java:624)
at org.apache.mina.filter.ssl.SslHandler.unwrap(SslHandler.java:728)
at org.apache.mina.filter.ssl.SslHandler.messageReceived(SslHandler.java:360)
at org.apache.mina.filter.ssl.SslFilter.messageReceived(SslFilter.java:468)
at org.apache.mina.core.filterchain.DefaultIoFilterChain.callNextMessageReceived(DefaultIoFilterChain.java:417)
at org.apache.mina.core.filterchain.DefaultIoFilterChain.access$1200(DefaultIoFilterChain.java:47)
at org.apache.mina.core.filterchain.DefaultIoFilterChain$EntryImpl$1.messageReceived(DefaultIoFilterChain.java:765)
at org.apache.mina.core.filterchain.IoFilterAdapter.messageReceived(IoFilterAdapter.java:109)
at org.apache.mina.core.filterchain.DefaultIoFilterChain.callNextMessageReceived(DefaultIoFilterChain.java:417)
at org.apache.mina.core.filterchain.DefaultIoFilterChain.fireMessageReceived(DefaultIoFilterChain.java:410)
at org.apache.mina.core.polling.AbstractPollingIoProcessor.read(AbstractPollingIoProcessor.java:710)
at org.apache.mina.core.polling.AbstractPollingIoProcessor.process(AbstractPollingIoProcessor.java:664)
at org.apache.mina.core.polling.AbstractPollingIoProcessor.process(AbstractPollingIoProcessor.java:653)
at org.apache.mina.core.polling.AbstractPollingIoProcessor.access$600(AbstractPollingIoProcessor.java:67)
at org.apache.mina.core.polling.AbstractPollingIoProcessor$Processor.run(AbstractPollingIoProcessor.java:1124)
at org.apache.mina.util.NamePreservingRunnable.run(NamePreservingRunnable.java:64)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:745)
Caused by: javax.crypto.BadPaddingException: bad record MAC
at sun.security.ssl.EngineInputRecord.decrypt(EngineInputRecord.java:246)
at sun.security.ssl.SSLEngineImpl.readRecord(SSLEngineImpl.java:959)
... 21 more
排查过程
1.首先搜索相关的资料:
http://stackoverflow.com/questions/8154617/how-to-troubleshoot-ssl-bad-record-mac-exception
http://stackoverflow.com/questions/20085490/sslengine-unwrap-javax-crypto-badpaddingexception-bad-record-mac
其中通过how-to-troubleshoot-ssl-bad-record-mac-exception的第二个答案可以看到官网对这个问题的定义:(点击TLS可访问wiki文档https://en.wikipedia.org/wiki/Transport_Layer_Security)
![SSL/TLS的bad record MAC问题排查_第1张图片](http://img.e-com-net.com/image/info8/8a4fdf6364994a9a944e75174fd27326.jpg)
知道这是TLS的消息摘要的认证出问题,很可能服务端收到了不完整的报文。此时可猜测几个原因会造成这个问题:
1.服务端的问题:a. 服务端tcp或mina缓冲区处理问题(缓冲区满,被覆盖),b.mina对某session的分组报文的接收不完整问题(半包); c.服务端的TLS处理存在问题;
2.客户端问题:a.连续请求时,客户端的缓冲区问题;客户端TLS的处理存在问题;
3.其它(网络原因造成的丢包等)
2.问题复现
这个问题之所以比较不好处理和断定,首先是因为是随机出现,而且出现概率很低,问题不好复现,只能摸索着找到复现的条件来复现。所以首先想到的是做压力测试。模拟单个客户端线程连续发包和多个客户端线程连续发包,都没有出现Bad MAC问题(而是出现了OOM异常,这是MINA层弹出的内存溢出)。经过多次测试发现,客户端exe只有在请求企业级组织结构及人员列表会很偶然才出现Bad MAC问题,所以断定很可能是在这个特殊的处理场景下的问题,因此将企业级组织结构及人员列表的数目加大几倍进行测试。跟预期的接近,Bad MAC问题越来越频繁地出现。现在分两条线进行排查,客户端着重跟踪这类请求的过程,服务端则在可复现的条件下进行排查。
3.抓包分析
服务端导入mina源码,在错误弹出的地方,跟踪调试源码,打印出入参和出参及捕获到的通信报文;同时用wireshark抓包后,对异常的地方进行报文分析,比对wireshark和服务端捕获的报文。比对结果两者一致,说明即使在错误的时候,服务端接收到了客户端发来的完整报文,到此,可以看出很可能问题出现在客户端(当然服务端的上层处理也有可能出现bug)。由此可以想到,要是能够解密出报文,来查看报文是否完整,就可以进一步确证客户端端发来的报文本身就是不完整的。
4.使用wireshark解密ssl/tls报文
具体步骤请参考博文:wireshark解密用临时秘钥加密的ssl/tls数据包
解密出来的数据是不完整的,到此可以把注意力放到客户端发送报文的处理过程上了。
5.缩小排查范围,集中处理雷区
理解请求企业级组织结构及人员列表处理过程后了解到,客户端在此类请求上采用了多线程处理(从经验得到,随机出现的异常有一部分是多线程导致的),进一步进行测试和验证,处理了线程安全导致了客户端tls加密模块产生了不完整加密报文的问题。
通信问题排查总结:
1.确定问题复现场景和条件,在测试和修正验证都需要知道这个复现条件;
2.通信底层的问题往往需要进行抓包分析;
3.跟踪直接异常点,进行状态的拦截记录,追溯问题