OpenVPN莫名其妙断线的问题及其解决Word格式文档下载.docx
《OpenVPN莫名其妙断线的问题及其解决Word格式文档下载.docx》由会员分享,可在线阅读,更多相关《OpenVPN莫名其妙断线的问题及其解决Word格式文档下载.docx(11页珍藏版)》请在冰豆网上搜索。
[succeeded]
16Test证书/UDPv4READ[22]fromP_ACK_V1kid=0[1]
16Test证书/UDPv4WRITE[114]toP_CONTROL_V1kid=0[]pid=5DATAlen=100
[6]5234
16Test证书/UDPv4READ[22]fromP_ACK_V1kid=0[2]
16Test证书/UDPv4WRITE[114]toP_CONTROL_V1kid=0[]pid=6DATAlen=100
[7]5634
16Test证书/UDPv4READ[22]fromP_ACK_V1kid=0[3]
16Test证书/UDPv4WRITE[114]toP_CONTROL_V1kid=0[]pid=7DATAlen=100
[8]5674
16Test证书/UDPv4READ[22]fromP_ACK_V1kid=0[4]
16Test证书/UDPv4WRITE[114]toP_CONTROL_V1kid=0[]pid=8DATAlen=100
[9]5678
16Test证书/UDPv4READ[22]fromP_ACK_V1kid=0[5]
16Test证书/UDPv4WRITE[114]toP_CONTROL_V1kid=0[]pid=9DATAlen=100
[10]9678
16Test证书/UDPv4READ[22]fromP_ACK_V1kid=0[6]
16Test证书/UDPv4WRITE[114]toP_CONTROL_V1kid=0[]pid=10DATAlen=100
[11]91078
16Test证书/UDPv4READ[22]fromP_ACK_V1kid=0[7]
16Test证书/UDPv4WRITE[114]toP_CONTROL_V1kid=0[]pid=11DATAlen=100
[12]910118
16Test证书/UDPv4READ[22]fromP_ACK_V1kid=0[9]
[12]10118
17MULTI:
REAPrange240->
256
17GETINSTBYREAL:
17Test证书/UDPv4READ[22]fromP_ACK_V1kid=0[10]
17Test证书/ACKoutputsequencebroken:
[12]118
17Test证书/UDPv4READ[22]fromP_ACK_V1kid=0[11]
[12]8
18MULTI:
REAPrange0->
16
18Test证书/TLS:
tls_pre_encrypt:
key_id=0
18Test证书/SENTPING
18Test证书/ACKoutputsequencebroken:
18Test证书/UDPv4WRITE[53]toP_DATA_V1kid=0DATAlen=52
....持续了60秒没有收到ID为8的ACK,因此一直都是ACKoutputsequencebroken:
54:
15Test证书/TLSError:
TLSkeynegotiationfailedtooccurwithin60seconds(checkyournetworkconnectivity)
TLShandshakefailedwithpeer没隔一段时间就会断一次,并且重连还不一定总能重连成功!
因此这里的问题有两点:
a.连接正常时断开(ping-restart的情况,上述日志没有展示)
b.重连时不成功(上述日志展示的)
2.分析
使用UDP的OpenVPN就是事多,为了避免重传叠加,在恶劣环境下还真得用UDP。
然而OpenVPN实现的UDPreliable层是一个高度简化的“按序确认连接”层,它仅仅确保了数据安序到达,并且有确认机制,和TCP那是没法比。
不过如果看一下TCP最初的方案,你会发现,TCP的精髓其实就是OpenVPN的reliable层,后来的复杂性都是针对特定情况的优化!
和TCP的实现一样,不对ACK进行ACK对发送端提出了重传滑动窗口未确认包的要求,因为纯ACK可能会丢失,这里先不讨论捎带ACK。
ACK一旦丢失,发送端肯定就要重传没有被ACK的包,关键是“什么时候去重传它”,协议本身一般都有一个或者多个Timer,Timer到期就重传,然而我个人认为这个Timer不能依赖上层,而要在协议本身实现,毕竟重传这种事对上层是不可见的!
然而,OpenVPN的reliable层在ACK丢失的应对方面却什么都没有实现,通过以上的日志可以看出,连续的:
Test证书/ACKoutputsequencebroken:
说明ID为8的包一直都得不到重传,并且从:
这几行日志可以看出,确实是没有收到ID为8的包地ACK,说明它丢失了,接下来发送的数据包将持续填充发送窗口,直到填满,ID为8的包还未重传并且收到对端对其的ACK,因此就导致了ACKoutputsequencebroken,通过查代码,12-8=4,而4正是发送窗口的长度。
持续了很久ACKoutputsequencebroken之后,还是没有重传,直到:
a.隧道建立之后的ping-restart过期
b.隧道建立阶段的TLShandshakefailed
实际上,正确的方式应该是,检测到窗口爆满就应该马上重传。
TCP通过三次重复ACK知晓丢包,而OpenVPN的reliable则通过ACKoutputsequencebroken知晓ACK丢失,这是一个信号,应该在获取这个信号后做点什么了!
3.原始方案
方案很简单,那就是在打印ACKoutputsequencebroken的逻辑块内重传所有未确认的包,然而作为一种优化,仅仅重传ID最小的包即可。
这是因为,之所以到达ACKoutputsequencebroken,是因为窗口满了,之所以满是因为ID最小的包未确认,占据了很大的一块空间以及其后面的实际上可能已经确认了的空间,因此只要ID最小的包被确认,窗口就放开了,故而仅重传ID最小的包,以期待对端能再次给出确认。
方案虽简单,但是不落实到代码还是0,以下是一些尝试
4.第一次尝试-出错
7月25日下班后,又睡不着了,自己躲在女儿的小屋,开始了coding。
首先确认一下对于乱序或者重放的包,对端也能ACK,如果不能,那就要大改了,找到了的代码,在tls_pre_decrypt中:
[plain]viewplaincopy
if(op!
=P_ACK_V1&
&
reliable_can_get(ks->
rec_reliable)){
packet_id_typeid;
/*ExtractthepacketIDfromthepacket*/
if(reliable_ack_read_packet_id(buf,&
id)){
/*Avoiddeadlockbyrejectingpacketthatwouldde-sequentializereceivebuffer*/
if(ks->
rec_reliable,id)){
if(reliable_not_replay(ks->
/*Saveincomingciphertextpackettoreliablebuffer*/
structbuffer*in=reliable_get_buf(ks->
rec_reliable);
ASSERT(in);
ASSERT(buf_copy(in,buf));
reliable_mark_active_incoming(ks->
rec_reliable,in,id,op);
}
.ACKoutputsequencebroken:
[12]8
二次尝试-成功
失败!
夜以沉默,心思向谁说
然而这个问题没有那么复杂,案件的侦破很简单,那就是看代码,终于找到了reliable_schedule_now函数,关键是它的注释:
/*scheduleallpendingpacketsforimmediateretransmit*/
重传!
对的,是重传!
既然OpenVPN本身有了重传,那么我的那个重传就是多此一举了!
因此还是按照步骤来吧,直接调用这个接口即可,话说一定要用既有的接口,千万不要重复实现既有逻辑!
于是patch变得更加简单了,仅仅修改一个reliable_get_buf_output_sequenced函数即可:
structbuffer*
reliable_get_buf_output_sequenced(structreliable*rel)
{
structgc_arenagc=gc_new();
inti;
packet_id_typemin_id=0;
boolmin_id_defined=false;
structbuffer*ret=NULL;
/*findminimumactivepacket_id*/
for(i=0;
i<
rel->
size;
++i){
conststructreliable_entry*e=&
rel->
array[i];
if(e->
active){
if(!
min_id_defined||e->
packet_id<
min_id){
min_id_defined=true;
min_id=e->
packet_id;
min_id_defined||(int)(rel->
packet_id-min_id)<
size){
ret=reliable_get_buf(rel);
}else{
#ifdefRETRY
reliable_schedule_now(rel);
化(去掉副作用)
上面的修改虽然简单,但是带来了一个副作用,那就是一次broken会带来reliable窗口里面所有的包都会重传,其实我们只需要重传ID最小的那个就可以了,毕竟它是罪魁祸首!
如果因为网络环境导致的ACK丢失,继而重传了所有的包,可能会带来更多的丢包,这个在TCP上体现最深刻了,因此只重传最小ID的那个包,既然它的ACK丢失导致了broken,那么就再发它一次,保证网络管道上的包数量守恒!
另外,如果毫无判断地重传,可能会误判很多ACK丢包,其实有些ID稍微大些的ACK并没有丢,它只是乱序到达了而已!
没有免费的午餐,因此不得不作的就是修改reliable_schedule_now逻辑:
...
reliable_schedule_id(rel,0,min_id);
.
}
reliable_schedule_id(rel,0,min_id);
真正的修改是schedule逻辑:
void
reliable_schedule_id(structreliable*rel,time_ttimeout,packet_id_typeid)
dmsg(D_REL_DEBUG,"
ACKreliable_schedule_now"
);
hold=false;
++i)
{
structreliable_entry*e=&
active)
if(id!
=0&
e->
packet_id==id){
next_try=now+timeout;
timeout=rel->
initial_timeout;
break;
e->
#endif
/*scheduleallpendingpacketsforimmediateretransmit*/
reliable_schedule_now(structreliable*rel)
#调用新接口函数
returnreliable_schedule_id(rel,0,0);
#else
f