scylla/policies/address_translator.rs
1use std::collections::HashMap;
2use std::net::SocketAddr;
3use std::str::FromStr as _;
4
5use async_trait::async_trait;
6use uuid::Uuid;
7
8use crate::errors::TranslationError;
9
10/// Data used to issue connections to a node that is possibly subject to address translation.
11///
12/// Built from `PeerEndpoint` if its `NodeAddr` variant implies address translation possibility.
13#[derive(Debug)]
14pub struct UntranslatedPeer<'a> {
15 pub(crate) host_id: Uuid,
16 pub(crate) untranslated_address: SocketAddr,
17 pub(crate) datacenter: Option<&'a str>,
18 pub(crate) rack: Option<&'a str>,
19}
20
21impl UntranslatedPeer<'_> {
22 /// The unique identifier of the node in the cluster.
23 #[inline]
24 pub fn host_id(&self) -> Uuid {
25 self.host_id
26 }
27
28 /// The address of the node in the cluster as broadcast by the node itself.
29 /// It may be subject to address translation.
30 #[inline]
31 pub fn untranslated_address(&self) -> SocketAddr {
32 self.untranslated_address
33 }
34
35 /// The datacenter the node resides in.
36 #[inline]
37 pub fn datacenter(&self) -> Option<&str> {
38 self.datacenter
39 }
40
41 /// The rack the node resides in.
42 #[inline]
43 pub fn rack(&self) -> Option<&str> {
44 self.rack
45 }
46}
47
48/// Translates IP addresses received from ScyllaDB nodes into locally reachable addresses.
49///
50/// The driver auto-detects new ScyllaDB nodes added to the cluster through server side pushed
51/// notifications and through checking the system tables. For each node, the address the driver
52/// receives corresponds to the address set as `rpc_address` in the node yaml file. In most
53/// cases, this is the correct address to use by the driver and that is what is used by default.
54/// However, sometimes the addresses received through this mechanism will either not be reachable
55/// directly by the driver or should not be the preferred address to use to reach the node (for
56/// instance, the `rpc_address` set on ScyllaDB nodes might be a private IP, but some clients
57/// may have to use a public IP, or pass by a router, e.g. through NAT, to reach that node).
58/// This interface allows to deal with such cases, by allowing to translate an address as sent
59/// by a ScyllaDB node to another address to be used by the driver for connection.
60///
61/// Please note that the "known nodes" addresses provided while creating
62/// the [`Session`](crate::client::session::Session) instance are not translated,
63/// only IP addresses retrieved from or sent by Cassandra nodes to the driver are.
64#[async_trait]
65pub trait AddressTranslator: Send + Sync {
66 async fn translate_address(
67 &self,
68 untranslated_peer: &UntranslatedPeer,
69 ) -> Result<SocketAddr, TranslationError>;
70}
71
72#[async_trait]
73impl AddressTranslator for HashMap<SocketAddr, SocketAddr> {
74 async fn translate_address(
75 &self,
76 untranslated_peer: &UntranslatedPeer,
77 ) -> Result<SocketAddr, TranslationError> {
78 match self.get(&untranslated_peer.untranslated_address()) {
79 Some(&translated_addr) => Ok(translated_addr),
80 None => Err(TranslationError::NoRuleForAddress(
81 untranslated_peer.untranslated_address(),
82 )),
83 }
84 }
85}
86
87#[async_trait]
88// Notice: this is inefficient, but what else can we do with such poor representation as str?
89// After all, the cluster size is small enough to make this irrelevant.
90impl AddressTranslator for HashMap<&'static str, &'static str> {
91 async fn translate_address(
92 &self,
93 untranslated_peer: &UntranslatedPeer,
94 ) -> Result<SocketAddr, TranslationError> {
95 for (&rule_addr_str, &translated_addr_str) in self.iter() {
96 if let Ok(rule_addr) = SocketAddr::from_str(rule_addr_str) {
97 if rule_addr == untranslated_peer.untranslated_address() {
98 return SocketAddr::from_str(translated_addr_str).map_err(|reason| {
99 TranslationError::InvalidAddressInRule {
100 translated_addr_str,
101 reason,
102 }
103 });
104 }
105 }
106 }
107 Err(TranslationError::NoRuleForAddress(
108 untranslated_peer.untranslated_address(),
109 ))
110 }
111}