Implement rfc6052 embed and extract fns
This commit is contained in:
parent
7e6abe67bb
commit
2d097e64eb
@ -41,7 +41,7 @@ exclude = ["/.github/", "/.vscode/"]
|
||||
# lazy_static = "1.4.0"
|
||||
|
||||
[workspace]
|
||||
members = ["libs/easy-tun", "libs/fast-nat", "libs/interproto"]
|
||||
members = ["libs/easy-tun", "libs/fast-nat", "libs/interproto", "libs/rfc6052"]
|
||||
|
||||
[[bin]]
|
||||
name = "protomask"
|
||||
@ -60,6 +60,7 @@ path = "src/protomask-6over4.rs"
|
||||
easy-tun = { path = "libs/easy-tun" }
|
||||
fast-nat = { path = "libs/fast-nat" }
|
||||
interproto = { path = "libs/interproto" }
|
||||
rfc6052 = { path = "libs/rfc6052" }
|
||||
|
||||
# External Dependencies
|
||||
tokio = { version = "1.29.1", features = ["macros", "rt-multi-thread"] }
|
||||
|
17
libs/rfc6052/Cargo.toml
Normal file
17
libs/rfc6052/Cargo.toml
Normal file
@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "rfc6052"
|
||||
version = "1.0.0"
|
||||
authors = ["Evan Pratten <ewpratten@gmail.com>"]
|
||||
edition = "2021"
|
||||
description = "Rust functions for interacting with RFC6052 IPv4-Embedded IPv6 Addresses"
|
||||
readme = "README.md"
|
||||
homepage = "https://github.com/ewpratten/protomask/tree/master/libs/rfc6052"
|
||||
documentation = "https://docs.rs/rfc6052"
|
||||
repository = "https://github.com/ewpratten/protomask"
|
||||
license = "GPL-3.0"
|
||||
keywords = []
|
||||
categories = []
|
||||
|
||||
[dependencies]
|
||||
thiserror = "^1.0.44"
|
||||
ipnet = "^2.8.0"
|
3
libs/rfc6052/README.md
Normal file
3
libs/rfc6052/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# RFC6052 for Rust
|
||||
|
||||
This library provides functions for interacting with [RFC6052](https://datatracker.ietf.org/doc/html/rfc6052) IPv4-Embedded IPv6 Addresses.
|
126
libs/rfc6052/src/embed.rs
Normal file
126
libs/rfc6052/src/embed.rs
Normal file
@ -0,0 +1,126 @@
|
||||
use ipnet::Ipv6Net;
|
||||
use std::cmp::{max, min};
|
||||
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||
|
||||
use crate::error::Error;
|
||||
use crate::ALLOWED_PREFIX_LENS;
|
||||
|
||||
/// Embeds an IPv4 address into an IPv6 prefix following the method defined in [RFC6052 Section 2.2](https://datatracker.ietf.org/doc/html/rfc6052#section-2.2)
|
||||
pub fn embed_ipv4_addr(ipv4_addr: Ipv4Addr, ipv6_prefix: Ipv6Net) -> Result<Ipv6Addr, Error> {
|
||||
// Fail if the prefix length is invalid
|
||||
if !ALLOWED_PREFIX_LENS.contains(&ipv6_prefix.prefix_len()) {
|
||||
return Err(Error::InvalidPrefixLength(ipv6_prefix.prefix_len()));
|
||||
}
|
||||
|
||||
// Fall through to the unchecked version of this function
|
||||
Ok(unsafe { embed_ipv4_addr_unchecked(ipv4_addr, ipv6_prefix) })
|
||||
}
|
||||
|
||||
/// Embeds an IPv4 address into an IPv6 prefix following the method defined in [RFC6052 Section 2.2](https://datatracker.ietf.org/doc/html/rfc6052#section-2.2)
|
||||
///
|
||||
/// **Warning:** This function does not check that the prefix length is valid according to the RFC. Use `embed_ipv4_addr` instead.
|
||||
pub unsafe fn embed_ipv4_addr_unchecked(
|
||||
ipv4_addr: Ipv4Addr,
|
||||
ipv6_prefix: Ipv6Net,
|
||||
) -> Ipv6Addr {
|
||||
// Convert to integer types
|
||||
let ipv4_addr = u32::from(ipv4_addr);
|
||||
let prefix_len = ipv6_prefix.prefix_len() as i16;
|
||||
let ipv6_prefix = u128::from(ipv6_prefix.addr());
|
||||
|
||||
// According to the RFC, the IPv4 address must be split on the boundary of bits 64..71.
|
||||
// To accomplish this, we split the IPv4 address into two parts so we can separately mask
|
||||
// and shift them into place on each side of the boundary
|
||||
Ipv6Addr::from(
|
||||
ipv6_prefix
|
||||
| (((ipv4_addr as u128 & (0xffff_ffffu128 << (32 + min(0, prefix_len - 64)))) as u128)
|
||||
<< (128 - prefix_len - 32))
|
||||
| (((ipv4_addr as u128) << max(0, 128 - prefix_len - 32 - 8)) & 0x00ff_ffff_ffff_ffff),
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_embed_len_32() {
|
||||
unsafe {
|
||||
assert_eq!(
|
||||
embed_ipv4_addr_unchecked(
|
||||
"192.0.2.1".parse().unwrap(),
|
||||
"64:ff9b::/32".parse().unwrap()
|
||||
),
|
||||
"64:ff9b:c000:0201::".parse::<Ipv6Addr>().unwrap()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_embed_len_40() {
|
||||
unsafe {
|
||||
assert_eq!(
|
||||
embed_ipv4_addr_unchecked(
|
||||
"192.0.2.1".parse().unwrap(),
|
||||
"64:ff9b::/40".parse().unwrap(),
|
||||
),
|
||||
"64:ff9b:00c0:0002:0001::".parse::<Ipv6Addr>().unwrap()
|
||||
);
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn test_embed_len_48() {
|
||||
unsafe {
|
||||
assert_eq!(
|
||||
embed_ipv4_addr_unchecked(
|
||||
"192.0.2.1".parse().unwrap(),
|
||||
"64:ff9b::/48".parse().unwrap(),
|
||||
),
|
||||
"64:ff9b:0000:c000:0002:0100::".parse::<Ipv6Addr>().unwrap()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_embed_len_56() {
|
||||
unsafe {
|
||||
assert_eq!(
|
||||
embed_ipv4_addr_unchecked(
|
||||
"192.0.2.1".parse().unwrap(),
|
||||
"64:ff9b::/56".parse().unwrap(),
|
||||
),
|
||||
"64:ff9b:0000:00c0:0000:0201::".parse::<Ipv6Addr>().unwrap()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_embed_len_64() {
|
||||
unsafe {
|
||||
assert_eq!(
|
||||
embed_ipv4_addr_unchecked(
|
||||
"192.0.2.1".parse().unwrap(),
|
||||
"64:ff9b::/64".parse().unwrap(),
|
||||
),
|
||||
"64:ff9b:0000:0000:00c0:0002:0100::"
|
||||
.parse::<Ipv6Addr>()
|
||||
.unwrap()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_embed_len_96() {
|
||||
unsafe {
|
||||
assert_eq!(
|
||||
embed_ipv4_addr_unchecked(
|
||||
"192.0.2.1".parse().unwrap(),
|
||||
"64:ff9b::/96".parse().unwrap(),
|
||||
),
|
||||
"64:ff9b:0000:0000:0000:0000:c000:0201"
|
||||
.parse::<Ipv6Addr>()
|
||||
.unwrap()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
7
libs/rfc6052/src/error.rs
Normal file
7
libs/rfc6052/src/error.rs
Normal file
@ -0,0 +1,7 @@
|
||||
//! Error types for this library
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error("Invalid IPv6 prefix length: {0}. Must be one of 32, 40, 48, 56, 64, or 96")]
|
||||
InvalidPrefixLength(u8),
|
||||
}
|
96
libs/rfc6052/src/extract.rs
Normal file
96
libs/rfc6052/src/extract.rs
Normal file
@ -0,0 +1,96 @@
|
||||
use crate::{error::Error, ALLOWED_PREFIX_LENS};
|
||||
use std::cmp::max;
|
||||
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||
|
||||
// /// Extracts an IPv4 address from an IPv6 prefix following the method defined in [RFC6052 Section 2.2](https://datatracker.ietf.org/doc/html/rfc6052#section-2.2)
|
||||
// pub fn extract_ipv4_addr(ipv6_addr: Ipv6Addr, prefix_length: u8) -> Result<Ipv4Addr, Error> {
|
||||
// // Fail if the prefix length is invalid
|
||||
// if !ALLOWED_PREFIX_LENS.contains(&prefix_length) {
|
||||
// return Err(Error::InvalidPrefixLength(prefix_length));
|
||||
// }
|
||||
|
||||
// // Fall through to the unchecked version of this function
|
||||
// Ok(unsafe { extract_ipv4_addr_unchecked(ipv6_addr, prefix_length) })
|
||||
// }
|
||||
|
||||
/// Extracts an IPv4 address from an IPv6 prefix following the method defined in [RFC6052 Section 2.2](https://datatracker.ietf.org/doc/html/rfc6052#section-2.2)
|
||||
///
|
||||
/// **Warning:** This function does not check that the prefix length is valid according to the RFC. Use `extract_ipv4_addr` instead.
|
||||
pub unsafe fn extract_ipv4_addr_unchecked(ipv6_addr: Ipv6Addr, prefix_length: u8) -> Ipv4Addr {
|
||||
// Convert the IPv6 address to a number for easier manipulation
|
||||
let ipv6_addr = u128::from(ipv6_addr);
|
||||
let host_part = ipv6_addr & ((1 << (128 - prefix_length)) - 1);
|
||||
|
||||
// Extract the IPv4 address from the IPv6 address
|
||||
Ipv4Addr::from(
|
||||
// format!("{:02x}",
|
||||
(((host_part & 0xffff_ffff_ffff_ffff_0000_0000_0000_0000)
|
||||
| (host_part & 0x00ff_ffff_ffff_ffff) << 8)
|
||||
>> max(8, 128 - prefix_length - 32)) as u32, // )
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_extract_len_32() {
|
||||
unsafe {
|
||||
assert_eq!(
|
||||
extract_ipv4_addr_unchecked("64:ff9b:c000:0201::".parse().unwrap(), 32),
|
||||
"192.0.2.1".parse::<Ipv4Addr>().unwrap(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_len_40() {
|
||||
unsafe {
|
||||
assert_eq!(
|
||||
extract_ipv4_addr_unchecked("64:ff9b:00c0:0002:0001::".parse().unwrap(), 40),
|
||||
"192.0.2.1".parse::<Ipv4Addr>().unwrap(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_len_48() {
|
||||
unsafe {
|
||||
assert_eq!(
|
||||
extract_ipv4_addr_unchecked("64:ff9b:0000:c000:0002:0100::".parse().unwrap(), 48),
|
||||
"192.0.2.1".parse::<Ipv4Addr>().unwrap(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_len_56() {
|
||||
unsafe {
|
||||
assert_eq!(
|
||||
extract_ipv4_addr_unchecked("64:ff9b:0000:00c0:0000:0201::".parse().unwrap(), 56),
|
||||
"192.0.2.1".parse::<Ipv4Addr>().unwrap(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_len_64() {
|
||||
unsafe {
|
||||
assert_eq!(
|
||||
extract_ipv4_addr_unchecked("64:ff9b:0000:0000:00c0:0002:0100::".parse().unwrap(), 64),
|
||||
"192.0.2.1".parse::<Ipv4Addr>().unwrap(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_len_96() {
|
||||
unsafe {
|
||||
assert_eq!(
|
||||
extract_ipv4_addr_unchecked("64:ff9b:0000:0000:0000:0000:c000:0201".parse().unwrap(), 96),
|
||||
"192.0.2.1".parse::<Ipv4Addr>().unwrap(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
14
libs/rfc6052/src/lib.rs
Normal file
14
libs/rfc6052/src/lib.rs
Normal file
@ -0,0 +1,14 @@
|
||||
#![doc = include_str!("../README.md")]
|
||||
#![deny(clippy::pedantic)]
|
||||
#![allow(clippy::module_name_repetitions)]
|
||||
#![allow(clippy::missing_errors_doc)]
|
||||
#![allow(clippy::missing_panics_doc)]
|
||||
|
||||
pub mod error;
|
||||
|
||||
mod embed;
|
||||
mod extract;
|
||||
pub use embed::embed_ipv4_addr;
|
||||
|
||||
/// All allowed IPv6 prefix lengths according to [RFC6052 Section 2.2](https://datatracker.ietf.org/doc/html/rfc6052#section-2.2)
|
||||
pub const ALLOWED_PREFIX_LENS: [u8; 6] = [32, 40, 48, 56, 64, 96];
|
@ -1,10 +1,6 @@
|
||||
//! Utilities for interacting with [RFC6052](https://datatracker.ietf.org/doc/html/rfc6052) "IPv4-Embedded IPv6 Addresses"
|
||||
|
||||
use std::{
|
||||
cmp::{max, min},
|
||||
net::{Ipv4Addr, Ipv6Addr},
|
||||
str::FromStr,
|
||||
};
|
||||
use std::str::FromStr;
|
||||
|
||||
use ipnet::Ipv6Net;
|
||||
|
||||
@ -14,103 +10,13 @@ pub fn parse_network_specific_prefix(string: &str) -> Result<Ipv6Net, String> {
|
||||
let net = Ipv6Net::from_str(string).map_err(|err| err.to_string())?;
|
||||
|
||||
// Ensure the prefix length is one of the allowed lengths according to RFC6052 Section 2.2
|
||||
if ![32, 40, 48, 56, 64, 96].contains(&net.prefix_len()) {
|
||||
return Err("Prefix length must be one of 32, 40, 48, 56, 64, or 96".to_owned());
|
||||
if !rfc6052::ALLOWED_PREFIX_LENS.contains(&net.prefix_len()) {
|
||||
return Err(format!(
|
||||
"Prefix length must be one of {:?}",
|
||||
rfc6052::ALLOWED_PREFIX_LENS
|
||||
));
|
||||
}
|
||||
|
||||
// Return the parsed network struct
|
||||
Ok(net)
|
||||
}
|
||||
|
||||
/// Embeds an IPv4 address into an IPv6 prefix
|
||||
pub fn embed_to_ipv6(ipv4_addr: Ipv4Addr, ipv6_prefix: Ipv6Net) -> Ipv6Addr {
|
||||
// Convert to integer types
|
||||
let ipv4_addr = u32::from(ipv4_addr);
|
||||
let prefix_len = ipv6_prefix.prefix_len() as i16;
|
||||
let ipv6_prefix = u128::from(ipv6_prefix.addr());
|
||||
|
||||
// According to the RFC, the IPv4 address must be split on the boundary of bits 64..71.
|
||||
// To accomplish this, we split the IPv4 address into two parts so we can separately mask
|
||||
// and shift them into place on each side of the boundary
|
||||
Ipv6Addr::from(
|
||||
ipv6_prefix
|
||||
| (((ipv4_addr as u128 & (0xffff_ffffu128 << (32 + min(0, prefix_len - 64)))) as u128)
|
||||
<< (128 - prefix_len - 32))
|
||||
| (((ipv4_addr as u128) << max(0, 128 - prefix_len - 32 - 8)) & 0x00ff_ffff_ffff_ffff),
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_embed_len_32() {
|
||||
assert_eq!(
|
||||
embed_to_ipv6(
|
||||
"192.0.2.1".parse().unwrap(),
|
||||
"64:ff9b::/32".parse().unwrap()
|
||||
),
|
||||
"64:ff9b:c000:0201::".parse::<Ipv6Addr>().unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_embed_len_40() {
|
||||
assert_eq!(
|
||||
embed_to_ipv6(
|
||||
"192.0.2.1".parse().unwrap(),
|
||||
"64:ff9b::/40".parse().unwrap(),
|
||||
),
|
||||
"64:ff9b:00c0:0002:0001::".parse::<Ipv6Addr>().unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_embed_len_48() {
|
||||
assert_eq!(
|
||||
embed_to_ipv6(
|
||||
"192.0.2.1".parse().unwrap(),
|
||||
"64:ff9b::/48".parse().unwrap(),
|
||||
),
|
||||
"64:ff9b:0000:c000:0002:0100::".parse::<Ipv6Addr>().unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_embed_len_56() {
|
||||
assert_eq!(
|
||||
embed_to_ipv6(
|
||||
"192.0.2.1".parse().unwrap(),
|
||||
"64:ff9b::/56".parse().unwrap(),
|
||||
),
|
||||
"64:ff9b:0000:00c0:0000:0201::".parse::<Ipv6Addr>().unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_embed_len_64() {
|
||||
assert_eq!(
|
||||
embed_to_ipv6(
|
||||
"192.0.2.1".parse().unwrap(),
|
||||
"64:ff9b::/64".parse().unwrap(),
|
||||
),
|
||||
"64:ff9b:0000:0000:00c0:0002:0100::"
|
||||
.parse::<Ipv6Addr>()
|
||||
.unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_embed_len_96() {
|
||||
assert_eq!(
|
||||
embed_to_ipv6(
|
||||
"192.0.2.1".parse().unwrap(),
|
||||
"64:ff9b::/96".parse().unwrap(),
|
||||
),
|
||||
"64:ff9b:0000:0000:0000:0000:c000:0201"
|
||||
.parse::<Ipv6Addr>()
|
||||
.unwrap()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user