| // Copyright 2018 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| //! The Internet Protocol, versions 4 and 6. |
| |
| mod forwarding; |
| mod icmp; |
| mod igmp; |
| mod types; |
| |
| // It's ok to `pub use` rather `pub(crate) use` here because the items in |
| // `types` which are themselves `pub(crate)` will still not be allowed to be |
| // re-exported from the root. |
| pub use self::types::*; |
| |
| use log::{debug, trace}; |
| use std::mem; |
| |
| use packet::{ |
| BufferMut, BufferSerializer, MtuError, ParsablePacket, ParseBufferMut, ParseMetadata, |
| Serializer, |
| }; |
| use specialize_ip_macro::specialize_ip_address; |
| |
| use crate::device::{DeviceId, FrameDestination}; |
| use crate::ip::forwarding::{Destination, ForwardingTable}; |
| use crate::{Context, EventDispatcher}; |
| |
| // default IPv4 TTL or IPv6 hops |
| const DEFAULT_TTL: u8 = 64; |
| |
| // Minimum MTU required by all IPv6 devices. |
| pub(crate) const IPV6_MIN_MTU: u32 = 1280; |
| |
| /// The state associated with the IP layer. |
| #[derive(Default)] |
| pub(crate) struct IpLayerState { |
| v4: IpLayerStateInner<Ipv4>, |
| v6: IpLayerStateInner<Ipv6>, |
| } |
| |
| #[derive(Default)] |
| struct IpLayerStateInner<I: Ip> { |
| forward: bool, |
| table: ForwardingTable<I>, |
| } |
| |
| // TODO(joshlf): Once we support multiple extension headers in IPv6, we will |
| // need to verify that the callers of this function are still sound. In |
| // particular, they may accidentally pass a parse_metadata argument which |
| // corresponds to a single extension header rather than all of the IPv6 headers. |
| |
| /// Dispatch a received IP packet to the appropriate protocol. |
| /// |
| /// `device` is the device the packet was received on. `parse_metadata` is the |
| /// parse metadata associated with parsing the IP headers. It is used to undo |
| /// that parsing. Both `device` and `parse_metadata` are required in order to |
| /// send ICMP messages in response to unrecognized protocols or ports. If either |
| /// of `device` or `parse_metadata` is `None`, the caller promises that the |
| /// protocol and port are recognized. |
| /// |
| /// # Panics |
| /// |
| /// `dispatch_receive_ip_packet` panics if the protocol is unrecognized and |
| /// `parse_metadata` is `None`. |
| fn dispatch_receive_ip_packet<D: EventDispatcher, I: IpAddress, B: BufferMut>( |
| ctx: &mut Context<D>, |
| device: Option<DeviceId>, |
| src_ip: I, |
| dst_ip: I, |
| proto: IpProto, |
| mut buffer: B, |
| parse_metadata: Option<ParseMetadata>, |
| ) { |
| increment_counter!(ctx, "dispatch_receive_ip_packet"); |
| let res = match proto { |
| IpProto::Icmp | IpProto::Icmpv6 => { |
| icmp::receive_icmp_packet(ctx, src_ip, dst_ip, buffer); |
| Ok(()) |
| } |
| IpProto::Igmp => { |
| igmp::receive_igmp_packet(ctx, src_ip, dst_ip, buffer); |
| Ok(()) |
| } |
| IpProto::Tcp => crate::transport::tcp::receive_ip_packet(ctx, src_ip, dst_ip, buffer), |
| IpProto::Udp => crate::transport::udp::receive_ip_packet(ctx, src_ip, dst_ip, buffer), |
| IpProto::Other(_) => { |
| // Undo the parsing of the IP packet header so that the buffer |
| // contains the entire original IP packet. |
| let meta = parse_metadata.unwrap(); |
| buffer.undo_parse(meta); |
| icmp::send_icmp_protocol_unreachable( |
| ctx, |
| device.unwrap(), |
| src_ip, |
| dst_ip, |
| buffer, |
| meta.header_len(), |
| ); |
| Ok(()) |
| } |
| }; |
| |
| if let Err(mut buffer) = res { |
| // TODO(joshlf): What if we're called from a loopback handler, and |
| // device and parse_metadata are None? In other words, what happens if |
| // we attempt to send to a loopback port which is unreachable? We will |
| // eventually need to restructure the control flow here to handle that |
| // case. |
| |
| // tcp::receive_ip_packet and udp::receive_ip_packet promise to return |
| // the buffer in the same state it was in when they were called. Thus, |
| // all we have to do is undo the parsing of the IP packet header, and |
| // the buffer will be back to containing the entire original IP packet. |
| let meta = parse_metadata.unwrap(); |
| buffer.undo_parse(meta); |
| icmp::send_icmp_port_unreachable( |
| ctx, |
| device.unwrap(), |
| src_ip, |
| dst_ip, |
| buffer, |
| meta.header_len(), |
| ); |
| } |
| } |
| |
| /// Drop a packet and extract some of the fields. |
| /// |
| /// `drop_packet!` saves the results of the `src_ip()`, `dst_ip()`, `proto()`, |
| /// and `parse_metadata()` methods, drops the packet, and returns them. This |
| /// exposes the buffer that the packet was borrowed from so that it can be used |
| /// directly again. |
| macro_rules! drop_packet { |
| ($packet:expr) => {{ |
| let src_ip = $packet.src_ip(); |
| let dst_ip = $packet.dst_ip(); |
| let proto = $packet.proto(); |
| let meta = $packet.parse_metadata(); |
| mem::drop($packet); |
| (src_ip, dst_ip, proto, meta) |
| }}; |
| } |
| |
| /// Drop a packet and undo the effects of parsing it. |
| /// |
| /// `drop_packet_and_undo_parse!` takes a `$packet` and a `$buffer` which the |
| /// packet was parsed from. It saves the results of the `src_ip()`, `dst_ip()`, |
| /// `proto()`, and `parse_metadata()` methods. It drops `$packet` and uses the |
| /// result of `parse_metadata()` to undo the effects of parsing the packet. |
| /// Finally, it returns the source IP, destination IP, protocol, and parse |
| /// metadata. |
| macro_rules! drop_packet_and_undo_parse { |
| ($packet:expr, $buffer:expr) => {{ |
| let (src_ip, dst_ip, proto, meta) = drop_packet!($packet); |
| $buffer.undo_parse(meta); |
| (src_ip, dst_ip, proto, meta) |
| }}; |
| } |
| |
| /// Receive an IP packet from a device. |
| /// |
| /// `frame_dst` specifies whether this packet was received in a broadcast or |
| /// unicast link-layer frame. |
| pub(crate) fn receive_ip_packet<D: EventDispatcher, B: BufferMut, I: Ip>( |
| ctx: &mut Context<D>, |
| device: DeviceId, |
| frame_dst: FrameDestination, |
| mut buffer: B, |
| ) { |
| trace!("receive_ip_packet({})", device); |
| let mut packet = if let Ok(packet) = buffer.parse_mut::<<I as IpExt<_>>::Packet>() { |
| packet |
| } else { |
| // TODO(joshlf): Do something with ICMP here? |
| return; |
| }; |
| trace!("receive_ip_packet: parsed packet: {:?}", packet); |
| |
| if I::LOOPBACK_SUBNET.contains(packet.dst_ip()) { |
| // A packet from outside this host was sent with the destination IP of |
| // the loopback address, which is illegal. Loopback traffic is handled |
| // explicitly in send_ip_packet. |
| // |
| // TODO(joshlf): Do something with ICMP here? |
| debug!("got packet from remote host for loopback address {}", packet.dst_ip()); |
| } else if deliver(ctx, device, packet.dst_ip()) { |
| trace!("receive_ip_packet: delivering locally"); |
| // TODO(joshlf): |
| // - Do something with ICMP if we don't have a handler for that protocol? |
| // - Check for already-expired TTL? |
| let (src_ip, dst_ip, proto, meta) = drop_packet!(packet); |
| dispatch_receive_ip_packet(ctx, Some(device), src_ip, dst_ip, proto, buffer, Some(meta)); |
| } else if let Some(dest) = forward(ctx, packet.dst_ip()) { |
| let ttl = packet.ttl(); |
| if ttl > 1 { |
| trace!("receive_ip_packet: forwarding"); |
| packet.set_ttl(ttl - 1); |
| let (src_ip, dst_ip, _, _) = drop_packet_and_undo_parse!(packet, buffer); |
| if let Err((err, ser)) = crate::device::send_ip_frame( |
| ctx, |
| dest.device, |
| dest.next_hop, |
| BufferSerializer::new_vec(buffer), |
| ) { |
| #[specialize_ip_address] |
| fn send_packet_too_big<D: EventDispatcher, A: IpAddress, B: BufferMut>( |
| ctx: &mut Context<D>, |
| device: DeviceId, |
| src_ip: A, |
| dst_ip: A, |
| mtu: u32, |
| original_packet: B, |
| ) { |
| #[ipv6addr] |
| crate::ip::icmp::send_icmpv6_packet_too_big( |
| ctx, |
| device, |
| src_ip, |
| dst_ip, |
| mtu, |
| original_packet, |
| ); |
| } |
| |
| debug!("failed to forward IP packet: {:?}", err); |
| if err.is_mtu() { |
| trace!("receive_ip_packet: Sending ICMPv6 Packet Too Big"); |
| // TODO(joshlf): Increment the TTL since we just decremented |
| // it. The fact that we don't do this is technically a |
| // violation of the ICMP spec (we're not encapsulating the |
| // original packet that caused the issue, but a slightly |
| // modified version of it), but it's not that big of a deal |
| // because it won't affect the sender's ability to figure |
| // out the minimum path MTU. This may break other logic, |
| // though, so we should still fix it eventually. |
| let mtu = crate::device::get_mtu(ctx, device); |
| send_packet_too_big(ctx, device, src_ip, dst_ip, mtu, ser.into_buffer()); |
| } |
| } |
| } else { |
| // TTL is 0 or would become 0 after decrement; see "TTL" section, |
| // https://tools.ietf.org/html/rfc791#page-14 |
| let (src_ip, dst_ip, _, meta) = drop_packet_and_undo_parse!(packet, buffer); |
| icmp::send_icmp_ttl_expired(ctx, device, src_ip, dst_ip, buffer, meta.header_len()); |
| debug!("received IP packet dropped due to expired TTL"); |
| } |
| } else { |
| let (src_ip, dst_ip, _, meta) = drop_packet_and_undo_parse!(packet, buffer); |
| icmp::send_icmp_net_unreachable(ctx, device, src_ip, dst_ip, buffer, meta.header_len()); |
| debug!("received IP packet with no known route to destination {}", dst_ip); |
| } |
| } |
| |
| /// Get the local address of the interface that will be used to route to a |
| /// remote address. |
| /// |
| /// `local_address_for_remote` looks up the route to `remote`. If one is found, |
| /// it returns the IP address of the interface specified by the route, or `None` |
| /// if the interface has no IP address. |
| pub(crate) fn local_address_for_remote<D: EventDispatcher, A: IpAddress>( |
| ctx: &mut Context<D>, |
| remote: A, |
| ) -> Option<A> { |
| let route = lookup_route(&ctx.state().ip, remote)?; |
| crate::device::get_ip_addr_subnet(ctx, route.device).map(AddrSubnet::into_addr) |
| } |
| |
| // Should we deliver this packet locally? |
| // deliver returns true if: |
| // - dst_ip is equal to the address set on the device |
| // - dst_ip is equal to the broadcast address of the subnet set on the device |
| // - dst_ip is equal to the global broadcast address |
| #[specialize_ip_address] |
| fn deliver<D: EventDispatcher, A: IpAddress>( |
| ctx: &mut Context<D>, |
| device: DeviceId, |
| dst_ip: A, |
| ) -> bool { |
| // TODO(joshlf): |
| // - This implements a strict host model (in which we only accept packets |
| // which are addressed to the device over which they were received). This |
| // is the easiest to implement for the time being, but we should actually |
| // put real thought into what our host model should be (NET-1011). |
| #[ipv4addr] |
| return crate::device::get_ip_addr_subnet(ctx, device) |
| .map(AddrSubnet::into_addr_subnet) |
| .map(|(addr, subnet)| dst_ip == addr || dst_ip == subnet.broadcast()) |
| .unwrap_or(dst_ip == Ipv4::BROADCAST_ADDRESS); |
| #[ipv6addr] |
| return log_unimplemented!(false, "ip::deliver: Ipv6 not implemeneted"); |
| } |
| |
| // Should we forward this packet, and if so, to whom? |
| #[specialize_ip_address] |
| fn forward<D: EventDispatcher, A: IpAddress>( |
| ctx: &mut Context<D>, |
| dst_ip: A, |
| ) -> Option<Destination<A::Version>> { |
| let ip_state = &ctx.state().ip; |
| |
| #[ipv4addr] |
| let forward = ip_state.v4.forward; |
| #[ipv6addr] |
| let forward = ip_state.v6.forward; |
| |
| if forward { |
| lookup_route(ip_state, dst_ip) |
| } else { |
| None |
| } |
| } |
| |
| // Look up the route to a host. |
| #[specialize_ip_address] |
| fn lookup_route<A: IpAddress>(state: &IpLayerState, dst_ip: A) -> Option<Destination<A::Version>> { |
| #[ipv4addr] |
| return state.v4.table.lookup(dst_ip); |
| #[ipv6addr] |
| return state.v6.table.lookup(dst_ip); |
| } |
| |
| /// Add a route to the forwarding table. |
| #[specialize_ip_address] |
| pub(crate) fn add_route<D: EventDispatcher, A: IpAddress>( |
| ctx: &mut Context<D>, |
| subnet: Subnet<A>, |
| next_hop: A, |
| ) { |
| let state = &mut ctx.state_mut().ip; |
| |
| #[ipv4addr] |
| state.v4.table.add_route(subnet, next_hop); |
| #[ipv6addr] |
| state.v6.table.add_route(subnet, next_hop); |
| } |
| |
| /// Add a device route to the forwarding table. |
| #[specialize_ip_address] |
| pub(crate) fn add_device_route<D: EventDispatcher, A: IpAddress>( |
| ctx: &mut Context<D>, |
| subnet: Subnet<A>, |
| device: DeviceId, |
| ) { |
| let state = &mut ctx.state_mut().ip; |
| |
| #[ipv4addr] |
| state.v4.table.add_device_route(subnet, device); |
| #[ipv6addr] |
| state.v6.table.add_device_route(subnet, device); |
| } |
| |
| /// Return the routes for the provided `IpAddress` type |
| #[specialize_ip_address] |
| pub(crate) fn iter_routes<D: EventDispatcher, I: IpAddress>( |
| ctx: &Context<D>, |
| ) -> std::slice::Iter<Entry<I>> { |
| let state = &ctx.state().ip; |
| #[ipv4addr] |
| return state.v4.table.iter_routes(); |
| #[ipv6addr] |
| return state.v6.table.iter_routes(); |
| } |
| |
| /// Is this one of our local addresses? |
| /// |
| /// `is_local_addr` returns whether `addr` is the address associated with one of |
| /// our local interfaces. |
| pub(crate) fn is_local_addr<D: EventDispatcher, A: IpAddress>( |
| ctx: &mut Context<D>, |
| addr: A, |
| ) -> bool { |
| log_unimplemented!(false, "ip::is_local_addr: not implemented") |
| } |
| |
| /// Send an IP packet to a remote host. |
| /// |
| /// `send_ip_packet` accepts a destination IP address, a protocol, and a |
| /// callback. It computes the routing information, and invokes the callback with |
| /// the computed destination address. The callback returns a |
| /// `SerializationRequest`, which is serialized in a new IP packet and sent. |
| pub(crate) fn send_ip_packet<D: EventDispatcher, A, S, F>( |
| ctx: &mut Context<D>, |
| dst_ip: A, |
| proto: IpProto, |
| get_body: F, |
| ) -> Result<(), (MtuError<S::InnerError>, S)> |
| where |
| A: IpAddress, |
| S: Serializer, |
| F: FnOnce(A) -> S, |
| { |
| trace!("send_ip_packet({}, {})", dst_ip, proto); |
| increment_counter!(ctx, "send_ip_packet"); |
| if A::Version::LOOPBACK_SUBNET.contains(dst_ip) { |
| increment_counter!(ctx, "send_ip_packet::loopback"); |
| |
| // TODO(joshlf): Currently, we have no way of representing the loopback |
| // device as a DeviceId. We will need to fix that eventually. |
| |
| // TODO(joshlf): Currently, we serialize using the normal Serializer |
| // functionality. I wonder if, in the case of delivering to loopback, we |
| // can do something more efficient? |
| let mut buffer = get_body(A::Version::LOOPBACK_ADDRESS) |
| .serialize_outer() |
| .map_err(|(err, ser)| (err.into(), ser))?; |
| // TODO(joshlf): Respond with some kind of error if we don't have a |
| // handler for that protocol? Maybe simulate what would have happened |
| // (w.r.t ICMP) if this were a remote host? |
| |
| // NOTE(joshlf): By passing a DeviceId and ParseMetadata of None here, |
| // we are promising that the protocol will be recognized (and the call |
| // will panic if it's not). This is OK because the fact that we're |
| // sending a packet with this protocol means we are able to process that |
| // protocol. |
| // |
| // NOTE(joshlf): By doing that, we are also promising that the port will |
| // be recognized. That is NOT OK, and the call will panic in that case. |
| // TODO(joshlf): Fix this. |
| dispatch_receive_ip_packet( |
| ctx, |
| None, |
| A::Version::LOOPBACK_ADDRESS, |
| dst_ip, |
| proto, |
| buffer.as_buf_mut(), |
| None, |
| ); |
| } else if let Some(dest) = lookup_route(&ctx.state().ip, dst_ip) { |
| // TODO(joshlf): Are we sure that a device route can never be set for a |
| // device without an IP address? At the least, this is not currently |
| // enforced anywhere, and is a DoS vector. |
| let src_ip = crate::device::get_ip_addr_subnet(ctx, dest.device) |
| .expect("IP device route set for device without IP address") |
| .addr(); |
| send_ip_packet_from_device( |
| ctx, |
| dest.device, |
| src_ip, |
| dst_ip, |
| dest.next_hop, |
| proto, |
| get_body(src_ip), |
| )?; |
| } else { |
| debug!("No route to host"); |
| // TODO(joshlf): No route to host |
| } |
| |
| Ok(()) |
| } |
| |
| /// Send an IP packet to a remote host from a specific source address. |
| /// |
| /// `send_ip_packet_from` accepts a source and destination IP address and a |
| /// `SerializationRequest`. It computes the routing information and serializes |
| /// the request in a new IP packet and sends it. |
| /// |
| /// `send_ip_packet_from` computes a route to the destination with the |
| /// restriction that the packet must originate from the source address, and must |
| /// eagress over the interface associated with that source address. If this |
| /// restriction cannot be met, a "no route to host" error is returned. |
| pub(crate) fn send_ip_packet_from<D: EventDispatcher, A, S>( |
| ctx: &mut Context<D>, |
| src_ip: A, |
| dst_ip: A, |
| proto: IpProto, |
| body: S, |
| ) -> Result<(), (MtuError<S::InnerError>, S)> |
| where |
| A: IpAddress, |
| S: Serializer, |
| { |
| // TODO(joshlf): Figure out how to compute a route with the restrictions |
| // mentioned in the doc comment. |
| log_unimplemented!(Ok(()), "ip::send_ip_packet_from: not implemented") |
| } |
| |
| /// Send an IP packet to a remote host over a specific device. |
| /// |
| /// `send_ip_packet_from_device` accepts a device, a source and destination IP |
| /// address, a next hop IP address, and a `SerializationRequest`. It computes |
| /// the routing information and serializes the request in a new IP packet and |
| /// sends it. |
| /// |
| /// # Panics |
| /// |
| /// Since `send_ip_packet_from_device` specifies a physical device, it cannot |
| /// send to or from a loopback IP address. If either `src_ip` or `dst_ip` are in |
| /// the loopback subnet, `send_ip_packet_from_device` will panic. |
| pub(crate) fn send_ip_packet_from_device<D: EventDispatcher, A, S>( |
| ctx: &mut Context<D>, |
| device: DeviceId, |
| src_ip: A, |
| dst_ip: A, |
| next_hop: A, |
| proto: IpProto, |
| body: S, |
| ) -> Result<(), (MtuError<S::InnerError>, S)> |
| where |
| A: IpAddress, |
| S: Serializer, |
| { |
| assert!(!A::Version::LOOPBACK_SUBNET.contains(src_ip)); |
| assert!(!A::Version::LOOPBACK_SUBNET.contains(dst_ip)); |
| |
| let body = body.encapsulate(<A::Version as IpExt<&[u8]>>::PacketBuilder::new( |
| src_ip, |
| dst_ip, |
| DEFAULT_TTL, |
| proto, |
| )); |
| crate::device::send_ip_frame(ctx, device, next_hop, body) |
| .map_err(|(err, ser)| (err, ser.into_serializer())) |
| } |
| |
| /// Send an ICMP response to a remote host. |
| /// |
| /// Unlike other send functions, `send_icmp_response` takes the ingress device, |
| /// source IP, and destination IP of the packet *being responded to*. It uses |
| /// ICMP-specific logic to figure out whether and how to send an ICMP response. |
| /// `get_body` returns a `Serializer` with the bytes of the ICMP packet. |
| fn send_icmp_response<D: EventDispatcher, A, S, F>( |
| ctx: &mut Context<D>, |
| device: DeviceId, |
| src_ip: A, |
| dst_ip: A, |
| proto: IpProto, |
| get_body: F, |
| ) -> Result<(), (MtuError<S::InnerError>, S)> |
| where |
| A: IpAddress, |
| S: Serializer, |
| F: FnOnce(A) -> S, |
| { |
| trace!("send_icmp_response({}, {}, {}, {})", device, src_ip, dst_ip, proto); |
| increment_counter!(ctx, "send_icmp_response"); |
| |
| // TODO(joshlf): Should this be used for ICMP echo replies as well? |
| |
| // TODO(joshlf): Come up with rules for when to send ICMP responses. E.g., |
| // should we send a response over a different device than the device that |
| // the original packet ingressed over? We'll probably want to consult BCP 38 |
| // (aka RFC 2827) and RFC 3704. |
| |
| let ip_state = &mut ctx.state_mut().ip; |
| if let Some(route) = lookup_route(ip_state, src_ip) { |
| if let Some(local_ip) = |
| crate::device::get_ip_addr_subnet(ctx, route.device).map(AddrSubnet::into_addr) |
| { |
| send_ip_packet_from_device( |
| ctx, |
| route.device, |
| local_ip, |
| src_ip, |
| route.next_hop, |
| proto, |
| get_body(local_ip), |
| )?; |
| } else { |
| log_unimplemented!( |
| (), |
| "Sending ICMP over unnumbered device {} is unimplemented", |
| route.device |
| ); |
| |
| // TODO(joshlf): We need a general-purpose mechanism for choosing a |
| // source address in cases where we're a) acting as a router (and |
| // thus sending packets with our own source address, but not as a |
| // result of any local application behavior) and, b) sending over an |
| // unnumbered device (one without any configured IP address). ICMP |
| // is the notable use case. Most likely, we will want to pick the IP |
| // address of a different local device. See for an explanation of |
| // why we might have this setup: |
| // https://www.cisco.com/c/en/us/support/docs/ip/hot-standby-router-protocol-hsrp/13786-20.html#unnumbered_iface |
| } |
| } else { |
| debug!("Can't send ICMP response to {}: no route to host", src_ip); |
| } |
| |
| Ok(()) |
| } |