Our last post showed you basics of NAT/PAT and how to verify it using a single traffic flow in Ostinato. If you are new to NAT, you will find it helpful to read that first.
As in the previous post, this post will also refer to PAT and NAPT generically as NAT.
Generating NAT flows
To verify NAT scale, we need to generate a large number of unique flows e.g. if you want to verify a NAT scale of 64000, you need to generate 64000 outbound and 64000 inbound flows for a total of 128K flows.
Ostinato makes it easy to generate multiple flows with the following features -
- Vary IP address
- Vary any packet field using variable fields
Vary IP address
When specifying the source and destination IP address in the Ostinato stream, you can configure it to be incremented with each packet. We can use this to increment the source IP address to simulate multiple internal devices for outgoing flows. Similarly for incoming flows we can increment the destination IP address.
You can vary IPv6 addresses also similarly.
You can vary either source or destination IP address or both.
Variable fields
The TCP/UDP stream configuration in Ostinato allows us to set a fixed value for the TCP/UDP ports, but does NOT allow us to vary them.
However, the variable fields configuration on the stream can vary ANY packet field in the packet. We can use this to vary the source L4 port for outbound flows and destination L4 port for inbound flows.
🔥 Tip
As you might remember, NAT also uses the IP protocol field as a key to identify unique flows, so another option is to vary the IP protocol fields. However, this is not recommended since some NAT boxes may not recognize and drop uncommon protocols.
It is best to stick to common protocols such as TCP, UDP and ICMP.
Better Together
We can combine both features like this example -
- Src IP: 192.168.1.1/16, count: 1000
- Dst IP: 10.0.0.1
- Src UDP Port: 5000, count:1000
- Dst UDP Port: 8080
This would generate the following 1000 flows with a single Ostinato stream -
(192.168.1.1:5000) ==> (10.0.0.1:8080)
(192.168.1.2:5001) ==> (10.0.0.1:8080)
(192.168.1.3:5002) ==> (10.0.0.1:8080)
...
(192.168.4.232:5998) ==> (10.0.0.1:8080)
(192.168.4.233:5999) ==> (10.0.0.1:8080)
If you look at the flows closely, you will see that both the source IP address and the source port increment together. This is because, by default, each variable field is incremented with each packet.
To scale much higher, we want to increment 1000 ports for each IP address, something like this -
(192.168.1.1:5000) ==> (10.0.0.1:8080)
(192.168.1.1:5001) ==> (10.0.0.1:8080)
...
(192.168.1.1:5999) ==> (10.0.0.1:8080)
(192.168.1.2:5000) ==> (10.0.0.1:8080)
(192.168.1.2:5001) ==> (10.0.0.1:8080)
...
(192.168.1.2:5999) ==> (10.0.0.1:8080)
(192.168.1.3:5000) ==> (10.0.0.1:8080)
...
(192.168.4.232:5998) ==> (10.0.0.1:8080)
(192.168.4.233:5999) ==> (10.0.0.1:8080)
This would yield us 1000*1000 = 1 million flows with a single Ostinato stream!
So, how can we achieve this?
Vary with a skip
By using the variable field’s skip parameter!
We vary both the source IP and source L4 port as earlier, but use skip with one of them (but not both).
Option 1: Vary source IP with a skip
First, we change our stream to NOT vary the source IP address using the IP protocol configuration and so change the mode to Fixed (since skip is available only with variable fields).
To use skip, we use variable fields to vary the source IP address and configure the skip parameter to 1000
.
Unfortunately, the variable fields UI (until v1.3.0) only allows decimal values, so we need to convert the IP address to a decimal value - IP 192.168.1.1
= Hex 0xc0a80101
= Decimal 3232235777
. If this seems clumsy, you can go for option 2 below.
After making these changes, we will get 1 million unique flows as follows -
(192.168.1.1:5000) ==> (10.0.0.1:8080)
(192.168.1.1:5001) ==> (10.0.0.1:8080)
...
(192.168.1.1:5999) ==> (10.0.0.1:8080)
(192.168.1.2:5000) ==> (10.0.0.1:8080)
(192.168.1.2:5001) ==> (10.0.0.1:8080)
...
(192.168.1.2:5999) ==> (10.0.0.1:8080)
(192.168.1.3:5000) ==> (10.0.0.1:8080)
...
(192.168.4.232:5998) ==> (10.0.0.1:8080)
(192.168.4.233:5999) ==> (10.0.0.1:8080)
Option 2: Vary source L4 port with a skip
We vary the source IP using the IP protocol configuration (not using variable fields).
And vary the source L4 port using variable fields with a skip value of 1000.
With the above configuration, it is the exact same set of 1 million flows as shown above in option 1, just in a different order -
(192.168.1.1:5000) ==> (10.0.0.1:8080)
(192.168.1.2:5000) ==> (10.0.0.1:8080)
...
(192.168.4.233:5000) ==> (10.0.0.1:8080)
(192.168.1.1:5001) ==> (10.0.0.1:8080)
(192.168.1.2:5001) ==> (10.0.0.1:8080)
...
...
(192.168.4.232:5999) ==> (10.0.0.1:8080)
(192.168.4.233:5999) ==> (10.0.0.1:8080)
Note, the skip parameter is available Ostinato v1.4.0 onwards. If you are using an older version, don’t worry - we have a workaround, keep reading.
Vary with a mask and step
If your Ostinato version doesn’t support the skip parameter, we can use the variable field’s mask and step parameters to simulate a skip - with a limitation.
This may be slightly mind-bending, so before you proceed, you should review how the variable field mask works in general and also for bit fields (i.e. non 8/16/32-bit fields).
The limitation in this approach is that the skip parameter value MUST be a power of 2. So, for our example in this post, instead of a skip value of 1000, we need to use a value of 1024 (2^10).
So, for 1 million flows with a (simulated) skip value of 1024 for the UDP source port, we need to vary the source IP for a count of 1,000,000/1024 = ~977
(instead of 1000).
We will configure the source IP to vary using the Ostinato IP protocol configuration -
To vary the UDP source port, we will use a custom variable field and overlay it on our UDP header and set up the mask and step parameters as shown below -
Offset 0 2 4
+----+----+----+----+----+---
UDP Hdr | ss | dd | ...
+----+----+----+----+----+---
VarField | vf |
+----+----+----+----+
Mask | FF | FF | 00 | 00 |
+----+----+----+----+
| 00 | 00 | 00 | 01 | <== increments every 1 packet
Step +----+----+----+----+
| 00 | 01 | <== increments every 65536 (0x10000) packets
+----+----+
ss
is the 16-bit source port and dd
is the 16-bit destination port in the UDP header. We use a 32-bit variable field vf
that overlays both the ports.
We set up the mask value corresponding to the 16-bit destination port to 0x0000
so that it is never modified.
The 32-bit variable field (vf) increments at every packet but will be effective only once every 65536 (0x10000) iterations when the bits in the source port portion of our variable field changes - which effectively is what we want!
However, we want source port to change every 1024 iterations, not 65536 - to achieve that we set our step parameter to 65536/1024 = 64
.
The initial value for the 32-bit variable field should be 5000 (0x1388)
in the upper 16 bits (corresponding to source port) and 0 in the lower 16 bits (corresponding to destination port) - 0x13880000
i.e. decimal 327680000
Finally, the count - 977*1024 = 1000448
.
To summarize, here are all the variable field parameters to vary the source UDP port from 5000 to 6023 incrementing by 1 every 1024 packets -
- Field - Custom
- Type - Counter32
- Offset - 0
- Mask - 0xFFFF0000
- Mode - Increment
- Value - 327680000
- Count - 1000448
- Step - 64
This configuration will generate the following 1,000,448 (977*1024)
flows -
(192.168.1.1:5000) ==> (10.0.0.1:8080)
(192.168.1.2:5000) ==> (10.0.0.1:8080)
...
(192.168.4.210:5000) ==> (10.0.0.1:8080)
(192.168.1.1:5001) ==> (10.0.0.1:8080)
(192.168.1.2:5001) ==> (10.0.0.1:8080)
...
...
(192.168.4.210:6023) ==> (10.0.0.1:8080)
(192.168.4.210:6023) ==> (10.0.0.1:8080)
Confusing calculation?
We’ve got you! Here’s a handy little calculator to make it easy to compute the right variable field values for this option -
Flow Calculator
A final note
Ostinato prebuilds all its traffic flows and packets - so if you configure it to generate ~1 million unique flows as shown in this post, it will take a few minutes to build those first before sending them out!