Skip to content

Commit

Permalink
Merge branch 'master' into sbruens/replaycache
Browse files Browse the repository at this point in the history
  • Loading branch information
sbruens committed Oct 7, 2024
2 parents a13ab28 + d240aa1 commit be65ae3
Show file tree
Hide file tree
Showing 12 changed files with 130 additions and 67 deletions.
6 changes: 4 additions & 2 deletions caddy/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@ go 1.23

require (
github.com/Jigsaw-Code/outline-sdk v0.0.16
github.com/Jigsaw-Code/outline-ss-server v1.6.0
github.com/Jigsaw-Code/outline-ss-server v1.7.2
github.com/caddyserver/caddy/v2 v2.8.4
github.com/mholt/caddy-l4 v0.0.0-20240812213304-afa78d72257b
github.com/prometheus/client_golang v1.20.0
go.uber.org/zap v1.27.0
)

require (
Expand Down Expand Up @@ -58,6 +57,7 @@ require (
github.com/klauspost/compress v1.17.9 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/libdns/libdns v0.2.2 // indirect
github.com/lmittmann/tint v1.0.5 // indirect
github.com/manifoldco/promptui v0.9.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
Expand All @@ -69,6 +69,7 @@ require (
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/onsi/ginkgo/v2 v2.15.0 // indirect
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 // indirect
github.com/oschwald/geoip2-golang v1.8.0 // indirect
github.com/oschwald/maxminddb-golang v1.10.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
Expand Down Expand Up @@ -102,6 +103,7 @@ require (
go.uber.org/automaxprocs v1.5.3 // indirect
go.uber.org/mock v0.4.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
go.uber.org/zap/exp v0.2.0 // indirect
golang.org/x/crypto v0.24.0 // indirect
golang.org/x/crypto/x509roots/fallback v0.0.0-20240507223354-67b13616a595 // indirect
Expand Down
6 changes: 6 additions & 0 deletions caddy/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/Jigsaw-Code/outline-sdk v0.0.16 h1:WbHmv80FKDIpzEmR3GehTbq5CibYTLvcxIIpMMILiEs=
github.com/Jigsaw-Code/outline-sdk v0.0.16/go.mod h1:e1oQZbSdLJBBuHgfeQsgEkvkuyIePPwstUeZRGq0KO8=
github.com/Jigsaw-Code/outline-ss-server v1.7.1 h1:KLrolmZZfuBx48GM4XblH0XoTK+wMWsEbx/QDZOuibs=
github.com/Jigsaw-Code/outline-ss-server v1.7.1/go.mod h1:cKPicPWlLWZKJfkQ3CBpQm8a3gXrA2+dpQvsECqBVz8=
github.com/Jigsaw-Code/outline-ss-server v1.7.2 h1:A//m3KNsguZwhI6AJyFl0Gj8SpwZM7cy+FPoxCNzz28=
github.com/Jigsaw-Code/outline-ss-server v1.7.2/go.mod h1:cKPicPWlLWZKJfkQ3CBpQm8a3gXrA2+dpQvsECqBVz8=
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
Expand Down Expand Up @@ -269,6 +273,8 @@ github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/libdns/libdns v0.2.2 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s=
github.com/libdns/libdns v0.2.2/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
github.com/lmittmann/tint v1.0.5 h1:NQclAutOfYsqs2F1Lenue6OoWCajs5wJcP3DfWVpePw=
github.com/lmittmann/tint v1.0.5/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
Expand Down
28 changes: 28 additions & 0 deletions caddy/module.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// 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 caddy

import (
"github.com/caddyserver/caddy/v2"
)

type ModuleRegistration caddy.ModuleInfo

var _ caddy.Module = (*ModuleRegistration)(nil)

// CaddyModule implements the caddy.Module interface
func (m ModuleRegistration) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo(m)
}
8 changes: 6 additions & 2 deletions cmd/outline-ss-server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import (
"net/http"
"os"
"os/signal"
"strconv"
"sync"
"syscall"
"time"
Expand Down Expand Up @@ -212,7 +211,10 @@ func (s *OutlineServer) runConfig(config Config) (func() error, error) {
cipherList.PushBack(&entry)
}
for portNum, cipherList := range portCiphers {
addr := net.JoinHostPort("::", strconv.Itoa(portNum))
// NOTE: We explicitly construct the address string with only the port
// number. This will result in an address that listens on all available
// network interfaces (both IPv4 and IPv6).
addr := fmt.Sprintf(":%d", portNum)

ciphers := service.NewCipherList()
ciphers.Update(cipherList)
Expand All @@ -223,6 +225,7 @@ func (s *OutlineServer) runConfig(config Config) (func() error, error) {
service.WithNatTimeout(s.natTimeout),
service.WithMetrics(s.serviceMetrics),
service.WithReplayCache(&s.replayCache),
service.WithLogger(slog.Default()),
)
ln, err := lnSet.ListenStream(addr)
if err != nil {
Expand Down Expand Up @@ -250,6 +253,7 @@ func (s *OutlineServer) runConfig(config Config) (func() error, error) {
service.WithNatTimeout(s.natTimeout),
service.WithMetrics(s.serviceMetrics),
service.WithReplayCache(&s.replayCache),
service.WithLogger(slog.Default()),
)
if err != nil {
return err
Expand Down
4 changes: 2 additions & 2 deletions service/listeners.go
Original file line number Diff line number Diff line change
Expand Up @@ -288,10 +288,10 @@ func (m *multiPacketListener) Acquire() (net.PacketConn, error) {
buffer := make([]byte, serverUDPBufferSize)
for {
n, addr, err := m.pc.ReadFrom(buffer)
buffer = buffer[:n]
pkt := buffer[:n]
select {
case req := <-m.readCh:
n := copy(req.buffer, buffer)
n := copy(req.buffer, pkt)
req.respCh <- struct {
n int
addr net.Addr
Expand Down
27 changes: 27 additions & 0 deletions service/listeners_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,33 @@ func TestListenerManagerPacketListenerCreatesListenerOnDemand(t *testing.T) {
<-done
}

func TestMultiPacketListener_SequentialReads(t *testing.T) {
m := NewListenerManager()
conn, err := m.ListenPacket("127.0.0.1:0")
require.NoError(t, err)
udpConn, err := net.Dial("udp", conn.LocalAddr().String())
require.NoError(t, err)

// Send and receive the first packet.
data1 := []byte("hello")
_, err = udpConn.Write(data1)
require.NoError(t, err)
received1 := make([]byte, serverUDPBufferSize)
n1, _, err := conn.ReadFrom(received1)
require.NoError(t, err)

// Send and receive a second larger packet.
data2 := []byte("a longer message than the first one")
_, err = udpConn.Write(data2)
require.NoError(t, err)
received2 := make([]byte, serverUDPBufferSize)
n2, _, err := conn.ReadFrom(received2)
require.NoError(t, err)

require.Equal(t, string(data1), string(received1[:n1]))
require.Equal(t, string(data2), string(received2[:n2]))
}

func BenchmarkMultiStreamListener_Acquire(b *testing.B) {
lm := NewListenerManager()

Expand Down
21 changes: 5 additions & 16 deletions service/logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,12 @@
package service

import (
"context"
"io"
"log/slog"
"math"
)

type Logger interface {
Enabled(ctx context.Context, level slog.Level) bool
LogAttrs(ctx context.Context, level slog.Level, msg string, attrs ...slog.Attr)
}

type noopLogger struct {
}

var _ Logger = (*noopLogger)(nil)

func (l *noopLogger) Enabled(ctx context.Context, level slog.Level) bool {
return false
}

func (l *noopLogger) LogAttrs(ctx context.Context, level slog.Level, msg string, attrs ...slog.Attr) {
func noopLogger() *slog.Logger {
// TODO: Use built-in no-op log level when available: https://go.dev/issue/62005
return slog.New(slog.NewTextHandler(io.Discard, &slog.HandlerOptions{Level: slog.Level(math.MaxInt)}))
}
7 changes: 4 additions & 3 deletions service/shadowsocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package service

import (
"context"
"log/slog"
"net"
"time"

Expand Down Expand Up @@ -50,7 +51,7 @@ type Service interface {
type Option func(s *ssService)

type ssService struct {
logger Logger
logger *slog.Logger
metrics ServiceMetrics
ciphers CipherList
natTimeout time.Duration
Expand All @@ -74,7 +75,7 @@ func NewShadowsocksService(opts ...Option) (Service, error) {
}
// If no logger is provided via options, use a noop logger.
if s.logger == nil {
s.logger = &noopLogger{}
s.logger = noopLogger()
}

// TODO: Register initial data metrics at zero.
Expand All @@ -92,7 +93,7 @@ func NewShadowsocksService(opts ...Option) (Service, error) {

// WithLogger can be used to provide a custom log target. If not provided,
// the service uses a noop logger (i.e., no logging).
func WithLogger(l Logger) Option {
func WithLogger(l *slog.Logger) Option {
return func(s *ssService) {
s.logger = l
}
Expand Down
37 changes: 20 additions & 17 deletions service/tcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,10 @@ func remoteIP(conn net.Conn) netip.Addr {
}

// Wrapper for slog.Debug during TCP access key searches.
func debugTCP(l Logger, template string, cipherID string, attr slog.Attr) {
func debugTCP(l *slog.Logger, template string, cipherID string, attr slog.Attr) {
// This is an optimization to reduce unnecessary allocations due to an interaction
// between Go's inlining/escape analysis and varargs functions like slog.Debug.
if l != nil && l.Enabled(nil, slog.LevelDebug) {
if l.Enabled(nil, slog.LevelDebug) {
l.LogAttrs(nil, slog.LevelDebug, fmt.Sprintf("TCP: %s", template), slog.String("ID", cipherID), attr)
}
}
Expand All @@ -72,7 +72,7 @@ func debugTCP(l Logger, template string, cipherID string, attr slog.Attr) {
// required = saltSize + 2 + cipher.TagSize, the number of bytes needed to authenticate the connection.
const bytesForKeyFinding = 50

func findAccessKey(clientReader io.Reader, clientIP netip.Addr, cipherList CipherList, l Logger) (*CipherEntry, io.Reader, []byte, time.Duration, error) {
func findAccessKey(clientReader io.Reader, clientIP netip.Addr, cipherList CipherList, l *slog.Logger) (*CipherEntry, io.Reader, []byte, time.Duration, error) {
// We snapshot the list because it may be modified while we use it.
ciphers := cipherList.SnapshotForClientIP(clientIP)
firstBytes := make([]byte, bytesForKeyFinding)
Expand All @@ -95,7 +95,7 @@ func findAccessKey(clientReader io.Reader, clientIP netip.Addr, cipherList Ciphe
}

// Implements a trial decryption search. This assumes that all ciphers are AEAD.
func findEntry(firstBytes []byte, ciphers []*list.Element, l Logger) (*CipherEntry, *list.Element) {
func findEntry(firstBytes []byte, ciphers []*list.Element, l *slog.Logger) (*CipherEntry, *list.Element) {
// To hold the decrypted chunk length.
chunkLenBuf := [2]byte{}
for ci, elt := range ciphers {
Expand All @@ -116,12 +116,12 @@ type StreamAuthenticateFunc func(clientConn transport.StreamConn) (string, trans

// NewShadowsocksStreamAuthenticator creates a stream authenticator that uses Shadowsocks.
// TODO(fortuna): Offer alternative transports.
func NewShadowsocksStreamAuthenticator(ciphers CipherList, replayCache *ReplayCache, metrics ShadowsocksConnMetrics, l Logger) StreamAuthenticateFunc {
func NewShadowsocksStreamAuthenticator(ciphers CipherList, replayCache *ReplayCache, metrics ShadowsocksConnMetrics, l *slog.Logger) StreamAuthenticateFunc {
if metrics == nil {
metrics = &NoOpShadowsocksConnMetrics{}
}
if l == nil {
l = &noopLogger{}
l = noopLogger()
}
return func(clientConn transport.StreamConn) (string, transport.StreamConn, *onet.ConnectionError) {
// Find the cipher and acess key id.
Expand Down Expand Up @@ -157,7 +157,7 @@ func NewShadowsocksStreamAuthenticator(ciphers CipherList, replayCache *ReplayCa
}

type streamHandler struct {
l Logger
logger *slog.Logger
listenerId string
readTimeout time.Duration
authenticate StreamAuthenticateFunc
Expand All @@ -167,7 +167,7 @@ type streamHandler struct {
// NewStreamHandler creates a StreamHandler
func NewStreamHandler(authenticate StreamAuthenticateFunc, timeout time.Duration) StreamHandler {
return &streamHandler{
l: &noopLogger{},
logger: noopLogger(),
readTimeout: timeout,
authenticate: authenticate,
dialer: defaultDialer,
Expand All @@ -186,14 +186,17 @@ func makeValidatingTCPStreamDialer(targetIPValidator onet.TargetIPValidator) tra
// StreamHandler is a handler that handles stream connections.
type StreamHandler interface {
Handle(ctx context.Context, conn transport.StreamConn, connMetrics TCPConnMetrics)
// SetLogger sets the logger used to log messages.
SetLogger(l Logger)
// SetLogger sets the logger used to log messages. Uses a no-op logger if nil.
SetLogger(l *slog.Logger)
// SetTargetDialer sets the [transport.StreamDialer] to be used to connect to target addresses.
SetTargetDialer(dialer transport.StreamDialer)
}

func (s *streamHandler) SetLogger(l Logger) {
s.l = l
func (s *streamHandler) SetLogger(l *slog.Logger) {
if l == nil {
l = noopLogger()
}
s.logger = l
}

func (s *streamHandler) SetTargetDialer(dialer transport.StreamDialer) {
Expand Down Expand Up @@ -268,11 +271,11 @@ func (h *streamHandler) Handle(ctx context.Context, clientConn transport.StreamC
status := "OK"
if connError != nil {
status = connError.Status
h.l.LogAttrs(nil, slog.LevelDebug, "TCP: Error", slog.String("msg", connError.Message), slog.Any("cause", connError.Cause))
h.logger.LogAttrs(nil, slog.LevelDebug, "TCP: Error", slog.String("msg", connError.Message), slog.Any("cause", connError.Cause))
}
connMetrics.AddClosed(status, proxyMetrics, connDuration)
measuredClientConn.Close() // Closing after the metrics are added aids integration testing.
h.l.LogAttrs(nil, slog.LevelDebug, "TCP: Done.", slog.String("status", status), slog.Duration("duration", connDuration))
h.logger.LogAttrs(nil, slog.LevelDebug, "TCP: Done.", slog.String("status", status), slog.Duration("duration", connDuration))
}

func getProxyRequest(clientConn transport.StreamConn) (string, error) {
Expand All @@ -287,7 +290,7 @@ func getProxyRequest(clientConn transport.StreamConn) (string, error) {
return tgtAddr.String(), nil
}

func proxyConnection(l Logger, ctx context.Context, dialer transport.StreamDialer, tgtAddr string, clientConn transport.StreamConn) *onet.ConnectionError {
func proxyConnection(l *slog.Logger, ctx context.Context, dialer transport.StreamDialer, tgtAddr string, clientConn transport.StreamConn) *onet.ConnectionError {
tgtConn, dialErr := dialer.DialStream(ctx, tgtAddr)
if dialErr != nil {
// We don't drain so dial errors and invalid addresses are communicated quickly.
Expand Down Expand Up @@ -362,7 +365,7 @@ func (h *streamHandler) handleConnection(ctx context.Context, outerConn transpor
tgtConn = metrics.MeasureConn(tgtConn, &proxyMetrics.ProxyTarget, &proxyMetrics.TargetProxy)
return tgtConn, nil
})
return proxyConnection(h.l, ctx, dialer, tgtAddr, innerConn)
return proxyConnection(h.logger, ctx, dialer, tgtAddr, innerConn)
}

// Keep the connection open until we hit the authentication deadline to protect against probing attacks
Expand All @@ -371,7 +374,7 @@ func (h *streamHandler) absorbProbe(clientConn io.ReadCloser, connMetrics TCPCon
// This line updates proxyMetrics.ClientProxy before it's used in AddTCPProbe.
_, drainErr := io.Copy(io.Discard, clientConn) // drain socket
drainResult := drainErrToString(drainErr)
h.l.LogAttrs(nil, slog.LevelDebug, "Drain error.", slog.Any("err", drainErr), slog.String("result", drainResult))
h.logger.LogAttrs(nil, slog.LevelDebug, "Drain error.", slog.Any("err", drainErr), slog.String("result", drainResult))
connMetrics.AddProbe(status, drainResult, proxyMetrics.ClientProxy)
}

Expand Down
4 changes: 2 additions & 2 deletions service/tcp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ func BenchmarkTCPFindCipherFail(b *testing.B) {
}
clientIP := clientConn.RemoteAddr().(*net.TCPAddr).AddrPort().Addr()
b.StartTimer()
findAccessKey(clientConn, clientIP, cipherList, nil)
findAccessKey(clientConn, clientIP, cipherList, noopLogger())
b.StopTimer()
}
}
Expand Down Expand Up @@ -205,7 +205,7 @@ func BenchmarkTCPFindCipherRepeat(b *testing.B) {
cipher := cipherEntries[cipherNumber].CryptoKey
go shadowsocks.NewWriter(writer, cipher).Write(makeTestPayload(50))
b.StartTimer()
_, _, _, _, err := findAccessKey(&c, clientIP, cipherList, nil)
_, _, _, _, err := findAccessKey(&c, clientIP, cipherList, noopLogger())
b.StopTimer()
if err != nil {
b.Error(err)
Expand Down
Loading

0 comments on commit be65ae3

Please sign in to comment.