diff --git a/cmd/outline-ss-server/config_example_source.yml b/cmd/outline-ss-server/config_example_source.yml new file mode 100644 index 00000000..bc2a299b --- /dev/null +++ b/cmd/outline-ss-server/config_example_source.yml @@ -0,0 +1,42 @@ +# Copyright 2024 The Outline Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +services: + - listeners: + # TODO(sbruens): Allow a string-based listener config, as a convenient short-form + # to create a direct listener, e.g. `- tcp/[::]:9000`. + - type: tcp + address: "[::]:9000" + - type: udp + address: "[::]:9000" + # keys: + # - id: user-0 + # cipher: chacha20-ietf-poly1305 + # secret: Secret0 + # - id: user-1 + # cipher: chacha20-ietf-poly1305 + # secret: Secret1 + source: + url: "file://config_example.deprecated.yml" + + + - listeners: + - type: tcp + address: "[::]:9001" + - type: udp + address: "[::]:9001" + keys: + - id: user-2 + cipher: chacha20-ietf-poly1305 + secret: Secret2 diff --git a/cmd/outline-ss-server/main.go b/cmd/outline-ss-server/main.go index ee845837..71947192 100644 --- a/cmd/outline-ss-server/main.go +++ b/cmd/outline-ss-server/main.go @@ -23,14 +23,12 @@ import ( "net/http" "os" "os/signal" - "strconv" "sync" "syscall" "time" "github.com/Jigsaw-Code/outline-sdk/transport/shadowsocks" "github.com/Jigsaw-Code/outline-ss-server/ipinfo" - "github.com/Jigsaw-Code/outline-ss-server/key" outline_prometheus "github.com/Jigsaw-Code/outline-ss-server/prometheus" "github.com/Jigsaw-Code/outline-ss-server/service" "github.com/lmittmann/tint" @@ -67,107 +65,6 @@ type OutlineServer struct { replayCache service.ReplayCache } -type CipherUpdater struct { - Ciphers service.CipherList - cond *sync.Cond - ciphersByID map[string]*service.CipherEntry -} - -func (c *CipherUpdater) AddKey(key key.Key) error { - // Wait until Ciphers are initialized. - c.cond.L.Lock() - for c.Ciphers == nil { - c.cond.Wait() - } - c.cond.L.Unlock() - cryptoKey, err := shadowsocks.NewEncryptionKey(key.Cipher, key.Secret) - if err != nil { - return fmt.Errorf("failed to create encyption key for key %v: %w", key.ID, err) - } - entry := service.MakeCipherEntry(key.ID, cryptoKey, key.Secret) - slog.Info("Added key ", "keyID", key.ID) - c.Ciphers.AddEntry(&entry) - // Store the entry in a map for fast removal - c.ciphersByID[key.ID] = &entry - return nil -} - -func (c *CipherUpdater) RemoveKey(key key.Key) error { - if c.Ciphers == nil { - return fmt.Errorf("no Cipher available while removing key %v", key.ID) - } - entry, exists := c.ciphersByID[key.ID] - if exists { - c.Ciphers.RemoveEntry(entry) - return nil - } else { - return fmt.Errorf("key %v was not found", key.ID) - } -} - -func (c *CipherUpdater) AddCipher(ciphers service.CipherList) { - c.cond.L.Lock() - if c.Ciphers == nil { - c.Ciphers = ciphers - c.cond.Broadcast() // Notify all waiting goroutines - } - c.cond.L.Unlock() -} - -func (s *OutlineServer) loadSource(filename string) error { - file_source := key.NewFileSource(filename) - var config *Config - var wg sync.WaitGroup - wg.Add(1) - updater := &CipherUpdater{ - cond: sync.NewCond(&sync.Mutex{}), - ciphersByID: make(map[string]*service.CipherEntry), - } - go func() { - for cmd := range file_source.Channel() { - switch cmd.Action { - case key.AddAction: - if config == nil { - config = &Config{ - Services: []ServiceConfig{ - ServiceConfig{ - Listeners: []ListenerConfig{ - ListenerConfig{Type: listenerTypeTCP, Address: "[::]:" + strconv.Itoa(cmd.Key.Port)}, - ListenerConfig{Type: listenerTypeUDP, Address: "[::]:" + strconv.Itoa(cmd.Key.Port)}, - }, - Keys: []KeyConfig{ - KeyConfig{cmd.Key.ID, cmd.Key.Cipher, cmd.Key.Secret}, - }, - }, - }, - } - wg.Done() - } else { - updater.AddKey(cmd.Key) - } - case key.RemoveAction: - updater.RemoveKey(cmd.Key) - } - } - }() - // Wait until at least we have one key in the config. - wg.Wait() - - // We hot swap the config by having the old and new listeners both live at - // the same time. This means we create listeners for the new config first, - // and then close the old ones after. - stopConfig, err := s.runConfig(*config, updater) - if err != nil { - return err - } - - if err := s.Stop(); err != nil { - slog.Warn("Failed to stop old config.", "err", err) - } - s.stopConfig = stopConfig - return nil -} - func (s *OutlineServer) loadConfig(filename string) error { configData, err := os.ReadFile(filename) if err != nil { @@ -184,7 +81,7 @@ func (s *OutlineServer) loadConfig(filename string) error { // We hot swap the config by having the old and new listeners both live at // the same time. This means we create listeners for the new config first, // and then close the old ones after. - stopConfig, err := s.runConfig(*config, nil) + stopConfig, err := s.runConfig(*config) if err != nil { return err } @@ -219,7 +116,8 @@ func newCipherListFromConfig(config ServiceConfig) (service.CipherList, error) { ciphers := service.NewCipherList() ciphers.Update(cipherList) - config.Source.Register(ciphers) + slog.Info("newCipherListFromConfig with config", "config", config) + config.Source.Register(ciphers, slog.Default()) return ciphers, nil } @@ -285,7 +183,7 @@ func (ls *listenerSet) Len() int { return len(ls.listenerCloseFuncs) } -func (s *OutlineServer) runConfig(config Config, updater *CipherUpdater) (func() error, error) { +func (s *OutlineServer) runConfig(config Config) (func() error, error) { startErrCh := make(chan error) stopErrCh := make(chan error) stopCh := make(chan struct{}) @@ -322,9 +220,6 @@ func (s *OutlineServer) runConfig(config Config, updater *CipherUpdater) (func() addr := fmt.Sprintf(":%d", portNum) ciphers := service.NewCipherList() - if updater != nil { - updater.AddCipher(ciphers) - } ciphers.Update(cipherList) ssService, err := service.NewShadowsocksService( service.WithCiphers(ciphers), @@ -351,9 +246,6 @@ func (s *OutlineServer) runConfig(config Config, updater *CipherUpdater) (func() } for _, serviceConfig := range config.Services { ciphers, err := newCipherListFromConfig(serviceConfig) - if updater != nil { - updater.AddCipher(ciphers) - } if err != nil { return fmt.Errorf("failed to create cipher list from config: %v", err) } diff --git a/cmd/outline-ss-server/server_test.go b/cmd/outline-ss-server/server_test.go index 3e41456f..223cb5c1 100644 --- a/cmd/outline-ss-server/server_test.go +++ b/cmd/outline-ss-server/server_test.go @@ -35,3 +35,18 @@ func TestRunOutlineServer(t *testing.T) { t.Errorf("Error while stopping server: %v", err) } } + +func TestRunOutlineServerWithSource(t *testing.T) { + serverMetrics := newPrometheusServerMetrics() + serviceMetrics, err := prometheus.NewServiceMetrics(nil) + if err != nil { + t.Fatalf("Failed to create Prometheus service metrics: %v", err) + } + server, err := RunOutlineServer("config_example_source.yml", 30*time.Second, serverMetrics, serviceMetrics, 10000) + if err != nil { + t.Fatalf("RunOutlineServer() error = %v", err) + } + if err := server.Stop(); err != nil { + t.Errorf("Error while stopping server: %v", err) + } +} diff --git a/cmd/outline-ss-server/source.go b/cmd/outline-ss-server/source.go index bd85bffb..74413182 100644 --- a/cmd/outline-ss-server/source.go +++ b/cmd/outline-ss-server/source.go @@ -11,8 +11,7 @@ import ( ) type Source struct { - Url string // URL of the key source, e.g. "file://path/to/keys" - updater KeyUpdater + Url string // URL of the key source, e.g. "file://path/to/keys" } type KeyUpdater struct { @@ -46,19 +45,21 @@ func (c *KeyUpdater) RemoveKey(key key.Key) error { } } -func (s *Source) Register(c service.CipherList) { +func (s *Source) Register(c service.CipherList, logger *slog.Logger) { if strings.HasPrefix(s.Url, "file://") { - newFileUpdater(c, key.NewFileSource(s.Url[7:])).Listen() + newFileUpdater(c, key.NewFileSource(s.Url[7:]), logger).Listen() } } type FileUpdater struct { KeyUpdater fileSource key.Source + logger *slog.Logger } -func newFileUpdater(c service.CipherList, s key.Source) *FileUpdater { +func newFileUpdater(c service.CipherList, s key.Source, logger *slog.Logger) *FileUpdater { fu := &FileUpdater{ + logger: logger, fileSource: s, } fu.KeyUpdater.Ciphers = c @@ -72,8 +73,10 @@ func (fu *FileUpdater) Listen() { switch cmd.Action { case key.AddAction: fu.KeyUpdater.AddKey(cmd.Key) + fu.logger.Info("Added key ", "keyID", cmd.Key.ID) case key.RemoveAction: fu.KeyUpdater.RemoveKey(cmd.Key) + fu.logger.Info("Removed key ", "keyID", cmd.Key.ID) } } }()