1use std::sync::{Arc, Mutex};
10use std::time::Duration;
11
12use anyhow::{Context, Result, anyhow, bail};
13use bytes::Bytes;
14use futures::{FutureExt, SinkExt, StreamExt};
15use serde::{Deserialize, Serialize};
16use tokio::io::{AsyncReadExt, AsyncWriteExt};
17use tokio::net::{TcpListener, TcpStream};
18use tokio::sync::{Semaphore, mpsc, watch};
19use tokio_util::codec::{Framed, LengthDelimitedCodec};
20use tokio_util::sync::CancellationToken;
21use tokio_util::task::TaskTracker;
22use uuid::Uuid;
23
24use crate::common::handoff::{
25 HandoffActionId, HandoffActionOutcome, HandoffFact, HandoffId, HandoffOrder,
26 IssuedHandoffAction,
27};
28use crate::common::protocols::EngineType;
29
30pub const BOOTSTRAP_PROTOCOL_VERSION: u16 = 1;
31pub const MAX_BOOTSTRAP_FRAME_BYTES: usize = 64 * 1024;
32const MAGIC: [u8; 4] = *b"DMHF";
33const HEADER_BYTES: usize = 8;
34const RENDEZVOUS_TIMEOUT: Duration = Duration::from_secs(30);
35
36#[derive(Clone, Debug)]
37pub struct BootstrapServerConfig {
38 pub max_pending_connections: usize,
39 pub registration_timeout: Duration,
40}
41
42impl Default for BootstrapServerConfig {
43 fn default() -> Self {
44 Self {
45 max_pending_connections: 256,
46 registration_timeout: RENDEZVOUS_TIMEOUT,
47 }
48 }
49}
50
51#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
52pub enum BootstrapParticipantRole {
53 Destination,
54}
55
56#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
57pub struct BootstrapIdentity {
58 pub handoff_id: HandoffId,
59 pub bootstrap_room: u64,
60 pub request_id: Uuid,
61}
62
63#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
64pub struct ParticipantRegistration {
65 pub role: BootstrapParticipantRole,
66 pub dp_rank: u32,
67 pub order: HandoffOrder,
68 pub engine_type: EngineType,
69}
70
71#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
72pub enum BootstrapMessage {
73 Register(ParticipantRegistration),
74 Registered,
75 Action(IssuedHandoffAction),
76 ActionAck {
77 action_id: HandoffActionId,
78 outcome: HandoffActionOutcome,
79 },
80 Fact(HandoffFact),
81 Complete,
82 Abort {
83 message: String,
84 },
85 Overloaded,
86 ProtocolError {
87 message: String,
88 },
89}
90
91#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
92struct BootstrapWireFrame {
93 identity: BootstrapIdentity,
94 message: BootstrapMessage,
95}
96
97pub struct IncomingBootstrapConnection {
98 pub identity: BootstrapIdentity,
99 pub registration: ParticipantRegistration,
100 pub connection: BootstrapConnection,
101}
102
103pub struct BootstrapConnection {
104 identity: BootstrapIdentity,
105 framed: Framed<TcpStream, LengthDelimitedCodec>,
106}
107
108impl BootstrapConnection {
109 pub fn identity(&self) -> &BootstrapIdentity {
110 &self.identity
111 }
112
113 pub async fn send(&mut self, message: BootstrapMessage) -> Result<()> {
114 let payload = serde_json::to_vec(&BootstrapWireFrame {
115 identity: self.identity.clone(),
116 message,
117 })?;
118 if payload.is_empty() || payload.len() > MAX_BOOTSTRAP_FRAME_BYTES {
119 bail!(
120 "bootstrap frame length {} is outside 1..={MAX_BOOTSTRAP_FRAME_BYTES}",
121 payload.len()
122 );
123 }
124 tokio::time::timeout(RENDEZVOUS_TIMEOUT, self.framed.send(Bytes::from(payload)))
125 .await
126 .map_err(|_| anyhow!("bootstrap send timed out"))??;
127 Ok(())
128 }
129
130 pub async fn recv(&mut self) -> Result<Option<BootstrapMessage>> {
131 let Some(frame) = self.framed.next().await.transpose()? else {
132 return Ok(None);
133 };
134 if frame.is_empty() {
135 bail!("bootstrap received an empty frame");
136 }
137 let frame: BootstrapWireFrame =
138 serde_json::from_slice(&frame).context("bootstrap frame contains malformed JSON")?;
139 if frame.identity != self.identity {
140 bail!("bootstrap frame changed session identity");
141 }
142 Ok(Some(frame.message))
143 }
144
145 pub fn peer_closed_now(&self) -> Result<bool> {
146 let mut byte = [0u8; 1];
147 match self.framed.get_ref().peek(&mut byte).now_or_never() {
148 Some(Ok(0)) => Ok(true),
149 Some(Ok(_)) | None => Ok(false),
150 Some(Err(error)) => Err(error.into()),
151 }
152 }
153}
154
155pub struct BootstrapServer {
156 port: u16,
157 incoming_rx: Mutex<Option<mpsc::Receiver<IncomingBootstrapConnection>>>,
158 closed_rx: watch::Receiver<bool>,
159 #[cfg(test)]
160 accepted_with_slot_rx: watch::Receiver<u64>,
161}
162
163impl BootstrapServer {
164 pub async fn start(
165 port: u16,
166 cancel: CancellationToken,
167 config: BootstrapServerConfig,
168 ) -> Result<Arc<Self>> {
169 if config.max_pending_connections == 0 {
170 bail!("bootstrap max_pending_connections must be at least one");
171 }
172 let listener = TcpListener::bind(format!("0.0.0.0:{port}")).await?;
173 let actual_port = listener.local_addr()?.port();
174 let (incoming_tx, incoming_rx) = mpsc::channel(config.max_pending_connections);
175 let permits = Arc::new(Semaphore::new(config.max_pending_connections));
176 let overload_permits = Arc::new(Semaphore::new(1));
177 let (closed_tx, closed_rx) = watch::channel(false);
178 #[cfg(test)]
179 let (accepted_with_slot_tx, accepted_with_slot_rx) = watch::channel(0_u64);
180 let server = Arc::new(Self {
181 port: actual_port,
182 incoming_rx: Mutex::new(Some(incoming_rx)),
183 closed_rx,
184 #[cfg(test)]
185 accepted_with_slot_rx,
186 });
187
188 tokio::spawn(async move {
189 let connections = TaskTracker::new();
190 #[cfg(test)]
191 let mut accepted_with_slot = 0_u64;
192 loop {
193 let accepted = tokio::select! {
194 biased;
195 _ = cancel.cancelled() => break,
196 accepted = listener.accept() => accepted,
197 };
198 let Ok((stream, _)) = accepted else {
199 tokio::select! {
200 _ = cancel.cancelled() => break,
201 _ = tokio::time::sleep(Duration::from_millis(10)) => {}
202 }
203 continue;
204 };
205 let Ok(permit) = permits.clone().try_acquire_owned() else {
206 let Ok(overload_permit) = overload_permits.clone().try_acquire_owned() else {
207 drop(stream);
208 continue;
209 };
210 let registration_timeout = config.registration_timeout;
211 let connection_cancel = cancel.clone();
212 connections.spawn(async move {
213 let _permit = overload_permit;
214 let incoming = tokio::select! {
215 biased;
216 _ = connection_cancel.cancelled() => return,
217 incoming = tokio::time::timeout(
218 registration_timeout,
219 accept_connection(stream),
220 ) => incoming,
221 };
222 let Ok(Ok(mut incoming)) = incoming else {
223 return;
224 };
225 let _ = incoming.connection.send(BootstrapMessage::Overloaded).await;
226 });
227 continue;
228 };
229 #[cfg(test)]
230 {
231 accepted_with_slot = accepted_with_slot
232 .checked_add(1)
233 .expect("bootstrap accepted-connection test counter overflow");
234 let _ = accepted_with_slot_tx.send(accepted_with_slot);
235 }
236 let incoming_tx = incoming_tx.clone();
237 let registration_timeout = config.registration_timeout;
238 let connection_cancel = cancel.clone();
239 connections.spawn(async move {
240 let _permit = permit;
241 let incoming = tokio::select! {
242 biased;
243 _ = connection_cancel.cancelled() => return,
244 incoming = tokio::time::timeout(
245 registration_timeout,
246 accept_connection(stream),
247 ) => incoming,
248 };
249 let Ok(incoming) = incoming else {
250 return;
251 };
252 let Ok(mut incoming) = incoming else {
253 return;
254 };
255 if let Err(error) = incoming_tx.try_send(incoming) {
256 incoming = error.into_inner();
257 let _ = incoming.connection.send(BootstrapMessage::Overloaded).await;
258 }
259 });
260 }
261 connections.close();
262 connections.wait().await;
263 let _ = closed_tx.send(true);
264 });
265
266 Ok(server)
267 }
268
269 pub fn take_incoming_receiver(&self) -> Option<mpsc::Receiver<IncomingBootstrapConnection>> {
270 self.incoming_rx
271 .lock()
272 .expect("bootstrap incoming receiver mutex poisoned")
273 .take()
274 }
275
276 pub fn port(&self) -> u16 {
277 self.port
278 }
279
280 pub async fn wait_closed(&self) {
281 let mut closed_rx = self.closed_rx.clone();
282 if *closed_rx.borrow() {
283 return;
284 }
285 let _ = closed_rx.wait_for(|closed| *closed).await;
286 }
287
288 #[cfg(test)]
289 async fn wait_for_accepted_with_slot(&self, expected: u64) {
290 let mut accepted = self.accepted_with_slot_rx.clone();
291 let _ = accepted.wait_for(|count| *count >= expected).await;
292 }
293}
294
295pub async fn connect_to_prefill(
296 host: &str,
297 port: u16,
298 identity: BootstrapIdentity,
299 registration: ParticipantRegistration,
300) -> Result<BootstrapConnection> {
301 let addr = bootstrap_addr(host, port);
302 let mut stream = tokio::time::timeout(RENDEZVOUS_TIMEOUT, TcpStream::connect(&addr))
303 .await
304 .map_err(|_| anyhow!("bootstrap connect timeout to {addr}"))??;
305 tokio::time::timeout(RENDEZVOUS_TIMEOUT, write_header(&mut stream))
306 .await
307 .map_err(|_| anyhow!("bootstrap header send timed out"))??;
308 let mut connection = BootstrapConnection {
309 identity,
310 framed: framed(stream),
311 };
312 connection
313 .send(BootstrapMessage::Register(registration))
314 .await?;
315 Ok(connection)
316}
317
318fn bootstrap_addr(host: &str, port: u16) -> String {
319 let host = host
320 .strip_prefix('[')
321 .and_then(|host| host.strip_suffix(']'))
322 .unwrap_or(host);
323 if host.contains(':') {
324 format!("[{host}]:{port}")
325 } else {
326 format!("{host}:{port}")
327 }
328}
329
330async fn accept_connection(mut stream: TcpStream) -> Result<IncomingBootstrapConnection> {
331 read_header(&mut stream).await?;
332 let mut connection = BootstrapConnection {
333 identity: BootstrapIdentity {
334 handoff_id: HandoffId::default(),
335 bootstrap_room: 0,
336 request_id: Uuid::nil(),
337 },
338 framed: framed(stream),
339 };
340 let Some(frame) = connection.framed.next().await.transpose()? else {
341 bail!("bootstrap connection closed before registration");
342 };
343 let frame: BootstrapWireFrame =
344 serde_json::from_slice(&frame).context("bootstrap registration contains malformed JSON")?;
345 let BootstrapMessage::Register(registration) = frame.message else {
346 bail!("bootstrap first frame must register a participant");
347 };
348 if registration.role != BootstrapParticipantRole::Destination {
349 bail!("bootstrap server accepts only destination participants");
350 }
351 connection.identity = frame.identity.clone();
352 Ok(IncomingBootstrapConnection {
353 identity: frame.identity,
354 registration,
355 connection,
356 })
357}
358
359fn framed(stream: TcpStream) -> Framed<TcpStream, LengthDelimitedCodec> {
360 LengthDelimitedCodec::builder()
361 .little_endian()
362 .length_field_type::<u32>()
363 .max_frame_length(MAX_BOOTSTRAP_FRAME_BYTES)
364 .new_framed(stream)
365}
366
367async fn write_header(stream: &mut TcpStream) -> Result<()> {
368 let mut header = [0u8; HEADER_BYTES];
369 header[..4].copy_from_slice(&MAGIC);
370 header[4..6].copy_from_slice(&BOOTSTRAP_PROTOCOL_VERSION.to_le_bytes());
371 stream.write_all(&header).await?;
372 Ok(())
373}
374
375async fn read_header(stream: &mut TcpStream) -> Result<()> {
376 let mut header = [0u8; HEADER_BYTES];
377 stream.read_exact(&mut header).await?;
378 if header[..4] != MAGIC {
379 bail!("bootstrap protocol magic mismatch");
380 }
381 let version = u16::from_le_bytes([header[4], header[5]]);
382 if version != BOOTSTRAP_PROTOCOL_VERSION {
383 bail!("unsupported bootstrap protocol version {version}");
384 }
385 let flags = u16::from_le_bytes([header[6], header[7]]);
386 if flags != 0 {
387 bail!("bootstrap protocol flags must be zero");
388 }
389 Ok(())
390}
391
392#[cfg(test)]
393mod tests {
394 use super::*;
395
396 fn identity(value: u128) -> BootstrapIdentity {
397 BootstrapIdentity {
398 handoff_id: HandoffId::from(Uuid::from_u128(value)),
399 bootstrap_room: value as u64,
400 request_id: Uuid::from_u128(value + 100),
401 }
402 }
403
404 fn registration() -> ParticipantRegistration {
405 ParticipantRegistration {
406 role: BootstrapParticipantRole::Destination,
407 dp_rank: 0,
408 order: HandoffOrder::SourceFirst,
409 engine_type: EngineType::Vllm,
410 }
411 }
412
413 #[test]
414 fn bootstrap_address_preserves_ipv6_literals() {
415 assert_eq!(bootstrap_addr("[::1]", 1234), "[::1]:1234");
416 assert_eq!(bootstrap_addr("::1", 1234), "[::1]:1234");
417 assert_eq!(bootstrap_addr("127.0.0.1", 1234), "127.0.0.1:1234");
418 }
419
420 #[tokio::test]
421 async fn send_rejects_oversized_frame_before_codec_allocation() {
422 let cancel = CancellationToken::new();
423 let server = BootstrapServer::start(0, cancel.clone(), BootstrapServerConfig::default())
424 .await
425 .unwrap();
426 let mut client =
427 connect_to_prefill("127.0.0.1", server.port(), identity(2), registration())
428 .await
429 .unwrap();
430 let error = client
431 .send(BootstrapMessage::ProtocolError {
432 message: "x".repeat(MAX_BOOTSTRAP_FRAME_BYTES),
433 })
434 .await
435 .unwrap_err();
436 assert!(error.to_string().contains("outside"));
437 cancel.cancel();
438 }
439
440 #[tokio::test]
441 async fn bad_magic_never_enters_the_incoming_queue() {
442 let cancel = CancellationToken::new();
443 let server = BootstrapServer::start(0, cancel.clone(), BootstrapServerConfig::default())
444 .await
445 .unwrap();
446 let mut incoming_rx = server.take_incoming_receiver().unwrap();
447 let mut stream = TcpStream::connect(("127.0.0.1", server.port()))
448 .await
449 .unwrap();
450 stream.write_all(b"NOPE\x01\x00\x00\x00").await.unwrap();
451 let mut byte = [0_u8; 1];
452 let _ = stream.read(&mut byte).await;
453 assert!(matches!(
454 incoming_rx.try_recv(),
455 Err(mpsc::error::TryRecvError::Empty)
456 ));
457 cancel.cancel();
458 }
459
460 #[tokio::test]
461 async fn invalid_headers_and_frames_never_enter_session_ownership() {
462 let cancel = CancellationToken::new();
463 let server = BootstrapServer::start(0, cancel.clone(), BootstrapServerConfig::default())
464 .await
465 .unwrap();
466 let mut incoming_rx = server.take_incoming_receiver().unwrap();
467
468 for header in [
469 [MAGIC[0], MAGIC[1], MAGIC[2], MAGIC[3], 2, 0, 0, 0],
470 [MAGIC[0], MAGIC[1], MAGIC[2], MAGIC[3], 1, 0, 1, 0],
471 ] {
472 let mut stream = TcpStream::connect(("127.0.0.1", server.port()))
473 .await
474 .unwrap();
475 stream.write_all(&header).await.unwrap();
476 let mut byte = [0_u8; 1];
477 let _ = stream.read(&mut byte).await;
478 }
479
480 let mut malformed = TcpStream::connect(("127.0.0.1", server.port()))
481 .await
482 .unwrap();
483 write_header(&mut malformed).await.unwrap();
484 malformed.write_all(&1u32.to_le_bytes()).await.unwrap();
485 malformed.write_all(b"{").await.unwrap();
486 let mut byte = [0_u8; 1];
487 let _ = malformed.read(&mut byte).await;
488
489 let mut oversized = TcpStream::connect(("127.0.0.1", server.port()))
490 .await
491 .unwrap();
492 write_header(&mut oversized).await.unwrap();
493 oversized
494 .write_all(&((MAX_BOOTSTRAP_FRAME_BYTES + 1) as u32).to_le_bytes())
495 .await
496 .unwrap();
497 let _ = oversized.read(&mut byte).await;
498
499 assert!(matches!(
500 incoming_rx.try_recv(),
501 Err(mpsc::error::TryRecvError::Empty)
502 ));
503 cancel.cancel();
504 server.wait_closed().await;
505 }
506
507 #[tokio::test]
508 async fn changed_identity_is_rejected_after_registration() {
509 let cancel = CancellationToken::new();
510 let server = BootstrapServer::start(0, cancel.clone(), BootstrapServerConfig::default())
511 .await
512 .unwrap();
513 let mut incoming_rx = server.take_incoming_receiver().unwrap();
514 let mut client =
515 connect_to_prefill("127.0.0.1", server.port(), identity(3), registration())
516 .await
517 .unwrap();
518 let mut incoming = incoming_rx.recv().await.unwrap();
519
520 client.identity = identity(4);
521 client.send(BootstrapMessage::Complete).await.unwrap();
522 let error = incoming.connection.recv().await.unwrap_err();
523 assert!(error.to_string().contains("changed session identity"));
524 cancel.cancel();
525 }
526
527 #[tokio::test]
528 async fn first_frame_must_be_registration() {
529 let cancel = CancellationToken::new();
530 let server = BootstrapServer::start(0, cancel.clone(), BootstrapServerConfig::default())
531 .await
532 .unwrap();
533 let mut incoming_rx = server.take_incoming_receiver().unwrap();
534 let mut stream = TcpStream::connect(("127.0.0.1", server.port()))
535 .await
536 .unwrap();
537 write_header(&mut stream).await.unwrap();
538 let frame = serde_json::to_vec(&BootstrapWireFrame {
539 identity: identity(5),
540 message: BootstrapMessage::Registered,
541 })
542 .unwrap();
543 let mut framed_stream = framed(stream);
544 framed_stream.send(Bytes::from(frame)).await.unwrap();
545 let _ = framed_stream.next().await;
546
547 assert!(matches!(
548 incoming_rx.try_recv(),
549 Err(mpsc::error::TryRecvError::Empty)
550 ));
551 cancel.cancel();
552 }
553
554 #[tokio::test]
555 async fn full_incoming_queue_returns_overloaded() {
556 let cancel = CancellationToken::new();
557 let server = BootstrapServer::start(
558 0,
559 cancel.clone(),
560 BootstrapServerConfig {
561 max_pending_connections: 1,
562 ..BootstrapServerConfig::default()
563 },
564 )
565 .await
566 .unwrap();
567 let _incoming_rx = server.take_incoming_receiver().unwrap();
568 let _first = connect_to_prefill("127.0.0.1", server.port(), identity(6), registration())
569 .await
570 .unwrap();
571 server.wait_for_accepted_with_slot(1).await;
572 let mut second =
573 connect_to_prefill("127.0.0.1", server.port(), identity(7), registration())
574 .await
575 .unwrap();
576
577 assert_eq!(
578 tokio::time::timeout(Duration::from_secs(1), second.recv())
579 .await
580 .unwrap()
581 .unwrap(),
582 Some(BootstrapMessage::Overloaded)
583 );
584 cancel.cancel();
585 }
586
587 #[tokio::test]
588 async fn half_open_registration_saturation_returns_overloaded() {
589 let cancel = CancellationToken::new();
590 let server = BootstrapServer::start(
591 0,
592 cancel.clone(),
593 BootstrapServerConfig {
594 max_pending_connections: 1,
595 ..BootstrapServerConfig::default()
596 },
597 )
598 .await
599 .unwrap();
600 let _incoming_rx = server.take_incoming_receiver().unwrap();
601 let _half_open = TcpStream::connect(("127.0.0.1", server.port()))
602 .await
603 .unwrap();
604 server.wait_for_accepted_with_slot(1).await;
605
606 let mut rejected =
607 connect_to_prefill("127.0.0.1", server.port(), identity(8), registration())
608 .await
609 .unwrap();
610 assert_eq!(
611 tokio::time::timeout(Duration::from_secs(1), rejected.recv())
612 .await
613 .unwrap()
614 .unwrap(),
615 Some(BootstrapMessage::Overloaded)
616 );
617 cancel.cancel();
618 server.wait_closed().await;
619 }
620
621 #[tokio::test]
622 async fn shutdown_closes_half_open_registration() {
623 let cancel = CancellationToken::new();
624 let server = BootstrapServer::start(0, cancel.clone(), BootstrapServerConfig::default())
625 .await
626 .unwrap();
627 let mut stream = TcpStream::connect(("127.0.0.1", server.port()))
628 .await
629 .unwrap();
630 server.wait_for_accepted_with_slot(1).await;
631 cancel.cancel();
632
633 let mut byte = [0u8; 1];
634 assert_eq!(
635 tokio::time::timeout(Duration::from_secs(1), stream.read(&mut byte))
636 .await
637 .unwrap()
638 .unwrap(),
639 0
640 );
641 server.wait_closed().await;
642 }
643}