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

Generated by: LCOV version 2.1-beta