-
Notifications
You must be signed in to change notification settings - Fork 447
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
Add raw sockets #18
Add raw sockets #18
Conversation
Looks like the recent nightly broke the |
This... is actually really troublesome. |
This is 100% OK, I was going to start using those myself.
I dislike rustfmt and hand-format all of my code; so please don't mind if I ask to fix some particularly egregious cases. |
Done. |
src/common/mod.rs
Outdated
@@ -0,0 +1 @@ | |||
pub mod ring_buffer; |
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'd prefer this to be a pub mod smoltcp::storage
, with associated documentation.
src/common/ring_buffer.rs
Outdated
@@ -0,0 +1,118 @@ | |||
use managed::Managed; | |||
|
|||
pub trait Resetable { |
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.
Resettable
, should be in smoltcp::storage
, and needs documentation to explain the rationale.
src/common/ring_buffer.rs
Outdated
pub struct RingBuffer<'a, T: 'a> { | ||
storage: Managed<'a, [T]>, | ||
read_at: usize, | ||
length: usize, |
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 it if you at least kept the formatting as-is when moving code around. My preferences notwithstanding this makes git's copy/move tracking mechanisms work better.
src/iface/ethernet.rs
Outdated
for raw_socket in sockets.iter_mut().filter_map(Socket::as_raw) { | ||
if let Ok(()) = raw_socket.process(timestamp, &IpRepr::Ipv4(ipv4_repr), | ||
ipv4_packet.payload()) { | ||
return Ok(()); |
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.
That's not how raw sockets work on hosted systems. To quote Linux man pages:
When a packet is received, it is passed to any raw sockets which have been bound to its protocol before it is passed to other protocol handlers (e.g., kernel protocol modules).
Note that it does not say that the packet is not passed to other protocol handlers. Indeed, that's why TCP sockets still work even if you're running tcpdump (or ping) concurrently.
src/socket/raw.rs
Outdated
/// transmit and receive packet buffers. | ||
#[derive(Debug)] | ||
pub struct RawSocket<'a, 'b: 'a> { | ||
ip_addr: IpAddress, |
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.
A raw socket isn't bound to an IP address, only to a protocol.
src/socket/raw.rs
Outdated
/// Enqueue a packet to be sent to the given IP address, and fill it from a slice. | ||
/// | ||
/// See also [send](#method.send). | ||
pub fn send_slice(&mut self, data: &[u8], ip_addr: IpAddress) -> Result<usize, ()> { |
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's no need to accept IpAddress in send, or provide it in recv; it is the responsibility of the client to dissect the packet.
let startup_time = Instant::now(); | ||
|
||
loop { | ||
{ |
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.
This scope isn't necessary since ping only needs one socket.
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's necessary because both sockets.get_mut
and iface.poll
take a mutable reference to sockets
. It's the same as in the client.rs
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.
Oh, of course.
examples/ping.rs
Outdated
"Can't allocate packet", | ||
); | ||
let mut packet = | ||
Icmpv4Packet::new(raw_payload).expect("Can't create ICMPv4 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.
Let's just unwrap()
. If we're going to add more proper error handling to examples it should be done in sync (and use error!
, not expect()
).
Icmpv4Repr::parse(&packet) | ||
{ | ||
let seq_no = seq_no as usize; | ||
if let Some(_) = waiting_queue.get(&seq_no) { |
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 can drastically simplify this by embedding the timestamp in the sent packet :) As a bonus the ping program will be possible to run with no_std code after this change.
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 already do embed the timestamp, waiting_queue is used for timeout calculation.
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, I missed that, sorry. Hm... should be fine the way it is, then.
I'd like to keep |
Take a closer look at EthernetInterface. It already selects a source IP as "first IP that matches destination IP protocol" in a few code paths.
Why is a marker necessary? The first nibble of the packet has that already. |
Right, I see it now.
A raw socket is bound to a version of IP on creation, by using either AF_INET or AF_INET6 as the |
Let's add a |
Actually, hang on. It might be not so wise to tie it to IP. Raw sockets that receive, for example, Ethernet frames, 802.11 frames, 802.15.4 frames, are all still useful. Let's make a enum specifically listing what a raw socket can receive, instead. |
README.md
Outdated
@@ -214,6 +214,23 @@ cargo run --example client -- tap0 ADDRESS PORT | |||
It connects to the given address (not a hostname) and port (e.g. `socat stdio tcp4-listen 1234`), | |||
and will respond with reversed chunks of the input indefinitely. | |||
|
|||
### examples/ping.rs | |||
|
|||
_examples/ping.rs_ implements a minimail version of the `ping` utility using raw sockets. |
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.
minimal
(just passing by)
Wait. |
Of course, IP addresses also have unspecified ranges, so we can make EthernetInterface look at that too. |
So, I make an Unspecified repr from Ipv4Repr, turning all-zero src_addr into an Unspecified addr. It seems to work with all-zero src_addr in |
I think it's a better idea to change the condition in EthernetInterface to check for unspecified source address in any kind of IpRepr (via a query method on the latter); this will make any kind of sockets, such as ICMP sockets, work the same. |
Why is |
I'd like to have it as an associated constant but those aren't in stable yet. |
They'll be stable soon, rust-lang/rust#29646. |
I'm not sure if I should remove the address replacement logic from |
Your |
Done. I'm just not sure if the src address replacement should be a part of lowering IpRepr to concrete representation, it looks like two separate actions to me. But if |
Address replacement is the whole point of IpRepr::lower, the exact representation details are irrelevant. The name might not have been the best choice. And I'm not happy with your fix. There shouldn't be a |
Thanks! Any chance you could update (or add?) a test for |
src/iface/ethernet.rs
Outdated
@@ -179,6 +179,11 @@ impl<'a, 'b, 'c, DeviceT: Device + 'a> Interface<'a, 'b, 'c, DeviceT> { | |||
ð_frame.src_addr()); | |||
} | |||
|
|||
for raw_socket in sockets.iter_mut().filter_map(Socket::as_raw) { | |||
let _ = raw_socket.process(timestamp, &IpRepr::Ipv4(ipv4_repr), |
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 worried about this let _ =
. What errors can happen here besides Exhausted
?
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.
Error::Rejected, and it definitely should be ignored.
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 right, so only that. Then can you add a match that ignores this error and panics on any other? That will be robust in light of code evolution (or help when fuzzing).
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 mean return Err(e)
, not panic? Becuase you do return Err(e)
for socket errors.
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.
Panics, through unreachable!()
, since those branches should be unreachable indeed.
src/socket/mod.rs
Outdated
Socket::Raw(ref mut raw) => Some(raw), | ||
_ => 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.
I don't think single-use helper functions are necessary. Just use as_socket
where this would be called...
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.
Oh, this is a different issue, as_socket
doesn't let us interrogate. Sure. Let's add try_as_socket
in addition to as_socket
to the AsSocket
trait, that would be much cleaner.
src/socket/raw.rs
Outdated
#[derive(Debug)] | ||
pub struct RawSocket<'a, 'b: 'a> { | ||
payload_type: PayloadType, | ||
ip_proto: IpProtocol, |
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 is no need to have separate payload_type
and ip_proto
fields because the latter is strictly derived from the former.
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? payload_type
could be Ipv4 or Ipv6 and ip_proto is independent of that.
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.
Oh, of course.
Okay, just rename it to ip_protocol
.
PayloadType::Ipv4 => { | ||
let mut ipv4_packet = Ipv4Packet::new(packet_buf.as_mut())?; | ||
ipv4_packet.fill_checksum(); | ||
let ipv4_packet = Ipv4Packet::new(&*ipv4_packet.into_inner())?; |
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 the reinitialization?
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.
Ipv4Repr::parse
requires non-mutable Ipv4Packet
.
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.
&mut T
to &T
is a valid cast, no?
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.
That what I thought, but it doesn't work for me for some reason:
error[E0308]: mismatched types
--> socket/raw.rs:185:49
|
185 | let ipv4_repr = Ipv4Repr::parse(&ipv4_packet)?;
| ^^^^^^^^^^^^ types differ in mutability
|
= note: expected type `&wire::ipv4::Packet<&_>`
found type `&wire::ipv4::Packet<&mut [u8]>`
= help: here are some functions which might fulfill your needs:
- .into_inner()
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.
Ohhh right, that's an artifact of how my wire
impls are done, nevermind.
src/storage/ring_buffer.rs
Outdated
/// | ||
/// In-palace analog of Default. | ||
/// Used by RingBuffer for initializing a storage. | ||
pub trait Resetable { |
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.
Resettable
, and let's move it to smoltcp::storage
since it is not really specific to ring buffers.
src/storage/ring_buffer.rs
Outdated
|
||
/// A trait for setting a value to the default state. | ||
/// | ||
/// In-palace analog of Default. |
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-place
Okay, this is much better. After the last round of review I don't think there will be any further problems. |
I believe that's all with the change requests. |
Wonderful, thanks! Do you think you can rebase the commits? |
Something like this? |
Yup! I'll merge tomorrow, I think. |
Good. I have some ideas about packet dispatching but I think this time I'll open an issue to discuss it first. |
Sure, go ahead! |
Merged, with stylistic, documentation, and formatting changes. No functional changes. Most notably I've replaced |
@batonius Incidentally--did you decide to do anything about ICMP sockets? I think (out of stuff that redox supports right now) ICMP sockets and DHCP support are definitely in scope of smoltcp, and DNS support almost certainly is. At the very least the |
My current goal is to replace the two low-level network daemons, ethernetd and ipd, with a unified one based on smoltcp, while keeping the interface ipd provides to tcpd, udpd and icmpd, which is basically raw sockets. Eventually, I'd like to have ICMP sockets, DHCP and DNS all integrated into that unified daemon, but only after I integrate udpd and tcpd into it. |
Sounds good to me! |
Problem: smoltcp doesn't support raw sockets, low-level utilities like
ping
and custom IP protocols are impossible to implement.Solution: implement raw sockets, provide an example of their usage.
Changes introduced by this pull request:
socket::udp::SocketBuffer
has been factored out as genericcommon::RingBuffer
.socket::raw
module has been implemented.iface::EthernetInterface
has been changed to support raw sockets, giving them priority over the built-in handlers.ping
example has been implemented.Status: the
ping
example works as expected.TODOs:
SocketSet
container should be complimented with lookup tables for raw and tcp/udp sockets.Other: