diff --git a/backend/src/daemon/discovery/service/docker.rs b/backend/src/daemon/discovery/service/docker.rs index 35ffb663..34f685ce 100644 --- a/backend/src/daemon/discovery/service/docker.rs +++ b/backend/src/daemon/discovery/service/docker.rs @@ -1,7 +1,6 @@ use anyhow::anyhow; use anyhow::{Error, Result}; use async_trait::async_trait; -use bollard::API_DEFAULT_VERSION; use bollard::{ Docker, query_parameters::{InspectContainerOptions, ListContainersOptions, ListNetworksOptions}, @@ -32,8 +31,8 @@ use crate::server::services::r#impl::virtualization::{ }; use crate::server::shared::storage::traits::StorableEntity; use crate::server::shared::types::entities::{DiscoveryMetadata, EntitySource}; -use crate::server::subnets::r#impl::base::{Subnet, SubnetBase}; -use crate::server::subnets::r#impl::types::{SubnetType, SubnetTypeDiscriminants}; +use crate::server::subnets::r#impl::base::Subnet; +use crate::server::subnets::r#impl::types::SubnetTypeDiscriminants; use crate::{ daemon::discovery::service::base::{ CreatesDiscoveredEntities, DiscoversNetworkedEntities, DiscoveryRunner, @@ -47,7 +46,6 @@ use crate::{ }, }, }; -use cidr::IpCidr; use mac_address::MacAddress; use uuid::Uuid; @@ -89,7 +87,13 @@ impl RunsDiscovery for DiscoveryRunner { .await? .ok_or_else(|| anyhow::anyhow!("Network ID not set"))?; - let docker = self.new_local_docker_client().await?; + let docker_proxy = self.as_ref().config_store.get_docker_proxy().await; + + let docker = self + .as_ref() + .utils + .new_local_docker_client(docker_proxy) + .await?; self.domain .docker_client .set(docker.clone()) @@ -214,8 +218,16 @@ impl DiscoversNetworkedEntities for DiscoveryRunner { .get_own_interfaces(self.discovery_type(), daemon_id, network_id) .await?; + let docker = self + .domain + .docker_client + .get() + .ok_or_else(|| anyhow!("Docker client unavailable"))?; + let docker_subnets = self - .get_subnets_from_docker_networks(daemon_id, network_id) + .as_ref() + .utils + .get_subnets_from_docker_networks(daemon_id, network_id, docker, self.discovery_type()) .await?; let subnets: Vec = [host_subnets, docker_subnets].concat(); @@ -228,26 +240,7 @@ impl DiscoversNetworkedEntities for DiscoveryRunner { } impl DiscoveryRunner { - /// Create a new Docker discovery instance connecting to a remote Docker daemon - pub async fn new_local_docker_client(&self) -> Result { - tracing::debug!("Connecting to Docker daemon"); - - let client = - if let Ok(Some(docker_proxy)) = self.as_ref().config_store.get_docker_proxy().await { - Docker::connect_with_http(&docker_proxy, 4, API_DEFAULT_VERSION) - .map_err(|e| anyhow::anyhow!("Failed to connect to Docker: {}", e))? - } else { - Docker::connect_with_local_defaults() - .map_err(|e| anyhow::anyhow!("Failed to connect to Docker: {}", e))? - }; - - client.ping().await?; - - Ok(client) - } - - /// Create docker daemon service which has all discovered containers in containers field - /// Create netvisor daemon service which has container relationship with docker daemon service + /// Create docker daemon service which has container relationship with docker daemon service pub async fn create_docker_daemon_service(&self) -> Result<(Host, Vec), Error> { let daemon_id = self.as_ref().config_store.get_id().await?; let network_id = self @@ -742,54 +735,6 @@ impl DiscoveryRunner { .map_err(|e| anyhow!(e)) } - pub async fn get_subnets_from_docker_networks( - &self, - daemon_id: Uuid, - network_id: Uuid, - ) -> Result> { - let docker = self - .domain - .docker_client - .get() - .ok_or_else(|| anyhow!("Docker client unavailable"))?; - - let subnets: Vec = docker - .list_networks(None::) - .await? - .into_iter() - .filter_map(|n| { - let network_name = n.name.clone().unwrap_or("Unknown Network".to_string()); - n.ipam.clone().map(|ipam| (network_name, ipam)) - }) - .filter_map(|(network_name, ipam)| ipam.config.map(|config| (network_name, config))) - .flat_map(|(network_name, configs)| { - configs - .iter() - .filter_map(|c| { - if let Some(cidr) = &c.subnet { - return Some(Subnet::new(SubnetBase { - cidr: IpCidr::from_str(cidr).ok()?, - description: None, - network_id, - name: network_name.clone(), - subnet_type: SubnetType::DockerBridge, - source: EntitySource::Discovery { - metadata: vec![DiscoveryMetadata::new( - self.discovery_type(), - daemon_id, - )], - }, - })); - } - None - }) - .collect::>() - }) - .collect(); - - Ok(subnets) - } - pub async fn get_containers_and_summaries( &self, ) -> Result, Error> { diff --git a/backend/src/daemon/discovery/service/self_report.rs b/backend/src/daemon/discovery/service/self_report.rs index 4a5d7d92..fbdd8201 100644 --- a/backend/src/daemon/discovery/service/self_report.rs +++ b/backend/src/daemon/discovery/service/self_report.rs @@ -42,6 +42,7 @@ use crate::{ use anyhow::{Error, Result}; use async_trait::async_trait; use chrono::Utc; +use cidr::IpCidr; use futures::future::try_join_all; use std::net::{IpAddr, Ipv4Addr}; use strum::IntoDiscriminant; @@ -113,10 +114,38 @@ impl RunsDiscovery for DiscoveryRunner { .get_own_interfaces(self.discovery_type(), daemon_id, network_id) .await?; + // Get docker subnets to double verify that subnet interface string matching filtered them correctly + let docker_proxy = self.as_ref().config_store.get_docker_proxy().await; + let docker_client = self + .as_ref() + .utils + .new_local_docker_client(docker_proxy) + .await; + + let (docker_cidrs, has_docker_socket) = if let Ok(docker_client) = docker_client { + let docker_subnets = self + .as_ref() + .utils + .get_subnets_from_docker_networks( + daemon_id, + network_id, + &docker_client, + self.discovery_type(), + ) + .await?; + let docker_cidrs: Vec = docker_subnets.iter().map(|s| s.base.cidr).collect(); + (docker_cidrs, true) + } else { + (Vec::new(), false) + }; + // Filter out docker bridge subnets, those are handled in docker discovery let subnets_to_create: Vec = subnets .into_iter() - .filter(|s| s.base.subnet_type.discriminant() != SubnetTypeDiscriminants::DockerBridge) + .filter(|s| { + s.base.subnet_type.discriminant() != SubnetTypeDiscriminants::DockerBridge + && !docker_cidrs.contains(&s.base.cidr) + }) .collect(); let subnet_futures = subnets_to_create @@ -124,9 +153,6 @@ impl RunsDiscovery for DiscoveryRunner { .map(|subnet| self.create_subnet(subnet)); let created_subnets = try_join_all(subnet_futures).await?; - // Get docker socket - let has_docker_socket = self.as_ref().utils.get_own_docker_socket().await?; - // Update capabilities let interfaced_subnet_ids: Vec = created_subnets.iter().map(|s| s.id).collect(); diff --git a/backend/src/daemon/runtime/service.rs b/backend/src/daemon/runtime/service.rs index 7a79ecf3..dcf0105b 100644 --- a/backend/src/daemon/runtime/service.rs +++ b/backend/src/daemon/runtime/service.rs @@ -186,8 +186,14 @@ impl DaemonRuntimeService { self.config_store.set_network_id(network_id).await?; self.config_store.set_api_key(api_key).await?; + let docker_proxy = self.config_store.get_docker_proxy().await; + let daemon_id = self.config_store.get_id().await?; - let has_docker_client = self.utils.get_own_docker_socket().await?; + let has_docker_client = self + .utils + .new_local_docker_client(docker_proxy) + .await + .is_ok(); // Check if already registered if let Some(existing_host_id) = self.config_store.get_host_id().await? { diff --git a/backend/src/daemon/utils/base.rs b/backend/src/daemon/utils/base.rs index d3d6d9be..7a741ca8 100644 --- a/backend/src/daemon/utils/base.rs +++ b/backend/src/daemon/utils/base.rs @@ -1,10 +1,14 @@ use crate::server::discovery::r#impl::types::DiscoveryType; use crate::server::hosts::r#impl::interfaces::{Interface, InterfaceBase}; -use crate::server::subnets::r#impl::base::Subnet; +use crate::server::shared::storage::traits::StorableEntity; +use crate::server::shared::types::entities::{DiscoveryMetadata, EntitySource}; +use crate::server::subnets::r#impl::base::{Subnet, SubnetBase}; +use crate::server::subnets::r#impl::types::SubnetType; use anyhow::Error; use anyhow::anyhow; use async_trait::async_trait; -use bollard::Docker; +use bollard::query_parameters::ListNetworksOptions; +use bollard::{API_DEFAULT_VERSION, Docker}; use cidr::IpCidr; use local_ip_address::local_ip; use mac_address::MacAddress; @@ -12,6 +16,7 @@ use net_route::Handle; use pnet::ipnetwork::IpNetwork; use std::collections::HashMap; use std::net::IpAddr; +use std::str::FromStr; use std::time::Duration; use uuid::Uuid; @@ -103,18 +108,67 @@ pub trait DaemonUtils { Ok((interfaces_list, subnets)) } - async fn get_own_docker_socket(&self) -> Result { - match Docker::connect_with_local_defaults() { - Ok(docker) => { - // Actually verify it's a Docker daemon by pinging it - if docker.ping().await.is_ok() { - Ok(true) - } else { - Ok(false) - } - } - Err(_) => Ok(false), - } + async fn new_local_docker_client( + &self, + docker_proxy: Result, Error>, + ) -> Result { + tracing::debug!("Connecting to Docker daemon"); + + let client = if let Ok(Some(docker_proxy)) = docker_proxy { + Docker::connect_with_http(&docker_proxy, 4, API_DEFAULT_VERSION) + .map_err(|e| anyhow::anyhow!("Failed to connect to Docker: {}", e))? + } else { + Docker::connect_with_local_defaults() + .map_err(|e| anyhow::anyhow!("Failed to connect to Docker: {}", e))? + }; + + client.ping().await?; + + Ok(client) + } + + async fn get_subnets_from_docker_networks( + &self, + daemon_id: Uuid, + network_id: Uuid, + client: &Docker, + discovery_type: DiscoveryType, + ) -> Result, Error> { + let subnets: Vec = client + .list_networks(None::) + .await? + .into_iter() + .filter_map(|n| { + let network_name = n.name.clone().unwrap_or("Unknown Network".to_string()); + n.ipam.clone().map(|ipam| (network_name, ipam)) + }) + .filter_map(|(network_name, ipam)| ipam.config.map(|config| (network_name, config))) + .flat_map(|(network_name, configs)| { + configs + .iter() + .filter_map(|c| { + if let Some(cidr) = &c.subnet { + return Some(Subnet::new(SubnetBase { + cidr: IpCidr::from_str(cidr).ok()?, + description: None, + network_id, + name: network_name.clone(), + subnet_type: SubnetType::DockerBridge, + source: EntitySource::Discovery { + metadata: vec![DiscoveryMetadata::new( + discovery_type.clone(), + daemon_id, + )], + }, + })); + } + None + }) + .collect::>() + }) + .collect(); + + Ok(subnets) } async fn get_own_routing_table_gateway_ips(&self) -> Result, Error> { diff --git a/backend/src/server/subnets/impl/types.rs b/backend/src/server/subnets/impl/types.rs index bb26e390..b2cc74f1 100644 --- a/backend/src/server/subnets/impl/types.rs +++ b/backend/src/server/subnets/impl/types.rs @@ -47,7 +47,7 @@ pub enum SubnetType { impl SubnetType { pub fn from_interface_name(interface_name: &str) -> Self { // Docker containers - if Self::match_interface_names(&["docker", "br-"], interface_name) { + if Self::match_interface_names(&["docker", "br-", "docker"], interface_name) { return SubnetType::DockerBridge; } @@ -98,8 +98,8 @@ impl SubnetType { fn match_interface_names(patterns: &[&str], interface_name: &str) -> bool { let name_lower = interface_name.to_lowercase(); patterns.iter().any(|pattern| { - if *pattern == "br-" { - // Special case for Docker bridges: br- followed by hex chars + if *pattern == "br-" || *pattern == "docker-" { + // Special case for Docker bridges: br- or docker- followed by hex chars name_lower.starts_with(pattern) && name_lower .get(pattern.len()..)