> ## Documentation Index
> Fetch the complete documentation index at: https://wireblast.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Flows and addressing

> How Wireblast generates flows, why they are deterministic, and how it works out the next-hop MAC.

A flow here is what a router or a load balancer would call one: a stable combination of source IP, destination IP, source port and destination port. `--flows 64` means 64 distinct tuples, cycled through as packets go out.

This matters when you're testing anything that hashes: ECMP, LAG, RSS, a firewall's connection table. One flow exercises one path. A thousand flows exercise the spread.

## Generating them

```bash theme={null}
sudo wireblast -i eno2 --dst-ip 192.0.2.10 --flows 1000 --src-port 1024
```

By default the **source port increments** per flow, starting at `--src-port`, and the destination port stays fixed. That mirrors real client traffic, where many clients hit one service port.

Add `--vary-dst-port` to increment both, which spreads across a wider hash space.

Give `--dst-ip` a CIDR and destinations cycle across flows too:

```bash theme={null}
sudo wireblast -i eno2 --dst-ip 10.0.0.0/24 --flows 256
```

Network and broadcast addresses are skipped for /30 and shorter. A /31 uses both of its addresses ([RFC 3021](https://www.rfc-editor.org/rfc/rfc3021)), and a /32 uses its single address.

## Order

`--flow-order sequential` (the default) walks flows in order. `--flow-order random` scatters them, so consecutive packets land on different tuples.

"Random" is a fixed permutation, not randomness: a coprime stride derived from the golden ratio. The same config always produces the same order. You get scattering without losing reproducibility.

## It's deterministic

The same configuration always produces the same tuples in the same order. Two things follow:

* **A run is reproducible.** Same flags today and next week, same packets.
* **Queue count doesn't change the traffic.** Queue *q* of *Q* takes flow *q* and steps by *Q*, so together they cover every flow exactly once per cycle. Changing `--queues` changes which queue carries a flow, never which flows exist. If there are fewer flows than queues, the modulo folds them back so every queue stays busy.

That second one is what makes `--queues` safe to use as an experimental variable. See [finding your max rate](/guides/max-rate).

## Working out the next-hop MAC

Wireblast needs a destination MAC before it can send anything. It works one out from your routing and neighbour tables, and if it can't, it stops and tells you why. **It never falls back to broadcast**, because flooding every port in the broadcast domain is not a reasonable thing to do by accident.

```mermaid theme={null}
flowchart TD
    S([Start]) --> E{--dst-mac given?}
    E -->|yes| U[Use it. No lookups at all.]
    E -->|no| M{Mode has IP addresses?}
    M -->|"raw"| F1[Stop: nothing to resolve from.<br/>Give --dst-mac.]
    M -->|"pcap / receive"| N[Not needed]
    M -->|yes| R{Destination on-link?}
    R -->|yes| NB{In the neighbour table?}
    NB -->|yes| OK1[Use it]
    NB -->|no| A[Send an ARP probe, wait 1.5s]
    A --> A2{Answered?}
    A2 -->|yes| OK2[Use it]
    A2 -->|no| F2[Stop: it did not answer ARP]
    R -->|no| G{Route has a gateway?}
    G -->|yes| GW[Resolve the gateway's MAC instead]
    G -->|no| F3[Stop: every destination<br/>would need its own MAC]
```

The review screen shows which branch it took:

```text theme={null}
next hop      3c:ec:ef:b4:c2:dc (given with --dst-mac)
next hop      3c:ec:ef:b4:c2:dc (from the neighbour table)
next hop      3c:ec:ef:b4:c2:dc (resolved by ARP)
next hop      00:1a:2b:3c:4d:5e (gateway's MAC)
```

### When it stops

Each failure names the fix.

<AccordionGroup>
  <Accordion title="The destination did not answer ARP">
    ```text theme={null}
    the destination 192.0.2.10 did not answer ARP on eno2, so its MAC address is
    unknown.
    Give an explicit --dst-mac (or fill in Destination MAC in the wizard) to continue.
    Check it is reachable (`ping -c1 192.0.2.10`), then try again — or give --dst-mac directly.
    ```

    Usually the destination isn't up, or isn't on the VLAN you think it is. If you're deliberately sending at something that won't answer, like a black hole, a tap, or a device in another rack, just give `--dst-mac`.
  </Accordion>

  <Accordion title="The route leaves through a different interface">
    ```text theme={null}
    the route to 192.0.2.10 leaves through eno1, but the run is configured to
    transmit from eno2. Wireblast will not quietly send from a different interface
    than you chose.
    ```

    Either transmit from that interface, or keep the one you picked and give the next-hop MAC on it. Wireblast won't silently switch interfaces on you.
  </Accordion>

  <Accordion title="A CIDR destination spanning many hosts">
    ```text theme={null}
    10.0.0.0/24 is directly connected to eno2, and a run across 254 destinations
    in it would need a different MAC address for each one. Wireblast resolves one
    next hop per run, so it cannot do that automatically.
    ```

    Target a single address, or point the run at a router and give its MAC. This is the common one when you first try a CIDR destination on a local subnet.
  </Accordion>

  <Accordion title="Split routing">
    ```text theme={null}
    the destination 10.0.0.0/8 is not covered by a single next hop: the routing
    table has a more specific route for 10.1.0.0/16, so different destinations in
    the range would leave through different next hops.
    ```

    Narrow the destination range, or pin the MAC.
  </Accordion>
</AccordionGroup>

## IPv6

Everything above works for IPv6. Give `--dst-ip` and `--src-ip` IPv6 addresses and Wireblast builds IPv6 frames; give `--dst-ip` an IPv6 prefix and destinations cycle across it just like a v4 CIDR.

```bash theme={null}
sudo wireblast -i eno2 --dst-ip 2001:db8::/64 --flows 1000 \
  --dst-mac 3c:ec:ef:b4:c2:dc --pps 1M
```

Two differences from IPv4:

* **No addresses are reserved.** IPv6 has no network or broadcast address, so every address in the prefix is used, including the first one.
* **Frames start a little larger.** The IPv6 header is 40 bytes rather than 20, and the UDP checksum is mandatory, so the smallest frame is 66 bytes for UDP and 78 for TCP (four more with a VLAN tag).

Next-hop resolution is the same decision tree, using neighbour discovery (NDP) where the IPv4 path uses ARP. Source and destination must be the same family: a v4 source with a v6 destination is rejected up front. Runnable [IPv6 examples](https://github.com/atoonk/wireblast/tree/main/examples) are in the repo.

## Source addressing

`--src-ip` defaults to an address of the chosen interface. If the interface has several, Wireblast picks one and says which:

```text theme={null}
eno2 has 3 IPv4 addresses; using 192.168.0.2 (override with --src-ip)
```

If it has none, you have to say:

```text theme={null}
eno2 has no IPv4 address to send from; set --src-ip explicitly
```

That's common on a NIC used purely for testing, where the addresses live on a [VLAN sub-interface](/guides/vlan) instead. Nothing stops you sending from an address the host doesn't own, but replies won't come back unless something upstream routes them to you.

`--src-mac` defaults to the interface's own hardware address, and you can override it if you're testing MAC learning or filtering.
