Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IGMP processing #178

Closed
wants to merge 1 commit into from
Closed

IGMP processing #178

wants to merge 1 commit into from

Conversation

astro
Copy link
Contributor

@astro astro commented Mar 5, 2018

This is on top of #177

Once you start review, please keep in mind that this requires a rust-managed release with the ManagedMap.iter() pull request.

@astro astro mentioned this pull request Mar 5, 2018
@astro astro force-pushed the igmp-processing branch 4 times, most recently from 1ae88cd to 6d2c499 Compare March 6, 2018 19:28
Copy link
Collaborator

@dlrobertson dlrobertson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for picking up work on this! This PR could actually help the effort towards IPv6 support.

neighbor_cache: NeighborCache<'b>,
ethernet_addr: EthernetAddress,
ip_addrs: ManagedSlice<'c, IpCidr>,
#[cfg(feature = "proto-ipv4")]
ipv4_gateway: Option<Ipv4Address>,
#[cfg(feature = "proto-ipv4")]
ipv4_mcast_groups: ManagedMap<'e, Ipv4Address, ()>,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

multicast addresses are used in IPv6 as well. See: MLDv2. The first implementation may only store IpAddress::Ipv4 variants in this map, but I think the map should contain IpAddress instead of Ipv4Address.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unlike ip_addrs (for IPv4) you may join a number of of groups. I didn't want to waste 16 bytes per IPv4 address. My use-case is Embedded Rust.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it may optimize space for use-cases that are IPv4 only, but for use cases that are IPv4 and IPv6 we then have two ManagedMaps one for IPv4 and the other for IPv6.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even in dual-stack mode we save 12 bytes per IPv4 multicast group. Do you think that is not worth it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would really like to keep IPv4 and IPv6 mcast groups in separate maps.

Memory space efficiency shouldn't depend on disabling IPv6. Seen that way, doing that could even reduce IPv6 adoption.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would still prefer that we have one map, but I can be okay with separate maps and I don't think it should keep this PR from getting merged.

///
/// [join_multicast_group]: struct.EthernetInterface.html#method.join_multicast_group
#[cfg(feature = "proto-ipv4")]
pub fn ipv4_mcast_groups<T>(mut self, ipv4_mcast_groups: T) -> InterfaceBuilder<'b, 'c, 'e, DeviceT>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, I don't think this should be IPv4 specific.


Ok(())
}
// Multicast is not yet implemented for other address families
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you mean IGMP is not implemented in other address families. Multicast is a requirement for IPv6. See MLDv2.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MLD is not yet implemented in smoltcp.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, this comment made me think it was saying Multicast doesn't exist for other families. My bad.


Ok(())
}
// Multicast is not yet implemented for other address families
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same.

}

/// Check whether the interface listens to given destination multicast IP address.
#[cfg(feature = "proto-ipv4")]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, shouldn't be IPv4 specific.

@m-labs-homu
Copy link

☔ The latest upstream changes (presumably 53abe8c) made this pull request unmergeable. Please resolve the merge conflicts.

@astro
Copy link
Contributor Author

astro commented Mar 12, 2018

I've rebased onto current master.

Copy link
Contributor

@whitequark whitequark left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In addition to inline comments, I think IGMP should get a proto-igmp feature flag since it is not mandatory and I imagine smallest systems would want to omit the considerable amount of code it adds.

@@ -38,14 +44,19 @@ use socket::UdpSocket;
use socket::TcpSocket;
use super::{NeighborCache, NeighborAnswer};

#[cfg(feature = "proto-ipv4")]
const IPV4_MCAST_ALL_SYSTEMS: [u8; 4] = [224, 0, 0, 1];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These should be associated consts in Ipv4Address.

/// Returns whether address was added successfully, and, if
/// possible, whether an initial immediate announcement has been
/// sent.
// timestamp is unused in absence of feature "proto-ipv4"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just call it _timestamp, this is preferred over #[allow(unused)].

#[cfg(not(feature = "proto-ipv4"))]
let reported_any = false;

if processed_any || emitted_any || reported_any {
readiness_may_have_changed = true;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAICT emitting an IGMP packet cannot change readiness of any socket.

fn process_ethernet<'frame, T: AsRef<[u8]>>
(&mut self, sockets: &mut SocketSet, timestamp: Instant, frame: &'frame T) ->
Result<Packet<'frame>>
{
let eth_frame = EthernetFrame::new_checked(frame)?;

// Ignore any packets not directed to our hardware address.
// Check whether to keep the packet
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like a pointless change (here and below).

@@ -734,6 +950,80 @@ impl<'b, 'c> InterfaceInner<'b, 'c> {
}
}

/// Host duties of the **IGMPv2** protocol
///
/// and we are not worried about routing i.e. we can ignore
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lots of missing words, punctuation, bad formatting, etc in this doc comment.

@@ -68,7 +68,7 @@ impl RawSocketDesc {
unsafe {
let len = libc::send(self.lower, buffer.as_ptr() as *const libc::c_void,
buffer.len(), 0);
if len == -1 { Err(io::Error::last_os_error()).unwrap() }
if len == -1 { return Err(io::Error::last_os_error()) }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this change? The unwrap was deliberate.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please elaborate why you should not be able to gracefully handle the error?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC, my reasoning was that this call should never fail.

@@ -154,6 +154,8 @@ impl<'a, 'b> RawSocket<'a, 'b> {
pub(crate) fn dispatch<F>(&mut self, checksum_caps: &ChecksumCapabilities, emit: F) ->
Result<()>
where F: FnOnce((IpRepr, &[u8])) -> Result<()> {
// checksum_caps is unused in absence of feature "proto-ipv4"
#[allow(unused)]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above re: _timeout.

@astro
Copy link
Contributor Author

astro commented Mar 13, 2018

Ready for the next round of review :-)

Copy link
Contributor

@whitequark whitequark left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good, but needs some changes!

Cargo.toml Outdated
@@ -32,14 +32,15 @@ verbose = []
"phy-tap_interface" = ["std", "libc"]
"proto-ipv4" = []
"proto-ipv6" = []
"proto-igmp" = []
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: since IGMP is a part of IPv4 protocol suite, can you please put it after proto-ipv4?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also proto-igmp should imply proto-ipv4.

Cargo.toml Outdated
"socket-raw" = []
"socket-udp" = []
"socket-tcp" = []
"socket-icmp" = []
default = [
"std", "log", # needed for `cargo test --no-default-features --features default` :/
"phy-raw_socket", "phy-tap_interface",
"proto-ipv4", "proto-ipv6",
"proto-ipv4", "proto-ipv6", "proto-igmp",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here too.

let mut sockets = SocketSet::new(vec![]);

// Must fit at least one IGMP packet
let raw_rx_buffer = RawSocketBuffer::new(vec![RawPacketMetadata::empty(); 2], vec![0; 512]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These will need to be updated for current master.

@@ -0,0 +1,111 @@
#[macro_use]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 great that you wrote an example!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@podhrmic did that

neighbor_cache: NeighborCache<'b>,
ethernet_addr: EthernetAddress,
ip_addrs: ManagedSlice<'c, IpCidr>,
#[cfg(feature = "proto-ipv4")]
ipv4_gateway: Option<Ipv4Address>,
#[cfg(feature = "proto-ipv4")]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be gated by proto-igmp?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right! Thank you for pointing out.

IgmpVersion::Version1 =>
Duration::from_millis(100),
IgmpVersion::Version2 => {
// No dependence on a random generator
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should probably be an issue about adding RNG to IGMP code.

timestamp + interval,
interval,
first_group_addr.clone()
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain why you cannot just report immediately? Preferably in the comments.


#[cfg(all(feature = "proto-ipv4", feature = "proto-igmp"))]
fn igmp_report_packet<'any>(&self, version: IgmpVersion, group_addr: Ipv4Address) -> Option<Packet<'any>> {
self.ipv4_address().map(|iface_addr| {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer a let iface_addr = match self.ipv4_address() { ... } here.

@@ -155,13 +155,13 @@ impl<'a, 'b> RawSocket<'a, 'b> {
Result<()>
where F: FnOnce((IpRepr, &[u8])) -> Result<()> {
fn prepare<'a>(protocol: IpProtocol, buffer: &'a mut [u8],
checksum_caps: &ChecksumCapabilities) -> Result<(IpRepr, &'a [u8])> {
_checksum_caps: &ChecksumCapabilities) -> Result<(IpRepr, &'a [u8])> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused when feature = "proto-ipv4" is absent.

src/wire/igmp.rs Outdated
@@ -173,7 +173,7 @@ impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum Repr {
MembershipQuery {
max_resp_time: Duration,
max_resp_duration: Duration,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rebase error? I specifically renamed it to max_resp_time, since that's how the field is called in the RFC.

Copy link
Contributor Author

@astro astro Mar 24, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I disagree, because in Repr we're no longer dealing with the max_resp_time code from Packet but with a proper Duration. I'll rename it back if you don't agree that this is a significant difference between the two.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The concept is the same (maximum response time), the units are different (floating point in i8 vs Duration). You could call it max_resp_time_fp and max_resp_time_duration but that's annoyingly long.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok!

@whitequark whitequark mentioned this pull request Mar 24, 2018
dst_addr: group_addr,
protocol: IpProtocol::Igmp,
payload_len: igmp_repr.buffer_len(),
hop_limit: 1,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From #60:

the TTL value for group query has to be set to 1, and to 255 for membership report (according to Wireshark and RFC 3717)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like the semantics of TTL=255, that is: reporting group membership to every group listener across routers. I'm not seeing it in the wild either.

As this is IGMPv2 being implemented here, not IP over Optical Networks: A Framework(?), from RFC 2236:

All IGMP messages described in this document are sent with IP TTL 1, and contain the IP Router Alert option [RFC 2113] in their IP header.
[…]
When a group's timer expires, the host multicasts a Version 2 Membership Report to the group, with IP TTL of 1.

Ok, I didn't know about the Router Alert option… but I guess the comment regarding TTL might have been a mistake.

@astro
Copy link
Contributor Author

astro commented Mar 24, 2018

Next round.

@podhrmic
Copy link
Contributor

podhrmic commented Apr 2, 2018

What is the status here? @astro do you need help implementing the changes?

@whitequark
Copy link
Contributor

It's blocked on review that I haven't had time to perform yet, sorry.

@podhrmic
Copy link
Contributor

@whitequark can I help here in any way to get this merged?

@whitequark
Copy link
Contributor

I'll try to review it soon

@podhrmic
Copy link
Contributor

@whitequark any updates here?

pub fn ipv4_mcast_groups<T>(mut self, ipv4_mcast_groups: T) -> Self
where T: Into<ManagedMap<'e, Ipv4Address, ()>>
{
self.ipv4_mcast_groups = ipv4_mcast_groups.into();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to decide what to do here with storage that isn't empty. Do we clear it? Or do we allow bootstrapping an interface already joined to multicast groups? I'm leaning to the latter because it lets you do things like saving state across reboots, if you want.

Either choice needs to be documented.

Copy link
Contributor

@whitequark whitequark left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks again for doing all this work! I did one more round of review, and I believe that once these issues are fixed, this can be merged right away.


if socket.can_recv() {
// For display purposes only - normally we wouldn't process incoming IGMP packets
// in the applicaiton layer
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this code processes an IGMP packet.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does. I verified.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm very sorry. You're right.

}
}

let _poll_at = iface.poll(&mut sockets, timestamp); // ignore the errors (or perhaps log them)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The other examples use expect, so this one should too.

let mut next_addrs = self.inner.ipv4_mcast_groups
.iter()
.map(|(group_addr, &())| group_addr)
.skip_while(|group_addr| *group_addr != &addr);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I mean an index. Like an usize.

///
/// If built without feature `proto-igmp` this function will
/// always return `false`.
pub fn has_mcast_group<T: Into<IpAddress>>(&self, addr: T) -> bool {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

has_multicast_group for consistency with other functions.

@whitequark
Copy link
Contributor

@podhrmic See comments above.

@astro
Copy link
Contributor Author

astro commented Apr 25, 2018

Added commits addressing your latest review. Please verify.

let igmp_repr = match IgmpRepr::parse(&igmp_packet) {
Ok(igmp_repr) => igmp_repr,
Err(_) => return Ok(Packet::None),
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you do this? smoltcp intentionally pops up errors to the very top of the stack, such that if you receive malformed packets and nothing works, you do not have to wonder as to what is the cause.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You said you want the example to panic on Err. That was happening to me all the time due to IGMPv3 traffic and invalid checksums.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the explanation. I will revert this part, and make sure examples log errors instead of crashing on them. I did not realize that IGMPv3 traffic would cause this.

@m-labs-homu
Copy link

☔ The latest upstream changes (presumably ce2fbb7) made this pull request unmergeable. Please resolve the merge conflicts.

@m-labs-homu
Copy link

☔ The latest upstream changes (presumably 06d1813) made this pull request unmergeable. Please resolve the merge conflicts.

@astro
Copy link
Contributor Author

astro commented Jul 26, 2018

I'm verry sorry, but could you please tell me at which point we are in the review process?

@whitequark
Copy link
Contributor

@astro To expedite merging, I've fixed all remaining issues I found. Please review them, and squash the commits once you find the state of the branch satisfactory. I'll merge afterwards.

@astro
Copy link
Contributor Author

astro commented Jul 31, 2018

I corrected the description of IGMP report intervals to general queries in README.md.

All your code changes make sense to me. Thanks for the great work!

@whitequark
Copy link
Contributor

Thanks for the fixes, and I apologize again for the delay. Can you squash?

@astro
Copy link
Contributor Author

astro commented Jul 31, 2018

Squashed!

@whitequark
Copy link
Contributor

@m-labs-homu r+

@m-labs-homu
Copy link

📌 Commit 777d4bd has been approved by whitequark

m-labs-homu pushed a commit that referenced this pull request Jul 31, 2018
Closes: #178
Approved by: whitequark
@m-labs-homu
Copy link

⌛ Testing commit 777d4bd with merge bada3f7...

@m-labs-homu
Copy link

💥 Test timed out

@whitequark
Copy link
Contributor

@m-labs-homu retry

@m-labs-homu
Copy link

⌛ Testing commit 777d4bd with merge 58a5473...

@m-labs-homu
Copy link

☀️ Test successful - status-travis
Approved by: whitequark
Pushing 58a5473 to master...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants