smoltcp/iface/
packet.rs

1use crate::phy::DeviceCapabilities;
2use crate::wire::*;
3
4#[allow(clippy::large_enum_variant)]
5#[derive(Debug, PartialEq)]
6#[cfg_attr(feature = "defmt", derive(defmt::Format))]
7#[cfg(feature = "medium-ethernet")]
8pub(crate) enum EthernetPacket<'a> {
9    #[cfg(feature = "proto-ipv4")]
10    Arp(ArpRepr),
11    Ip(Packet<'a>),
12}
13
14#[derive(Debug, PartialEq)]
15#[cfg_attr(feature = "defmt", derive(defmt::Format))]
16pub(crate) enum Packet<'p> {
17    #[cfg(feature = "proto-ipv4")]
18    Ipv4(PacketV4<'p>),
19    #[cfg(feature = "proto-ipv6")]
20    Ipv6(PacketV6<'p>),
21}
22
23impl<'p> Packet<'p> {
24    pub(crate) fn new(ip_repr: IpRepr, payload: IpPayload<'p>) -> Self {
25        match ip_repr {
26            #[cfg(feature = "proto-ipv4")]
27            IpRepr::Ipv4(header) => Self::new_ipv4(header, payload),
28            #[cfg(feature = "proto-ipv6")]
29            IpRepr::Ipv6(header) => Self::new_ipv6(header, payload),
30        }
31    }
32
33    #[cfg(feature = "proto-ipv4")]
34    pub(crate) fn new_ipv4(ip_repr: Ipv4Repr, payload: IpPayload<'p>) -> Self {
35        Self::Ipv4(PacketV4 {
36            header: ip_repr,
37            payload,
38        })
39    }
40
41    #[cfg(feature = "proto-ipv6")]
42    pub(crate) fn new_ipv6(ip_repr: Ipv6Repr, payload: IpPayload<'p>) -> Self {
43        Self::Ipv6(PacketV6 {
44            header: ip_repr,
45            #[cfg(feature = "proto-ipv6-hbh")]
46            hop_by_hop: None,
47            #[cfg(feature = "proto-ipv6-fragmentation")]
48            fragment: None,
49            #[cfg(feature = "proto-ipv6-routing")]
50            routing: None,
51            payload,
52        })
53    }
54
55    pub(crate) fn ip_repr(&self) -> IpRepr {
56        match self {
57            #[cfg(feature = "proto-ipv4")]
58            Packet::Ipv4(p) => IpRepr::Ipv4(p.header),
59            #[cfg(feature = "proto-ipv6")]
60            Packet::Ipv6(p) => IpRepr::Ipv6(p.header),
61        }
62    }
63
64    pub(crate) fn payload(&self) -> &IpPayload<'p> {
65        match self {
66            #[cfg(feature = "proto-ipv4")]
67            Packet::Ipv4(p) => &p.payload,
68            #[cfg(feature = "proto-ipv6")]
69            Packet::Ipv6(p) => &p.payload,
70        }
71    }
72
73    pub(crate) fn emit_payload(
74        &self,
75        _ip_repr: &IpRepr,
76        payload: &mut [u8],
77        caps: &DeviceCapabilities,
78    ) {
79        match self.payload() {
80            #[cfg(feature = "proto-ipv4")]
81            IpPayload::Icmpv4(icmpv4_repr) => {
82                icmpv4_repr.emit(&mut Icmpv4Packet::new_unchecked(payload), &caps.checksum)
83            }
84            #[cfg(feature = "proto-igmp")]
85            IpPayload::Igmp(igmp_repr) => igmp_repr.emit(&mut IgmpPacket::new_unchecked(payload)),
86            #[cfg(feature = "proto-ipv6")]
87            IpPayload::Icmpv6(icmpv6_repr) => icmpv6_repr.emit(
88                &_ip_repr.src_addr(),
89                &_ip_repr.dst_addr(),
90                &mut Icmpv6Packet::new_unchecked(payload),
91                &caps.checksum,
92            ),
93            #[cfg(feature = "socket-raw")]
94            IpPayload::Raw(raw_packet) => payload.copy_from_slice(raw_packet),
95            #[cfg(any(feature = "socket-udp", feature = "socket-dns"))]
96            IpPayload::Udp(udp_repr, inner_payload) => udp_repr.emit(
97                &mut UdpPacket::new_unchecked(payload),
98                &_ip_repr.src_addr(),
99                &_ip_repr.dst_addr(),
100                inner_payload.len(),
101                |buf| buf.copy_from_slice(inner_payload),
102                &caps.checksum,
103            ),
104            #[cfg(feature = "socket-tcp")]
105            IpPayload::Tcp(mut tcp_repr) => {
106                // This is a terrible hack to make TCP performance more acceptable on systems
107                // where the TCP buffers are significantly larger than network buffers,
108                // e.g. a 64 kB TCP receive buffer (and so, when empty, a 64k window)
109                // together with four 1500 B Ethernet receive buffers. If left untreated,
110                // this would result in our peer pushing our window and sever packet loss.
111                //
112                // I'm really not happy about this "solution" but I don't know what else to do.
113                if let Some(max_burst_size) = caps.max_burst_size {
114                    let mut max_segment_size = caps.max_transmission_unit;
115                    max_segment_size -= _ip_repr.header_len();
116                    max_segment_size -= tcp_repr.header_len();
117
118                    let max_window_size = max_burst_size * max_segment_size;
119                    if tcp_repr.window_len as usize > max_window_size {
120                        tcp_repr.window_len = max_window_size as u16;
121                    }
122                }
123
124                tcp_repr.emit(
125                    &mut TcpPacket::new_unchecked(payload),
126                    &_ip_repr.src_addr(),
127                    &_ip_repr.dst_addr(),
128                    &caps.checksum,
129                );
130            }
131            #[cfg(feature = "socket-dhcpv4")]
132            IpPayload::Dhcpv4(udp_repr, dhcp_repr) => udp_repr.emit(
133                &mut UdpPacket::new_unchecked(payload),
134                &_ip_repr.src_addr(),
135                &_ip_repr.dst_addr(),
136                dhcp_repr.buffer_len(),
137                |buf| dhcp_repr.emit(&mut DhcpPacket::new_unchecked(buf)).unwrap(),
138                &caps.checksum,
139            ),
140        }
141    }
142}
143
144#[derive(Debug, PartialEq)]
145#[cfg_attr(feature = "defmt", derive(defmt::Format))]
146#[cfg(feature = "proto-ipv4")]
147pub(crate) struct PacketV4<'p> {
148    header: Ipv4Repr,
149    payload: IpPayload<'p>,
150}
151
152#[derive(Debug, PartialEq)]
153#[cfg_attr(feature = "defmt", derive(defmt::Format))]
154#[cfg(feature = "proto-ipv6")]
155pub(crate) struct PacketV6<'p> {
156    pub(crate) header: Ipv6Repr,
157    #[cfg(feature = "proto-ipv6-hbh")]
158    pub(crate) hop_by_hop: Option<Ipv6HopByHopRepr<'p>>,
159    #[cfg(feature = "proto-ipv6-fragmentation")]
160    pub(crate) fragment: Option<Ipv6FragmentRepr>,
161    #[cfg(feature = "proto-ipv6-routing")]
162    pub(crate) routing: Option<Ipv6RoutingRepr<'p>>,
163    pub(crate) payload: IpPayload<'p>,
164}
165
166#[derive(Debug, PartialEq)]
167#[cfg_attr(feature = "defmt", derive(defmt::Format))]
168pub(crate) enum IpPayload<'p> {
169    #[cfg(feature = "proto-ipv4")]
170    Icmpv4(Icmpv4Repr<'p>),
171    #[cfg(feature = "proto-igmp")]
172    Igmp(IgmpRepr),
173    #[cfg(feature = "proto-ipv6")]
174    Icmpv6(Icmpv6Repr<'p>),
175    #[cfg(feature = "socket-raw")]
176    Raw(&'p [u8]),
177    #[cfg(any(feature = "socket-udp", feature = "socket-dns"))]
178    Udp(UdpRepr, &'p [u8]),
179    #[cfg(feature = "socket-tcp")]
180    Tcp(TcpRepr<'p>),
181    #[cfg(feature = "socket-dhcpv4")]
182    Dhcpv4(UdpRepr, DhcpRepr<'p>),
183}
184
185impl<'p> IpPayload<'p> {
186    #[cfg(feature = "proto-sixlowpan")]
187    pub(crate) fn as_sixlowpan_next_header(&self) -> SixlowpanNextHeader {
188        match self {
189            #[cfg(feature = "proto-ipv4")]
190            Self::Icmpv4(_) => unreachable!(),
191            #[cfg(feature = "socket-dhcpv4")]
192            Self::Dhcpv4(..) => unreachable!(),
193            #[cfg(feature = "proto-ipv6")]
194            Self::Icmpv6(_) => SixlowpanNextHeader::Uncompressed(IpProtocol::Icmpv6),
195            #[cfg(feature = "proto-igmp")]
196            Self::Igmp(_) => unreachable!(),
197            #[cfg(feature = "socket-tcp")]
198            Self::Tcp(_) => SixlowpanNextHeader::Uncompressed(IpProtocol::Tcp),
199            #[cfg(feature = "socket-udp")]
200            Self::Udp(..) => SixlowpanNextHeader::Compressed,
201            #[cfg(feature = "socket-raw")]
202            Self::Raw(_) => todo!(),
203        }
204    }
205}
206
207#[cfg(any(feature = "proto-ipv4", feature = "proto-ipv6"))]
208pub(crate) fn icmp_reply_payload_len(len: usize, mtu: usize, header_len: usize) -> usize {
209    // Send back as much of the original payload as will fit within
210    // the minimum MTU required by IPv4. See RFC 1812 § 4.3.2.3 for
211    // more details.
212    //
213    // Since the entire network layer packet must fit within the minimum
214    // MTU supported, the payload must not exceed the following:
215    //
216    // <min mtu> - IP Header Size * 2 - ICMPv4 DstUnreachable hdr size
217    len.min(mtu - header_len * 2 - 8)
218}
219
220#[cfg(feature = "proto-igmp")]
221pub(crate) enum IgmpReportState {
222    Inactive,
223    ToGeneralQuery {
224        version: IgmpVersion,
225        timeout: crate::time::Instant,
226        interval: crate::time::Duration,
227        next_index: usize,
228    },
229    ToSpecificQuery {
230        version: IgmpVersion,
231        timeout: crate::time::Instant,
232        group: Ipv4Address,
233    },
234}