Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: split out UDP association handling from serving #221

Merged
merged 75 commits into from
Jan 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
b2aa859
refactor: split UDP serving from handling of packets
sbruens Nov 18, 2024
c0c0e9f
Use a read channel.
sbruens Nov 20, 2024
8d95309
Rename `wrappedPacketConn` to just `packetConn`.
sbruens Nov 20, 2024
51d5c88
Update `shadowsocks_handler`.
sbruens Nov 21, 2024
422542b
Change the `HandlePacket` API to not require the `pkt`.
sbruens Nov 22, 2024
c10176f
Remove the NAT map from the `Handle()` function.
sbruens Nov 25, 2024
004c6c3
Rework the metrics now that the NAT is no longer done by the handler.
sbruens Nov 26, 2024
a91d55b
Move the metrics `AddClosed()` call to after the `timedCopy` returns.
sbruens Nov 26, 2024
1d7200b
Remove the explicit `targetConn.Close()` and let it expire on its own.
sbruens Nov 26, 2024
4b8eeae
Add the NAT metrics back in at the server level.
sbruens Nov 27, 2024
1e89e85
Use buffer pool on `packetHandler` instead of global.
sbruens Nov 27, 2024
b4e7dbb
Revert wrapping the `clientConn` with shadowsocks encryption/decryption.
sbruens Nov 27, 2024
63c2cff
Rename `packet` to `association`.
sbruens Nov 27, 2024
1eeb4d6
Fix tests.
sbruens Nov 28, 2024
08e8bba
Update the docstring for `PacketServe`.
sbruens Nov 28, 2024
34a01d7
Fix comment to refer to "associations".
sbruens Nov 28, 2024
033ba9d
Fix metrics test.
sbruens Dec 3, 2024
87a9d23
Use `slicepool`.
sbruens Dec 3, 2024
50feaa2
Let the assocation handler provide the buffer.
sbruens Dec 4, 2024
b7a4a3c
Remove the `packetConnWrapper` and move the logic into `natconn` inst…
sbruens Dec 10, 2024
f80294c
Fix close while reading of `natconn`.
sbruens Dec 10, 2024
36d4b27
Simplify the natmap a little.
sbruens Dec 10, 2024
f5d9ac3
Refactor `PacketServe` to use events (close and read).
sbruens Dec 13, 2024
e0547f2
Use correct logger.
sbruens Dec 13, 2024
3dc12d1
Keep `readCh` and `closeCh` unbuffered.
sbruens Dec 16, 2024
afb9cd1
Catch panics in the `ReadFrom` go routine.
sbruens Dec 16, 2024
5d2acc6
Close the `readCh` instead of sending the error on the `readCh`.
sbruens Dec 16, 2024
7cd0f1f
Wrap a logger with the association's client address so we can simplif…
sbruens Dec 16, 2024
078032c
Reference GitHub issue for supporting multiple IPs.
sbruens Dec 16, 2024
2b3f746
Simplify packet handling with a new `association` struct.
sbruens Dec 18, 2024
2f2268b
Add some comments to the `Association` interface.
sbruens Dec 18, 2024
bfe4b35
Consolidate debug logging.
sbruens Dec 18, 2024
f37f23a
Rename some vars.
sbruens Dec 18, 2024
08de4c3
Update doc.
sbruens Dec 18, 2024
418c0e4
Format.
sbruens Dec 19, 2024
98988b0
Do not unpack first packets twice.
sbruens Dec 19, 2024
b9c0286
Move handling into the association.
sbruens Dec 20, 2024
d14ea20
Merge branch 'master' into sbruens/udp-split-serving
sbruens Dec 20, 2024
eef630a
Add some comments to the timeout value.
sbruens Dec 20, 2024
1c462b1
Don't set the stream dialer in the old config flow.
sbruens Dec 20, 2024
0918050
Rename `AddAuthentication` and `AddClose`.
sbruens Jan 6, 2025
78af4be
Update comment to reflect it handles packets from both directions.
sbruens Jan 6, 2025
2cf398c
Separate the interfaces for `Handle()` and `HandlePacket()`.
sbruens Jan 6, 2025
1055cb1
Fix typo in `UDPAssocationMetrics`.
sbruens Jan 6, 2025
f8ab81a
Don't pass `conn` to `Handle()`.
sbruens Jan 6, 2025
e6ef2f3
Update comment.
sbruens Jan 6, 2025
2939f8a
Add comments.
sbruens Jan 6, 2025
23a03e9
Remove ConnAssociation in favor of a `HandleAssociation(Conn, PacketA…
sbruens Jan 8, 2025
aa130f0
Split `Service` interface into outline-ss-server and Caddy interfaces.
sbruens Jan 8, 2025
19fb5f7
Remove unused const.
sbruens Jan 8, 2025
c656b36
Don't require `conn` in `HandleAssociation()`.
sbruens Jan 8, 2025
31cec7c
Exit the loop if the connection is closed.
sbruens Jan 9, 2025
0642698
Re-use global buffer pool.
sbruens Jan 10, 2025
a16c1b3
Remove app-specific interfaces.
sbruens Jan 10, 2025
77983fc
Decouple the association and the packet handling.
sbruens Jan 11, 2025
f3d63ae
Move timedCopy handling to the packet handler.
sbruens Jan 11, 2025
4c4072c
Remove unused property.
sbruens Jan 13, 2025
413a1aa
Decouple shadowsocks from association.
sbruens Jan 13, 2025
db3d0a3
Move authentication into its own function.
sbruens Jan 13, 2025
5f685bb
Remove the `packetMetrics` struct.
sbruens Jan 13, 2025
1dbfa99
Update tests.
sbruens Jan 13, 2025
23050ef
Remove the `Metrics()` method.
sbruens Jan 13, 2025
47222f6
Move variables into the anonymous functions.
sbruens Jan 16, 2025
8e9af05
Rename stream and packet handlers `Handle()` methods.
sbruens Jan 17, 2025
5704718
More `handle` naming clarification.
sbruens Jan 17, 2025
0cbcd61
Refactor to make packet handler an association handler.
sbruens Jan 21, 2025
bcac22b
Only handle the association if it was new.
sbruens Jan 21, 2025
220d1d7
Fix the metric race condition in tests.
sbruens Jan 21, 2025
d81f128
Move `AddNATEntry()` call to new entry only.
sbruens Jan 21, 2025
96de2a6
Format.
sbruens Jan 22, 2025
09e471f
Address review comments.
sbruens Jan 22, 2025
64c48ce
Make `clientConn` an `io.Writer`.
sbruens Jan 22, 2025
a62e0b6
Handle the `EOF` case and stop reading from the connection.
sbruens Jan 23, 2025
2427670
Address review comments.
sbruens Jan 24, 2025
1c9d3c5
Remove debug log.
sbruens Jan 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 16 additions & 12 deletions caddy/shadowsocks_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,21 @@ import (
"fmt"
"log/slog"
"net"
"time"

"github.com/Jigsaw-Code/outline-sdk/transport"
"github.com/Jigsaw-Code/outline-sdk/transport/shadowsocks"
outline "github.com/Jigsaw-Code/outline-ss-server/service"
"github.com/caddyserver/caddy/v2"
"github.com/mholt/caddy-l4/layer4"

outline "github.com/Jigsaw-Code/outline-ss-server/service"
)

const ssModuleName = "layer4.handlers.shadowsocks"

// A UDP NAT timeout of at least 5 minutes is recommended in RFC 4787 Section 4.3.
const defaultNatTimeout time.Duration = 5 * time.Minute

func init() {
caddy.RegisterModule(ModuleRegistration{
ID: ssModuleName,
Expand All @@ -45,8 +50,10 @@ type KeyConfig struct {
type ShadowsocksHandler struct {
Keys []KeyConfig `json:"keys,omitempty"`

service outline.Service
logger *slog.Logger
streamHandler outline.StreamHandler
associationHandler outline.AssociationHandler
metrics outline.ServiceMetrics
logger *slog.Logger
}

var (
Expand All @@ -70,6 +77,7 @@ func (h *ShadowsocksHandler) Provision(ctx caddy.Context) error {
if !ok {
return fmt.Errorf("module `%s` is of type `%T`, expected `OutlineApp`", outlineModuleName, app)
}
h.metrics = app.Metrics

if len(h.Keys) == 0 {
h.logger.Warn("no keys configured")
Expand Down Expand Up @@ -97,26 +105,22 @@ func (h *ShadowsocksHandler) Provision(ctx caddy.Context) error {
ciphers := outline.NewCipherList()
ciphers.Update(cipherList)

service, err := outline.NewShadowsocksService(
h.streamHandler, h.associationHandler = outline.NewShadowsocksHandlers(
outline.WithLogger(h.logger),
outline.WithCiphers(ciphers),
outline.WithMetrics(app.Metrics),
outline.WithMetrics(h.metrics),
outline.WithReplayCache(&app.ReplayCache),
)
if err != nil {
return err
}
h.service = service
return nil
}

// Handle implements layer4.NextHandler.
func (h *ShadowsocksHandler) Handle(cx *layer4.Connection, _ layer4.Handler) error {
switch conn := cx.Conn.(type) {
case transport.StreamConn:
h.service.HandleStream(cx.Context, conn)
case net.PacketConn:
h.service.HandlePacket(conn)
h.streamHandler.HandleStream(cx.Context, conn, h.metrics.AddOpenTCPConnection(conn))
case net.Conn:
h.associationHandler.HandleAssociation(cx.Context, conn, h.metrics.AddOpenUDPAssociation(conn))
default:
return fmt.Errorf("failed to handle unknown connection type: %t", conn)
}
Expand Down
27 changes: 18 additions & 9 deletions cmd/outline-ss-server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package main

import (
"container/list"
"context"
"flag"
"fmt"
"log/slog"
Expand All @@ -27,6 +28,7 @@ import (
"syscall"
"time"

"github.com/Jigsaw-Code/outline-sdk/transport"
"github.com/Jigsaw-Code/outline-sdk/transport/shadowsocks"
"github.com/lmittmann/tint"
"github.com/prometheus/client_golang/prometheus"
Expand Down Expand Up @@ -223,40 +225,43 @@ func (s *OutlineServer) runConfig(config Config) (func() error, error) {
ciphers := service.NewCipherList()
ciphers.Update(cipherList)

ssService, err := service.NewShadowsocksService(
streamHandler, associationHandler := service.NewShadowsocksHandlers(
service.WithCiphers(ciphers),
service.WithNatTimeout(s.natTimeout),
service.WithMetrics(s.serviceMetrics),
service.WithReplayCache(&s.replayCache),
service.WithPacketListener(service.MakeTargetUDPListener(s.natTimeout, 0)),
service.WithLogger(slog.Default()),
)
ln, err := lnSet.ListenStream(addr)
if err != nil {
return err
}
slog.Info("TCP service started.", "address", ln.Addr().String())
go service.StreamServe(ln.AcceptStream, ssService.HandleStream)
go service.StreamServe(ln.AcceptStream, func(ctx context.Context, conn transport.StreamConn) {
streamHandler.HandleStream(ctx, conn, s.serviceMetrics.AddOpenTCPConnection(conn))
})

pc, err := lnSet.ListenPacket(addr)
if err != nil {
return err
}
slog.Info("UDP service started.", "address", pc.LocalAddr().String())
go ssService.HandlePacket(pc)
go service.PacketServe(pc, func(ctx context.Context, conn net.Conn) {
associationHandler.HandleAssociation(ctx, conn, s.serviceMetrics.AddOpenUDPAssociation(conn))
}, s.serverMetrics)
}

for _, serviceConfig := range config.Services {
ciphers, err := newCipherListFromConfig(serviceConfig)
if err != nil {
return fmt.Errorf("failed to create cipher list from config: %v", err)
}
ssService, err := service.NewShadowsocksService(
streamHandler, associationHandler := service.NewShadowsocksHandlers(
service.WithCiphers(ciphers),
service.WithNatTimeout(s.natTimeout),
service.WithMetrics(s.serviceMetrics),
service.WithReplayCache(&s.replayCache),
service.WithStreamDialer(service.MakeValidatingTCPStreamDialer(onet.RequirePublicIP, serviceConfig.Dialer.Fwmark)),
service.WithPacketListener(service.MakeTargetUDPListener(serviceConfig.Dialer.Fwmark)),
service.WithPacketListener(service.MakeTargetUDPListener(s.natTimeout, serviceConfig.Dialer.Fwmark)),
fortuna marked this conversation as resolved.
Show resolved Hide resolved
service.WithLogger(slog.Default()),
)
if err != nil {
Expand All @@ -275,7 +280,9 @@ func (s *OutlineServer) runConfig(config Config) (func() error, error) {
}
return serviceConfig.Dialer.Fwmark
}())
go service.StreamServe(ln.AcceptStream, ssService.HandleStream)
go service.StreamServe(ln.AcceptStream, func(ctx context.Context, conn transport.StreamConn) {
streamHandler.HandleStream(ctx, conn, s.serviceMetrics.AddOpenTCPConnection(conn))
})
case listenerTypeUDP:
pc, err := lnSet.ListenPacket(lnConfig.Address)
if err != nil {
Expand All @@ -287,7 +294,9 @@ func (s *OutlineServer) runConfig(config Config) (func() error, error) {
}
return serviceConfig.Dialer.Fwmark
}())
go ssService.HandlePacket(pc)
go service.PacketServe(pc, func(ctx context.Context, conn net.Conn) {
associationHandler.HandleAssociation(ctx, conn, s.serviceMetrics.AddOpenUDPAssociation(conn))
}, s.serverMetrics)
}
}
totalCipherCount += len(serviceConfig.Keys)
Expand Down
32 changes: 29 additions & 3 deletions cmd/outline-ss-server/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package main
import (
"time"

"github.com/Jigsaw-Code/outline-ss-server/service"
"github.com/prometheus/client_golang/prometheus"
)

Expand All @@ -25,12 +26,15 @@ var now = time.Now

type serverMetrics struct {
// NOTE: New metrics need to be added to `newPrometheusServerMetrics()`, `Describe()` and `Collect()`.
buildInfo *prometheus.GaugeVec
accessKeys prometheus.Gauge
ports prometheus.Gauge
buildInfo *prometheus.GaugeVec
accessKeys prometheus.Gauge
ports prometheus.Gauge
addedNatEntries prometheus.Counter
removedNatEntries prometheus.Counter
}

var _ prometheus.Collector = (*serverMetrics)(nil)
var _ service.NATMetrics = (*serverMetrics)(nil)

// newPrometheusServerMetrics constructs a Prometheus metrics collector for server
// related metrics.
Expand All @@ -48,19 +52,33 @@ func newPrometheusServerMetrics() *serverMetrics {
Name: "ports",
Help: "Count of open ports",
}),
addedNatEntries: prometheus.NewCounter(prometheus.CounterOpts{
Subsystem: "udp",
Name: "nat_entries_added",
Help: "Entries added to the UDP NAT table",
}),
removedNatEntries: prometheus.NewCounter(prometheus.CounterOpts{
Subsystem: "udp",
Name: "nat_entries_removed",
Help: "Entries removed from the UDP NAT table",
}),
}
}

func (m *serverMetrics) Describe(ch chan<- *prometheus.Desc) {
m.buildInfo.Describe(ch)
m.accessKeys.Describe(ch)
m.ports.Describe(ch)
m.addedNatEntries.Describe(ch)
m.removedNatEntries.Describe(ch)
}

func (m *serverMetrics) Collect(ch chan<- prometheus.Metric) {
m.buildInfo.Collect(ch)
m.accessKeys.Collect(ch)
m.ports.Collect(ch)
m.addedNatEntries.Collect(ch)
m.removedNatEntries.Collect(ch)
}

func (m *serverMetrics) SetVersion(version string) {
Expand All @@ -71,3 +89,11 @@ func (m *serverMetrics) SetNumAccessKeys(numKeys int, ports int) {
m.accessKeys.Set(float64(numKeys))
m.ports.Set(float64(ports))
}

func (m *serverMetrics) AddNATEntry() {
m.addedNatEntries.Inc()
}

func (m *serverMetrics) RemoveNATEntry() {
m.removedNatEntries.Inc()
}
Loading
Loading