Skip to content

Commit

Permalink
Merge branch 'main' of https://github.com/smoltcp-rs/smoltcp into fix…
Browse files Browse the repository at this point in the history
…/mismatching-src-ip
  • Loading branch information
MathiasKoch committed Jan 13, 2025
2 parents d07c192 + 74bd1e5 commit 5e08aaf
Show file tree
Hide file tree
Showing 19 changed files with 820 additions and 152 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ jobs:
- name: Run Coverage
run: ./ci.sh coverage
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
uses: codecov/codecov-action@v5
with:
#token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos
token: ${{ secrets.CODECOV_TOKEN }}
files: lcov.info
fail_ci_if_error: false
55 changes: 51 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,59 @@
# Changelog
# Changelog
All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Changed
- iface: The `poll` function now only performs a single cycle of processing sockets ([#954](https://github.com/smoltcp-rs/smoltcp/pull/954))
No unreleased changes yet. Please send PRs!

## [0.12.0] - 2024-11-28

Almost a year in the making, the highlights of the release are the migration to `core::net` IP types, IPv6 multicast, TCP improvements, and many fixes. Smoltcp now connects your gadgets to the Internet better than ever.

- Minimum Supported Rust Version (MSRV) bumped to 1.80.
- iface
- IPv6 multicast ([#914](https://github.com/smoltcp-rs/smoltcp/pull/914), [#976](https://github.com/smoltcp-rs/smoltcp/pull/976), [#988](https://github.com/smoltcp-rs/smoltcp/pull/988), [#1009](https://github.com/smoltcp-rs/smoltcp/pull/1009), [#1012](https://github.com/smoltcp-rs/smoltcp/pull/1012))
- Add `poll_egress()` and `poll_ingress_single()` methods for finer-grained control of what and how many packets are processed. ([#954](https://github.com/smoltcp-rs/smoltcp/pull/954), [#991](https://github.com/smoltcp-rs/smoltcp/pull/991), [#993](https://github.com/smoltcp-rs/smoltcp/pull/993))
- Multicast join/leave no longer requires access to device+timestamp. ([#985](https://github.com/smoltcp-rs/smoltcp/pull/985))
- Reset expiry of entries in the neighbor cache on packet reception ([#966](https://github.com/smoltcp-rs/smoltcp/pull/966))
- Honor `any_ip` for ARP ([#880](https://github.com/smoltcp-rs/smoltcp/pull/880))
- Honor `any_ip` for IPv6 ([#900](https://github.com/smoltcp-rs/smoltcp/pull/900))
- Use own source address for ARP and NDISC Solicitations ([#984](https://github.com/smoltcp-rs/smoltcp/pull/984))
- fix panic when discarding HBH Option with multicast destination address ([#996](https://github.com/smoltcp-rs/smoltcp/pull/996))
- fix panic with 6lowpan frag datagram_size < 40 ([#997](https://github.com/smoltcp-rs/smoltcp/pull/997))
- fix panic if no suitable IPv6 src_addr is found ([#895](https://github.com/smoltcp-rs/smoltcp/pull/895))
- Fix specific length IP packets not being fragmented ([#1008](https://github.com/smoltcp-rs/smoltcp/pull/1008))
- tcp
- Add support for congestion control ([#907](https://github.com/smoltcp-rs/smoltcp/pull/907))
- Add support for simultaneous open ([#1001](https://github.com/smoltcp-rs/smoltcp/pull/1001))
- Add support for Timestamp option ([#939](https://github.com/smoltcp-rs/smoltcp/pull/939))
- Send immediate ACKs after RMSS bytes of data ([#1002](https://github.com/smoltcp-rs/smoltcp/pull/1002))
- Do not ignore FIN if segment is partially outside the window. ([#977](https://github.com/smoltcp-rs/smoltcp/pull/977))
- Correctly set internal sACK flag for client sockets ([#995](https://github.com/smoltcp-rs/smoltcp/pull/995))
- Only reset remote_last_ts if some data is enqueued ([#917](https://github.com/smoltcp-rs/smoltcp/pull/917))
- Don't delay ACKs for significant window updates ([#935](https://github.com/smoltcp-rs/smoltcp/pull/935))
- Add `listen_endpoint` getter ([#1005](https://github.com/smoltcp-rs/smoltcp/pull/1005))
- socket
- UDP,ICMP,raw: Add `send_queue`/`recv_queue` ([#1003](https://github.com/smoltcp-rs/smoltcp/pull/1003))
- ICMP: split ICMPv4/v6 accept and process ([#887](https://github.com/smoltcp-rs/smoltcp/pull/887))
- UDP: Store local and use local address in metadata ([#904](https://github.com/smoltcp-rs/smoltcp/pull/904))
- DNS: fix panic if server list is too long ([#986](https://github.com/smoltcp-rs/smoltcp/pull/986))
- DNS: fix panic if no valid source address is found ([#987](https://github.com/smoltcp-rs/smoltcp/pull/987))
- phy
- Change mutability of `RxToken`'s `consume` argument. ([#924](https://github.com/smoltcp-rs/smoltcp/pull/924))
- Add support for NetBSD ([#883](https://github.com/smoltcp-rs/smoltcp/pull/883))
- Add minimum support for iOS ([#896](https://github.com/smoltcp-rs/smoltcp/pull/896))
- Add BPF support for FreeBSD ([#906](https://github.com/smoltcp-rs/smoltcp/pull/906))
- disable checksums on loopback ([#919](https://github.com/smoltcp-rs/smoltcp/pull/919))
- wire
- Use core::net types for IP addresses. ([#937](https://github.com/smoltcp-rs/smoltcp/pull/937), [#994](https://github.com/smoltcp-rs/smoltcp/pull/994))
- Add missing exports in wire for DNS ([#891](https://github.com/smoltcp-rs/smoltcp/pull/891))
- rename Scope to MulticastScope ([#898](https://github.com/smoltcp-rs/smoltcp/pull/898))
- Re-export `dhcpv4::Flags` and `dhcpv4::OpCode` ([#901](https://github.com/smoltcp-rs/smoltcp/pull/901))
- Make Address:v6() constructor const ([#975](https://github.com/smoltcp-rs/smoltcp/pull/975))
- Ipv6RoutingHeader::clear_reserved: fix offsets for Type 2 routing headers. ([#882](https://github.com/smoltcp-rs/smoltcp/pull/882))

## [0.11.0] - 2023-12-23

Expand Down Expand Up @@ -281,7 +327,8 @@ only processed when directed to the 255.255.255.255 address. ([377](https://gith
- Use #[non_exhaustive] for enums and structs ([409](https://github.com/smoltcp-rs/smoltcp/pull/409), [411](https://github.com/smoltcp-rs/smoltcp/pull/411))
- Simplify lifetime parameters of sockets, SocketSet, EthernetInterface ([410](https://github.com/smoltcp-rs/smoltcp/pull/410), [413](https://github.com/smoltcp-rs/smoltcp/pull/413))

[Unreleased]: https://github.com/smoltcp-rs/smoltcp/compare/v0.11.0...HEAD
[Unreleased]: https://github.com/smoltcp-rs/smoltcp/compare/v0.12.0...HEAD
[0.12.0]: https://github.com/smoltcp-rs/smoltcp/compare/v0.11.0...v0.12.0
[0.11.0]: https://github.com/smoltcp-rs/smoltcp/compare/v0.10.0...v0.11.0
[0.10.0]: https://github.com/smoltcp-rs/smoltcp/compare/v0.9.1...v0.10.0
[0.9.1]: https://github.com/smoltcp-rs/smoltcp/compare/v0.9.0...v0.9.1
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "smoltcp"
version = "0.11.0"
version = "0.12.0"
edition = "2021"
rust-version = "1.80"
authors = ["whitequark <whitequark@whitequark.org>"]
Expand Down
10 changes: 10 additions & 0 deletions src/iface/interface/ipv6.rs
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,16 @@ impl InterfaceInner {
#[cfg(feature = "medium-ip")]
Medium::Ip => None,
},
#[cfg(feature = "multicast")]
Icmpv6Repr::Mld(repr) => match repr {
// [RFC 3810 § 6.2], reception checks
MldRepr::Query { .. }
if ip_repr.hop_limit == 1 && ip_repr.src_addr.is_link_local() =>
{
self.process_mldv2(ip_repr, repr)
}
_ => None,
},

// Don't report an error if a packet with unknown type
// has been handled by an ICMP socket
Expand Down
9 changes: 7 additions & 2 deletions src/iface/interface/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,12 @@ impl Interface {
pub fn update_ip_addrs<F: FnOnce(&mut Vec<IpCidr, IFACE_MAX_ADDR_COUNT>)>(&mut self, f: F) {
f(&mut self.inner.ip_addrs);
InterfaceInner::flush_neighbor_cache(&mut self.inner);
InterfaceInner::check_ip_addrs(&self.inner.ip_addrs)
InterfaceInner::check_ip_addrs(&self.inner.ip_addrs);

#[cfg(all(feature = "proto-ipv6", feature = "multicast"))]
if self.inner.caps.medium == Medium::Ethernet {
self.update_solicited_node_groups();
}
}

/// Check whether the interface has the given IP address assigned.
Expand Down Expand Up @@ -1216,7 +1221,7 @@ impl InterfaceInner {
#[cfg(feature = "proto-ipv4")]
IpRepr::Ipv4(repr) => {
// If we have an IPv4 packet, then we need to check if we need to fragment it.
if total_ip_len > self.caps.max_transmission_unit {
if total_ip_len > self.caps.ip_mtu() {
#[cfg(feature = "proto-ipv4-fragmentation")]
{
net_debug!("start fragmentation");
Expand Down
136 changes: 133 additions & 3 deletions src/iface/interface/multicast.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use core::result::Result;
use heapless::LinearMap;
use heapless::{LinearMap, Vec};

#[cfg(feature = "proto-ipv4")]
#[cfg(any(feature = "proto-ipv4", feature = "proto-ipv6"))]
use super::{check, IpPayload, Packet};
use super::{Interface, InterfaceInner};
use crate::config::IFACE_MAX_MULTICAST_GROUP_COUNT;
use crate::config::{IFACE_MAX_ADDR_COUNT, IFACE_MAX_MULTICAST_GROUP_COUNT};
use crate::phy::{Device, PacketMeta};
use crate::wire::*;

Expand Down Expand Up @@ -34,6 +34,18 @@ pub(crate) enum IgmpReportState {
},
}

#[cfg(feature = "proto-ipv6")]
pub(crate) enum MldReportState {
Inactive,
ToGeneralQuery {
timeout: crate::time::Instant,
},
ToSpecificQuery {
group: Ipv6Address,
timeout: crate::time::Instant,
},
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum GroupState {
/// Joining group, we have to send the join packet.
Expand All @@ -49,6 +61,8 @@ pub(crate) struct State {
/// When to report for (all or) the next multicast group membership via IGMP
#[cfg(feature = "proto-ipv4")]
igmp_report_state: IgmpReportState,
#[cfg(feature = "proto-ipv6")]
mld_report_state: MldReportState,
}

impl State {
Expand All @@ -57,6 +71,8 @@ impl State {
groups: LinearMap::new(),
#[cfg(feature = "proto-ipv4")]
igmp_report_state: IgmpReportState::Inactive,
#[cfg(feature = "proto-ipv6")]
mld_report_state: MldReportState::Inactive,
}
}

Expand Down Expand Up @@ -140,6 +156,29 @@ impl Interface {
self.inner.has_multicast_group(addr)
}

#[cfg(feature = "proto-ipv6")]
pub(super) fn update_solicited_node_groups(&mut self) {
// Remove old solicited-node multicast addresses
let removals: Vec<_, IFACE_MAX_MULTICAST_GROUP_COUNT> = self
.inner
.multicast
.groups
.keys()
.cloned()
.filter(|a| matches!(a, IpAddress::Ipv6(a) if a.is_solicited_node_multicast() && !self.inner.has_solicited_node(*a)))
.collect();
for removal in removals {
let _ = self.leave_multicast_group(removal);
}

let cidrs: Vec<IpCidr, IFACE_MAX_ADDR_COUNT> = Vec::from_slice(self.ip_addrs()).unwrap();
for cidr in cidrs {
if let IpCidr::Ipv6(cidr) = cidr {
let _ = self.join_multicast_group(cidr.address().solicited_node());
}
}
}

/// Do multicast egress.
///
/// - Send join/leave packets according to the multicast group state.
Expand Down Expand Up @@ -306,6 +345,46 @@ impl Interface {
}
_ => {}
}
#[cfg(feature = "proto-ipv6")]
match self.inner.multicast.mld_report_state {
MldReportState::ToGeneralQuery { timeout } if self.inner.now >= timeout => {
let records = self
.inner
.multicast
.groups
.iter()
.filter_map(|(addr, _)| match addr {
IpAddress::Ipv6(addr) => Some(MldAddressRecordRepr::new(
MldRecordType::ModeIsExclude,
*addr,
)),
#[allow(unreachable_patterns)]
_ => None,
})
.collect::<heapless::Vec<_, IFACE_MAX_MULTICAST_GROUP_COUNT>>();
if let Some(pkt) = self.inner.mldv2_report_packet(&records) {
if let Some(tx_token) = device.transmit(self.inner.now) {
self.inner
.dispatch_ip(tx_token, PacketMeta::default(), pkt, &mut self.fragmenter)
.unwrap();
};
};
self.inner.multicast.mld_report_state = MldReportState::Inactive;
}
MldReportState::ToSpecificQuery { group, timeout } if self.inner.now >= timeout => {
let record = MldAddressRecordRepr::new(MldRecordType::ModeIsExclude, group);
if let Some(pkt) = self.inner.mldv2_report_packet(&[record]) {
if let Some(tx_token) = device.transmit(self.inner.now) {
// NOTE(unwrap): packet destination is multicast, which is always routable and doesn't require neighbor discovery.
self.inner
.dispatch_ip(tx_token, PacketMeta::default(), pkt, &mut self.fragmenter)
.unwrap();
}
}
self.inner.multicast.mld_report_state = MldReportState::Inactive;
}
_ => {}
}
}
}

Expand Down Expand Up @@ -425,4 +504,55 @@ impl InterfaceInner {
)
})
}

/// Host duties of the **MLDv2** protocol.
///
/// Sets up `mld_report_state` for responding to MLD general/specific membership queries.
/// Membership must not be reported immediately in order to avoid flooding the network
/// after a query is broadcasted by a router; Currently the delay is fixed and not randomized.
#[cfg(feature = "proto-ipv6")]
pub(super) fn process_mldv2<'frame>(
&mut self,
ip_repr: Ipv6Repr,
repr: MldRepr<'frame>,
) -> Option<Packet<'frame>> {
match repr {
MldRepr::Query {
mcast_addr,
max_resp_code,
..
} => {
// Do not respont immediately to the query, but wait a random time
let delay = crate::time::Duration::from_millis(
(self.rand.rand_u16() % max_resp_code).into(),
);
// General query
if mcast_addr.is_unspecified()
&& (ip_repr.dst_addr == IPV6_LINK_LOCAL_ALL_NODES
|| self.has_ip_addr(ip_repr.dst_addr))
{
let ipv6_multicast_group_count = self
.multicast
.groups
.keys()
.filter(|a| matches!(a, IpAddress::Ipv6(_)))
.count();
if ipv6_multicast_group_count != 0 {
self.multicast.mld_report_state = MldReportState::ToGeneralQuery {
timeout: self.now + delay,
};
}
}
if self.has_multicast_group(mcast_addr) && ip_repr.dst_addr == mcast_addr {
self.multicast.mld_report_state = MldReportState::ToSpecificQuery {
group: mcast_addr,
timeout: self.now + delay,
};
}
None
}
MldRepr::Report { .. } => None,
MldRepr::ReportRecordReprs { .. } => None,
}
}
}
76 changes: 76 additions & 0 deletions src/iface/interface/tests/ipv4.rs
Original file line number Diff line number Diff line change
Expand Up @@ -759,6 +759,82 @@ fn test_handle_igmp(#[case] medium: Medium) {
}
}

#[rstest]
#[case(Medium::Ip)]
#[cfg(all(feature = "proto-ipv4-fragmentation", feature = "medium-ip"))]
#[case(Medium::Ethernet)]
#[cfg(all(feature = "proto-ipv4-fragmentation", feature = "medium-ethernet"))]
fn test_packet_len(#[case] medium: Medium) {
use crate::config::FRAGMENTATION_BUFFER_SIZE;

let (mut iface, _, _) = setup(medium);

struct TestTxToken {
max_transmission_unit: usize,
}

impl TxToken for TestTxToken {
fn consume<R, F>(self, len: usize, f: F) -> R
where
F: FnOnce(&mut [u8]) -> R,
{
net_debug!("TxToken get len: {}", len);
assert!(len <= self.max_transmission_unit);
let mut junk = [0; 1536];
f(&mut junk[..len])
}
}

iface.inner.neighbor_cache.fill(
IpAddress::Ipv4(Ipv4Address::new(127, 0, 0, 1)),
HardwareAddress::Ethernet(EthernetAddress::from_bytes(&[
0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
])),
Instant::ZERO,
);

for ip_packet_len in [
100,
iface.inner.ip_mtu(),
iface.inner.ip_mtu() + 1,
FRAGMENTATION_BUFFER_SIZE,
] {
net_debug!("ip_packet_len: {}", ip_packet_len);

let mut ip_repr = Ipv4Repr {
src_addr: Ipv4Address::new(127, 0, 0, 1),
dst_addr: Ipv4Address::new(127, 0, 0, 1),
next_header: IpProtocol::Udp,
payload_len: 0,
hop_limit: 64,
};
let udp_repr = UdpRepr {
src_port: 12345,
dst_port: 54321,
};

let ip_packet_payload_len = ip_packet_len - ip_repr.buffer_len();
let udp_packet_payload_len = ip_packet_payload_len - udp_repr.header_len();
ip_repr.payload_len = ip_packet_payload_len;

let udp_packet_payload = vec![1; udp_packet_payload_len];
let ip_payload = IpPayload::Udp(udp_repr, &udp_packet_payload);
let ip_packet = Packet::new_ipv4(ip_repr, ip_payload);

assert_eq!(
iface.inner.dispatch_ip(
TestTxToken {
max_transmission_unit: iface.inner.caps.max_transmission_unit
},
PacketMeta::default(),
ip_packet,
&mut iface.fragmenter,
),
Ok(())
);
}
}

#[rstest]
#[case(Medium::Ip)]
#[cfg(all(feature = "socket-raw", feature = "medium-ip"))]
Expand Down
Loading

0 comments on commit 5e08aaf

Please sign in to comment.