From 2738b45f5749b1c1e05692a1b19743aeead79cc4 Mon Sep 17 00:00:00 2001 From: sbruens Date: Fri, 6 Sep 2024 12:52:38 -0400 Subject: [PATCH] Move the service into `shadowsocks.go`. --- cmd/outline-ss-server/main.go | 4 +- service/service.go | 123 ---------------------------------- service/shadowsocks.go | 112 ++++++++++++++++++++++++++++++- 3 files changed, 113 insertions(+), 126 deletions(-) delete mode 100644 service/service.go diff --git a/cmd/outline-ss-server/main.go b/cmd/outline-ss-server/main.go index 6fd84dfa..543d036d 100644 --- a/cmd/outline-ss-server/main.go +++ b/cmd/outline-ss-server/main.go @@ -217,7 +217,7 @@ func (s *SSServer) runConfig(config Config) (func() error, error) { ciphers := service.NewCipherList() ciphers.Update(cipherList) - ssService, err := service.NewService( + ssService, err := service.NewShadowsocksService( service.WithCiphers(ciphers), service.WithNatTimeout(s.natTimeout), service.WithMetrics(s.serviceMetrics), @@ -243,7 +243,7 @@ func (s *SSServer) runConfig(config Config) (func() error, error) { if err != nil { return fmt.Errorf("failed to create cipher list from config: %v", err) } - ssService, err := service.NewService( + ssService, err := service.NewShadowsocksService( service.WithCiphers(ciphers), service.WithNatTimeout(s.natTimeout), service.WithMetrics(s.serviceMetrics), diff --git a/service/service.go b/service/service.go deleted file mode 100644 index 065fc22d..00000000 --- a/service/service.go +++ /dev/null @@ -1,123 +0,0 @@ -// 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. - -package service - -import ( - "context" - "net" - "time" - - "github.com/Jigsaw-Code/outline-sdk/transport" -) - -const ( - // 59 seconds is most common timeout for servers that do not respond to invalid requests - tcpReadTimeout time.Duration = 59 * time.Second - - // A UDP NAT timeout of at least 5 minutes is recommended in RFC 4787 Section 4.3. - defaultNatTimeout time.Duration = 5 * time.Minute -) - -type ServiceMetrics interface { - UDPMetrics - AddOpenTCPConnection(conn net.Conn) TCPConnMetrics - AddCipherSearch(proto string, accessKeyFound bool, timeToCipher time.Duration) -} - -type Service interface { - HandleStream(ctx context.Context, conn transport.StreamConn) - HandlePacket(conn net.PacketConn) -} - -// Option user's option. -type Option func(s *ssService) - -type ssService struct { - m ServiceMetrics - ciphers CipherList - natTimeout time.Duration - replayCache *ReplayCache - - sh StreamHandler - ph PacketHandler -} - -func NewService(opts ...Option) (Service, error) { - s := &ssService{} - - for _, opt := range opts { - opt(s) - } - - if s.natTimeout == 0 { - s.natTimeout = defaultNatTimeout - } - return s, nil -} - -// WithCiphers option function. -func WithCiphers(ciphers CipherList) Option { - return func(s *ssService) { - s.ciphers = ciphers - } -} - -// WithMetrics option function. -func WithMetrics(metrics ServiceMetrics) Option { - return func(s *ssService) { - s.m = metrics - } -} - -// WithReplayCache option function. -func WithReplayCache(replayCache *ReplayCache) Option { - return func(s *ssService) { - s.replayCache = replayCache - } -} - -func WithNatTimeout(natTimeout time.Duration) Option { - return func(s *ssService) { - s.natTimeout = natTimeout - } -} - -func (s *ssService) HandleStream(ctx context.Context, conn transport.StreamConn) { - if s.sh == nil { - authFunc := NewShadowsocksStreamAuthenticator(s.ciphers, s.replayCache, &ssConnMetrics{ServiceMetrics: s.m, proto: "tcp"}) - // TODO: Register initial data metrics at zero. - s.sh = NewStreamHandler(authFunc, tcpReadTimeout) - } - connMetrics := s.m.AddOpenTCPConnection(conn) - s.sh.Handle(ctx, conn, connMetrics) -} - -func (s *ssService) HandlePacket(conn net.PacketConn) { - if s.ph == nil { - s.ph = NewPacketHandler(s.natTimeout, s.ciphers, s.m, &ssConnMetrics{ServiceMetrics: s.m, proto: "udp"}) - } - s.ph.Handle(conn) -} - -type ssConnMetrics struct { - ServiceMetrics - proto string -} - -var _ ShadowsocksConnMetrics = (*ssConnMetrics)(nil) - -func (cm *ssConnMetrics) AddCipherSearch(accessKeyFound bool, timeToCipher time.Duration) { - cm.ServiceMetrics.AddCipherSearch(cm.proto, accessKeyFound, timeToCipher) -} diff --git a/service/shadowsocks.go b/service/shadowsocks.go index 97329c3a..87814df8 100644 --- a/service/shadowsocks.go +++ b/service/shadowsocks.go @@ -14,9 +14,119 @@ package service -import "time" +import ( + "context" + "net" + "time" + + "github.com/Jigsaw-Code/outline-sdk/transport" +) + +const ( + // 59 seconds is most common timeout for servers that do not respond to invalid requests + tcpReadTimeout time.Duration = 59 * time.Second + + // A UDP NAT timeout of at least 5 minutes is recommended in RFC 4787 Section 4.3. + defaultNatTimeout time.Duration = 5 * time.Minute +) // ShadowsocksConnMetrics is used to report Shadowsocks related metrics on connections. type ShadowsocksConnMetrics interface { AddCipherSearch(accessKeyFound bool, timeToCipher time.Duration) } + +type ServiceMetrics interface { + UDPMetrics + AddOpenTCPConnection(conn net.Conn) TCPConnMetrics + AddCipherSearch(proto string, accessKeyFound bool, timeToCipher time.Duration) +} + +type Service interface { + HandleStream(ctx context.Context, conn transport.StreamConn) + HandlePacket(conn net.PacketConn) +} + +// Option is a Shadowsocks service constructor option. +type Option func(s *ssService) + +type ssService struct { + m ServiceMetrics + ciphers CipherList + natTimeout time.Duration + replayCache *ReplayCache + + sh StreamHandler + ph PacketHandler +} + +// NewShadowsocksService creates a new service +func NewShadowsocksService(opts ...Option) (Service, error) { + s := &ssService{} + + for _, opt := range opts { + opt(s) + } + + if s.natTimeout == 0 { + s.natTimeout = defaultNatTimeout + } + return s, nil +} + +// WithCiphers option function. +func WithCiphers(ciphers CipherList) Option { + return func(s *ssService) { + s.ciphers = ciphers + } +} + +// WithMetrics option function. +func WithMetrics(metrics ServiceMetrics) Option { + return func(s *ssService) { + s.m = metrics + } +} + +// WithReplayCache option function. +func WithReplayCache(replayCache *ReplayCache) Option { + return func(s *ssService) { + s.replayCache = replayCache + } +} + +// WithNatTimeout option function. +func WithNatTimeout(natTimeout time.Duration) Option { + return func(s *ssService) { + s.natTimeout = natTimeout + } +} + +// HandleStream handles a Shadowsocks stream-based connection. +func (s *ssService) HandleStream(ctx context.Context, conn transport.StreamConn) { + if s.sh == nil { + authFunc := NewShadowsocksStreamAuthenticator(s.ciphers, s.replayCache, &ssConnMetrics{ServiceMetrics: s.m, proto: "tcp"}) + // TODO: Register initial data metrics at zero. + s.sh = NewStreamHandler(authFunc, tcpReadTimeout) + } + connMetrics := s.m.AddOpenTCPConnection(conn) + s.sh.Handle(ctx, conn, connMetrics) +} + +// HandlePacket handles a Shadowsocks packet connection. +func (s *ssService) HandlePacket(conn net.PacketConn) { + if s.ph == nil { + s.ph = NewPacketHandler(s.natTimeout, s.ciphers, s.m, &ssConnMetrics{ServiceMetrics: s.m, proto: "udp"}) + } + s.ph.Handle(conn) +} + +type ssConnMetrics struct { + ServiceMetrics + proto string +} + +var _ ShadowsocksConnMetrics = (*ssConnMetrics)(nil) + +func (cm *ssConnMetrics) AddCipherSearch(accessKeyFound bool, timeToCipher time.Duration) { + cm.ServiceMetrics.AddCipherSearch(cm.proto, accessKeyFound, timeToCipher) +}