LCOV - code coverage report
Current view: top level - proxy/src/auth - credentials.rs (source / functions) Coverage Total Hit
Test: ccf45ed1c149555259baec52d6229a81013dcd6a.info Lines: 96.0 % 376 361
Test Date: 2024-08-21 17:32:46 Functions: 80.5 % 41 33

            Line data    Source code
       1              : //! User credentials used in authentication.
       2              : 
       3              : use crate::{
       4              :     auth::password_hack::parse_endpoint_param,
       5              :     context::RequestMonitoring,
       6              :     error::{ReportableError, UserFacingError},
       7              :     metrics::{Metrics, SniKind},
       8              :     proxy::NeonOptions,
       9              :     serverless::SERVERLESS_DRIVER_SNI,
      10              :     EndpointId, RoleName,
      11              : };
      12              : use itertools::Itertools;
      13              : use pq_proto::StartupMessageParams;
      14              : use std::{collections::HashSet, net::IpAddr, str::FromStr};
      15              : use thiserror::Error;
      16              : use tracing::{info, warn};
      17              : 
      18            0 : #[derive(Debug, Error, PartialEq, Eq, Clone)]
      19              : pub enum ComputeUserInfoParseError {
      20              :     #[error("Parameter '{0}' is missing in startup packet.")]
      21              :     MissingKey(&'static str),
      22              : 
      23              :     #[error(
      24              :         "Inconsistent project name inferred from \
      25              :          SNI ('{}') and project option ('{}').",
      26              :         .domain, .option,
      27              :     )]
      28              :     InconsistentProjectNames {
      29              :         domain: EndpointId,
      30              :         option: EndpointId,
      31              :     },
      32              : 
      33              :     #[error(
      34              :         "Common name inferred from SNI ('{}') is not known",
      35              :         .cn,
      36              :     )]
      37              :     UnknownCommonName { cn: String },
      38              : 
      39              :     #[error("Project name ('{0}') must contain only alphanumeric characters and hyphen.")]
      40              :     MalformedProjectName(EndpointId),
      41              : }
      42              : 
      43              : impl UserFacingError for ComputeUserInfoParseError {}
      44              : 
      45              : impl ReportableError for ComputeUserInfoParseError {
      46            0 :     fn get_error_kind(&self) -> crate::error::ErrorKind {
      47            0 :         crate::error::ErrorKind::User
      48            0 :     }
      49              : }
      50              : 
      51              : /// Various client credentials which we use for authentication.
      52              : /// Note that we don't store any kind of client key or password here.
      53              : #[derive(Debug, Clone, PartialEq, Eq)]
      54              : pub struct ComputeUserInfoMaybeEndpoint {
      55              :     pub user: RoleName,
      56              :     pub endpoint_id: Option<EndpointId>,
      57              :     pub options: NeonOptions,
      58              : }
      59              : 
      60              : impl ComputeUserInfoMaybeEndpoint {
      61              :     #[inline]
      62            0 :     pub fn endpoint(&self) -> Option<&str> {
      63            0 :         self.endpoint_id.as_deref()
      64            0 :     }
      65              : }
      66              : 
      67           54 : pub fn endpoint_sni(
      68           54 :     sni: &str,
      69           54 :     common_names: &HashSet<String>,
      70           54 : ) -> Result<Option<EndpointId>, ComputeUserInfoParseError> {
      71           54 :     let Some((subdomain, common_name)) = sni.split_once('.') else {
      72            0 :         return Err(ComputeUserInfoParseError::UnknownCommonName { cn: sni.into() });
      73              :     };
      74           54 :     if !common_names.contains(common_name) {
      75            2 :         return Err(ComputeUserInfoParseError::UnknownCommonName {
      76            2 :             cn: common_name.into(),
      77            2 :         });
      78           52 :     }
      79           52 :     if subdomain == SERVERLESS_DRIVER_SNI {
      80            0 :         return Ok(None);
      81           52 :     }
      82           52 :     Ok(Some(EndpointId::from(subdomain)))
      83           54 : }
      84              : 
      85              : impl ComputeUserInfoMaybeEndpoint {
      86           26 :     pub fn parse(
      87           26 :         ctx: &RequestMonitoring,
      88           26 :         params: &StartupMessageParams,
      89           26 :         sni: Option<&str>,
      90           26 :         common_names: Option<&HashSet<String>>,
      91           26 :     ) -> Result<Self, ComputeUserInfoParseError> {
      92           26 :         // Some parameters are stored in the startup message.
      93           26 :         let get_param = |key| {
      94           26 :             params
      95           26 :                 .get(key)
      96           26 :                 .ok_or(ComputeUserInfoParseError::MissingKey(key))
      97           26 :         };
      98           26 :         let user: RoleName = get_param("user")?.into();
      99           26 : 
     100           26 :         // Project name might be passed via PG's command-line options.
     101           26 :         let endpoint_option = params
     102           26 :             .options_raw()
     103           26 :             .and_then(|options| {
     104           14 :                 // We support both `project` (deprecated) and `endpoint` options for backward compatibility.
     105           14 :                 // However, if both are present, we don't exactly know which one to use.
     106           14 :                 // Therefore we require that only one of them is present.
     107           14 :                 options
     108           14 :                     .filter_map(parse_endpoint_param)
     109           14 :                     .at_most_one()
     110           14 :                     .ok()?
     111           26 :             })
     112           26 :             .map(|name| name.into());
     113              : 
     114           26 :         let endpoint_from_domain = if let Some(sni_str) = sni {
     115           14 :             if let Some(cn) = common_names {
     116           14 :                 endpoint_sni(sni_str, cn)?
     117              :             } else {
     118            0 :                 None
     119              :             }
     120              :         } else {
     121           12 :             None
     122              :         };
     123              : 
     124           24 :         let endpoint = match (endpoint_option, endpoint_from_domain) {
     125              :             // Invariant: if we have both project name variants, they should match.
     126            4 :             (Some(option), Some(domain)) if option != domain => {
     127            2 :                 Some(Err(ComputeUserInfoParseError::InconsistentProjectNames {
     128            2 :                     domain,
     129            2 :                     option,
     130            2 :                 }))
     131              :             }
     132              :             // Invariant: project name may not contain certain characters.
     133           22 :             (a, b) => a.or(b).map(|name| match project_name_valid(name.as_ref()) {
     134            0 :                 false => Err(ComputeUserInfoParseError::MalformedProjectName(name)),
     135           14 :                 true => Ok(name),
     136           22 :             }),
     137              :         }
     138           24 :         .transpose()?;
     139              : 
     140           22 :         if let Some(ep) = &endpoint {
     141           14 :             ctx.set_endpoint_id(ep.clone());
     142           14 :         }
     143              : 
     144           22 :         let metrics = Metrics::get();
     145           22 :         info!(%user, "credentials");
     146           22 :         if sni.is_some() {
     147           10 :             info!("Connection with sni");
     148           10 :             metrics.proxy.accepted_connections_by_sni.inc(SniKind::Sni);
     149           12 :         } else if endpoint.is_some() {
     150            4 :             metrics
     151            4 :                 .proxy
     152            4 :                 .accepted_connections_by_sni
     153            4 :                 .inc(SniKind::NoSni);
     154            4 :             info!("Connection without sni");
     155              :         } else {
     156            8 :             metrics
     157            8 :                 .proxy
     158            8 :                 .accepted_connections_by_sni
     159            8 :                 .inc(SniKind::PasswordHack);
     160            8 :             info!("Connection with password hack");
     161              :         }
     162              : 
     163           22 :         let options = NeonOptions::parse_params(params);
     164           22 : 
     165           22 :         Ok(Self {
     166           22 :             user,
     167           22 :             endpoint_id: endpoint,
     168           22 :             options,
     169           22 :         })
     170           26 :     }
     171              : }
     172              : 
     173           14 : pub fn check_peer_addr_is_in_list(peer_addr: &IpAddr, ip_list: &[IpPattern]) -> bool {
     174           14 :     ip_list.is_empty() || ip_list.iter().any(|pattern| check_ip(peer_addr, pattern))
     175           14 : }
     176              : 
     177              : #[derive(Debug, Clone, Eq, PartialEq)]
     178              : pub enum IpPattern {
     179              :     Subnet(ipnet::IpNet),
     180              :     Range(IpAddr, IpAddr),
     181              :     Single(IpAddr),
     182              :     None,
     183              : }
     184              : 
     185              : impl<'de> serde::de::Deserialize<'de> for IpPattern {
     186           12 :     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
     187           12 :     where
     188           12 :         D: serde::Deserializer<'de>,
     189           12 :     {
     190           12 :         struct StrVisitor;
     191           12 :         impl<'de> serde::de::Visitor<'de> for StrVisitor {
     192           12 :             type Value = IpPattern;
     193           12 : 
     194           12 :             fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
     195            0 :                 write!(formatter, "comma separated list with ip address, ip address range, or ip address subnet mask")
     196            0 :             }
     197           12 : 
     198           12 :             fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
     199           12 :             where
     200           12 :                 E: serde::de::Error,
     201           12 :             {
     202           12 :                 Ok(parse_ip_pattern(v).unwrap_or_else(|e| {
     203            2 :                     warn!("Cannot parse ip pattern {v}: {e}");
     204           12 :                     IpPattern::None
     205           12 :                 }))
     206           12 :             }
     207           12 :         }
     208           12 :         deserializer.deserialize_str(StrVisitor)
     209           12 :     }
     210              : }
     211              : 
     212              : impl FromStr for IpPattern {
     213              :     type Err = anyhow::Error;
     214              : 
     215           12 :     fn from_str(s: &str) -> Result<Self, Self::Err> {
     216           12 :         parse_ip_pattern(s)
     217           12 :     }
     218              : }
     219              : 
     220           40 : fn parse_ip_pattern(pattern: &str) -> anyhow::Result<IpPattern> {
     221           40 :     if pattern.contains('/') {
     222            4 :         let subnet: ipnet::IpNet = pattern.parse()?;
     223            2 :         return Ok(IpPattern::Subnet(subnet));
     224           36 :     }
     225           36 :     if let Some((start, end)) = pattern.split_once('-') {
     226            6 :         let start: IpAddr = start.parse()?;
     227            4 :         let end: IpAddr = end.parse()?;
     228            2 :         return Ok(IpPattern::Range(start, end));
     229           30 :     }
     230           30 :     let addr: IpAddr = pattern.parse()?;
     231           24 :     Ok(IpPattern::Single(addr))
     232           40 : }
     233              : 
     234           28 : fn check_ip(ip: &IpAddr, pattern: &IpPattern) -> bool {
     235           28 :     match pattern {
     236            6 :         IpPattern::Subnet(subnet) => subnet.contains(ip),
     237           10 :         IpPattern::Range(start, end) => start <= ip && ip <= end,
     238           10 :         IpPattern::Single(addr) => addr == ip,
     239            2 :         IpPattern::None => false,
     240              :     }
     241           28 : }
     242              : 
     243           14 : fn project_name_valid(name: &str) -> bool {
     244           46 :     name.chars().all(|c| c.is_alphanumeric() || c == '-')
     245           14 : }
     246              : 
     247              : #[cfg(test)]
     248              : mod tests {
     249              :     use super::*;
     250              :     use serde_json::json;
     251              :     use ComputeUserInfoParseError::*;
     252              : 
     253              :     #[test]
     254            2 :     fn parse_bare_minimum() -> anyhow::Result<()> {
     255            2 :         // According to postgresql, only `user` should be required.
     256            2 :         let options = StartupMessageParams::new([("user", "john_doe")]);
     257            2 :         let ctx = RequestMonitoring::test();
     258            2 :         let user_info = ComputeUserInfoMaybeEndpoint::parse(&ctx, &options, None, None)?;
     259            2 :         assert_eq!(user_info.user, "john_doe");
     260            2 :         assert_eq!(user_info.endpoint_id, None);
     261              : 
     262            2 :         Ok(())
     263            2 :     }
     264              : 
     265              :     #[test]
     266            2 :     fn parse_excessive() -> anyhow::Result<()> {
     267            2 :         let options = StartupMessageParams::new([
     268            2 :             ("user", "john_doe"),
     269            2 :             ("database", "world"), // should be ignored
     270            2 :             ("foo", "bar"),        // should be ignored
     271            2 :         ]);
     272            2 :         let ctx = RequestMonitoring::test();
     273            2 :         let user_info = ComputeUserInfoMaybeEndpoint::parse(&ctx, &options, None, None)?;
     274            2 :         assert_eq!(user_info.user, "john_doe");
     275            2 :         assert_eq!(user_info.endpoint_id, None);
     276              : 
     277            2 :         Ok(())
     278            2 :     }
     279              : 
     280              :     #[test]
     281            2 :     fn parse_project_from_sni() -> anyhow::Result<()> {
     282            2 :         let options = StartupMessageParams::new([("user", "john_doe")]);
     283            2 : 
     284            2 :         let sni = Some("foo.localhost");
     285            2 :         let common_names = Some(["localhost".into()].into());
     286            2 : 
     287            2 :         let ctx = RequestMonitoring::test();
     288            2 :         let user_info =
     289            2 :             ComputeUserInfoMaybeEndpoint::parse(&ctx, &options, sni, common_names.as_ref())?;
     290            2 :         assert_eq!(user_info.user, "john_doe");
     291            2 :         assert_eq!(user_info.endpoint_id.as_deref(), Some("foo"));
     292            2 :         assert_eq!(user_info.options.get_cache_key("foo"), "foo");
     293              : 
     294            2 :         Ok(())
     295            2 :     }
     296              : 
     297              :     #[test]
     298            2 :     fn parse_project_from_options() -> anyhow::Result<()> {
     299            2 :         let options = StartupMessageParams::new([
     300            2 :             ("user", "john_doe"),
     301            2 :             ("options", "-ckey=1 project=bar -c geqo=off"),
     302            2 :         ]);
     303            2 : 
     304            2 :         let ctx = RequestMonitoring::test();
     305            2 :         let user_info = ComputeUserInfoMaybeEndpoint::parse(&ctx, &options, None, None)?;
     306            2 :         assert_eq!(user_info.user, "john_doe");
     307            2 :         assert_eq!(user_info.endpoint_id.as_deref(), Some("bar"));
     308              : 
     309            2 :         Ok(())
     310            2 :     }
     311              : 
     312              :     #[test]
     313            2 :     fn parse_endpoint_from_options() -> anyhow::Result<()> {
     314            2 :         let options = StartupMessageParams::new([
     315            2 :             ("user", "john_doe"),
     316            2 :             ("options", "-ckey=1 endpoint=bar -c geqo=off"),
     317            2 :         ]);
     318            2 : 
     319            2 :         let ctx = RequestMonitoring::test();
     320            2 :         let user_info = ComputeUserInfoMaybeEndpoint::parse(&ctx, &options, None, None)?;
     321            2 :         assert_eq!(user_info.user, "john_doe");
     322            2 :         assert_eq!(user_info.endpoint_id.as_deref(), Some("bar"));
     323              : 
     324            2 :         Ok(())
     325            2 :     }
     326              : 
     327              :     #[test]
     328            2 :     fn parse_three_endpoints_from_options() -> anyhow::Result<()> {
     329            2 :         let options = StartupMessageParams::new([
     330            2 :             ("user", "john_doe"),
     331            2 :             (
     332            2 :                 "options",
     333            2 :                 "-ckey=1 endpoint=one endpoint=two endpoint=three -c geqo=off",
     334            2 :             ),
     335            2 :         ]);
     336            2 : 
     337            2 :         let ctx = RequestMonitoring::test();
     338            2 :         let user_info = ComputeUserInfoMaybeEndpoint::parse(&ctx, &options, None, None)?;
     339            2 :         assert_eq!(user_info.user, "john_doe");
     340            2 :         assert!(user_info.endpoint_id.is_none());
     341              : 
     342            2 :         Ok(())
     343            2 :     }
     344              : 
     345              :     #[test]
     346            2 :     fn parse_when_endpoint_and_project_are_in_options() -> anyhow::Result<()> {
     347            2 :         let options = StartupMessageParams::new([
     348            2 :             ("user", "john_doe"),
     349            2 :             ("options", "-ckey=1 endpoint=bar project=foo -c geqo=off"),
     350            2 :         ]);
     351            2 : 
     352            2 :         let ctx = RequestMonitoring::test();
     353            2 :         let user_info = ComputeUserInfoMaybeEndpoint::parse(&ctx, &options, None, None)?;
     354            2 :         assert_eq!(user_info.user, "john_doe");
     355            2 :         assert!(user_info.endpoint_id.is_none());
     356              : 
     357            2 :         Ok(())
     358            2 :     }
     359              : 
     360              :     #[test]
     361            2 :     fn parse_projects_identical() -> anyhow::Result<()> {
     362            2 :         let options = StartupMessageParams::new([("user", "john_doe"), ("options", "project=baz")]);
     363            2 : 
     364            2 :         let sni = Some("baz.localhost");
     365            2 :         let common_names = Some(["localhost".into()].into());
     366            2 : 
     367            2 :         let ctx = RequestMonitoring::test();
     368            2 :         let user_info =
     369            2 :             ComputeUserInfoMaybeEndpoint::parse(&ctx, &options, sni, common_names.as_ref())?;
     370            2 :         assert_eq!(user_info.user, "john_doe");
     371            2 :         assert_eq!(user_info.endpoint_id.as_deref(), Some("baz"));
     372              : 
     373            2 :         Ok(())
     374            2 :     }
     375              : 
     376              :     #[test]
     377            2 :     fn parse_multi_common_names() -> anyhow::Result<()> {
     378            2 :         let options = StartupMessageParams::new([("user", "john_doe")]);
     379            2 : 
     380            2 :         let common_names = Some(["a.com".into(), "b.com".into()].into());
     381            2 :         let sni = Some("p1.a.com");
     382            2 :         let ctx = RequestMonitoring::test();
     383            2 :         let user_info =
     384            2 :             ComputeUserInfoMaybeEndpoint::parse(&ctx, &options, sni, common_names.as_ref())?;
     385            2 :         assert_eq!(user_info.endpoint_id.as_deref(), Some("p1"));
     386              : 
     387            2 :         let common_names = Some(["a.com".into(), "b.com".into()].into());
     388            2 :         let sni = Some("p1.b.com");
     389            2 :         let ctx = RequestMonitoring::test();
     390            2 :         let user_info =
     391            2 :             ComputeUserInfoMaybeEndpoint::parse(&ctx, &options, sni, common_names.as_ref())?;
     392            2 :         assert_eq!(user_info.endpoint_id.as_deref(), Some("p1"));
     393              : 
     394            2 :         Ok(())
     395            2 :     }
     396              : 
     397              :     #[test]
     398            2 :     fn parse_projects_different() {
     399            2 :         let options =
     400            2 :             StartupMessageParams::new([("user", "john_doe"), ("options", "project=first")]);
     401            2 : 
     402            2 :         let sni = Some("second.localhost");
     403            2 :         let common_names = Some(["localhost".into()].into());
     404            2 : 
     405            2 :         let ctx = RequestMonitoring::test();
     406            2 :         let err = ComputeUserInfoMaybeEndpoint::parse(&ctx, &options, sni, common_names.as_ref())
     407            2 :             .expect_err("should fail");
     408            2 :         match err {
     409            2 :             InconsistentProjectNames { domain, option } => {
     410            2 :                 assert_eq!(option, "first");
     411            2 :                 assert_eq!(domain, "second");
     412              :             }
     413            0 :             _ => panic!("bad error: {err:?}"),
     414              :         }
     415            2 :     }
     416              : 
     417              :     #[test]
     418            2 :     fn parse_inconsistent_sni() {
     419            2 :         let options = StartupMessageParams::new([("user", "john_doe")]);
     420            2 : 
     421            2 :         let sni = Some("project.localhost");
     422            2 :         let common_names = Some(["example.com".into()].into());
     423            2 : 
     424            2 :         let ctx = RequestMonitoring::test();
     425            2 :         let err = ComputeUserInfoMaybeEndpoint::parse(&ctx, &options, sni, common_names.as_ref())
     426            2 :             .expect_err("should fail");
     427            2 :         match err {
     428            2 :             UnknownCommonName { cn } => {
     429            2 :                 assert_eq!(cn, "localhost");
     430              :             }
     431            0 :             _ => panic!("bad error: {err:?}"),
     432              :         }
     433            2 :     }
     434              : 
     435              :     #[test]
     436            2 :     fn parse_neon_options() -> anyhow::Result<()> {
     437            2 :         let options = StartupMessageParams::new([
     438            2 :             ("user", "john_doe"),
     439            2 :             ("options", "neon_lsn:0/2 neon_endpoint_type:read_write"),
     440            2 :         ]);
     441            2 : 
     442            2 :         let sni = Some("project.localhost");
     443            2 :         let common_names = Some(["localhost".into()].into());
     444            2 :         let ctx = RequestMonitoring::test();
     445            2 :         let user_info =
     446            2 :             ComputeUserInfoMaybeEndpoint::parse(&ctx, &options, sni, common_names.as_ref())?;
     447            2 :         assert_eq!(user_info.endpoint_id.as_deref(), Some("project"));
     448            2 :         assert_eq!(
     449            2 :             user_info.options.get_cache_key("project"),
     450            2 :             "project endpoint_type:read_write lsn:0/2"
     451            2 :         );
     452              : 
     453            2 :         Ok(())
     454            2 :     }
     455              : 
     456              :     #[test]
     457            2 :     fn test_check_peer_addr_is_in_list() {
     458            8 :         fn check(v: serde_json::Value) -> bool {
     459            8 :             let peer_addr = IpAddr::from([127, 0, 0, 1]);
     460            8 :             let ip_list: Vec<IpPattern> = serde_json::from_value(v).unwrap();
     461            8 :             check_peer_addr_is_in_list(&peer_addr, &ip_list)
     462            8 :         }
     463            2 : 
     464            2 :         assert!(check(json!([])));
     465            2 :         assert!(check(json!(["127.0.0.1"])));
     466            2 :         assert!(!check(json!(["8.8.8.8"])));
     467              :         // If there is an incorrect address, it will be skipped.
     468            2 :         assert!(check(json!(["88.8.8", "127.0.0.1"])));
     469            2 :     }
     470              :     #[test]
     471            2 :     fn test_parse_ip_v4() -> anyhow::Result<()> {
     472            2 :         let peer_addr = IpAddr::from([127, 0, 0, 1]);
     473              :         // Ok
     474            2 :         assert_eq!(parse_ip_pattern("127.0.0.1")?, IpPattern::Single(peer_addr));
     475            2 :         assert_eq!(
     476            2 :             parse_ip_pattern("127.0.0.1/31")?,
     477            2 :             IpPattern::Subnet(ipnet::IpNet::new(peer_addr, 31)?)
     478              :         );
     479            2 :         assert_eq!(
     480            2 :             parse_ip_pattern("0.0.0.0-200.0.1.2")?,
     481            2 :             IpPattern::Range(IpAddr::from([0, 0, 0, 0]), IpAddr::from([200, 0, 1, 2]))
     482              :         );
     483              : 
     484              :         // Error
     485            2 :         assert!(parse_ip_pattern("300.0.1.2").is_err());
     486            2 :         assert!(parse_ip_pattern("30.1.2").is_err());
     487            2 :         assert!(parse_ip_pattern("127.0.0.1/33").is_err());
     488            2 :         assert!(parse_ip_pattern("127.0.0.1-127.0.3").is_err());
     489            2 :         assert!(parse_ip_pattern("1234.0.0.1-127.0.3.0").is_err());
     490            2 :         Ok(())
     491            2 :     }
     492              : 
     493              :     #[test]
     494            2 :     fn test_check_ipv4() -> anyhow::Result<()> {
     495            2 :         let peer_addr = IpAddr::from([127, 0, 0, 1]);
     496            2 :         let peer_addr_next = IpAddr::from([127, 0, 0, 2]);
     497            2 :         let peer_addr_prev = IpAddr::from([127, 0, 0, 0]);
     498            2 :         // Success
     499            2 :         assert!(check_ip(&peer_addr, &IpPattern::Single(peer_addr)));
     500            2 :         assert!(check_ip(
     501            2 :             &peer_addr,
     502            2 :             &IpPattern::Subnet(ipnet::IpNet::new(peer_addr_prev, 31)?)
     503              :         ));
     504            2 :         assert!(check_ip(
     505            2 :             &peer_addr,
     506            2 :             &IpPattern::Subnet(ipnet::IpNet::new(peer_addr_next, 30)?)
     507              :         ));
     508            2 :         assert!(check_ip(
     509            2 :             &peer_addr,
     510            2 :             &IpPattern::Range(IpAddr::from([0, 0, 0, 0]), IpAddr::from([200, 0, 1, 2]))
     511            2 :         ));
     512            2 :         assert!(check_ip(
     513            2 :             &peer_addr,
     514            2 :             &IpPattern::Range(peer_addr, peer_addr)
     515            2 :         ));
     516              : 
     517              :         // Not success
     518            2 :         assert!(!check_ip(&peer_addr, &IpPattern::Single(peer_addr_prev)));
     519            2 :         assert!(!check_ip(
     520            2 :             &peer_addr,
     521            2 :             &IpPattern::Subnet(ipnet::IpNet::new(peer_addr_next, 31)?)
     522              :         ));
     523            2 :         assert!(!check_ip(
     524            2 :             &peer_addr,
     525            2 :             &IpPattern::Range(IpAddr::from([0, 0, 0, 0]), peer_addr_prev)
     526            2 :         ));
     527            2 :         assert!(!check_ip(
     528            2 :             &peer_addr,
     529            2 :             &IpPattern::Range(peer_addr_next, IpAddr::from([128, 0, 0, 0]))
     530            2 :         ));
     531              :         // There is no check that for range start <= end. But it's fine as long as for all this cases the result is false.
     532            2 :         assert!(!check_ip(
     533            2 :             &peer_addr,
     534            2 :             &IpPattern::Range(peer_addr, peer_addr_prev)
     535            2 :         ));
     536            2 :         Ok(())
     537            2 :     }
     538              : }
        

Generated by: LCOV version 2.1-beta