Under attack?
Company - 2009 to today

Seventeen years of writing our own packet filter.

Six times we have thrown away the data plane and built a new one. Not because a vendor released something, but because the traffic outgrew what we had - and each time the answer was code we wrote ourselves rather than an appliance we bought.

2009Generation 1

Hosting game servers, and having to write the protection ourselves

We began as a host for game servers - Counter-Strike and Half-Life among them. They were attacked constantly, and a game server is an unforgiving thing to defend: a few seconds of packet loss ends the round, and the customer does not wait for an explanation before moving somewhere else.

So we went looking for something to put in front of them, and found that nothing available in 2009 actually fit. The appliances of the day were built for web and mail. They thought in connections per second and bytes per second, and neither the traffic of a game server nor an attack on one looks anything like that. What existed was either priced for carriers, or simply blind to what we were being hit with. So we started writing our own.

The first filter we wrote understood exactly one thing: the Source engine query protocol.

That narrowness was the point. A generic firewall of the day counted packets and could tell you a port was busy. It could not tell a real player's join from an A2S query flood, because it had no idea what either looked like. Ours could, because it was written against the protocol rather than against the port number - a principle that survived every rewrite since and is still how the filter decides today.

What forced the next step Protecting one protocol well is a product. Protecting a network means protecting protocols you have not seen yet, and the filter had nowhere to put them.
Protected
Game servers
Data plane
Userspace
Direction
Inbound only
2010Generation 2

A custom Linux kernel, with our own netfilter modules

We stopped configuring someone else's firewall and started writing the firewall. Our own netfilter modules, in a kernel we built ourselves.

Stock iptables could express "drop this port". It could not express the rules we actually needed - rate limits keyed on things the rule language had no word for, state that outlived a single packet, decisions that depended on what the payload said. A module could. From this point on, every rule we have ever shipped has been code, and every code path in the data plane has been ours to read.

It also put us inside the kernel's own accounting. That is where we learned the lesson that drove the next three generations: the expensive part of dropping a packet is not the decision. It is everything the operating system already did before the decision was reached.

What forced the next step Every packet, wanted or not, was allocated a socket buffer before our module ever saw it. At ten gigabits that allocation is the ceiling, and no amount of cleverness in the rule removes it.
Data plane
In-kernel
Rules
Custom C modules
Ceiling
skb allocation
2012Generation 3

Kernel bypass with netmap

Netmap mapped the network card's receive rings straight into our own memory. The operating system stopped touching attack traffic altogether.

This was the first generation that could hold line rate on ordinary hardware, and the first where the cost of a dropped packet was a handful of instructions rather than a kernel allocation. It changed what was worth checking: when dropping is nearly free, you can afford to look at the payload of everything, not just at headers.

It also changed the shape of the product. With volume no longer the binding constraint, the interesting work moved to telling one packet from another - and that is where it has stayed ever since.

What forced the next step Netmap needs a driver that supports it, and the cards that did were not the cards with the queue steering and timestamping we wanted. The software had outrun the hardware underneath it.
Data plane
Kernel bypass
Framework
netmap
Drop cost
~instructions
2014Generation 4

Solarflare cards, and a filter built around the hardware

We moved to Solarflare network adapters and wrote against their kernel-bypass API directly - the first time the card itself became part of the design rather than a component underneath it.

Solarflare was what the trading world used when microseconds were money. The same properties matter under a flood: packets steered to the right core by the silicon, timestamps taken at the wire rather than inferred, a receive path that skips the general-purpose stack. We stopped compensating for the hardware in software.

This is also the generation where a single machine stopped being the unit. Filters began to be deployed in pairs and then in stacks, with traffic divided between them - the beginning of the clustering that every point of presence now uses.

What forced the next step One vendor's API is one vendor's roadmap. Our data plane could only run on cards we could still buy, and that is a decision we had handed to somebody else.
Hardware
Solarflare
Steering
In silicon
Deployment
First clusters
2018Generation 5

DPDK, and a fleet that stopped being identical

Part of the fleet moved to DPDK - vendor-neutral, mature by then, and able to run on whatever card the site happened to have.

Only part, deliberately. Replacing a working data plane everywhere at once is how you find out what it did that you had forgotten, in production, during an attack. So the generations ran side by side for a long time, and the rules had to mean the same thing on both. That is when rule logic became something written once and compiled for whichever data plane was underneath.

DPDK bought portability at a price we felt immediately: it owns the whole network card. Everything the kernel gives you for free - routing, the neighbour table, the tooling every engineer already knows - you now maintain yourself, or do without.

What forced the next step Owning the entire stack to filter packets is a large tax to pay for a small part of the job, and it is paid on every machine, every day, whether or not an attack is happening.
Data plane
DPDK
Hardware
Vendor-neutral
Cost
No kernel
2024Generation 6 - today

XDP and eBPF: line rate, with the kernel still underneath

The current filter runs as an eBPF program attached to the driver's receive path. A packet is classified and dropped before a socket buffer is ever allocated - and everything the kernel does is still there for the traffic we keep.

It is the first generation that did not force a trade. Netmap and DPDK bought speed by leaving the operating system; XDP gets the same drop cost without leaving it. The routing table, the tooling, the neighbour discovery and every ordinary Linux facility still work on the far side of the filter.

What eBPF added on top was not speed but changeability. The filter can be replaced while traffic is flowing, without dropping a packet and without losing connection state. A new attack vector is now a rule written, verified and loaded in the time it takes to read this paragraph - not a firmware release, not a maintenance window, and not a support case with a vendor.

Data plane
XDP / eBPF
Per server
400 Gbps
Measured
1000 Mpps/location
Reload
Zero loss
Alongside the data plane

What changed between the rewrites

The six milestones are the visible ones because they replace everything at once. Most of the work happens between them.

Direction

From one-way to both ways

For most of our history the filter saw only inbound traffic - the customer's return path went another way entirely, so a reply was something we had to infer rather than observe. Bidirectional deployment changed what is knowable: a SYN-ACK can be checked against an outbound SYN we actually recorded, instead of being judged on its own. Both modes are still supported, because not every customer's routing allows the second.

Scale

From one machine to a cluster

A filter used to be a box. It is now a group of machines sharing one traffic load by ECMP, each holding its weighted share of every budget so that the group enforces the figure written down rather than the number of machines racked. Adding a node raises capacity without loosening a single limit.

Footprint

From one site to several

Each new point of presence brings its own cluster rather than back-hauling to a central scrubber, so traffic is cleaned where it arrives. Between sites it rides our own wavelengths, which is why adding a location adds capacity instead of adding latency.

Rules

From thresholds to reasoning

Early rules were limits: this many packets per second, then drop. Today's rules ask which traffic caused the limit to be reached, and remove only that. A destination over budget sheds the upstream and the packet signature responsible instead of shedding everyone - the difference between surviving an attack and being taken down by your own protection.

Check it

How to verify this history

A timeline on a company website proves nothing by itself - anyone can type “since 2009”. Ours happens to be checkable from public records, so check it.

Public record

The domain where this story started

lan-servers.com is our original website - game hosting first, exactly as the 2009 chapter above describes, then dedicated servers as the fleet grew. A domain's registration date cannot be backdated: run the whois yourself, or use ICANN's lookup.

public record · verify it yourself
> whois lan-servers.com
Creation Date: 2009-11-06
# the registry's date, not ours
The one constant

Everything in the path is ours

Six data planes, three kernel-bypass frameworks, one hardware vendor tried and left behind, and not one line of the filtering logic bought from anybody.

That is not a point of pride, it is the reason the response time is what it is. When a vector appears that nothing recognises, the people who can write the rule are the people who wrote the data plane it runs in. The distance between seeing an attack and filtering it is one deploy, not one release cycle.

It is also why the history above reads as six rewrites rather than six purchases. Each one was a decision to keep owning the hard part.