A lot of how I design software today comes from the five years I spent at Ericsson working on packet classification for SE edge routers and SSR Smart Services Routers. I did not fully connect that work to C-Kernel-Engine (CKE) when I started CKE. But as its DSL, circuits, kernel maps, providers, and generated runtimes have matured, the influence has become quite obvious to me. If CKE is new to you, my earlier article What Is The C-Kernel-Engine? explains why I started it and how the pieces fit together.
At Ericsson I mostly specialized in ACLs, or access control lists. The work went much deeper than adding allow-and-deny rules. I worked on the CLI where an operator entered classification commands, the classification daemon that converted those commands into lookup tables, and the forwarding path where live packets were classified on ingress and egress. The same classification feature had to work across software algorithms, TCAM, different network processors and forwarding cards, and eventually an Intel DPDK virtual-router path.
That experience shaped how I think software should work. A user should be able to describe what they want at a useful level. The system should understand that intent, validate it, and transform it into something the available hardware can execute. The fast path should then do the work it was prepared to do. I have carried some version of that thinking into Antsand for more than a decade, and now much more directly into CKE.
Watch the companion presentation
This is a fairly dry systems-architecture subject. The presentation is mainly for readers who want to understand why CKE is structured around circuits, lowering, providers, generated runtimes, and evidence. It follows the packet-classification work that shaped how I think about those boundaries.
What I Worked On At Ericsson
I joined Ericsson Forwarding Services as a co-op developer in April 2012 and continued as a software developer until April 2017. I worked on Ericsson's SE edge routers and SSR Smart Services Routers. These were large carrier systems used by operators to provide fixed and mobile network services.
My main area was packet classification. At the top of the system, the operator needed a way to express classification rules through the command-line interface. I worked on the software that accepted those commands and passed the resulting messages to the classification daemon running on the routing plane.
I also worked on the classification daemon itself. It received the configuration and built the lookup tables. We supported different types of NPUs and forwarding planes, including EZchip, Ericsson PPA and Spider cards, software lookup, hardware lookup, and a DPDK-based virtual-router path.
For software lookup, I worked with linear lookup and MRFC, or Modified Recursive Flow Classification. Hardware lookup could use TCAM. Each implementation had different capabilities and constraints, but the operator should not have needed a completely different product model for every line card.
The broader algorithmic line came from Pankaj Gupta and Nick McKeown's Recursive Flow Classification work. RFC takes a multi-field packet key and reduces it through several lookup stages until it reaches the matching equivalence class. The attraction is speed: the expensive structure is prepared before packets enter the lookup path. The trade-off is that preprocessing and memory use can grow substantially with the rule set. Gupta's thesis chapter on packet classification gives the fuller background. The MRFC implementation I worked with belonged to this practical family of recursive classification approaches.
Later work such as ERFC, or Enhanced Recursive Flow Classification, focused directly on those costs. ERFC used a hash-based aggregated bit-vector scheme during preprocessing and a compressed, cacheable data structure to reduce memory use and improve lookup performance. Dip Singh's packet-classification overview is also a useful visual introduction to RFC, HiCuts, HyperCuts, and the wider set of trade-offs.
I also worked on forwarding-plane software. This is where the lookup was installed into the actual path of a packet. Classification could happen on ingress or egress. The packet could be inspected using L2, L3, and L4 header fields, along with other context available to the system. Based on the rule, the result could be used for filtering, QoS, forwarding, NAT, or reverse-path forwarding. We supported subscriber traffic, where state could be created dynamically, and direct traffic configured more explicitly.
This work involved C, Python, assembly, shell scripting, Linux kernel and device-driver work, user-space programming, Git, Gerrit, and debugging across a fairly large distributed system. I learned networking through a real product where a configuration entered in one place eventually had to change how packets moved through another processor and another card.
What I Learned At Two Different Layers
There were really two layers to what I learned at Ericsson. At the lower layer, I learned how Linux systems software works when it has to keep running as part of a large product. I fixed bugs in classification algorithms, timing problems, Linux IPC paths, and race conditions between processes and threads. I had to understand where state lived, which thread owned it, when a message could arrive, and what happened when two parts of the system changed at almost the same time.
That was a very good place to learn C and Linux programming. The operating system was not hidden behind a large application framework. Threads, processes, sockets, IPC, memory, timers, device interfaces, and networking were all part of the software design. A bug could sit in an algorithm, in a message boundary, in an ordering assumption, or in the way state was updated across processors.
The higher layer was learning how all of those pieces could still behave as one router. A large routing system can have many registered services. A daemon should not need to hard-code and maintain a direct interface to every other daemon it may communicate with. The system needs a way to register services, look them up, route messages, preserve message contracts, and isolate the implementation behind the service boundary.
Some of these ideas will look familiar to anyone who has used ZeroMQ: named or discoverable endpoints, asynchronous messages, queues, request/reply patterns, and producers that do not need to understand the receiver's internal implementation. We did not use ZeroMQ for this Ericsson work. The router had its own infrastructure, but many of the messaging ideas were similar because large communicating systems eventually face similar problems.
The forwarding plane needed the same kind of abstraction. Routing-plane services should be able to express the state they need installed without every daemon implementing a private integration for every NPU, line card, or software forwarding target. A forwarding abstraction receives the request and takes care of the target-specific path. That is how a system with many services and several generations of hardware can continue to evolve without every new card forcing a rewrite of the entire control plane.
I can see both layers in my own projects. Antsand uses modules and shared services so websites, blogs, forms, research logging, and business workflows can evolve without becoming one application full of direct dependencies. CKE uses circuits, kernel maps, providers, generated code, and evidence so a model can move across CPU implementations without putting every hardware detail into the model definition.
This is also why most of my systems remain focused on Linux and C. I still think Linux is the best environment for understanding and building this type of software. It exposes the system rather than trying to hide all of it, and the knowledge transfers from a small process to a router, an AI runtime, or eventually a robotics stack. My article on Linux system programming for AI kernels goes further into how those operating-system details now appear in CKE. AI makes it much easier for me to write, inspect, test, and iterate on more of the detailed implementation. The architecture still decides whether all of that generated or assisted code can work cohesively over time.
How I Think About Edge And Backhaul
The edge is where subscriber, enterprise, access, or service traffic enters a provider's routed infrastructure. This is often where the network starts applying identity, policy, QoS, filtering, NAT, and service selection. Ericsson historically described SmartEdge as a multi-service edge router for full-service broadband, which gives a reasonable picture of the systems I was working on.
Backhaul is more about carrying aggregated traffic from access networks or cell sites toward the provider core and services. In a real network these roles can overlap. A router can perform aggregation, routing, service-edge, and transport functions at the same time. I find the distinction useful because the edge is often deciding how a subscriber or flow should be treated, while backhaul is moving increasingly aggregated traffic through the topology.
Say a customer pays for a 3 Gbps internet service. That commercial agreement eventually has to become configuration in the network. The system needs to identify the subscriber or traffic using the context available to it, then associate that traffic with the correct QoS and forwarding treatment. The exact key can involve IP fields, protocol, ports, tunnel or subscriber context, and other metadata.
The router cannot return to the billing system and ask what to do for every packet. The slower systems establish the service and policy. The routing plane turns that into state for the available forwarding target. The packet path then performs the lookup using the state that has already been prepared.
The Three Parts Of The Router
I generally think of these systems as configuration, control, and forwarding.
The configuration interface is how the operator expresses intent. The control or routing plane owns the larger system state and works out how to represent that intent. The forwarding plane is where packets actually move. A large chassis may have a route processor and several line cards, with their own CPUs, NPUs, memories, and hardware lookup resources. The control plane has to keep track of which state belongs on which forwarding target.
The operator did not program TCAM rows manually. They entered a supported classification rule. The software validated it, combined it with the existing configuration, created the representation required by the target, and installed that representation into the forwarding path.
I now look at much of this as compiler work. There is a useful declaration at the top. There are internal representations and capability checks in the middle. There are different hardware-specific representations at the bottom. Then there is a runtime path that uses the result.
We did not need to call every part of it a compiler for that way of thinking to shape me.
From An Operator Command To A Packet Action
The flow was more or less this:
- The operator entered a classification command through the CLI.
- The interface parsed and validated the command and sent the configuration to the classification daemon.
- The daemon understood the rule in the context of priorities, existing rules, supported fields, and target capabilities.
- It built or updated the lookup representation required by that forwarding implementation.
- The state was installed on the relevant line card or software forwarding path.
- Packets were classified on ingress or egress using their headers and available context.
- The result selected filtering, QoS, forwarding, NAT, RPF, or another classification action.
Classification was therefore a reusable part of several services. Filtering could allow or deny a packet. QoS classification could assign a class used by scheduling or policing. Forwarding, NAT, and RPF consumed classification results in their own ways. The lookup was one part of a larger packet-processing pipeline.
The Same Feature Had To Work On Different Hardware
This was probably the part that influenced CKE the most. The product could not be designed around only one lookup engine.
A linear lookup was straightforward and useful for some workloads. MRFC organized the classification differently. TCAM provided masked parallel matching in hardware. EZchip and Ericsson forwarding cards had their own programming paths. DPDK made a general-purpose Intel CPU a serious packet-processing target.
I only worked a little on DPDK before I left that space, mostly around enabling ACLs in a software-based path, so I do not want to exaggerate that part of my experience. But I liked its direction. DPDK's ACL library lets software add rules to a context, build the runtime structures, and then classify packet tuples against that prepared context. The current documented implementation builds one or more 8-bit-stride multi-bit tries. Its scalar and SIMD classifiers traverse the same prepared structures, with SSE, AVX2, AVX-512, NEON, and other implementations processing different numbers of flows in parallel. This is more precise than calling the ACL structure a radix tree, although radix and longest-prefix-match structures also appear elsewhere in software routing. DPDK's packet framework describes ports, tables, and actions as pieces of a pipeline.
The broader idea stayed with me: commodity CPUs can do specialized work when the software is designed around how the processor and memory actually behave. That is now a major part of what I am exploring with CKE.
How This Shaped Antsand
I started building Antsand around 2012, at roughly the same time I started working at Ericsson. Antsand has changed many times since then, but it still powers my websites, ShivasNotes, content workflows, forms, sales tooling, and an increasing amount of my internal business software.
The scale is obviously different from a carrier router, but I can see the same preference in how I structure it. A module should own its responsibility. Data should have a clear model. A website should be able to connect to content and render it without owning the content system itself. A blog, form, or research log should become reusable infrastructure rather than another one-off page.
This is also why I keep using my own software. Antsand became useful by handling my real work for years. Each practical workflow exposed what the platform was missing. CKE is starting to go through the same process now. Whisper transcription became useful in my video workflow. Model bring-up is becoming repeatable. The next stages will connect more of this CPU work to Antsand, research logging, and Antshiv Robotics.
How The Same Thinking Appears In CKE
In CKE, a model template describes the repeated architecture and a circuit describes how the operations connect. The kernel map tells CKE which implementations can satisfy those operations. The DSL reads the model and weights, stitches the circuit, plans memory, resolves the available CPU providers, and generates native C. The official CKE concepts guide explains this vocabulary, while my kernel-registry deep dive shows how an operation reaches an exact C function and numerical contract.
This feels familiar to me. The model or circuit is similar to the operator expressing what needs to exist. The DSL and lowering system are similar to the control software working out how that declaration can run. The kernel map and provider selection are similar to resolving the work for a particular lookup engine or line card. The generated runtime is the installed execution path.
| Packet classification | CKE |
|---|---|
| Operator CLI and policy | Model metadata, template, and circuit |
| Classification daemon | DSL, lowering, and memory planning |
| Lookup builder and target adapter | Kernel map and provider resolver |
| Linear, MRFC, TCAM, NPU, or DPDK | Reference C, AVX2, AVX-512, AMX, and other providers |
| Installed forwarding state | Generated C and planned runtime buffers |
| Packet lookup and action | Tensor operation and model-state update |
| Counters and diagnostics | Kernel tests, X-Ray traces, parity, and nightly evidence |
There are practical differences. AI models carry large tensor state, attention caches, recurrent state, quantized weights, and numerical error through many layers. A packet lookup normally selects an action from prepared policy. CKE performs a long mathematical program. A small numerical mistake can remain hidden for several operations and then produce completely incoherent output.
This is why CKE needs the circuit to be explicit. If a kernel is missing, I want the DSL to stop and say which contract it cannot satisfy. If AVX2 and AVX-512 implementations exist, they should both satisfy the same mathematical interface. If the generated output differs from llama.cpp or PyTorch, CKE X-Ray should help us locate the first layer or operation where it diverged.
The goal is that a new model family mostly adds its model template, circuit, tensor mapping, and any genuinely new kernels. The DSL should already understand how to plan and generate the program. CKE is not completely there yet, but the delta has been getting smaller as more text, vision, audio, dense, MoE, and recurrent families pass through the architecture.
Why I Now See The Connection
I did not consciously decide to build an AI runtime like a router. I started CKE because I wanted to understand the kernels inside modern models and make them run directly on CPUs. As the project grew, I naturally separated model intent, compilation, providers, generated code, and evidence in a way that made sense to me.
Years of packet-classification work are probably why that structure felt normal. At Ericsson, one logical feature crossed a CLI, routing-plane daemon, table builders, software algorithms, hardware lookup, line cards, and the live packet path. With CKE, one model crosses metadata, a template, a circuit, kernel maps, provider selection, generated C, and numerical verification.
I also think this explains why I am comfortable with CKE being unusual. I have already seen a large system preserve one intent across very different hardware. The architecture matters more to me than making every model work through another family-specific branch as quickly as possible. Each new model should improve the reusable kernel and circuit portfolio.
Longer term, I want the same pieces to support training. I want to be able to stitch kernels into a circuit, inspect one layer, train it, extend it, and eventually run larger experiments on owned CPU compute. Inference is the first direction through the circuit. Backpropagation and optimization are the harder direction back through it.
The companion presentation walks through this networking influence visually. The CKE concepts guide explains the kernel, circuit, provider, and generated-runtime side in more detail.
Continue Reading
- My engineering background and Ericsson role
- Video: how Ericsson edge-router architecture shaped CKE
- What Is The C-Kernel-Engine?
- CKE concepts: kernels, circuits, providers, and generated runtimes
- C-Kernel-Engine on GitHub
- Join the CKE engineering Discord
- How the CKE kernel registry connects operations to exact C functions
- How CKE X-Ray found Qwen3.6's first bad circuit
- What Is Antsand?
- Linux system programming for AI kernels
- DPDK packet classification and ACL library
- DPDK packet framework
- Gupta and McKeown: Packet Classification on Multiple Fields
- Pankaj Gupta's thesis chapter on packet classification
- ERFC: An Enhanced Recursive Flow Classification Algorithm
- Packet Classification for High-Speed Packet Processing
- Ericsson's historical SmartEdge overview