use super::{
icmp::{translate_icmp_to_icmpv6, translate_icmpv6_to_icmp},
tcp::{recalculate_tcp_checksum_ipv4, recalculate_tcp_checksum_ipv6},
udp::{recalculate_udp_checksum_ipv4, recalculate_udp_checksum_ipv6},
};
use crate::error::{Error, Result};
use pnet::packet::{
ip::IpNextHeaderProtocols,
ipv4::{self, Ipv4Packet, MutableIpv4Packet},
ipv6::{Ipv6Packet, MutableIpv6Packet},
Packet,
};
use std::net::{Ipv4Addr, Ipv6Addr};
#[profiling::function]
pub fn translate_ipv4_to_ipv6(
ipv4_packet: &[u8],
new_source: Ipv6Addr,
new_destination: Ipv6Addr,
) -> Result<Vec<u8>> {
{
let ipv4_packet = Ipv4Packet::new(ipv4_packet).ok_or(Error::PacketTooShort {
expected: Ipv4Packet::minimum_packet_size(),
actual: ipv4_packet.len(),
})?;
let new_payload = match ipv4_packet.get_next_level_protocol() {
IpNextHeaderProtocols::Icmp => {
translate_icmp_to_icmpv6(ipv4_packet.payload(), new_source, new_destination)?
}
IpNextHeaderProtocols::Tcp => {
recalculate_tcp_checksum_ipv6(ipv4_packet.payload(), new_source, new_destination)?
}
IpNextHeaderProtocols::Udp => {
recalculate_udp_checksum_ipv6(ipv4_packet.payload(), new_source, new_destination)?
}
protocol => {
log::warn!("Unsupported next level protocol: {:?}", protocol);
ipv4_packet.payload().to_vec()
}
};
let mut output_buffer = vec![0u8; Ipv6Packet::minimum_packet_size() + new_payload.len()];
let mut ipv6_packet =
unsafe { MutableIpv6Packet::new(&mut output_buffer).unwrap_unchecked() };
ipv6_packet.set_version(6);
ipv6_packet.set_next_header(match ipv4_packet.get_next_level_protocol() {
IpNextHeaderProtocols::Icmp => IpNextHeaderProtocols::Icmpv6,
proto => proto,
});
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());
ipv6_packet.set_payload(&new_payload);
#[cfg(feature = "metrics")]
protomask_metrics::metric!(PACKET_COUNTER, PROTOCOL_IPV4, STATUS_TRANSLATED).inc();
Ok(output_buffer)
}
.map_err(|error| {
#[cfg(feature = "metrics")]
protomask_metrics::metric!(PACKET_COUNTER, PROTOCOL_IPV4, STATUS_DROPPED).inc();
error
})
}
#[profiling::function]
pub fn translate_ipv6_to_ipv4(
ipv6_packet: &[u8],
new_source: Ipv4Addr,
new_destination: Ipv4Addr,
) -> Result<Vec<u8>> {
{
let ipv6_packet = Ipv6Packet::new(ipv6_packet).ok_or(Error::PacketTooShort {
expected: Ipv6Packet::minimum_packet_size(),
actual: ipv6_packet.len(),
})?;
let new_payload = match ipv6_packet.get_next_header() {
IpNextHeaderProtocols::Icmpv6 => {
translate_icmpv6_to_icmp(ipv6_packet.payload(), new_source, new_destination)?
}
IpNextHeaderProtocols::Tcp => {
recalculate_tcp_checksum_ipv4(ipv6_packet.payload(), new_source, new_destination)?
}
IpNextHeaderProtocols::Udp => {
recalculate_udp_checksum_ipv4(ipv6_packet.payload(), new_source, new_destination)?
}
protocol => {
log::warn!("Unsupported next header: {:?}", protocol);
ipv6_packet.payload().to_vec()
}
};
let mut output_buffer = vec![0u8; Ipv4Packet::minimum_packet_size() + new_payload.len()];
let mut ipv4_packet =
unsafe { MutableIpv4Packet::new(&mut output_buffer).unwrap_unchecked() };
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,
proto => proto,
});
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(),
);
ipv4_packet.set_payload(&new_payload);
ipv4_packet.set_checksum(ipv4::checksum(&ipv4_packet.to_immutable()));
#[cfg(feature = "metrics")]
protomask_metrics::metric!(PACKET_COUNTER, PROTOCOL_IPV6, STATUS_TRANSLATED).inc();
Ok(output_buffer)
}
.map_err(|error| {
#[cfg(feature = "metrics")]
protomask_metrics::metric!(PACKET_COUNTER, PROTOCOL_IPV6, STATUS_DROPPED).inc();
error
})
}