As mentioned above, our current implementation of norm runs at user level, but we are primarily interested in assessing how well it might run as a streamlined kernel implementation, since it is reasonable to expect that a production normalizer will merit a highly optimized implementation.
To address this, norm incorporates a test mode whereby it reads an entire libpcap trace file into memory and in addition allocates sufficient memory to store all the resulting normalized packets. It then times how long it takes to run, reading packets from one pool of memory, normalizing them, and storing the results in the second memory pool. After measuring the performance, norm writes the second memory pool out to a libpcap trace file, so we can ensure that the test did in fact measure the normalizations we intended.
These measurements thus factor out the cost of getting packets to the normalizer and sending them out once the normalizer is done with them. For a user-level implementation, this cost is high, as it involves copying the entire packet stream up from kernel space to user space and then back down again; for a kernel implementation, it should be low (and we give evidence below that it is).
For baseline testing, we use three tracefiles:
We performed all of our measurements on an x86 PC running FreeBSD 4.2, with a 1.1GHz AMD Athlon Thunderbird processor and 133MHz SDRAM. In a bare-bones configuration suitable for a normalizer box, such a machine costs under US$1,000.
For an initial baseline comparison, we examine how fast norm can take packets from one memory pool and copy them to the other, without examining the packets at all:
Memory-to-memory copy only | ||
Trace | pkts/sec | bit rate |
T1,U1 | 727,270 | 2856 Mb/s |
U2 | 1,015,600 | 747 Mb/s |
Enabling all the checks that norm can perform for both inbound and outbound traffic6 examines the cost of performing the tests for the checks, even though most of them entail no actual packet transformation, since (as in normal operation) most fields do not require normalization:
All checks enabled | ||
Trace | pkts/sec | bit rate |
T1 | 101,000 | 397 Mb/s |
U1 | 378,000 | 1484 Mb/s |
U2 | 626,400 | 461 Mb/s |
Number of Normalizations | |||||
Trace | IP | TCP | UDP | ICMP | Total |
T1 | 111,551 | 757 | 0 | 0 | 112,308 |
Comparing against the baseline tests, we see that IP normalization is about half the speed of simply copying the packets. The large number of IP normalizations consist mostly of simple actions such as TTL restoration, and clearing the DF and Diffserv fields. We also see that TCP normalization, despite holding state, is not vastly more expensive, such that TCP/IP normalization is roughly one quarter of the speed of UDP/IP normalization.
These results do not, of course, mean that a kernel implementation forwarding between interfaces will achieve these speeds. However, the Linux implementation of the click modular router [7] can forward 333,000 small packets/sec on a 700MHz Pentium-III. The results above indicate that normalization is cheap enough that a normalizer implemented as (say) a click module should be able to forward normal traffic at line-speed on a bi-directional 100Mb/s link.
Furthermore, if the normalizer's incoming link is attacked by flooding with small packets, we should still have enough performance to sustain the outgoing link at full capacity. Thus we conclude that deployment of the normalizer would not worsen any denial-of-service attack based on link flooding.
A more stressful attack would be to flood the normalizer with small fragmented packets, especially if the attacker generates out-of-order fragments and intersperses many fragmented packets. Whilst a normalizer under attack can perform triage, preferentially dropping fragmented packets, we prefer to only do this as a last resort.
To test this attack, we took the T1 trace and fragmented every packet with an IP payload larger than 16 bytes: trace T1-frag comprises some 3 million IP fragments with a mean size of 35.7 bytes. Randomizing the order of the fragment stream over increasing intervals demonstrates the additional work the normalizer must perform. For example, with minimal re-ordering the normalizer can reassemble fragments at a rate of about 90Mb/s. However, if we randomize the order of fragments by up to 2,000 packets, then the number of packets simultaneously in the fragmentation cache grows to 335 and the data rate we can handle halves.
rnd | input | frag'ed | output | output | pkts in |
intv'l | frags/s | bit rate | pkts/sec | bit rate | cache |
100 | 299,670 | 86Mb/s | 9,989 | 39Mb/s | 70 |
500 | 245,640 | 70Mb/s | 8,188 | 32Mb/s | 133 |
1,000 | 202,200 | 58Mb/s | 6,740 | 26Mb/s | 211 |
2,000 | 144,870 | 41Mb/s | 4,829 | 19Mb/s | 335 |
It is clear that in the worst case, norm does need to perform triage, but that it can delay doing so until a large fraction of the packets are very badly fragmented, which is unlikely except when under attack.
The other attack that slows the normalizer noticeably is when norm has to cope with inconsistent TCP retransmissions. If we duplicate every TCP packet in T1, then this stresses the consistency mechanism:
All checks enabled | ||
Trace | pkts/sec | bit rate |
T1 | 101,000 | 397 Mb/s |
T1-dup | 60,220 | 236 Mb/s |
To conclude, a software implementation of a traffic normalizer appears to be capable of applying a large number of normalizations at line speed in a bi-directional 100Mb/s environment using commodity PC hardware. Such a normalizer is robust to denial-of-service attacks, although in the specific case of fragment reassembly, very severe attacks may require the normalizer to perform triage on the attack traffic.