amazon_cloudfront_client_routing_lib/
ip.rs

1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
5
6enum SubnetMask {
7    Ipv4 = 24,
8    Ipv6 = 48,
9}
10
11/// Struct containing 3 values needed for encoding: `client_subnet`,
12/// `subnet_mask`, and `is_ipv6`.
13///
14/// Each property is a u64 because
15/// [`EncodableData`](crate::client_routing_label::EncodableData) expects a u64.
16/// `client_subnet` should be formatted as a u64 by shifting each octet into an
17/// unsigned integer, applying the subnet mask, then shifting until the number
18/// conforms to 64 bits.
19///
20/// # Examples
21/// ```
22/// use amazon_cloudfront_client_routing_lib::ip::ClientSubnetEncodingData;
23///
24/// // raw values
25/// let client_ip = [1, 2, 3, 4];
26/// let subnet_mask = 24;
27/// let is_ipv6 = 0;
28///
29/// // shift bytes into int
30/// let mut client_subnet = u32::from_be_bytes(client_ip) as u64;
31/// // apply subnet mask
32/// client_subnet &= 0xffffff00;
33/// // conform to 64 bits
34/// client_subnet <<= 32;
35///
36/// let client_subnet_encoding_data = ClientSubnetEncodingData {
37///     client_subnet,
38///     subnet_mask,
39///     is_ipv6,
40/// };
41/// ```
42pub struct ClientSubnetEncodingData {
43    pub client_subnet: u64,
44    pub subnet_mask: u64,
45    pub is_ipv6: u64,
46}
47
48/// Parses passed `client_ip` into various data, returns
49/// [`ClientSubnetEncodingData`].
50///
51/// Takes in one param: `client_ip`. Attempts to parse the `client_ip` into an
52/// [`IpAddr`]. If successful, determines if it's an [`Ipv4Addr`] or an
53/// [`Ipv6Addr`]. Returns [`ClientSubnetEncodingData`] with the parsed
54/// information. If unsuccessful, returns [`ClientSubnetEncodingData`] with all
55/// properties set to 0.
56///
57/// # Examples:
58/// ```
59/// use amazon_cloudfront_client_routing_lib::ip::parse_client_ip;
60///
61/// // Ipv4
62/// let mut client_subnet_encoding_data = parse_client_ip("1.2.3.4");
63/// assert_eq!([1, 2, 3, 0, 0, 0, 0, 0], client_subnet_encoding_data.client_subnet.to_be_bytes());
64/// assert_eq!(24, client_subnet_encoding_data.subnet_mask);
65/// assert_eq!(0, client_subnet_encoding_data.is_ipv6);
66///
67/// // Ipv6
68/// client_subnet_encoding_data = parse_client_ip("0102:0304:0506:0708:090a:0b0c:0d0e:0f10");
69/// assert_eq!([1, 2, 3, 4, 5, 6, 0, 0], client_subnet_encoding_data.client_subnet.to_be_bytes());
70/// assert_eq!(48, client_subnet_encoding_data.subnet_mask);
71/// assert_eq!(1, client_subnet_encoding_data.is_ipv6);
72///
73/// // Invalid client ip
74/// client_subnet_encoding_data = parse_client_ip("1.2.a");
75/// assert_eq!([0, 0, 0, 0, 0, 0, 0, 0], client_subnet_encoding_data.client_subnet.to_be_bytes());
76/// assert_eq!(0, client_subnet_encoding_data.subnet_mask);
77/// assert_eq!(0, client_subnet_encoding_data.is_ipv6);
78/// ```
79pub fn parse_client_ip(client_ip: &str) -> ClientSubnetEncodingData {
80    if let Ok(addr) = client_ip.parse::<IpAddr>() {
81        if addr.is_ipv4() {
82            // unwrap is ok here because we verify it is parsable before
83            let ipv4_address: Ipv4Addr = client_ip.parse().unwrap();
84            ClientSubnetEncodingData {
85                client_subnet: (u32::from_be_bytes(ipv4_address.octets()) as u64 & 0xffffff00)
86                    << 32,
87                subnet_mask: SubnetMask::Ipv4 as u64,
88                is_ipv6: 0,
89            }
90        } else {
91            // unwrap is ok here because we verify it is parsable before
92            let ipv6_address: Ipv6Addr = client_ip.parse().unwrap();
93            ClientSubnetEncodingData {
94                client_subnet: ((u128::from_be_bytes(ipv6_address.octets()) >> 64)
95                    & 0xffffffffffff0000) as u64,
96                subnet_mask: SubnetMask::Ipv6 as u64,
97                is_ipv6: 1,
98            }
99        }
100    } else {
101        ClientSubnetEncodingData {
102            client_subnet: 0,
103            subnet_mask: 0,
104            is_ipv6: 0,
105        }
106    }
107}
108
109#[cfg(test)]
110mod tests {
111    use super::parse_client_ip;
112
113    #[test]
114    fn validate_parse_ipv4() {
115        let client_subnet_encoding_data = parse_client_ip("85.83.215.126");
116
117        assert_eq!(
118            6148494311290830848,
119            client_subnet_encoding_data.client_subnet
120        );
121        assert_eq!(24, client_subnet_encoding_data.subnet_mask);
122        assert_eq!(0, client_subnet_encoding_data.is_ipv6);
123    }
124
125    #[test]
126    fn validate_parse_ipv6() {
127        let client_subnet_encoding_data =
128            parse_client_ip("819e:5c2e:21e4:0094:4805:1635:f8e4:049b");
129
130        assert_eq!(
131            9340004030419828736,
132            client_subnet_encoding_data.client_subnet
133        );
134        assert_eq!(48, client_subnet_encoding_data.subnet_mask);
135        assert_eq!(1, client_subnet_encoding_data.is_ipv6);
136    }
137
138    #[test]
139    fn validate_parse_abbreviated_ipv6() {
140        let client_subnet_encoding_data = parse_client_ip("0319:7db1:f4d6::");
141
142        assert_eq!(
143            223347859801899008,
144            client_subnet_encoding_data.client_subnet
145        );
146        assert_eq!(48, client_subnet_encoding_data.subnet_mask);
147        assert_eq!(1, client_subnet_encoding_data.is_ipv6);
148    }
149
150    #[test]
151    fn validate_parse_invalid_client_ip() {
152        let client_subnet_encoding_data = parse_client_ip("1.2");
153
154        assert_eq!(0, client_subnet_encoding_data.client_subnet);
155        assert_eq!(0, client_subnet_encoding_data.subnet_mask);
156        assert_eq!(0, client_subnet_encoding_data.is_ipv6);
157    }
158}