The rate of emission of neighbor discovery packets is already
limited at the level of the entire neighbor cache, but poll()
would uselessly spin until the answer arrives (if ever).
- Do not send ICMPv4 responses for packets with a broadcast destination
address.
- Do not send DstUnreachable with ProtoUnreachable on receipt of a
packet with an unknown protocol with a non-unicast destination
address.
- Do not send DstUnreachable with PortUnreachable on receipt of a
UDP packet when no sockets are listening on the destination port
and the destination address is a non-unicast address.
- Send the correct amount of the original datagram when sending Destination
Unreachable error responses.
- Do not assume that a ip datagram has a payload when sending a proto
unreachable ICMPv4 error response.
- Add tests to iface tests.
- Ensure ICMP error responses are correctly formed when the
datagram has no payload.
- Ensure ICMP error responses are correctly handled for UDP packets
when no socket is listening on the destination port.
- Ensure the correct amount of the original payload is returned in
Destination Unreachable responses.
- Add tests for the following
- ICMP error responses are not sent in response to broadcast requests
- ARP requests are responded to and inserted into the cache
- ARP requests for someone else are not responded to, but the sender
is still inserted in the cache
- Add the ttl member to the IpRepr
- Add the ttl member along with setters and getters to the tcp and udp
socket types
- Add unit tests for the new set_ttl parameter
- Update usage of IpRepr to include the ttl value
To be precise, I'm talking about IPX, AppleTalk and DECnet here,
not things like PPPoE, ATAoE, FCoE, or PTP, which make sense
to implement on top of EthernetInterface but do not work on
the same level on top of it as IP.
This reverts commit 51b2f18d1165bf7257de8894df101299cc93b094.
There's no throughput difference so far as I could measure, but
this greatly increases worst-case latency. At some later point
we could perhaps pass a deadline to the poll function, but for now
reverting this is simple enough.
Typically, the poll function is used as a part of a larger RTOS.
If we only dispatch one packet per socket per poll() call,
then we have to wait for a complete scheduler roundtrip,
which is potentially a lot of time, and as a result we are
not filling the peer's window efficiently.
We still print them into our debug log though, because it has more
context; the caller may opt to ignore any poll errors and only
use the smoltcp debug log as a, well, debugging aid, or it could
print user-visible warnings to alert the user to unusual network
conditions.
Before this commit, anything that touched RawSocket or TapInterface
worked partly by accident and partly because of a horrible crutch
that resulted in massive latencies as well as inevitable packet loss
every time an ARP request had to be issued. Also, there was no way
to use poll() other than by continuously calling it in a busy loop.
After this commit, poll() indicates when the earliest timer expires,
and so the caller can sleep until that moment (or until packets
arrive).
Note that there is a subtle problem remaining: every time poll()
is called, every socket with a pending outbound packet whose
IP address doesn't correspond to a MAC address will send a new
ARP request, resulting in potentially a whole lot of such requests.
ARP rate limiting is a separate topic though.
This commit completely reworks packet dispatch in TCP sockets,
and brings significant improvements to processing as well.
In particular:
* Challenge ACKs now do not reset retransmit timer; instead,
TcpSocket::process directly returns a TcpRepr without altering
any internal state at all.
* Retransmit and close (aka TIME-WAIT) timers are unified
and restructured into a enum that actually matches semantics
of the timers.
* If a packet cannot be emitted, no internal state is changed.
* The dispatch of RST packets in case of connection abort
is brought in line with dispatch of all other packets.
* Packet dispatch now follows a series of steps with clean
separation of concerns, like packet processing:
1. If we should retransmit, update state to assume that
all in-flight packets are lost.
2. Prepare the packet that would be sent next, considering
the in-flight packets, if any.
3. Check if the packet contains anything new, or it's the same
as the one already in flight. If it is, bail.
4. Finalize and try to actually transmit the packet.
If we can't do that, bail.
5. Update the internal state to reflect that the packet
we've just sent is in flight.
We checked for frames too short before, but frames too long are
troublesome too, since e.g. TCP and UDP do not carry an explicit
payload length in their headers.
The use of this type has several drawbacks:
* It does not allow distinguishing between different error
conditions. In fact, we wrongly conflated some of them
before this commit.
* It does not allow propagation via ? and requires manual use
of map_err, which is especially tiresome for downstream code.
* It prevents us from expanding the set of error conditions
even if right now we have only one.
* It prevents us from blanket using Result<T> everywhere
(a nitpick at most).
Instead, use Result<T, Error> everywhere, and differentiate error
conditions where applicable.
Various parts of smoltcp require an arrow of time; a monotonically
increasing timestamp. Most obviously this is TCP sockets, but
the tracer and the pcap writer devices also benefit from having
timestamps. There are a few ways this could be implemented:
1. using a static Cell, global for the entire smoltcp crate;
2. using a static method on Device;
3. using an instance method on Device;
4. passing the current timestamp into *Interface::poll.
The first two options are undesirable because they create a notion
of global clock, and interfere e.g. with mocking.
The third option is undesirable because not all devices are
inherently tied to a particular clock, e.g. a loopback device isn't.
Therefore, the timestamp is injected into both sockets and devices
through the *Interface::poll method.
This is a form of an uninitialized read bug; although safe it caused
panics. In short, transmit buffers received from the network stack
should be considered uninitialized (in practice they will often
contain previously transmitted packets or parts thereof). Wrapping
them with the only method we had (e.g. Ipv4Packet) treated the buffer
as if it contained a valid incoming packet, which can easily fail
with Error::Truncated.
This commit splits every `fn new(buffer: T) -> Result<Self, Error>`
method on a `Packet` into three smaller ones:
* `fn check_len(&self) -> Result<(), Error>`, purely a validator;
* `fn new(T) -> Self`, purely a wrapper;
* `fn new_checked(T) -> Result<Self, Error>`, a validating wrapper.
This makes it easy to process ingress packets (using `new_checked`),
egress packets (using `new`), and, if needed, maintain the invariants
at any point during packet construction (using `check_len`).
Fixes#17.
Before this commit, IP payload length was calculated by subtracting
the IP header length from the total underlying buffer length, which
fails if the underlying buffer has padding, e.g. like Ethernet
does.