From 4bc03d84353171a89f6bffaa36739f0a05540a95 Mon Sep 17 00:00:00 2001 From: cnderrauber Date: Thu, 3 Mar 2022 18:26:14 +0800 Subject: [PATCH] Client configuration (#452) * client configuration * fix init roommanager --- go.mod | 1 + go.sum | 2 + pkg/clientconfiguration/conf.go | 10 ++ pkg/clientconfiguration/conf_test.go | 101 ++++++++++++++++++ pkg/clientconfiguration/match.go | 77 +++++++++++++ .../staticconfiguration.go | 54 ++++++++++ pkg/clientconfiguration/types.go | 9 ++ pkg/rtc/participant.go | 4 +- pkg/service/roommanager.go | 31 +++--- pkg/service/wire.go | 6 ++ pkg/service/wire_gen.go | 8 +- 11 files changed, 289 insertions(+), 14 deletions(-) create mode 100644 pkg/clientconfiguration/conf.go create mode 100644 pkg/clientconfiguration/conf_test.go create mode 100644 pkg/clientconfiguration/match.go create mode 100644 pkg/clientconfiguration/staticconfiguration.go create mode 100644 pkg/clientconfiguration/types.go diff --git a/go.mod b/go.mod index 8c9772593..9e63bdb05 100644 --- a/go.mod +++ b/go.mod @@ -50,6 +50,7 @@ require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.1.1 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect + github.com/d5/tengo/v2 v2.10.1 github.com/davecgh/go-spew v1.1.1 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/eapache/channels v1.1.0 // indirect diff --git a/go.sum b/go.sum index f145f9293..b0d9308f5 100644 --- a/go.sum +++ b/go.sum @@ -31,6 +31,8 @@ github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/d5/tengo/v2 v2.10.1 h1:Z7vmTAQfdoExNEB9kxgqxvoBBW9bf+8uYMiDyriX5HM= +github.com/d5/tengo/v2 v2.10.1/go.mod h1:XRGjEs5I9jYIKTxly6HCF8oiiilk5E/RYXOZ5b0DZC8= 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= diff --git a/pkg/clientconfiguration/conf.go b/pkg/clientconfiguration/conf.go new file mode 100644 index 000000000..10946d9b7 --- /dev/null +++ b/pkg/clientconfiguration/conf.go @@ -0,0 +1,10 @@ +package clientconfiguration + +// configurations for livekit-client, add more configuration to StaticConfigurations as need +var StaticConfigurations = []ConfigurationItem{ + // { + // Match: &ScriptMatch{Expr: `c.protocol <= 5 || c.browser == "firefox"`}, + // Configuration: &livekit.ClientConfiguration{ResumeConnection: livekit.ClientConfigSetting_DISABLED}, + // Merge: false, + // }, +} diff --git a/pkg/clientconfiguration/conf_test.go b/pkg/clientconfiguration/conf_test.go new file mode 100644 index 000000000..aac624764 --- /dev/null +++ b/pkg/clientconfiguration/conf_test.go @@ -0,0 +1,101 @@ +package clientconfiguration + +import ( + "testing" + + "github.com/livekit/protocol/livekit" + "github.com/stretchr/testify/require" +) + +func TestScriptMatchConfiguration(t *testing.T) { + t.Run("no merge", func(t *testing.T) { + confs := []ConfigurationItem{ + { + Match: &ScriptMatch{Expr: `c.protocol > 5 && c.browser != "firefox"`}, + Configuration: &livekit.ClientConfiguration{ + ResumeConnection: livekit.ClientConfigSetting_ENABLED, + }, + }, + } + + cm := NewStaticClientConfigurationManager(confs) + + conf := cm.GetConfiguration(&livekit.ClientInfo{Protocol: 4}) + require.Nil(t, conf) + + conf = cm.GetConfiguration(&livekit.ClientInfo{Protocol: 6, Browser: "firefox"}) + require.Nil(t, conf) + + conf = cm.GetConfiguration(&livekit.ClientInfo{Protocol: 6, Browser: "chrome"}) + require.Equal(t, conf.ResumeConnection, livekit.ClientConfigSetting_ENABLED) + }) + + t.Run("merge", func(t *testing.T) { + confs := []ConfigurationItem{ + { + Match: &ScriptMatch{Expr: `c.protocol > 5 && c.browser != "firefox"`}, + Configuration: &livekit.ClientConfiguration{ + ResumeConnection: livekit.ClientConfigSetting_ENABLED, + }, + Merge: true, + }, + { + Match: &ScriptMatch{Expr: `c.sdk == "ANDROID"`}, + Configuration: &livekit.ClientConfiguration{ + Video: &livekit.VideoConfiguration{ + HardwareEncoder: livekit.ClientConfigSetting_DISABLED, + }, + }, + Merge: true, + }, + } + + cm := NewStaticClientConfigurationManager(confs) + + conf := cm.GetConfiguration(&livekit.ClientInfo{Protocol: 4}) + require.Nil(t, conf) + + conf = cm.GetConfiguration(&livekit.ClientInfo{Protocol: 6, Browser: "firefox"}) + require.Nil(t, conf) + + conf = cm.GetConfiguration(&livekit.ClientInfo{Protocol: 6, Browser: "chrome", Sdk: 3}) + require.Equal(t, conf.ResumeConnection, livekit.ClientConfigSetting_ENABLED) + require.Equal(t, conf.Video.HardwareEncoder, livekit.ClientConfigSetting_DISABLED) + }) +} + +func TestScriptMatch(t *testing.T) { + client := &livekit.ClientInfo{ + Protocol: 6, + Browser: "chrome", + Sdk: 3, // android + DeviceModel: "12345", + } + + type testcase struct { + name string + expr string + result bool + err bool + } + + cases := []testcase{ + {name: "simple match", expr: `c.protocol > 5`, result: true}, + {name: "invalid expr", expr: `cc.protocol > 5`, err: true}, + {name: "unexist field", expr: `c.protocols > 5`, err: true}, + {name: "combined condition", expr: `c.protocol > 5 && (c.sdk=="ANDROID" || c.sdk=="IOS")`, result: true}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + match := &ScriptMatch{Expr: c.expr} + m, err := match.Match(client) + if c.err { + require.Error(t, err) + } else { + require.Equal(t, c.result, m) + } + }) + + } +} diff --git a/pkg/clientconfiguration/match.go b/pkg/clientconfiguration/match.go new file mode 100644 index 000000000..a060d83cc --- /dev/null +++ b/pkg/clientconfiguration/match.go @@ -0,0 +1,77 @@ +package clientconfiguration + +import ( + "context" + "errors" + + "github.com/d5/tengo/v2" + + "github.com/livekit/protocol/livekit" +) + +type Match interface { + Match(clientInfo *livekit.ClientInfo) (bool, error) +} + +type ScriptMatch struct { + Expr string +} + +// use result of eval script expression for match. +// expression examples: +// protocol bigger than 5 : c.protocol > 5 +// browser if firefox: c.browser == "firefox" +// combined rule : c.protocol > 5 && c.browser == "firefox" +func (m *ScriptMatch) Match(clientInfo *livekit.ClientInfo) (bool, error) { + res, err := tengo.Eval(context.TODO(), m.Expr, map[string]interface{}{"c": &clientObject{info: clientInfo}}) + if err != nil { + return false, err + } + + if val, ok := res.(bool); ok { + return val, nil + } + return false, errors.New("invalid match expression result") +} + +type clientObject struct { + tengo.ObjectImpl + info *livekit.ClientInfo +} + +func (c *clientObject) TypeName() string { + return "clientObject" +} + +func (c *clientObject) String() string { + return c.info.String() +} + +func (c *clientObject) IndexGet(index tengo.Object) (res tengo.Object, err error) { + field, ok := index.(*tengo.String) + if !ok { + return nil, tengo.ErrInvalidIndexType + } + + switch field.Value { + case "sdk": + return &tengo.String{Value: c.info.Sdk.String()}, nil + case "version": + return &tengo.String{Value: c.info.Version}, nil + case "protocol": + return &tengo.Int{Value: int64(c.info.Protocol)}, nil + case "os": + return &tengo.String{Value: c.info.Os}, nil + case "os_version": + return &tengo.String{Value: c.info.OsVersion}, nil + case "device_model": + return &tengo.String{Value: c.info.DeviceModel}, nil + case "browser": + return &tengo.String{Value: c.info.Browser}, nil + case "browser_version": + return &tengo.String{Value: c.info.BrowserVersion}, nil + case "address": + return &tengo.String{Value: c.info.Address}, nil + } + return &tengo.Undefined{}, nil +} diff --git a/pkg/clientconfiguration/staticconfiguration.go b/pkg/clientconfiguration/staticconfiguration.go new file mode 100644 index 000000000..e417bcbd6 --- /dev/null +++ b/pkg/clientconfiguration/staticconfiguration.go @@ -0,0 +1,54 @@ +package clientconfiguration + +import ( + "fmt" + + "github.com/livekit/protocol/livekit" + "github.com/livekit/protocol/logger" + "google.golang.org/protobuf/proto" +) + +type ConfigurationItem struct { + Match + Configuration *livekit.ClientConfiguration + Merge bool +} + +type StaticClientConfigurationManager struct { + confs []ConfigurationItem +} + +func NewStaticClientConfigurationManager(confs []ConfigurationItem) *StaticClientConfigurationManager { + return &StaticClientConfigurationManager{confs: confs} +} + +func (s *StaticClientConfigurationManager) GetConfiguration(clientInfo *livekit.ClientInfo) *livekit.ClientConfiguration { + var matchedConf []*livekit.ClientConfiguration + for _, c := range s.confs { + matched, err := c.Match.Match(clientInfo) + if err != nil { + logger.Errorw(fmt.Sprintf("matchrule failed, clientInfo: %s", clientInfo.String()), err) + continue + } + if !matched { + continue + } + if !c.Merge { + return c.Configuration + } + matchedConf = append(matchedConf, c.Configuration) + } + + var conf *livekit.ClientConfiguration + for k, v := range matchedConf { + if k == 0 { + conf = proto.Clone(matchedConf[0]).(*livekit.ClientConfiguration) + } else { + // TODO : there is a problem use protobuf merge, we don't have flag to indicate 'no value', + // don't override default behavior or other configuration's field. So a bool value = false or + // a int value = 0 will override same field in other configuration + proto.Merge(conf, v) + } + } + return conf +} diff --git a/pkg/clientconfiguration/types.go b/pkg/clientconfiguration/types.go new file mode 100644 index 000000000..5e7a8ca2f --- /dev/null +++ b/pkg/clientconfiguration/types.go @@ -0,0 +1,9 @@ +package clientconfiguration + +import ( + "github.com/livekit/protocol/livekit" +) + +type ClientConfigurationManager interface { + GetConfiguration(clientInfo *livekit.ClientInfo) *livekit.ClientConfiguration +} diff --git a/pkg/rtc/participant.go b/pkg/rtc/participant.go index f3530a6d2..99f4a21c1 100644 --- a/pkg/rtc/participant.go +++ b/pkg/rtc/participant.go @@ -60,6 +60,7 @@ type ParticipantParams struct { SimTracks map[uint32]SimulcastTrackInfo Grants *auth.ClaimGrants InitialVersion uint32 + ClientConf *livekit.ClientConfiguration } type ParticipantImpl struct { @@ -612,7 +613,8 @@ func (p *ParticipantImpl) SendJoinResponse( ServerRegion: region, IceServers: iceServers, // indicates both server and client support subscriber as primary - SubscriberPrimary: p.SubscriberAsPrimary(), + SubscriberPrimary: p.SubscriberAsPrimary(), + ClientConfiguration: p.params.ClientConf, }, }, }) diff --git a/pkg/service/roommanager.go b/pkg/service/roommanager.go index 332102582..4d0b7d1a0 100644 --- a/pkg/service/roommanager.go +++ b/pkg/service/roommanager.go @@ -11,6 +11,7 @@ import ( "github.com/livekit/protocol/logger" "github.com/livekit/protocol/utils" + "github.com/livekit/livekit-server/pkg/clientconfiguration" "github.com/livekit/livekit-server/pkg/config" "github.com/livekit/livekit-server/pkg/routing" "github.com/livekit/livekit-server/pkg/rtc" @@ -29,12 +30,13 @@ const ( type RoomManager struct { lock sync.RWMutex - config *config.Config - rtcConfig *rtc.WebRTCConfig - currentNode routing.LocalNode - router routing.Router - roomStore ObjectStore - telemetry telemetry.TelemetryService + config *config.Config + rtcConfig *rtc.WebRTCConfig + currentNode routing.LocalNode + router routing.Router + roomStore ObjectStore + telemetry telemetry.TelemetryService + clientConfManager clientconfiguration.ClientConfigurationManager rooms map[livekit.RoomName]*rtc.Room } @@ -45,6 +47,7 @@ func NewLocalRoomManager( currentNode routing.LocalNode, router routing.Router, telemetry telemetry.TelemetryService, + clientConfManager clientconfiguration.ClientConfigurationManager, ) (*RoomManager, error) { rtcConf, err := rtc.NewWebRTCConfig(conf, currentNode.Ip) @@ -53,12 +56,13 @@ func NewLocalRoomManager( } r := &RoomManager{ - config: conf, - rtcConfig: rtcConf, - currentNode: currentNode, - router: router, - roomStore: roomStore, - telemetry: telemetry, + config: conf, + rtcConfig: rtcConf, + currentNode: currentNode, + router: router, + roomStore: roomStore, + telemetry: telemetry, + clientConfManager: clientConfManager, rooms: make(map[livekit.RoomName]*rtc.Room), } @@ -228,6 +232,8 @@ func (r *RoomManager) StartSession(ctx context.Context, roomName livekit.RoomNam "protocol", pi.Client.Protocol, ) + clientConf := r.clientConfManager.GetConfiguration(pi.Client) + pv := types.ProtocolVersion(pi.Client.Protocol) rtcConf := *r.rtcConfig rtcConf.SetBufferFactory(room.GetBufferFactory()) @@ -248,6 +254,7 @@ func (r *RoomManager) StartSession(ctx context.Context, roomName livekit.RoomNam Grants: pi.Grants, Hidden: pi.Hidden, Logger: pLogger, + ClientConf: clientConf, }, pi.Permission) if err != nil { logger.Errorw("could not create participant", err) diff --git a/pkg/service/wire.go b/pkg/service/wire.go index 60e21af08..ac5fc8be9 100644 --- a/pkg/service/wire.go +++ b/pkg/service/wire.go @@ -10,6 +10,7 @@ import ( "github.com/go-redis/redis/v8" "github.com/google/wire" + "github.com/livekit/livekit-server/pkg/clientconfiguration" "github.com/livekit/protocol/auth" "github.com/livekit/protocol/livekit" "github.com/livekit/protocol/logger" @@ -33,6 +34,7 @@ func InitializeServer(conf *config.Config, currentNode routing.LocalNode) (*Live wire.Bind(new(EgressStore), new(ObjectStore)), createKeyProvider, createWebhookNotifier, + createClientConfiguration, routing.CreateRouter, wire.Bind(new(routing.MessageRouter), new(routing.Router)), wire.Bind(new(livekit.RoomService), new(*RoomService)), @@ -147,3 +149,7 @@ func createStore(rc *redis.Client) ObjectStore { } return NewLocalStore() } + +func createClientConfiguration() clientconfiguration.ClientConfigurationManager { + return clientconfiguration.NewStaticClientConfigurationManager(clientconfiguration.StaticConfigurations) +} diff --git a/pkg/service/wire_gen.go b/pkg/service/wire_gen.go index c3943b55b..277a91659 100644 --- a/pkg/service/wire_gen.go +++ b/pkg/service/wire_gen.go @@ -11,6 +11,7 @@ import ( "crypto/tls" "fmt" "github.com/go-redis/redis/v8" + "github.com/livekit/livekit-server/pkg/clientconfiguration" "github.com/livekit/livekit-server/pkg/config" "github.com/livekit/livekit-server/pkg/routing" "github.com/livekit/livekit-server/pkg/telemetry" @@ -54,7 +55,8 @@ func InitializeServer(conf *config.Config, currentNode routing.LocalNode) (*Live egressService := NewEgressService(messageBus, objectStore, roomService, telemetryService) recordingService := NewRecordingService(messageBus, telemetryService) rtcService := NewRTCService(conf, roomAllocator, objectStore, router, currentNode) - roomManager, err := NewLocalRoomManager(conf, objectStore, currentNode, router, telemetryService) + clientConfigurationManager := createClientConfiguration() + roomManager, err := NewLocalRoomManager(conf, objectStore, currentNode, router, telemetryService, clientConfigurationManager) if err != nil { return nil, err } @@ -167,3 +169,7 @@ func createStore(rc *redis.Client) ObjectStore { } return NewLocalStore() } + +func createClientConfiguration() clientconfiguration.ClientConfigurationManager { + return clientconfiguration.NewStaticClientConfigurationManager(clientconfiguration.StaticConfigurations) +}