nextcloud-spreed-signaling/backend_configuration.go
2026-01-12 13:16:30 +01:00

216 lines
5 KiB
Go

/**
* Standalone signaling server for the Nextcloud Spreed app.
* Copyright (C) 2020 struktur AG
*
* @author Joachim Bauch <bauch@struktur.de>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package signaling
import (
"fmt"
"net/url"
"slices"
"strings"
"sync"
"github.com/dlintw/goconf"
"github.com/strukturag/nextcloud-spreed-signaling/etcd"
"github.com/strukturag/nextcloud-spreed-signaling/internal"
"github.com/strukturag/nextcloud-spreed-signaling/log"
"github.com/strukturag/nextcloud-spreed-signaling/talk"
)
const (
BackendTypeStatic = "static"
BackendTypeEtcd = "etcd"
DefaultBackendType = BackendTypeStatic
)
type BackendStorage interface {
Close()
Reload(cfg *goconf.ConfigFile)
GetCompatBackend() *talk.Backend
GetBackend(u *url.URL) *talk.Backend
GetBackends() []*talk.Backend
}
type BackendStorageStats interface {
AddBackends(count int)
RemoveBackends(count int)
IncBackends()
DecBackends()
}
type backendStorageCommon struct {
mu sync.RWMutex
// +checklocks:mu
backends map[string][]*talk.Backend
stats BackendStorageStats // +checklocksignore: Only written to from constructor
}
func (s *backendStorageCommon) GetBackends() []*talk.Backend {
s.mu.RLock()
defer s.mu.RUnlock()
var result []*talk.Backend
for _, entries := range s.backends {
result = append(result, entries...)
}
slices.SortFunc(result, func(a, b *talk.Backend) int {
return strings.Compare(a.Id(), b.Id())
})
result = slices.CompactFunc(result, func(a, b *talk.Backend) bool {
return a.Id() == b.Id()
})
return result
}
func (s *backendStorageCommon) getBackendLocked(u *url.URL) *talk.Backend {
s.mu.RLock()
defer s.mu.RUnlock()
entries, found := s.backends[u.Host]
if !found {
return nil
}
url := u.String()
if url[len(url)-1] != '/' {
url += "/"
}
for _, entry := range entries {
if !entry.IsUrlAllowed(u) {
continue
}
if entry.HasUrl(url) {
return entry
}
}
return nil
}
type BackendConfiguration struct {
storage BackendStorage
}
type prometheusBackendStats struct{}
func (s *prometheusBackendStats) AddBackends(count int) {
statsBackendsCurrent.Add(float64(count))
}
func (s *prometheusBackendStats) RemoveBackends(count int) {
statsBackendsCurrent.Sub(float64(count))
}
func (s *prometheusBackendStats) IncBackends() {
statsBackendsCurrent.Inc()
}
func (s *prometheusBackendStats) DecBackends() {
statsBackendsCurrent.Dec()
}
var (
defaultBackendStats = &prometheusBackendStats{}
)
func NewBackendConfiguration(logger log.Logger, config *goconf.ConfigFile, etcdClient etcd.Client) (*BackendConfiguration, error) {
return NewBackendConfigurationWithStats(logger, config, etcdClient, nil)
}
func NewBackendConfigurationWithStats(logger log.Logger, config *goconf.ConfigFile, etcdClient etcd.Client, stats BackendStorageStats) (*BackendConfiguration, error) {
backendType, _ := config.GetString("backend", "backendtype")
if backendType == "" {
backendType = DefaultBackendType
}
if stats == nil {
RegisterBackendConfigurationStats()
stats = defaultBackendStats
}
var storage BackendStorage
var err error
switch backendType {
case BackendTypeStatic:
storage, err = NewBackendStorageStatic(logger, config, stats)
case BackendTypeEtcd:
storage, err = NewBackendStorageEtcd(logger, config, etcdClient, stats)
default:
err = fmt.Errorf("unknown backend type: %s", backendType)
}
if err != nil {
return nil, err
}
return &BackendConfiguration{
storage: storage,
}, nil
}
func (b *BackendConfiguration) Close() {
b.storage.Close()
}
func (b *BackendConfiguration) Reload(config *goconf.ConfigFile) {
b.storage.Reload(config)
}
func (b *BackendConfiguration) GetCompatBackend() *talk.Backend {
return b.storage.GetCompatBackend()
}
func (b *BackendConfiguration) GetBackend(u *url.URL) *talk.Backend {
u, _ = internal.CanonicalizeUrl(u)
return b.storage.GetBackend(u)
}
func (b *BackendConfiguration) GetBackends() []*talk.Backend {
return b.storage.GetBackends()
}
func (b *BackendConfiguration) IsUrlAllowed(u *url.URL) bool {
if u == nil {
// Reject all invalid URLs.
return false
}
backend := b.GetBackend(u)
return backend != nil
}
func (b *BackendConfiguration) GetSecret(u *url.URL) []byte {
if u == nil {
// Reject all invalid URLs.
return nil
}
entry := b.GetBackend(u)
if entry == nil {
return nil
}
return entry.Secret()
}