GeezerVison is showing github.com — net: add ability to reset a tcp socket · nodejs/node@da03e9f
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: doc/api/net.md +13Lines changed: 13 additions & 0 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -1055,6 +1055,19 @@ added: v0.5.10 | 1055 | 1055 |
| 1056 | 1056 | The numeric representation of the remote port. For example, `80` or `21`. | 1057 | 1057 |
| | 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 | + | 1058 | 1071 | ### `socket.resume()` | 1059 | 1072 |
| 1060 | 1073 | * Returns: {net.Socket} The socket itself. | |
Collapse file Copy file name to clipboardExpand all lines: lib/net.js +36-4Lines changed: 36 additions & 4 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -89,6 +89,7 @@ const { | 89 | 89 | ERR_INVALID_ARG_VALUE, | 90 | 90 | ERR_INVALID_FD_TYPE, | 91 | 91 | ERR_INVALID_IP_ADDRESS, | | 92 | + ERR_INVALID_HANDLE_TYPE, | 92 | 93 | ERR_SERVER_ALREADY_LISTEN, | 93 | 94 | ERR_SERVER_NOT_RUNNING, | 94 | 95 | ERR_SOCKET_CLOSED, | @@ -640,6 +641,21 @@ Socket.prototype.end = function(data, encoding, callback) { | 640 | 641 | return this; | 641 | 642 | }; | 642 | 643 |
| | 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 | +}; | 643 | 659 |
| 644 | 660 | Socket.prototype.pause = function() { | 645 | 661 | if (this[kBuffer] && !this.connecting && this._handle && | @@ -710,10 +726,20 @@ Socket.prototype._destroy = function(exception, cb) { | 710 | 726 | this[kBytesRead] = this._handle.bytesRead; | 711 | 727 | this[kBytesWritten] = this._handle.bytesWritten; | 712 | 728 |
| 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 | + } | 717 | 743 | this._handle.onread = noop; | 718 | 744 | this._handle = null; | 719 | 745 | this._sockname = null; | @@ -732,6 +758,12 @@ Socket.prototype._destroy = function(exception, cb) { | 732 | 758 | } | 733 | 759 | }; | 734 | 760 |
| | 761 | +Socket.prototype._reset = function() { | | 762 | + debug('reset connection'); | | 763 | + this.resetAndClosing = true; | | 764 | + return this.destroy(); | | 765 | +}; | | 766 | + | 735 | 767 | Socket.prototype._getpeername = function() { | 736 | 768 | if (!this._handle || !this._handle.getpeername) { | 737 | 769 | return this._peername || {}; | |
Collapse file Copy file name to clipboardExpand all lines: src/handle_wrap.h +1-1Lines changed: 1 addition & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
@@ -97,6 +97,7 @@ class HandleWrap : public AsyncWrap { | 97 | 97 | } | 98 | 98 |
| 99 | 99 | static void OnClose(uv_handle_t* handle); | | 100 | + enum { kInitialized, kClosing, kClosed } state_; | 100 | 101 |
| 101 | 102 | private: | 102 | 103 | friend class Environment; | @@ -109,7 +110,6 @@ class HandleWrap : public AsyncWrap { | 109 | 110 | // refer to `doc/contributing/node-postmortem-support.md` | 110 | 111 | friend int GenDebugSymbols(); | 111 | 112 | ListNode<HandleWrap> handle_wrap_queue_; | 112 | | - enum { kInitialized, kClosing, kClosed } state_; | 113 | 113 | uv_handle_t* const handle_; | 114 | 114 | }; | 115 | 115 |
| |
Collapse file Copy file name to clipboardExpand all lines: src/tcp_wrap.cc +24Lines changed: 24 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -97,6 +97,7 @@ void TCPWrap::Initialize(Local<Object> target, | 97 | 97 | GetSockOrPeerName<TCPWrap, uv_tcp_getpeername>); | 98 | 98 | env->SetProtoMethod(t, "setNoDelay", SetNoDelay); | 99 | 99 | env->SetProtoMethod(t, "setKeepAlive", SetKeepAlive); | | 100 | + env->SetProtoMethod(t, "reset", Reset); | 100 | 101 |
| 101 | 102 | #ifdef _WIN32 | 102 | 103 | env->SetProtoMethod(t, "setSimultaneousAccepts", SetSimultaneousAccepts); | @@ -134,6 +135,7 @@ void TCPWrap::RegisterExternalReferences(ExternalReferenceRegistry* registry) { | 134 | 135 | registry->Register(GetSockOrPeerName<TCPWrap, uv_tcp_getpeername>); | 135 | 136 | registry->Register(SetNoDelay); | 136 | 137 | registry->Register(SetKeepAlive); | | 138 | + registry->Register(Reset); | 137 | 139 | #ifdef _WIN32 | 138 | 140 | registry->Register(SetSimultaneousAccepts); | 139 | 141 | #endif | @@ -339,7 +341,29 @@ void TCPWrap::Connect(const FunctionCallbackInfo<Value>& args, | 339 | 341 |
| 340 | 342 | args.GetReturnValue().Set(err); | 341 | 343 | } | | 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; | 342 | 356 |
| | 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 | +} | 343 | 367 |
| 344 | 368 | // also used by udp_wrap.cc | 345 | 369 | MaybeLocal<Object> AddressToJS(Environment* env, | |
Collapse file Copy file name to clipboardExpand all lines: src/tcp_wrap.h +2Lines changed: 2 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -88,6 +88,8 @@ class TCPWrap : public ConnectionWrap<TCPWrap, uv_tcp_t> { | 88 | 88 | const v8::FunctionCallbackInfo<v8::Value>& args, | 89 | 89 | int family, | 90 | 90 | 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>()); | 91 | 93 |
| 92 | 94 | #ifdef _WIN32 | 93 | 95 | static void SetSimultaneousAccepts( | |
Collapse file Copy file name to clipboard +29Lines changed: 29 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 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 | +})); |
Collapse file Copy file name to clipboard +13Lines changed: 13 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 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(); |
Collapse file Copy file name to clipboard +20Lines changed: 20 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 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 | +})); |
Collapse file Copy file name to clipboard +13Lines changed: 13 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 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 | +); |
Collapse file Copy file name to clipboard +36Lines changed: 36 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 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 | +})); |
|