1

Implement rfc6052 embed and extract fns

This commit is contained in:
Evan Pratten 2023-08-02 19:45:06 -04:00
parent 7e6abe67bb
commit 2d097e64eb
8 changed files with 271 additions and 101 deletions

View File

@ -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
View 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
View 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
View 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()
);
}
}
}

View 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),
}

View 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
View 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];

View File

@ -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()
);
}
}