registry.go 2.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  1. // Copyright (C) 2019 The Syncthing Authors.
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this file,
  5. // You can obtain one at https://mozilla.org/MPL/2.0/.
  6. // Registry tracks connections/addresses on which we are listening on, to allow us to pick a connection/address that
  7. // has a NAT port mapping. This also makes our outgoing port stable and same as incoming port which should allow
  8. // better probability of punching through.
  9. package registry
  10. import (
  11. "strings"
  12. "github.com/syncthing/syncthing/lib/sync"
  13. )
  14. type Registry struct {
  15. mut sync.Mutex
  16. available map[string][]interface{}
  17. }
  18. func New() *Registry {
  19. return &Registry{
  20. mut: sync.NewMutex(),
  21. available: make(map[string][]interface{}),
  22. }
  23. }
  24. func (r *Registry) Register(scheme string, item interface{}) {
  25. r.mut.Lock()
  26. defer r.mut.Unlock()
  27. r.available[scheme] = append(r.available[scheme], item)
  28. }
  29. func (r *Registry) Unregister(scheme string, item interface{}) {
  30. r.mut.Lock()
  31. defer r.mut.Unlock()
  32. candidates := r.available[scheme]
  33. for i, existingItem := range candidates {
  34. if existingItem == item {
  35. candidates[i] = candidates[len(candidates)-1]
  36. candidates[len(candidates)-1] = nil
  37. r.available[scheme] = candidates[:len(candidates)-1]
  38. break
  39. }
  40. }
  41. }
  42. // Get returns an item for a schema compatible with the given scheme.
  43. // If any item satisfies preferred, that has precedence over other items.
  44. func (r *Registry) Get(scheme string, preferred func(interface{}) bool) interface{} {
  45. r.mut.Lock()
  46. defer r.mut.Unlock()
  47. var (
  48. best interface{}
  49. bestPref bool
  50. bestScheme string
  51. )
  52. for availableScheme, items := range r.available {
  53. // quic:// should be considered ok for both quic4:// and quic6://
  54. if !strings.HasPrefix(scheme, availableScheme) {
  55. continue
  56. }
  57. for _, item := range items {
  58. better := best == nil
  59. pref := preferred(item)
  60. if !better {
  61. // In case of a tie, prefer "quic" to "quic[46]" etc.
  62. better = pref &&
  63. (!bestPref || len(availableScheme) < len(bestScheme))
  64. }
  65. if !better {
  66. continue
  67. }
  68. best, bestPref, bestScheme = item, pref, availableScheme
  69. }
  70. }
  71. return best
  72. }