Skip to content

Commit 8f5b457

Browse files
PupilTongbengl
authored andcommitted
net: add ability to reset a tcp socket
Fixes: #27428 PR-URL: #43112 Reviewed-By: Paolo Insogna <paolo@cowtech.it> Reviewed-By: Minwoo Jung <nodecorelab@gmail.com>
1 parent 41b69e3 commit 8f5b457

12 files changed

+232
-5
lines changed

doc/api/net.md

+13
Original file line numberDiff line numberDiff line change
@@ -1087,6 +1087,19 @@ added: v0.5.10
10871087

10881088
The numeric representation of the remote port. For example, `80` or `21`.
10891089

1090+
### `socket.resetAndDestroy()`
1091+
1092+
<!-- YAML
1093+
added: REPLACEME
1094+
-->
1095+
1096+
* Returns: {net.Socket}
1097+
1098+
Close the TCP connection by sending an RST packet and destroy the stream.
1099+
If this TCP socket is in connecting status, it will send an RST packet and destroy this TCP socket once it is connected.
1100+
Otherwise, it will call `socket.destroy` with an `ERR_SOCKET_CLOSED` Error.
1101+
If this is not a TCP socket (for example, a pipe), calling this method will immediately throw an `ERR_INVALID_HANDLE_TYPE` Error.
1102+
10901103
### `socket.resume()`
10911104

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

lib/net.js

+36-4
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
return this;
641642
};
642643

644+
Socket.prototype.resetAndDestroy = function() {
645+
if (this._handle) {
646+
if (!(this._handle instanceof TCP))
647+
throw new ERR_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(new ERR_SOCKET_CLOSED());
656+
}
657+
return this;
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+
const err = 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+
return this.destroy();
765+
};
766+
735767
Socket.prototype._getpeername = function() {
736768
if (!this._handle || !this._handle.getpeername || this.connecting) {
737769
return this._peername || {};

src/handle_wrap.h

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

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

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

src/tcp_wrap.cc

+24
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+
void TCPWrap::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+
int TCPWrap::Reset(Local<Value> close_callback) {
355+
if (state_ != kInitialized) return 0;
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

+2
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(const char* ip_address, int port, T* addr)> uv_ip_addr);
91+
static void Reset(const v8::FunctionCallbackInfo<v8::Value>& args);
92+
int Reset(v8::Local<v8::Value> close_callback = v8::Local<v8::Value>());
9193

9294
#ifdef _WIN32
9395
static void SetSimultaneousAccepts(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
'use strict';
2+
const common = require('../common');
3+
const net = require('net');
4+
const assert = require('assert');
5+
6+
const server = net.createServer();
7+
server.listen(0, common.mustCall(function() {
8+
const port = server.address().port;
9+
const conn = 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+
}));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
'use strict';
2+
const common = require('../common');
3+
const net = require('net');
4+
5+
const server = net.createServer();
6+
server.listen(0);
7+
const port = server.address().port;
8+
const socket = 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();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const net = require('net');
5+
6+
const server = net.createServer();
7+
server.listen(0, common.mustCall(function() {
8+
const port = server.address().port;
9+
const conn = 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+
}));
+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
'use strict';
2+
const common = require('../common');
3+
const net = require('net');
4+
5+
const socket = new net.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+
);
+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
'use strict';
2+
const common = require('../common');
3+
const assert = require('assert');
4+
const net = require('net');
5+
6+
const sockets = [];
7+
8+
const server = 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+
}));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const net = require('net');
5+
const assert = require('assert');
6+
7+
const server = net.createServer();
8+
server.listen(0, common.mustCall(() => {
9+
const port = server.address().port;
10+
const conn = net.createConnection(port);
11+
server.on('connection', (socket) => {
12+
socket.on('error', common.expectsError({
13+
code: 'ECONNRESET',
14+
message: 'read ECONNRESET',
15+
name: 'Error'
16+
}));
17+
});
18+
19+
conn.on('connect', common.mustCall(() => {
20+
assert.strictEqual(conn, conn.resetAndDestroy().destroy());
21+
conn.on('error', common.mustNotCall());
22+
23+
conn.write(Buffer.from('fzfzfzfzfz'), common.expectsError({
24+
code: 'ERR_STREAM_DESTROYED',
25+
message: 'Cannot call write after a stream was destroyed',
26+
name: 'Error'
27+
}));
28+
server.close();
29+
}));
30+
}));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
'use strict';
2+
const common = require('../common');
3+
const net = require('net');
4+
5+
const server = net.createServer();
6+
server.listen(0);
7+
const port = server.address().port;
8+
const conn = net.createConnection(port);
9+
10+
conn.on('error', common.mustCall(() => {
11+
conn.resetAndDestroy();
12+
}));
13+
14+
conn.on('close', common.mustCall());
15+
server.close();

0 commit comments

Comments
 (0)