-
Notifications
You must be signed in to change notification settings - Fork 0
[RFC] eBPF offload consideration #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
|
This is going to be almost perfect, thanks! One tiny request: can you please add a short summary of the implementation? The maps we create, when you're do we manage map entries, etc. Also, one note on how everything is handled in the user space TURN server except ChannelData packets and also the reverse path. I'll try to go through the code later but don't wait for me just feel free to file the PR any time you see fit. |
|
Thanks @rg0now ! Made some edit in the OP. WDYT? |
37c028c to
c8ba98d
Compare
examples/turn-server/xdp/Dockerfile
Outdated
| @@ -0,0 +1,50 @@ | |||
| FROM golang:latest | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Stupid question: why aren't we doing multistage? It was easier this way or there's some fundamental reason why this can't be done with ebpf?
internal/allocation/allocation.go
Outdated
| client := offload.NewConnection(a.fiveTuple.SrcAddr.(*net.UDPAddr), a.fiveTuple.DstAddr.(*net.UDPAddr), uint32(c.Number)) | ||
| err := offload.Engine.Upsert(peer, client, []string{}) | ||
| if err != nil { | ||
| offload.Engine.Logger().Errorf("failed to init offload between peer: %+v and client: %+v due to: %s", peer, client, err) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pion log format requires all log messages to start with a capital letter...:-( Steffen always complains about my PRs due to this same reason...:-) also, please avoid #v in log messages: use proper stringification instead and carefully format all log messages
internal/allocation/allocation.go
Outdated
|
|
||
| // disable offload | ||
| if offload.Engine != nil { | ||
| // TODO: use FiveTuple + int channelid |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this comment still meaningful?
internal/allocation/allocation.go
Outdated
| // disable offload | ||
| if offload.Engine != nil { | ||
| // TODO: use FiveTuple + int channelid | ||
| peer := offload.NewConnection(cAddr.(*net.UDPAddr), a.RelayAddr.(*net.UDPAddr), uint32(number)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we make NewConnection take general net.Addrs instead of UDP addresses?
internal/offload/xdp.go
Outdated
| return err | ||
| } | ||
|
|
||
| o.log.Debugf("XDP: remove offload between peer: %+v and client: %+v", p, c) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please, remove %v everywhere, make this into a proper stringified error, it is extremenely important that we can trace this info in our log messages! Also, please report the stats on shutdown (transmitted: x pkgs, y bytes)
internal/offload/xdp.go
Outdated
| return err | ||
| } | ||
|
|
||
| o.log.Debugf("XDP: create offload between peer: %+v and client: %+v", p, c) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove %v and properly report which 5-tuple was shortcut to which 5-tuple for which protocol and channel id.
internal/offload/xdp.go
Outdated
| } | ||
|
|
||
| // GetStat queries statistics about an offloaded connection | ||
| func (o *XdpEngine) GetStat(con Connection) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let us refer to an offload with a handle. I think it is a terrible public API to make the user always mess with 5-tuples to ask for stats: let the Getstat signature be:
func (o *XdpEngine) GetStat(handle int) (Stat, error) {...}
where Stat should be our stats. Do not print stats, return them to the user!
internal/offload/xdp.go
Outdated
| } | ||
|
|
||
| // Remove removes an XDP offload between a peer and a client | ||
| func (o *XdpEngine) Remove(peer, client Connection) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make Remove taking an offload handle instead of 5-tuples
|
I'm not sure about the UDP checksum issue: what's the status now? We should not try to upstream the code until we find out how to generate correct checksums |
d46cb08 to
856dc91
Compare
3d0fc0e to
09b48ae
Compare
4ac0f62 to
5304857
Compare
b59fd8f to
07bfae9
Compare
07bfae9 to
f5c1fbf
Compare
120127b to
4b776f2
Compare
4b776f2 to
c5bdc92
Compare
c5bdc92 to
3c4f115
Compare
Hi,
Me and @rg0now have been investigating on boosting pion/turn performance with eBPF. As a first step, we implemented an eBPF/XDP offload for UDP channel bindings. This way, pion/turn can offload the channel data processing to the kernel. Below we present our implementation details, early results and call for a discussion to consider eBPF offload in pion/turn.
Implementation details
How does it work?
The XDP offload handles ChannelData messages only. The userspace TURN server is responsible for all the other functionality from building channels to handle requests and etc. The offload mechanisms are activated after a successful channel binding, in the method
Allocation.AddChannelBind. The userspace TURN server sends peer and client info (5-tuples and channel id) to the XDP program via an eBPF map. From that point the XDP program can detect channel data coming from the peer or from the client. When a channel binding gets removed the corresponding data will be deleted from the eBPF maps and thus there will be no offload for that channel.Changes to pion/turn
New: We introduce a new internal
offloadpackage, which manages offload mechanisms. Currently, there are two implementations: the XDPOffload that uses XDP, and a NullOffload for testing purposes.Changed: The kernel offload complicates lifecycle management since eBPF/XDP offload outlives TURN server objects. This calls for new public methods in package turn to manage the offload engine's lifetime:
InitOffloadstarts the offload engine (e.g., loads the XDP program and creates eBPF maps) andShutdownOffloadremoves the offload engine. Note that these methods should be called by the application as shown in theserver_test.gobenchmark.But after everything is set up, channel binding offload management happens in
Allocation.AddChannelBindandAllocation.DeleteChannelBindwith no change in their usage.eBPF/XDP details
The XDP part consist of a program that describes the packet processing logic to be executed when the network interface receives a packet. The XDP program uses eBPF maps to communicate with the user space TURN server.
Maps: The XDP offload uses the following maps to keep track of connections, store statistics, and to aid traffic redirects between interfaces:
turn_server_downstream_mapturn_server_upstream_mapturn_server_stats_mapturn_server_interface_ip_addresses_mapXDP Program: The XDP program receives all packets as they arrive to the network interface. It filters IPv4/UDP packets (caveat: VLAN and other tunneling options are not supported), and checks whether the packets belong to any channel binding (i.e., checks the 5-tuple and channel-id). If there is a match, the program does the ChannelData handling: updates 5-tuple, adds or removes the ChannelData header, keeps track of statistics, and finally redirects the packet to the corresponding network interface. Other non channel data packets are passed to the network stack for further processing (e.g., channel refresh messages and other STUN/TURN traffic goes to user space TURN server).
Results
CPU profiling
Prior results are promising. The CPU profiling with the benchmark (pion#298) shows that the
server.ReadLoop()that took 47.9 sec before, runs for 0.96 sec with the XDP offload.Flame graph w/o the offload:

Flame graph w/ XDP offload:

Microbenchmark with simple-server
Measurements with iperf, turncat (our in-house TURN proxy), and the simple-server example show outstanding (150x!) delay reduction and significant (6x) bandwidth boost.
Measurement setup
Delay results
Bandwidth results
Discussion
bpftoolto dump the map content)bpf_redirect()that handles packet redirects in eBPF/XDP supports redirects to NIC egress queues in XDP. This prevents supporting scenarios when clients exchange traffic in a server-local 'loop'.lointerface). We disabled the XDP offload for host-local redirects.