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.
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:
PacketSender -> WireGuardDevice -> UdpStatelessSocket
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, orALL. - Repeats each source IP
packets-per-iptimes. - 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:
- Configure
PacketSenderwith candidatesource-ipv4-rangevalues. - Configure
PacketReceiverwith the same ranges. - Send packets toward the receiver-side host or packet chain.
- Read the receiver report.
- Treat source IPs with high or complete delivery as the better candidates.
For single-protocol modes, set PacketReceiver.expected-packets-per-ip to the
sender packets-per-ip. For protocol-number: "ALL", set it to
packets-per-ip * 255.
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:
| Field | Type | Description |
|---|---|---|
name | string | User-chosen node name. Must be unique inside the config file. |
type | string | Must be exactly "PacketSender". |
settings | object | Required and must not be empty. |
next | string | Required. The next layer-3 node receives generated packets. |
Required settings fields:
| Field | Type | Description |
|---|---|---|
source-ipv4-range | string or array of strings | One IPv4 CIDR range, or an array of IPv4 CIDR ranges. |
dest-ipv4 | string | Destination IPv4 address. Must be a single IPv4 address, not CIDR. |
protocol-number | string | TCP, UDP, ICMP, or ALL. Case-insensitive. |
duration-ms | integer | Positive send duration in milliseconds. |
dest-port | integer | Required for TCP, UDP, and ALL. Must be non-zero and in 0..65535. |
src-port | integer 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
| Setting | Type | Default | Description |
|---|---|---|---|
packets-per-ip | integer | 1 | Positive 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:
| Field | Value |
|---|---|
| TTL | 64 |
| fragment flag | Don't Fragment (DF) |
| source IP | one address from source-ipv4-range |
| destination IP | dest-ipv4 |
| checksums | calculated before materialization |
Protocol-specific shapes:
protocol-number | Generated packet |
|---|---|
TCP | Minimal TCP segment with ACK flag, deterministic sequence/ack numbers, configured ports, and valid checksums. |
UDP | Minimal UDP datagram with configured ports and valid checksums. |
ICMP | ICMP echo request with deterministic identifier/sequence and valid checksums. |
ALL | For 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
PacketReceiveron the target side to get per-source delivery counts. - Choose
report-after-msonPacketReceiverlonger than senderduration-msplus expected network delay and reordering time. - Use
ICMPfor simple path probing when allowed. - Use
TCP,UDP, orALLwhen 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.