1pub mod actions;
3
4pub use actions::DotClientProtocol;
5
6use anyhow::{Context, Result};
7use hickory_proto::op::{Message as DnsMessage, MessageType, OpCode, Query};
8use hickory_proto::rr::{Name, RecordType};
9use std::net::SocketAddr;
10use std::str::FromStr;
11use std::sync::Arc;
12use tokio::io::{AsyncReadExt, AsyncWriteExt, ReadHalf, WriteHalf};
13use tokio::net::TcpStream;
14use tokio::sync::{mpsc, Mutex};
15use tokio_rustls::client::TlsStream;
16use tokio_rustls::rustls::pki_types::ServerName;
17use tokio_rustls::rustls::{ClientConfig, RootCertStore};
18use tokio_rustls::TlsConnector;
19use tracing::{debug, error, info, trace};
20
21use crate::client::dot::actions::{DOT_CLIENT_CONNECTED_EVENT, DOT_CLIENT_RESPONSE_RECEIVED_EVENT};
22use crate::client::llm_budget::call_llm_for_client;
23use crate::llm::actions::client_trait::{Client as ClientTrait, ClientActionResult};
24use crate::llm::ollama_client::OllamaClient;
25use crate::llm::ClientLlmResult;
26use crate::protocol::Event;
27use crate::state::app_state::AppState;
28use crate::state::{ClientId, ClientStatus};
29
30#[derive(Debug, Clone, PartialEq)]
32enum ConnectionState {
33 Idle,
34 Processing,
35 Accumulating,
36}
37
38struct ClientData {
40 state: ConnectionState,
41 queued_responses: Vec<Vec<u8>>,
42 memory: String,
43}
44
45pub struct DotClient;
47
48impl DotClient {
49 pub async fn connect_with_llm_actions(
51 remote_addr: String,
52 llm_client: OllamaClient,
53 app_state: Arc<AppState>,
54 status_tx: mpsc::UnboundedSender<String>,
55 client_id: ClientId,
56 startup_params: Option<crate::protocol::StartupParams>,
57 ) -> Result<SocketAddr> {
58 info!("DoT client {} connecting to {}", client_id, remote_addr);
59 let _ = status_tx.send(format!(
60 "[CLIENT] DoT client {} connecting to {}",
61 client_id, remote_addr
62 ));
63
64 let verify_tls = startup_params
68 .as_ref()
69 .map(|p| p.get_optional_bool("verify_tls"))
70 .transpose()?
71 .flatten()
72 .unwrap_or(true);
73 let server_name_override = startup_params
74 .as_ref()
75 .map(|p| p.get_optional_string("server_name"))
76 .transpose()?
77 .flatten();
78
79 let remote_socket_addr: SocketAddr = remote_addr
81 .parse()
82 .context("Invalid remote address format")?;
83
84 let server_name = server_name_override
86 .as_deref()
87 .unwrap_or_else(|| remote_addr.split(':').next().unwrap_or("dns.server"));
88
89 let _ = tokio_rustls::rustls::crypto::ring::default_provider().install_default();
94
95 let root_store = RootCertStore {
97 roots: webpki_roots::TLS_SERVER_ROOTS.to_vec(),
98 };
99
100 let config = if verify_tls {
101 ClientConfig::builder()
102 .with_root_certificates(root_store)
103 .with_no_client_auth()
104 } else {
105 debug!("DoT client {} accepting invalid certificates", client_id);
106 ClientConfig::builder()
107 .dangerous()
108 .with_custom_certificate_verifier(Arc::new(NoVerification))
109 .with_no_client_auth()
110 };
111
112 let connector = TlsConnector::from(Arc::new(config));
113
114 let tcp_stream = TcpStream::connect(&remote_socket_addr)
116 .await
117 .context("Failed to connect to DoT server")?;
118
119 let local_addr = tcp_stream.local_addr()?;
120
121 let server_name = match ServerName::try_from(server_name.to_string()) {
123 Ok(name) => name,
124 Err(_) => {
125 debug!("Failed to parse server name, using IP");
126 ServerName::try_from(remote_socket_addr.ip().to_string())
127 .map_err(|e| anyhow::anyhow!("Invalid server name: {}", e))?
128 }
129 };
130
131 let tls_stream = connector
132 .connect(server_name, tcp_stream)
133 .await
134 .context("TLS handshake failed")?;
135
136 info!("DoT client {} connected to {}", client_id, remote_addr);
137 let _ = status_tx.send(format!("[CLIENT] DoT client {} connected", client_id));
138
139 app_state
141 .update_client_status(client_id, ClientStatus::Connected)
142 .await;
143 let _ = status_tx.send("__UPDATE_UI__".to_string());
144
145 let (read_half, write_half) = tokio::io::split(tls_stream);
147 let write_half_arc = Arc::new(Mutex::new(write_half));
148
149 let client_data = Arc::new(Mutex::new(ClientData {
151 state: ConnectionState::Idle,
152 queued_responses: Vec::new(),
153 memory: String::new(),
154 }));
155
156 let command_rx =
160 crate::client::command_support::register_command_channel(&app_state, client_id).await;
161
162 let cmd_state = app_state.clone();
166 let cmd_tx = status_tx.clone();
167 let cmd_write = write_half_arc.clone();
168 let cmd_task = tokio::spawn(async move {
169 Self::command_loop(command_rx, cmd_write, client_id, cmd_state, cmd_tx).await;
170 });
171 app_state.register_client_task(client_id, cmd_task).await;
172
173 let write_for_read = write_half_arc.clone();
175 let client_data_for_read = client_data.clone();
176 let read_app_state = app_state.clone();
177 let read_llm_client = llm_client.clone();
178 let read_status_tx = status_tx.clone();
179
180 let task_registrar = app_state.clone();
183 let task_handle = tokio::spawn(async move {
184 if let Err(e) = Self::read_loop(
185 read_half,
186 write_for_read,
187 client_id,
188 read_app_state.clone(),
189 read_llm_client,
190 read_status_tx,
191 client_data_for_read,
192 )
193 .await
194 {
195 error!("DoT client {} read loop error: {}", client_id, e);
196 }
197 read_app_state.remove_client_handle(client_id).await;
201 });
202 task_registrar
203 .register_client_task(client_id, task_handle)
204 .await;
205
206 if let Some(instruction) = app_state.get_instruction_for_client(client_id).await {
208 let protocol = Arc::new(DotClientProtocol::new());
209 let event = Event::new(
210 &DOT_CLIENT_CONNECTED_EVENT,
211 serde_json::json!({
212 "remote_addr": remote_addr,
213 }),
214 );
215
216 match call_llm_for_client(
217 &llm_client,
218 &app_state,
219 client_id.to_string(),
220 &instruction,
221 &client_data.lock().await.memory,
222 Some(&event),
223 protocol.as_ref(),
224 &status_tx,
225 )
226 .await
227 {
228 Ok(ClientLlmResult {
229 actions,
230 memory_updates,
231 }) => {
232 if let Some(mem) = memory_updates {
234 client_data.lock().await.memory = mem;
235 }
236
237 for action_json in actions {
239 if let Err(e) = Self::execute_client_action(
240 client_id,
241 action_json,
242 &write_half_arc,
243 &status_tx,
244 &app_state,
245 )
246 .await
247 {
248 error!("DoT client {} action execution failed: {}", client_id, e);
249 }
250 }
251 }
252 Err(e) => {
253 error!("LLM error for DoT client {}: {}", client_id, e);
254 }
255 }
256 }
257
258 Ok(local_addr)
259 }
260
261 async fn read_loop(
263 mut read_half: ReadHalf<TlsStream<TcpStream>>,
264 write_half: Arc<Mutex<WriteHalf<TlsStream<TcpStream>>>>,
265 client_id: ClientId,
266 app_state: Arc<AppState>,
267 llm_client: OllamaClient,
268 status_tx: mpsc::UnboundedSender<String>,
269 client_data: Arc<Mutex<ClientData>>,
270 ) -> Result<()> {
271 loop {
272 if app_state.get_client(client_id).await.is_none() {
274 info!("DoT client {} stopped", client_id);
275 break;
276 }
277
278 let mut len_buf = [0u8; 2];
280 match read_half.read_exact(&mut len_buf).await {
281 Ok(_) => {}
282 Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => {
283 debug!("DoT client {} connection closed by server", client_id);
284 let _ =
285 status_tx.send(format!("[CLIENT] DoT client {} disconnected", client_id));
286 app_state
287 .update_client_status(client_id, ClientStatus::Disconnected)
288 .await;
289 break;
290 }
291 Err(e) => {
292 error!(
293 "DoT client {} failed to read length prefix: {}",
294 client_id, e
295 );
296 break;
297 }
298 }
299
300 let dns_len = u16::from_be_bytes(len_buf) as usize;
301
302 if dns_len == 0 || dns_len > 65535 {
303 error!(
304 "DoT client {} invalid DNS message length: {}",
305 client_id, dns_len
306 );
307 break;
308 }
309
310 let mut dns_buf = vec![0u8; dns_len];
312 if let Err(e) = read_half.read_exact(&mut dns_buf).await {
313 error!("DoT client {} failed to read DNS message: {}", client_id, e);
314 break;
315 }
316
317 debug!("DoT client {} received {} bytes", client_id, dns_len);
318 trace!("DoT response hex: {}", hex::encode(&dns_buf));
319
320 let dns_message = match DnsMessage::from_vec(&dns_buf) {
322 Ok(msg) => msg,
323 Err(e) => {
324 error!(
325 "DoT client {} failed to parse DNS message: {}",
326 client_id, e
327 );
328 continue;
329 }
330 };
331
332 let query_id = dns_message.id();
334 let response_code = format!("{:?}", dns_message.response_code());
335
336 let answers: Vec<serde_json::Value> = dns_message
337 .answers()
338 .iter()
339 .map(|record| {
340 let data_str = match record.data() {
341 Some(data) => format!("{}", data),
342 None => "NULL".to_string(),
343 };
344 serde_json::json!({
345 "name": record.name().to_utf8(),
346 "type": format!("{:?}", record.record_type()),
347 "ttl": record.ttl(),
348 "data": data_str,
349 })
350 })
351 .collect();
352
353 let authorities: Vec<serde_json::Value> = dns_message
354 .name_servers()
355 .iter()
356 .map(|record| {
357 let data_str = match record.data() {
358 Some(data) => format!("{}", data),
359 None => "NULL".to_string(),
360 };
361 serde_json::json!({
362 "name": record.name().to_utf8(),
363 "type": format!("{:?}", record.record_type()),
364 "ttl": record.ttl(),
365 "data": data_str,
366 })
367 })
368 .collect();
369
370 let additionals: Vec<serde_json::Value> = dns_message
371 .additionals()
372 .iter()
373 .map(|record| {
374 let data_str = match record.data() {
375 Some(data) => format!("{}", data),
376 None => "NULL".to_string(),
377 };
378 serde_json::json!({
379 "name": record.name().to_utf8(),
380 "type": format!("{:?}", record.record_type()),
381 "ttl": record.ttl(),
382 "data": data_str,
383 })
384 })
385 .collect();
386
387 info!(
388 "DoT client {} received response: ID={}, Code={}, Answers={}",
389 client_id,
390 query_id,
391 response_code,
392 answers.len()
393 );
394
395 let mut client_data_lock = client_data.lock().await;
397
398 match client_data_lock.state {
399 ConnectionState::Processing => {
400 debug!("DoT client {} queuing response (LLM processing)", client_id);
402 client_data_lock.state = ConnectionState::Accumulating;
403 client_data_lock.queued_responses.push(dns_buf);
404 continue;
405 }
406 ConnectionState::Accumulating => {
407 debug!("DoT client {} already accumulating", client_id);
409 client_data_lock.queued_responses.push(dns_buf);
410 continue;
411 }
412 ConnectionState::Idle => {
413 client_data_lock.state = ConnectionState::Processing;
415 drop(client_data_lock);
416 }
417 }
418
419 if let Some(instruction) = app_state.get_instruction_for_client(client_id).await {
421 let protocol = Arc::new(DotClientProtocol::new());
422 let event = Event::new(
423 &DOT_CLIENT_RESPONSE_RECEIVED_EVENT,
424 serde_json::json!({
425 "query_id": query_id,
426 "response_code": response_code,
427 "answers": answers,
428 "authorities": authorities,
429 "additionals": additionals,
430 }),
431 );
432
433 match call_llm_for_client(
434 &llm_client,
435 &app_state,
436 client_id.to_string(),
437 &instruction,
438 &client_data.lock().await.memory,
439 Some(&event),
440 protocol.as_ref(),
441 &status_tx,
442 )
443 .await
444 {
445 Ok(ClientLlmResult {
446 actions,
447 memory_updates,
448 }) => {
449 if let Some(mem) = memory_updates {
451 client_data.lock().await.memory = mem;
452 }
453
454 for action_json in actions {
456 if let Err(e) = Self::execute_client_action(
457 client_id,
458 action_json,
459 &write_half,
460 &status_tx,
461 &app_state,
462 )
463 .await
464 {
465 error!("DoT client {} action execution failed: {}", client_id, e);
466 }
467 }
468 }
469 Err(e) => {
470 error!("LLM error for DoT client {}: {}", client_id, e);
471 }
472 }
473 }
474
475 let mut client_data_lock = client_data.lock().await;
477 if !client_data_lock.queued_responses.is_empty() {
478 client_data_lock.queued_responses.clear();
479 }
480 client_data_lock.state = ConnectionState::Idle;
481 }
482
483 Ok(())
484 }
485
486 async fn command_loop(
495 mut command_rx: mpsc::Receiver<crate::state::client_handles::ClientCommand>,
496 write_half: Arc<Mutex<WriteHalf<TlsStream<TcpStream>>>>,
497 client_id: ClientId,
498 app_state: Arc<AppState>,
499 status_tx: mpsc::UnboundedSender<String>,
500 ) {
501 use crate::llm::actions::protocol_trait::Protocol;
502 use crate::state::client_handles::ClientSendOutcome;
503 use crate::state::AccessLogOwner;
504
505 let protocol = DotClientProtocol::new();
506 while let Some(command) = command_rx.recv().await {
507 let action = command.action.clone();
508 let outcome = match protocol.execute_action(action.clone()) {
509 Err(e) => Ok(ClientSendOutcome::Rejected {
510 error: e.to_string(),
511 }),
512 Ok(result) => Self::apply_action(result, &write_half, client_id, &status_tx)
513 .await
514 .map(|applied| match applied {
515 Applied::Disconnect => ClientSendOutcome::Disconnected,
516 Applied::Sent(0) => ClientSendOutcome::Executed {
517 detail: "executed (nothing to write)".to_string(),
518 },
519 Applied::Sent(bytes_sent) => ClientSendOutcome::Sent { bytes_sent },
520 }),
521 };
522
523 let outcome_json = match &outcome {
524 Ok(outcome) => serde_json::to_value(outcome).unwrap_or(serde_json::Value::Null),
525 Err(e) => serde_json::json!({"error": e.to_string()}),
526 };
527 app_state
528 .record_access_log(
529 AccessLogOwner::Client(client_id.as_u32()),
530 protocol.protocol_name(),
531 None,
532 "injected_action",
533 action,
534 vec![outcome_json],
535 )
536 .await;
537
538 let disconnect = matches!(outcome, Ok(ClientSendOutcome::Disconnected));
539 if let Err(e) = &outcome {
540 error!("DoT client {} injected action failed: {}", client_id, e);
541 let _ = status_tx.send(format!(
542 "[WARN] Client {} injected action failed: {}",
543 client_id, e
544 ));
545 }
546 let _ = status_tx.send("__UPDATE_UI__".to_string());
547 crate::client::command_support::reply(command, outcome);
548
549 if disconnect {
550 let _ = write_half.lock().await.shutdown().await;
553 break;
554 }
555 }
556 }
557
558 async fn execute_client_action(
560 client_id: ClientId,
561 action_json: serde_json::Value,
562 write_half: &Arc<Mutex<WriteHalf<TlsStream<TcpStream>>>>,
563 status_tx: &mpsc::UnboundedSender<String>,
564 app_state: &Arc<AppState>,
565 ) -> Result<()> {
566 let protocol = DotClientProtocol::new();
567 let action_result = protocol.execute_action(action_json)?;
568
569 if let Applied::Disconnect =
570 Self::apply_action(action_result, write_half, client_id, status_tx).await?
571 {
572 info!("DoT client {} disconnecting", client_id);
573 app_state.remove_client(client_id).await;
575 let _ = status_tx.send(format!("[CLIENT] DoT client {} disconnected", client_id));
576 let _ = status_tx.send("__UPDATE_UI__".to_string());
577 }
578
579 Ok(())
580 }
581
582 async fn apply_action(
584 action_result: ClientActionResult,
585 write_half: &Arc<Mutex<WriteHalf<TlsStream<TcpStream>>>>,
586 client_id: ClientId,
587 status_tx: &mpsc::UnboundedSender<String>,
588 ) -> Result<Applied> {
589 match action_result {
590 ClientActionResult::Custom { name, data } if name == "dns_query" => {
591 let bytes_sent = Self::send_dns_query(
592 client_id,
593 data.get("domain")
594 .and_then(|v| v.as_str())
595 .context("Missing domain")?
596 .to_string(),
597 data.get("query_type")
598 .and_then(|v| v.as_str())
599 .context("Missing query_type")?
600 .to_string(),
601 data.get("recursive")
602 .and_then(|v| v.as_bool())
603 .unwrap_or(true),
604 write_half,
605 status_tx,
606 )
607 .await?;
608 Ok(Applied::Sent(bytes_sent))
609 }
610 ClientActionResult::Disconnect => Ok(Applied::Disconnect),
611 ClientActionResult::WaitForMore => {
612 debug!("DoT client {} waiting for more data", client_id);
613 Ok(Applied::Sent(0))
614 }
615 _ => Ok(Applied::Sent(0)),
617 }
618 }
619
620 async fn send_dns_query(
622 client_id: ClientId,
623 domain: String,
624 query_type: String,
625 recursive: bool,
626 write_half: &Arc<Mutex<WriteHalf<TlsStream<TcpStream>>>>,
627 status_tx: &mpsc::UnboundedSender<String>,
628 ) -> Result<usize> {
629 info!(
630 "DoT client {} querying {} {}",
631 client_id, domain, query_type
632 );
633 let _ = status_tx.send(format!("[CLIENT] DoT query: {} {}", domain, query_type));
634
635 let record_type = RecordType::from_str(&query_type)
637 .context(format!("Invalid query type: {}", query_type))?;
638
639 let name = Name::from_str(&domain).context(format!("Invalid domain name: {}", domain))?;
641
642 let mut message = DnsMessage::new();
644 message.set_id(rand::random());
645 message.set_message_type(MessageType::Query);
646 message.set_op_code(OpCode::Query);
647 message.set_recursion_desired(recursive);
648
649 let query = Query::query(name, record_type);
650 message.add_query(query);
651
652 let dns_bytes = message.to_vec().context("Failed to encode DNS message")?;
654
655 trace!("DoT query hex: {}", hex::encode(&dns_bytes));
656
657 let len = u16::try_from(dns_bytes.len()).context("DoT request exceeds u16 frame length")?;
659 let mut prefixed_message = len.to_be_bytes().to_vec();
660 prefixed_message.extend_from_slice(&dns_bytes);
661
662 {
664 let mut writer = write_half.lock().await;
665 writer
666 .write_all(&prefixed_message)
667 .await
668 .context("Failed to send DNS query over TLS")?;
669 writer
670 .flush()
671 .await
672 .context("Failed to flush DNS query over TLS")?;
673 }
674
675 debug!(
676 "DoT client {} sent DNS query ({} bytes)",
677 client_id,
678 prefixed_message.len()
679 );
680
681 Ok(prefixed_message.len())
682 }
683}
684
685enum Applied {
687 Sent(usize),
689 Disconnect,
691}
692
693#[derive(Debug)]
696struct NoVerification;
697
698impl tokio_rustls::rustls::client::danger::ServerCertVerifier for NoVerification {
699 fn verify_server_cert(
700 &self,
701 _end_entity: &tokio_rustls::rustls::pki_types::CertificateDer<'_>,
702 _intermediates: &[tokio_rustls::rustls::pki_types::CertificateDer<'_>],
703 _server_name: &ServerName<'_>,
704 _ocsp_response: &[u8],
705 _now: tokio_rustls::rustls::pki_types::UnixTime,
706 ) -> Result<tokio_rustls::rustls::client::danger::ServerCertVerified, tokio_rustls::rustls::Error>
707 {
708 Ok(tokio_rustls::rustls::client::danger::ServerCertVerified::assertion())
709 }
710
711 fn verify_tls12_signature(
712 &self,
713 _message: &[u8],
714 _cert: &tokio_rustls::rustls::pki_types::CertificateDer<'_>,
715 _dss: &tokio_rustls::rustls::DigitallySignedStruct,
716 ) -> Result<
717 tokio_rustls::rustls::client::danger::HandshakeSignatureValid,
718 tokio_rustls::rustls::Error,
719 > {
720 Ok(tokio_rustls::rustls::client::danger::HandshakeSignatureValid::assertion())
721 }
722
723 fn verify_tls13_signature(
724 &self,
725 _message: &[u8],
726 _cert: &tokio_rustls::rustls::pki_types::CertificateDer<'_>,
727 _dss: &tokio_rustls::rustls::DigitallySignedStruct,
728 ) -> Result<
729 tokio_rustls::rustls::client::danger::HandshakeSignatureValid,
730 tokio_rustls::rustls::Error,
731 > {
732 Ok(tokio_rustls::rustls::client::danger::HandshakeSignatureValid::assertion())
733 }
734
735 fn supported_verify_schemes(&self) -> Vec<tokio_rustls::rustls::SignatureScheme> {
736 tokio_rustls::rustls::crypto::ring::default_provider()
737 .signature_verification_algorithms
738 .supported_schemes()
739 }
740}