Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 19 additions & 74 deletions backend/src/daemon/discovery/service/docker.rs
Original file line number Diff line number Diff line change
@@ -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},
Expand Down Expand Up @@ -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,
Expand All @@ -47,7 +46,6 @@ use crate::{
},
},
};
use cidr::IpCidr;
use mac_address::MacAddress;
use uuid::Uuid;

Expand Down Expand Up @@ -89,7 +87,13 @@ impl RunsDiscovery for DiscoveryRunner<DockerScanDiscovery> {
.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())
Expand Down Expand Up @@ -214,8 +218,16 @@ impl DiscoversNetworkedEntities for DiscoveryRunner<DockerScanDiscovery> {
.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<Subnet> = [host_subnets, docker_subnets].concat();
Expand All @@ -228,26 +240,7 @@ impl DiscoversNetworkedEntities for DiscoveryRunner<DockerScanDiscovery> {
}

impl DiscoveryRunner<DockerScanDiscovery> {
/// Create a new Docker discovery instance connecting to a remote Docker daemon
pub async fn new_local_docker_client(&self) -> Result<Docker, Error> {
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<Service>), Error> {
let daemon_id = self.as_ref().config_store.get_id().await?;
let network_id = self
Expand Down Expand Up @@ -742,54 +735,6 @@ impl DiscoveryRunner<DockerScanDiscovery> {
.map_err(|e| anyhow!(e))
}

pub async fn get_subnets_from_docker_networks(
&self,
daemon_id: Uuid,
network_id: Uuid,
) -> Result<Vec<Subnet>> {
let docker = self
.domain
.docker_client
.get()
.ok_or_else(|| anyhow!("Docker client unavailable"))?;

let subnets: Vec<Subnet> = docker
.list_networks(None::<ListNetworksOptions>)
.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::<Vec<Subnet>>()
})
.collect();

Ok(subnets)
}

pub async fn get_containers_and_summaries(
&self,
) -> Result<Vec<(ContainerInspectResponse, ContainerSummary)>, Error> {
Expand Down
34 changes: 30 additions & 4 deletions backend/src/daemon/discovery/service/self_report.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -113,20 +114,45 @@ impl RunsDiscovery for DiscoveryRunner<SelfReportDiscovery> {
.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<IpCidr> = 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<Subnet> = 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
.iter()
.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<Uuid> = created_subnets.iter().map(|s| s.id).collect();

Expand Down
8 changes: 7 additions & 1 deletion backend/src/daemon/runtime/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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? {
Expand Down
82 changes: 68 additions & 14 deletions backend/src/daemon/utils/base.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
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;
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;

Expand Down Expand Up @@ -103,18 +108,67 @@ pub trait DaemonUtils {
Ok((interfaces_list, subnets))
}

async fn get_own_docker_socket(&self) -> Result<bool, Error> {
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<Option<String>, Error>,
) -> Result<Docker, Error> {
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<Vec<Subnet>, Error> {
let subnets: Vec<Subnet> = client
.list_networks(None::<ListNetworksOptions>)
.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::<Vec<Subnet>>()
})
.collect();

Ok(subnets)
}

async fn get_own_routing_table_gateway_ips(&self) -> Result<Vec<IpAddr>, Error> {
Expand Down
6 changes: 3 additions & 3 deletions backend/src/server/subnets/impl/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -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()..)
Expand Down
Loading