Skip to content

Commit

Permalink
ipv6: check hbh before checking IPv6 addr.
Browse files Browse the repository at this point in the history
With IPv6, the hop-by-hop option should be checked allong every node on
the route. Only then the IPv6 destination address is checked.

ip(hbh): return Param Problem for some options

For some options, a ICMP parameter problem needs to be transmitted back
to the source.
  • Loading branch information
thvdveld committed Nov 29, 2023
1 parent 2ba9799 commit cc1b038
Show file tree
Hide file tree
Showing 2 changed files with 250 additions and 69 deletions.
171 changes: 105 additions & 66 deletions src/iface/interface/ipv6.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,24 @@ use crate::socket::AnySocket;
use crate::phy::PacketMeta;
use crate::wire::*;

/// Enum used for the process_hopbyhop function. In some cases, when discarding a packet, an ICMMP
/// parameter problem message needs to be transmitted to the source of the address. In other cases,
/// the processing of the IP packet can continue.
#[allow(clippy::large_enum_variant)]
enum HopByHopResponse<'frame> {
/// Continue processing the IPv6 packet.
Continue((IpProtocol, &'frame [u8])),
/// Discard the packet and maybe send back an ICMPv6 packet.
Discard(Option<IpPacket<'frame>>),
}

// We implement `Default` such that we can use the check! macro.
impl Default for HopByHopResponse<'_> {
fn default() -> Self {
Self::Discard(None)
}
}

impl InterfaceInner {
pub(super) fn process_ipv6<'frame>(
&mut self,
Expand All @@ -26,7 +44,19 @@ impl InterfaceInner {
return None;
}

let ip_payload = ipv6_packet.payload();
let (next_header, ip_payload) = if ipv6_repr.next_header == IpProtocol::HopByHop {
match self.process_hopbyhop(ipv6_repr, ipv6_packet.payload()) {
HopByHopResponse::Discard(e) => return e,
HopByHopResponse::Continue(next) => next,
}
} else {
(ipv6_repr.next_header, ipv6_packet.payload())
};

if !self.has_ip_addr(ipv6_repr.dst_addr) && !self.has_multicast_group(ipv6_repr.dst_addr) {
net_trace!("packet IP address not for this interface");
return None;
}

#[cfg(feature = "socket-raw")]
let handled_by_raw_socket = self.raw_socket_filter(sockets, &ipv6_repr.into(), ip_payload);
Expand All @@ -37,15 +67,71 @@ impl InterfaceInner {
sockets,
meta,
ipv6_repr,
ipv6_repr.next_header,
next_header,
handled_by_raw_socket,
ip_payload,
)
}

fn process_hopbyhop<'frame>(
&mut self,
ipv6_repr: Ipv6Repr,
ip_payload: &'frame [u8],
) -> HopByHopResponse<'frame> {
let param_problem = || {
let payload_len =
icmp_reply_payload_len(ip_payload.len(), IPV6_MIN_MTU, ipv6_repr.buffer_len());
self.icmpv6_reply(
ipv6_repr,
Icmpv6Repr::ParamProblem {
reason: Icmpv6ParamProblem::UnrecognizedOption,
pointer: ipv6_repr.buffer_len() as u32,
header: ipv6_repr,
data: &ip_payload[0..payload_len],
},
)
};

let ext_hdr = check!(Ipv6ExtHeader::new_checked(ip_payload));
let ext_repr = check!(Ipv6ExtHeaderRepr::parse(&ext_hdr));
let hbh_hdr = check!(Ipv6HopByHopHeader::new_checked(ext_repr.data));
let hbh_repr = check!(Ipv6HopByHopRepr::parse(&hbh_hdr));

for opt_repr in &hbh_repr.options {
match opt_repr {
Ipv6OptionRepr::Pad1 | Ipv6OptionRepr::PadN(_) => (),
#[cfg(feature = "proto-rpl")]
Ipv6OptionRepr::Rpl(_) => {}

Ipv6OptionRepr::Unknown { type_, .. } => {
match Ipv6OptionFailureType::from(*type_) {
Ipv6OptionFailureType::Skip => (),
Ipv6OptionFailureType::Discard => {
return HopByHopResponse::Discard(None);
}
Ipv6OptionFailureType::DiscardSendAll => {
return HopByHopResponse::Discard(param_problem());
}
Ipv6OptionFailureType::DiscardSendUnicast
if !ipv6_repr.dst_addr.is_multicast() =>
{
return HopByHopResponse::Discard(param_problem());
}
_ => unreachable!(),
}
}
}
}

HopByHopResponse::Continue((
ext_repr.next_header,
&ip_payload[ext_repr.header_len() + ext_repr.data.len()..],
))
}

/// Given the next header value forward the payload onto the correct process
/// function.
pub(super) fn process_nxt_hdr<'frame>(
fn process_nxt_hdr<'frame>(
&mut self,
sockets: &mut SocketSet,
meta: PacketMeta,
Expand Down Expand Up @@ -81,10 +167,6 @@ impl InterfaceInner {
#[cfg(feature = "socket-tcp")]
IpProtocol::Tcp => self.process_tcp(sockets, ipv6_repr.into(), ip_payload),

IpProtocol::HopByHop => {
self.process_hopbyhop(sockets, meta, ipv6_repr, handled_by_raw_socket, ip_payload)
}

#[cfg(feature = "socket-raw")]
_ if handled_by_raw_socket => None,

Expand Down Expand Up @@ -235,70 +317,27 @@ impl InterfaceInner {
}
}

pub(super) fn process_hopbyhop<'frame>(
&mut self,
sockets: &mut SocketSet,
meta: PacketMeta,
ipv6_repr: Ipv6Repr,
handled_by_raw_socket: bool,
ip_payload: &'frame [u8],
) -> Option<IpPacket<'frame>> {
let ext_hdr = check!(Ipv6ExtHeader::new_checked(ip_payload));
let ext_repr = check!(Ipv6ExtHeaderRepr::parse(&ext_hdr));
let hbh_hdr = check!(Ipv6HopByHopHeader::new_checked(ext_repr.data));
let hbh_repr = check!(Ipv6HopByHopRepr::parse(&hbh_hdr));

for opt_repr in &hbh_repr.options {
match opt_repr {
Ipv6OptionRepr::Pad1 | Ipv6OptionRepr::PadN(_) => (),
#[cfg(feature = "proto-rpl")]
Ipv6OptionRepr::Rpl(_) => {}

Ipv6OptionRepr::Unknown { type_, .. } => {
match Ipv6OptionFailureType::from(*type_) {
Ipv6OptionFailureType::Skip => (),
Ipv6OptionFailureType::Discard => {
return None;
}
_ => {
// FIXME(dlrobertson): Send an ICMPv6 parameter problem message
// here.
return None;
}
}
}
}
}
self.process_nxt_hdr(
sockets,
meta,
ipv6_repr,
ext_repr.next_header,
handled_by_raw_socket,
&ip_payload[ext_repr.header_len() + ext_repr.data.len()..],
)
}

pub(super) fn icmpv6_reply<'frame, 'icmp: 'frame>(
&self,
ipv6_repr: Ipv6Repr,
icmp_repr: Icmpv6Repr<'icmp>,
) -> Option<IpPacket<'frame>> {
if ipv6_repr.dst_addr.is_unicast() {
let ipv6_reply_repr = Ipv6Repr {
src_addr: ipv6_repr.dst_addr,
dst_addr: ipv6_repr.src_addr,
next_header: IpProtocol::Icmpv6,
payload_len: icmp_repr.buffer_len(),
hop_limit: 64,
};
Some(IpPacket::new_ipv6(
ipv6_reply_repr,
IpPayload::Icmpv6(icmp_repr),
))
let src_addr = if ipv6_repr.dst_addr.is_unicast() {
ipv6_repr.dst_addr
} else {
// Do not send any ICMP replies to a broadcast destination address.
None
}
self.ipv6_addr().unwrap()
};

let ipv6_reply_repr = Ipv6Repr {
src_addr,
dst_addr: ipv6_repr.src_addr,
next_header: IpProtocol::Icmpv6,
payload_len: icmp_repr.buffer_len(),
hop_limit: 64,
};
Some(IpPacket::new_ipv6(
ipv6_reply_repr,
IpPayload::Icmpv6(icmp_repr),
))
}
}
148 changes: 145 additions & 3 deletions src/iface/interface/tests/ipv6.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,129 @@ fn hop_by_hop_discard_with_icmp(#[case] medium: Medium) {
);
}

#[rstest]
#[case::ip(Medium::Ip)]
#[cfg(feature = "medium-ip")]
fn hop_by_hop_discard_param_problem(#[case] medium: Medium) {
use crate::iface::ip_packet::Ipv6Packet;

// The following contains:
// - IPv6 header
// - Hop-by-hop, with options:
// - PADN (skipped)
// - Unknown option (discard + ParamProblem)
// - ICMP echo request
let data = [
0x60, 0x0, 0x0, 0x0, 0x0, 0x1b, 0x0, 0x40, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x1, 0x3a, 0x0, 0xC0, 0x0, 0x40, 0x0, 0x1, 0x0, 0x80, 0x0, 0x2c, 0x88,
0x0, 0x2a, 0x1, 0xa4, 0x4c, 0x6f, 0x72, 0x65, 0x6d, 0x20, 0x49, 0x70, 0x73, 0x75, 0x6d,
];

let response = Some(IpPacket::Ipv6(Ipv6Packet {
header: Ipv6Repr {
src_addr: Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 1),
dst_addr: Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 2),
next_header: IpProtocol::Icmpv6,
payload_len: 75,
hop_limit: 64,
},
#[cfg(feature = "proto-ipv6-hbh")]
hop_by_hop: None,
#[cfg(feature = "proto-ipv6-routing")]
routing: None,
payload: IpPayload::Icmpv6(Icmpv6Repr::ParamProblem {
reason: Icmpv6ParamProblem::UnrecognizedOption,
pointer: 40,
header: Ipv6Repr {
src_addr: Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 2),
dst_addr: Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 1),
next_header: IpProtocol::HopByHop,
payload_len: 27,
hop_limit: 64,
},
data: &[
0x3a, 0x0, 0xC0, 0x0, 0x40, 0x0, 0x1, 0x0, 0x80, 0x0, 0x2c, 0x88, 0x0, 0x2a, 0x1,
0xa4, 0x4c, 0x6f, 0x72, 0x65, 0x6d, 0x20, 0x49, 0x70, 0x73, 0x75, 0x6d,
],
}),
}));

let (mut iface, mut sockets, _device) = setup(medium);

assert_eq!(
iface.inner.process_ipv6(
&mut sockets,
PacketMeta::default(),
&Ipv6PacketWire::new_checked(&data[..]).unwrap()
),
response
);
}

#[rstest]
#[case::ip(Medium::Ip)]
#[cfg(feature = "medium-ip")]
fn hop_by_hop_discard_with_multicast(#[case] medium: Medium) {
use crate::iface::ip_packet::Ipv6Packet;

// The following contains:
// - IPv6 header
// - Hop-by-hop, with options:
// - PADN (skipped)
// - Unknown option (discard (0b11) + ParamProblem)
// - ICMP echo request
//
// In this case, even if the destination address is a multicast address, an ICMPv6 ParamProblem
// should be transmitted.
let data = [
0x60, 0x0, 0x0, 0x0, 0x0, 0x1b, 0x0, 0x40, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xff, 0x02, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x1, 0x3a, 0x0, 0x80, 0x0, 0x40, 0x0, 0x1, 0x0, 0x80, 0x0, 0x2c, 0x88,
0x0, 0x2a, 0x1, 0xa4, 0x4c, 0x6f, 0x72, 0x65, 0x6d, 0x20, 0x49, 0x70, 0x73, 0x75, 0x6d,
];

let response = Some(IpPacket::Ipv6(Ipv6Packet {
header: Ipv6Repr {
src_addr: Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1),
dst_addr: Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 2),
next_header: IpProtocol::Icmpv6,
payload_len: 75,
hop_limit: 64,
},
#[cfg(feature = "proto-ipv6-hbh")]
hop_by_hop: None,
#[cfg(feature = "proto-ipv6-routing")]
routing: None,
payload: IpPayload::Icmpv6(Icmpv6Repr::ParamProblem {
reason: Icmpv6ParamProblem::UnrecognizedOption,
pointer: 40,
header: Ipv6Repr {
src_addr: Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 2),
dst_addr: Ipv6Address::new(0xff02, 0, 0, 0, 0, 0, 0, 1),
next_header: IpProtocol::HopByHop,
payload_len: 27,
hop_limit: 64,
},
data: &[
0x3a, 0x0, 0x80, 0x0, 0x40, 0x0, 0x1, 0x0, 0x80, 0x0, 0x2c, 0x88, 0x0, 0x2a, 0x1,
0xa4, 0x4c, 0x6f, 0x72, 0x65, 0x6d, 0x20, 0x49, 0x70, 0x73, 0x75, 0x6d,
],
}),
}));

let (mut iface, mut sockets, _device) = setup(medium);

assert_eq!(
iface.inner.process_ipv6(
&mut sockets,
PacketMeta::default(),
&Ipv6PacketWire::new_checked(&data[..]).unwrap()
),
response
);
}

#[rstest]
#[case::ip(Medium::Ip)]
#[cfg(feature = "medium-ip")]
Expand Down Expand Up @@ -314,14 +437,33 @@ fn icmp_echo_reply_as_input(#[case] medium: Medium) {
#[case::ieee802154(Medium::Ieee802154)]
#[cfg(feature = "medium-ieee802154")]
fn unknown_proto_with_multicast_dst_address(#[case] medium: Medium) {
// Since the destination address is multicast, we should not answer with an ICMPv6 message.
let data = [
0x60, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc, 0x40, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xff, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x1,
];

let response = None;
let response = Some(IpPacket::new_ipv6(
Ipv6Repr {
src_addr: Ipv6Address::from_parts(&[0xfe80, 0, 0, 0, 0, 0, 0, 0x0001]),
dst_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002]),
hop_limit: 64,
next_header: IpProtocol::Icmpv6,
payload_len: 48,
},
IpPayload::Icmpv6(Icmpv6Repr::ParamProblem {
reason: Icmpv6ParamProblem::UnrecognizedNxtHdr,
pointer: 40,
header: Ipv6Repr {
src_addr: Ipv6Address::from_parts(&[0xfdbe, 0, 0, 0, 0, 0, 0, 0x0002]),
dst_addr: Ipv6Address::from_parts(&[0xff02, 0, 0, 0, 0, 0, 0, 0x0001]),
hop_limit: 64,
next_header: IpProtocol::Unknown(0x0c),
payload_len: 0,
},
data: &[],
}),
));

let (mut iface, mut sockets, _device) = setup(medium);

Expand All @@ -343,7 +485,7 @@ fn unknown_proto_with_multicast_dst_address(#[case] medium: Medium) {
#[case::ieee802154(Medium::Ieee802154)]
#[cfg(feature = "medium-ieee802154")]
fn unknown_proto(#[case] medium: Medium) {
// Since the destination address is multicast, we should not answer with an ICMPv6 message.
// Since the destination address is multicast, we should answer with an ICMPv6 message.
let data = [
0x60, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc, 0x40, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xfd, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
Expand Down

0 comments on commit cc1b038

Please sign in to comment.