-
Notifications
You must be signed in to change notification settings - Fork 0
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
[RFC] eBPF offload consideration #2
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.Addr
s 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
offload
package, 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:
InitOffload
starts the offload engine (e.g., loads the XDP program and creates eBPF maps) andShutdownOffload
removes the offload engine. Note that these methods should be called by the application as shown in theserver_test.go
benchmark.But after everything is set up, channel binding offload management happens in
Allocation.AddChannelBind
andAllocation.DeleteChannelBind
with 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_map
turn_server_upstream_map
turn_server_stats_map
turn_server_interface_ip_addresses_map
XDP 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
bpftool
to 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'.lo
interface). We disabled the XDP offload for host-local redirects.