1
This commit is contained in:
Evan Pratten 2023-08-02 22:44:30 -04:00
parent 032dcb7fa9
commit 833d4308fb
9 changed files with 231 additions and 15 deletions

View File

@ -41,7 +41,13 @@ exclude = ["/.github/", "/.vscode/"]
# lazy_static = "1.4.0"
[workspace]
members = ["libs/easy-tun", "libs/fast-nat", "libs/interproto", "libs/rfc6052"]
members = [
"libs/easy-tun",
"libs/fast-nat",
"libs/interproto",
"libs/rfc6052",
"libs/rtnl",
]
[[bin]]
name = "protomask"
@ -61,6 +67,7 @@ easy-tun = { path = "libs/easy-tun" }
fast-nat = { path = "libs/fast-nat" }
interproto = { path = "libs/interproto" }
rfc6052 = { path = "libs/rfc6052" }
rtnl = { path = "libs/rtnl", features = ["tokio"] }
# External Dependencies
tokio = { version = "1.29.1", features = ["macros", "rt-multi-thread"] }

View File

@ -65,6 +65,7 @@ pub fn translate_ipv4_to_ipv6(
ipv6_packet.set_hop_limit(ipv4_packet.get_ttl());
ipv6_packet.set_source(new_source);
ipv6_packet.set_destination(new_destination);
ipv6_packet.set_payload_length(new_payload.len().try_into().unwrap());
// Copy the payload to the buffer
ipv6_packet.set_payload(&new_payload);
@ -118,6 +119,7 @@ pub fn translate_ipv6_to_ipv4(
// Set the header fields
ipv4_packet.set_version(4);
ipv4_packet.set_header_length(5);
ipv4_packet.set_ttl(ipv6_packet.get_hop_limit());
ipv4_packet.set_next_level_protocol(match ipv6_packet.get_next_header() {
IpNextHeaderProtocols::Icmpv6 => IpNextHeaderProtocols::Icmp,
@ -125,6 +127,7 @@ pub fn translate_ipv6_to_ipv4(
});
ipv4_packet.set_source(new_source);
ipv4_packet.set_destination(new_destination);
ipv4_packet.set_total_length((Ipv4Packet::minimum_packet_size() + new_payload.len()).try_into().unwrap());
// Copy the payload to the buffer
ipv4_packet.set_payload(&new_payload);

View File

@ -1,17 +1,21 @@
[package]
name = "rfc6052"
version = "1.0.0"
name = "rtnl"
version = "0.1.0"
authors = ["Evan Pratten <ewpratten@gmail.com>"]
edition = "2021"
description = "Rust functions for interacting with RFC6052 IPv4-Embedded IPv6 Addresses"
description = "Slightly sane wrapper around rtnetlink"
readme = "README.md"
homepage = "https://github.com/ewpratten/protomask/tree/master/libs/rfc6052"
documentation = "https://docs.rs/rfc6052"
homepage = "https://github.com/ewpratten/protomask/tree/master/libs/rtnl"
documentation = "https://docs.rs/rtnl"
repository = "https://github.com/ewpratten/protomask"
license = "GPL-3.0"
keywords = []
categories = []
[dependencies]
thiserror = "^1.0.44"
ipnet = "^2.8.0"
tokio = { version = "1.29.1", optional = true, features = ["rt-multi-thread"] }
log = "0.4.19"
rtnetlink = "0.13.1"
futures = "0.3.28"
ipnet = "^2.8.0"

3
libs/rtnl/README.md Normal file
View File

@ -0,0 +1,3 @@
# RTNL
A slightly sane wrapper around [`rtnetlink`](https://crates.io/crates/rtnetlink)

71
libs/rtnl/src/ip.rs Normal file
View File

@ -0,0 +1,71 @@
//! Utilities for manipulating the addresses assigned to links
use std::net::IpAddr;
use futures::TryStreamExt;
use rtnetlink::Handle;
/// Add an IP address to a link
pub async fn addr_add(
ip_addr: IpAddr,
prefix_len: u8,
rt_handle: &Handle,
link_index: u32,
) -> Result<(), rtnetlink::Error> {
log::trace!("Adding address {} to link {}", ip_addr, link_index);
rt_handle
.address()
.add(link_index, ip_addr, prefix_len)
.execute()
.await
.map_err(|err| {
log::error!("Failed to add address {} to link {}", ip_addr, link_index);
log::error!("{}", err);
err
})
}
/// Remove an IP address from a link
pub async fn addr_del(
ip_addr: IpAddr,
prefix_len: u8,
rt_handle: &Handle,
link_index: u32,
) -> Result<(), rtnetlink::Error> {
log::trace!("Removing address {} from link {}", ip_addr, link_index);
// Find the address message that matches the given address
if let Some(address_message) = rt_handle
.address()
.get()
.set_link_index_filter(link_index)
.set_address_filter(ip_addr)
.set_prefix_length_filter(prefix_len)
.execute()
.try_next()
.await
.map_err(|err| {
log::error!("Failed to find address {} on link {}", ip_addr, link_index);
log::error!("{}", err);
err
})?
{
// Delete the address
rt_handle
.address()
.del(address_message)
.execute()
.await
.map_err(|err| {
log::error!(
"Failed to remove address {} from link {}",
ip_addr,
link_index
);
log::error!("{}", err);
err
})?;
}
Ok(())
}

22
libs/rtnl/src/lib.rs Normal file
View File

@ -0,0 +1,22 @@
#![doc = include_str!("../README.md")]
#![deny(clippy::pedantic)]
#![allow(clippy::module_name_repetitions)]
#![allow(clippy::missing_errors_doc)]
#![allow(clippy::missing_panics_doc)]
#![allow(clippy::missing_safety_doc)]
pub mod link;
pub mod ip;
pub mod route;
/// Get a handle on a new rtnetlink connection
#[cfg(feature="tokio")]
pub fn new_handle() -> Result<rtnetlink::Handle, std::io::Error> {
let (rt_connection, rt_handle, _) = rtnetlink::new_connection().map_err(|err| {
log::error!("Failed to open rtnetlink connection");
log::error!("{}", err);
err
})?;
tokio::spawn(rt_connection);
Ok(rt_handle)
}

31
libs/rtnl/src/link.rs Normal file
View File

@ -0,0 +1,31 @@
//! Utilities for operating on a link/interface/device
use futures::TryStreamExt;
use rtnetlink::Handle;
/// Bring up a link by its link index
pub async fn link_up(rt_handle: &Handle, link_index: u32) -> Result<(), rtnetlink::Error> {
log::trace!("Bringing up link {}", link_index);
rt_handle.link().set(link_index).up().execute().await
}
/// Bring down a link by its link index
pub async fn link_down(rt_handle: &Handle, link_index: u32) -> Result<(), rtnetlink::Error> {
log::trace!("Bringing down link {}", link_index);
rt_handle.link().set(link_index).down().execute().await
}
/// Get the link index of a link by its name
pub async fn get_link_index(
rt_handle: &Handle,
link_name: &str,
) -> Result<Option<u32>, rtnetlink::Error> {
Ok(rt_handle
.link()
.get()
.match_name(link_name.to_owned())
.execute()
.try_next()
.await?
.map(|message| message.header.index))
}

41
libs/rtnl/src/route.rs Normal file
View File

@ -0,0 +1,41 @@
//! Utilities for interacting with the routing table
use ipnet::IpNet;
use rtnetlink::Handle;
/// Add a route to a link
pub async fn route_add(
destination: IpNet,
rt_handle: &Handle,
link_index: u32,
) -> Result<(), rtnetlink::Error> {
log::trace!("Adding route {} to link {}", destination, link_index);
match destination {
IpNet::V4(destination) => rt_handle
.route()
.add()
.v4()
.output_interface(link_index)
.destination_prefix(destination.addr(), destination.prefix_len())
.execute()
.await
.map_err(|err| {
log::error!("Failed to add route {} to link", destination);
log::error!("{}", err);
err
}),
IpNet::V6(destination) => rt_handle
.route()
.add()
.v6()
.output_interface(link_index)
.destination_prefix(destination.addr(), destination.prefix_len())
.execute()
.await
.map_err(|err| {
log::error!("Failed to add route {} to link", destination);
log::error!("{}", err);
err
}),
}
}

View File

@ -7,7 +7,7 @@ use clap::Parser;
use common::{logging::enable_logger, rfc6052::parse_network_specific_prefix};
use easy_tun::Tun;
use interproto::protocols::ip::{translate_ipv4_to_ipv6, translate_ipv6_to_ipv4};
use ipnet::Ipv6Net;
use ipnet::{IpNet, Ipv4Net, Ipv6Net};
use nix::unistd::Uid;
use rfc6052::{embed_ipv4_addr_unchecked, extract_ipv4_addr_unchecked};
use std::{
@ -48,7 +48,26 @@ pub async fn main() {
}
// Bring up a TUN interface
log::debug!("Creating new TUN interface");
let mut tun = Tun::new(&args.interface).unwrap();
log::debug!("Created TUN interface: {}", tun.name());
// Configure the new interface
// - Bring up
// - Add IPv6 prefix as a route
// - Point IPv4 default route to the new interface
let rt_handle = rtnl::new_handle().unwrap();
let tun_link_idx = rtnl::link::get_link_index(&rt_handle, tun.name())
.await
.unwrap()
.unwrap();
rtnl::link::link_up(&rt_handle, tun_link_idx).await.unwrap();
rtnl::route::route_add(IpNet::V6(args.embed_prefix), &rt_handle, tun_link_idx)
.await
.unwrap();
rtnl::route::route_add(IpNet::V4(Ipv4Net::default()), &rt_handle, tun_link_idx)
.await
.unwrap();
// Translate all incoming packets
log::info!("Translating packets on {}", tun.name());
@ -60,7 +79,7 @@ pub async fn main() {
// Translate it based on the Layer 3 protocol number
let layer_3_proto = buffer[0] >> 4;
log::trace!("New packet with layer 3 protocol: {}", layer_3_proto);
let output = match layer_3_proto {
match match layer_3_proto {
// IPv4
4 => translate_ipv4_to_ipv6(
&buffer[..len],
@ -99,10 +118,25 @@ pub async fn main() {
log::warn!("Unknown Layer 3 protocol: {}", proto);
continue;
}
}
.unwrap();
// Write the translated packet back to the TUN interface
tun.write(&output).unwrap();
} {
Ok(data) => {
// Write the translated packet back to the TUN interface
tun.write(&data).unwrap();
}
Err(error) => match error {
interproto::error::Error::PacketTooShort { expected, actual } => log::warn!(
"Got packet with length {} when expecting at least {} bytes",
actual,
expected
),
interproto::error::Error::UnsupportedIcmpType(icmp_type) => {
log::warn!("Got a packet with an unsupported ICMP type: {}", icmp_type)
}
interproto::error::Error::UnsupportedIcmpv6Type(icmpv6_type) => log::warn!(
"Got a packet with an unsupported ICMPv6 type: {}",
icmpv6_type
),
},
};
}
}