mirror of
https://github.com/hackdiaz-dev/free-turn-proxy.git
synced 2026-10-12 09:41:53 +02:00
196 lines
3.9 KiB
Go
196 lines
3.9 KiB
Go
package clientsdb
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"os"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
type ClientInfo struct {
|
|
Comment string `json:"comment,omitempty"`
|
|
}
|
|
|
|
type Data struct {
|
|
Clients map[string]ClientInfo `json:"clients"`
|
|
}
|
|
|
|
type DB struct {
|
|
mu sync.RWMutex
|
|
path string
|
|
data Data
|
|
lastModified time.Time
|
|
}
|
|
|
|
func New(path string) (*DB, error) {
|
|
db := &DB{
|
|
path: path,
|
|
data: Data{Clients: make(map[string]ClientInfo)},
|
|
}
|
|
|
|
if err := db.load(); err != nil {
|
|
if !os.IsNotExist(err) {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return db, nil
|
|
}
|
|
|
|
func (db *DB) StartHotReload(interval time.Duration) {
|
|
go func() {
|
|
ticker := time.NewTicker(interval)
|
|
defer ticker.Stop()
|
|
for range ticker.C {
|
|
db.loadIfModified()
|
|
}
|
|
}()
|
|
}
|
|
|
|
func (db *DB) IsAuthorized(clientID string) bool {
|
|
db.mu.RLock()
|
|
defer db.mu.RUnlock()
|
|
_, ok := db.data.Clients[clientID]
|
|
return ok
|
|
}
|
|
|
|
func (db *DB) Add(clientID, comment string) error {
|
|
db.mu.Lock()
|
|
defer db.mu.Unlock()
|
|
|
|
db.data.Clients[clientID] = ClientInfo{Comment: comment}
|
|
return db.save()
|
|
}
|
|
|
|
func (db *DB) Remove(clientID string) error {
|
|
db.mu.Lock()
|
|
defer db.mu.Unlock()
|
|
|
|
delete(db.data.Clients, clientID)
|
|
return db.save()
|
|
}
|
|
|
|
func (db *DB) List() map[string]ClientInfo {
|
|
db.mu.RLock()
|
|
defer db.mu.RUnlock()
|
|
|
|
res := make(map[string]ClientInfo)
|
|
for k, v := range db.data.Clients {
|
|
res[k] = v
|
|
}
|
|
return res
|
|
}
|
|
|
|
func (db *DB) load() error {
|
|
stat, err := os.Stat(db.path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
b, err := os.ReadFile(db.path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var d Data
|
|
if err := json.Unmarshal(b, &d); err != nil {
|
|
return fmt.Errorf("failed to parse %s: %w", db.path, err)
|
|
}
|
|
|
|
if d.Clients == nil {
|
|
d.Clients = make(map[string]ClientInfo)
|
|
}
|
|
|
|
db.data = d
|
|
db.lastModified = stat.ModTime()
|
|
return nil
|
|
}
|
|
|
|
func (db *DB) loadIfModified() {
|
|
stat, err := os.Stat(db.path)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
db.mu.RLock()
|
|
modTime := db.lastModified
|
|
db.mu.RUnlock()
|
|
|
|
if stat.ModTime().After(modTime) {
|
|
db.mu.Lock()
|
|
_ = db.load()
|
|
db.mu.Unlock()
|
|
}
|
|
}
|
|
|
|
func (db *DB) save() error {
|
|
b, err := json.MarshalIndent(db.data, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
tmpFile := db.path + ".tmp"
|
|
err = os.WriteFile(tmpFile, b, 0o600) // 0o600: файл содержит Client ID токены авторизации
|
|
if err == nil {
|
|
err = os.Rename(tmpFile, db.path)
|
|
}
|
|
if err == nil {
|
|
stat, _ := os.Stat(db.path)
|
|
if stat != nil {
|
|
db.lastModified = stat.ModTime()
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
|
|
// Тег режима едет хвостом той же записи: клиент до этого поля его не писал, а читатель
|
|
// брал ровно 1+len байт - лишний байт старый сервер молча пропускает.
|
|
const (
|
|
ModeUnset byte = 0
|
|
ModeUDP byte = 1
|
|
ModeTCP byte = 2
|
|
)
|
|
|
|
// WriteClientID отправляет Client ID (1 байт длины + строка + 1 байт режима).
|
|
func WriteClientID(conn net.Conn, clientID string, mode byte) error {
|
|
b := []byte(clientID)
|
|
if len(b) > 255 {
|
|
b = b[:255]
|
|
}
|
|
buf := make([]byte, 1+len(b)+1)
|
|
buf[0] = byte(len(b)) //nolint:gosec // len(b) усечён до ≤255 выше
|
|
copy(buf[1:], b)
|
|
buf[1+len(b)] = mode
|
|
_, err := conn.Write(buf)
|
|
return err
|
|
}
|
|
|
|
// ReadClientID читает Client ID из первой DTLS-записи. Режим ModeUnset - клиент старше
|
|
// тега, режим у него всегда udp.
|
|
func ReadClientID(conn net.Conn) (string, byte, error) {
|
|
_ = conn.SetReadDeadline(time.Now().Add(5 * time.Second))
|
|
defer func() { _ = conn.SetReadDeadline(time.Time{}) }()
|
|
|
|
buf := make([]byte, 257)
|
|
n, err := conn.Read(buf)
|
|
if err != nil {
|
|
return "", ModeUnset, err
|
|
}
|
|
if n == 0 {
|
|
return "", ModeUnset, nil
|
|
}
|
|
|
|
l := int(buf[0])
|
|
if n < 1+l {
|
|
return "", ModeUnset, io.ErrUnexpectedEOF
|
|
}
|
|
mode := ModeUnset
|
|
if n > 1+l {
|
|
mode = buf[1+l]
|
|
}
|
|
return string(buf[1 : 1+l]), mode, nil
|
|
}
|