246 lines
7.3 KiB
Rust
246 lines
7.3 KiB
Rust
#[cfg(not(test))]
|
||
use crate::adapters::proxy_router::{
|
||
ProxyRouterAdapter, ProxyRouterError, ProxyRouterErrorKind, ProxyRouterGeneratedConfig,
|
||
ProxyRouterRequest,
|
||
};
|
||
use crate::models::{
|
||
ComponentId, ComponentState, ComponentStatus, Profile, ProfileItemType, Protocol,
|
||
ProxyProtocol, Target,
|
||
};
|
||
#[cfg(test)]
|
||
use crate::proxy_router::{
|
||
ProxyRouterAdapter, ProxyRouterError, ProxyRouterErrorKind, ProxyRouterGeneratedConfig,
|
||
ProxyRouterRequest,
|
||
};
|
||
use serde::{Deserialize, Serialize};
|
||
|
||
pub const PROXIFYRE_ADAPTER_ID: &str = "proxifyre";
|
||
pub const PROXIFYRE_OUTPUT_FILE: &str = "proxifyre-app-config.json";
|
||
|
||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||
pub struct ProxiFyreAdapter {
|
||
log_level: String,
|
||
bypass_lan: bool,
|
||
}
|
||
|
||
impl ProxiFyreAdapter {
|
||
pub fn new(log_level: impl Into<String>, bypass_lan: bool) -> Self {
|
||
Self {
|
||
log_level: log_level.into(),
|
||
bypass_lan,
|
||
}
|
||
}
|
||
|
||
pub fn generate_proxifyre_config(
|
||
&self,
|
||
request: ProxyRouterRequest<'_>,
|
||
) -> Result<ProxiFyreConfig, ProxyRouterError> {
|
||
let mut proxies = Vec::new();
|
||
|
||
for profile in request.profiles.iter().filter(|profile| profile.enabled) {
|
||
let target = find_target(profile, request.targets)?;
|
||
ensure_target_supported(profile, target, request.components)?;
|
||
|
||
let app_names = app_names_for_profile(profile);
|
||
if app_names.is_empty() {
|
||
return Err(ProxyRouterError::new(
|
||
ProxyRouterErrorKind::EmptyProfileItems,
|
||
format!(
|
||
"В профиле '{}' нет приложений для маршрутизации",
|
||
profile.id
|
||
),
|
||
));
|
||
}
|
||
|
||
proxies.push(ProxiFyreProxy {
|
||
app_names,
|
||
socks5_proxy_endpoint: format!("{}:{}", target.host, target.port),
|
||
supported_protocols: protocols_for_profile(profile),
|
||
});
|
||
}
|
||
|
||
Ok(ProxiFyreConfig {
|
||
log_level: self.log_level.clone(),
|
||
bypass_lan: self.bypass_lan,
|
||
proxies,
|
||
})
|
||
}
|
||
}
|
||
|
||
impl Default for ProxiFyreAdapter {
|
||
fn default() -> Self {
|
||
Self::new("Info", true)
|
||
}
|
||
}
|
||
|
||
impl ProxyRouterAdapter for ProxiFyreAdapter {
|
||
fn id(&self) -> &'static str {
|
||
PROXIFYRE_ADAPTER_ID
|
||
}
|
||
|
||
fn output_file_name(&self) -> &'static str {
|
||
PROXIFYRE_OUTPUT_FILE
|
||
}
|
||
|
||
fn generate_config(
|
||
&self,
|
||
request: ProxyRouterRequest<'_>,
|
||
) -> Result<ProxyRouterGeneratedConfig, ProxyRouterError> {
|
||
let config = self.generate_proxifyre_config(request)?;
|
||
let enabled_profiles = config.proxies.len();
|
||
let routed_apps = config
|
||
.proxies
|
||
.iter()
|
||
.map(|proxy| proxy.app_names.len())
|
||
.sum();
|
||
let contents = serde_json::to_string_pretty(&config).map_err(|error| {
|
||
ProxyRouterError::new(
|
||
ProxyRouterErrorKind::Serialization,
|
||
format!("Не удалось сериализовать конфиг ProxiFyre: {error}"),
|
||
)
|
||
})?;
|
||
|
||
Ok(ProxyRouterGeneratedConfig {
|
||
adapter_id: self.id().to_string(),
|
||
output_file_name: self.output_file_name().to_string(),
|
||
contents,
|
||
enabled_profiles,
|
||
routed_apps,
|
||
})
|
||
}
|
||
}
|
||
|
||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||
pub struct ProxiFyreConfig {
|
||
#[serde(rename = "logLevel")]
|
||
pub log_level: String,
|
||
#[serde(rename = "bypassLan")]
|
||
pub bypass_lan: bool,
|
||
pub proxies: Vec<ProxiFyreProxy>,
|
||
}
|
||
|
||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||
pub struct ProxiFyreProxy {
|
||
#[serde(rename = "appNames")]
|
||
pub app_names: Vec<String>,
|
||
#[serde(rename = "socks5ProxyEndpoint")]
|
||
pub socks5_proxy_endpoint: String,
|
||
#[serde(rename = "supportedProtocols")]
|
||
pub supported_protocols: Vec<String>,
|
||
}
|
||
|
||
fn find_target<'a>(
|
||
profile: &Profile,
|
||
targets: &'a [Target],
|
||
) -> Result<&'a Target, ProxyRouterError> {
|
||
targets
|
||
.iter()
|
||
.find(|target| target.id == profile.target_id)
|
||
.ok_or_else(|| {
|
||
ProxyRouterError::new(
|
||
ProxyRouterErrorKind::MissingTarget,
|
||
format!(
|
||
"Профиль '{}' ссылается на отсутствующую цель '{}'",
|
||
profile.id, profile.target_id
|
||
),
|
||
)
|
||
})
|
||
}
|
||
|
||
fn ensure_target_supported(
|
||
profile: &Profile,
|
||
target: &Target,
|
||
components: &[ComponentStatus],
|
||
) -> Result<(), ProxyRouterError> {
|
||
if target.protocol != ProxyProtocol::Socks5 {
|
||
return Err(ProxyRouterError::new(
|
||
ProxyRouterErrorKind::UnsupportedTargetProtocol,
|
||
format!(
|
||
"Цель '{}' использует HTTP, но ProxiFyre требует SOCKS5",
|
||
target.id
|
||
),
|
||
));
|
||
}
|
||
|
||
if let Some(required_component) = &target.requires_component {
|
||
let Some(status) = components
|
||
.iter()
|
||
.find(|component| &component.id == required_component)
|
||
else {
|
||
return Err(ProxyRouterError::new(
|
||
ProxyRouterErrorKind::MissingRequiredComponent,
|
||
format!(
|
||
"Цель '{}' профиля '{}' требует отсутствующий компонент '{}'",
|
||
target.id,
|
||
profile.id,
|
||
component_id_label(required_component)
|
||
),
|
||
));
|
||
};
|
||
|
||
if !component_is_running(status) {
|
||
return Err(ProxyRouterError::new(
|
||
ProxyRouterErrorKind::RequiredComponentNotRunning,
|
||
format!(
|
||
"Цель '{}' профиля '{}' требует запущенный компонент '{}'",
|
||
target.id,
|
||
profile.id,
|
||
component_id_label(required_component)
|
||
),
|
||
));
|
||
}
|
||
}
|
||
|
||
Ok(())
|
||
}
|
||
|
||
fn component_is_running(status: &ComponentStatus) -> bool {
|
||
status.installed && status.running && status.state == ComponentState::Running
|
||
}
|
||
|
||
fn app_names_for_profile(profile: &Profile) -> Vec<String> {
|
||
let mut names = Vec::new();
|
||
|
||
for item in &profile.items {
|
||
let value = item.value.trim();
|
||
if value.is_empty() {
|
||
continue;
|
||
}
|
||
|
||
let app_name = match item.item_type {
|
||
ProfileItemType::Process | ProfileItemType::Folder | ProfileItemType::Exe => value,
|
||
};
|
||
|
||
if !names.iter().any(|existing| existing == app_name) {
|
||
names.push(app_name.to_string());
|
||
}
|
||
}
|
||
|
||
names
|
||
}
|
||
|
||
fn protocols_for_profile(profile: &Profile) -> Vec<String> {
|
||
let mut protocols = Vec::new();
|
||
|
||
for protocol in &profile.protocols {
|
||
let value = match protocol {
|
||
Protocol::Tcp => "TCP",
|
||
Protocol::Udp => "UDP",
|
||
};
|
||
|
||
if !protocols.iter().any(|existing| existing == value) {
|
||
protocols.push(value.to_string());
|
||
}
|
||
}
|
||
|
||
protocols
|
||
}
|
||
|
||
fn component_id_label(component_id: &ComponentId) -> &'static str {
|
||
match component_id {
|
||
ComponentId::ControlApp => "control-app",
|
||
ComponentId::Proxyfier => "proxyfier",
|
||
ComponentId::Singbox => "singbox",
|
||
}
|
||
}
|