Skip to content

Commit da03e9f

Browse files
PupilTongtargos
authored andcommitted
net: add ability to reset a tcp socket
Fixes: #27428 PR-URL: #43112 Reviewed-By: Paolo Insogna <[email protected]> Reviewed-By: Minwoo Jung <[email protected]>
1 parent a51bdf6 commit da03e9f

12 files changed

+232
-5
lines changed

‎doc/api/net.md‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1055,6 +1055,19 @@ added: v0.5.10
10551055

10561056
The numeric representation of the remote port. For example, `80` or `21`.
10571057

1058+
### `socket.resetAndDestroy()`
1059+
1060+
<!-- YAML
1061+
added: REPLACEME
1062+
-->
1063+
1064+
* Returns:{net.Socket}
1065+
1066+
Close the TCP connection by sending an RST packet and destroy the stream.
1067+
If this TCP socket is in connecting status, it will send an RST packet and destroy this TCP socket once it is connected.
1068+
Otherwise, it will call `socket.destroy` with an `ERR_SOCKET_CLOSED` Error.
1069+
If this is not a TCP socket (for example, a pipe), calling this method will immediately throw an `ERR_INVALID_HANDLE_TYPE` Error.
1070+
10581071
### `socket.resume()`
10591072

10601073
* Returns:{net.Socket} The socket itself.

‎lib/net.js‎

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ const{
8989
ERR_INVALID_ARG_VALUE,
9090
ERR_INVALID_FD_TYPE,
9191
ERR_INVALID_IP_ADDRESS,
92+
ERR_INVALID_HANDLE_TYPE,
9293
ERR_SERVER_ALREADY_LISTEN,
9394
ERR_SERVER_NOT_RUNNING,
9495
ERR_SOCKET_CLOSED,
@@ -640,6 +641,21 @@ Socket.prototype.end = function(data, encoding, callback){
640641
returnthis;
641642
};
642643

644+
Socket.prototype.resetAndDestroy=function(){
645+
if(this._handle){
646+
if(!(this._handleinstanceofTCP))
647+
thrownewERR_INVALID_HANDLE_TYPE();
648+
if(this.connecting){
649+
debug('reset wait for connection');
650+
this.once('connect',()=>this._reset());
651+
}else{
652+
this._reset();
653+
}
654+
}else{
655+
this.destroy(newERR_SOCKET_CLOSED());
656+
}
657+
returnthis;
658+
};
643659

644660
Socket.prototype.pause=function(){
645661
if(this[kBuffer]&&!this.connecting&&this._handle&&
@@ -710,10 +726,20 @@ Socket.prototype._destroy = function(exception, cb){
710726
this[kBytesRead]=this._handle.bytesRead;
711727
this[kBytesWritten]=this._handle.bytesWritten;
712728

713-
this._handle.close(()=>{
714-
debug('emit close');
715-
this.emit('close',isException);
716-
});
729+
if(this.resetAndClosing){
730+
this.resetAndClosing=false;
731+
consterr=this._handle.reset(()=>{
732+
debug('emit close');
733+
this.emit('close',isException);
734+
});
735+
if(err)
736+
this.emit('error',errnoException(err,'reset'));
737+
}else{
738+
this._handle.close(()=>{
739+
debug('emit close');
740+
this.emit('close',isException);
741+
});
742+
}
717743
this._handle.onread=noop;
718744
this._handle=null;
719745
this._sockname=null;
@@ -732,6 +758,12 @@ Socket.prototype._destroy = function(exception, cb){
732758
}
733759
};
734760

761+
Socket.prototype._reset=function(){
762+
debug('reset connection');
763+
this.resetAndClosing=true;
764+
returnthis.destroy();
765+
};
766+
735767
Socket.prototype._getpeername=function(){
736768
if(!this._handle||!this._handle.getpeername){
737769
returnthis._peername||{};

‎src/handle_wrap.h‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ class HandleWrap : public AsyncWrap{
9797
}
9898

9999
staticvoidOnClose(uv_handle_t* handle);
100+
enum{kInitialized, kClosing, kClosed } state_;
100101

101102
private:
102103
friendclassEnvironment;
@@ -109,7 +110,6 @@ class HandleWrap : public AsyncWrap{
109110
// refer to `doc/contributing/node-postmortem-support.md`
110111
friendintGenDebugSymbols();
111112
ListNode<HandleWrap> handle_wrap_queue_;
112-
enum{kInitialized, kClosing, kClosed } state_;
113113
uv_handle_t* const handle_;
114114
};
115115

‎src/tcp_wrap.cc‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ void TCPWrap::Initialize(Local<Object> target,
9797
GetSockOrPeerName<TCPWrap, uv_tcp_getpeername>);
9898
env->SetProtoMethod(t, "setNoDelay", SetNoDelay);
9999
env->SetProtoMethod(t, "setKeepAlive", SetKeepAlive);
100+
env->SetProtoMethod(t, "reset", Reset);
100101

101102
#ifdef _WIN32
102103
env->SetProtoMethod(t, "setSimultaneousAccepts", SetSimultaneousAccepts);
@@ -134,6 +135,7 @@ void TCPWrap::RegisterExternalReferences(ExternalReferenceRegistry* registry){
134135
registry->Register(GetSockOrPeerName<TCPWrap, uv_tcp_getpeername>);
135136
registry->Register(SetNoDelay);
136137
registry->Register(SetKeepAlive);
138+
registry->Register(Reset);
137139
#ifdef _WIN32
138140
registry->Register(SetSimultaneousAccepts);
139141
#endif
@@ -339,7 +341,29 @@ void TCPWrap::Connect(const FunctionCallbackInfo<Value>& args,
339341

340342
args.GetReturnValue().Set(err);
341343
}
344+
voidTCPWrap::Reset(const FunctionCallbackInfo<Value>& args){
345+
TCPWrap* wrap;
346+
ASSIGN_OR_RETURN_UNWRAP(
347+
&wrap, args.Holder(), args.GetReturnValue().Set(UV_EBADF));
348+
349+
int err = wrap->Reset(args[0]);
350+
351+
args.GetReturnValue().Set(err);
352+
}
353+
354+
intTCPWrap::Reset(Local<Value> close_callback){
355+
if (state_ != kInitialized) return0;
342356

357+
int err = uv_tcp_close_reset(&handle_, OnClose);
358+
state_ = kClosing;
359+
if (!err & !close_callback.IsEmpty() && close_callback->IsFunction() &&
360+
!persistent().IsEmpty()){
361+
object()
362+
->Set(env()->context(), env()->handle_onclose_symbol(), close_callback)
363+
.Check();
364+
}
365+
return err;
366+
}
343367

344368
// also used by udp_wrap.cc
345369
MaybeLocal<Object> AddressToJS(Environment* env,

‎src/tcp_wrap.h‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ class TCPWrap : public ConnectionWrap<TCPWrap, uv_tcp_t>{
8888
const v8::FunctionCallbackInfo<v8::Value>& args,
8989
int family,
9090
std::function<int(constchar* ip_address, int port, T* addr)> uv_ip_addr);
91+
staticvoidReset(const v8::FunctionCallbackInfo<v8::Value>& args);
92+
intReset(v8::Local<v8::Value> close_callback = v8::Local<v8::Value>());
9193

9294
#ifdef _WIN32
9395
staticvoidSetSimultaneousAccepts(
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
'use strict';
2+
constcommon=require('../common');
3+
constnet=require('net');
4+
constassert=require('assert');
5+
6+
constserver=net.createServer();
7+
server.listen(0,common.mustCall(function(){
8+
constport=server.address().port;
9+
constconn=net.createConnection(port);
10+
server.on('connection',(socket)=>{
11+
socket.on('error',common.expectsError({
12+
code: 'ECONNRESET',
13+
message: 'read ECONNRESET',
14+
name: 'Error'
15+
}));
16+
});
17+
18+
conn.on('connect',common.mustCall(function(){
19+
assert.strictEqual(conn,conn.resetAndDestroy().destroy());
20+
conn.on('error',common.mustNotCall());
21+
22+
conn.write(Buffer.from('fzfzfzfzfz'),common.expectsError({
23+
code: 'ERR_STREAM_DESTROYED',
24+
message: 'Cannot call write after a stream was destroyed',
25+
name: 'Error'
26+
}));
27+
server.close();
28+
}));
29+
}));
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
'use strict';
2+
constcommon=require('../common');
3+
constnet=require('net');
4+
5+
constserver=net.createServer();
6+
server.listen(0);
7+
constport=server.address().port;
8+
constsocket=net.connect(port,common.localhostIPv4,common.mustNotCall());
9+
socket.on('error',common.mustNotCall());
10+
server.close();
11+
socket.resetAndDestroy();
12+
// `reset` waiting socket connected to sent the RST packet
13+
socket.destroy();
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
'use strict';
2+
3+
constcommon=require('../common');
4+
constnet=require('net');
5+
6+
constserver=net.createServer();
7+
server.listen(0,common.mustCall(function(){
8+
constport=server.address().port;
9+
constconn=net.createConnection(port);
10+
conn.on('close',common.mustCall());
11+
server.on('connection',(socket)=>{
12+
socket.on('error',common.expectsError({
13+
code: 'ECONNRESET',
14+
message: 'read ECONNRESET',
15+
name: 'Error'
16+
}));
17+
server.close();
18+
});
19+
conn.resetAndDestroy();
20+
}));
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
'use strict';
2+
constcommon=require('../common');
3+
constnet=require('net');
4+
5+
constsocket=newnet.Socket();
6+
socket.resetAndDestroy();
7+
// Emit error if socket is not connecting/connected
8+
socket.on('error',common.mustCall(
9+
common.expectsError({
10+
code: 'ERR_SOCKET_CLOSED',
11+
name: 'Error'
12+
}))
13+
);
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
'use strict';
2+
constcommon=require('../common');
3+
constassert=require('assert');
4+
constnet=require('net');
5+
6+
constsockets=[];
7+
8+
constserver=net.createServer(function(c){
9+
c.on('close',common.mustCall());
10+
11+
sockets.push(c);
12+
13+
if(sockets.length===2){
14+
assert.strictEqual(server.close(),server);
15+
sockets.forEach((c)=>c.resetAndDestroy());
16+
}
17+
});
18+
19+
server.on('close',common.mustCall());
20+
21+
assert.strictEqual(server,server.listen(0,()=>{
22+
net.createConnection(server.address().port)
23+
.on('error',common.mustCall(
24+
common.expectsError({
25+
code: 'ECONNRESET',
26+
name: 'Error'
27+
}))
28+
);
29+
net.createConnection(server.address().port)
30+
.on('error',common.mustCall(
31+
common.expectsError({
32+
code: 'ECONNRESET',
33+
name: 'Error'
34+
}))
35+
);
36+
}));

0 commit comments

Comments
(0)