use crate::api::generated::machine::{
ConnectRecord as ProtoConnectRecord, GenerateClientConfiguration as ProtoGenerateClientConfig,
GenerateClientConfigurationRequest as ProtoGenerateClientConfigRequest,
GenerateClientConfigurationResponse as ProtoGenerateClientConfigResponse,
Netstat as ProtoNetstat, NetstatRequest as ProtoNetstatRequest,
NetstatResponse as ProtoNetstatResponse, PacketCaptureRequest as ProtoPacketCaptureRequest,
RollbackResponse as ProtoRollbackResponse,
};
#[derive(Debug, Clone)]
pub struct RollbackResponse {
pub results: Vec<RollbackResult>,
}
#[derive(Debug, Clone)]
pub struct RollbackResult {
pub node: Option<String>,
}
impl From<ProtoRollbackResponse> for RollbackResponse {
fn from(proto: ProtoRollbackResponse) -> Self {
Self {
results: proto
.messages
.into_iter()
.map(|m| RollbackResult {
node: m.metadata.map(|meta| meta.hostname),
})
.collect(),
}
}
}
impl RollbackResponse {
#[must_use]
pub fn first(&self) -> Option<&RollbackResult> {
self.results.first()
}
#[must_use]
pub fn is_success(&self) -> bool {
!self.results.is_empty()
}
}
#[derive(Debug, Clone, Default)]
pub struct GenerateClientConfigurationRequest {
pub roles: Vec<String>,
pub crt_ttl_seconds: Option<i64>,
}
impl GenerateClientConfigurationRequest {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_roles(roles: Vec<String>) -> Self {
Self {
roles,
crt_ttl_seconds: None,
}
}
#[must_use]
pub fn builder() -> GenerateClientConfigurationRequestBuilder {
GenerateClientConfigurationRequestBuilder::default()
}
}
impl From<GenerateClientConfigurationRequest> for ProtoGenerateClientConfigRequest {
fn from(req: GenerateClientConfigurationRequest) -> Self {
Self {
roles: req.roles,
crt_ttl: req.crt_ttl_seconds.map(|s| prost_types::Duration {
seconds: s,
nanos: 0,
}),
}
}
}
#[derive(Debug, Clone, Default)]
pub struct GenerateClientConfigurationRequestBuilder {
roles: Vec<String>,
crt_ttl_seconds: Option<i64>,
}
impl GenerateClientConfigurationRequestBuilder {
#[must_use]
pub fn role(mut self, role: impl Into<String>) -> Self {
self.roles.push(role.into());
self
}
#[must_use]
pub fn roles(mut self, roles: Vec<String>) -> Self {
self.roles.extend(roles);
self
}
#[must_use]
pub fn crt_ttl_seconds(mut self, ttl: i64) -> Self {
self.crt_ttl_seconds = Some(ttl);
self
}
#[must_use]
pub fn crt_ttl_hours(mut self, hours: i64) -> Self {
self.crt_ttl_seconds = Some(hours * 3600);
self
}
#[must_use]
pub fn crt_ttl_days(mut self, days: i64) -> Self {
self.crt_ttl_seconds = Some(days * 86400);
self
}
#[must_use]
pub fn build(self) -> GenerateClientConfigurationRequest {
GenerateClientConfigurationRequest {
roles: self.roles,
crt_ttl_seconds: self.crt_ttl_seconds,
}
}
}
#[derive(Debug, Clone)]
pub struct GenerateClientConfigurationResult {
pub node: Option<String>,
pub ca: Vec<u8>,
pub crt: Vec<u8>,
pub key: Vec<u8>,
pub talosconfig: Vec<u8>,
}
impl From<ProtoGenerateClientConfig> for GenerateClientConfigurationResult {
fn from(proto: ProtoGenerateClientConfig) -> Self {
Self {
node: proto.metadata.map(|m| m.hostname),
ca: proto.ca,
crt: proto.crt,
key: proto.key,
talosconfig: proto.talosconfig,
}
}
}
impl GenerateClientConfigurationResult {
#[must_use]
pub fn ca_as_str(&self) -> Option<&str> {
std::str::from_utf8(&self.ca).ok()
}
#[must_use]
pub fn crt_as_str(&self) -> Option<&str> {
std::str::from_utf8(&self.crt).ok()
}
#[must_use]
pub fn key_as_str(&self) -> Option<&str> {
std::str::from_utf8(&self.key).ok()
}
#[must_use]
pub fn talosconfig_as_str(&self) -> Option<&str> {
std::str::from_utf8(&self.talosconfig).ok()
}
}
#[derive(Debug, Clone)]
pub struct GenerateClientConfigurationResponse {
pub results: Vec<GenerateClientConfigurationResult>,
}
impl From<ProtoGenerateClientConfigResponse> for GenerateClientConfigurationResponse {
fn from(proto: ProtoGenerateClientConfigResponse) -> Self {
Self {
results: proto
.messages
.into_iter()
.map(GenerateClientConfigurationResult::from)
.collect(),
}
}
}
impl GenerateClientConfigurationResponse {
#[must_use]
pub fn first(&self) -> Option<&GenerateClientConfigurationResult> {
self.results.first()
}
}
#[derive(Debug, Clone)]
pub struct PacketCaptureRequest {
pub interface: String,
pub promiscuous: bool,
pub snap_len: u32,
}
impl PacketCaptureRequest {
#[must_use]
pub fn new(interface: impl Into<String>) -> Self {
Self {
interface: interface.into(),
promiscuous: false,
snap_len: 65535,
}
}
#[must_use]
pub fn builder(interface: impl Into<String>) -> PacketCaptureRequestBuilder {
PacketCaptureRequestBuilder::new(interface)
}
}
impl From<PacketCaptureRequest> for ProtoPacketCaptureRequest {
fn from(req: PacketCaptureRequest) -> Self {
Self {
interface: req.interface,
promiscuous: req.promiscuous,
snap_len: req.snap_len,
bpf_filter: Vec::new(), }
}
}
#[derive(Debug, Clone)]
pub struct PacketCaptureRequestBuilder {
interface: String,
promiscuous: bool,
snap_len: u32,
}
impl PacketCaptureRequestBuilder {
#[must_use]
pub fn new(interface: impl Into<String>) -> Self {
Self {
interface: interface.into(),
promiscuous: false,
snap_len: 65535,
}
}
#[must_use]
pub fn promiscuous(mut self, enabled: bool) -> Self {
self.promiscuous = enabled;
self
}
#[must_use]
pub fn snap_len(mut self, len: u32) -> Self {
self.snap_len = len;
self
}
#[must_use]
pub fn build(self) -> PacketCaptureRequest {
PacketCaptureRequest {
interface: self.interface,
promiscuous: self.promiscuous,
snap_len: self.snap_len,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct PacketCaptureResponse {
pub data: Vec<u8>,
pub node: Option<String>,
}
impl PacketCaptureResponse {
#[must_use]
pub fn new(data: Vec<u8>, node: Option<String>) -> Self {
Self { data, node }
}
#[must_use]
pub fn len(&self) -> usize {
self.data.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.data.is_empty()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum NetstatFilter {
#[default]
All,
Connected,
Listening,
}
impl From<NetstatFilter> for i32 {
fn from(filter: NetstatFilter) -> Self {
match filter {
NetstatFilter::All => 0,
NetstatFilter::Connected => 1,
NetstatFilter::Listening => 2,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct L4ProtoFilter {
pub tcp: bool,
pub tcp6: bool,
pub udp: bool,
pub udp6: bool,
}
impl L4ProtoFilter {
#[must_use]
pub fn all() -> Self {
Self {
tcp: true,
tcp6: true,
udp: true,
udp6: true,
}
}
#[must_use]
pub fn tcp_only() -> Self {
Self {
tcp: true,
tcp6: true,
..Default::default()
}
}
#[must_use]
pub fn udp_only() -> Self {
Self {
udp: true,
udp6: true,
..Default::default()
}
}
}
#[derive(Debug, Clone, Default)]
pub struct NetstatRequest {
pub filter: NetstatFilter,
pub include_pid: bool,
pub l4proto: Option<L4ProtoFilter>,
pub host_network: bool,
pub all_netns: bool,
}
impl NetstatRequest {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn listening() -> Self {
Self {
filter: NetstatFilter::Listening,
..Default::default()
}
}
#[must_use]
pub fn connected() -> Self {
Self {
filter: NetstatFilter::Connected,
..Default::default()
}
}
#[must_use]
pub fn builder() -> NetstatRequestBuilder {
NetstatRequestBuilder::default()
}
}
impl From<NetstatRequest> for ProtoNetstatRequest {
fn from(req: NetstatRequest) -> Self {
use crate::api::generated::machine::netstat_request::{Feature, L4proto, NetNs};
Self {
filter: req.filter.into(),
feature: if req.include_pid {
Some(Feature { pid: true })
} else {
None
},
l4proto: req.l4proto.map(|l4| L4proto {
tcp: l4.tcp,
tcp6: l4.tcp6,
udp: l4.udp,
udp6: l4.udp6,
udplite: false,
udplite6: false,
raw: false,
raw6: false,
}),
netns: Some(NetNs {
hostnetwork: req.host_network,
netns: Vec::new(),
allnetns: req.all_netns,
}),
}
}
}
#[derive(Debug, Clone, Default)]
pub struct NetstatRequestBuilder {
filter: NetstatFilter,
include_pid: bool,
l4proto: Option<L4ProtoFilter>,
host_network: bool,
all_netns: bool,
}
impl NetstatRequestBuilder {
#[must_use]
pub fn filter(mut self, filter: NetstatFilter) -> Self {
self.filter = filter;
self
}
#[must_use]
pub fn include_pid(mut self, include: bool) -> Self {
self.include_pid = include;
self
}
#[must_use]
pub fn l4proto(mut self, l4proto: L4ProtoFilter) -> Self {
self.l4proto = Some(l4proto);
self
}
#[must_use]
pub fn host_network(mut self, include: bool) -> Self {
self.host_network = include;
self
}
#[must_use]
pub fn all_netns(mut self, include: bool) -> Self {
self.all_netns = include;
self
}
#[must_use]
pub fn build(self) -> NetstatRequest {
NetstatRequest {
filter: self.filter,
include_pid: self.include_pid,
l4proto: self.l4proto,
host_network: self.host_network,
all_netns: self.all_netns,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConnectionState {
Reserved,
Established,
SynSent,
SynRecv,
FinWait1,
FinWait2,
TimeWait,
Close,
CloseWait,
LastAck,
Listen,
Closing,
}
impl From<i32> for ConnectionState {
fn from(state: i32) -> Self {
match state {
1 => Self::Established,
2 => Self::SynSent,
3 => Self::SynRecv,
4 => Self::FinWait1,
5 => Self::FinWait2,
6 => Self::TimeWait,
7 => Self::Close,
8 => Self::CloseWait,
9 => Self::LastAck,
10 => Self::Listen,
11 => Self::Closing,
_ => Self::Reserved,
}
}
}
#[derive(Debug, Clone)]
pub struct ConnectionRecord {
pub l4proto: String,
pub local_ip: String,
pub local_port: u32,
pub remote_ip: String,
pub remote_port: u32,
pub state: ConnectionState,
pub tx_queue: u64,
pub rx_queue: u64,
pub pid: Option<u32>,
pub process_name: Option<String>,
pub netns: String,
}
impl From<ProtoConnectRecord> for ConnectionRecord {
fn from(proto: ProtoConnectRecord) -> Self {
let (pid, process_name) = proto
.process
.map(|p| (Some(p.pid), Some(p.name)))
.unwrap_or((None, None));
Self {
l4proto: proto.l4proto,
local_ip: proto.localip,
local_port: proto.localport,
remote_ip: proto.remoteip,
remote_port: proto.remoteport,
state: ConnectionState::from(proto.state),
tx_queue: proto.txqueue,
rx_queue: proto.rxqueue,
pid,
process_name,
netns: proto.netns,
}
}
}
#[derive(Debug, Clone)]
pub struct NetstatResult {
pub node: Option<String>,
pub connections: Vec<ConnectionRecord>,
}
impl From<ProtoNetstat> for NetstatResult {
fn from(proto: ProtoNetstat) -> Self {
Self {
node: proto.metadata.map(|m| m.hostname),
connections: proto
.connectrecord
.into_iter()
.map(ConnectionRecord::from)
.collect(),
}
}
}
#[derive(Debug, Clone)]
pub struct NetstatResponse {
pub results: Vec<NetstatResult>,
}
impl From<ProtoNetstatResponse> for NetstatResponse {
fn from(proto: ProtoNetstatResponse) -> Self {
Self {
results: proto
.messages
.into_iter()
.map(NetstatResult::from)
.collect(),
}
}
}
impl NetstatResponse {
#[must_use]
pub fn first(&self) -> Option<&NetstatResult> {
self.results.first()
}
#[must_use]
pub fn total_connections(&self) -> usize {
self.results.iter().map(|r| r.connections.len()).sum()
}
#[must_use]
pub fn listening(&self) -> Vec<&ConnectionRecord> {
self.results
.iter()
.flat_map(|r| &r.connections)
.filter(|c| c.state == ConnectionState::Listen)
.collect()
}
#[must_use]
pub fn established(&self) -> Vec<&ConnectionRecord> {
self.results
.iter()
.flat_map(|r| &r.connections)
.filter(|c| c.state == ConnectionState::Established)
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rollback_response() {
let result = RollbackResult {
node: Some("node1".to_string()),
};
assert_eq!(result.node, Some("node1".to_string()));
}
#[test]
fn test_generate_client_config_request() {
let req = GenerateClientConfigurationRequest::new();
assert!(req.roles.is_empty());
}
#[test]
fn test_generate_client_config_builder() {
let req = GenerateClientConfigurationRequest::builder()
.role("os:admin")
.role("os:reader")
.crt_ttl_days(30)
.build();
assert_eq!(req.roles, vec!["os:admin", "os:reader"]);
assert_eq!(req.crt_ttl_seconds, Some(30 * 86400));
}
#[test]
fn test_packet_capture_request() {
let req = PacketCaptureRequest::new("eth0");
assert_eq!(req.interface, "eth0");
assert!(!req.promiscuous);
assert_eq!(req.snap_len, 65535);
}
#[test]
fn test_packet_capture_builder() {
let req = PacketCaptureRequest::builder("bond0")
.promiscuous(true)
.snap_len(1500)
.build();
assert_eq!(req.interface, "bond0");
assert!(req.promiscuous);
assert_eq!(req.snap_len, 1500);
}
#[test]
fn test_netstat_request() {
let req = NetstatRequest::listening();
assert_eq!(req.filter, NetstatFilter::Listening);
}
#[test]
fn test_netstat_builder() {
let req = NetstatRequest::builder()
.filter(NetstatFilter::Connected)
.include_pid(true)
.l4proto(L4ProtoFilter::tcp_only())
.host_network(true)
.build();
assert_eq!(req.filter, NetstatFilter::Connected);
assert!(req.include_pid);
assert!(req.l4proto.is_some());
assert!(req.host_network);
}
#[test]
fn test_connection_state() {
assert_eq!(ConnectionState::from(1), ConnectionState::Established);
assert_eq!(ConnectionState::from(10), ConnectionState::Listen);
assert_eq!(ConnectionState::from(999), ConnectionState::Reserved);
}
#[test]
fn test_l4proto_filter() {
let all = L4ProtoFilter::all();
assert!(all.tcp && all.tcp6 && all.udp && all.udp6);
let tcp = L4ProtoFilter::tcp_only();
assert!(tcp.tcp && tcp.tcp6 && !tcp.udp && !tcp.udp6);
}
}