1use crate::api::generated::machine::{
8 ConnectRecord as ProtoConnectRecord, GenerateClientConfiguration as ProtoGenerateClientConfig,
9 GenerateClientConfigurationRequest as ProtoGenerateClientConfigRequest,
10 GenerateClientConfigurationResponse as ProtoGenerateClientConfigResponse,
11 Netstat as ProtoNetstat, NetstatRequest as ProtoNetstatRequest,
12 NetstatResponse as ProtoNetstatResponse, PacketCaptureRequest as ProtoPacketCaptureRequest,
13 RollbackResponse as ProtoRollbackResponse,
14};
15
16#[derive(Debug, Clone)]
22pub struct RollbackResponse {
23 pub results: Vec<RollbackResult>,
25}
26
27#[derive(Debug, Clone)]
29pub struct RollbackResult {
30 pub node: Option<String>,
32}
33
34impl From<ProtoRollbackResponse> for RollbackResponse {
35 fn from(proto: ProtoRollbackResponse) -> Self {
36 Self {
37 results: proto
38 .messages
39 .into_iter()
40 .map(|m| RollbackResult {
41 node: m.metadata.map(|meta| meta.hostname),
42 })
43 .collect(),
44 }
45 }
46}
47
48impl RollbackResponse {
49 #[must_use]
51 pub fn first(&self) -> Option<&RollbackResult> {
52 self.results.first()
53 }
54
55 #[must_use]
57 pub fn is_success(&self) -> bool {
58 !self.results.is_empty()
59 }
60}
61
62#[derive(Debug, Clone, Default)]
68pub struct GenerateClientConfigurationRequest {
69 pub roles: Vec<String>,
71 pub crt_ttl_seconds: Option<i64>,
73}
74
75impl GenerateClientConfigurationRequest {
76 #[must_use]
78 pub fn new() -> Self {
79 Self::default()
80 }
81
82 #[must_use]
84 pub fn with_roles(roles: Vec<String>) -> Self {
85 Self {
86 roles,
87 crt_ttl_seconds: None,
88 }
89 }
90
91 #[must_use]
93 pub fn builder() -> GenerateClientConfigurationRequestBuilder {
94 GenerateClientConfigurationRequestBuilder::default()
95 }
96}
97
98impl From<GenerateClientConfigurationRequest> for ProtoGenerateClientConfigRequest {
99 fn from(req: GenerateClientConfigurationRequest) -> Self {
100 Self {
101 roles: req.roles,
102 crt_ttl: req.crt_ttl_seconds.map(|s| prost_types::Duration {
103 seconds: s,
104 nanos: 0,
105 }),
106 }
107 }
108}
109
110#[derive(Debug, Clone, Default)]
112pub struct GenerateClientConfigurationRequestBuilder {
113 roles: Vec<String>,
114 crt_ttl_seconds: Option<i64>,
115}
116
117impl GenerateClientConfigurationRequestBuilder {
118 #[must_use]
120 pub fn role(mut self, role: impl Into<String>) -> Self {
121 self.roles.push(role.into());
122 self
123 }
124
125 #[must_use]
127 pub fn roles(mut self, roles: Vec<String>) -> Self {
128 self.roles.extend(roles);
129 self
130 }
131
132 #[must_use]
134 pub fn crt_ttl_seconds(mut self, ttl: i64) -> Self {
135 self.crt_ttl_seconds = Some(ttl);
136 self
137 }
138
139 #[must_use]
141 pub fn crt_ttl_hours(mut self, hours: i64) -> Self {
142 self.crt_ttl_seconds = Some(hours * 3600);
143 self
144 }
145
146 #[must_use]
148 pub fn crt_ttl_days(mut self, days: i64) -> Self {
149 self.crt_ttl_seconds = Some(days * 86400);
150 self
151 }
152
153 #[must_use]
155 pub fn build(self) -> GenerateClientConfigurationRequest {
156 GenerateClientConfigurationRequest {
157 roles: self.roles,
158 crt_ttl_seconds: self.crt_ttl_seconds,
159 }
160 }
161}
162
163#[derive(Debug, Clone)]
165pub struct GenerateClientConfigurationResult {
166 pub node: Option<String>,
168 pub ca: Vec<u8>,
170 pub crt: Vec<u8>,
172 pub key: Vec<u8>,
174 pub talosconfig: Vec<u8>,
176}
177
178impl From<ProtoGenerateClientConfig> for GenerateClientConfigurationResult {
179 fn from(proto: ProtoGenerateClientConfig) -> Self {
180 Self {
181 node: proto.metadata.map(|m| m.hostname),
182 ca: proto.ca,
183 crt: proto.crt,
184 key: proto.key,
185 talosconfig: proto.talosconfig,
186 }
187 }
188}
189
190impl GenerateClientConfigurationResult {
191 #[must_use]
193 pub fn ca_as_str(&self) -> Option<&str> {
194 std::str::from_utf8(&self.ca).ok()
195 }
196
197 #[must_use]
199 pub fn crt_as_str(&self) -> Option<&str> {
200 std::str::from_utf8(&self.crt).ok()
201 }
202
203 #[must_use]
205 pub fn key_as_str(&self) -> Option<&str> {
206 std::str::from_utf8(&self.key).ok()
207 }
208
209 #[must_use]
211 pub fn talosconfig_as_str(&self) -> Option<&str> {
212 std::str::from_utf8(&self.talosconfig).ok()
213 }
214}
215
216#[derive(Debug, Clone)]
218pub struct GenerateClientConfigurationResponse {
219 pub results: Vec<GenerateClientConfigurationResult>,
221}
222
223impl From<ProtoGenerateClientConfigResponse> for GenerateClientConfigurationResponse {
224 fn from(proto: ProtoGenerateClientConfigResponse) -> Self {
225 Self {
226 results: proto
227 .messages
228 .into_iter()
229 .map(GenerateClientConfigurationResult::from)
230 .collect(),
231 }
232 }
233}
234
235impl GenerateClientConfigurationResponse {
236 #[must_use]
238 pub fn first(&self) -> Option<&GenerateClientConfigurationResult> {
239 self.results.first()
240 }
241}
242
243#[derive(Debug, Clone)]
249pub struct PacketCaptureRequest {
250 pub interface: String,
252 pub promiscuous: bool,
254 pub snap_len: u32,
256}
257
258impl PacketCaptureRequest {
259 #[must_use]
261 pub fn new(interface: impl Into<String>) -> Self {
262 Self {
263 interface: interface.into(),
264 promiscuous: false,
265 snap_len: 65535,
266 }
267 }
268
269 #[must_use]
271 pub fn builder(interface: impl Into<String>) -> PacketCaptureRequestBuilder {
272 PacketCaptureRequestBuilder::new(interface)
273 }
274}
275
276impl From<PacketCaptureRequest> for ProtoPacketCaptureRequest {
277 fn from(req: PacketCaptureRequest) -> Self {
278 Self {
279 interface: req.interface,
280 promiscuous: req.promiscuous,
281 snap_len: req.snap_len,
282 bpf_filter: Vec::new(), }
284 }
285}
286
287#[derive(Debug, Clone)]
289pub struct PacketCaptureRequestBuilder {
290 interface: String,
291 promiscuous: bool,
292 snap_len: u32,
293}
294
295impl PacketCaptureRequestBuilder {
296 #[must_use]
298 pub fn new(interface: impl Into<String>) -> Self {
299 Self {
300 interface: interface.into(),
301 promiscuous: false,
302 snap_len: 65535,
303 }
304 }
305
306 #[must_use]
308 pub fn promiscuous(mut self, enabled: bool) -> Self {
309 self.promiscuous = enabled;
310 self
311 }
312
313 #[must_use]
315 pub fn snap_len(mut self, len: u32) -> Self {
316 self.snap_len = len;
317 self
318 }
319
320 #[must_use]
322 pub fn build(self) -> PacketCaptureRequest {
323 PacketCaptureRequest {
324 interface: self.interface,
325 promiscuous: self.promiscuous,
326 snap_len: self.snap_len,
327 }
328 }
329}
330
331#[derive(Debug, Clone, Default)]
333pub struct PacketCaptureResponse {
334 pub data: Vec<u8>,
336 pub node: Option<String>,
338}
339
340impl PacketCaptureResponse {
341 #[must_use]
343 pub fn new(data: Vec<u8>, node: Option<String>) -> Self {
344 Self { data, node }
345 }
346
347 #[must_use]
349 pub fn len(&self) -> usize {
350 self.data.len()
351 }
352
353 #[must_use]
355 pub fn is_empty(&self) -> bool {
356 self.data.is_empty()
357 }
358}
359
360#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
366pub enum NetstatFilter {
367 #[default]
369 All,
370 Connected,
372 Listening,
374}
375
376impl From<NetstatFilter> for i32 {
377 fn from(filter: NetstatFilter) -> Self {
378 match filter {
379 NetstatFilter::All => 0,
380 NetstatFilter::Connected => 1,
381 NetstatFilter::Listening => 2,
382 }
383 }
384}
385
386#[derive(Debug, Clone, Default)]
388pub struct L4ProtoFilter {
389 pub tcp: bool,
391 pub tcp6: bool,
393 pub udp: bool,
395 pub udp6: bool,
397}
398
399impl L4ProtoFilter {
400 #[must_use]
402 pub fn all() -> Self {
403 Self {
404 tcp: true,
405 tcp6: true,
406 udp: true,
407 udp6: true,
408 }
409 }
410
411 #[must_use]
413 pub fn tcp_only() -> Self {
414 Self {
415 tcp: true,
416 tcp6: true,
417 ..Default::default()
418 }
419 }
420
421 #[must_use]
423 pub fn udp_only() -> Self {
424 Self {
425 udp: true,
426 udp6: true,
427 ..Default::default()
428 }
429 }
430}
431
432#[derive(Debug, Clone, Default)]
434pub struct NetstatRequest {
435 pub filter: NetstatFilter,
437 pub include_pid: bool,
439 pub l4proto: Option<L4ProtoFilter>,
441 pub host_network: bool,
443 pub all_netns: bool,
445}
446
447impl NetstatRequest {
448 #[must_use]
450 pub fn new() -> Self {
451 Self::default()
452 }
453
454 #[must_use]
456 pub fn listening() -> Self {
457 Self {
458 filter: NetstatFilter::Listening,
459 ..Default::default()
460 }
461 }
462
463 #[must_use]
465 pub fn connected() -> Self {
466 Self {
467 filter: NetstatFilter::Connected,
468 ..Default::default()
469 }
470 }
471
472 #[must_use]
474 pub fn builder() -> NetstatRequestBuilder {
475 NetstatRequestBuilder::default()
476 }
477}
478
479impl From<NetstatRequest> for ProtoNetstatRequest {
480 fn from(req: NetstatRequest) -> Self {
481 use crate::api::generated::machine::netstat_request::{Feature, L4proto, NetNs};
482
483 Self {
484 filter: req.filter.into(),
485 feature: if req.include_pid {
486 Some(Feature { pid: true })
487 } else {
488 None
489 },
490 l4proto: req.l4proto.map(|l4| L4proto {
491 tcp: l4.tcp,
492 tcp6: l4.tcp6,
493 udp: l4.udp,
494 udp6: l4.udp6,
495 udplite: false,
496 udplite6: false,
497 raw: false,
498 raw6: false,
499 }),
500 netns: Some(NetNs {
501 hostnetwork: req.host_network,
502 netns: Vec::new(),
503 allnetns: req.all_netns,
504 }),
505 }
506 }
507}
508
509#[derive(Debug, Clone, Default)]
511pub struct NetstatRequestBuilder {
512 filter: NetstatFilter,
513 include_pid: bool,
514 l4proto: Option<L4ProtoFilter>,
515 host_network: bool,
516 all_netns: bool,
517}
518
519impl NetstatRequestBuilder {
520 #[must_use]
522 pub fn filter(mut self, filter: NetstatFilter) -> Self {
523 self.filter = filter;
524 self
525 }
526
527 #[must_use]
529 pub fn include_pid(mut self, include: bool) -> Self {
530 self.include_pid = include;
531 self
532 }
533
534 #[must_use]
536 pub fn l4proto(mut self, l4proto: L4ProtoFilter) -> Self {
537 self.l4proto = Some(l4proto);
538 self
539 }
540
541 #[must_use]
543 pub fn host_network(mut self, include: bool) -> Self {
544 self.host_network = include;
545 self
546 }
547
548 #[must_use]
550 pub fn all_netns(mut self, include: bool) -> Self {
551 self.all_netns = include;
552 self
553 }
554
555 #[must_use]
557 pub fn build(self) -> NetstatRequest {
558 NetstatRequest {
559 filter: self.filter,
560 include_pid: self.include_pid,
561 l4proto: self.l4proto,
562 host_network: self.host_network,
563 all_netns: self.all_netns,
564 }
565 }
566}
567
568#[derive(Debug, Clone, Copy, PartialEq, Eq)]
570pub enum ConnectionState {
571 Reserved,
573 Established,
575 SynSent,
577 SynRecv,
579 FinWait1,
581 FinWait2,
583 TimeWait,
585 Close,
587 CloseWait,
589 LastAck,
591 Listen,
593 Closing,
595}
596
597impl From<i32> for ConnectionState {
598 fn from(state: i32) -> Self {
599 match state {
600 1 => Self::Established,
601 2 => Self::SynSent,
602 3 => Self::SynRecv,
603 4 => Self::FinWait1,
604 5 => Self::FinWait2,
605 6 => Self::TimeWait,
606 7 => Self::Close,
607 8 => Self::CloseWait,
608 9 => Self::LastAck,
609 10 => Self::Listen,
610 11 => Self::Closing,
611 _ => Self::Reserved,
612 }
613 }
614}
615
616#[derive(Debug, Clone)]
618pub struct ConnectionRecord {
619 pub l4proto: String,
621 pub local_ip: String,
623 pub local_port: u32,
625 pub remote_ip: String,
627 pub remote_port: u32,
629 pub state: ConnectionState,
631 pub tx_queue: u64,
633 pub rx_queue: u64,
635 pub pid: Option<u32>,
637 pub process_name: Option<String>,
639 pub netns: String,
641}
642
643impl From<ProtoConnectRecord> for ConnectionRecord {
644 fn from(proto: ProtoConnectRecord) -> Self {
645 let (pid, process_name) = proto
646 .process
647 .map(|p| (Some(p.pid), Some(p.name)))
648 .unwrap_or((None, None));
649
650 Self {
651 l4proto: proto.l4proto,
652 local_ip: proto.localip,
653 local_port: proto.localport,
654 remote_ip: proto.remoteip,
655 remote_port: proto.remoteport,
656 state: ConnectionState::from(proto.state),
657 tx_queue: proto.txqueue,
658 rx_queue: proto.rxqueue,
659 pid,
660 process_name,
661 netns: proto.netns,
662 }
663 }
664}
665
666#[derive(Debug, Clone)]
668pub struct NetstatResult {
669 pub node: Option<String>,
671 pub connections: Vec<ConnectionRecord>,
673}
674
675impl From<ProtoNetstat> for NetstatResult {
676 fn from(proto: ProtoNetstat) -> Self {
677 Self {
678 node: proto.metadata.map(|m| m.hostname),
679 connections: proto
680 .connectrecord
681 .into_iter()
682 .map(ConnectionRecord::from)
683 .collect(),
684 }
685 }
686}
687
688#[derive(Debug, Clone)]
690pub struct NetstatResponse {
691 pub results: Vec<NetstatResult>,
693}
694
695impl From<ProtoNetstatResponse> for NetstatResponse {
696 fn from(proto: ProtoNetstatResponse) -> Self {
697 Self {
698 results: proto
699 .messages
700 .into_iter()
701 .map(NetstatResult::from)
702 .collect(),
703 }
704 }
705}
706
707impl NetstatResponse {
708 #[must_use]
710 pub fn first(&self) -> Option<&NetstatResult> {
711 self.results.first()
712 }
713
714 #[must_use]
716 pub fn total_connections(&self) -> usize {
717 self.results.iter().map(|r| r.connections.len()).sum()
718 }
719
720 #[must_use]
722 pub fn listening(&self) -> Vec<&ConnectionRecord> {
723 self.results
724 .iter()
725 .flat_map(|r| &r.connections)
726 .filter(|c| c.state == ConnectionState::Listen)
727 .collect()
728 }
729
730 #[must_use]
732 pub fn established(&self) -> Vec<&ConnectionRecord> {
733 self.results
734 .iter()
735 .flat_map(|r| &r.connections)
736 .filter(|c| c.state == ConnectionState::Established)
737 .collect()
738 }
739}
740
741#[cfg(test)]
742mod tests {
743 use super::*;
744
745 #[test]
746 fn test_rollback_response() {
747 let result = RollbackResult {
748 node: Some("node1".to_string()),
749 };
750 assert_eq!(result.node, Some("node1".to_string()));
751 }
752
753 #[test]
754 fn test_generate_client_config_request() {
755 let req = GenerateClientConfigurationRequest::new();
756 assert!(req.roles.is_empty());
757 }
758
759 #[test]
760 fn test_generate_client_config_builder() {
761 let req = GenerateClientConfigurationRequest::builder()
762 .role("os:admin")
763 .role("os:reader")
764 .crt_ttl_days(30)
765 .build();
766
767 assert_eq!(req.roles, vec!["os:admin", "os:reader"]);
768 assert_eq!(req.crt_ttl_seconds, Some(30 * 86400));
769 }
770
771 #[test]
772 fn test_packet_capture_request() {
773 let req = PacketCaptureRequest::new("eth0");
774 assert_eq!(req.interface, "eth0");
775 assert!(!req.promiscuous);
776 assert_eq!(req.snap_len, 65535);
777 }
778
779 #[test]
780 fn test_packet_capture_builder() {
781 let req = PacketCaptureRequest::builder("bond0")
782 .promiscuous(true)
783 .snap_len(1500)
784 .build();
785
786 assert_eq!(req.interface, "bond0");
787 assert!(req.promiscuous);
788 assert_eq!(req.snap_len, 1500);
789 }
790
791 #[test]
792 fn test_netstat_request() {
793 let req = NetstatRequest::listening();
794 assert_eq!(req.filter, NetstatFilter::Listening);
795 }
796
797 #[test]
798 fn test_netstat_builder() {
799 let req = NetstatRequest::builder()
800 .filter(NetstatFilter::Connected)
801 .include_pid(true)
802 .l4proto(L4ProtoFilter::tcp_only())
803 .host_network(true)
804 .build();
805
806 assert_eq!(req.filter, NetstatFilter::Connected);
807 assert!(req.include_pid);
808 assert!(req.l4proto.is_some());
809 assert!(req.host_network);
810 }
811
812 #[test]
813 fn test_connection_state() {
814 assert_eq!(ConnectionState::from(1), ConnectionState::Established);
815 assert_eq!(ConnectionState::from(10), ConnectionState::Listen);
816 assert_eq!(ConnectionState::from(999), ConnectionState::Reserved);
817 }
818
819 #[test]
820 fn test_l4proto_filter() {
821 let all = L4ProtoFilter::all();
822 assert!(all.tcp && all.tcp6 && all.udp && all.udp6);
823
824 let tcp = L4ProtoFilter::tcp_only();
825 assert!(tcp.tcp && tcp.tcp6 && !tcp.udp && !tcp.udp6);
826 }
827}