Skip to content

Commit

Permalink
feat(connlib): add ipv6 to ipv4 header translation for non-fragmented…
Browse files Browse the repository at this point in the history
… packets
  • Loading branch information
conectado committed May 27, 2024
1 parent b91f899 commit 051462f
Showing 1 changed file with 175 additions and 4 deletions.
179 changes: 175 additions & 4 deletions rust/ip-packet/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use pnet_packet::{
},
icmpv6::{Icmpv6Packet, Icmpv6Types, MutableIcmpv6Packet},
ip::{IpNextHeaderProtocol, IpNextHeaderProtocols},
ipv4::{Ipv4Packet, MutableIpv4Packet},
ipv4::{Ipv4Flags, Ipv4Packet, MutableIpv4Packet},
ipv6::{Ipv6Packet, MutableIpv6Packet},
tcp::{MutableTcpPacket, TcpPacket},
udp::{MutableUdpPacket, UdpPacket},
Expand Down Expand Up @@ -82,7 +82,7 @@ pub enum MutableIpPacket<'a> {
Ipv6(MutableIpv6Packet<'a>),
}

fn ipv4_embedded(ip: Ipv4Addr) -> Ipv6Addr {
pub fn ipv4_embedded(ip: Ipv4Addr) -> Ipv6Addr {
Ipv6Addr::new(
0x64,
0xff9b,
Expand All @@ -95,9 +95,29 @@ fn ipv4_embedded(ip: Ipv4Addr) -> Ipv6Addr {
)
}

pub fn ipv6_translated(ip: Ipv6Addr) -> Option<Ipv4Addr> {
if ip.segments()[0] != 0x64
|| ip.segments()[1] != 0xff9b
|| ip.segments()[2] != 0
|| ip.segments()[3] != 0
|| ip.segments()[4] != 0
|| ip.segments()[5] != 0
{
return None;
}

Some(Ipv4Addr::new(
ip.octets()[12],
ip.octets()[13],
ip.octets()[14],
ip.octets()[15],
))
}

// This functions returns an Ipv6Packet but the payload is always empty, this is an easy way to represent a header.
// TODO: we might want to implement an special type for this
pub fn ipv6_header_from_ipv4(v4: Ipv4Packet, dst: Ipv6Addr) -> Ipv6Packet<'static> {
// Note use ipv4_embedded to obtain an ipv6 src from an ipv4 one
pub fn ipv6_header_from_ipv4(v4: Ipv4Packet, src: Ipv6Addr, dst: Ipv6Addr) -> Ipv6Packet<'static> {
let buf = vec![0; 40];
let mut pkt = MutableIpv6Packet::owned(buf).unwrap();

Expand Down Expand Up @@ -184,7 +204,7 @@ pub fn ipv6_header_from_ipv4(v4: Ipv4Packet, dst: Ipv6Addr) -> Ipv6Packet<'stati
// 127.0.0.1, etc.), the translator SHOULD silently drop the packet
// (as discussed in Section 5.3.7 of [RFC1812]).
// TODO: drop illegal source address? I don't think we need to do it
pkt.set_source(ipv4_embedded(v4.get_source()));
pkt.set_source(src);

// Destination Address: In the stateless mode, which is to say that if
// the IPv4 destination address is within a range of configured IPv4
Expand All @@ -204,6 +224,157 @@ pub fn ipv6_header_from_ipv4(v4: Ipv4Packet, dst: Ipv6Addr) -> Ipv6Packet<'stati
pkt.consume_to_immutable()
}

// This functions returns an Ipv6Packet but the payload is always empty, this is an easy way to represent a header.
// TODO: we might want to implement an special type for this
// Note: use ipv6_translated to obtain back an ipv4 from an embedded address
pub fn ipv4_header_from_ipv6(
v6: Ipv6Packet,
src: Ipv4Addr,
dst: Ipv4Addr,
) -> Option<Ipv4Packet<'static>> {
let buf = vec![0; 20];
let mut pkt = MutableIpv4Packet::owned(buf).unwrap();

// TODO:
// If there is no IPv6 Fragment Header, the IPv4 header fields are set
// as follows:
// Note the RFC has notes on how to set fragmentation fields.

// Version: 4
pkt.set_version(4);

// Internet Header Length: 5 (no IPv4 options)
pkt.set_header_length(5);

// Type of Service (TOS) Octet: By default, copied from the IPv6
// Traffic Class (all 8 bits). According to [RFC2474], the semantics
// of the bits are identical in IPv4 and IPv6. However, in some IPv4
// environments, these bits might be used with the old semantics of
// "Type Of Service and Precedence". An implementation of a
// translator SHOULD provide the ability to ignore the IPv6 traffic
// class and always set the IPv4 TOS Octet to a specified value. In
// addition, if the translator is at an administrative boundary, the
// filtering and update considerations of [RFC2475] may be
// applicable.
pkt.set_dscp(v6.get_traffic_class());

// Total Length: Payload length value from the IPv6 header, plus the
// size of the IPv4 header.
pkt.set_total_length(v6.get_payload_length() + 5);

// Identification: All zero. In order to avoid black holes caused by
// ICMPv4 filtering or non-[RFC2460]-compatible IPv6 hosts (a
// workaround is discussed in Section 6), the translator MAY provide
// a function to generate the identification value if the packet size
// is greater than 88 bytes and less than or equal to 1280 bytes.
// The translator SHOULD provide a method for operators to enable or
// disable this function.
pkt.set_identification(0);

// Flags: The More Fragments flag is set to zero. The Don't Fragment
// (DF) flag is set to one. In order to avoid black holes caused by
// ICMPv4 filtering or non-[RFC2460]-compatible IPv6 hosts (a
// workaround is discussed in Section 6), the translator MAY provide
// a function as follows. If the packet size is greater than 88
// bytes and less than or equal to 1280 bytes, it sets the DF flag to
// zero; otherwise, it sets the DF flag to one. The translator
// SHOULD provide a method for operators to enable or disable this
// function.
pkt.set_flags(Ipv4Flags::DontFragment | !Ipv4Flags::MoreFragments);

// Fragment Offset: All zeros.
pkt.set_fragment_offset(0);

// Time to Live: Time to Live is derived from Hop Limit value in IPv6
// header. Since the translator is a router, as part of forwarding
// the packet it needs to decrement either the IPv6 Hop Limit (before
// the translation) or the IPv4 TTL (after the translation). As part
// of decrementing the TTL or Hop Limit the translator (as any
// router) MUST check for zero and send the ICMPv4 "TTL Exceeded" or
// ICMPv6 "Hop Limit Exceeded" error.
// Same note as for the other translation
pkt.set_ttl(v6.get_hop_limit());

// Protocol: The IPv6-Frag (44) header is handled as discussed in
// Section 5.1.1. ICMPv6 (58) is changed to ICMPv4 (1), and the
// payload is translated as discussed in Section 5.2. The IPv6
// headers HOPOPT (0), IPv6-Route (43), and IPv6-Opts (60) are
// skipped over during processing as they have no meaning in IPv4.
// For the first 'next header' that does not match one of the cases
// above, its Next Header value (which contains the transport
// protocol number) is copied to the protocol field in the IPv4
// header. This means that all transport protocols are translated.
// Note: Some translated protocols will fail at the receiver for
// various reasons: some are known to fail when translated (e.g.,
// IPsec Authentication Header (51)), and others will fail
// checksum validation if the address translation is not checksum
// neutral [RFC6052] and the translator does not update the
// transport protocol's checksum (because the translator doesn't
// support recalculating the checksum for that transport protocol;
// see Section 5.5).

// Note: this seems to suggest there can be more than 1 next level protocol?
// maybe I'm misreading this.

match v6.get_next_header() {
IpNextHeaderProtocols::Ipv6Frag => {
// TODO:
return None;
}
IpNextHeaderProtocols::Icmpv6 => {
pkt.set_next_level_protocol(IpNextHeaderProtocols::Icmp);
}
IpNextHeaderProtocols::Hopopt
| IpNextHeaderProtocols::Ipv6Route
| IpNextHeaderProtocols::Ipv6Opts => {
return None;
}
proto => {
pkt.set_next_level_protocol(proto);
}
}

// Header Checksum: Computed once the IPv4 header has been created.

// Source Address: In the stateless mode (which is to say that if the
// IPv6 source address is within the range of a configured IPv6
// translation prefix), the IPv4 source address is derived from the
// IPv6 source address per [RFC6052], Section 2.3. Note that the
// original IPv6 source address is an IPv4-translatable address. A
// workflow example of stateless translation is shown in Appendix A
// of this document. If the translator only supports stateless mode
// and if the IPv6 source address is not within the range of
// configured IPv6 prefix(es), the translator SHOULD drop the packet
// and respond with an ICMPv6 "Destination Unreachable, Source
// address failed ingress/egress policy" (Type 1, Code 5).

// In the stateful mode, which is to say that if the IPv6 source
// address is not within the range of any configured IPv6 stateless
// translation prefix, the IPv4 source address and transport-layer
// source port corresponding to the IPv4-related IPv6 source address
// and source port are derived from the Binding Information Bases
// (BIBs) as described in [RFC6146].

// In stateless and stateful modes, if the translator gets an illegal
// source address (e.g., ::1, etc.), the translator SHOULD silently
// drop the packet.
pkt.set_source(src);

// Destination Address: The IPv4 destination address is derived from
// the IPv6 destination address of the datagram being translated per
// [RFC6052], Section 2.3. Note that the original IPv6 destination
// address is an IPv4-converted address.
pkt.set_destination(dst);

// TODO?: If a Routing header with a non-zero Segments Left field is present,
// then the packet MUST NOT be translated, and an ICMPv6 "parameter
// problem/erroneous header field encountered" (Type 4, Code 0) error
// message, with the Pointer field indicating the first byte of the
// Segments Left field, SHOULD be returned to the sender.

Some(pkt.consume_to_immutable())
}

impl<'a> MutableIpPacket<'a> {
pub fn new(buf: &'a mut [u8]) -> Option<Self> {
match buf[0] >> 4 {
Expand Down

0 comments on commit 051462f

Please sign in to comment.