Repository navigation
Expand file tree
/
Copy pathP2PeerCon.cpp
More file actions
6200 lines (5759 loc) · 256 KB
/
Copy pathP2PeerCon.cpp
File metadata and controls
6200 lines (5759 loc) · 256 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Copyright © 2002-2009, 2026 Ivyware Pty Ltd, Khrustal & Mann
// MELBOURNE, VICTORIA, AUSTRALIA, 3000
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied. See the License for the specific language governing
// permissions and limitations under the License.
//
//
// Description: P2PeerCon base class implementation
// NOTES: Manages connections between P2PeerHub's
//
#include "stdafx.h"
#include "Kernel32_Ext.h"
#include "P2PeerCon.h"
#include "P2PeerHub.h"
#include "P2Pwin32.h"
#include "P2PCngCrypto.h" // ephemeral ECDH P-256, HKDF-SHA256
#include "P2PeerioGcm.h" // the session cypher installed on agreement
#include <map> // P2PeerConSourceTally
#include <mutex> // ThatAddrLock
static std::mutex& ThatAddrLock ( const P2PeerCon* pCon ); // with CopyP2Paddress()
///////////////////////////////////////////////////////////////////////
// A freshly sealed message, rebuilt compactly for the wire (F-L4-1, 3.4.4)
// NOTES: Sealing swaps the body in with SetData, which grows it inside the
// message's own heap and leaves the old block behind as free space,
// and the full-replica send path puts the whole heap in the frame. So
// a sealed frame was the plaintext's block PLUS the sealed one: ~2x the
// body, and a body over ~15 KB broke the 32768-byte frame cap and the
// next hub dropped the LINK. SealAppMsgOutbound zeroes the plaintext,
// so what this removes is the size
// : HOW: the frame is the whole heap, so the copy goes into a heap sized
// for what is live: the bytes that were not the body before the seal
// (nOverhead, measured by the caller), plus the sealed body, plus a
// margin for the seal marker and anything the send path adds after
// this. Too small only costs a grow - the heap extends itself, by at
// most a fifth - and the assignment copies the live items alone, as
// the copy constructor's does
// : NOT the copy constructor itself: it reserves the source's used
// count plus 4 KB, and the frame being the whole heap, it came out
// BIGGER (measured, p2p_sealwire: 9568 bytes against 6541). NOT
// Msgcore's partial image either: rebuilt as a P2PeerMsg it loses the
// root prefix ("Incompatible c_vBlob() type") - nothing in the tree
// sends with a mask other than ~0, and that path shows it
// : Returns the message to send and deletes the one it replaced. The
// connection owns m_pP2PeerMsgSend outright - taken off its queue,
// deleted when the send completes - so nothing else holds the old one
// : A failed copy keeps the original. Its plaintext is already zeroed,
// so that costs size, not confidentiality
static P2PeerMsg*
CompactSealed ( P2PeerMsg *pMsg, size_t nOverhead )
{
const size_t kMargin = 512;
const size_t nCapacity = nOverhead + pMsg -> DataSize ( ) + kMargin;
P2PeerMsg *pCompact = 0;
try
{
pCompact = new P2PeerMsg ( pMsg -> GetP2Pmsgnn ( ), (VBLsize)nCapacity );
*pCompact = *pMsg;
}
catch ( P2Pevent *pEVT )
{
if ( pEVT ) pEVT -> Cancel ( );
delete pCompact;
pCompact = 0;
}
catch ( ... )
{
delete pCompact;
pCompact = 0;
}
if ( !pCompact )
return pMsg;
delete pMsg;
return pCompact;
}
///////////////////////////////////////////////////////////////////////
// Per-source accept accounting (Stage 4 step 11)
// NOTES: One block per SERVICE, shared with every connection it accepts, for
// the same reason m_pxAccepted is shared: neither the service nor a
// child has to outlive the other, and a back-pointer to the service
// would dangle whenever a child survives a hub teardown
// : A map rather than a counter, because the question is per KEY, and a
// lock rather than an atomic, because the read-decide-insert on admit
// and the decrement-erase on release are each more than one operation.
// The section is this block's own and is held for the length of a map
// operation, so it contends with nothing but other accepts on the same
// service
// : Entries are ERASED at zero rather than left at zero. A source that
// connects once and goes must not cost the service a map entry
// forever - that is the same unbounded growth the accept cap exists to
// stop, moved one level down and made permanent
struct P2PeerConSourceTally
{
CRITICAL_SECTION m_oCSection;
std::map<P2PsourceKey,long> m_oCounts;
P2PeerConSourceTally ( ) { InitializeCriticalSection ( &m_oCSection ); }
~P2PeerConSourceTally ( ) { DeleteCriticalSection ( &m_oCSection ); }
long
Count ( P2PsourceKey xSource ) const
{
if ( !xSource ) return 0;
P2PsafeCS oSafeCS = const_cast<CRITICAL_SECTION&>(m_oCSection);
std::map<P2PsourceKey,long>::const_iterator it = m_oCounts.find ( xSource );
return it == m_oCounts.end ( ) ? 0 : it->second;
}
void
Add ( P2PsourceKey xSource )
{
if ( !xSource ) return;
P2PsafeCS oSafeCS = m_oCSection;
m_oCounts[xSource] += 1;
}
void
Sub ( P2PsourceKey xSource )
{
if ( !xSource ) return;
P2PsafeCS oSafeCS = m_oCSection;
std::map<P2PsourceKey,long>::iterator it = m_oCounts.find ( xSource );
if ( it == m_oCounts.end ( ) )
return; // never counted; nothing to give back
if ( --it->second <= 0 )
m_oCounts.erase ( it );
}
};
///////////////////////////////////////////////////////////////////////
// Constructors and destructor
IMPLEMENT_DYNCREATE(P2PeerCon,P2PeerConPlc)
constexpr __time64_t
iLoginTimeoutSeconds = 20; // Login sequences must be
// processed within this time
P2PeerCon::P2PeerCon ( )
{
// Firstly
RenderThisSafe ( );
}
//
// Parameters: P2PaddrSTR strThatP2Paddr
// Identification address for that, or the other end
//
P2PeerCon::P2PeerCon ( P2PaddrSTR pThatP2PaddrSTR
, P2Peerio *pP2Peerio )
{
// Firstly
RenderThisSafe ( );
m_oP2Padomain = pThatP2PaddrSTR;
//m_oThisP2Paddr = pThisP2PaddrSTR;
m_oThatP2Paddr = pThatP2PaddrSTR;
if ( !pP2Peerio )
pP2Peerio = new P2Peerio ( );
m_pP2Peerio = pP2Peerio -> Register ( this );
}
P2PeerCon::P2PeerCon ( P2Peerio *pP2Peerio )
{
// Firstly
RenderThisSafe ( );
//m_oThisP2Paddr = pThisP2PaddrSTR;
//m_oThatP2Paddr = pThisP2PaddrSTR;
if ( !pP2Peerio )
pP2Peerio = new P2Peerio ( );
m_pP2Peerio = pP2Peerio -> Register ( this );
}
P2PeerCon::~P2PeerCon ( )
{
// Timers
// NOTES: Target specific timers. Others handled in derived class
// : Swallowed, because this is a DESTRUCTOR and an exception leaving
// one is std::terminate. CancelP2PmsgTimer no longer throws on the
// fired-already race (P2Pwin32.cpp) which is what used to reach
// here, but a destructor is the wrong place to depend on that
// holding - and a timer that cannot be cancelled is not a reason to
// take the process down either way
try
{
CancelP2PmsgTimer ( m_uRestartTimerID );
CancelP2PmsgTimer ( m_uLoginTimerID );
CancelP2PmsgTimer ( m_uThrottleTimerID );
}
catch ( P2Pevent *pEVT ) { if ( pEVT ) pEVT->Cancel ( false ); }
catch ( ... ) { }
// Accept admission control
// NOTES: Give the slot back. This is the ONLY release path, deliberately:
// a connection is counted for exactly as long as the object exists,
// so no close path, drop path or exception unwind can leak a slot by
// forgetting to call something. The counter is shared rather than
// reached through the service, so it is still alive here even when
// the service was destroyed first
if ( m_bAcceptCounted && m_pxAccepted )
{
m_pxAccepted -> fetch_sub ( 1 );
m_bAcceptCounted = false;
}
// Per-source slot, released on the same principle and in the same place:
// counted for exactly as long as the object exists, so no close path, drop
// path or exception unwind can leak one
// NOTES: m_xAcceptSource is non-zero exactly when a slot was taken, so it
// is both the flag and the key. Cleared, so a double destruction
// would give back nothing rather than something
if ( m_xAcceptSource && m_pxSourceTally )
{
m_pxSourceTally -> Sub ( m_xAcceptSource );
m_xAcceptSource = 0;
}
// P2PeerMsg queue
// NOTES: D39. The message HANDED TO THE WIRE is not on the queue - the
// send path takes it off with GetP2PeerMsg() and parks it in
// m_pP2PeerMsgSend until the send completion returns, and only
// then releases it. FlushP2PeerMsg() cannot see it, so a
// connection destroyed with a send still outstanding lost the
// only pointer to that message and leaked the whole P2PeerMsg
// graph - which is why LSan reported it with NO live root.
// : OnClose() drains BOTH (it reports each as undeliverable), so
// this is only reachable when the object is destroyed without
// its ON_P2PeerCon_CLOSE handler running - a hub torn down with
// a message in flight.
// : Released the way OnClose() releases it, by plain delete: the
// message was never confirmed sent, so P2Peerio's
// PreDestroySendP2PeerMsg() reuse hook - which is for messages
// that WERE sent - does not apply.
delete m_pP2PeerMsgSend;
m_pP2PeerMsgSend = 0;
FlushP2PeerMsg ( );
DeleteCriticalSection ( &m_oCSectMsgQue );
// Resources
Drop ( 0 );
// OVERLAPPED resources
// NOTES: Reclaimed previously, to be sure, to be sure.
DropOVERLAPPED ( m_pOVERLAPPEDsend, false );
DropOVERLAPPED ( m_pOVERLAPPEDrecv, false );
DropOVERLAPPED ( m_pOVERLAPPEDaccept, false );
DropOVERLAPPED ( m_pOVERLAPPEDconnect, false );
// Session key agreement
// NOTES: The deferred login payload is application data that never
// reached the wire (the connection dropped mid-exchange), so it is
// released here rather than leaked
KeyXReset ( );
// Garbage collection
//delete m_pP2PeerMsgQue;
delete m_pP2Peerio;
if ( m_pEvent )
m_pEvent -> Cancel ( false );
delete m_pstrVisualSummary;
}
void
P2PeerCon::RenderThisSafe ( )
{
// Publics
m_uAutoRestart = 20000; // Autorestart delay in MSecs
// Resources
m_hP2PmsgCon = NULL;
m_hCPort = NULL;
m_hFile = NULL;
m_hFileCPort = NULL;
m_dwCompletionKey = (UINT_PTR)this;
m_hCPortP2PumpID = 0;
m_pP2PeerTarget = 0;
m_pP2Peerio = 0;
m_pstrVisualSummary = 0;
// Transport trust class - the operator's ceiling only. The transport's
// own answer is a virtual and has nothing to initialise here; this is the
// demotion, and the top of the enum means "not demoted"
m_eTrustCeiling = P2PeerConTrust_InProcess;
// Peer login authentication
// NOTES: Per-connection runtime state, not configuration. Refer
// P2PeerCon.h and P2PAuthLogin.h
memset ( m_AuthNonce, 0, sizeof(m_AuthNonce) );
m_bAuthNonce = false;
m_cbAuthStrip = 0;
m_oAuthPeer = P2Paddr ( );
// Session key agreement
m_pKeyX = 0;
memset ( m_KeyXPubThis, 0, sizeof(m_KeyXPubThis) );
memset ( m_KeyXPubThat, 0, sizeof(m_KeyXPubThat) );
memset ( m_KeyXBind, 0, sizeof(m_KeyXBind) );
m_bKeyXBound = false;
m_bKeyXDone = false;
m_bKeyXClient = false;
m_pLoginDefer = 0;
m_cbLoginDefer = 0;
m_bLoginDefer = false;
// Timers
m_uRestartTimerID = 0;
m_uLoginTimerID = 0;
m_uThrottleTimerID = 0;
m_bRecvThrottled = false;
m_cRecvThrottled = 0;
// Accept admission control
// NOTES: THE COUNTER IS ALLOCATED HERE, and it used to be allocated by the
// first AcceptSpawn() off a SERVICE instead. That saved one control
// block on a connection that never accepts anything, and cost a
// data race: the pump thread wrote the pointer inside AcceptSpawn()
// while the owner thread read it through GetAcceptedCount(), which
// is a bare `m_pxAccepted ? ...` test. TSan, p2p_conreap,
// 2026-09-17. Allocating in the constructor is the only fix that
// does not put a lock on the accept path: after this the pointer is
// written once, before the object can be reached from anywhere, and
// never again on a service
// : A child still takes its PARENT's block in AcceptSpawn(), so the
// one allocated here is dropped on the spot for an accepted
// connection. One allocation, freed immediately, on a path that is
// already doing a socket accept
m_pxAccepted = std::make_shared<std::atomic<long>> ( 0 );
m_xMaxAccepted = DEF_P2PeerConAccept;
m_xLoginDeadline = DEF_P2PeerConLogin;
m_bAcceptCounted = false;
// Per-source, and the same rule for the same reason
m_pxSourceTally = std::make_shared<P2PeerConSourceTally> ( );
m_xMaxAcceptedPerSource = DEF_P2PeerConAcceptSource;
m_xAcceptSource = 0;
// Overlapped IO
m_pP2PeerMsgSend = 0;
//m_bP2PeerMsgHdr = true;
// Overlapped IO buffers
m_pOVERLAPPEDsend = 0;
m_pOVERLAPPEDrecv = 0;
m_pOVERLAPPEDaccept = 0;
m_pOVERLAPPEDconnect = 0;
// Queues
//m_pP2PeerMsgQue = new P2PeerMsgQue ( 0 );
InitializeCriticalSection ( &m_oCSectMsgQue );
// Addressing
m_eP2PeerIDmap = P2PeerIDmap_NONE;
m_oVNetPeer = P2Paddr ( ); // not a VNet mount
//m_oThisP2Paddr = 0; m_nThisP2PeerID1 = 0;
//m_oThatP2Paddr = 0; m_nThatP2PeerID1 = 0;
// Control
m_dwState . store ( 0 );
m_eP2PeerConMode = P2PeerCon_Unknown;
m_pEvent = 0;
}
P2PeerCon*
P2PeerCon::AcceptSpawn ( P2PeerCon *pConSpawn )
{
// Accept
// NOTES: Mandatory P2PeerConPlc integration - and it is LAST, below.
// Posting links the child into the hub's list, and from then on
// any thread walking that list reads it: P2PeerWeb's site workers
// read GetMode() and the address of every connection they find.
// Posted first, as it was until 3.3.2, those walks read the
// settlement below while this thread was still writing it (TSan,
// W4). Posted last, the hub lock PostP2PmsgCon and
// RetainP2PmsgCons both take orders every store here before them
const bool bPost = pConSpawn != this && !pConSpawn->m_hP2PmsgCon;
// Settlement
if ( pConSpawn != this )
{
pConSpawn -> m_hCPortP2PumpID = m_hCPortP2PumpID;
pConSpawn -> m_hCPort = m_hCPort;
delete pConSpawn -> m_pP2Peerio;
pConSpawn -> m_pP2Peerio = m_pP2Peerio -> Clone();
pConSpawn -> m_pP2Peerio -> Register ( pConSpawn );
//pConSpawn -> m_oThisP2Paddr = m_oThisP2Paddr;
pConSpawn -> m_oThisP2Paddr1 = L"";
// A listener with ConState_AcceptUnnamed hands out a blank for
// OnAccept(addr) to fill, here and not after the post below
pConSpawn -> m_oThatP2Paddr = HasState ( ConState_AcceptUnnamed )
? P2Paddr ( )
: m_oThatP2Paddr;
pConSpawn -> m_oThatP2Paddr1 = L"";
pConSpawn -> m_eP2PeerIDmap = m_eP2PeerIDmap;
pConSpawn -> m_eP2PeerConMode = P2PeerCon_Accept;
pConSpawn -> m_oP2Padomain = m_oP2Padomain;
// The trust CEILING, and it is the one line in the trust-class feature
// that this function can forget.
// NOTES: The CLASS is not here and cannot be - TrustClass() is a
// virtual, the child is the same class as the service, and that
// is the whole reason this feature is allowed to exist beside
// the hub-only rule in SECURITY.md
// : The DEMOTION is per-object state and has to be carried, or an
// operator who demoted a listener demoted only the object that
// never carries traffic. Losing this line makes a child read
// the class its transport vouches for instead of the lower one
// the operator asked for - which is the direction that fails
// towards the transport's own honest answer rather than past it,
// and it is pinned by a gate-test phase rather than left to that
pConSpawn -> m_eTrustCeiling = m_eTrustCeiling;
// A service that is a VNet mount accepts mounts (p2p_PeerLan_L7b.md §7
// R3): the child speaks for the same far network the listener was
// posted for, and without this line it would bind sources as an
// ordinary child and drop the link on the first one
pConSpawn -> m_oVNetPeer = m_oVNetPeer;
pConSpawn -> m_dwState . store (
m_dwState.load ( ) & ~( ConState_Recv
| ConState_Send
| ConState_Login
| ConState_AcceptUnnamed ) );
// Accept admission control
// NOTES: Accounting only. The REFUSAL is the transport's, and it has
// already happened by the time we are here - only the transport
// knows how to discard a half-accepted endpoint, and it has to do
// it before allocating the connection this is settling. Refer
// P2PeerConWsa::AcceptSpawn()
// : Allocated in the CONSTRUCTOR, not here. It was lazy until
// 2026-09-17, and the lazy write was on this thread while the
// owner read the same pointer - refer RenderThisSafe()
// : The child inherits the login deadline and NOT the cap. The cap
// belongs to whoever accepts; an accepted connection accepts
// nothing, and a cap sitting on it would be read by nobody
// : The child is not reachable from any other thread yet, so these
// stores need no ordering of their own - they are atomic because
// the members are, not because this moment demands it
pConSpawn -> m_pxAccepted = m_pxAccepted;
pConSpawn -> m_xMaxAccepted . store ( 0 );
pConSpawn -> m_xLoginDeadline . store ( m_xLoginDeadline.load ( ) );
pConSpawn -> m_bAcceptCounted = true;
m_pxAccepted -> fetch_add ( 1 );
// Per-source, same shape and same moment
// NOTES: The key is asked of THIS - the service - and it must be asked
// HERE. This is the last instant the half-accepted endpoint is
// still the service's; the transport hands it to the child on the
// next statement after this call returns, and afterwards nothing
// can name where it came from
// : Allocated with the connection even when no bound is set,
// because the count is worth having either way -
// GetAcceptedCountFromSource() is
// what an operator asks when deciding what to set the bound TO
// : A zero key stores nothing. m_xAcceptSource stays 0, the
// destructor gives nothing back, and no map entry is made for a
// transport that has no sources to distinguish
pConSpawn -> m_pxSourceTally = m_pxSourceTally;
pConSpawn -> m_xMaxAcceptedPerSource . store ( 0 );
pConSpawn -> m_xAcceptSource = AcceptSourceKey ( );
m_pxSourceTally -> Add ( pConSpawn->m_xAcceptSource );
}
// Accept, now that the child is whole
if ( bPost )
{
pConSpawn -> m_pP2PeerTarget = m_pP2PeerTarget;
PostP2PmsgCon ( GetP2PmsgHubID(/*this*/), pConSpawn );
}
// Tidy up, and
// NOTES:
return pConSpawn;
}
///////////////////////////////////////////////////////////////////////
// IOCP Integration
//
//
// Associates connection object with IO Completion Port
// NOTES: Refer win32 CreateIoCompletionPort() for further details
//
//
// Parameters: HANDLE hFile
// File for with IOCP instance is to be created
void
P2PeerCon::CreateIOCP ( HANDLE hFile )
{
// To be sure, to be sure
if ( ( m_hFile &&
m_hFile != hFile ) ||
m_hFileCPort )
EVERR->Module (__FUNCTION__)
->AFP((UINT_PTR)hFile)->AFPcon(this)
->Message (_T("IOCP already created") )
->Advice (_T("Incomplete reset or sequence bug") )
->HResult ( GetLastError() )->Throw();
m_hFile = hFile;
// IO completion port attachment
// NOTES:
m_hFileCPort = CreateIoCompletionPort ( m_hFile
, m_hCPort
, m_dwCompletionKey
, 0 );
if ( m_hFileCPort == 0 )
EVERR->Module (__FUNCTION__)
->AFPcon(this)
->Message (_T("CreateIoCompletionPort() failed") )
->HResult ( GetLastError() )->Throw();
}
//
// Description: Processes I/O completion port packets mapped to and
// instance of this object.
// NOTES: Such packets are retieved via the Microsoft
// File Storage SDK - GetQueuedCompletionStatus()
// module.
//
// Exceptions: Notification will eventually be routed through to
// ON_P2PeerCon_CLOSE() handler. At which point Restart()
// may be used on non accepted sockets or this object
// conDROP'ed
// : ON_P2PeerCon_CLOSE() handler to which this posted object
// is routed MUST cancel the attached event. OnClose()
// contains such default processing.
// : P2P_Close notification MUST be performed once per
// attached event. Subsequent P2P_Close notifications are
// blocked until the original event is cancelled.
//
//
// Parameters: DWORD dwError
// Number of bytes transferred during an I/O operation
// that has completed. Refer ::GetQueuedCompletionStatus
// for further details.
//
// DWORD dbBytes
// Number of bytes transferred during an I/O operation
// that has completed. Refer ::GetQueuedCompletionStatus
// for further details.
//
// OVERLAPPEDcon *pOVERLAPPEDcon
// Address of OVERLAPPEDcon structure that was specified
// for the I/O operation.
//
bool
P2PeerCon::On_QueuedCompletionStatus ( DWORD dwError
, DWORD dwBytes
, OVERLAPPEDcon *pOVERLAPPEDcon )
{
// Pre-processing
HRESULT hr = dwError;
if ( hr == S_OK )
hr = pOVERLAPPEDcon -> hr;
ASSERT(pOVERLAPPEDcon->bQueued);
ASSERT(AfxIsValidAddress(pOVERLAPPEDcon, sizeof(OVERLAPPEDcon)));
// Recv
// NOTES: Default implementation.
if ( pOVERLAPPEDcon == m_pOVERLAPPEDrecv )
{
ASSERT(m_eP2PeerConMode!=P2PeerCon_SERVICE);
// F-S4-1 - ORDERLY CLOSE, and it is read here because this is the only
// point at which both halves of the question are in hand.
//
// Two completely different things arrive at this branch as a SUCCESS
// carrying ZERO BYTES, and on Windows nothing else tells them apart:
//
// - an ARMING post. PostOVERLAPPED() and RearmRecv() do not issue a
// read; they PostQueuedCompletionStatus(.., 0, ..) so that the
// completion re-enters here and RecvP2PeerMsg() issues the actual
// read. Accept() and OnConnect() start every connection this way
// - the peer's FIN. A real ReadFile that completes with 0 bytes,
// which is the ONLY notification a stream socket gives of an
// orderly close
//
// Treating the second as the first is the whole defect: the branch
// parsed no message, posted no further read and returned, so the
// connection was never dropped and never reaped. On Windows both
// accept bounds then counted connections EVER ACCEPTED rather than
// connections currently held - SetMaxAccepted(1024) retiring a service
// permanently after 1024 connect-and-leave cycles, which makes the
// admission-control protection a slower version of the attack it
// exists to refuse - plus a slow leak of connection objects.
//
// Treating the FIRST as the second is equally wrong and was measured
// before this was written: it dropped every connection at accept.
//
// So the answer is a MARK SET AT SUBMISSION, cleared here on receipt -
// the same shape as OVERLAPPED::_p2p_key. bRecvSubmitted is true only
// when P2Peerio::Recv actually put a read on the wire for this object.
// Read and clear it FIRST: the loop below re-enters RecvP2PeerMsg,
// which submits the next read and sets the mark again.
//
// LINUX ALREADY DID THIS and that is why the defect was one-platform:
// the io_uring shim marks the op _p2p_op=P2POP_READ at submission and
// reports res==0 on a read as ERROR_HANDLE_EOF. Synthesising the same
// code here is deliberate - it puts both platforms on the ONE existing
// failure path below rather than giving Windows a second close route
// with its own notification order to get wrong.
bool bRecvCompleted = pOVERLAPPEDcon->bRecvSubmitted;
pOVERLAPPEDcon->bRecvSubmitted = false;
if ( hr == S_OK &&
dwBytes == 0 &&
bRecvCompleted &&
RecvZeroIsOrderlyClose ( ) )
hr = ERROR_HANDLE_EOF;
// Success
// NOTES: Received buffer may contain several messages, in which
// case we process to completion.
// : Handle 3rdParty formats by overriding P2Peerio
releaseOVERLAPPED ( pOVERLAPPEDcon );
if ( hr == S_OK &&
m_hFileCPort )
{
// BACKPRESSURE, and it is expressed by NOT ASKING (Stage 4 step 12).
// RecvP2PeerMsg() is what issues the next Recv() on this connection,
// so declining to call it leaves no read outstanding: the socket
// buffer fills, the TCP window closes, and the peer is throttled by
// the transport rather than by an exception thrown at it. Nothing is
// discarded and nothing is refused - the bytes stay where they are
// until this connection asks again.
//
// In the LOOP CONDITION rather than at the bottom of the body,
// because the budget can already be at its mark when this completion
// arrives - in which case the right number of messages to take off
// this peer is none, and a check at the bottom would take one first.
//
// The resume is RearmRecv(), which posts m_pOVERLAPPEDrecv to arm a
// read rather than perform one and so re-enters here. It is NOT
// PostOVERLAPPED, and not for tidiness: refer RearmRecv for the
// guard that made the class's own P2PsigCon_RECV signal unusable on
// any connection that had ever received a message.
P2PeerMsg *pMsg;
pOVERLAPPEDcon -> dwBytes += dwBytes;
while ( !HoldRecvForBackpressure ( ) &&
(pMsg=m_pP2Peerio
->RecvP2PeerMsg(m_hFile,pOVERLAPPEDcon)) != NULL )
{
// Interface mapping
// NOTES: Mandatory
if ( m_eP2PeerIDmap )
PerformIFaddrTransform ( true, pMsg );
// Special interceptions
// NOTES: AuthGateInbound() proves the claimed identity BEFORE
// the message is posted, so a refusal drops the
// connection before OnLogin() can set ConState_Login -
// the state every later check keys off. It is a no-op
// on a hub that does not require authentication.
// Session key agreement. Consumed here and never posted: the
// exchange is library business, so it needs no signal, no pump-map
// entry and no application handler. Both arms delete the message
// themselves because the pump never takes ownership of it.
if ( pMsg->Map_MatchName(P2Pmsg_KeyX) )
{
try { KeyXOnRequest ( pMsg ); }
catch ( ... ) { delete pMsg; throw; }
delete pMsg;
continue;
}
else if ( pMsg->Map_MatchName(P2Pmsg_KeyXAck) )
{
try { KeyXOnAck ( pMsg ); }
catch ( ... ) { delete pMsg; throw; }
delete pMsg;
continue;
}
else if ( pMsg->Map_MatchName(P2Pmsg_Login) &&
!pMsg->GetSource() )
{
AuthGateInbound ( pMsg, false );
PostP2Pmsg ( m_oThatP2Paddr, CN_P2PeerCon, P2P_Login
, this, pMsg, m_hCPortP2PumpID ); //was 0
}
else if ( pMsg->Map_MatchName(P2Pmsg_Login) &&
pMsg->GetSource() )
{
AuthGateInbound ( pMsg, false );
PostP2Pmsg ( pMsg->GetSource(), CN_P2PeerCon, P2P_Login
, this, pMsg, m_hCPortP2PumpID ); //was 0
}
else if ( pMsg->Map_MatchName(P2Pmsg_LoginAck) )
{
AuthGateInbound ( pMsg, true );
PostP2Pmsg ( m_oThatP2Paddr, CN_P2PeerCon, P2P_LoginAck
, this, pMsg, m_hCPortP2PumpID ); // was 0
}
/*else if ( pMsg->GetDestin() == m_oThisP2Paddr &&
pMsg->Type() == MSG_P2PeerAck )
PostP2Pmsg ( m_oThatP2Paddr, CN_P2PeerCon, P2P_Ack
, this, pMsg )*/
// Application message. Login / LoginAck are handled by the branches
// above and the cipher-exchange (P2Pmsg_CypherEx) is intercepted in
// the io layer and posted as a CN_P2PeerCon notification
// (P2Peerio::DecryptP2PiomageSwap returns NULL for it), so none of
// the handshake traffic reaches here.
//
// GateAppMsgInbound() enforces both admission rules - login
// completed, and the declared source bound to the logged-in identity
// (SECURITY_REVIEW M2) - and on refusal deletes the message and
// throws, dropping the connection. It is shared with the
// P2PsigCon_MSG injection path so the two routes cannot drift.
else
{
GateAppMsgInbound ( pMsg );
// W3 (p2p_PumpPerf.md): this recv completion already runs on the
// destination pump thread, so dispatch the parsed application
// message in place instead of round-tripping the pump's own FIFO -
// but only when that is safe (local pump + empty FIFO, so ordering
// is preserved). Otherwise fall back to the FIFO exactly as before.
if ( !TryInlineDispatchP2Pmsg ( pMsg->GetSource(), pMsg
, m_hCPortP2PumpID ) )
PostP2Pmsg ( pMsg->GetSource(), CN_P2PeerMsg, 0
,(P2PeerCon *)0, pMsg, m_hCPortP2PumpID ); // was 0
}
}
}
// Failure
// NOTES: Remote connection dropped out. P2PeerMsg receipt for
// connection discontinued.
// : Perform On_P2PeerCon_CLOSE() notification.
else if ( hr &&
m_hFileCPort )
{
EVERR->Module (__FUNCTION__)
->AFP(dwError)->AFP(dwBytes)->AFPcon(this)
->Message_T("Receive failure" )
->Advice_T ("Conection dropped out" )
->HResult( hr )->Throw();
}
// Cancellation
// NOTES: IoCancel(m_hFileCPort), CloseHandle(m_hFileCPort) or
// whatever issued. P2PeerMsg receipt for connection
// discontinued
else
m_pOVERLAPPEDrecv = DropOVERLAPPED ( pOVERLAPPEDcon );
}
// Send
// NOTES: Confirmation must be received before next P2PeerMsg
// transmission is attempted.
else if ( pOVERLAPPEDcon == m_pOVERLAPPEDsend )
{
ASSERT(m_eP2PeerConMode!=P2PeerCon_SERVICE);
// Success
// NOTES: Passed buffer contains details of previous message
// and return with details of next
// : Handle 3rdParty formats by overriding RecvP2PeerMsg()
releaseOVERLAPPED ( pOVERLAPPEDcon );
if ( hr == S_OK &&
m_hFileCPort )
{
if ( m_pP2PeerMsgSend )
{
((P2PeerMsgPrefix *)m_pP2PeerMsgSend->r_data().c_vBlob())
-> uiState &= ~P2PeerMsgState_Posted;
delete m_pP2Peerio -> PreDestroySendP2PeerMsg ( m_pP2PeerMsgSend );
m_pP2PeerMsgSend = 0;
}
// Fetch next message
if ( m_pP2PeerMsgSend == NULL )
{
m_pP2PeerMsgSend = GetP2PeerMsg ( );
// Interface mapping
// NOTES: Mandatory
if ( m_eP2PeerIDmap &&
m_pP2PeerMsgSend )
PerformIFaddrTransform ( false, m_pP2PeerMsgSend );
// VNet mount (p2p_PeerLan_L7b.md §7)
// NOTES: Rewrites the addresses into the far network's terms, and is
// a gateway rather than a pipe: neither the seal nor the
// attestation below runs on a mount, since past the rewrite
// this hub's keys name nothing. Refer VNetMountOut
const bool bVNetMount = IsVNetMount ( );
if ( bVNetMount && m_pP2PeerMsgSend &&
!VNetMountOut ( m_pP2PeerMsgSend ) )
{
delete m_pP2PeerMsgSend;
m_pP2PeerMsgSend = 0;
}
// End-to-end seal
// NOTES: FIRST of the two, and the order is load-bearing. The
// attestation digests the BODY, and the receiver checks it in
// GateAppMsgInbound -> GateRelayInbound, which runs BEFORE
// OpenAppMsgInbound (:2708 then :2731). So the bytes the far
// end verifies are the SEALED ones, and the signature has to
// be made over those - seal, then attest.
// : THIS USED TO RUN SECOND, and its own note argued for the
// order it did not implement: attesting first signs the
// plaintext, sealing then replaces the body, and the far end
// verifies a signature over something it no longer has. The
// result was "signature does not verify" at the destination
// and a dropped link, on the DEFAULT policy. It was invisible
// because nothing reached it - c_vBlob could not grow a
// payload, so no relayed message survived the seal at all
// (Msgcore/P2Pmsg.cpp:983). p2p_sealbcast is the run.
// : It is also the better construction on its own merits.
// Signing the ciphertext is encrypt-then-sign, the same
// discipline p2pseal uses inside its own envelope, so a
// forgery is thrown out before any asymmetric work is done on
// attacker-chosen plaintext.
// : THE ONE HOOK ON THIS PATH THAT CAN REFUSE. Everything else
// here reports and sends anyway; a seal that could not be made
// drops the message, because sending it would be sending in
// clear the thing the operator asked to be hidden. Refusing
// before the attestation also saves an ECDSA operation on a
// message that is about to be dropped
if ( !bVNetMount && m_pP2PeerMsgSend )
{
const bool bWasSealed = m_pP2PeerMsgSend->IsSealed ( );
// What is not the body, measured BEFORE the seal grows the heap
const size_t nUsed = m_pP2PeerMsgSend->P3PmsgBSTR::Sizeof ( );
const size_t nBody = m_pP2PeerMsgSend->DataSize ( );
const size_t nOverhead = nUsed > nBody ? nUsed - nBody : nUsed;
if ( !SealAppMsgOutbound ( m_pP2PeerMsgSend ) )
{
delete m_pP2PeerMsgSend;
m_pP2PeerMsgSend = 0;
}
// Sealed HERE, just now: send a compact copy. Refer CompactSealed
else if ( !bWasSealed && m_pP2PeerMsgSend->IsSealed ( ) )
m_pP2PeerMsgSend = CompactSealed ( m_pP2PeerMsgSend, nOverhead );
}
// Origin attestation
// NOTES: AFTER the transform and after the seal, so what is signed is
// what goes on the wire. This is the single funnel every
// outbound P2PeerMsg passes through, which is why it is here
// rather than in PostP2PeerMsg - the queue is entered from
// application threads and left from exactly one
// : Does nothing at all unless the hub requires attestation.
// Refer AttestAppMsgOutbound
if ( !bVNetMount && m_pP2PeerMsgSend )
AttestAppMsgOutbound ( m_pP2PeerMsgSend );
}
// May become idle here
if ( m_pP2PeerMsgSend )
m_pP2Peerio -> SendP2PeerMsg ( m_hFile, m_pP2PeerMsgSend
, pOVERLAPPEDcon );
else if ( HasState ( ConState_CloseOnIdle ) )
Drop ( 0 );
}
// Failure
// NOTES: Remote connection dropped out. P2PeerMsg transmission
// for connection discontinued.
// : Perform On_P2PeerCon_CLOSE() notification.
else if ( hr &&
m_hFileCPort )
EVERR->Module (__FUNCTION__)
->AFP(dwError)->AFP(dwBytes)->AFPcon(this)
->Message_T("Send failed")
->Advice_T("Connection dropped out" )
->HResult( hr )->Throw();
// Cancellation
// NOTES: IoCancel(m_hFileCPort), CloseHandle(m_hFileCPort) or
// whatever issued. P2PeerMsg transmission for connection
// discontinued
else
m_pOVERLAPPEDsend = DropOVERLAPPED ( pOVERLAPPEDcon );
}
// Accept
// NOTES: Perform On_P2PeerCon_ACCEPT() notification. At which
// point the intercepting handler will initiate state
// changes.
// : Perform On_P2PeerCon_LISTEN() notification. At which
// point the intercepting handler will listen for another
// connection
else if ( pOVERLAPPEDcon == m_pOVERLAPPEDaccept )
{
ASSERT(m_eP2PeerConMode==P2PeerCon_SERVICE);
// Success
// NOTES: Honour expectation that the On_P2PeerCon_ACCEPT()
// processed before the ON_P2PeerCon_LISTEN()
// notification
releaseOVERLAPPED ( pOVERLAPPEDcon );
if ( hr == S_OK &&
m_hFileCPort )
{
// Split into listening, usually this, and accepted P2PeerCon
// NOTES: The underlying P2PeerCon objects have different styles
// according to the derived type.
//P2PeerCon *pConAccept = 0, *pConListen = 0;
//AcceptSplit ( &pConListen, &pConAccept );
// Perform ON_P2PeerOLD_ACCEPT() notification
// NOTES: Accepted P2PeerCon is spawned within the handler
//if ( pConAccept == 0 && pConListen == 0 )
PostP2Pmsg ( m_oThatP2Paddr, CN_P2PeerCon, P2P_Accept
, this, 0, m_hCPortP2PumpID );
// ON_P2PeerOLD_ACCEPT() notification
// NOTES: Spawn accepted connection
//if ( pConAccept )
//PostP2Pmsg ( pConAccept->m_oThatP2Paddr, CN_P2PeerCon, P2P_Accept
// , pConAccept, 0 );
// ON_P2PeerCon_LISTEN() notification
// NOTES: Listen for new connection, or stop listening etc
//if ( pConListen )
//{
//P2PaddrSTR strThatP2Paddr = pConListen->m_oThatP2Paddr;
//PostP2Pmsg ( pConListen->m_oThatP2Paddr.IsNull()
// ?pConListen->m_oThatP2Paddr
// :pConListen->m_oThisP2Paddr
//PostP2Pmsg ( (strThatP2Paddr&1)==0?pConListen->m_oThisP2Paddr
// :pConListen->m_oThatP2Paddr
// , CN_P2PeerCon, P2P_Listen, pConListen, 0 );
//}
}
// Failure
// NOTES: Accepted connection failure
else if ( hr &&
m_hFileCPort )
EVERR->Module (__FUNCTION__)
->AFP(dwError)->AFP(dwBytes)->AFPcon(this)
->Message(_T("Overlapped accept failed"))
->HResult( hr ) -> Throw();
// Cancellation
// NOTES: IoCancel(m_hFileCPort), CloseHandle(m_hFileCPort) or
// whatever issued. Acceptance discontinued
else
m_pOVERLAPPEDaccept = DropOVERLAPPED ( pOVERLAPPEDcon );
}
// Connect
// NOTES: Perform On_P2PeerCon_CONNECT() notification. At which
// point the intercepting handler will initiate the
// appropriate state changes.
else if ( pOVERLAPPEDcon == m_pOVERLAPPEDconnect )
{
ASSERT(m_eP2PeerConMode!=P2PeerCon_SERVICE);
// Success
// NOTES: OVERLAPPEDsend and OVERLAPPEDrecv buffer creation
// triggers IO Completion port scheduling immediately
// after the object has been routed through the
// P2PeerCon_MAP.
releaseOVERLAPPED ( pOVERLAPPEDcon );
if ( hr == S_OK &&
m_hFileCPort )
{
if ( m_pOVERLAPPEDsend == NULL )
m_pOVERLAPPEDsend = MakeOVERLAPPED ( );
if ( m_pOVERLAPPEDrecv == NULL )
m_pOVERLAPPEDrecv = MakeOVERLAPPED ( m_pP2Peerio->GetMaxRecvSize() );
// Perform On_P2PeerCon_CONNECT notification
PostP2Pmsg ( m_oThatP2Paddr, CN_P2PeerCon, P2P_Connect
, this, (P2PeerMsg *)0, m_hCPortP2PumpID );
}
// Failure
// NOTES: Connection failed to complete
// : Perform On_P2PeerCon_CLOSE() notification.
else if ( hr &&
m_hFileCPort )
{
EVERR->Module (__FUNCTION__)
->AFP(dwError)->AFP(dwBytes)->AFPcon(this)
->Message(_T("Remote connection request failed") )
->Advice (_T("Remote server not running") )
->Advice (_T("Firewall configuration") )
/*"\t: Invalid connection address [%s]\n"
"\t: Invalid connection port [%i]"
, m_oCSIpAddress
, m_nIpPort*/
->HResult( hr )->Throw();
}
// Cancellation
// NOTES: IoCancel(m_hFileCPort), CloseHandle(m_hFileCPort) or
// whatever issued.
else
m_pOVERLAPPEDconnect = DropOVERLAPPED ( pOVERLAPPEDcon );
}
// P2Psignal_MSG
// NOTES: Injects P2PeerMsg into P2PeerCon object. Primarily
// used to emulate 3rdParty and legacy interfaces from
// P2Peerio derived objects
else if ( pOVERLAPPEDcon->nSigID == P2PsigCon_MSG )
{
ASSERT(m_eP2PeerConMode!=P2PeerCon_SERVICE);
releaseOVERLAPPED ( pOVERLAPPEDcon );