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(all(feature = "proto-ipv4", feature = "multicast"))]
85            IpPayload::Igmp(igmp_repr) => igmp_repr.emit(&mut IgmpPacket::new_unchecked(payload)),
86            #[cfg(feature = "proto-ipv6")]
87            IpPayload::Icmpv6(icmpv6_repr) => {
88                let ipv6_repr = match _ip_repr {
89                    #[cfg(feature = "proto-ipv4")]
90                    IpRepr::Ipv4(_) => unreachable!(),
91                    IpRepr::Ipv6(repr) => repr,
92                };
93
94                icmpv6_repr.emit(
95                    &ipv6_repr.src_addr,
96                    &ipv6_repr.dst_addr,
97                    &mut Icmpv6Packet::new_unchecked(payload),
98                    &caps.checksum,
99                )
100            }
101            #[cfg(feature = "proto-ipv6")]
102            IpPayload::HopByHopIcmpv6(hbh_repr, icmpv6_repr) => {
103                let ipv6_repr = match _ip_repr {
104                    #[cfg(feature = "proto-ipv4")]
105                    IpRepr::Ipv4(_) => unreachable!(),
106                    IpRepr::Ipv6(repr) => repr,
107                };
108
109                let ipv6_ext_hdr = Ipv6ExtHeaderRepr {
110                    next_header: IpProtocol::Icmpv6,
111                    length: 0,
112                    data: &[],
113                };
114                ipv6_ext_hdr.emit(&mut Ipv6ExtHeader::new_unchecked(
115                    &mut payload[..ipv6_ext_hdr.header_len()],
116                ));
117
118                let hbh_start = ipv6_ext_hdr.header_len();
119                let hbh_end = hbh_start + hbh_repr.buffer_len();
120                hbh_repr.emit(&mut Ipv6HopByHopHeader::new_unchecked(
121                    &mut payload[hbh_start..hbh_end],
122                ));
123
124                icmpv6_repr.emit(
125                    &ipv6_repr.src_addr,
126                    &ipv6_repr.dst_addr,
127                    &mut Icmpv6Packet::new_unchecked(&mut payload[hbh_end..]),
128                    &caps.checksum,
129                );
130            }
131
132            #[cfg(feature = "socket-raw")]
133            IpPayload::Raw(raw_packet) => payload.copy_from_slice(raw_packet),
134            #[cfg(any(feature = "socket-udp", feature = "socket-dns"))]
135            IpPayload::Udp(udp_repr, inner_payload) => udp_repr.emit(
136                &mut UdpPacket::new_unchecked(payload),
137                &_ip_repr.src_addr(),
138                &_ip_repr.dst_addr(),
139                inner_payload.len(),
140                |buf| buf.copy_from_slice(inner_payload),
141                &caps.checksum,
142            ),
143            #[cfg(feature = "socket-tcp")]
144            IpPayload::Tcp(mut tcp_repr) => {
145                // This is a terrible hack to make TCP performance more acceptable on systems
146                // where the TCP buffers are significantly larger than network buffers,
147                // e.g. a 64 kB TCP receive buffer (and so, when empty, a 64k window)
148                // together with four 1500 B Ethernet receive buffers. If left untreated,
149                // this would result in our peer pushing our window and sever packet loss.
150                //
151                // I'm really not happy about this "solution" but I don't know what else to do.
152                if let Some(max_burst_size) = caps.max_burst_size {
153                    let mut max_segment_size = caps.max_transmission_unit;
154                    max_segment_size -= _ip_repr.header_len();
155                    max_segment_size -= tcp_repr.header_len();
156
157                    let max_window_size = max_burst_size * max_segment_size;
158                    if tcp_repr.window_len as usize > max_window_size {
159                        tcp_repr.window_len = max_window_size as u16;
160                    }
161                }
162
163                tcp_repr.emit(
164                    &mut TcpPacket::new_unchecked(payload),
165                    &_ip_repr.src_addr(),
166                    &_ip_repr.dst_addr(),
167                    &caps.checksum,
168                );
169            }
170            #[cfg(feature = "socket-dhcpv4")]
171            IpPayload::Dhcpv4(udp_repr, dhcp_repr) => udp_repr.emit(
172                &mut UdpPacket::new_unchecked(payload),
173                &_ip_repr.src_addr(),
174                &_ip_repr.dst_addr(),
175                dhcp_repr.buffer_len(),
176                |buf| dhcp_repr.emit(&mut DhcpPacket::new_unchecked(buf)).unwrap(),
177                &caps.checksum,
178            ),
179        }
180    }
181}
182
183#[derive(Debug, PartialEq)]
184#[cfg_attr(feature = "defmt", derive(defmt::Format))]
185#[cfg(feature = "proto-ipv4")]
186pub(crate) struct PacketV4<'p> {
187    header: Ipv4Repr,
188    payload: IpPayload<'p>,
189}
190
191#[derive(Debug, PartialEq)]
192#[cfg_attr(feature = "defmt", derive(defmt::Format))]
193#[cfg(feature = "proto-ipv6")]
194pub(crate) struct PacketV6<'p> {
195    pub(crate) header: Ipv6Repr,
196    #[cfg(feature = "proto-ipv6-hbh")]
197    pub(crate) hop_by_hop: Option<Ipv6HopByHopRepr<'p>>,
198    #[cfg(feature = "proto-ipv6-fragmentation")]
199    pub(crate) fragment: Option<Ipv6FragmentRepr>,
200    #[cfg(feature = "proto-ipv6-routing")]
201    pub(crate) routing: Option<Ipv6RoutingRepr<'p>>,
202    pub(crate) payload: IpPayload<'p>,
203}
204
205#[derive(Debug, PartialEq)]
206#[cfg_attr(feature = "defmt", derive(defmt::Format))]
207pub(crate) enum IpPayload<'p> {
208    #[cfg(feature = "proto-ipv4")]
209    Icmpv4(Icmpv4Repr<'p>),
210    #[cfg(all(feature = "proto-ipv4", feature = "multicast"))]
211    Igmp(IgmpRepr),
212    #[cfg(feature = "proto-ipv6")]
213    Icmpv6(Icmpv6Repr<'p>),
214    #[cfg(feature = "proto-ipv6")]
215    HopByHopIcmpv6(Ipv6HopByHopRepr<'p>, Icmpv6Repr<'p>),
216    #[cfg(feature = "socket-raw")]
217    Raw(&'p [u8]),
218    #[cfg(any(feature = "socket-udp", feature = "socket-dns"))]
219    Udp(UdpRepr, &'p [u8]),
220    #[cfg(feature = "socket-tcp")]
221    Tcp(TcpRepr<'p>),
222    #[cfg(feature = "socket-dhcpv4")]
223    Dhcpv4(UdpRepr, DhcpRepr<'p>),
224}
225
226impl<'p> IpPayload<'p> {
227    #[cfg(feature = "proto-sixlowpan")]
228    pub(crate) fn as_sixlowpan_next_header(&self) -> SixlowpanNextHeader {
229        match self {
230            #[cfg(feature = "proto-ipv4")]
231            Self::Icmpv4(_) => unreachable!(),
232            #[cfg(feature = "socket-dhcpv4")]
233            Self::Dhcpv4(..) => unreachable!(),
234            #[cfg(feature = "proto-ipv6")]
235            Self::Icmpv6(_) => SixlowpanNextHeader::Uncompressed(IpProtocol::Icmpv6),
236            #[cfg(feature = "proto-ipv6")]
237            Self::HopByHopIcmpv6(_, _) => unreachable!(),
238            #[cfg(all(feature = "proto-ipv4", feature = "multicast"))]
239            Self::Igmp(_) => unreachable!(),
240            #[cfg(feature = "socket-tcp")]
241            Self::Tcp(_) => SixlowpanNextHeader::Uncompressed(IpProtocol::Tcp),
242            #[cfg(feature = "socket-udp")]
243            Self::Udp(..) => SixlowpanNextHeader::Compressed,
244            #[cfg(feature = "socket-raw")]
245            Self::Raw(_) => todo!(),
246        }
247    }
248}
249
250#[cfg(any(feature = "proto-ipv4", feature = "proto-ipv6"))]
251pub(crate) fn icmp_reply_payload_len(len: usize, mtu: usize, header_len: usize) -> usize {
252    // Send back as much of the original payload as will fit within
253    // the minimum MTU required by IPv4. See RFC 1812 ยง 4.3.2.3 for
254    // more details.
255    //
256    // Since the entire network layer packet must fit within the minimum
257    // MTU supported, the payload must not exceed the following:
258    //
259    // <min mtu> - IP Header Size * 2 - ICMPv4 DstUnreachable hdr size
260    len.min(mtu - header_len * 2 - 8)
261}