IPv4 Wire Coverage
August 29, 2026 ยท View on GitHub
This page describes the IPv4 packet-layer support in the crafter crate: what
the Ipv4 layer builds and decodes, how dependent header fields are filled on
compile(), how deliberate overrides are preserved, how options and fragment
metadata are exposed, and what is intentionally out of scope.
crafter treats IPv4 as one packet layer. It composes with /, compiles into a
single IPv4 datagram, decodes from the decode_from_l3 entrypoints, and stays
inspectable through summary(), show(), and typed getters. The base Ipv4
layer is not an IP stack, router, path MTU engine, scanner, or fuzzer. IPv4
fragment generation and reassembly live in the packet-stream IpFragment and
IpDefrag transforms under crafter::wire. See
Explicit exclusions.
All wire facts on this page trace to reviewed RFC text and IANA registries. The RFCs and registries the IPv4 layer implements are listed in Standards and RFCs implemented at the end of this guide.
Coverage at a glance
| Area | State | Notes |
|---|---|---|
| Base header | Supported | Version, IHL, DS field, Total Length, ID, flags, fragment offset, TTL, Protocol, checksum, source, destination. |
| Construction | Supported | Ipv4::new(), with_addresses, builder setters, / composition, and raw payloads. |
| Auto-filled fields | Supported | IHL, Total Length, Protocol for ICMP/TCP/UDP stacks, option padding, and header checksum. |
| Deliberate overrides | Supported | Explicit DS field, Total Length, ID, flags, fragment offset, TTL, Protocol, checksum, addresses, and options are preserved within the header validation model. |
| DSCP / ECN | Supported | Dscp, Ecn, ds_field, dscp, ecn, and decode-time getters. |
| Protocol numbers | Supported | Ipv4Protocol variants, IPPROTO_* constants, labels in summaries, and Raw fallback for unknown or unsupported payloads. |
| Checksum status | Supported | Header checksum auto-fill on compile; decode records Ipv4ChecksumStatus. Invalid checksums remain inspectable. |
| Options | Supported | Raw options, typed Ipv4Option helpers, Ipv4OptionIter, parsed_options, and option-kind metadata. |
| Fragment fields and transforms | Supported | ID, reserved/DF/MF flags, fragment offset, Ipv4FragmentInfo, receive-side IpDefrag, and transmit-side IpFragment. |
| Decode errors | Supported | Malformed IPv4 headers and options return structured CrafterError values. |
| Inspection | Supported | summary(), show(), hexdump(), and per-field getters. |
| Pcap / oracle coverage | Supported | Focused public API tests, malformed corpus entries, deterministic fixtures, pcap-mode validation, and the ipv4-enrichment oracle profile. |
IPv4 construction
The Ipv4 layer is exported through crafter::prelude::* and from the crate
root. Build it with Ipv4::new() or Ipv4::with_addresses, set fields with
builder methods, and compose it with the next layer using /:
use crafter::prelude::*;
use std::net::Ipv4Addr;
let packet = Ipv4::new()
.src(Ipv4Addr::new(192, 0, 2, 10))
.dst(Ipv4Addr::new(198, 51, 100, 20))
.ttl(64)
/ Udp::new().sport(53000).dport(53)
/ Raw::from("dns bytes");
let compiled = packet.compile()?;
println!("{}", packet.summary());
println!("{}", compiled.hexdump());
Ipv4::new() uses deterministic packet-builder defaults: version 4, DS field
0, identification 1, flags 0, fragment offset 0, TTL 64, protocol
0, and loopback source/destination addresses. For user-facing examples and
tests, prefer explicit documentation addresses such as 192.0.2.0/24 and
198.51.100.0/24.
Setters cover the full header surface:
version,ihl,total_length/lentos/ds_field,dscp,ecnidentification/idflags,reserved_flag,dont_fragment,more_fragmentsfragment_offset/fragttlprotocol/protochecksum/chksumsrc,src_str,dst,dst_stroption,ipv4_option/ip_option,options,clear_options
compile() fills fields that were left unset:
- IHL from the fixed 20-octet header plus option bytes and padding.
- Total Length from the IPv4 header plus payload.
- Protocol from the next layer when the stack contains ICMPv4, TCP, or UDP.
- Option padding to a 32-bit boundary.
- IPv4 header checksum over the IPv4 header only.
Explicit values are not rewritten just because they are unusual. For example,
an explicit protocol(253), checksum(0xbeef), ttl(0), reserved flag bit, or
maximum fragment offset is emitted as requested when it fits the modeled wire
field. Structural constraints still apply: IHL must be 5..15 words, options must
fit in the 60-octet header limit, flags must fit in three bits, fragment offset
must fit in 13 bits, and Total Length must be at least the header length.
DSCP and ECN
IPv4's historical TOS octet is exposed as the DS field: six DSCP bits followed by two ECN bits. The raw compatibility methods remain available, but new code should prefer DS field terminology.
use crafter::prelude::*;
use std::net::Ipv4Addr;
let packet = Ipv4::with_addresses(
Ipv4Addr::new(192, 0, 2, 10),
Ipv4Addr::new(198, 51, 100, 20),
)
.dscp(Dscp::new(46)?) // EF codepoint value
.ecn(Ecn::Ect0)
/ Raw::from("marked payload");
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv4, packet.compile()?.as_bytes())?;
let ipv4 = decoded.layer::<Ipv4>().expect("IPv4 layer");
assert_eq!(ipv4.dscp_value(), Dscp::new(46)?);
assert_eq!(ipv4.ecn_value(), Ecn::Ect0);
Useful DS field APIs:
Dscp::new(value)accepts six-bit values0..=63.Dscp::from_ds_field(byte)extracts the six high bits.Ecn::new(value)accepts two-bit values0..=3.Ecn::from_ds_field(byte)extracts the two low bits.Ipv4::ds_field(byte)sets the full octet.Ipv4::dscp(dscp)preserves the current ECN bits.Ipv4::ecn(ecn)preserves the current DSCP bits.ds_field_value,tos_value,dscp_value, andecn_valueinspect the builder or decoded layer.
The ECN variants are Ecn::NotEct, Ecn::Ect1, Ecn::Ect0, and Ecn::Ce.
summary() prints nonzero DS field state as ds=dscp=.../ecn=...; show()
always includes tos, dscp, and ecn.
Protocol numbers and Raw fallback
The IPv4 Protocol field can be set as a raw byte or through the common
Ipv4Protocol enum:
use crafter::prelude::*;
use std::net::Ipv4Addr;
let tcp_probe = Ipv4::with_addresses(
Ipv4Addr::new(192, 0, 2, 10),
Ipv4Addr::new(198, 51, 100, 20),
)
.ipv4_protocol(Ipv4Protocol::Tcp)
/ Tcp::new().sport(41000).dport(443).syn();
let opaque = Ipv4::with_addresses(
Ipv4Addr::new(192, 0, 2, 10),
Ipv4Addr::new(198, 51, 100, 20),
)
.protocol(IPPROTO_EXPERIMENTAL_1)
/ Raw::from("opaque-ipv4");
Exported protocol constants include IPPROTO_ICMP, IPPROTO_TCP,
IPPROTO_UDP, IPPROTO_IPV6, IPPROTO_GRE, IPPROTO_ESP, IPPROTO_AH,
IPPROTO_ICMPV6, IPPROTO_OSPF, IPPROTO_SCTP,
IPPROTO_EXPERIMENTAL_1, and IPPROTO_EXPERIMENTAL_2. Matching
Ipv4Protocol variants exist for those common values.
compile() can infer 1, 6, or 17 from ICMPv4, TCP, or UDP when the
Protocol field was not set explicitly. It does not infer body encoders for every
IANA assignment. If the Protocol value is unknown, experimental, reserved, or
known but unsupported by the current registry, decode keeps the Ipv4 layer
typed and preserves any payload as a trailing Raw layer. An unknown protocol
with an empty payload does not synthesize an empty Raw.
Header checksum status
The IPv4 header checksum covers only the IPv4 header. compile() writes a
correct checksum when checksum / chksum was left unset. An explicit checksum
is emitted verbatim, including a deliberately invalid value.
Decode records checksum validation on the Ipv4 layer:
use crafter::prelude::*;
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv4, bytes)?;
let ipv4 = decoded.layer::<Ipv4>().expect("IPv4 layer");
match ipv4.checksum_status() {
Ipv4ChecksumStatus::Valid => {}
Ipv4ChecksumStatus::Invalid => eprintln!("invalid IPv4 header checksum"),
Ipv4ChecksumStatus::NotChecked => eprintln!("checksum was not checked"),
}
checksum_value() returns the explicit or decoded 16-bit field value.
checksum_status() returns Ipv4ChecksumStatus::NotChecked, Valid, or
Invalid. Invalid checksums do not cause decode to drop the datagram: the
packet remains inspectable and re-compiles with the same checksum value.
summary() includes checksum_status=invalid only for invalid checksums;
show() always includes the checksum field and status.
Options
IPv4 options can be appended as raw bytes or as typed Ipv4Option values:
use crafter::prelude::*;
use std::net::Ipv4Addr;
let ip = Ipv4::with_addresses(
Ipv4Addr::new(192, 0, 2, 10),
Ipv4Addr::new(198, 51, 100, 20),
)
.ipv4_option(Ipv4Option::router_alert(0))?;
let ip = ip.ipv4_option(Ipv4Option::timestamp(5, 0, vec![0x0102_0304]))?;
let packet = ip.protocol(IPPROTO_EXPERIMENTAL_1) / Raw::from("optioned");
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv4, packet.compile()?.as_bytes())?;
let ipv4 = decoded.layer::<Ipv4>().expect("IPv4 layer");
for option in ipv4.option_iter() {
println!("{:?}", option?);
}
Typed constructors include:
Ipv4Option::end_of_list()andIpv4Option::no_operation()Ipv4Option::generic(kind, data)Ipv4Option::timestamp(pointer, overflow, timestamps)Ipv4Option::timestamp_with_addresses(pointer, overflow, entries)Ipv4Option::timestamp_prespecified(pointer, overflow, entries)Ipv4Option::router_alert(value)Ipv4Option::record_route(pointer, routes)Ipv4Option::loose_source_route(pointer, routes)Ipv4Option::strict_source_route(pointer, routes)Ipv4Option::traceroute(id_number, outbound_hop_count, return_hop_count, originator)
Option inspection APIs:
option_bytes()returns the raw option area, including decode-time padding bytes.option_iter()iterates over decodedIpv4Optionvalues.parsed_options()collects typed options into aVec<Ipv4Option>.Ipv4OptionKind::new(kind)splits a kind byte into copied flag, class, and option number.Ipv4OptionKind::is_experimental()classifies the RFC 4727 experiment option values.
compile() pads the IPv4 header to a 32-bit boundary. Unknown options are
preserved as Ipv4Option::Generic. Malformed option envelopes return structured
errors such as ipv4 option, ipv4.option.length, ipv4.option.pointer, or
ipv4.option.timestamp; they do not panic or loop.
Fragment Fields And Transforms
The Ipv4 layer exposes IPv4's fragmentation-related header fields. The layer
itself only models one datagram header; it does not keep fragment queues or
split outbound streams. Use IpDefrag on receive-side Sniffer pipelines and
IpFragment on transmit-side Transmitter pipelines when a packet stream needs
those transforms.
use crafter::prelude::*;
use std::net::Ipv4Addr;
let packet = Ipv4::with_addresses(
Ipv4Addr::new(192, 0, 2, 10),
Ipv4Addr::new(198, 51, 100, 20),
)
.identification(0x4242)
.more_fragments(true)
.fragment_offset(7)
.protocol(IPPROTO_EXPERIMENTAL_1)
/ Raw::from("fragment metadata example");
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv4, packet.compile()?.as_bytes())?;
let ipv4 = decoded.layer::<Ipv4>().expect("IPv4 layer");
let fragment = ipv4.fragment_info();
assert_eq!(fragment.identification(), 0x4242);
assert!(fragment.has_more_fragments());
assert_eq!(fragment.fragment_offset(), 7);
Field helpers:
identification/id, read withidentification_value()flags, read withflags_value()reserved_flag, read withis_reserved_flag_set()dont_fragment, read withis_dont_fragment()more_fragments, read withhas_more_fragments()fragment_offset/frag, read withfragment_offset_value()fragment_info()for anIpv4FragmentInfosnapshotis_fragmented()to testMF || fragment_offset != 0
Decode policy is intentionally conservative. A non-initial fragment
(fragment_offset != 0) keeps the IPv4 header typed and preserves the payload as
Raw, because the transport header is not available without reassembly. An
offset-zero packet with MF set may decode a complete transport header when the
payload is self-consistent; otherwise the payload remains Raw. The decoder
does not maintain fragment caches by itself.
Receive-side reassembly belongs on a source or sniffer:
use crafter::prelude::*;
let first = PacketRecord::new(
Ipv4::new().src("192.0.2.10")?.dst("198.51.100.20")?
.protocol(IPPROTO_EXPERIMENTAL_1)
.identification(0x2024)
.more_fragments(true)
.fragment_offset(0)
/ Raw::from_bytes(b"abcdefgh"),
);
let final_fragment = PacketRecord::new(
Ipv4::new().src("192.0.2.10")?.dst("198.51.100.20")?
.protocol(IPPROTO_EXPERIMENTAL_1)
.identification(0x2024)
.fragment_offset(1)
/ Raw::from_bytes(b"ijkl"),
);
let records = Sniffer::new(VecPacketSource::new([final_fragment, first]))
.with(IpDefrag::new())
.collect_records()?;
for metadata in records[0].metadata().ip_defrag_metadata() {
println!("{:?}", metadata);
}
# Ok::<(), crafter::CrafterError>(())
IpDefrag groups IPv4 fragments by source, destination, protocol, and
identification. It accepts exact duplicate ranges, records overlaps in
IpDefragMetadata, and does not silently emit ambiguous bytes for conflicting
overlaps. State is bounded by configured datagram count, byte count, and age.
Transmit-side fragmentation belongs on a writer or transmitter. The default
IPv4 policy honors DF: if a DF-set packet is larger than the configured MTU,
IpFragment returns a structured error instead of fragmenting unless the caller
chooses an explicit override policy.
use crafter::prelude::*;
let writer = MemoryPacketWriter::dry_run();
let mut tx = Transmitter::new(writer).with(IpFragment::new(576));
let reports = tx.send(
Ipv4::new().src("192.0.2.10")?.dst("198.51.100.20")?
.identification(0x2025)
/ Udp::new().sport(40000).dport(40001)
/ Raw::from_bytes(&[0u8; 1200]),
)?;
assert!(reports.iter().all(|report| report.is_dry_run()));
# Ok::<(), crafter::CrafterError>(())
Examples use documentation address space and offline or dry-run writers. Do not turn fragment examples into live traffic instructions; external operator tooling owns any explicitly authorized live run.
The IpFragment and IpDefrag transforms are source-backed for IPv4 by:
- RFC 791 - the IPv4 fragmentation and reassembly model: 8-octet fragment offsets, DF and MF behavior, the source/destination/protocol/identification reassembly identity, and the header fields (IHL, Total Length, Header Checksum) recomputed per fragment.
- RFC 6864 - the atomic versus non-atomic distinction. For atomic datagrams
(
DF=1,MF=0, offset0) the Identification value carries no fragmentation meaning; the transforms do not invent uniqueness requirements for those packets. Non-atomic fragmentation preserves the RFC 791 identity tuple.
IpDefrag does not silently emit ambiguous bytes for conflicting overlaps, and
IpFragment honors DF rather than splitting a DF-set datagram. Live PMTUD,
fragment caches, and stack delivery stay out of scope.
Decode behavior
Use Packet::decode_from_l3(NetworkLayer::Ipv4, bytes) for raw IPv4 datagrams.
The same IPv4 decoder is reached through link-layer decoders such as Ethernet,
Linux cooked capture, null/loopback, and pcap readers when the link type says
the payload is IPv4.
Decode behavior:
- A valid IPv4 header becomes an
Ipv4layer with all header fields populated. - ICMPv4, TCP, and UDP payloads dispatch through the protocol registry when the payload is complete enough for the corresponding decoder.
- Unknown or unsupported Protocol values preserve the payload as
Raw. - Non-initial fragments preserve the payload as
Raw. - Bytes after the IPv4 Total Length boundary are outside the datagram and become
a separate trailing
Rawlayer. - A Total Length that is smaller than the header length is an invalid-field error.
- A Total Length that is larger than the available buffer is a buffer-too-short error.
- Version other than 4, IHL below 5, truncated fixed headers, truncated option
areas, and malformed option lengths return structured
CrafterErrorvalues.
This keeps malformed or unknown traffic inspectable without turning truncation or unsupported protocol numbers into panics.
Inspection
Every IPv4 packet remains inspectable:
Packet::summary()joins each layer's one-line summary. The IPv4 line includessrc,dst,proto, nonzero DS field state, nonzero flags, nonzero fragment offset, invalid checksum status, and option count.Packet::show()prints the full field tree. IPv4 fields includeversion,ihl,tos,dscp,ecn,total_length,id,flags,fragment_offset,ttl,protocol,checksum,checksum_status,src,dst,option_count, and rawoptions.Packet::hexdump()andCompiledPacket::hexdump()produce canonical byte dumps for compiled packets.
Example:
use crafter::prelude::*;
use std::net::Ipv4Addr;
let packet = Ipv4::with_addresses(
Ipv4Addr::new(192, 0, 2, 10),
Ipv4Addr::new(198, 51, 100, 20),
)
.dscp(Dscp::new(46)?)
.ecn(Ecn::Ce)
.dont_fragment(true)
.protocol(IPPROTO_EXPERIMENTAL_1)
/ Raw::from("inspect");
println!("{}", packet.summary());
println!("{}", packet.show());
Per-field getters mirror the inspection output:
version_value,ihl_value,header_lentos_value,ds_field_value,dscp_value,ecn_valuetotal_length_valueidentification_value,flags_value,fragment_offset_valuettl_value,protocol_valuechecksum_value,checksum_statussource,destinationoption_bytes,option_iter,parsed_options
Validation coverage
IPv4 behavior is covered by focused offline tests and deterministic fixtures:
crafter/tests/ipv4_public_api.rspins construction, compile output, decode, DSCP/ECN helpers, checksum status, options, fragment fields, unknown-protocolRawfallback, Total Length boundaries,summary(), andshow().crafter/tests/fixtures/malformed/core-decode-corpus.hexincludes malformed IPv4 option cases for structured-error coverage.tools/oracle/specs/profiles.yamldefines theipv4-enrichmentprofile for focused offline IPv4 header behavior.tools/oracle/specs/features/ip-fragment-transforms.yamlcovers IPv4 fragment transform contracts and runnable offline packet cases.tools/oracle/specs/stacks.yamlincludes IPv4 payload stacks for boundary fields, unknown protocolRaw, MF+offset fragments, TTL 255, and option coverage.tools/oracle/adapters/src/bin/vectors/cases.rscontains deterministic IPv4 vectors, including option and source-route/traceroute cases.- Pcap-mode validation and fixture tests exercise IPv4 datagrams through classic pcap paths and link wrappers such as Ethernet, VLAN, Linux cooked capture, and null/loopback.
All documented examples use documentation address space and offline construction or decode. The crate exposes explicit live send and bounded capture primitives, while target selection and execution remain external.
Explicit exclusions
crafter stays a packet primitive. The IPv4 layer does not implement:
- IPv4 routing, forwarding, TTL decrement, route selection, or ICMP generation caused by forwarding.
- Stack delivery of reassembled data, TCP stream reassembly, or application
payload reconstruction after
IpDefrag. - Global IPv4 Identification allocation or uniqueness tracking.
- Path MTU Discovery, Packetization Layer PMTUD, MTU probing, or MTU caches.
- A full IP stack, scanner, fuzzer, analyzer workflow, or live traffic policy.
The fields needed to build or inspect those packets are present; the workflows that decide when to send them belong outside the crate primitive.
Standards and RFCs implemented
The IPv4 layer traces every wire fact to reviewed RFC text and IANA registries. The library implements the following for IPv4 (deferred or out-of-scope items are marked):
- RFC 791 - Internet Protocol - the IPv4 base header, option encoding, header checksum, and the fragmentation/reassembly model.
- RFC 1122 - Requirements for Internet Hosts - host requirements for version handling, checksum validation, TTL guidance, option robustness, and fragmentation/reassembly expectations.
- RFC 2474 - Differentiated Services (DS) Field - the six-bit DSCP layout that replaces the historical TOS octet.
- RFC 3168 - Explicit Congestion Notification (ECN) - the two ECN bits and the Not-ECT, ECT(1), ECT(0), and CE codepoints.
- RFC 6864 - Updated IPv4 ID Field - atomic versus non-atomic datagram semantics for the Identification field.
- RFC 2113 - IP Router Alert Option - the Router Alert option wire format (option value 148; value 0 means routers examine the packet).
- RFC 1393 - Traceroute Using an IP Option - the Traceroute option data layout.
- RFC 4727 - Experimental Values - the RFC 3692-style IPv4 option experiment values, classified for inspection (never a production default).
- RFC 7126 - Filtering of IPv4 Packets Containing IPv4 Options - operational guidance for option handling; informs documentation and test scope, adds no new wire encoding.
- IANA Assigned Internet Protocol Numbers - authority for the Protocol field numeric assignments, names, and reserved/experimental ranges.
- IANA Internet Protocol Version 4 (IPv4) Parameters - authority for IPv4 option numbers, Router Alert values, and the recommended default TTL (64).
- IANA Differentiated Services Field Codepoints (DSCP) - authority for DSCP codepoint names and ECN field registrations.
Out of scope for the IPv4 layer: routing, forwarding, TTL decrement, ICMP
generation from forwarding, global Identification allocation, Path MTU
Discovery, automatic decode-time reassembly, and layer-owned fragmentation.
Use the explicit IpFragment and IpDefrag wire transforms for packet-stream
fragmentation and reassembly.