Skip to content

Commit

Permalink
Implement eBPF/XDP offload for UDP channel data
Browse files Browse the repository at this point in the history
Co-authored-by: Gabor Retvari <retvari@tmit.bme.hu>
  • Loading branch information
levaitamas and rg0now committed Nov 16, 2023
1 parent 1f1673d commit 09b48ae
Show file tree
Hide file tree
Showing 26 changed files with 2,336 additions and 3 deletions.
35 changes: 35 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
# SPDX-License-Identifier: MIT

GO=go
CLANG_FORMAT=clang-format

default: build

build: generate

fetch-libbpf-headers:
@if ! find internal/offload/xdp/headers/bpf_* >/dev/null 2>&1; then\
cd internal/offload/xdp/headers && \
./fetch-libbpf-headers.sh;\
fi

generate: fetch-libbpf-headers
cd internal/offload/xdp/ && \
$(GO) generate

format-offload:
$(CLANG_FORMAT) -i --style=file internal/offload/xdp/xdp.c

clean-offload:
rm -vf internal/offload/xdp/bpf_bpfe*.o
rm -vf internal/offload/xdp/bpf_bpfe*.go

purge-offload: clean-offload
rm -vf internal/offload/xdp/headers/bpf_*

test:
go test -v

bench: build
go test -bench=.
1 change: 1 addition & 0 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,5 @@ var (
errNonSTUNMessage = errors.New("non-STUN message from STUN server")
errFailedToDecodeSTUN = errors.New("failed to decode STUN message")
errUnexpectedSTUNRequestMessage = errors.New("unexpected STUN request message")
errUnsupportedOffloadMechanism = errors.New("unsupported offload mechanism")
)
79 changes: 79 additions & 0 deletions examples/turn-server/xdp/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
# SPDX-License-Identifier: MIT

##### builder
FROM golang:alpine as builder

ARG VERSION=master

RUN apk update && \
apk upgrade && \
apk add --no-cache \
clang \
llvm \
linux-headers \
bsd-compat-headers \
musl-dev \
make \
git \
bash \
curl \
tar


WORKDIR /build
# Clone Source using GIT
#RUN git clone --branch=$VERSION --depth=1 https://github.com/pion/turn.git turn && rm -rf turn/.git
RUN git clone --branch=server-ebpf-offload --depth=1 https://github.com/l7mp/turn.git turn && rm -rf turn/.git

WORKDIR /build/turn

#RUN rm internal/offload/xdp/*.o
RUN make

WORKDIR /build/turn/examples/turn-server/xdp

# Download all the dependencies
# RUN go get -d -v ./...



# Build static binary
RUN CGO_ENABLED=0 go build -trimpath -ldflags="-w -s" -o turn-server main.go

##### main
FROM alpine

ARG BUILD_DATE
ARG VCS_REF
ARG VERSION=master

LABEL org.label-schema.build-date="${BUILD_DATE}" \
org.label-schema.name="pion-turn" \
org.label-schema.description="A toolkit for building TURN clients and servers in Go" \
org.label-schema.usage="https://github.com/pion/turn#readme" \
org.label-schema.vcs-ref="${VCS_REF}" \
org.label-schema.vcs-url="https://github.com/pion/turn" \
org.label-schema.vendor="Sean-Der" \
org.label-schema.version="${VERSION}" \
maintainer="https://github.com/pion"

ENV REALM localhost
ENV USERS username=password
ENV UDP_PORT 3478
ENV PUBLIC_IP 127.0.0.1

EXPOSE 3478
#EXPOSE 49152:65535/tcp
#EXPOSE 49152:65535/udp

USER nobody

# Copy the executable
COPY --from=builder /build/turn/examples/turn-server/xdp/turn-server /usr/bin/

# Run the executable
CMD turn-server -public-ip $PUBLIC_IP -users $USERS -realm $REALM -port $UDP_PORT

# docker build -t pion-turn -f Dockerfile .
# docker run --rm --cap-add=NET_ADMIN --cap-add=SYS_ADMIN --cap-add=BPF --privileged -e REALM="localhost" -e USERS="username=password" -e UDP_PORT="3478" -e PUBLIC_IP="127.0.0.1" -p 3478:3478 pion-turn
28 changes: 28 additions & 0 deletions examples/turn-server/xdp/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
# SPDX-License-Identifier: MIT

version: "3.1"

services:
pion-turn:
container_name: "pion-turn"
image: pion-turn:${VERSION:-latest}
build:
context: ./
stdin_open: true
environment:
- VERSION=${PION_TURN_VERSION:-master}
- REALM=${PION_TURN_REALM:-localhost}
- USERS=${PION_TURN_USERS:-username=password}
- PUBLIC_IP=${PION_TURN_PUBLIC_IP:-127.0.0.1}
- UDP_PORT=${PION_TURN_UDP_PORT:-3478}
network_mode: host
ports:
# STUN
- "${PION_TURN_UDP_PORT:-3478}:${PION_TURN_UDP_PORT:-3478}"
# TURN
- "49152-65535:49152-65535"
cap_add:
- NET_ADMIN
- SYS_ADMIN
- NET_RAW
91 changes: 91 additions & 0 deletions examples/turn-server/xdp/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

// Package main implements a simple TURN server with XDP offload
package main

import (
"flag"
"log"
"net"
"os"
"os/signal"
"regexp"
"strconv"
"syscall"

"github.com/pion/logging"
"github.com/pion/turn/v3"
)

func main() {
publicIP := flag.String("public-ip", "", "IP Address that TURN can be contacted by.")
port := flag.Int("port", 3478, "Listening port.")
users := flag.String("users", "", "List of username and password (e.g. \"user=pass,user=pass\")")
realm := flag.String("realm", "pion.ly", "Realm (defaults to \"pion.ly\")")
flag.Parse()

if len(*publicIP) == 0 {
log.Fatalf("'public-ip' is required")
} else if len(*users) == 0 {
log.Fatalf("'users' is required")
}

// Create a UDP listener to pass into pion/turn
// pion/turn itself doesn't allocate any UDP sockets, but lets the user pass them in
// this allows us to add logging, storage or modify inbound/outbound traffic
udpListener, err := net.ListenPacket("udp4", "0.0.0.0:"+strconv.Itoa(*port))
if err != nil {
log.Panicf("Failed to create TURN server listener: %s", err)
}

// Cache -users flag for easy lookup later
// If passwords are stored they should be saved to your DB hashed using turn.GenerateAuthKey
usersMap := map[string][]byte{}
for _, kv := range regexp.MustCompile(`(\w+)=(\w+)`).FindAllStringSubmatch(*users, -1) {
usersMap[kv[1]] = turn.GenerateAuthKey(kv[1], *realm, kv[2])
}

// Init the XDP offload engine
loggerFactory := logging.NewDefaultLoggerFactory()
err = turn.InitOffload(turn.OffloadConfig{Log: loggerFactory.NewLogger("offload")})
if err != nil {
log.Fatalf("Failed to init offload engine: %s", err)
}
defer turn.ShutdownOffload()

s, err := turn.NewServer(turn.ServerConfig{
Realm: *realm,
// Set AuthHandler callback
// This is called every time a user tries to authenticate with the TURN server
// Return the key for that user, or false when no user is found
AuthHandler: func(username string, realm string, srcAddr net.Addr) ([]byte, bool) {
if key, ok := usersMap[username]; ok {
return key, true
}
return nil, false
},
// PacketConnConfigs is a list of UDP Listeners and the configuration around them
PacketConnConfigs: []turn.PacketConnConfig{
{
PacketConn: udpListener,
RelayAddressGenerator: &turn.RelayAddressGeneratorStatic{
RelayAddress: net.ParseIP(*publicIP), // Claim that we are listening on IP passed by user (This should be your Public IP)
Address: "0.0.0.0", // But actually be listening on every interface
},
},
},
})
if err != nil {
log.Panic(err)
}

// Block until user sends SIGINT or SIGTERM
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
<-sigs

if err = s.Close(); err != nil {
log.Panic(err)
}
}
5 changes: 4 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/pion/turn/v3

go 1.13
go 1.16

Check failure on line 3 in go.mod

View workflow job for this annotation

GitHub Actions / lint / Metadata

Invalid Go version

Found 1.16. Expected 1.19

require (
github.com/pion/logging v0.2.2
Expand All @@ -10,3 +10,6 @@ require (
github.com/stretchr/testify v1.8.4
golang.org/x/sys v0.11.0
)

// ebpf/xdp offload
require github.com/cilium/ebpf v0.10.0
15 changes: 15 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
github.com/cilium/ebpf v0.10.0 h1:nk5HPMeoBXtOzbkZBWym+ZWq1GIiHUsBFXxwewXAHLQ=
github.com/cilium/ebpf v0.10.0/go.mod h1:DPiVdY/kT534dgc9ERmvP8mWA+9gvwgKfRvk4nNWnoE=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/pion/dtls/v2 v2.2.7 h1:cSUBsETxepsCSFSxC3mc/aDo14qQLMSL+O6IjG28yV8=
github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s=
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
Expand All @@ -13,8 +24,11 @@ github.com/pion/transport/v2 v2.2.1 h1:7qYnCBlpgSJNYMbLCKuSY9KbQdBFoETvPNETv0y4N
github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g=
github.com/pion/transport/v3 v3.0.1 h1:gDTlPJwROfSfz6QfSi0ZmeCSkFcnWWiiR9ES0ouANiM=
github.com/pion/transport/v3 v3.0.1/go.mod h1:UY7kiITrlMv7/IKgd5eTUcaahZx5oUN3l9SzK5f5xE0=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
Expand Down Expand Up @@ -47,6 +61,7 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
Expand Down
39 changes: 37 additions & 2 deletions internal/allocation/allocation.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/pion/logging"
"github.com/pion/stun/v2"
"github.com/pion/turn/v3/internal/ipnet"
"github.com/pion/turn/v3/internal/offload"
"github.com/pion/turn/v3/internal/proto"
)

Expand Down Expand Up @@ -111,6 +112,21 @@ func (a *Allocation) AddChannelBind(c *ChannelBind, lifetime time.Duration) erro
a.channelBindings = append(a.channelBindings, c)
c.start(lifetime)

// enable offload
// currently we support offload for UDP connections only
peer := offload.Connection{
RemoteAddr: c.Peer,
LocalAddr: a.RelayAddr,
Protocol: proto.ProtoUDP,
}
client := offload.Connection{
RemoteAddr: a.fiveTuple.SrcAddr,
LocalAddr: a.fiveTuple.DstAddr,
Protocol: proto.ProtoUDP,
ChannelID: uint32(c.Number),
}
_ = offload.Engine.Upsert(client, peer)

// Channel binds also refresh permissions.
a.AddPermission(NewPermission(c.Peer, a.log))
} else {
Expand All @@ -128,14 +144,33 @@ func (a *Allocation) RemoveChannelBind(number proto.ChannelNumber) bool {
a.channelBindingsLock.Lock()
defer a.channelBindingsLock.Unlock()

var cAddr net.Addr
ret := false

for i := len(a.channelBindings) - 1; i >= 0; i-- {
if a.channelBindings[i].Number == number {
cAddr = a.channelBindings[i].Peer
a.channelBindings = append(a.channelBindings[:i], a.channelBindings[i+1:]...)
return true
ret = true
break
}
}

return false
// disable offload
peer := offload.Connection{
RemoteAddr: cAddr,
LocalAddr: a.RelayAddr,
Protocol: proto.ProtoUDP,
ChannelID: uint32(number),
}
client := offload.Connection{
RemoteAddr: a.RelayAddr,
LocalAddr: cAddr,
Protocol: proto.ProtoUDP,
}
_ = offload.Engine.Remove(client, peer)

return ret
}

// GetChannelByNumber gets the ChannelBind from this allocation by id
Expand Down
12 changes: 12 additions & 0 deletions internal/offload/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

package offload

import "errors"

var (
errUnsupportedProtocol = errors.New("offload: protocol not supported")
errXDPAlreadyInitialized = errors.New("offload: XDP engine is already initialized")
errXDPLocalRedirectProhibited = errors.New("offload: XDP local redirect not allowed")
)
Loading

0 comments on commit 09b48ae

Please sign in to comment.