Skip to content

Commit

Permalink
Merge branch 'l7mp:main' into staging
Browse files Browse the repository at this point in the history
  • Loading branch information
notdurson authored Dec 5, 2024
2 parents 2f5c5bc + 09033d1 commit 36ac716
Show file tree
Hide file tree
Showing 15 changed files with 223 additions and 55 deletions.
26 changes: 7 additions & 19 deletions docs/examples/kurento-magic-mirror/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,9 @@ In this demo you will learn the following steps to:

### Prerequisites

Consult the [STUNner installation and configuration guide](../../INSTALL.md) to set up STUNner.
The tutorial assumes a fresh STUNner installation; see the [STUNner installation and configuration guide](../../INSTALL.md). Create a namespace called `stunner` if there is none. You need a WebRTC-compatible browser to run this tutorial. Basically any modern browser will do; we usually test our WebRTC applications with Firefox.

### Quick installation

The simplest way to deploy the demo is to clone the [STUNner git
repository](https://github.com/l7mp/stunner) and deploy the
[manifest](kurento-magic-mirror-server.yaml) packaged with STUNner.

Install the STUNner gateway operator and STUNner ([more info](https://github.com/l7mp/stunner-helm)):

```console
helm repo add stunner https://l7mp.io/stunner
helm repo update
helm install stunner-gateway-operator stunner/stunner-gateway-operator --create-namespace --namespace=stunner-system
helm install stunner stunner/stunner
```
### Install the Application

Install the WebRTC application and Kurento media servers, altogether with the corresponding services and Kubernetes objects (see the content of the yaml for details):

Expand All @@ -48,8 +35,9 @@ $ cd stunner
$ kubectl apply -f docs/examples/kurento-magic-mirror/kurento-magic-mirror-server.yaml
```

### Configuration
Configure STUNner to act as a STUN server towards clients, and to let media reach the media server.
### STUNner configuration

Next, we configure STUNner to act as a TURN server towards clients, and to let media reach the media server.

```console
$ kubectl apply -f docs/examples/kurento-magic-mirror/kurento-magic-mirror-stunner.yaml
Expand All @@ -69,7 +57,7 @@ self-signed certificate, and hit the `Start` button.

### Scaling

This demo uses the AI/ML based computer vision features built into Kubernetes to process media. As
This demo uses the AI/ML based computer vision features built into Kurento to process media. As
such, it is fairly hard on the media server CPU. Thanks to STUNner, the media server pool can be
simply (auto-)scaled with Kubernetes effortlessly.

Expand All @@ -82,4 +70,4 @@ $ kubectl scale deployment kms --replicas=4

# Help

STUNner development is coordinated in Discord, feel free to [join](https://discord.gg/DyPgEsbwzc).
STUNner development is coordinated in Discord, feel free to [join](https://discord.gg/DyPgEsbwzc).
11 changes: 6 additions & 5 deletions docs/examples/neko/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,14 @@ To run this example, you need:
* a [deployed STUNner](../../INSTALL.md#installation-1) (presumably the latest stable version),
* optionally, an [Ingress controller](../TLS.md#ingress) to ingest traffic into the cluster.

### Quick installation
### STUNner configuration

The simplest way to deploy the demo is to clone the [STUNner git repository](https://github.com/l7mp/stunner) and deploy the [manifest](neko.yaml) packaged with STUNner.

Configure STUNner to act as a STUN server towards clients, and to let media reach the media server.
First, configure STUNner to act as a TURN server towards clients, and to let media reach the media server.

```console
git clone https://github.com/l7mp/stunner
cd stunner/docs/examples/neko

kubectl apply -f stunner.yaml
```

Expand All @@ -43,7 +42,9 @@ STUNNERIP=$(kubectl get service udp-gateway -n default -o jsonpath='{.status.loa
> [!NOTE]
> This IP should be accessible from your browser. If that "public IP" is behind a NAT, you can overwrite it with the actual public IP that routes to the service by hand (e.g. `STUNNERIP=<your public IP>`).
We need to give this public IP the Neko configuration in the `NEKO_ICESERVERS` environment variable, inside the `json` content (basically this will tell you browser to use STUNner as a STUN/TURN server).
### Setup Neko

We need to set the STUNner public IP in the Neko configuration file under the `NEKO_ICESERVERS` environment variable, which is inside the `json` content. This will tell you browser to use STUNner as a STUN/TURN server.
You can do that by hand, or by this fancy `sed` command:
```console
sed -i "s/turn:[\.0-9]*:3478/turn:$STUNNERIP:3478/g" neko.yaml
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ require (
github.com/oapi-codegen/runtime v1.1.1
github.com/pion/datachannel v1.5.9
github.com/pion/dtls/v3 v3.0.3
github.com/pion/ice/v4 v4.0.2
github.com/pion/logging v0.2.2
github.com/pion/transport/v3 v3.0.7
github.com/pion/turn/v4 v4.0.0
Expand Down Expand Up @@ -81,7 +82,6 @@ require (
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
github.com/perimeterx/marshmallow v1.1.5 // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/pion/ice/v4 v4.0.2 // indirect
github.com/pion/interceptor v0.1.37 // indirect
github.com/pion/mdns/v2 v2.0.7 // indirect
github.com/pion/randutil v0.1.0 // indirect
Expand Down Expand Up @@ -123,4 +123,4 @@ require (
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
)

// replace github.com/pion/turn/v2 => github.com/l7mp/turn/v2 v2.0.11
replace github.com/pion/turn/v4 => github.com/l7mp/turn/v4 v4.0.1-0.20241205140700-99276149099b
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/l7mp/turn/v4 v4.0.1-0.20241205140700-99276149099b h1:HYqcjCtvPIqjoHUfM/WHzJMBAh9ZgCY8qbq8AbxZj6k=
github.com/l7mp/turn/v4 v4.0.1-0.20241205140700-99276149099b/go.mod h1:0F3wpjtwvLRrpRiKXB4YzJ9B88nlS3ppSQnYldpnh0s=
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0=
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
Expand Down Expand Up @@ -155,8 +157,6 @@ github.com/pion/stun/v3 v3.0.0 h1:4h1gwhWLWuZWOJIJR9s2ferRO+W3zA/b6ijOI6mKzUw=
github.com/pion/stun/v3 v3.0.0/go.mod h1:HvCN8txt8mwi4FBvS3EmDghW6aQJ24T+y+1TKjB5jyU=
github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0=
github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo=
github.com/pion/turn/v4 v4.0.0 h1:qxplo3Rxa9Yg1xXDxxH8xaqcyGUtbHYw4QSCvmFWvhM=
github.com/pion/turn/v4 v4.0.0/go.mod h1:MuPDkm15nYSklKpN8vWJ9W2M0PlyQZqYt1McGuxG7mA=
github.com/pion/webrtc/v4 v4.0.1 h1:6Unwc6JzoTsjxetcAIoWH81RUM4K5dBc1BbJGcF9WVE=
github.com/pion/webrtc/v4 v4.0.1/go.mod h1:SfNn8CcFxR6OUVjLXVslAQ3a3994JhyE3Hw1jAuqEto=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
Expand Down
72 changes: 64 additions & 8 deletions handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ package stunner

import (
"errors"
"fmt"
"net"

"github.com/l7mp/stunner/internal/object"
"github.com/l7mp/stunner/internal/util"
"github.com/pion/turn/v4"

stnrv1 "github.com/l7mp/stunner/pkg/apis/v1"
a12n "github.com/l7mp/stunner/pkg/authentication"
Expand All @@ -22,7 +24,7 @@ func (s *Stunner) NewAuthHandler() a12n.AuthHandler {

switch auth.Type {
case stnrv1.AuthTypeStatic:
auth.Log.Infof("static auth request: username=%q realm=%q srcAddr=%v\n",
auth.Log.Tracef("static auth request: username=%q realm=%q srcAddr=%v\n",
username, realm, srcAddr)

key := a12n.GenerateAuthKey(auth.Username, auth.Realm, auth.Password)
Expand All @@ -31,11 +33,11 @@ func (s *Stunner) NewAuthHandler() a12n.AuthHandler {
return key, true
}

auth.Log.Info("static auth request: failed: invalid username")
auth.Log.Infof("static auth request: failed: invalid username")
return nil, false

case stnrv1.AuthTypeEphemeral:
auth.Log.Infof("ephemeral auth request: username=%q realm=%q srcAddr=%v",
auth.Log.Tracef("ephemeral auth request: username=%q realm=%q srcAddr=%v",
username, realm, srcAddr)

if err := a12n.CheckTimeWindowedUsername(username); err != nil {
Expand All @@ -45,12 +47,12 @@ func (s *Stunner) NewAuthHandler() a12n.AuthHandler {

password, err := a12n.GetLongTermCredential(username, auth.Secret)
if err != nil {
auth.Log.Infof("ephemeral auth request: error generating password: %s",
auth.Log.Debugf("ephemeral auth request: error generating password: %s",
err)
return nil, false
}

auth.Log.Info("ephemeral auth request: success")
auth.Log.Debug("ephemeral auth request: success")
return a12n.GenerateAuthKey(username, auth.Realm, password), true

default:
Expand All @@ -71,7 +73,7 @@ func (s *Stunner) NewPermissionHandler(l *object.Listener) a12n.PermissionHandle
auth := s.GetAuth()

peerIP := peer.String()
auth.Log.Debugf("permission handler for listener %q: client %q, peer %q", l.Name,
auth.Log.Tracef("permission handler for listener %q: client %q, peer %q", l.Name,
src.String(), peerIP)

clusters := s.clusterManager.Keys()
Expand All @@ -81,14 +83,14 @@ func (s *Stunner) NewPermissionHandler(l *object.Listener) a12n.PermissionHandle
auth.Log.Tracef("considering cluster %q", r)
c := s.GetCluster(r)
if c.Route(peer) {
auth.Log.Infof("permission granted on listener %q for client "+
auth.Log.Debugf("permission granted on listener %q for client "+
"%q to peer %s via cluster %q", l.Name, src.String(),
peerIP, c.Name)
return true
}
}
}
auth.Log.Debugf("permission denied on listener %q for client %q to peer %s: "+
auth.Log.Infof("permission denied on listener %q for client %q to peer %s: "+
"no route to endpoint", l.Name, src.String(), peerIP)
return false
}
Expand Down Expand Up @@ -119,3 +121,57 @@ func (s *Stunner) NewRealmHandler() object.RealmHandler {
func (s *Stunner) NewStatusHandler() object.StatusHandler {
return func() stnrv1.Status { return s.Status() }
}

var lifecycleEventHandlerConstructor = newLifecycleEventHandlerStub

// NewLifecycleEventHandler creates a set of callbcks for tracking the lifecycle of TURN allocations.
func (s *Stunner) NewLifecycleEventHandler() turn.EventHandlers {
return lifecycleEventHandlerConstructor(s)
}

// LifecycleEventHandlerStub is a simple stub that logs allocation events.
func newLifecycleEventHandlerStub(s *Stunner) turn.EventHandlers {
return turn.EventHandlers{
OnAuth: func(src, dst net.Addr, proto, username, realm string, method string, verdict bool) {
status := "REJECTED"
if verdict {
status = "ACCEPTED"
}
s.log.Infof("Authentication request: client=%s, method=%s, verdict=%s",
dumpClient(src, dst, proto, username, realm), method, status)
},
OnAllocationCreated: func(src, dst net.Addr, proto, username, realm string, relayAddr net.Addr, reqPort int) {
s.log.Infof("Allocation created: client=%s, relay-address=%s, requested-port=%d",
dumpClient(src, dst, proto, username, realm), relayAddr.String(), reqPort)
},
OnAllocationDeleted: func(src, dst net.Addr, proto, username, realm string) {
s.log.Infof("Allocation deleted: client=%s", dumpClient(src, dst, proto, username, realm))
},
OnAllocationError: func(src, dst net.Addr, proto, message string) {
s.log.Infof("Allocation error: client=%s-%s:%s, error=%s", src, dst, proto, message)
},
OnPermissionCreated: func(src, dst net.Addr, proto, username, realm string, relayAddr net.Addr, peer net.IP) {
s.log.Infof("Permission created: client=%s, relay-addr=%s, peer=%s",
dumpClient(src, dst, proto, username, realm), relayAddr.String(), peer.String())
},
OnPermissionDeleted: func(src, dst net.Addr, proto, username, realm string, relayAddr net.Addr, peer net.IP) {
s.log.Infof("Permission deleted: client=%s, relay-addr=%s, peer=%s",
dumpClient(src, dst, proto, username, realm), relayAddr.String(), peer.String())
},
OnChannelCreated: func(src, dst net.Addr, proto, username, realm string, relayAddr, peer net.Addr, chanNum uint16) {
s.log.Infof("Channel created: client=%s, relay-addr=%s, peer=%s, channel-num=%d",
dumpClient(src, dst, proto, username, realm), relayAddr.String(),
peer.String(), chanNum)
},
OnChannelDeleted: func(src, dst net.Addr, proto, username, realm string, relayAddr, peer net.Addr, chanNum uint16) {
s.log.Infof("Channel deleted: client=%s, relay-addr=%s, peer=%s, channel-num=%d",
dumpClient(src, dst, proto, username, realm), relayAddr.String(),
peer.String(), chanNum)
},
}
}

func dumpClient(srcAddr, dstAddr net.Addr, protocol, username, realm string) string {
return fmt.Sprintf("%s-%s:%s, username=%s, realm=%s", srcAddr.String(), dstAddr.String(),
protocol, username, realm)
}
19 changes: 14 additions & 5 deletions internal/object/admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/prometheus/client_golang/prometheus/promhttp"

stnrv1 "github.com/l7mp/stunner/pkg/apis/v1"
licensecfg "github.com/l7mp/stunner/pkg/config/license"
)

const DefaultAdminObjectName = "DefaultAdmin"
Expand All @@ -25,6 +26,8 @@ type Admin struct {
MetricsEndpoint, HealthCheckEndpoint string
metricsServer, healthCheckServer *http.Server
health *http.ServeMux
licenseManager licensecfg.ConfigManager
licenseConfig *stnrv1.LicenseConfig
log logging.LeveledLogger
}

Expand All @@ -36,9 +39,10 @@ func NewAdmin(conf stnrv1.Config, dryRun bool, rc ReadinessHandler, status Statu
}

admin := Admin{
DryRun: dryRun,
health: http.NewServeMux(),
log: logger.NewLogger("stunner-admin"),
DryRun: dryRun,
health: http.NewServeMux(),
licenseManager: licensecfg.New(logger.NewLogger("license")),
log: logger.NewLogger("admin"),
}
admin.log.Tracef("NewAdmin: %s", req.String())

Expand All @@ -49,6 +53,7 @@ func NewAdmin(conf stnrv1.Config, dryRun bool, rc ReadinessHandler, status Statu
w.WriteHeader(http.StatusOK)
w.Write([]byte("{}\n")) //nolint:errcheck
})

// readniness checker calls the checker from the factory
admin.health.HandleFunc("/ready", func(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
Expand All @@ -63,6 +68,7 @@ func NewAdmin(conf stnrv1.Config, dryRun bool, rc ReadinessHandler, status Statu
http.StatusOK, "READY")))
}
})

// status handler returns the status
admin.health.HandleFunc("/status", func(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
Expand Down Expand Up @@ -118,6 +124,9 @@ func (a *Admin) Reconcile(conf stnrv1.Config) error {
return err
}

a.licenseManager.Reconcile(req.LicenseConfig)
a.licenseConfig = req.LicenseConfig

return nil
}

Expand All @@ -144,6 +153,7 @@ func (a *Admin) GetConfig() stnrv1.Config {
LogLevel: a.LogLevel,
MetricsEndpoint: a.MetricsEndpoint,
HealthCheckEndpoint: &h,
LicenseConfig: a.licenseConfig,
}
}

Expand Down Expand Up @@ -177,9 +187,8 @@ func (a *Admin) Status() stnrv1.Status {
LogLevel: a.LogLevel,
MetricsEndpoint: a.MetricsEndpoint,
HealthCheckEndpoint: a.HealthCheckEndpoint,
LicensingInfo: a.licenseManager.Status(),
}

// add licensing status here
return &s
}

Expand Down
2 changes: 1 addition & 1 deletion internal/object/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func NewAuth(conf stnrv1.Config, logger logging.LoggerFactory) (Object, error) {
return nil, stnrv1.ErrInvalidConf
}

auth := Auth{Log: logger.NewLogger("stunner-auth")}
auth := Auth{Log: logger.NewLogger("auth")}
auth.Log.Tracef("NewAuth: %s", req.String())

if err := auth.Reconcile(req); err != nil && !errors.Is(err, ErrRestartRequired) {
Expand Down
2 changes: 1 addition & 1 deletion internal/object/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func NewCluster(conf stnrv1.Config, resolver resolver.DnsResolver, logger loggin
Domains: []string{},
Resolver: resolver,
logger: logger,
log: logger.NewLogger(fmt.Sprintf("stunner-cluster-%s", req.Name)),
log: logger.NewLogger(fmt.Sprintf("cluster-%s", req.Name)),
}

c.log.Tracef("NewCluster: %s", req.String())
Expand Down
2 changes: 1 addition & 1 deletion internal/object/listener.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func NewListener(conf stnrv1.Config, net transport.Net, realmHandler RealmHandle
getRealm: realmHandler,
Conns: []any{},
logger: logger,
log: logger.NewLogger(fmt.Sprintf("stunner-listener-%s", req.Name)),
log: logger.NewLogger(fmt.Sprintf("listener-%s", req.Name)),
}

l.log.Tracef("NewListener: %s", req.String())
Expand Down
Loading

0 comments on commit 36ac716

Please sign in to comment.