[recovery-netstack] ip: Improve extension traits, make them public

- Add IpPacketBuilder trait, implemented by Ipv{4,6}PacketBuilder
- Use IpPacketBuilder in IP module, simplifying sending logic
- Add more documentation to IP extension traits
- Make IP extension traits public so they can be used by other
  modules

Test: Existing tests pass; future CLs make use of this functionality,
      and are tested
Change-Id: I6bd3f68c09795b1c0a89e24ed1b218939ef7ff9c
diff --git a/garnet/bin/recovery_netstack/core/src/ip/mod.rs b/garnet/bin/recovery_netstack/core/src/ip/mod.rs
index 77126d6..40bb405 100644
--- a/garnet/bin/recovery_netstack/core/src/ip/mod.rs
+++ b/garnet/bin/recovery_netstack/core/src/ip/mod.rs
@@ -13,16 +13,12 @@
 pub use self::types::*;
 
 use log::{debug, trace};
-use std::fmt::Debug;
 use std::mem;
 
 use packet::{BufferMut, BufferSerializer, ParsablePacket, ParseBufferMut, Serializer};
-use zerocopy::{ByteSlice, ByteSliceMut};
 
 use crate::device::DeviceId;
 use crate::ip::forwarding::{Destination, ForwardingTable};
-use crate::wire::ipv4::{Ipv4Packet, Ipv4PacketBuilder};
-use crate::wire::ipv6::{Ipv6Packet, Ipv6PacketBuilder};
 use crate::{Context, EventDispatcher};
 
 // default IPv4 TTL or IPv6 hops
@@ -319,96 +315,11 @@
     assert!(!A::Version::LOOPBACK_SUBNET.contains(src_ip));
     assert!(!A::Version::LOOPBACK_SUBNET.contains(dst_ip));
 
-    specialize_ip_addr!(
-        fn serialize<D, S>(
-            ctx: &mut Context<D>, device: DeviceId, src_ip: Self, dst_ip: Self, next_hop: Self, ttl: u8, proto: IpProto, body: S
-        )
-        where
-            D: EventDispatcher,
-            S: Serializer,
-        {
-            Ipv4Addr => {
-                let body = body.encapsulate(Ipv4PacketBuilder::new(src_ip, dst_ip, ttl, proto));
-                crate::device::send_ip_frame(ctx, device, next_hop, body);
-            }
-            Ipv6Addr => {
-                let body = body.encapsulate(Ipv6PacketBuilder::new(src_ip, dst_ip, ttl, proto));
-                crate::device::send_ip_frame(ctx, device, next_hop, body);
-            }
-        }
-    );
-    A::serialize(ctx, device, src_ip, dst_ip, next_hop, DEFAULT_TTL, proto, body)
-}
-
-// An `Ip` extension trait for internal use.
-//
-// This trait adds extra associated types that are useful for our implementation
-// here, but which consumers outside of the ip module do not need to see.
-trait IpExt<B: ByteSlice>: Ip {
-    type Packet: IpPacket<B, Self>;
-}
-
-impl<B: ByteSlice, I: Ip> IpExt<B> for I {
-    default type Packet = !;
-}
-
-impl<B: ByteSlice> IpExt<B> for Ipv4 {
-    type Packet = Ipv4Packet<B>;
-}
-
-impl<B: ByteSlice> IpExt<B> for Ipv6 {
-    type Packet = Ipv6Packet<B>;
-}
-
-// `Ipv4Packet` or `Ipv6Packet`
-trait IpPacket<B: ByteSlice, I: Ip>: Sized + Debug + ParsablePacket<B, ()> {
-    fn src_ip(&self) -> I::Addr;
-    fn dst_ip(&self) -> I::Addr;
-    fn proto(&self) -> IpProto;
-    fn ttl(&self) -> u8;
-    fn set_ttl(&mut self, ttl: u8)
-    where
-        B: ByteSliceMut;
-}
-
-impl<B: ByteSlice> IpPacket<B, Ipv4> for Ipv4Packet<B> {
-    fn src_ip(&self) -> Ipv4Addr {
-        Ipv4Packet::src_ip(self)
-    }
-    fn dst_ip(&self) -> Ipv4Addr {
-        Ipv4Packet::dst_ip(self)
-    }
-    fn proto(&self) -> IpProto {
-        Ipv4Packet::proto(self)
-    }
-    fn ttl(&self) -> u8 {
-        Ipv4Packet::ttl(self)
-    }
-    fn set_ttl(&mut self, ttl: u8)
-    where
-        B: ByteSliceMut,
-    {
-        Ipv4Packet::set_ttl(self, ttl)
-    }
-}
-
-impl<B: ByteSlice> IpPacket<B, Ipv6> for Ipv6Packet<B> {
-    fn src_ip(&self) -> Ipv6Addr {
-        Ipv6Packet::src_ip(self)
-    }
-    fn dst_ip(&self) -> Ipv6Addr {
-        Ipv6Packet::dst_ip(self)
-    }
-    fn proto(&self) -> IpProto {
-        Ipv6Packet::proto(self)
-    }
-    fn ttl(&self) -> u8 {
-        Ipv6Packet::hop_limit(self)
-    }
-    fn set_ttl(&mut self, ttl: u8)
-    where
-        B: ByteSliceMut,
-    {
-        Ipv6Packet::set_hop_limit(self, ttl)
-    }
+    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);
 }
diff --git a/garnet/bin/recovery_netstack/core/src/ip/types.rs b/garnet/bin/recovery_netstack/core/src/ip/types.rs
index 77e34c8..08e0f76 100644
--- a/garnet/bin/recovery_netstack/core/src/ip/types.rs
+++ b/garnet/bin/recovery_netstack/core/src/ip/types.rs
@@ -7,7 +7,12 @@
 use std::hash::Hash;
 
 use byteorder::{ByteOrder, NetworkEndian};
-use zerocopy::{AsBytes, FromBytes, Unaligned};
+use packet::{PacketBuilder, ParsablePacket};
+use zerocopy::{AsBytes, ByteSlice, ByteSliceMut, FromBytes, Unaligned};
+
+use crate::error::ParseError;
+use crate::wire::ipv4::{Ipv4Packet, Ipv4PacketBuilder};
+use crate::wire::ipv6::{Ipv6Packet, Ipv6PacketBuilder};
 
 /// An IP protocol version.
 #[allow(missing_docs)]
@@ -427,6 +432,128 @@
     }
 }
 
+/// An extension trait to the `Ip` trait adding an associated `Packet` type.
+///
+/// `IpExt` extends the `Ip` trait, adding an associated `Packet` type. It
+/// cannot be part of the `Ip` trait because it requires a `B: ByteSlice`
+/// parameter (due to the requirements of `packet::ParsablePacket`).
+pub trait IpExt<B: ByteSlice>: Ip {
+    type Packet: IpPacket<B, Self, Builder = Self::PacketBuilder>;
+    type PacketBuilder: IpPacketBuilder<Self>;
+}
+
+// NOTE(joshlf): We know that this is safe because we seal the Ip trait to only
+// be implemented by Ipv4 and Ipv6.
+impl<B: ByteSlice, I: Ip> IpExt<B> for I {
+    default type Packet = !;
+    default type PacketBuilder = !;
+}
+
+impl<B: ByteSlice> IpExt<B> for Ipv4 {
+    type Packet = Ipv4Packet<B>;
+    type PacketBuilder = Ipv4PacketBuilder;
+}
+
+impl<B: ByteSlice> IpExt<B> for Ipv6 {
+    type Packet = Ipv6Packet<B>;
+    type PacketBuilder = Ipv6PacketBuilder;
+}
+
+/// An IPv4 or IPv6 packet.
+///
+/// `IpPacket` is implemented by `Ipv4Packet` and `Ipv6Packet`.
+pub trait IpPacket<B: ByteSlice, I: Ip>:
+    Sized + Debug + ParsablePacket<B, (), Error = ParseError>
+{
+    /// A builder for this packet type.
+    type Builder: IpPacketBuilder<I>;
+
+    /// The source IP address.
+    fn src_ip(&self) -> I::Addr;
+
+    /// The destination IP address.
+    fn dst_ip(&self) -> I::Addr;
+
+    /// The protocol (IPv4) or next header (IPv6) field.
+    fn proto(&self) -> IpProto;
+
+    /// The Time to Live (TTL).
+    fn ttl(&self) -> u8;
+
+    /// Set the Time to Live (TTL).
+    ///
+    /// `set_ttl` updates the packet's TTL in place.
+    fn set_ttl(&mut self, ttl: u8)
+    where
+        B: ByteSliceMut;
+}
+
+impl<B: ByteSlice> IpPacket<B, Ipv4> for Ipv4Packet<B> {
+    type Builder = Ipv4PacketBuilder;
+
+    fn src_ip(&self) -> Ipv4Addr {
+        Ipv4Packet::src_ip(self)
+    }
+    fn dst_ip(&self) -> Ipv4Addr {
+        Ipv4Packet::dst_ip(self)
+    }
+    fn proto(&self) -> IpProto {
+        Ipv4Packet::proto(self)
+    }
+    fn ttl(&self) -> u8 {
+        Ipv4Packet::ttl(self)
+    }
+    fn set_ttl(&mut self, ttl: u8)
+    where
+        B: ByteSliceMut,
+    {
+        Ipv4Packet::set_ttl(self, ttl)
+    }
+}
+
+impl<B: ByteSlice> IpPacket<B, Ipv6> for Ipv6Packet<B> {
+    type Builder = Ipv6PacketBuilder;
+
+    fn src_ip(&self) -> Ipv6Addr {
+        Ipv6Packet::src_ip(self)
+    }
+    fn dst_ip(&self) -> Ipv6Addr {
+        Ipv6Packet::dst_ip(self)
+    }
+    fn proto(&self) -> IpProto {
+        Ipv6Packet::proto(self)
+    }
+    fn ttl(&self) -> u8 {
+        Ipv6Packet::hop_limit(self)
+    }
+    fn set_ttl(&mut self, ttl: u8)
+    where
+        B: ByteSliceMut,
+    {
+        Ipv6Packet::set_hop_limit(self, ttl)
+    }
+}
+
+/// A builder for IP packets.
+///
+/// `IpPacketBuilder` is implemented by `Ipv4PacketBuilder` and
+/// `Ipv6PacketBuilder`.
+pub trait IpPacketBuilder<I: Ip>: PacketBuilder {
+    fn new(src_ip: I::Addr, dst_ip: I::Addr, ttl: u8, proto: IpProto) -> Self;
+}
+
+impl IpPacketBuilder<Ipv4> for Ipv4PacketBuilder {
+    fn new(src_ip: Ipv4Addr, dst_ip: Ipv4Addr, ttl: u8, proto: IpProto) -> Ipv4PacketBuilder {
+        Ipv4PacketBuilder::new(src_ip, dst_ip, ttl, proto)
+    }
+}
+
+impl IpPacketBuilder<Ipv6> for Ipv6PacketBuilder {
+    fn new(src_ip: Ipv6Addr, dst_ip: Ipv6Addr, ttl: u8, proto: IpProto) -> Ipv6PacketBuilder {
+        Ipv6PacketBuilder::new(src_ip, dst_ip, ttl, proto)
+    }
+}
+
 /// An IPv4 header option.
 ///
 /// An IPv4 header option comprises metadata about the option (which is stored