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
IGMP processing #178
Conversation
1ae88cd
to
6d2c499
Compare
There was a problem hiding this 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.
src/iface/ethernet.rs
Outdated
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, ()>, |
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 ManagedMap
s one for IPv4 and the other for IPv6.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
src/iface/ethernet.rs
Outdated
/// | ||
/// [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> |
There was a problem hiding this comment.
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.
src/iface/ethernet.rs
Outdated
|
||
Ok(()) | ||
} | ||
// Multicast is not yet implemented for other address families |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
src/iface/ethernet.rs
Outdated
|
||
Ok(()) | ||
} | ||
// Multicast is not yet implemented for other address families |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same.
src/iface/ethernet.rs
Outdated
} | ||
|
||
/// Check whether the interface listens to given destination multicast IP address. | ||
#[cfg(feature = "proto-ipv4")] |
There was a problem hiding this comment.
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.
☔ The latest upstream changes (presumably 53abe8c) made this pull request unmergeable. Please resolve the merge conflicts. |
I've rebased onto current master. |
There was a problem hiding this 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.
src/iface/ethernet.rs
Outdated
@@ -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]; |
There was a problem hiding this comment.
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
.
src/iface/ethernet.rs
Outdated
/// 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" |
There was a problem hiding this comment.
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)]
.
src/iface/ethernet.rs
Outdated
#[cfg(not(feature = "proto-ipv4"))] | ||
let reported_any = false; | ||
|
||
if processed_any || emitted_any || reported_any { | ||
readiness_may_have_changed = true; |
There was a problem hiding this comment.
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.
src/iface/ethernet.rs
Outdated
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 |
There was a problem hiding this comment.
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).
src/iface/ethernet.rs
Outdated
@@ -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 |
There was a problem hiding this comment.
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.
src/phy/sys/raw_socket.rs
Outdated
@@ -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()) } |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
src/socket/raw.rs
Outdated
@@ -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)] |
There was a problem hiding this comment.
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
.
Ready for the next round of review :-) |
There was a problem hiding this 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" = [] |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here too.
examples/multicast.rs
Outdated
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]); |
There was a problem hiding this comment.
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] |
There was a problem hiding this comment.
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!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@podhrmic did that
src/iface/ethernet.rs
Outdated
neighbor_cache: NeighborCache<'b>, | ||
ethernet_addr: EthernetAddress, | ||
ip_addrs: ManagedSlice<'c, IpCidr>, | ||
#[cfg(feature = "proto-ipv4")] | ||
ipv4_gateway: Option<Ipv4Address>, | ||
#[cfg(feature = "proto-ipv4")] |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
src/iface/ethernet.rs
Outdated
timestamp + interval, | ||
interval, | ||
first_group_addr.clone() | ||
); |
There was a problem hiding this comment.
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.
src/iface/ethernet.rs
Outdated
|
||
#[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| { |
There was a problem hiding this comment.
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])> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why?
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok!
src/iface/ethernet.rs
Outdated
dst_addr: group_addr, | ||
protocol: IpProtocol::Igmp, | ||
payload_len: igmp_repr.buffer_len(), | ||
hop_limit: 1, |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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.
Next round. |
What is the status here? @astro do you need help implementing the changes? |
It's blocked on review that I haven't had time to perform yet, sorry. |
@whitequark can I help here in any way to get this merged? |
I'll try to review it soon |
@whitequark any updates here? |
src/iface/ethernet.rs
Outdated
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(); |
There was a problem hiding this comment.
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.
There was a problem hiding this 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.
examples/multicast.rs
Outdated
|
||
if socket.can_recv() { | ||
// For display purposes only - normally we wouldn't process incoming IGMP packets | ||
// in the applicaiton layer |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It does. I verified.
There was a problem hiding this comment.
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.
examples/multicast.rs
Outdated
} | ||
} | ||
|
||
let _poll_at = iface.poll(&mut sockets, timestamp); // ignore the errors (or perhaps log them) |
There was a problem hiding this comment.
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.
src/iface/ethernet.rs
Outdated
let mut next_addrs = self.inner.ipv4_mcast_groups | ||
.iter() | ||
.map(|(group_addr, &())| group_addr) | ||
.skip_while(|group_addr| *group_addr != &addr); |
There was a problem hiding this comment.
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
.
src/iface/ethernet.rs
Outdated
/// | ||
/// If built without feature `proto-igmp` this function will | ||
/// always return `false`. | ||
pub fn has_mcast_group<T: Into<IpAddress>>(&self, addr: T) -> bool { |
There was a problem hiding this comment.
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.
@podhrmic See comments above. |
Added commits addressing your latest review. Please verify. |
src/iface/ethernet.rs
Outdated
let igmp_repr = match IgmpRepr::parse(&igmp_packet) { | ||
Ok(igmp_repr) => igmp_repr, | ||
Err(_) => return Ok(Packet::None), | ||
}; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
☔ The latest upstream changes (presumably ce2fbb7) made this pull request unmergeable. Please resolve the merge conflicts. |
☔ The latest upstream changes (presumably 06d1813) made this pull request unmergeable. Please resolve the merge conflicts. |
I'm verry sorry, but could you please tell me at which point we are in the review process? |
@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. |
I corrected the description of IGMP report intervals to general queries in All your code changes make sense to me. Thanks for the great work! |
Thanks for the fixes, and I apologize again for the delay. Can you squash? |
Squashed! |
@m-labs-homu r+ |
📌 Commit 777d4bd has been approved by |
Closes: #178 Approved by: whitequark
💥 Test timed out |
@m-labs-homu retry |
☀️ Test successful - status-travis |
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.