mirror of
https://github.com/sudosylabs/vnidrop.git
synced 2026-08-05 18:39:55 +02:00
feat(network): add relay connection policies
This commit is contained in:
@@ -12,7 +12,9 @@ pub(crate) const MAX_RELAY_URL_BYTES: usize = 2_048;
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, uniffi::Enum)]
|
||||
pub enum CoreRelayMode {
|
||||
Automatic,
|
||||
Custom,
|
||||
StrictCustom,
|
||||
CustomWithDirectFallback,
|
||||
LocalOnly,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, uniffi::Record)]
|
||||
@@ -33,13 +35,21 @@ impl Default for CoreNetworkConfig {
|
||||
impl CoreNetworkConfig {
|
||||
pub(crate) fn validated_relay_urls(&self) -> anyhow::Result<Vec<RelayUrl>> {
|
||||
match self.mode {
|
||||
CoreRelayMode::Automatic => {
|
||||
CoreRelayMode::Automatic | CoreRelayMode::LocalOnly => {
|
||||
if !self.relay_urls.is_empty() {
|
||||
anyhow::bail!("automatic relay mode must not include custom relay URLs");
|
||||
anyhow::bail!(
|
||||
"{} relay mode must not include custom relay URLs",
|
||||
match self.mode {
|
||||
CoreRelayMode::Automatic => "automatic",
|
||||
CoreRelayMode::LocalOnly => "local-only",
|
||||
CoreRelayMode::StrictCustom
|
||||
| CoreRelayMode::CustomWithDirectFallback => unreachable!(),
|
||||
}
|
||||
);
|
||||
}
|
||||
Ok(Vec::new())
|
||||
}
|
||||
CoreRelayMode::Custom => {
|
||||
CoreRelayMode::StrictCustom | CoreRelayMode::CustomWithDirectFallback => {
|
||||
if self.relay_urls.is_empty() {
|
||||
anyhow::bail!("custom relay mode requires at least one relay URL");
|
||||
}
|
||||
|
||||
@@ -61,6 +61,23 @@ use crate::{
|
||||
|
||||
const RELAY_CONNECT_TIMEOUT: Duration = Duration::from_secs(10);
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(crate) enum RelayStatus {
|
||||
Disabled,
|
||||
Connected,
|
||||
Unreachable,
|
||||
}
|
||||
|
||||
impl RelayStatus {
|
||||
fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::Disabled => "disabled",
|
||||
Self::Connected => "connected",
|
||||
Self::Unreachable => "unreachable",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Owns the Iroh endpoint, blob store, transfer history, and byte streaming.
|
||||
/// Kotlin owns app lifecycle and platform file picking.
|
||||
pub(super) struct CoreInner {
|
||||
@@ -122,7 +139,7 @@ impl CoreInner {
|
||||
.bind()
|
||||
.await?
|
||||
}
|
||||
CoreRelayMode::Custom => {
|
||||
CoreRelayMode::StrictCustom | CoreRelayMode::CustomWithDirectFallback => {
|
||||
let relay_map = RelayMap::from_iter(relay_urls.iter().cloned().map(|url| {
|
||||
// Loopback HTTP is a development escape hatch. Without TLS the
|
||||
// relay cannot serve Iroh's QUIC address-discovery endpoint.
|
||||
@@ -141,13 +158,22 @@ impl CoreInner {
|
||||
.bind()
|
||||
.await?
|
||||
}
|
||||
CoreRelayMode::LocalOnly => {
|
||||
Endpoint::builder(presets::Minimal)
|
||||
.relay_mode(RelayMode::Disabled)
|
||||
.secret_key(secret_key)
|
||||
.bind()
|
||||
.await?
|
||||
}
|
||||
};
|
||||
if let Err(error) =
|
||||
wait_for_relay(&endpoint, relay_mode, &relay_urls, RELAY_CONNECT_TIMEOUT).await
|
||||
{
|
||||
endpoint.close().await;
|
||||
return Err(error);
|
||||
}
|
||||
let relay_status =
|
||||
match wait_for_relay(&endpoint, relay_mode, &relay_urls, RELAY_CONNECT_TIMEOUT).await {
|
||||
Ok(status) => status,
|
||||
Err(error) => {
|
||||
endpoint.close().await;
|
||||
return Err(error);
|
||||
}
|
||||
};
|
||||
|
||||
// Provider events are where the sender sees remote readers. The core
|
||||
// uses them for send progress and for the current approval gate.
|
||||
@@ -160,6 +186,14 @@ impl CoreInner {
|
||||
limits.event_queue_capacity as usize,
|
||||
limits.max_events,
|
||||
));
|
||||
event_hub.emit_endpoint(
|
||||
"network",
|
||||
"relay-status",
|
||||
json!({
|
||||
"mode": relay_mode_label(relay_mode),
|
||||
"status": relay_status.as_str(),
|
||||
}),
|
||||
);
|
||||
for recovered in recovered_transfers {
|
||||
event_hub.emit_transfer(
|
||||
recovered.transfer_id,
|
||||
@@ -377,7 +411,7 @@ pub(crate) fn filter_peer_addr_for_relay_mode(
|
||||
) -> Result<EndpointAddr> {
|
||||
match relay_mode {
|
||||
CoreRelayMode::Automatic => Ok(addr.clone()),
|
||||
CoreRelayMode::Custom => {
|
||||
CoreRelayMode::StrictCustom | CoreRelayMode::CustomWithDirectFallback => {
|
||||
let mut filtered = EndpointAddr::new(addr.id);
|
||||
for ip_addr in addr.ip_addrs().copied() {
|
||||
filtered = filtered.with_ip_addr(ip_addr);
|
||||
@@ -396,6 +430,16 @@ pub(crate) fn filter_peer_addr_for_relay_mode(
|
||||
}
|
||||
Ok(filtered)
|
||||
}
|
||||
CoreRelayMode::LocalOnly => {
|
||||
let mut filtered = EndpointAddr::new(addr.id);
|
||||
for ip_addr in addr.ip_addrs().copied() {
|
||||
filtered = filtered.with_ip_addr(ip_addr);
|
||||
}
|
||||
if filtered.is_empty() {
|
||||
anyhow::bail!("invitation has no direct address allowed by local-only mode");
|
||||
}
|
||||
Ok(filtered)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -404,17 +448,16 @@ pub(crate) async fn wait_for_relay(
|
||||
relay_mode: CoreRelayMode,
|
||||
relay_urls: &[RelayUrl],
|
||||
timeout: Duration,
|
||||
) -> Result<()> {
|
||||
) -> Result<RelayStatus> {
|
||||
if relay_mode == CoreRelayMode::LocalOnly {
|
||||
return Ok(RelayStatus::Disabled);
|
||||
}
|
||||
if tokio::time::timeout(timeout, endpoint.online())
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
match relay_mode {
|
||||
CoreRelayMode::Automatic => anyhow::bail!(
|
||||
"timed out after {} seconds while connecting to automatic relays; verify network access and relay availability",
|
||||
timeout.as_secs_f32(),
|
||||
),
|
||||
CoreRelayMode::Custom => {
|
||||
CoreRelayMode::StrictCustom => {
|
||||
let configured_relays = relay_urls
|
||||
.iter()
|
||||
.map(ToString::to_string)
|
||||
@@ -425,9 +468,22 @@ pub(crate) async fn wait_for_relay(
|
||||
timeout.as_secs_f32(),
|
||||
);
|
||||
}
|
||||
CoreRelayMode::Automatic | CoreRelayMode::CustomWithDirectFallback => {
|
||||
return Ok(RelayStatus::Unreachable);
|
||||
}
|
||||
CoreRelayMode::LocalOnly => unreachable!(),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
Ok(RelayStatus::Connected)
|
||||
}
|
||||
|
||||
fn relay_mode_label(relay_mode: CoreRelayMode) -> &'static str {
|
||||
match relay_mode {
|
||||
CoreRelayMode::Automatic => "automatic",
|
||||
CoreRelayMode::StrictCustom => "strict-custom",
|
||||
CoreRelayMode::CustomWithDirectFallback => "custom-with-direct-fallback",
|
||||
CoreRelayMode::LocalOnly => "local-only",
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn share_tag_name(local_id: &str) -> String {
|
||||
|
||||
@@ -7,7 +7,7 @@ use crate::{
|
||||
default_core_network_config, CoreNetworkConfig, CoreRelayMode, MAX_CUSTOM_RELAYS,
|
||||
MAX_RELAY_URL_BYTES,
|
||||
},
|
||||
runtime::{filter_peer_addr_for_relay_mode, wait_for_relay},
|
||||
runtime::{filter_peer_addr_for_relay_mode, wait_for_relay, RelayStatus},
|
||||
};
|
||||
|
||||
#[test]
|
||||
@@ -32,17 +32,28 @@ fn relay_mode_and_url_list_must_be_consistent() {
|
||||
};
|
||||
assert!(automatic_with_url.validated_relay_urls().is_err());
|
||||
|
||||
let custom_without_url = CoreNetworkConfig {
|
||||
mode: CoreRelayMode::Custom,
|
||||
relay_urls: Vec::new(),
|
||||
for mode in [
|
||||
CoreRelayMode::StrictCustom,
|
||||
CoreRelayMode::CustomWithDirectFallback,
|
||||
] {
|
||||
let custom_without_url = CoreNetworkConfig {
|
||||
mode,
|
||||
relay_urls: Vec::new(),
|
||||
};
|
||||
assert!(custom_without_url.validated_relay_urls().is_err());
|
||||
}
|
||||
|
||||
let local_only_with_url = CoreNetworkConfig {
|
||||
mode: CoreRelayMode::LocalOnly,
|
||||
relay_urls: vec!["https://relay.example.com".to_string()],
|
||||
};
|
||||
assert!(custom_without_url.validated_relay_urls().is_err());
|
||||
assert!(local_only_with_url.validated_relay_urls().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn custom_relay_urls_allow_https_and_loopback_http() {
|
||||
let config = CoreNetworkConfig {
|
||||
mode: CoreRelayMode::Custom,
|
||||
mode: CoreRelayMode::StrictCustom,
|
||||
relay_urls: vec![
|
||||
"https://relay.example.com".to_string(),
|
||||
"http://localhost:3340".to_string(),
|
||||
@@ -68,7 +79,7 @@ fn custom_relay_urls_reject_unsafe_or_ambiguous_values() {
|
||||
" https://relay.example.com",
|
||||
] {
|
||||
let config = CoreNetworkConfig {
|
||||
mode: CoreRelayMode::Custom,
|
||||
mode: CoreRelayMode::StrictCustom,
|
||||
relay_urls: vec![value.to_string()],
|
||||
};
|
||||
assert!(
|
||||
@@ -81,7 +92,7 @@ fn custom_relay_urls_reject_unsafe_or_ambiguous_values() {
|
||||
#[test]
|
||||
fn custom_relay_urls_are_bounded_and_unique_after_normalization() {
|
||||
let duplicates = CoreNetworkConfig {
|
||||
mode: CoreRelayMode::Custom,
|
||||
mode: CoreRelayMode::StrictCustom,
|
||||
relay_urls: vec![
|
||||
"https://relay.example.com".to_string(),
|
||||
"https://relay.example.com/".to_string(),
|
||||
@@ -90,7 +101,7 @@ fn custom_relay_urls_are_bounded_and_unique_after_normalization() {
|
||||
assert!(duplicates.validated_relay_urls().is_err());
|
||||
|
||||
let too_many = CoreNetworkConfig {
|
||||
mode: CoreRelayMode::Custom,
|
||||
mode: CoreRelayMode::StrictCustom,
|
||||
relay_urls: (0..=MAX_CUSTOM_RELAYS)
|
||||
.map(|index| format!("https://relay-{index}.example.com"))
|
||||
.collect(),
|
||||
@@ -98,7 +109,7 @@ fn custom_relay_urls_are_bounded_and_unique_after_normalization() {
|
||||
assert!(too_many.validated_relay_urls().is_err());
|
||||
|
||||
let too_long = CoreNetworkConfig {
|
||||
mode: CoreRelayMode::Custom,
|
||||
mode: CoreRelayMode::StrictCustom,
|
||||
relay_urls: vec![format!(
|
||||
"https://{}.example.com",
|
||||
"a".repeat(MAX_RELAY_URL_BYTES)
|
||||
@@ -119,7 +130,7 @@ fn strict_custom_mode_filters_peer_relays_but_retains_direct_addresses() {
|
||||
|
||||
let filtered = filter_peer_addr_for_relay_mode(
|
||||
&addr,
|
||||
CoreRelayMode::Custom,
|
||||
CoreRelayMode::StrictCustom,
|
||||
std::slice::from_ref(&allowed),
|
||||
)
|
||||
.unwrap();
|
||||
@@ -140,14 +151,32 @@ fn strict_custom_mode_filters_peer_relays_but_retains_direct_addresses() {
|
||||
EndpointAddr::new(SecretKey::generate().public()).with_relay_url(disallowed);
|
||||
assert!(filter_peer_addr_for_relay_mode(
|
||||
&disallowed_only,
|
||||
CoreRelayMode::Custom,
|
||||
CoreRelayMode::StrictCustom,
|
||||
std::slice::from_ref(&allowed),
|
||||
)
|
||||
.is_err());
|
||||
|
||||
let fallback_filtered = filter_peer_addr_for_relay_mode(
|
||||
&addr,
|
||||
CoreRelayMode::CustomWithDirectFallback,
|
||||
std::slice::from_ref(&allowed),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(fallback_filtered, filtered);
|
||||
|
||||
let local_only = filter_peer_addr_for_relay_mode(&addr, CoreRelayMode::LocalOnly, &[]).unwrap();
|
||||
assert_eq!(local_only.relay_urls().count(), 0);
|
||||
assert_eq!(
|
||||
local_only.ip_addrs().copied().collect::<Vec<_>>(),
|
||||
vec![direct]
|
||||
);
|
||||
assert!(
|
||||
filter_peer_addr_for_relay_mode(&disallowed_only, CoreRelayMode::LocalOnly, &[]).is_err()
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn unreachable_relay_wait_is_bounded_and_actionable_for_each_mode() {
|
||||
async fn relay_wait_enforces_only_strict_custom_mode() {
|
||||
let relay_url: RelayUrl = "http://127.0.0.1:9".parse().unwrap();
|
||||
let endpoint = Endpoint::builder(presets::Minimal)
|
||||
.relay_mode(RelayMode::custom([relay_url.clone()]))
|
||||
@@ -158,7 +187,7 @@ async fn unreachable_relay_wait_is_bounded_and_actionable_for_each_mode() {
|
||||
|
||||
let error = wait_for_relay(
|
||||
&endpoint,
|
||||
CoreRelayMode::Custom,
|
||||
CoreRelayMode::StrictCustom,
|
||||
std::slice::from_ref(&relay_url),
|
||||
Duration::from_millis(50),
|
||||
)
|
||||
@@ -169,18 +198,35 @@ async fn unreachable_relay_wait_is_bounded_and_actionable_for_each_mode() {
|
||||
assert!(error.to_string().contains(relay_url.as_str()));
|
||||
assert!(error.to_string().contains("verify the URLs"));
|
||||
|
||||
let automatic_error = wait_for_relay(
|
||||
let automatic_status = wait_for_relay(
|
||||
&endpoint,
|
||||
CoreRelayMode::Automatic,
|
||||
&[],
|
||||
Duration::from_millis(50),
|
||||
)
|
||||
.await
|
||||
.unwrap_err();
|
||||
.unwrap();
|
||||
assert!(started.elapsed() < Duration::from_secs(1));
|
||||
assert!(automatic_error.to_string().contains("automatic relays"));
|
||||
assert!(automatic_error
|
||||
.to_string()
|
||||
.contains("verify network access"));
|
||||
assert_eq!(automatic_status, RelayStatus::Unreachable);
|
||||
|
||||
let fallback_status = wait_for_relay(
|
||||
&endpoint,
|
||||
CoreRelayMode::CustomWithDirectFallback,
|
||||
std::slice::from_ref(&relay_url),
|
||||
Duration::from_millis(50),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(fallback_status, RelayStatus::Unreachable);
|
||||
|
||||
let local_only_status = wait_for_relay(
|
||||
&endpoint,
|
||||
CoreRelayMode::LocalOnly,
|
||||
&[],
|
||||
Duration::from_millis(50),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(local_only_status, RelayStatus::Disabled);
|
||||
endpoint.close().await;
|
||||
}
|
||||
|
||||
@@ -120,14 +120,21 @@ fn saved_ticket_relay_profile_matching_is_mode_aware_and_order_insensitive() {
|
||||
assert!(ticket_matches_relay_profile(
|
||||
&custom_ticket,
|
||||
&limits,
|
||||
CoreRelayMode::Custom,
|
||||
CoreRelayMode::StrictCustom,
|
||||
&[relay_b.clone(), relay_a.clone()],
|
||||
)
|
||||
.unwrap());
|
||||
assert!(ticket_matches_relay_profile(
|
||||
&custom_ticket,
|
||||
&limits,
|
||||
CoreRelayMode::CustomWithDirectFallback,
|
||||
&[relay_b.clone(), relay_a.clone()],
|
||||
)
|
||||
.unwrap());
|
||||
assert!(!ticket_matches_relay_profile(
|
||||
&custom_ticket,
|
||||
&limits,
|
||||
CoreRelayMode::Custom,
|
||||
CoreRelayMode::StrictCustom,
|
||||
&[relay_a.clone(), relay_c],
|
||||
)
|
||||
.unwrap());
|
||||
@@ -145,10 +152,21 @@ fn saved_ticket_relay_profile_matching_is_mode_aware_and_order_insensitive() {
|
||||
assert!(!ticket_matches_relay_profile(
|
||||
&automatic_ticket,
|
||||
&limits,
|
||||
CoreRelayMode::Custom,
|
||||
CoreRelayMode::StrictCustom,
|
||||
&[relay_a],
|
||||
)
|
||||
.unwrap());
|
||||
assert!(ticket_matches_relay_profile(
|
||||
&automatic_ticket,
|
||||
&limits,
|
||||
CoreRelayMode::LocalOnly,
|
||||
&[],
|
||||
)
|
||||
.unwrap());
|
||||
assert!(
|
||||
!ticket_matches_relay_profile(&custom_ticket, &limits, CoreRelayMode::LocalOnly, &[],)
|
||||
.unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -142,7 +142,7 @@ pub(crate) fn parse_transfer_ticket_with_limits(
|
||||
Vec::new()
|
||||
} else {
|
||||
CoreNetworkConfig {
|
||||
mode: CoreRelayMode::Custom,
|
||||
mode: CoreRelayMode::StrictCustom,
|
||||
relay_urls: ticket.relay_urls,
|
||||
}
|
||||
.validated_relay_urls()
|
||||
@@ -171,7 +171,7 @@ pub(crate) fn ticket_matches_relay_profile(
|
||||
let parsed = parse_transfer_ticket_with_limits(value, limits)?;
|
||||
match relay_mode {
|
||||
CoreRelayMode::Automatic => Ok(parsed.advertised_custom_relay_urls.is_empty()),
|
||||
CoreRelayMode::Custom => {
|
||||
CoreRelayMode::StrictCustom | CoreRelayMode::CustomWithDirectFallback => {
|
||||
let advertised = parsed
|
||||
.advertised_custom_relay_urls
|
||||
.into_iter()
|
||||
@@ -179,6 +179,8 @@ pub(crate) fn ticket_matches_relay_profile(
|
||||
let configured = custom_relay_urls.iter().cloned().collect::<BTreeSet<_>>();
|
||||
Ok(advertised == configured)
|
||||
}
|
||||
CoreRelayMode::LocalOnly => Ok(parsed.advertised_custom_relay_urls.is_empty()
|
||||
&& parsed.blob_ticket.addr().relay_urls().next().is_none()),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,24 +11,64 @@ use vnidrop::{CoreNetworkConfig, CoreRelayMode};
|
||||
|
||||
fn custom_config(relay_urls: &[&str]) -> CoreNetworkConfig {
|
||||
CoreNetworkConfig {
|
||||
mode: CoreRelayMode::Custom,
|
||||
mode: CoreRelayMode::StrictCustom,
|
||||
relay_urls: relay_urls.iter().map(ToString::to_string).collect(),
|
||||
}
|
||||
}
|
||||
|
||||
fn local_only_config() -> CoreNetworkConfig {
|
||||
CoreNetworkConfig {
|
||||
mode: CoreRelayMode::LocalOnly,
|
||||
relay_urls: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn read_blob_ticket(ticket: &str) -> (Value, BlobTicket) {
|
||||
let encoded = ticket.strip_prefix("vnd1:").unwrap();
|
||||
let payload = BASE64URL_NOPAD.decode(encoded.as_bytes()).unwrap();
|
||||
let value: Value = serde_json::from_slice(&payload).unwrap();
|
||||
let blob_ticket = BlobTicket::from_str(value["blob_ticket"].as_str().unwrap()).unwrap();
|
||||
let (mut addr, hash, format) = blob_ticket.into_parts();
|
||||
for relay_url in value["relay_urls"].as_array().unwrap() {
|
||||
addr = addr.with_relay_url(relay_url.as_str().unwrap().parse().unwrap());
|
||||
if let Some(relay_urls) = value["relay_urls"].as_array() {
|
||||
for relay_url in relay_urls {
|
||||
addr = addr.with_relay_url(relay_url.as_str().unwrap().parse().unwrap());
|
||||
}
|
||||
}
|
||||
let blob_ticket = BlobTicket::new(addr, hash, format);
|
||||
(value, blob_ticket)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn local_only_mode_advertises_direct_addresses_and_transfers_on_lan() {
|
||||
let sender = TestNode::with_network_config(local_only_config());
|
||||
let receiver = TestNode::with_network_config(local_only_config());
|
||||
let source_dir = tempfile::tempdir().unwrap();
|
||||
let output_dir = tempfile::tempdir().unwrap();
|
||||
let source_path = source_dir.path().join("local-only.txt");
|
||||
std::fs::write(&source_path, b"direct on the local network").unwrap();
|
||||
|
||||
let share = share_path(&sender.core, &source_path, 402, "local-only.txt", false);
|
||||
let (ticket_value, blob_ticket) = read_blob_ticket(&share.ticket);
|
||||
assert!(ticket_value.get("relay_urls").is_none());
|
||||
assert_eq!(blob_ticket.addr().relay_urls().count(), 0);
|
||||
assert!(!sender.core.status().addr.contains("iroh.link"));
|
||||
|
||||
receive_with_response(
|
||||
&sender.core,
|
||||
share.transfer_id,
|
||||
receiver.core.arc(),
|
||||
share.ticket,
|
||||
output_dir.path(),
|
||||
true,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
std::fs::read(output_dir.path().join("local-only.txt")).unwrap(),
|
||||
b"direct on the local network"
|
||||
);
|
||||
}
|
||||
|
||||
fn with_relay_only_address(ticket: &str, relay_url: &str) -> String {
|
||||
let (mut value, blob_ticket) = read_blob_ticket(ticket);
|
||||
let relay_url: RelayUrl = relay_url.parse().unwrap();
|
||||
|
||||
@@ -151,7 +151,7 @@ fn persisted_share_is_revoked_when_restarted_with_a_different_relay_profile() {
|
||||
core_dir.path(),
|
||||
Arc::new(RecordingSink::default()),
|
||||
CoreNetworkConfig {
|
||||
mode: CoreRelayMode::Custom,
|
||||
mode: CoreRelayMode::StrictCustom,
|
||||
relay_urls: vec![relay_a.url.clone()],
|
||||
},
|
||||
);
|
||||
@@ -163,7 +163,7 @@ fn persisted_share_is_revoked_when_restarted_with_a_different_relay_profile() {
|
||||
core_dir.path(),
|
||||
Arc::new(RecordingSink::default()),
|
||||
CoreNetworkConfig {
|
||||
mode: CoreRelayMode::Custom,
|
||||
mode: CoreRelayMode::StrictCustom,
|
||||
relay_urls: vec![relay_b.url.clone()],
|
||||
},
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user