In no particular order:
Flush all the "class", "subnet" and "host" functions. IP address classes
haven't really meant anything for years. In practice, the only IP addreses
you can reliably separate into subnet and host parts are addresses on your
-own- subnet -- for the rest of the Internet you generally have no idea what
anybody's subnet mask is.
The IP4 address space is indeed embedded in the IP6 address space, but I
don't think this means that you want to make (ip-address-ip4? x) imply
(ip-address-ip6? x). That will depend on how your operating system
supports IP4 and IP6. There might be occasions where you want distinct IP4
addresses so that you can be explict about the fact that you want to
communicate throught the IP4 stack. I don't really know. You should
probably read throught the RFCs that describe the proposed IP6 "sockets"
interface. That would be RFCs 2292 and 2553.
The most common operation on addresses are (1) to pass them in and out of
your network stack, (2) to display and parse them, and (3), on those
occasions where you -do- know a subnet, to test for membership on that
subnet. So you might want to support another ADT, called say "IP Subnet"
that you display and parse using the standard "/" notation. So for
(ip-subnet-member (string->ip-address "126.96.36.199")
=> 3 ; returns #F or host number.
There are other useful operations I can imagine on such subnet objects.
Having addresses be an ADT has the great advantage of letting the
implementation print them in some informative way. When debugging, I'd
#<IP4 Addr 188.8.131.52 (mintaka.lcs.mit.edu)>
Actually, I would argue against providing any way to translate directly
between IP addreses and integers. Avoid the bigendian/littlendian
alltogether: For occasions where people need access to the raw address,
given them an operation that returns a vector of bytes.