JDBC Connection Reset问题分析

6 篇文章 0 订阅

当启动失败时,日志里有如下的异常,看起来似乎和网络有关。

java.sql.SQLRecoverableException: I/O Exception: Connection reset

at oracle.jdbc.driver.SQLStateMapping.newSQLException(SQLStateMapping.java:281)

at oracle.jdbc.driver.DatabaseError.newSQLException(DatabaseError.java:118)

at oracle.jdbc.driver.DatabaseError.throwSqlException(DatabaseError.java:224)

at oracle.jdbc.driver.DatabaseError.throwSqlException(DatabaseError.java:296)

at oracle.jdbc.driver.DatabaseError.throwSqlException(DatabaseError.java:611)

at oracle.jdbc.driver.T4CConnection.logon(T4CConnection.java:455)

at oracle.jdbc.driver.PhysicalConnection.<init>(PhysicalConnection.java:494)

at oracle.jdbc.driver.T4CConnection.<init>(T4CConnection.java:199)

at oracle.jdbc.driver.T4CDriverExtension.getConnection(T4CDriverExtension.java:30)

at oracle.jdbc.driver.OracleDriver.connect(OracleDriver.java:503)

at java.sql.DriverManager.getConnection(DriverManager.java:582)

at java.sql.DriverManager.getConnection(DriverManager.java:154)

根据网上的资料,在sqlnet.ora文件中定义SQLNET.INBOUND_CONNECT_TIMEOUT变量,经过尝试,设置为0或者一个比较大的值如30,都可以消除掉前述问题。根据资料介绍,这个变量用来控制客户端通过认证的时间间隔,假如认证时间超时,则本次数据库链接创建操作就会失败,而缩短超时时间,可以有效的阻止DoS类型的攻击。根据安全加固操作指导,加固操作确实包含用于修改认证超时时间的指令。

问题定位到这里,应该说找到了规避手段,也了解引发问题的初因,但现场实施人员对于我的解释并不满意。好在我又找到一条新线索。

新线索

Oracle官网论坛里找到一个帖子,讨论的问题和我遇到的问题类似,但提出的问题原因和解决方法比较有意思。按照帖子里的说法,问题的根因和Java的安全随机数生成器的实现原理相关。

java.security.SecureRandom is a standard API provided by sun. Among various methods offered by this class void nextBytes(byte[]) is one. This method is used for generating random bytes. Oracle 11g JDBC drivers use this API to generate random number during

login. Users using Linux have been encountering SQLException("Io exception: Connection

reset").

 

The problem is two fold

 

1. The JVM tries to list all the files in the /tmp (or alternate tmp directory set by -Djava.io.tmpdir) when SecureRandom.nextBytes(byte[]) is invoked. If the number of files is large the method takes a long time to respond and hence cause the server to timeout

 

2. The method void nextBytes(byte[]) uses /dev/random on Linux and on some machines which lack the random number generating hardware the operation slows down to the extent of bringing the whole login process to a halt. Ultimately the the user encounters SQLException("Io exception:

Connection reset")

 

Users upgrading to 11g can encounter this issue if the underlying OS is Linux which is running on a faulty hardware.

 

Cause

The cause of this has not yet been determined exactly. It could either be a problem in your hardware or the fact that for some reason the software cannot read from /dev/random

 

Solution 

Change the setup for your application, so you add the next parameter to the java command:

 

-Djava.security.egd=file:/dev/../dev/urandom

现场实施人员对于这个帖子里的信息比较感兴趣。按照帖子里的修改方法,在测试环境和生产环境做了多次验证,惊喜的发现问题得到了解决。

 

随机数生成器

如果不是为了解决问题,平时也不会去刻意查阅底层实现相关的原理,这次是个好机会。网上关于/dev/random的介绍很多,只列出要点:

1) /dev/randomLinux内核提供的安全随机数生成设备;

2) /dev/random依赖系统中断信息来生成随机数,因而设备数目比较少时,产生随机数的速度比较慢,当应用对随机数的需求比较大时会供不应求;

3) /dev/random在读取时会阻塞调用线程;

4) /dev/urandom/dev/random的改良版本,解决了随机数生成慢、阻塞调用的问题,但同时稍微降低了安全性;

5) Linux环境下man random命令可以查阅到/dev/random/dev/urandom的介绍,比较详尽;


附:

回到tomcat文档里的建议,采用非阻塞的熵源(entropy source),通过java系统属性来设置:

-Djava.security.egd=file:/dev/./urandom

这个系统属性egd表示熵收集守护进程(entropy gathering daemon),但这里值为何要在devrandom之间加一个点呢?是因为一个jdk的bug,在这个bug的连接里有人反馈及时对 securerandom.source 设置为 /dev/urandom 它也仍然使用的 /dev/random,有人提供了变通的解决方法,其中一个变通的做法是对securerandom.source设置为 /dev/./urandom 才行。也有人评论说这个不是bug,是有意为之。

在JAVA中的配置发生器

在JAVA中可以通过两种方式去设置指定的随机数发生器

1.      -Djava.security.egd=file:/dev/random或者 -Djava.security.egd=file:/dev/urandom

2.      修改配置文件java.security 在jvm_home\jre\lib\security

参数securerandom.source=file:/dev/urandom

/dev/random 是堵塞的,在读取随机数的时候,当熵池值为空的时候会堵塞影响性能,尤其是系统大并发的生成随机数的时候,如果在随机数要求不高的情况下,可以去读取/dev/urandom

整个流程如下:

JAVA中首先读取系统参数java.security.egd,如果值为空的时候,读取java.security配置文件中的参数securerandom.source, 在通常情况下,就是读取参数securerandom.source,默认值是/dev/urandom,也就是因该是不堵塞的。

但实际情况是,在测试linux环境下的时候,你会发现默认值却是堵塞的。

查看代码sun.security.provider.SeedGenerator.java

[java]  view plain  copy
  print ?
  1. if (egdSource.equals(URL_DEV_RANDOM) || egdSource.equals(URL_DEV_URANDOM)){    
  2.             try {    
  3.                 instance = new NativeSeedGenerator();    
  4.                 if (debug != null) {    
  5.                   debug.println("the instance:"+instance.getClass());    
  6.                     debug.println("Using operating system seed generator");    
  7.                 }    
  8.             } catch (IOException e) {    
  9.                 if (debug != null) {    
  10.                     debug.println("Failed to use operating system seed "    
  11.                                   + "generator: "+ e.toString());    
  12.                 }    
  13.             }    
  14.         }    
  15. else if (egdSource.length() != 0) {    
  16.             try {    
  17.                 instance = new URLSeedGenerator(egdSource);    
  18.                 if (debug != null) {    
  19.                     debug.println("Using URL seed generator reading from "    
  20.                                   + egdSource);    
  21.                 }    
  22.             } catch (IOException e) {    
  23.                 if (debug != null)    
  24.                     debug.println("Failed to create seed generator with "    
  25.                                   + egdSource + ": " + e.toString());    
  26.             }    
  27.         }    

在代码中可以看到当配置值是file:/dev/random或者file:/dev/urandom的时候,启用NativeSeedGenerator, 而在linux下的NativeSeedGenerator类

[java]  view plain  copy
  print ?
  1. class NativeSeedGenerator extends SeedGenerator.URLSeedGenerator{  
  2.     NativeSeedGenerator() throws IOException {  
  3.   
  4.         super();  
  5.   
  6.     }  
  7.   
  8. }  

只是简单的继承了一下URLSeedGenerator, 而对URLSeedGenerator默认的构造函数里则强制读取了文件/dev/random

[java]  view plain  copy
  print ?
  1. URLSeedGenerator()throwsIOException {  
  2.   
  3.             this(SeedGenerator.URL_DEV_RANDOM);  
  4.   
  5.         }  

也就是说哪怕设置了-Djava.security.egd=file:/dev/urandom,最后的结果一样是读取file:/dev/random, 不清楚究竟是代码的bug,还是有意为之?但解决办法相当的有趣,因为在上面的代码中强匹配了file:/dev/random,file:/dev/urandom的值

而对linux来说要表示urandom的路径就很多种方式了

比如file:/dev/./urandom 或者 file:/dev/../dev/urandom 这样就可以绕过JAVA的简单检查了,这应该是JAVA的一个security的bug

设置

[java] view plain copy
 print?
  1. -Djava.security.egd=file:/dev/./urandom  

就可以绕过保护,而使用URLSeedGenerator,读取文件/dev/./urandom 也就是/dev/urandom文件


Tip: 打开security的debug log

通过设置参数 

-Djava.security.debug=all

可以控制台看到所有security的log


参考资料:

http://blog.csdn.net/jackie_xiaonan/article/details/37740359

http://ifeve.com/jvm-random-and-entropy-source/

http://blog.csdn.net/raintungli/article/details/42876073





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值