fix public address check
This commit is contained in:
@@ -54,8 +54,8 @@ pub const IPADDR_TABLE_SIZE: usize = 1024;
|
||||
pub const IPADDR_MAX_INACTIVE_DURATION_US: TimestampDuration =
|
||||
TimestampDuration::new(300_000_000u64); // 5 minutes
|
||||
pub const NODE_CONTACT_METHOD_CACHE_SIZE: usize = 1024;
|
||||
pub const PUBLIC_ADDRESS_CHANGE_DETECTION_COUNT: usize = 3;
|
||||
pub const PUBLIC_ADDRESS_CHECK_CACHE_SIZE: usize = 8;
|
||||
pub const PUBLIC_ADDRESS_CHANGE_DETECTION_COUNT: usize = 5;
|
||||
pub const PUBLIC_ADDRESS_CHECK_CACHE_SIZE: usize = 10;
|
||||
pub const PUBLIC_ADDRESS_CHECK_TASK_INTERVAL_SECS: u32 = 60;
|
||||
pub const PUBLIC_ADDRESS_INCONSISTENCY_TIMEOUT_US: TimestampDuration =
|
||||
TimestampDuration::new(300_000_000u64); // 5 minutes
|
||||
@@ -1116,199 +1116,4 @@ impl NetworkManager {
|
||||
// Inform caller that we dealt with the envelope locally
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
// Determine if a local IP address has changed
|
||||
// this means we should restart the low level network and and recreate all of our dial info
|
||||
// Wait until we have received confirmation from N different peers
|
||||
pub fn report_local_network_socket_address(
|
||||
&self,
|
||||
_socket_address: SocketAddress,
|
||||
_connection_descriptor: ConnectionDescriptor,
|
||||
_reporting_peer: NodeRef,
|
||||
) {
|
||||
// XXX: Nothing here yet.
|
||||
}
|
||||
|
||||
// Determine if a global IP address has changed
|
||||
// this means we should recreate our public dial info if it is not static and rediscover it
|
||||
// Wait until we have received confirmation from N different peers
|
||||
pub fn report_public_internet_socket_address(
|
||||
&self,
|
||||
socket_address: SocketAddress, // the socket address as seen by the remote peer
|
||||
connection_descriptor: ConnectionDescriptor, // the connection descriptor used
|
||||
reporting_peer: NodeRef, // the peer's noderef reporting the socket address
|
||||
) {
|
||||
#[cfg(feature = "verbose-tracing")]
|
||||
debug!("report_global_socket_address\nsocket_address: {:#?}\nconnection_descriptor: {:#?}\nreporting_peer: {:#?}", socket_address, connection_descriptor, reporting_peer);
|
||||
|
||||
// Ignore these reports if we are currently detecting public dial info
|
||||
let net = self.net();
|
||||
if net.needs_public_dial_info_check() {
|
||||
return;
|
||||
}
|
||||
|
||||
let routing_table = self.routing_table();
|
||||
let (detect_address_changes, ip6_prefix_size) = self.with_config(|c| {
|
||||
(
|
||||
c.network.detect_address_changes,
|
||||
c.network.max_connections_per_ip6_prefix_size as usize,
|
||||
)
|
||||
});
|
||||
|
||||
// Get the ip(block) this report is coming from
|
||||
let ipblock = ip_to_ipblock(
|
||||
ip6_prefix_size,
|
||||
connection_descriptor.remote_address().to_ip_addr(),
|
||||
);
|
||||
|
||||
// Store the reported address if it isn't denylisted
|
||||
let key = PublicAddressCheckCacheKey(
|
||||
connection_descriptor.protocol_type(),
|
||||
connection_descriptor.address_type(),
|
||||
);
|
||||
|
||||
let mut inner = self.inner.lock();
|
||||
let inner = &mut *inner;
|
||||
|
||||
let pacc = inner
|
||||
.public_address_check_cache
|
||||
.entry(key)
|
||||
.or_insert_with(|| LruCache::new(PUBLIC_ADDRESS_CHECK_CACHE_SIZE));
|
||||
let pait = inner
|
||||
.public_address_inconsistencies_table
|
||||
.entry(key)
|
||||
.or_insert_with(|| HashMap::new());
|
||||
if pait.contains_key(&ipblock) {
|
||||
return;
|
||||
}
|
||||
pacc.insert(ipblock, socket_address, |_k, _v| {
|
||||
// do nothing on LRU evict
|
||||
});
|
||||
|
||||
// Determine if our external address has likely changed
|
||||
let mut bad_public_address_detection_punishment: Option<
|
||||
Box<dyn FnOnce() + Send + 'static>,
|
||||
> = None;
|
||||
let public_internet_network_class = routing_table
|
||||
.get_network_class(RoutingDomain::PublicInternet)
|
||||
.unwrap_or(NetworkClass::Invalid);
|
||||
let needs_public_address_detection =
|
||||
if matches!(public_internet_network_class, NetworkClass::InboundCapable) {
|
||||
// Get the dial info filter for this connection so we can check if we have any public dialinfo that may have changed
|
||||
let dial_info_filter = connection_descriptor.make_dial_info_filter();
|
||||
|
||||
// Get current external ip/port from registered global dialinfo
|
||||
let current_addresses: BTreeSet<SocketAddress> = routing_table
|
||||
.all_filtered_dial_info_details(
|
||||
RoutingDomain::PublicInternet.into(),
|
||||
&dial_info_filter,
|
||||
)
|
||||
.iter()
|
||||
.map(|did| did.dial_info.socket_address())
|
||||
.collect();
|
||||
|
||||
// If we are inbound capable, but start to see inconsistent socket addresses from multiple reporting peers
|
||||
// then we zap the network class and re-detect it
|
||||
let mut inconsistencies = Vec::new();
|
||||
|
||||
// Iteration goes from most recent to least recent node/address pair
|
||||
for (reporting_ip_block, a) in pacc {
|
||||
// If this address is not one of our current addresses (inconsistent)
|
||||
// and we haven't already denylisted the reporting source,
|
||||
if !current_addresses.contains(a) && !pait.contains_key(reporting_ip_block) {
|
||||
// Record the origin of the inconsistency
|
||||
inconsistencies.push(*reporting_ip_block);
|
||||
}
|
||||
}
|
||||
|
||||
// If we have enough inconsistencies to consider changing our public dial info,
|
||||
// add them to our denylist (throttling) and go ahead and check for new
|
||||
// public dialinfo
|
||||
let inconsistent = if inconsistencies.len() >= PUBLIC_ADDRESS_CHANGE_DETECTION_COUNT
|
||||
{
|
||||
let exp_ts = get_aligned_timestamp() + PUBLIC_ADDRESS_INCONSISTENCY_TIMEOUT_US;
|
||||
for i in &inconsistencies {
|
||||
pait.insert(*i, exp_ts);
|
||||
}
|
||||
|
||||
// Run this routine if the inconsistent nodes turn out to be lying
|
||||
let this = self.clone();
|
||||
bad_public_address_detection_punishment = Some(Box::new(move || {
|
||||
let mut inner = this.inner.lock();
|
||||
let pait = inner
|
||||
.public_address_inconsistencies_table
|
||||
.entry(key)
|
||||
.or_insert_with(|| HashMap::new());
|
||||
let exp_ts = get_aligned_timestamp()
|
||||
+ PUBLIC_ADDRESS_INCONSISTENCY_PUNISHMENT_TIMEOUT_US;
|
||||
for i in inconsistencies {
|
||||
pait.insert(i, exp_ts);
|
||||
}
|
||||
}));
|
||||
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
// // debug code
|
||||
// if inconsistent {
|
||||
// trace!("public_address_check_cache: {:#?}\ncurrent_addresses: {:#?}\ninconsistencies: {}", inner
|
||||
// .public_address_check_cache, current_addresses, inconsistencies);
|
||||
// }
|
||||
|
||||
inconsistent
|
||||
} else if matches!(public_internet_network_class, NetworkClass::OutboundOnly) {
|
||||
// If we are currently outbound only, we don't have any public dial info
|
||||
// but if we are starting to see consistent socket address from multiple reporting peers
|
||||
// then we may be become inbound capable, so zap the network class so we can re-detect it and any public dial info
|
||||
|
||||
let mut consistencies = 0;
|
||||
let mut consistent = false;
|
||||
let mut current_address = Option::<SocketAddress>::None;
|
||||
// Iteration goes from most recent to least recent node/address pair
|
||||
let pacc = inner
|
||||
.public_address_check_cache
|
||||
.entry(key)
|
||||
.or_insert_with(|| LruCache::new(PUBLIC_ADDRESS_CHECK_CACHE_SIZE));
|
||||
|
||||
for (_, a) in pacc {
|
||||
if let Some(current_address) = current_address {
|
||||
if current_address == *a {
|
||||
consistencies += 1;
|
||||
if consistencies >= PUBLIC_ADDRESS_CHANGE_DETECTION_COUNT {
|
||||
consistent = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
current_address = Some(*a);
|
||||
}
|
||||
}
|
||||
consistent
|
||||
} else {
|
||||
// If we are a webapp we never do this.
|
||||
// If we have invalid network class, then public address detection is already going to happen via the network_class_discovery task
|
||||
false
|
||||
};
|
||||
|
||||
if needs_public_address_detection {
|
||||
if detect_address_changes {
|
||||
// Reset the address check cache now so we can start detecting fresh
|
||||
info!("Public address has changed, detecting public dial info");
|
||||
|
||||
inner.public_address_check_cache.clear();
|
||||
|
||||
// Re-detect the public dialinfo
|
||||
net.set_needs_public_dial_info_check(bad_public_address_detection_punishment);
|
||||
} else {
|
||||
warn!("Public address may have changed. Restarting the server may be required.");
|
||||
warn!("report_global_socket_address\nsocket_address: {:#?}\nconnection_descriptor: {:#?}\nreporting_peer: {:#?}", socket_address, connection_descriptor, reporting_peer);
|
||||
warn!(
|
||||
"public_address_check_cache: {:#?}",
|
||||
inner.public_address_check_cache
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -79,13 +79,13 @@ impl RawUdpProtocolHandler {
|
||||
};
|
||||
|
||||
#[cfg(feature = "verbose-tracing")]
|
||||
tracing::Span::current().record("ret.len", &size);
|
||||
tracing::Span::current().record("ret.len", &message_len);
|
||||
#[cfg(feature = "verbose-tracing")]
|
||||
tracing::Span::current().record("ret.descriptor", &format!("{:?}", descriptor).as_str());
|
||||
Ok((message_len, descriptor))
|
||||
}
|
||||
|
||||
#[cfg_attr(feature="verbose-tracing", instrument(level = "trace", err, skip(self, data), fields(data.len = data.len(), ret.len, ret.descriptor)))]
|
||||
#[cfg_attr(feature="verbose-tracing", instrument(level = "trace", err, skip(self, data), fields(data.len = data.len(), ret.descriptor)))]
|
||||
pub async fn send_message(
|
||||
&self,
|
||||
data: Vec<u8>,
|
||||
@@ -133,8 +133,6 @@ impl RawUdpProtocolHandler {
|
||||
SocketAddress::from_socket_addr(local_socket_addr),
|
||||
);
|
||||
|
||||
#[cfg(feature = "verbose-tracing")]
|
||||
tracing::Span::current().record("ret.len", &len);
|
||||
#[cfg(feature = "verbose-tracing")]
|
||||
tracing::Span::current().record("ret.descriptor", &format!("{:?}", descriptor).as_str());
|
||||
Ok(NetworkResult::value(descriptor))
|
||||
|
@@ -24,4 +24,250 @@ impl NetworkManager {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Determine if a local IP address has changed
|
||||
// this means we should restart the low level network and and recreate all of our dial info
|
||||
// Wait until we have received confirmation from N different peers
|
||||
pub fn report_local_network_socket_address(
|
||||
&self,
|
||||
_socket_address: SocketAddress,
|
||||
_connection_descriptor: ConnectionDescriptor,
|
||||
_reporting_peer: NodeRef,
|
||||
) {
|
||||
// XXX: Nothing here yet.
|
||||
}
|
||||
|
||||
// Determine if a global IP address has changed
|
||||
// this means we should recreate our public dial info if it is not static and rediscover it
|
||||
// Wait until we have received confirmation from N different peers
|
||||
pub fn report_public_internet_socket_address(
|
||||
&self,
|
||||
socket_address: SocketAddress, // the socket address as seen by the remote peer
|
||||
connection_descriptor: ConnectionDescriptor, // the connection descriptor used
|
||||
reporting_peer: NodeRef, // the peer's noderef reporting the socket address
|
||||
) {
|
||||
#[cfg(feature = "network-result-extra")]
|
||||
debug!("report_global_socket_address\nsocket_address: {:#?}\nconnection_descriptor: {:#?}\nreporting_peer: {:#?}", socket_address, connection_descriptor, reporting_peer);
|
||||
|
||||
// Ignore these reports if we are currently detecting public dial info
|
||||
let net = self.net();
|
||||
if net.needs_public_dial_info_check() {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we are a webapp we should skip this completely
|
||||
// because we will never get inbound dialinfo directly on our public ip address
|
||||
// If we have an invalid network class, this is not necessary yet
|
||||
let routing_table = self.routing_table();
|
||||
let public_internet_network_class = routing_table
|
||||
.get_network_class(RoutingDomain::PublicInternet)
|
||||
.unwrap_or(NetworkClass::Invalid);
|
||||
if matches!(
|
||||
public_internet_network_class,
|
||||
NetworkClass::Invalid | NetworkClass::WebApp
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
let (detect_address_changes, ip6_prefix_size) = self.with_config(|c| {
|
||||
(
|
||||
c.network.detect_address_changes,
|
||||
c.network.max_connections_per_ip6_prefix_size as usize,
|
||||
)
|
||||
});
|
||||
|
||||
// Get the ip(block) this report is coming from
|
||||
let ipblock = ip_to_ipblock(
|
||||
ip6_prefix_size,
|
||||
connection_descriptor.remote_address().to_ip_addr(),
|
||||
);
|
||||
|
||||
// Reject public address reports from nodes that we know are behind symmetric nat or
|
||||
// nodes that must be using a relay for everything
|
||||
let Some(node_info) = reporting_peer.node_info(RoutingDomain::PublicInternet) else {
|
||||
return;
|
||||
};
|
||||
if node_info.network_class() != NetworkClass::InboundCapable {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the public address report is coming from a node/block that gives an 'inconsistent' location
|
||||
// meaning that the node may be not useful for public address detection
|
||||
// This is done on a per address/protocol basis
|
||||
|
||||
let mut inner = self.inner.lock();
|
||||
let inner = &mut *inner;
|
||||
|
||||
let addr_proto_type_key = PublicAddressCheckCacheKey(
|
||||
connection_descriptor.protocol_type(),
|
||||
connection_descriptor.address_type(),
|
||||
);
|
||||
if inner
|
||||
.public_address_inconsistencies_table
|
||||
.get(&addr_proto_type_key)
|
||||
.map(|pait| pait.contains_key(&ipblock))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Insert this new public address into the lru cache for the address check
|
||||
// if we've seen this address before, it brings it to the front
|
||||
let pacc = inner
|
||||
.public_address_check_cache
|
||||
.entry(addr_proto_type_key)
|
||||
.or_insert_with(|| LruCache::new(PUBLIC_ADDRESS_CHECK_CACHE_SIZE));
|
||||
pacc.insert(ipblock, socket_address, |_k, _v| {
|
||||
// do nothing on LRU evict
|
||||
});
|
||||
|
||||
// Determine if our external address has likely changed
|
||||
let mut bad_public_address_detection_punishment: Option<
|
||||
Box<dyn FnOnce() + Send + 'static>,
|
||||
> = None;
|
||||
|
||||
let needs_public_address_detection = if matches!(
|
||||
public_internet_network_class,
|
||||
NetworkClass::InboundCapable
|
||||
) {
|
||||
// Get the dial info filter for this connection so we can check if we have any public dialinfo that may have changed
|
||||
let dial_info_filter = connection_descriptor.make_dial_info_filter();
|
||||
|
||||
// Get current external ip/port from registered global dialinfo
|
||||
let current_addresses: BTreeSet<SocketAddress> = routing_table
|
||||
.all_filtered_dial_info_details(
|
||||
RoutingDomain::PublicInternet.into(),
|
||||
&dial_info_filter,
|
||||
)
|
||||
.iter()
|
||||
.map(|did| {
|
||||
// Strip port from direct and mapped addresses
|
||||
// as the incoming dialinfo may not match the outbound
|
||||
// connections' NAT mapping. In this case we only check for IP address changes.
|
||||
if did.class == DialInfoClass::Direct || did.class == DialInfoClass::Mapped {
|
||||
did.dial_info.socket_address().with_port(0)
|
||||
} else {
|
||||
did.dial_info.socket_address()
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
// If we are inbound capable, but start to see inconsistent socket addresses from multiple reporting peers
|
||||
// then we zap the network class and re-detect it
|
||||
|
||||
// Keep list of the origin ip blocks of inconsistent public address reports
|
||||
let mut inconsistencies = Vec::new();
|
||||
|
||||
// Iteration goes from most recent to least recent node/address pair
|
||||
for (reporting_ip_block, a) in pacc {
|
||||
// If this address is not one of our current addresses (inconsistent)
|
||||
// and we haven't already denylisted the reporting source,
|
||||
// Also check address with port zero in the even we are only checking changes to ip addresses
|
||||
if !current_addresses.contains(a)
|
||||
&& !current_addresses.contains(&a.with_port(0))
|
||||
&& !inner
|
||||
.public_address_inconsistencies_table
|
||||
.get(&addr_proto_type_key)
|
||||
.map(|pait| pait.contains_key(reporting_ip_block))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
// Record the origin of the inconsistency
|
||||
#[cfg(feature = "network-result-extra")]
|
||||
debug!("inconsistency added from {:?}: reported {:?} with current_addresses = {:?}", reporting_ip_block, a, current_addresses);
|
||||
|
||||
inconsistencies.push(*reporting_ip_block);
|
||||
}
|
||||
}
|
||||
|
||||
// If we have enough inconsistencies to consider changing our public dial info,
|
||||
// add them to our denylist (throttling) and go ahead and check for new
|
||||
// public dialinfo
|
||||
let inconsistent = if inconsistencies.len() >= PUBLIC_ADDRESS_CHANGE_DETECTION_COUNT {
|
||||
let exp_ts = get_aligned_timestamp() + PUBLIC_ADDRESS_INCONSISTENCY_TIMEOUT_US;
|
||||
let pait = inner
|
||||
.public_address_inconsistencies_table
|
||||
.entry(addr_proto_type_key)
|
||||
.or_insert_with(|| HashMap::new());
|
||||
for i in &inconsistencies {
|
||||
pait.insert(*i, exp_ts);
|
||||
}
|
||||
|
||||
// Run this routine if the inconsistent nodes turn out to be lying
|
||||
let this = self.clone();
|
||||
bad_public_address_detection_punishment = Some(Box::new(move || {
|
||||
let mut inner = this.inner.lock();
|
||||
let pait = inner
|
||||
.public_address_inconsistencies_table
|
||||
.entry(addr_proto_type_key)
|
||||
.or_insert_with(|| HashMap::new());
|
||||
let exp_ts = get_aligned_timestamp()
|
||||
+ PUBLIC_ADDRESS_INCONSISTENCY_PUNISHMENT_TIMEOUT_US;
|
||||
for i in inconsistencies {
|
||||
pait.insert(i, exp_ts);
|
||||
}
|
||||
}));
|
||||
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
// // debug code
|
||||
// if inconsistent {
|
||||
// trace!("public_address_check_cache: {:#?}\ncurrent_addresses: {:#?}\ninconsistencies: {}", inner
|
||||
// .public_address_check_cache, current_addresses, inconsistencies);
|
||||
// }
|
||||
|
||||
inconsistent
|
||||
} else if matches!(public_internet_network_class, NetworkClass::OutboundOnly) {
|
||||
// If we are currently outbound only, we don't have any public dial info
|
||||
// but if we are starting to see consistent socket address from multiple reporting peers
|
||||
// then we may be become inbound capable, so zap the network class so we can re-detect it and any public dial info
|
||||
|
||||
let mut consistencies = 0;
|
||||
let mut consistent = false;
|
||||
let mut current_address = Option::<SocketAddress>::None;
|
||||
|
||||
// Iteration goes from most recent to least recent node/address pair
|
||||
for (_, a) in pacc {
|
||||
if let Some(current_address) = current_address {
|
||||
if current_address == *a {
|
||||
consistencies += 1;
|
||||
if consistencies >= PUBLIC_ADDRESS_CHANGE_DETECTION_COUNT {
|
||||
consistent = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
current_address = Some(*a);
|
||||
}
|
||||
}
|
||||
consistent
|
||||
} else {
|
||||
// If we are a webapp we never do this.
|
||||
// If we have invalid network class, then public address detection is already going to happen via the network_class_discovery task
|
||||
|
||||
// we should have checked for this condition earlier at the top of this function
|
||||
unreachable!();
|
||||
};
|
||||
|
||||
if needs_public_address_detection {
|
||||
if detect_address_changes {
|
||||
// Reset the address check cache now so we can start detecting fresh
|
||||
info!("Public address has changed, detecting public dial info");
|
||||
|
||||
inner.public_address_check_cache.clear();
|
||||
|
||||
// Re-detect the public dialinfo
|
||||
net.set_needs_public_dial_info_check(bad_public_address_detection_punishment);
|
||||
} else {
|
||||
warn!("Public address may have changed. Restarting the server may be required.");
|
||||
warn!("report_global_socket_address\nsocket_address: {:#?}\nconnection_descriptor: {:#?}\nreporting_peer: {:#?}", socket_address, connection_descriptor, reporting_peer);
|
||||
warn!(
|
||||
"public_address_check_cache: {:#?}",
|
||||
inner.public_address_check_cache
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -33,6 +33,11 @@ impl SocketAddress {
|
||||
pub fn set_port(&mut self, port: u16) {
|
||||
self.port = port
|
||||
}
|
||||
pub fn with_port(&self, port: u16) -> Self {
|
||||
let mut sa = self.clone();
|
||||
sa.port = port;
|
||||
sa
|
||||
}
|
||||
pub fn to_canonical(&self) -> SocketAddress {
|
||||
SocketAddress {
|
||||
address: self.address.to_canonical(),
|
||||
|
Reference in New Issue
Block a user