smoltcp/iface/interface/
ipv4.rs

1use super::*;
2
3#[cfg(feature = "socket-dhcpv4")]
4use crate::socket::dhcpv4;
5#[cfg(feature = "socket-icmp")]
6use crate::socket::icmp;
7use crate::socket::AnySocket;
8
9use crate::phy::{Medium, TxToken};
10use crate::time::Instant;
11use crate::wire::*;
12
13impl InterfaceInner {
14    pub(super) fn process_ipv4<'a>(
15        &mut self,
16        sockets: &mut SocketSet,
17        meta: PacketMeta,
18        ipv4_packet: &Ipv4Packet<&'a [u8]>,
19        frag: &'a mut FragmentsBuffer,
20    ) -> Option<Packet<'a>> {
21        let ipv4_repr = check!(Ipv4Repr::parse(ipv4_packet, &self.caps.checksum));
22        if !self.is_unicast_v4(ipv4_repr.src_addr) && !ipv4_repr.src_addr.is_unspecified() {
23            // Discard packets with non-unicast source addresses but allow unspecified
24            net_debug!("non-unicast or unspecified source address");
25            return None;
26        }
27
28        #[cfg(feature = "proto-ipv4-fragmentation")]
29        let ip_payload = {
30            if ipv4_packet.more_frags() || ipv4_packet.frag_offset() != 0 {
31                let key = FragKey::Ipv4(ipv4_packet.get_key());
32
33                let f = match frag.assembler.get(&key, self.now + frag.reassembly_timeout) {
34                    Ok(f) => f,
35                    Err(_) => {
36                        net_debug!("No available packet assembler for fragmented packet");
37                        return None;
38                    }
39                };
40
41                if !ipv4_packet.more_frags() {
42                    // This is the last fragment, so we know the total size
43                    check!(f.set_total_size(
44                        ipv4_packet.total_len() as usize - ipv4_packet.header_len() as usize
45                            + ipv4_packet.frag_offset() as usize,
46                    ));
47                }
48
49                if let Err(e) = f.add(ipv4_packet.payload(), ipv4_packet.frag_offset() as usize) {
50                    net_debug!("fragmentation error: {:?}", e);
51                    return None;
52                }
53
54                // NOTE: according to the standard, the total length needs to be
55                // recomputed, as well as the checksum. However, we don't really use
56                // the IPv4 header after the packet is reassembled.
57                match f.assemble() {
58                    Some(payload) => payload,
59                    None => return None,
60                }
61            } else {
62                ipv4_packet.payload()
63            }
64        };
65
66        #[cfg(not(feature = "proto-ipv4-fragmentation"))]
67        let ip_payload = ipv4_packet.payload();
68
69        let ip_repr = IpRepr::Ipv4(ipv4_repr);
70
71        #[cfg(feature = "socket-raw")]
72        let handled_by_raw_socket = self.raw_socket_filter(sockets, &ip_repr, ip_payload);
73        #[cfg(not(feature = "socket-raw"))]
74        let handled_by_raw_socket = false;
75
76        #[cfg(feature = "socket-dhcpv4")]
77        {
78            if ipv4_repr.next_header == IpProtocol::Udp
79                && matches!(self.caps.medium, Medium::Ethernet)
80            {
81                let udp_packet = check!(UdpPacket::new_checked(ip_payload));
82                if let Some(dhcp_socket) = sockets
83                    .items_mut()
84                    .find_map(|i| dhcpv4::Socket::downcast_mut(&mut i.socket))
85                {
86                    // First check for source and dest ports, then do `UdpRepr::parse` if they match.
87                    // This way we avoid validating the UDP checksum twice for all non-DHCP UDP packets (one here, one in `process_udp`)
88                    if udp_packet.src_port() == dhcp_socket.server_port
89                        && udp_packet.dst_port() == dhcp_socket.client_port
90                    {
91                        let (src_addr, dst_addr) = (ip_repr.src_addr(), ip_repr.dst_addr());
92                        let udp_repr = check!(UdpRepr::parse(
93                            &udp_packet,
94                            &src_addr,
95                            &dst_addr,
96                            &self.caps.checksum
97                        ));
98                        let udp_payload = udp_packet.payload();
99
100                        dhcp_socket.process(self, &ipv4_repr, &udp_repr, udp_payload);
101                        return None;
102                    }
103                }
104            }
105        }
106
107        if !self.has_ip_addr(ipv4_repr.dst_addr)
108            && !self.has_multicast_group(ipv4_repr.dst_addr)
109            && !self.is_broadcast_v4(ipv4_repr.dst_addr)
110        {
111            // Ignore IP packets not directed at us, or broadcast, or any of the multicast groups.
112            // If AnyIP is enabled, also check if the packet is routed locally.
113            if !self.any_ip
114                || !ipv4_repr.dst_addr.is_unicast()
115                || self
116                    .routes
117                    .lookup(&IpAddress::Ipv4(ipv4_repr.dst_addr), self.now)
118                    .map_or(true, |router_addr| !self.has_ip_addr(router_addr))
119            {
120                return None;
121            }
122        }
123
124        match ipv4_repr.next_header {
125            IpProtocol::Icmp => self.process_icmpv4(sockets, ip_repr, ip_payload),
126
127            #[cfg(feature = "proto-igmp")]
128            IpProtocol::Igmp => self.process_igmp(ipv4_repr, ip_payload),
129
130            #[cfg(any(feature = "socket-udp", feature = "socket-dns"))]
131            IpProtocol::Udp => {
132                let udp_packet = check!(UdpPacket::new_checked(ip_payload));
133                let udp_repr = check!(UdpRepr::parse(
134                    &udp_packet,
135                    &ipv4_repr.src_addr.into(),
136                    &ipv4_repr.dst_addr.into(),
137                    &self.checksum_caps(),
138                ));
139
140                self.process_udp(
141                    sockets,
142                    meta,
143                    ip_repr,
144                    udp_repr,
145                    handled_by_raw_socket,
146                    udp_packet.payload(),
147                    ip_payload,
148                )
149            }
150
151            #[cfg(feature = "socket-tcp")]
152            IpProtocol::Tcp => self.process_tcp(sockets, ip_repr, ip_payload),
153
154            _ if handled_by_raw_socket => None,
155
156            _ => {
157                // Send back as much of the original payload as we can.
158                let payload_len =
159                    icmp_reply_payload_len(ip_payload.len(), IPV4_MIN_MTU, ipv4_repr.buffer_len());
160                let icmp_reply_repr = Icmpv4Repr::DstUnreachable {
161                    reason: Icmpv4DstUnreachable::ProtoUnreachable,
162                    header: ipv4_repr,
163                    data: &ip_payload[0..payload_len],
164                };
165                self.icmpv4_reply(ipv4_repr, icmp_reply_repr)
166            }
167        }
168    }
169
170    #[cfg(feature = "medium-ethernet")]
171    pub(super) fn process_arp<'frame>(
172        &mut self,
173        timestamp: Instant,
174        eth_frame: &EthernetFrame<&'frame [u8]>,
175    ) -> Option<EthernetPacket<'frame>> {
176        let arp_packet = check!(ArpPacket::new_checked(eth_frame.payload()));
177        let arp_repr = check!(ArpRepr::parse(&arp_packet));
178
179        match arp_repr {
180            ArpRepr::EthernetIpv4 {
181                operation,
182                source_hardware_addr,
183                source_protocol_addr,
184                target_protocol_addr,
185                ..
186            } => {
187                // Only process ARP packets for us.
188                if !self.has_ip_addr(target_protocol_addr) {
189                    return None;
190                }
191
192                // Only process REQUEST and RESPONSE.
193                if let ArpOperation::Unknown(_) = operation {
194                    net_debug!("arp: unknown operation code");
195                    return None;
196                }
197
198                // Discard packets with non-unicast source addresses.
199                if !source_protocol_addr.is_unicast() || !source_hardware_addr.is_unicast() {
200                    net_debug!("arp: non-unicast source address");
201                    return None;
202                }
203
204                if !self.in_same_network(&IpAddress::Ipv4(source_protocol_addr)) {
205                    net_debug!("arp: source IP address not in same network as us");
206                    return None;
207                }
208
209                // Fill the ARP cache from any ARP packet aimed at us (both request or response).
210                // We fill from requests too because if someone is requesting our address they
211                // are probably going to talk to us, so we avoid having to request their address
212                // when we later reply to them.
213                self.neighbor_cache.fill(
214                    source_protocol_addr.into(),
215                    source_hardware_addr.into(),
216                    timestamp,
217                );
218
219                if operation == ArpOperation::Request {
220                    let src_hardware_addr = self.hardware_addr.ethernet_or_panic();
221
222                    Some(EthernetPacket::Arp(ArpRepr::EthernetIpv4 {
223                        operation: ArpOperation::Reply,
224                        source_hardware_addr: src_hardware_addr,
225                        source_protocol_addr: target_protocol_addr,
226                        target_hardware_addr: source_hardware_addr,
227                        target_protocol_addr: source_protocol_addr,
228                    }))
229                } else {
230                    None
231                }
232            }
233        }
234    }
235
236    pub(super) fn process_icmpv4<'frame>(
237        &mut self,
238        _sockets: &mut SocketSet,
239        ip_repr: IpRepr,
240        ip_payload: &'frame [u8],
241    ) -> Option<Packet<'frame>> {
242        let icmp_packet = check!(Icmpv4Packet::new_checked(ip_payload));
243        let icmp_repr = check!(Icmpv4Repr::parse(&icmp_packet, &self.caps.checksum));
244
245        #[cfg(feature = "socket-icmp")]
246        let mut handled_by_icmp_socket = false;
247
248        #[cfg(all(feature = "socket-icmp", feature = "proto-ipv4"))]
249        for icmp_socket in _sockets
250            .items_mut()
251            .filter_map(|i| icmp::Socket::downcast_mut(&mut i.socket))
252        {
253            if icmp_socket.accepts(self, &ip_repr, &icmp_repr.into()) {
254                icmp_socket.process(self, &ip_repr, &icmp_repr.into());
255                handled_by_icmp_socket = true;
256            }
257        }
258
259        match icmp_repr {
260            // Respond to echo requests.
261            #[cfg(feature = "proto-ipv4")]
262            Icmpv4Repr::EchoRequest {
263                ident,
264                seq_no,
265                data,
266            } => {
267                let icmp_reply_repr = Icmpv4Repr::EchoReply {
268                    ident,
269                    seq_no,
270                    data,
271                };
272                match ip_repr {
273                    IpRepr::Ipv4(ipv4_repr) => self.icmpv4_reply(ipv4_repr, icmp_reply_repr),
274                    #[allow(unreachable_patterns)]
275                    _ => unreachable!(),
276                }
277            }
278
279            // Ignore any echo replies.
280            Icmpv4Repr::EchoReply { .. } => None,
281
282            // Don't report an error if a packet with unknown type
283            // has been handled by an ICMP socket
284            #[cfg(feature = "socket-icmp")]
285            _ if handled_by_icmp_socket => None,
286
287            // FIXME: do something correct here?
288            _ => None,
289        }
290    }
291
292    pub(super) fn icmpv4_reply<'frame, 'icmp: 'frame>(
293        &self,
294        ipv4_repr: Ipv4Repr,
295        icmp_repr: Icmpv4Repr<'icmp>,
296    ) -> Option<Packet<'frame>> {
297        if !self.is_unicast_v4(ipv4_repr.src_addr) {
298            // Do not send ICMP replies to non-unicast sources
299            None
300        } else if self.is_unicast_v4(ipv4_repr.dst_addr) {
301            // Reply as normal when src_addr and dst_addr are both unicast
302            let ipv4_reply_repr = Ipv4Repr {
303                src_addr: ipv4_repr.dst_addr,
304                dst_addr: ipv4_repr.src_addr,
305                next_header: IpProtocol::Icmp,
306                payload_len: icmp_repr.buffer_len(),
307                hop_limit: 64,
308            };
309            Some(Packet::new_ipv4(
310                ipv4_reply_repr,
311                IpPayload::Icmpv4(icmp_repr),
312            ))
313        } else if self.is_broadcast_v4(ipv4_repr.dst_addr) {
314            // Only reply to broadcasts for echo replies and not other ICMP messages
315            match icmp_repr {
316                Icmpv4Repr::EchoReply { .. } => match self.ipv4_addr() {
317                    Some(src_addr) => {
318                        let ipv4_reply_repr = Ipv4Repr {
319                            src_addr,
320                            dst_addr: ipv4_repr.src_addr,
321                            next_header: IpProtocol::Icmp,
322                            payload_len: icmp_repr.buffer_len(),
323                            hop_limit: 64,
324                        };
325                        Some(Packet::new_ipv4(
326                            ipv4_reply_repr,
327                            IpPayload::Icmpv4(icmp_repr),
328                        ))
329                    }
330                    None => None,
331                },
332                _ => None,
333            }
334        } else {
335            None
336        }
337    }
338
339    #[cfg(feature = "proto-ipv4-fragmentation")]
340    pub(super) fn dispatch_ipv4_frag<Tx: TxToken>(&mut self, tx_token: Tx, frag: &mut Fragmenter) {
341        let caps = self.caps.clone();
342
343        let mtu_max = self.ip_mtu();
344        let ip_len = (frag.packet_len - frag.sent_bytes + frag.ipv4.repr.buffer_len()).min(mtu_max);
345        let payload_len = ip_len - frag.ipv4.repr.buffer_len();
346
347        let more_frags = (frag.packet_len - frag.sent_bytes) != payload_len;
348        frag.ipv4.repr.payload_len = payload_len;
349        frag.sent_bytes += payload_len;
350
351        let mut tx_len = ip_len;
352        #[cfg(feature = "medium-ethernet")]
353        if matches!(caps.medium, Medium::Ethernet) {
354            tx_len += EthernetFrame::<&[u8]>::header_len();
355        }
356
357        // Emit function for the Ethernet header.
358        #[cfg(feature = "medium-ethernet")]
359        let emit_ethernet = |repr: &IpRepr, tx_buffer: &mut [u8]| {
360            let mut frame = EthernetFrame::new_unchecked(tx_buffer);
361
362            let src_addr = self.hardware_addr.ethernet_or_panic();
363            frame.set_src_addr(src_addr);
364            frame.set_dst_addr(frag.ipv4.dst_hardware_addr);
365
366            match repr.version() {
367                #[cfg(feature = "proto-ipv4")]
368                IpVersion::Ipv4 => frame.set_ethertype(EthernetProtocol::Ipv4),
369                #[cfg(feature = "proto-ipv6")]
370                IpVersion::Ipv6 => frame.set_ethertype(EthernetProtocol::Ipv6),
371            }
372        };
373
374        tx_token.consume(tx_len, |mut tx_buffer| {
375            #[cfg(feature = "medium-ethernet")]
376            if matches!(self.caps.medium, Medium::Ethernet) {
377                emit_ethernet(&IpRepr::Ipv4(frag.ipv4.repr), tx_buffer);
378                tx_buffer = &mut tx_buffer[EthernetFrame::<&[u8]>::header_len()..];
379            }
380
381            let mut packet =
382                Ipv4Packet::new_unchecked(&mut tx_buffer[..frag.ipv4.repr.buffer_len()]);
383            frag.ipv4.repr.emit(&mut packet, &caps.checksum);
384            packet.set_ident(frag.ipv4.ident);
385            packet.set_more_frags(more_frags);
386            packet.set_dont_frag(false);
387            packet.set_frag_offset(frag.ipv4.frag_offset);
388
389            if caps.checksum.ipv4.tx() {
390                packet.fill_checksum();
391            }
392
393            tx_buffer[frag.ipv4.repr.buffer_len()..][..payload_len].copy_from_slice(
394                &frag.buffer[frag.ipv4.frag_offset as usize + frag.ipv4.repr.buffer_len()..]
395                    [..payload_len],
396            );
397
398            // Update the frag offset for the next fragment.
399            frag.ipv4.frag_offset += payload_len as u16;
400        })
401    }
402
403    #[cfg(feature = "proto-igmp")]
404    pub(super) fn igmp_report_packet<'any>(
405        &self,
406        version: IgmpVersion,
407        group_addr: Ipv4Address,
408    ) -> Option<Packet<'any>> {
409        let iface_addr = self.ipv4_addr()?;
410        let igmp_repr = IgmpRepr::MembershipReport {
411            group_addr,
412            version,
413        };
414        let pkt = Packet::new_ipv4(
415            Ipv4Repr {
416                src_addr: iface_addr,
417                // Send to the group being reported
418                dst_addr: group_addr,
419                next_header: IpProtocol::Igmp,
420                payload_len: igmp_repr.buffer_len(),
421                hop_limit: 1,
422                // [#183](https://github.com/m-labs/smoltcp/issues/183).
423            },
424            IpPayload::Igmp(igmp_repr),
425        );
426        Some(pkt)
427    }
428
429    #[cfg(feature = "proto-igmp")]
430    pub(super) fn igmp_leave_packet<'any>(&self, group_addr: Ipv4Address) -> Option<Packet<'any>> {
431        self.ipv4_addr().map(|iface_addr| {
432            let igmp_repr = IgmpRepr::LeaveGroup { group_addr };
433            Packet::new_ipv4(
434                Ipv4Repr {
435                    src_addr: iface_addr,
436                    dst_addr: Ipv4Address::MULTICAST_ALL_ROUTERS,
437                    next_header: IpProtocol::Igmp,
438                    payload_len: igmp_repr.buffer_len(),
439                    hop_limit: 1,
440                },
441                IpPayload::Igmp(igmp_repr),
442            )
443        })
444    }
445}