Skip to content
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 #360

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -29,4 +29,5 @@ var (
errFailedToDecodeSTUN = errors.New("failed to decode STUN message")
errUnexpectedSTUNRequestMessage = errors.New("unexpected STUN request message")
errRelayAddressGeneratorNil = errors.New("RelayAddressGenerator is nil")
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)
}
}
6 changes: 5 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.19
go 1.21.0

require (
github.com/pion/logging v0.2.2
Expand All @@ -18,5 +18,9 @@ require (
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/wlynxg/anet v0.0.3 // indirect
golang.org/x/crypto v0.21.0 // indirect
golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

// ebpf/xdp offload
require github.com/cilium/ebpf v0.15.0
14 changes: 14 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
github.com/cilium/ebpf v0.15.0 h1:7NxJhNiBT3NG8pZJ3c+yfrVdHY8ScgKD27sScgjLMMk=
github.com/cilium/ebpf v0.15.0/go.mod h1:DHp1WyrLeiBh19Cf/tfiSMhqheEiK8fXFZ4No0P1Hso=
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/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
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 @@ -16,6 +26,8 @@ github.com/pion/transport/v3 v3.0.5 h1:ofVrcbPNqVPuKaTO5AMFnFuJ1ZX7ElYiWzC5PCf9Y
github.com/pion/transport/v3 v3.0.5/go.mod h1:HvJr2N/JwNJAfipsRleqwFoR3t/pWyHeZUs89v3+t5s=
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.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
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 All @@ -34,6 +46,8 @@ golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2 h1:Jvc7gsqn21cJHCmAWx0LiimpP18LZmUxkT5Mp7EZ1mI=
golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
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
Loading