diff --git a/.gitignore b/.gitignore index 043956ee4..ce1c9461d 100644 --- a/.gitignore +++ b/.gitignore @@ -77,4 +77,7 @@ media_store/ build # golang workspaces -go.work* \ No newline at end of file +go.work* + +# helm chart +helm/dendrite/charts/ diff --git a/docs/FAQ.md b/docs/FAQ.md index 82b1581ea..2ef9e6c2b 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -117,6 +117,7 @@ The list of files that need to be stored is: - matrix-key.pem - dendrite.yaml - the postgres or sqlite DB +- the jetstream directory - the media store - the search index (although this can be regenerated) diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock index 195f60c6f..791d0f857 100644 --- a/docs/Gemfile.lock +++ b/docs/Gemfile.lock @@ -231,9 +231,9 @@ GEM jekyll-seo-tag (~> 2.1) minitest (5.17.0) multipart-post (2.1.1) - nokogiri (1.14.3-arm64-darwin) + nokogiri (1.16.2-arm64-darwin) racc (~> 1.4) - nokogiri (1.14.3-x86_64-linux) + nokogiri (1.16.2-x86_64-linux) racc (~> 1.4) octokit (4.22.0) faraday (>= 0.9) @@ -241,7 +241,7 @@ GEM pathutil (0.16.2) forwardable-extended (~> 2.6) public_suffix (4.0.7) - racc (1.6.2) + racc (1.7.3) rb-fsevent (0.11.1) rb-inotify (0.10.1) ffi (~> 1.0) diff --git a/federationapi/federationapi.go b/federationapi/federationapi.go index efbfa3315..d3730c7ca 100644 --- a/federationapi/federationapi.go +++ b/federationapi/federationapi.go @@ -113,10 +113,7 @@ func NewInternalAPI( _ = federationDB.RemoveAllServersFromBlacklist() } - stats := statistics.NewStatistics( - federationDB, - cfg.FederationMaxRetries+1, - cfg.P2PFederationRetriesUntilAssumedOffline+1) + stats := statistics.NewStatistics(federationDB, cfg.FederationMaxRetries+1, cfg.P2PFederationRetriesUntilAssumedOffline+1, cfg.EnableRelays) js, nats := natsInstance.Prepare(processContext, &cfg.Matrix.JetStream) diff --git a/federationapi/internal/federationclient_test.go b/federationapi/internal/federationclient_test.go index fe8d84ffb..47efb11da 100644 --- a/federationapi/internal/federationclient_test.go +++ b/federationapi/internal/federationclient_test.go @@ -61,7 +61,7 @@ func TestFederationClientQueryKeys(t *testing.T) { }, } fedClient := &testFedClient{} - stats := statistics.NewStatistics(testDB, FailuresUntilBlacklist, FailuresUntilAssumedOffline) + stats := statistics.NewStatistics(testDB, FailuresUntilBlacklist, FailuresUntilAssumedOffline, false) queues := queue.NewOutgoingQueues( testDB, process.NewProcessContext(), false, @@ -92,7 +92,7 @@ func TestFederationClientQueryKeysBlacklisted(t *testing.T) { }, } fedClient := &testFedClient{} - stats := statistics.NewStatistics(testDB, FailuresUntilBlacklist, FailuresUntilAssumedOffline) + stats := statistics.NewStatistics(testDB, FailuresUntilBlacklist, FailuresUntilAssumedOffline, false) queues := queue.NewOutgoingQueues( testDB, process.NewProcessContext(), false, @@ -122,7 +122,7 @@ func TestFederationClientQueryKeysFailure(t *testing.T) { }, } fedClient := &testFedClient{shouldFail: true} - stats := statistics.NewStatistics(testDB, FailuresUntilBlacklist, FailuresUntilAssumedOffline) + stats := statistics.NewStatistics(testDB, FailuresUntilBlacklist, FailuresUntilAssumedOffline, false) queues := queue.NewOutgoingQueues( testDB, process.NewProcessContext(), false, @@ -152,7 +152,7 @@ func TestFederationClientClaimKeys(t *testing.T) { }, } fedClient := &testFedClient{} - stats := statistics.NewStatistics(testDB, FailuresUntilBlacklist, FailuresUntilAssumedOffline) + stats := statistics.NewStatistics(testDB, FailuresUntilBlacklist, FailuresUntilAssumedOffline, false) queues := queue.NewOutgoingQueues( testDB, process.NewProcessContext(), false, @@ -183,7 +183,7 @@ func TestFederationClientClaimKeysBlacklisted(t *testing.T) { }, } fedClient := &testFedClient{} - stats := statistics.NewStatistics(testDB, FailuresUntilBlacklist, FailuresUntilAssumedOffline) + stats := statistics.NewStatistics(testDB, FailuresUntilBlacklist, FailuresUntilAssumedOffline, false) queues := queue.NewOutgoingQueues( testDB, process.NewProcessContext(), false, diff --git a/federationapi/internal/perform_test.go b/federationapi/internal/perform_test.go index 2795a018a..82f9b9db1 100644 --- a/federationapi/internal/perform_test.go +++ b/federationapi/internal/perform_test.go @@ -66,7 +66,7 @@ func TestPerformWakeupServers(t *testing.T) { }, } fedClient := &testFedClient{} - stats := statistics.NewStatistics(testDB, FailuresUntilBlacklist, FailuresUntilAssumedOffline) + stats := statistics.NewStatistics(testDB, FailuresUntilBlacklist, FailuresUntilAssumedOffline, true) queues := queue.NewOutgoingQueues( testDB, process.NewProcessContext(), false, @@ -112,7 +112,7 @@ func TestQueryRelayServers(t *testing.T) { }, } fedClient := &testFedClient{} - stats := statistics.NewStatistics(testDB, FailuresUntilBlacklist, FailuresUntilAssumedOffline) + stats := statistics.NewStatistics(testDB, FailuresUntilBlacklist, FailuresUntilAssumedOffline, false) queues := queue.NewOutgoingQueues( testDB, process.NewProcessContext(), false, @@ -153,7 +153,7 @@ func TestRemoveRelayServers(t *testing.T) { }, } fedClient := &testFedClient{} - stats := statistics.NewStatistics(testDB, FailuresUntilBlacklist, FailuresUntilAssumedOffline) + stats := statistics.NewStatistics(testDB, FailuresUntilBlacklist, FailuresUntilAssumedOffline, false) queues := queue.NewOutgoingQueues( testDB, process.NewProcessContext(), false, @@ -193,7 +193,7 @@ func TestPerformDirectoryLookup(t *testing.T) { }, } fedClient := &testFedClient{} - stats := statistics.NewStatistics(testDB, FailuresUntilBlacklist, FailuresUntilAssumedOffline) + stats := statistics.NewStatistics(testDB, FailuresUntilBlacklist, FailuresUntilAssumedOffline, false) queues := queue.NewOutgoingQueues( testDB, process.NewProcessContext(), false, @@ -232,7 +232,7 @@ func TestPerformDirectoryLookupRelaying(t *testing.T) { }, } fedClient := &testFedClient{} - stats := statistics.NewStatistics(testDB, FailuresUntilBlacklist, FailuresUntilAssumedOffline) + stats := statistics.NewStatistics(testDB, FailuresUntilBlacklist, FailuresUntilAssumedOffline, true) queues := queue.NewOutgoingQueues( testDB, process.NewProcessContext(), false, diff --git a/federationapi/queue/queue_test.go b/federationapi/queue/queue_test.go index 73d3b0598..6da863427 100644 --- a/federationapi/queue/queue_test.go +++ b/federationapi/queue/queue_test.go @@ -117,7 +117,7 @@ func testSetup(failuresUntilBlacklist uint32, failuresUntilAssumedOffline uint32 txRelayCount: *atomic.NewUint32(0), } - stats := statistics.NewStatistics(db, failuresUntilBlacklist, failuresUntilAssumedOffline) + stats := statistics.NewStatistics(db, failuresUntilBlacklist, failuresUntilAssumedOffline, false) signingInfo := []*fclient.SigningIdentity{ { KeyID: "ed21019:auto", diff --git a/federationapi/statistics/statistics.go b/federationapi/statistics/statistics.go index e5fc4b940..e133fc9c9 100644 --- a/federationapi/statistics/statistics.go +++ b/federationapi/statistics/statistics.go @@ -34,12 +34,15 @@ type Statistics struct { // mark the destination as offline. At this point we should attempt // to send messages to the user's async relay servers if we know them. FailuresUntilAssumedOffline uint32 + + enableRelays bool } func NewStatistics( db storage.Database, failuresUntilBlacklist uint32, failuresUntilAssumedOffline uint32, + enableRelays bool, ) Statistics { return Statistics{ DB: db, @@ -47,6 +50,7 @@ func NewStatistics( FailuresUntilAssumedOffline: failuresUntilAssumedOffline, backoffTimers: make(map[spec.ServerName]*time.Timer), servers: make(map[spec.ServerName]*ServerStatistics), + enableRelays: enableRelays, } } @@ -73,6 +77,13 @@ func (s *Statistics) ForServer(serverName spec.ServerName) *ServerStatistics { } else { server.blacklisted.Store(blacklisted) } + + // Don't bother hitting the database 2 additional times + // if we don't want to use relays. + if !s.enableRelays { + return server + } + assumedOffline, err := s.DB.IsServerAssumedOffline(context.Background(), serverName) if err != nil { logrus.WithError(err).Errorf("Failed to get assumed offline entry %q", serverName) diff --git a/federationapi/statistics/statistics_test.go b/federationapi/statistics/statistics_test.go index a930bc3b0..4376a9050 100644 --- a/federationapi/statistics/statistics_test.go +++ b/federationapi/statistics/statistics_test.go @@ -16,7 +16,7 @@ const ( ) func TestBackoff(t *testing.T) { - stats := NewStatistics(nil, FailuresUntilBlacklist, FailuresUntilAssumedOffline) + stats := NewStatistics(nil, FailuresUntilBlacklist, FailuresUntilAssumedOffline, false) server := ServerStatistics{ statistics: &stats, serverName: "test.com", @@ -106,7 +106,7 @@ func TestBackoff(t *testing.T) { } func TestRelayServersListing(t *testing.T) { - stats := NewStatistics(test.NewInMemoryFederationDatabase(), FailuresUntilBlacklist, FailuresUntilAssumedOffline) + stats := NewStatistics(test.NewInMemoryFederationDatabase(), FailuresUntilBlacklist, FailuresUntilAssumedOffline, false) server := ServerStatistics{statistics: &stats} server.AddRelayServers([]spec.ServerName{"relay1", "relay1", "relay2"}) relayServers := server.KnownRelayServers() diff --git a/helm/dendrite/Chart.yaml b/helm/dendrite/Chart.yaml index e28261c8e..2ede0cfe9 100644 --- a/helm/dendrite/Chart.yaml +++ b/helm/dendrite/Chart.yaml @@ -1,9 +1,10 @@ apiVersion: v2 name: dendrite -version: "0.13.7" +version: "0.14.0" appVersion: "0.13.6" description: Dendrite Matrix Homeserver type: application +icon: https://avatars.githubusercontent.com/u/8418310?s=48&v=4 keywords: - matrix - chat @@ -13,7 +14,7 @@ home: https://github.com/matrix-org/dendrite sources: - https://github.com/matrix-org/dendrite dependencies: -- name: postgresql - version: 12.1.7 - repository: https://charts.bitnami.com/bitnami - condition: postgresql.enabled + - name: postgresql + version: 14.2.3 + repository: https://charts.bitnami.com/bitnami + condition: postgresql.enabled diff --git a/helm/dendrite/README.md b/helm/dendrite/README.md index feaa543dd..091f575f0 100644 --- a/helm/dendrite/README.md +++ b/helm/dendrite/README.md @@ -1,7 +1,7 @@ # dendrite -![Version: 0.13.7](https://img.shields.io/badge/Version-0.13.7-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.13.6](https://img.shields.io/badge/AppVersion-0.13.6-informational?style=flat-square) +![Version: 0.14.0](https://img.shields.io/badge/Version-0.14.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.13.6](https://img.shields.io/badge/AppVersion-0.13.6-informational?style=flat-square) Dendrite Matrix Homeserver Status: **NOT PRODUCTION READY** @@ -37,7 +37,7 @@ Create a folder `appservices` and place your configurations in there. The confi | Repository | Name | Version | |------------|------|---------| -| https://charts.bitnami.com/bitnami | postgresql | 12.1.7 | +| https://charts.bitnami.com/bitnami | postgresql | 14.2.3 | ## Values | Key | Type | Default | Description | @@ -48,16 +48,19 @@ Create a folder `appservices` and place your configurations in there. The confi | signing_key.create | bool | `true` | Create a new signing key, if not exists | | signing_key.existingSecret | string | `""` | Use an existing secret | | resources | object | sets some sane default values | Default resource requests/limits. | -| persistence.jetstream | object | `{"capacity":"1Gi","existingClaim":""}` | The storage class to use for volume claims. Used unless specified at the specific component. Defaults to the cluster default storage class. # If defined, storageClassName: # If set to "-", storageClassName: "", which disables dynamic provisioning # If undefined (the default) or set to null, no storageClassName spec is # set, choosing the default provisioner. (gp2 on AWS, standard on # GKE, AWS & OpenStack) # storageClass: "" | +| persistence.storageClass | string | `nil` | The storage class to use for volume claims. Used unless specified at the specific component. Defaults to the cluster default storage class. If defined, storageClassName: If set to "-", storageClassName: "", which disables dynamic provisioning If undefined (the default) or set to null, no storageClassName spec is set, choosing the default provisioner. (gp2 on AWS, standard on GKE, AWS & OpenStack) | | persistence.jetstream.existingClaim | string | `""` | Use an existing volume claim for jetstream | | persistence.jetstream.capacity | string | `"1Gi"` | PVC Storage Request for the jetstream volume | +| persistence.jetstream.storageClass | string | `nil` | The storage class to use for volume claims. Defaults to persistence.storageClass If defined, storageClassName: If set to "-", storageClassName: "", which disables dynamic provisioning If undefined (the default) or set to null, no storageClassName spec is set, choosing the default provisioner. (gp2 on AWS, standard on GKE, AWS & OpenStack) | | persistence.media.existingClaim | string | `""` | Use an existing volume claim for media files | | persistence.media.capacity | string | `"1Gi"` | PVC Storage Request for the media volume | +| persistence.media.storageClass | string | `nil` | The storage class to use for volume claims. Defaults to persistence.storageClass If defined, storageClassName: If set to "-", storageClassName: "", which disables dynamic provisioning If undefined (the default) or set to null, no storageClassName spec is set, choosing the default provisioner. (gp2 on AWS, standard on GKE, AWS & OpenStack) | | persistence.search.existingClaim | string | `""` | Use an existing volume claim for the fulltext search index | | persistence.search.capacity | string | `"1Gi"` | PVC Storage Request for the search volume | +| persistence.search.storageClass | string | `nil` | The storage class to use for volume claims. Defaults to persistence.storageClass If defined, storageClassName: If set to "-", storageClassName: "", which disables dynamic provisioning If undefined (the default) or set to null, no storageClassName spec is set, choosing the default provisioner. (gp2 on AWS, standard on GKE, AWS & OpenStack) | | extraVolumes | list | `[]` | Add additional volumes to the Dendrite Pod | | extraVolumeMounts | list | `[]` | Configure additional mount points volumes in the Dendrite Pod | -| strategy.type | string | `"RollingUpdate"` | Strategy to use for rolling updates (e.g. Recreate, RollingUpdate) If you are using ReadWriteOnce volumes, you should probably use Recreate | +| strategy.type | string | `"Recreate"` | Strategy to use for rolling updates (e.g. Recreate, RollingUpdate) If you are using ReadWriteOnce volumes, you should probably use Recreate | | strategy.rollingUpdate.maxUnavailable | string | `"25%"` | Maximum number of pods that can be unavailable during the update process | | strategy.rollingUpdate.maxSurge | string | `"25%"` | Maximum number of pods that can be scheduled above the desired number of pods | | dendrite_config.version | int | `2` | | @@ -139,7 +142,7 @@ Create a folder `appservices` and place your configurations in there. The confi | dendrite_config.logging | list | `[{"level":"info","type":"std"}]` | Default logging configuration | | postgresql.enabled | bool | See value.yaml | Enable and configure postgres as the database for dendrite. | | postgresql.image.repository | string | `"bitnami/postgresql"` | | -| postgresql.image.tag | string | `"15.1.0"` | | +| postgresql.image.tag | string | `"16.2.0"` | | | postgresql.auth.username | string | `"dendrite"` | | | postgresql.auth.password | string | `"changeme"` | | | postgresql.auth.database | string | `"dendrite"` | | @@ -186,3 +189,5 @@ grafana: ``` PS: The label `release=kube-prometheus-stack` is setup with the helmchart of the Prometheus Operator. For Grafana Dashboards it may be necessary to enable scanning in the correct namespaces (or ALL), enabled by `sidecar.dashboards.searchNamespace` in [Helmchart of grafana](https://artifacthub.io/packages/helm/grafana/grafana) (which is part of PrometheusOperator, so `grafana.sidecar.dashboards.searchNamespace`) +---------------------------------------------- +Autogenerated from chart metadata using [helm-docs v1.13.0](https://github.com/norwoodj/helm-docs/releases/v1.13.0) \ No newline at end of file diff --git a/helm/dendrite/templates/ingress.yaml b/helm/dendrite/templates/ingress.yaml index 4bcaee12d..eee762511 100644 --- a/helm/dendrite/templates/ingress.yaml +++ b/helm/dendrite/templates/ingress.yaml @@ -4,6 +4,7 @@ {{- $wellKnownServerHost := default $serverNameHost (regexFind "^(\\[.+\\])?[^:]*" .Values.dendrite_config.global.well_known_server_name) -}} {{- $wellKnownClientHost := default $serverNameHost (regexFind "//(\\[.+\\])?[^:/]*" .Values.dendrite_config.global.well_known_client_name | trimAll "/") -}} {{- $allHosts := list $serverNameHost $wellKnownServerHost $wellKnownClientHost | uniq -}} + {{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} apiVersion: networking.k8s.io/v1 {{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} @@ -56,7 +57,7 @@ spec: service: name: {{ $fullName }} port: - name: http + number: {{ $.Values.service.port }} {{- else }} serviceName: {{ $fullName }} servicePort: http @@ -72,7 +73,7 @@ spec: service: name: {{ $fullName }} port: - name: http + number: {{ $.Values.service.port }} {{- else }} serviceName: {{ $fullName }} servicePort: http @@ -88,7 +89,7 @@ spec: service: name: {{ $fullName }} port: - name: http + number: {{ $.Values.service.port }} {{- else }} serviceName: {{ $fullName }} servicePort: http @@ -105,7 +106,7 @@ spec: service: name: {{ $fullName }} port: - name: http + number: {{ $.Values.service.port }} {{- else }} serviceName: {{ $fullName }} servicePort: http diff --git a/helm/dendrite/templates/service.yaml b/helm/dendrite/templates/service.yaml index 3b571df1f..1b709c79c 100644 --- a/helm/dendrite/templates/service.yaml +++ b/helm/dendrite/templates/service.yaml @@ -14,4 +14,4 @@ spec: - name: http protocol: TCP port: {{ .Values.service.port }} - targetPort: 8008 \ No newline at end of file + targetPort: http \ No newline at end of file diff --git a/helm/dendrite/values.yaml b/helm/dendrite/values.yaml index afce1d930..6c5abc90e 100644 --- a/helm/dendrite/values.yaml +++ b/helm/dendrite/values.yaml @@ -26,13 +26,13 @@ persistence: # -- The storage class to use for volume claims. # Used unless specified at the specific component. # Defaults to the cluster default storage class. - ## If defined, storageClassName: - ## If set to "-", storageClassName: "", which disables dynamic provisioning - ## If undefined (the default) or set to null, no storageClassName spec is - ## set, choosing the default provisioner. (gp2 on AWS, standard on - ## GKE, AWS & OpenStack) - ## - # storageClass: "" + # If defined, storageClassName: + # If set to "-", storageClassName: "", which disables dynamic provisioning + # If undefined (the default) or set to null, no storageClassName spec is + # set, choosing the default provisioner. (gp2 on AWS, standard on + # GKE, AWS & OpenStack) + # + storageClass: jetstream: # -- Use an existing volume claim for jetstream existingClaim: "" @@ -40,13 +40,12 @@ persistence: capacity: "1Gi" # -- The storage class to use for volume claims. # Defaults to persistence.storageClass - ## If defined, storageClassName: - ## If set to "-", storageClassName: "", which disables dynamic provisioning - ## If undefined (the default) or set to null, no storageClassName spec is - ## set, choosing the default provisioner. (gp2 on AWS, standard on - ## GKE, AWS & OpenStack) - ## - # storageClass: "" + # If defined, storageClassName: + # If set to "-", storageClassName: "", which disables dynamic provisioning + # If undefined (the default) or set to null, no storageClassName spec is + # set, choosing the default provisioner. (gp2 on AWS, standard on + # GKE, AWS & OpenStack) + storageClass: media: # -- Use an existing volume claim for media files existingClaim: "" @@ -54,13 +53,12 @@ persistence: capacity: "1Gi" # -- The storage class to use for volume claims. # Defaults to persistence.storageClass - ## If defined, storageClassName: - ## If set to "-", storageClassName: "", which disables dynamic provisioning - ## If undefined (the default) or set to null, no storageClassName spec is - ## set, choosing the default provisioner. (gp2 on AWS, standard on - ## GKE, AWS & OpenStack) - ## - # storageClass: "" + # If defined, storageClassName: + # If set to "-", storageClassName: "", which disables dynamic provisioning + # If undefined (the default) or set to null, no storageClassName spec is + # set, choosing the default provisioner. (gp2 on AWS, standard on + # GKE, AWS & OpenStack) + storageClass: search: # -- Use an existing volume claim for the fulltext search index existingClaim: "" @@ -68,13 +66,12 @@ persistence: capacity: "1Gi" # -- The storage class to use for volume claims. # Defaults to persistence.storageClass - ## If defined, storageClassName: - ## If set to "-", storageClassName: "", which disables dynamic provisioning - ## If undefined (the default) or set to null, no storageClassName spec is - ## set, choosing the default provisioner. (gp2 on AWS, standard on - ## GKE, AWS & OpenStack) - ## - # storageClass: "" + # If defined, storageClassName: + # If set to "-", storageClassName: "", which disables dynamic provisioning + # If undefined (the default) or set to null, no storageClassName spec is + # set, choosing the default provisioner. (gp2 on AWS, standard on + # GKE, AWS & OpenStack) + storageClass: # -- Add additional volumes to the Dendrite Pod extraVolumes: [] @@ -92,7 +89,7 @@ extraVolumeMounts: [] strategy: # -- Strategy to use for rolling updates (e.g. Recreate, RollingUpdate) # If you are using ReadWriteOnce volumes, you should probably use Recreate - type: RollingUpdate + type: Recreate rollingUpdate: # -- Maximum number of pods that can be unavailable during the update process maxUnavailable: 25% @@ -378,7 +375,7 @@ postgresql: enabled: false image: repository: bitnami/postgresql - tag: "15.1.0" + tag: "16.2.0" auth: username: dendrite password: changeme diff --git a/roomserver/acls/acls.go b/roomserver/acls/acls.go index 660f4f3bb..017682e05 100644 --- a/roomserver/acls/acls.go +++ b/roomserver/acls/acls.go @@ -41,15 +41,21 @@ type ServerACLDatabase interface { } type ServerACLs struct { - acls map[string]*serverACL // room ID -> ACL - aclsMutex sync.RWMutex // protects the above + acls map[string]*serverACL // room ID -> ACL + aclsMutex sync.RWMutex // protects the above + aclRegexCache map[string]**regexp.Regexp // Cache from "serverName" -> pointer to a regex + aclRegexCacheMutex sync.RWMutex // protects the above } func NewServerACLs(db ServerACLDatabase) *ServerACLs { ctx := context.TODO() acls := &ServerACLs{ acls: make(map[string]*serverACL), + // Be generous when creating the cache, as in reality + // there are hundreds of servers in an ACL. + aclRegexCache: make(map[string]**regexp.Regexp, 100), } + // Look up all of the rooms that the current state server knows about. rooms, err := db.GetKnownRooms(ctx) if err != nil { @@ -67,6 +73,7 @@ func NewServerACLs(db ServerACLDatabase) *ServerACLs { for _, event := range events { acls.OnServerACLUpdate(event) } + return acls } @@ -78,8 +85,8 @@ type ServerACL struct { type serverACL struct { ServerACL - allowedRegexes []*regexp.Regexp - deniedRegexes []*regexp.Regexp + allowedRegexes []**regexp.Regexp + deniedRegexes []**regexp.Regexp } func compileACLRegex(orig string) (*regexp.Regexp, error) { @@ -89,6 +96,25 @@ func compileACLRegex(orig string) (*regexp.Regexp, error) { return regexp.Compile(escaped) } +// cachedCompileACLRegex is a wrapper around compileACLRegex with added caching +func (s *ServerACLs) cachedCompileACLRegex(orig string) (**regexp.Regexp, error) { + s.aclRegexCacheMutex.RLock() + re, ok := s.aclRegexCache[orig] + if ok { + s.aclRegexCacheMutex.RUnlock() + return re, nil + } + s.aclRegexCacheMutex.RUnlock() + compiled, err := compileACLRegex(orig) + if err != nil { + return nil, err + } + s.aclRegexCacheMutex.Lock() + defer s.aclRegexCacheMutex.Unlock() + s.aclRegexCache[orig] = &compiled + return &compiled, nil +} + func (s *ServerACLs) OnServerACLUpdate(strippedEvent tables.StrippedEvent) { acls := &serverACL{} if err := json.Unmarshal([]byte(strippedEvent.ContentValue), &acls.ServerACL); err != nil { @@ -100,14 +126,14 @@ func (s *ServerACLs) OnServerACLUpdate(strippedEvent tables.StrippedEvent) { // special characters and then replace * and ? with their regex counterparts. // https://matrix.org/docs/spec/client_server/r0.6.1#m-room-server-acl for _, orig := range acls.Allowed { - if expr, err := compileACLRegex(orig); err != nil { + if expr, err := s.cachedCompileACLRegex(orig); err != nil { logrus.WithError(err).Errorf("Failed to compile allowed regex") } else { acls.allowedRegexes = append(acls.allowedRegexes, expr) } } for _, orig := range acls.Denied { - if expr, err := compileACLRegex(orig); err != nil { + if expr, err := s.cachedCompileACLRegex(orig); err != nil { logrus.WithError(err).Errorf("Failed to compile denied regex") } else { acls.deniedRegexes = append(acls.deniedRegexes, expr) @@ -118,6 +144,11 @@ func (s *ServerACLs) OnServerACLUpdate(strippedEvent tables.StrippedEvent) { "num_allowed": len(acls.allowedRegexes), "num_denied": len(acls.deniedRegexes), }).Debugf("Updating server ACLs for %q", strippedEvent.RoomID) + + // Clear out Denied and Allowed, now that we have the compiled regexes. + // They are not needed anymore from this point on. + acls.Denied = nil + acls.Allowed = nil s.aclsMutex.Lock() defer s.aclsMutex.Unlock() s.acls[strippedEvent.RoomID] = acls @@ -150,14 +181,14 @@ func (s *ServerACLs) IsServerBannedFromRoom(serverName spec.ServerName, roomID s // Check if the hostname matches one of the denied regexes. If it does then // the server is banned from the room. for _, expr := range acls.deniedRegexes { - if expr.MatchString(string(serverName)) { + if (*expr).MatchString(string(serverName)) { return true } } // Check if the hostname matches one of the allowed regexes. If it does then // the server is NOT banned from the room. for _, expr := range acls.allowedRegexes { - if expr.MatchString(string(serverName)) { + if (*expr).MatchString(string(serverName)) { return false } } diff --git a/roomserver/acls/acls_test.go b/roomserver/acls/acls_test.go index 9fb6a5581..efe1d2093 100644 --- a/roomserver/acls/acls_test.go +++ b/roomserver/acls/acls_test.go @@ -15,8 +15,14 @@ package acls import ( + "context" "regexp" "testing" + + "github.com/matrix-org/dendrite/roomserver/storage/tables" + "github.com/matrix-org/gomatrixserverlib" + "github.com/matrix-org/gomatrixserverlib/spec" + "github.com/stretchr/testify/assert" ) func TestOpenACLsWithBlacklist(t *testing.T) { @@ -38,8 +44,8 @@ func TestOpenACLsWithBlacklist(t *testing.T) { ServerACL: ServerACL{ AllowIPLiterals: true, }, - allowedRegexes: []*regexp.Regexp{allowRegex}, - deniedRegexes: []*regexp.Regexp{denyRegex}, + allowedRegexes: []**regexp.Regexp{&allowRegex}, + deniedRegexes: []**regexp.Regexp{&denyRegex}, } if acls.IsServerBannedFromRoom("1.2.3.4", roomID) { @@ -77,8 +83,8 @@ func TestDefaultACLsWithWhitelist(t *testing.T) { ServerACL: ServerACL{ AllowIPLiterals: false, }, - allowedRegexes: []*regexp.Regexp{allowRegex}, - deniedRegexes: []*regexp.Regexp{}, + allowedRegexes: []**regexp.Regexp{&allowRegex}, + deniedRegexes: []**regexp.Regexp{}, } if !acls.IsServerBannedFromRoom("1.2.3.4", roomID) { @@ -103,3 +109,45 @@ func TestDefaultACLsWithWhitelist(t *testing.T) { t.Fatal("Expected qux.com:4567 to be allowed but wasn't") } } + +var ( + content1 = `{"allow":["*"],"allow_ip_literals":false,"deny":["hello.world", "*.hello.world"]}` +) + +type dummyACLDB struct{} + +func (d dummyACLDB) GetKnownRooms(ctx context.Context) ([]string, error) { + return []string{"1", "2"}, nil +} + +func (d dummyACLDB) GetBulkStateContent(ctx context.Context, roomIDs []string, tuples []gomatrixserverlib.StateKeyTuple, allowWildcards bool) ([]tables.StrippedEvent, error) { + return []tables.StrippedEvent{ + { + RoomID: "1", + ContentValue: content1, + }, + { + RoomID: "2", + ContentValue: content1, + }, + }, nil +} + +func TestCachedRegex(t *testing.T) { + db := dummyACLDB{} + wantBannedServer := spec.ServerName("hello.world") + + acls := NewServerACLs(db) + + // Check that hello.world is banned in room 1 + banned := acls.IsServerBannedFromRoom(wantBannedServer, "1") + assert.True(t, banned) + + // Check that hello.world is banned in room 2 + banned = acls.IsServerBannedFromRoom(wantBannedServer, "2") + assert.True(t, banned) + + // Check that matrix.hello.world is banned in room 2 + banned = acls.IsServerBannedFromRoom("matrix."+wantBannedServer, "2") + assert.True(t, banned) +} diff --git a/setup/config/config_federationapi.go b/setup/config/config_federationapi.go index a72eee369..073c46e03 100644 --- a/setup/config/config_federationapi.go +++ b/setup/config/config_federationapi.go @@ -18,6 +18,13 @@ type FederationAPI struct { // The default value is 16 if not specified, which is circa 18 hours. FederationMaxRetries uint32 `yaml:"send_max_retries"` + // P2P Feature: Whether relaying to specific nodes should be enabled. + // Defaults to false. + // Note: Enabling relays introduces a huge startup delay, if you are not using + // relays and have many servers to re-hydrate on start. Only enable this + // if you are using relays! + EnableRelays bool `yaml:"enable_relays"` + // P2P Feature: How many consecutive failures that we should tolerate when // sending federation requests to a specific server until we should assume they // are offline. If we assume they are offline then we will attempt to send