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}