Skip to main content

PacketSender

PacketSender is a layer-3 IPv4 packet generator. It pre-builds raw IPv4 packets at startup and sends them across WaterWall worker packet lines over a configured duration.

It is mainly useful for controlled packet-path testing. Paired with PacketReceiver, it can help find good, reachable, or whitelisted source IPs in IP-restricted environments: generate packets from candidate source ranges, send them through the packet path, then compare the receiver report to see which source IPs arrived.

warning

Only use this on networks and address ranges you are authorized to test. Source-IP experiments also depend on the sender-side network. If your provider, kernel path, raw socket backend, or upstream network blocks non-local source addresses, the generated packets may never leave the sender.

Typical Placement​

Raw IPv4 output:

PacketSender -> RawSocket

With packet manipulation:

PacketSender -> IpManipulator -> RawSocket

WireGuard-style packet transport:

packet source branch: PacketSender -> Bridge A
UDP/WireGuard branch: UdpStatelessSocket -> WireGuardDevice -> Bridge B

Pair Bridge A and Bridge B. Both PacketSender and UdpStatelessSocket are chain-head-only, so they belong at the start of separate branches.

Matching receiver side:

RawSocket -> PacketReceiver

PacketSender is a chain-head singleton adapter. It must be at the start of a layer-3 chain and must have a next node.

What It Does​

  • Accepts one IPv4 source CIDR range or an array of source CIDR ranges.
  • Accepts one destination IPv4 address.
  • Builds raw IPv4 packets for TCP, UDP, ICMP, or ALL.
  • Repeats each source IP packets-per-ip times.
  • Materializes the whole packet store before sending starts.
  • Distributes packet indexes across workers.
  • Sends packets over duration-ms, using packet index deadlines to spread them across the configured duration.
  • Updates the packet line routing context before each send.
  • Does not close or finish worker packet lines after sending.
  • Stops a worker's timer and send loop if the downstream side finishes that worker packet line.

Source ranges are processed in config order. They are not interleaved.

Source-IP Allowlist Testing​

PacketSender and PacketReceiver are built for scenarios where the path or service is restricted by source IP. A common workflow is:

  1. Configure PacketSender with candidate source-ipv4-range values.
  2. Configure PacketReceiver with the same ranges.
  3. Send packets toward the receiver-side host or packet chain.
  4. Read the receiver report.
  5. Treat source IPs with high or complete delivery as the better candidates.

Set PacketReceiver.protocol-number to the expected protocol selection and PacketReceiver.expected-packets-per-ip to sender packets-per-ip, including in ALL mode. PacketReceiver calculates the per-IP total itself and reports each protocol separately. It also accepts arrays such as [6, 17] or ["TCP", "UDP"] when receiving several protocols.

Configuration Example​

ICMP probing:

{
"name": "packet-sender",
"type": "PacketSender",
"settings": {
"source-ipv4-range": [
"198.51.100.0/24",
"203.0.113.0/24"
],
"dest-ipv4": "203.0.113.20",
"protocol-number": "ICMP",
"duration-ms": 5000,
"packets-per-ip": 100
},
"next": "raw-ip"
}

TCP-shaped probing:

{
"name": "packet-sender",
"type": "PacketSender",
"settings": {
"source-ipv4-range": "198.51.100.0/24",
"dest-ipv4": "203.0.113.20",
"protocol-number": "TCP",
"duration-ms": 5000,
"packets-per-ip": 10,
"dest-port": 443,
"src-port": "random"
},
"next": "raw-ip"
}

Required Fields​

Top-level fields:

FieldTypeDescription
namestringUser-chosen node name. Must be unique inside the config file.
typestringMust be exactly "PacketSender".
settingsobjectRequired and must not be empty.
nextstringRequired. The next layer-3 node receives generated packets.

Required settings fields:

FieldTypeDescription
source-ipv4-rangestring or array of stringsOne IPv4 CIDR range, or an array of IPv4 CIDR ranges.
dest-ipv4stringDestination IPv4 address. Must be a single IPv4 address, not CIDR.
protocol-numberstringTCP, UDP, ICMP, or ALL. Case-insensitive.
duration-msintegerPositive send duration in milliseconds.
dest-portintegerRequired for TCP, UDP, and ALL. Must be non-zero and in 0..65535.
src-portinteger or "random"Required for TCP, UDP, and ALL. Must be non-zero, or "random".

dest-port and src-port are not required for ICMP.

Optional Settings​

SettingTypeDefaultDescription
packets-per-ipinteger1Positive repeat count for each source IP. In ALL mode, each repeat sends 255 protocol variants.

When src-port is "random", the current implementation selects a stable ephemeral-range port from 49152..65535 based on source IP, destination IP, and protocol. It is deterministic for the generated packet shape rather than random per packet.

Generated Packet Shapes​

All generated packets are IPv4 with:

FieldValue
TTL64
fragment flagDon't Fragment (DF)
source IPone address from source-ipv4-range
destination IPdest-ipv4
checksumscalculated before materialization

Protocol-specific shapes:

protocol-numberGenerated packet
TCPMinimal TCP segment with ACK flag, deterministic sequence/ack numbers, configured ports, and valid checksums.
UDPMinimal UDP datagram with configured ports and valid checksums.
ICMPICMP echo request with deterministic identifier/sequence and valid checksums.
ALLFor every source IP and repeat, one packet for protocol numbers 0 through 254. Protocols 6, 17, and 1 are valid TCP, UDP, and ICMP shapes; all others are IPv4 packets with an 8-byte opaque payload.

ALL mode emits 255 protocol variants per repeat, not 256.

Scheduling And Workers​

Before runtime starts, PacketSender materializes every packet into a contiguous packet store. During startup:

  • total packet indexes are divided across workers
  • each worker uses its persistent worker packet line
  • each packet gets a global deadline based on its index and duration-ms
  • workers send ready packets and re-arm one-shot timers when the next packet is not due yet

This spreads packets across the configured duration instead of sending the whole store as one immediate burst. If several packets are due at the same moment, a worker may send them back-to-back.

Limits​

  • Total source IP count must fit in uint32_t.
  • Total packet count must fit in uint64_t.
  • Total materialized packet bytes must fit in size_t.
  • The materialized packet store is capped at 8 GiB.
  • required_padding_left = 0.

Large ranges, ALL mode, and high packets-per-ip values grow memory usage quickly. Split large tests into smaller ranges or several runs if needed.

Packet-Line Semantics​

PacketSender uses worker packet lines created by the chain. These packet lines are persistent worker helper lines, not per-connection lines.

PacketSender does not destroy packet lines and does not treat Finish as successful completion. A downstream Finish permanently stops that worker's timer and send loop, while normal completion only logs after all assigned packets were sent.

Practical Notes​

  • Use PacketReceiver on the target side to get per-source delivery counts.
  • Choose report-after-ms on PacketReceiver longer than sender duration-ms plus expected network delay and reordering time.
  • Use ICMP for simple path probing when allowed.
  • Use TCP, UDP, or ALL when the restricted environment behaves differently by protocol or port.
  • If the sender-side network enforces source address validation, spoofed or non-local source ranges may be filtered before reaching WaterWall's peer.

Node Metadata​

Source-backed metadata:

PropertyValue
node flagskNodeFlagChainHead | kNodeFlagSingleton
can_have_prevfalse
can_have_nexttrue
layer_groupkNodeLayer3
layer_group_prev_nodekNodeLayerNone
layer_group_next_nodekNodeLayer3
required_padding_left0 bytes