A transparent proxy, also known as an inline proxy, intercepts client requests and redirects them without modifying the request or requiring client-side configuration. It operates invisibly to users, meaning they are unaware of its presence and do not need to adjust their network settings.
Transparent proxies can execute a range of functions, including content filtering, cache acceleration, traffic control, and load balancing. Commonly used technologies for implementing transparent proxies include TPROXY, NAT, and others.
In this newsletter, I’ll walk you through the implementation of Transparent proxies with eBPF. Specifically, we’ll utilize Golang alongside the ebpf-go package.
eBPF-accelerated Transparent Proxy
The implementation of a transparent proxy using eBPF involves three distinct eBPF programs, each responsible for different aspects of network interception and forwarding:
Address Replacement at Connection Establishment: The first eBPF program,
cgroup/connect4
, attaches to theconnect
system call. When a client attempts to connect to a target server, this program intercepts the connection attempt, replacing the target's IP address and port with those of the local transparent proxy — this redirection is completely transparent to the client. Simultaneously, the original target address and port are stored within amap_socks
eBPF map, enabling thecgroup/getsockopt
eBPF programs to reference this information later.Source Address Recording Post-Connection: The second eBPF program,
sockops
, executes once a connection between the proxy and the proxy is successfully established. Its primary function is to record the source address and port of the connection. This information is updated in the corresponding entry inmap_socks
eBPF map. Additionally, the source port and socket's cookie (unique identifier) are mapped inmap_ports
eBPF map ensuring that all necessary connection details are available to other eBPF program. This step is crucial for maintaining a stateful understanding of network connections.Forwarding Based on Original Destination Information: The third eBPF program,
cgroup/getsockopt
, is triggered when the proxy queries for the original destination information using thegetsockopt
call. This program retrieves the original socket's cookie frommap_ports
using the source port and then accesses the original destination information stored inmap_socks
. With this information, it establishes a connection with the original target server and forwards the client's request. This ensures that the traffic is transparently redirected to its intended destination after being processed by the proxy.
The following image depicts the entire setup, with each color representing a different stage. They execute in the following order:
Red -> Green -> Purple -> Pink
All three programs are linked to a specific cgroup. This ensures they’re only activated when processes within this group execute the designated system calls.
In theory, that’s all. Let’s see some code and tests.
Code Example
I find code example renders in Substack tedious, so I’ll refer to my GitHub repository with the code and test results.
Here’s the link.
💡 Hint: I’ve added some useful code comments for you to check — as always :)
Performance Evaluation
To conclude, I’ve conducted basic performance tests to evaluate the impact of our eBPF programs on the host server, specifically focusing on latency and CPU load when intercepting the traffic. The tests involved measuring the average latency over 10,000 requests.
Our results indicate that the eBPF programs add a constant eBPF overhead of approximately 1ms on average. Additionally, the average CPU load introduced by each hook is as follows: 0.4% for sockops
, 0.1% for cgroup/connect4
, and 0.09% for cgroup/getsockopt
. Basically, nothing.
These findings address the trade-off between the added latency and CPU load due to eBPF programs and the benefits of traffic interception.
⚠️ Note: This implementation was inspired by Pipy, extending its functionality by adding a user-space implementation using ebpf-go. Additionally, it introduces minor modifications to the kernel-space code, along with multiple added comments for clarity.
I hope you find this resource as enlightening as I did. Stay tuned for more exciting developments and updates in the world of eBPF in next week's newsletter.
Until then, keep 🐝-ing!
Warm regards, Teodor