diff options
| author | bitflash-maintainer <noreply@bitflash.invalid> | 2026-08-13 14:54:17 +0000 |
|---|---|---|
| committer | bitflash-maintainer <noreply@bitflash.invalid> | 2026-08-23 04:37:36 +0000 |
| commit | 0620e69d8cee549ce64d323682a39129900a4f1c (patch) | |
| tree | 09e189805bb64772fe9c04b4679c25131afdc3b2 /src | |
| parent | 88d12ee18e7a3df489cc600f92c4f68506d307fe (diff) | |
net: do not consume a header until the body is there, and cap what one can claim
Upstream #109's message-stall rework, hand-merged so it keeps the progress
timing this tree already had.
The old path consumed the header, found the payload short, inserted the header
back at the front of vRecv, then Sleep(100) inside the shared message thread.
A peer that announces a large payload and drips bytes made every pass pay an
O(n) insert and a 100 ms stall -- one peer slowing the thread that serves all
of them. Now the header is read from a copy and only skipped once the whole
body has arrived, so there is nothing to put back and nothing to sleep for.
Kept OURS, not upstream's, on the timeout: upstream restarts its clock only
when the (size, command) pair changes, which drops a peer sending one big
message slowly but honestly. This times the STALL -- the clock restarts on any
progress -- which is the distinction that matters on a slow link.
Also from #109: a header could announce 256 MB while serialize.h refuses
anything over MAX_SIZE (32 MB), so the check admitted an allocation the parser
would reject anyway and burned a peer slot for two minutes doing it.
MAX_PROTOCOL_MESSAGE_SIZE ties the two together. 8x less a peer can make us
hold, and this node takes public Stratum.
And #103's other half: ~CNode closed its socket with a raw closesocket(),
invisible to the accounting, and left hSocket set afterwards -- which is how a
descriptor number that now belongs to somebody else gets closed a second time.
(cherry picked from commit 2f25e2e07985e9bc732cadfbb3c9ac233895a814)
Diffstat (limited to 'src')
| -rw-r--r-- | src/main.cpp | 82 | ||||
| -rw-r--r-- | src/net.h | 15 |
2 files changed, 75 insertions, 22 deletions
diff --git a/src/main.cpp b/src/main.cpp index 37a15c6..11eb11d 100644 --- a/src/main.cpp +++ b/src/main.cpp | |||
| @@ -2827,11 +2827,13 @@ bool ProcessMessages(CNode* pfrom) | |||
| 2827 | if (LogAcceptsCategory("net")) printf("\n\nPROCESSMESSAGE SKIPPED %d BYTES\n\n", (int)(pstart - vRecv.begin())); | 2827 | if (LogAcceptsCategory("net")) printf("\n\nPROCESSMESSAGE SKIPPED %d BYTES\n\n", (int)(pstart - vRecv.begin())); |
| 2828 | vRecv.erase(vRecv.begin(), pstart); | 2828 | vRecv.erase(vRecv.begin(), pstart); |
| 2829 | 2829 | ||
| 2830 | // Read the header from a copy first. The old path consumed the header, | 2830 | // Read the header from a copy first, and do not consume it until the |
| 2831 | // noticed the payload was incomplete, inserted the header back at the | 2831 | // whole body is here. The old path consumed the header, noticed the |
| 2832 | // front of the receive buffer, then slept inside the shared message | 2832 | // payload was incomplete, inserted the header back at the front of the |
| 2833 | // thread. A peer could announce a large payload and drip bytes forever, | 2833 | // receive buffer, then slept inside the shared message thread. A peer |
| 2834 | // making every pass pay for an O(n) insert and a 100 ms stall. | 2834 | // could announce a large payload and drip bytes forever, making every |
| 2835 | // pass pay for an O(n) insert and a 100 ms stall -- a cheap way for one | ||
| 2836 | // peer to slow the thread that serves all of them. (upstream #109) | ||
| 2835 | CMessageHeader hdr; | 2837 | CMessageHeader hdr; |
| 2836 | CDataStream vHeader(vRecv.begin(), vRecv.begin() + sizeof(CMessageHeader), | 2838 | CDataStream vHeader(vRecv.begin(), vRecv.begin() + sizeof(CMessageHeader), |
| 2837 | vRecv.nType, vRecv.nVersion); | 2839 | vRecv.nType, vRecv.nVersion); |
| @@ -2841,7 +2843,10 @@ bool ProcessMessages(CNode* pfrom) | |||
| 2841 | if (LogAcceptsCategory("net")) printf("\n\nPROCESSMESSAGE: ERRORS IN HEADER %s\n\n\n", hdr.GetCommand().c_str()); | 2843 | if (LogAcceptsCategory("net")) printf("\n\nPROCESSMESSAGE: ERRORS IN HEADER %s\n\n\n", hdr.GetCommand().c_str()); |
| 2842 | if (hdr.nMessageSize > MAX_PROTOCOL_MESSAGE_SIZE) | 2844 | if (hdr.nMessageSize > MAX_PROTOCOL_MESSAGE_SIZE) |
| 2843 | pfrom->fDisconnect = true; | 2845 | pfrom->fDisconnect = true; |
| 2844 | pfrom->nIncompleteMessageStart = 0; | 2846 | pfrom->nMsgWaitSince = 0; |
| 2847 | pfrom->nMsgWaitSize = 0; | ||
| 2848 | // The header was read from a copy, so it is still in vRecv. Skip it | ||
| 2849 | // explicitly or this loop never advances. | ||
| 2845 | vRecv.ignore(sizeof(CMessageHeader)); | 2850 | vRecv.ignore(sizeof(CMessageHeader)); |
| 2846 | continue; | 2851 | continue; |
| 2847 | } | 2852 | } |
| @@ -2852,30 +2857,63 @@ bool ProcessMessages(CNode* pfrom) | |||
| 2852 | unsigned int nPayloadAvailable = vRecv.size() - sizeof(CMessageHeader); | 2857 | unsigned int nPayloadAvailable = vRecv.size() - sizeof(CMessageHeader); |
| 2853 | if (nMessageSize > nPayloadAvailable) | 2858 | if (nMessageSize > nPayloadAvailable) |
| 2854 | { | 2859 | { |
| 2855 | if (pfrom->nIncompleteMessageStart == 0 || | 2860 | // Wait for the rest of the message. The header stays in vRecv. |
| 2856 | pfrom->nIncompleteMessageSize != nMessageSize || | 2861 | // |
| 2857 | pfrom->strIncompleteMessageCommand != strCommand) | 2862 | // This is the mechanism the original note asked for: "need a |
| 2863 | // mechanism to give up waiting for overlong message size error". | ||
| 2864 | // Without one the wait is unbounded, and unbounded here means | ||
| 2865 | // permanent. The header stays at the front of vRecv and the loop | ||
| 2866 | // breaks; if the body never completes, every later pass re-reads | ||
| 2867 | // that same header and breaks in the same place. The peer is never | ||
| 2868 | // parsed again -- no blocks, no inv, not even its version message, | ||
| 2869 | // so it never reports a height either, which is why a wedged node | ||
| 2870 | // has peers and GetPeerMedianHeight() still returns -1. | ||
| 2871 | // | ||
| 2872 | // Nothing rescues it. A header could claim up to 256 MB and still | ||
| 2873 | // pass CMessageHeader::IsValid() -- now capped at | ||
| 2874 | // MAX_PROTOCOL_MESSAGE_SIZE -- and any byte that arrives | ||
| 2875 | // refreshes nLastRecv, so the inactivity timeout that drops a | ||
| 2876 | // silent peer never fires on one that is merely unreadable. The | ||
| 2877 | // socket stays ESTABLISHED and select() keeps offering it. | ||
| 2878 | // Measured on a wedged pool: this branch ran 2014 times in the | ||
| 2879 | // deaf window, against 607 in the healthy window before it. | ||
| 2880 | if (LogAcceptsCategory("net")) printf("MESSAGE-BREAK 2\n"); | ||
| 2881 | // | ||
| 2882 | // Time the STALL, not the wait. A big message on a slow link is | ||
| 2883 | // still arriving, and dropping that peer would be a bug of our own | ||
| 2884 | // making; what must be caught is a buffer that has stopped growing | ||
| 2885 | // while we go on expecting more. So the clock restarts whenever | ||
| 2886 | // even one more byte has landed since the last look. | ||
| 2887 | // | ||
| 2888 | // Upstream #109 times this differently: it restarts the clock only | ||
| 2889 | // when the (size, command) pair changes, which drops a peer sending | ||
| 2890 | // one big message slowly but honestly. Progress is the better test, | ||
| 2891 | // so that half is kept as it was. | ||
| 2892 | int64 nNow = GetTime(); | ||
| 2893 | if (pfrom->nMsgWaitSince == 0 || nPayloadAvailable > pfrom->nMsgWaitSize) | ||
| 2858 | { | 2894 | { |
| 2859 | pfrom->nIncompleteMessageStart = GetTime(); | 2895 | pfrom->nMsgWaitSince = nNow; |
| 2860 | pfrom->nIncompleteMessageSize = nMessageSize; | 2896 | pfrom->nMsgWaitSize = nPayloadAvailable; |
| 2861 | pfrom->strIncompleteMessageCommand = strCommand; | ||
| 2862 | } | 2897 | } |
| 2863 | else if (GetTime() - pfrom->nIncompleteMessageStart > | 2898 | else if (nNow - pfrom->nMsgWaitSince > BTF_MSG_WAIT_MAX_SECS) |
| 2864 | BTF_INCOMPLETE_MESSAGE_TIMEOUT_SECS) | ||
| 2865 | { | 2899 | { |
| 2866 | LogPrint("net", | 2900 | // Deliberately not behind a debug category. This is the node |
| 2867 | "disconnecting %s: incomplete %s message, %u of %u bytes after %d seconds\n", | 2901 | // going deaf to a peer, which is the thing nobody could see. |
| 2868 | pfrom->addr.ToString().c_str(), strCommand.c_str(), | 2902 | printf("peer %s announced a %u byte '%s' and has sent nothing more " |
| 2869 | nPayloadAvailable, nMessageSize, | 2903 | "for %d s (%d of %u payload bytes) -- dropping, this peer is unreadable\n", |
| 2870 | BTF_INCOMPLETE_MESSAGE_TIMEOUT_SECS); | 2904 | pfrom->addr.ToString().c_str(), nMessageSize, |
| 2905 | strCommand.c_str(), (int)(nNow - pfrom->nMsgWaitSince), | ||
| 2906 | (int)nPayloadAvailable, nMessageSize); | ||
| 2871 | pfrom->fDisconnect = true; | 2907 | pfrom->fDisconnect = true; |
| 2872 | } | 2908 | } |
| 2909 | // No re-insert, no Sleep(): the header was never consumed, so there | ||
| 2910 | // is nothing to put back, and sleeping here stalls every peer. | ||
| 2873 | break; | 2911 | break; |
| 2874 | } | 2912 | } |
| 2875 | pfrom->nIncompleteMessageStart = 0; | ||
| 2876 | pfrom->nIncompleteMessageSize = 0; | ||
| 2877 | pfrom->strIncompleteMessageCommand.clear(); | ||
| 2878 | vRecv.ignore(sizeof(CMessageHeader)); | 2913 | vRecv.ignore(sizeof(CMessageHeader)); |
| 2914 | // The body arrived, so this peer is readable again. | ||
| 2915 | pfrom->nMsgWaitSince = 0; | ||
| 2916 | pfrom->nMsgWaitSize = 0; | ||
| 2879 | 2917 | ||
| 2880 | // Copy message to its own buffer | 2918 | // Copy message to its own buffer |
| 2881 | CDataStream vMsg(vRecv.begin(), vRecv.begin() + nMessageSize, vRecv.nType, vRecv.nVersion); | 2919 | CDataStream vMsg(vRecv.begin(), vRecv.begin() + nMessageSize, vRecv.nType, vRecv.nVersion); |
| @@ -146,6 +146,16 @@ static const int64 BTF_NO_PEER_HEIGHT_MAX_SECS = 20 * 60; | |||
| 146 | // minutes is far beyond that, and well inside BTF_RECV_TIMEOUT_SECS. | 146 | // minutes is far beyond that, and well inside BTF_RECV_TIMEOUT_SECS. |
| 147 | static const int64 BTF_MSG_WAIT_MAX_SECS = 2 * 60; | 147 | static const int64 BTF_MSG_WAIT_MAX_SECS = 2 * 60; |
| 148 | 148 | ||
| 149 | // Largest payload a peer may announce in a message header: MAX_PROTOCOL_MESSAGE_SIZE | ||
| 150 | // is defined further up this file. This tree reached the same cap by the same | ||
| 151 | // reasoning as upstream #109 -- a 256 MB allowance is eight times what the | ||
| 152 | // deserializer will accept (serialize.h throws above MAX_SIZE), so the header | ||
| 153 | // check admitted an allocation the parser would only reject later, after | ||
| 154 | // BTF_MSG_WAIT_MAX_SECS of a stalled peer slot -- so only the note survives. | ||
| 155 | // | ||
| 156 | // Both definitions were textually clean and 59 lines apart, so the replay onto | ||
| 157 | // upstream merged them without a conflict and only the compiler objected. | ||
| 158 | |||
| 149 | // Send a ping after this long with nothing to say, so a peer running the | 159 | // Send a ping after this long with nothing to say, so a peer running the |
| 150 | // inactivity check above does not mistake a quiet node for a dead one. Must | 160 | // inactivity check above does not mistake a quiet node for a dead one. Must |
| 151 | // stay well under BTF_RECV_TIMEOUT_SECS. | 161 | // stay well under BTF_RECV_TIMEOUT_SECS. |
| @@ -741,6 +751,11 @@ public: | |||
| 741 | { | 751 | { |
| 742 | if (hSocket != INVALID_SOCKET) | 752 | if (hSocket != INVALID_SOCKET) |
| 743 | { | 753 | { |
| 754 | // Route through BtfCloseSocket so the accounting sees this close. | ||
| 755 | // A raw closesocket() here was invisible to sockcount, which is | ||
| 756 | // why the leak numbers never added up, and leaving the descriptor | ||
| 757 | // set afterwards is how a number that now belongs to somebody else | ||
| 758 | // gets closed a second time. (#103) | ||
| 744 | BtfCloseSocket(hSocket); | 759 | BtfCloseSocket(hSocket); |
| 745 | hSocket = INVALID_SOCKET; | 760 | hSocket = INVALID_SOCKET; |
| 746 | } | 761 | } |