Skip to content

Commit d83c02b

Browse files
author
Threepwood-7
committed
RUST-PAR-017: suppress OP_OUTOFPARTREQS on a banned peer's recycle
The listener sent the OP_OUTOFPARTREQS courtesy packet to every was-granted peer whose slot was recycled, including banned ones. The oracle sets bRequeue=false for a banned client's recycle (CheckForTimeOver, UploadQueue.cpp:2320-2321), so the Process requeue guard skips SendOutOfPartReqsAndAddToWaitingQueue (UploadQueue.cpp:881-885): the banned client's queue entry is dropped without the packet. Capture the peer's banned flag alongside the session identity and gate both listener send sites (demote-to-waiting and stale-session close) on it; the runtime queue already drops a banned session instead of re-queueing it.
1 parent f2ef099 commit d83c02b

1 file changed

Lines changed: 71 additions & 23 deletions

File tree

crates/emulebb-ed2k/src/ed2k_tcp/listener/session/upload_queue.rs

Lines changed: 71 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@ pub(in crate::ed2k_tcp) struct ListenerUploadQueue {
4444
// session key, not the ephemeral socket source port).
4545
diag_peer: Option<String>,
4646
diag_peer_hash: Option<[u8; 16]>,
47+
// Whether the peer bound to the current session was BANNED at admission
48+
// (mirrors the queue session key's `banned` flag). A banned client's slot
49+
// recycle must NOT get the OP_OUTOFPARTREQS courtesy packet (oracle
50+
// bRequeue=false, CheckForTimeOver, UploadQueue.cpp:2320-2321; requeue guard
51+
// in Process, UploadQueue.cpp:883-884).
52+
peer_banned: bool,
4753
verified_reader: Option<(Ed2kHash, Ed2kVerifiedRangeReader)>,
4854
// Per-connection ledger of requested upload blocks (fileHash, start, end,
4955
// count, first-seen) for MFC repeat_block_request parity. Bounded and pruned
@@ -64,6 +70,7 @@ impl ListenerUploadQueue {
6470
ever_granted: false,
6571
diag_peer: None,
6672
diag_peer_hash: None,
73+
peer_banned: false,
6774
verified_reader: None,
6875
block_request_ledger: Vec::new(),
6976
close_reason: None,
@@ -111,13 +118,25 @@ impl ListenerUploadQueue {
111118
None
112119
}
113120

114-
/// Capture the advertised peer identity for the `sched` diag emits.
121+
/// Capture the advertised peer identity for the `sched` diag emits and the
122+
/// banned-recycle packet suppression.
115123
fn record_diag_peer(&mut self, peer_identity: &Ed2kUploadPeerIdentity) {
116124
self.diag_peer = Some(diag_sched::peer_label(
117125
peer_identity.ip,
118126
peer_identity.tcp_port,
119127
));
120128
self.diag_peer_hash = peer_identity.user_hash;
129+
self.peer_banned = peer_identity.banned;
130+
}
131+
132+
/// Whether a slot demotion/recycle owes the peer the OP_OUTOFPARTREQS
133+
/// courtesy packet: only a peer that actually saw OP_ACCEPTUPLOADREQ, and
134+
/// never a BANNED one (oracle bRequeue=false for `IsBanned()`,
135+
/// UploadQueue.cpp:2320-2321 -> the Process requeue guard skips
136+
/// SendOutOfPartReqsAndAddToWaitingQueue, UploadQueue.cpp:883-884; the
137+
/// banned client's queue entry is dropped, not re-added).
138+
const fn should_send_out_of_part_reqs(&self) -> bool {
139+
self.granted_sent && !self.peer_banned
121140
}
122141

123142
/// Emit `upload_slot_opened` once per grant transition (peer + file known).
@@ -186,28 +205,32 @@ impl ListenerUploadQueue {
186205
}
187206
Ed2kUploadSessionStatus::Waiting { .. } => {
188207
// A slot we had granted was demoted back to the waiting queue (idle
189-
// recycle): tell the downloader to go OnQueue with OP_OUTOFPARTREQS
190-
// once, mirroring MFC SendOutOfPartReqsAndAddToWaitingQueue, then keep
191-
// the connection rather than closing and shedding the peer.
192-
// `granted_sent` is cleared so a later re-grant re-sends
193-
// OP_ACCEPTUPLOADREQ; `ever_granted` stays set for the funnel.
208+
// recycle or session-cap rotation): tell the downloader to go OnQueue
209+
// with OP_OUTOFPARTREQS once, mirroring MFC
210+
// SendOutOfPartReqsAndAddToWaitingQueue, then keep the connection
211+
// rather than closing and shedding the peer. A BANNED peer gets no
212+
// packet (oracle bRequeue=false). `granted_sent` is cleared so a later
213+
// re-grant re-sends OP_ACCEPTUPLOADREQ; `ever_granted` stays set for
214+
// the funnel.
194215
if self.granted_sent {
195-
let packet = encode_out_of_part_reqs();
196-
dump_ed2k_tcp_listener_send(
197-
peer_addr,
198-
transport.mode,
199-
"out_of_part_reqs",
200-
&packet,
201-
);
202-
transport.write_all(&packet).await?;
203-
if let (Some(peer), Some(file_hash)) =
204-
(self.diag_peer.as_deref(), self.file_hash.as_ref())
205-
{
206-
diag_sched::out_of_part_reqs(
207-
peer,
208-
self.diag_peer_hash,
209-
&file_hash.to_string(),
216+
if self.should_send_out_of_part_reqs() {
217+
let packet = encode_out_of_part_reqs();
218+
dump_ed2k_tcp_listener_send(
219+
peer_addr,
220+
transport.mode,
221+
"out_of_part_reqs",
222+
&packet,
210223
);
224+
transport.write_all(&packet).await?;
225+
if let (Some(peer), Some(file_hash)) =
226+
(self.diag_peer.as_deref(), self.file_hash.as_ref())
227+
{
228+
diag_sched::out_of_part_reqs(
229+
peer,
230+
self.diag_peer_hash,
231+
&file_hash.to_string(),
232+
);
233+
}
211234
}
212235
self.granted_sent = false;
213236
}
@@ -238,8 +261,10 @@ impl ListenerUploadQueue {
238261
// MFC CUpDownClient::SendOutOfPartReqsAndAddToWaitingQueue. Without it
239262
// the downloader is dropped silently and reconnects immediately (churn)
240263
// instead of re-queueing with the stock out-of-part-reqs cooldown. A
241-
// never-granted (Rejected) peer gets nothing, matching the master.
242-
if self.granted_sent {
264+
// never-granted (Rejected) peer gets nothing, matching the master --
265+
// and so does a BANNED peer, whose recycle is dropped without the
266+
// packet (oracle bRequeue=false, UploadQueue.cpp:2320-2321).
267+
if self.should_send_out_of_part_reqs() {
243268
let packet = encode_out_of_part_reqs();
244269
dump_ed2k_tcp_listener_send(
245270
peer_addr,
@@ -490,6 +515,7 @@ impl ListenerUploadQueue {
490515
self.close_reason = None;
491516
self.diag_peer = None;
492517
self.diag_peer_hash = None;
518+
self.peer_banned = false;
493519
self.verified_reader = None;
494520
}
495521

@@ -590,6 +616,28 @@ mod tests {
590616
use crate::ed2k_transfer::{ED2K_EMBLOCK_SIZE, Ed2kTransferRuntime};
591617
use crate::paths::unique_test_dir;
592618

619+
/// Oracle bRequeue=false (CheckForTimeOver, UploadQueue.cpp:2320-2321): a
620+
/// BANNED client's slot recycle must not get the OP_OUTOFPARTREQS courtesy
621+
/// packet, while a normal granted peer must. A never-granted peer gets
622+
/// nothing either way.
623+
#[test]
624+
fn out_of_part_reqs_is_suppressed_for_banned_peers() {
625+
let mut queue = ListenerUploadQueue::new();
626+
627+
// Never granted: no packet, banned or not.
628+
assert!(!queue.should_send_out_of_part_reqs());
629+
queue.peer_banned = true;
630+
assert!(!queue.should_send_out_of_part_reqs());
631+
632+
// Granted + banned: suppressed (oracle bRequeue=false).
633+
queue.granted_sent = true;
634+
assert!(!queue.should_send_out_of_part_reqs());
635+
636+
// Granted + not banned: the packet is owed.
637+
queue.peer_banned = false;
638+
assert!(queue.should_send_out_of_part_reqs());
639+
}
640+
593641
#[test]
594642
fn note_block_request_flags_repeat_within_window() {
595643
let mut queue = ListenerUploadQueue::new();

0 commit comments

Comments
 (0)