Skip to content

Commit 1a18692

Browse files
committed
Create connection handlers that can be shared between WebSocket and layer4 connections.
1 parent 8697a50 commit 1a18692

File tree

12 files changed

+484
-331
lines changed

12 files changed

+484
-331
lines changed

caddy/app.go

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,19 @@ import (
2828
"github.com/prometheus/client_golang/prometheus"
2929
)
3030

31-
const outlineModuleName = "outline"
31+
const (
32+
outlineModuleName = "outline"
33+
replayCacheCtxKey = "outline_replay_cache"
34+
metricsCtxKey = "outline_metrics"
35+
)
3236

3337
func init() {
3438
replayCache := outline.NewReplayCache(0)
3539
caddy.RegisterModule(ModuleRegistration{
3640
ID: outlineModuleName,
3741
New: func() caddy.Module {
3842
app := new(OutlineApp)
39-
app.ReplayCache = replayCache
43+
app.replayCache = replayCache
4044
return app
4145
},
4246
})
@@ -48,10 +52,11 @@ type ShadowsocksConfig struct {
4852

4953
type OutlineApp struct {
5054
ShadowsocksConfig *ShadowsocksConfig `json:"shadowsocks,omitempty"`
55+
Handlers ConnectionHandlers `json:"connection_handlers,omitempty"`
5156

52-
ReplayCache outline.ReplayCache
5357
logger *slog.Logger
54-
Metrics outline.ServiceMetrics
58+
replayCache outline.ReplayCache
59+
metrics outline.ServiceMetrics
5560
buildInfo *prometheus.GaugeVec
5661
}
5762

@@ -71,7 +76,7 @@ func (app *OutlineApp) Provision(ctx caddy.Context) error {
7176
app.logger.Info("provisioning app instance")
7277

7378
if app.ShadowsocksConfig != nil {
74-
if err := app.ReplayCache.Resize(app.ShadowsocksConfig.ReplayHistory); err != nil {
79+
if err := app.replayCache.Resize(app.ShadowsocksConfig.ReplayHistory); err != nil {
7580
return fmt.Errorf("failed to configure replay history with capacity %d: %v", app.ShadowsocksConfig.ReplayHistory, err)
7681
}
7782
}
@@ -83,6 +88,14 @@ func (app *OutlineApp) Provision(ctx caddy.Context) error {
8388
app.buildInfo.WithLabelValues("dev").Set(1)
8489
// TODO: Add replacement metrics for `shadowsocks_keys` and `shadowsocks_ports`.
8590

91+
ctx = ctx.WithValue(replayCacheCtxKey, app.replayCache)
92+
ctx = ctx.WithValue(metricsCtxKey, app.metrics)
93+
94+
err := app.Handlers.Provision(ctx)
95+
if err != nil {
96+
return err
97+
}
98+
8699
return nil
87100
}
88101

@@ -104,7 +117,7 @@ func (app *OutlineApp) defineMetrics() error {
104117
if err != nil {
105118
return err
106119
}
107-
app.Metrics, err = registerCollector(r, metrics)
120+
app.metrics, err = registerCollector(r, metrics)
108121
if err != nil {
109122
return err
110123
}

caddy/connection_handler.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Copyright 2024 The Outline Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package outlinecaddy
16+
17+
import (
18+
"encoding/json"
19+
"fmt"
20+
21+
"github.com/caddyserver/caddy/v2"
22+
"github.com/mholt/caddy-l4/layer4"
23+
)
24+
25+
type ConnectionHandler struct {
26+
Name string `json:"name,omitempty"`
27+
WrappedHandlerRaw json.RawMessage `json:"handle,omitempty" caddy:"namespace=layer4.handlers inline_key=handler"`
28+
29+
compiled layer4.NextHandler
30+
}
31+
32+
var (
33+
_ caddy.Provisioner = (*ConnectionHandler)(nil)
34+
_ layer4.NextHandler = (*ConnectionHandler)(nil)
35+
)
36+
37+
// Provision sets up the connection handler.
38+
func (ch *ConnectionHandler) Provision(ctx caddy.Context) error {
39+
mod, err := ctx.LoadModule(ch, "WrappedHandlerRaw")
40+
if err != nil {
41+
return err
42+
}
43+
compiled, ok := mod.(layer4.NextHandler)
44+
if !ok {
45+
return fmt.Errorf("module is of type `%T`, expected `layer4.NextHandler`", compiled)
46+
}
47+
ch.compiled = compiled
48+
return nil
49+
}
50+
51+
func (ch *ConnectionHandler) Handle(cx *layer4.Connection, next layer4.Handler) error {
52+
return ch.compiled.Handle(cx, next)
53+
}
54+
55+
type ConnectionHandlers []*ConnectionHandler
56+
57+
// Provision sets up all the connection handlers.
58+
func (ch ConnectionHandlers) Provision(ctx caddy.Context) error {
59+
for i, h := range ch {
60+
err := h.Provision(ctx)
61+
if err != nil {
62+
return fmt.Errorf("connection handler %d: %v", i, err)
63+
}
64+
}
65+
return nil
66+
}

caddy/examples/config_example.json

Lines changed: 65 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,10 @@
1111
"http": {
1212
"servers": {
1313
"metrics": {
14-
"listen": [
15-
":9091"
16-
],
14+
"listen": [":9091"],
1715
"routes": [
1816
{
19-
"match": [
20-
{
21-
"path": [
22-
"/metrics"
23-
]
24-
}
25-
],
17+
"match": [{"path": ["/metrics"]}],
2618
"handle": [
2719
{
2820
"disable_openmetrics": true,
@@ -31,115 +23,53 @@
3123
]
3224
}
3325
]
34-
}
35-
}
36-
},
37-
"layer4": {
38-
"servers": {
39-
"1": {
40-
"listen": [
41-
"tcp/[::]:9000",
42-
"udp/[::]:9000"
43-
],
26+
},
27+
"ws1": {
28+
"listen": [":8000"],
4429
"routes": [
4530
{
46-
"match": [
47-
{
48-
"http": [
49-
{
50-
"path": [
51-
"/tcp"
52-
],
53-
"header": {
54-
"Connection": ["*Upgrade*"],
55-
"Upgrade": ["websocket"]
56-
}
57-
}
58-
]
59-
}
60-
],
31+
"match": [{"path": ["/tcp"]}],
6132
"handle": [
6233
{
63-
"handler": "websocket",
64-
"type": "stream"
65-
},
66-
{
67-
"handler": "shadowsocks",
68-
"keys": [
69-
{
70-
"id": "user-0",
71-
"cipher": "chacha20-ietf-poly1305",
72-
"secret": "Secret0"
73-
},
74-
{
75-
"id": "user-1",
76-
"cipher": "chacha20-ietf-poly1305",
77-
"secret": "Secret1"
78-
}
79-
]
34+
"handler": "ws2outline",
35+
"type": "stream",
36+
"connection_handler": "ss1"
8037
}
8138
]
8239
},
8340
{
84-
"match": [
85-
{
86-
"http": [
87-
{
88-
"path": [
89-
"/udp"
90-
],
91-
"header": {
92-
"Connection": ["*Upgrade*"],
93-
"Upgrade": ["websocket"]
94-
}
95-
}
96-
]
97-
}
98-
],
41+
"match": [{"path": ["/udp"]}],
9942
"handle": [
10043
{
101-
"handler": "websocket",
102-
"type": "packet"
103-
},
104-
{
105-
"handler": "shadowsocks",
106-
"keys": [
107-
{
108-
"id": "user-0",
109-
"cipher": "chacha20-ietf-poly1305",
110-
"secret": "Secret0"
111-
},
112-
{
113-
"id": "user-1",
114-
"cipher": "chacha20-ietf-poly1305",
115-
"secret": "Secret1"
116-
}
117-
]
44+
"handler": "ws2outline",
45+
"type": "packet",
46+
"connection_handler": "ss1"
11847
}
11948
]
120-
},
49+
}
50+
]
51+
}
52+
}
53+
},
54+
"layer4": {
55+
"servers": {
56+
"ss1": {
57+
"listen": [
58+
"tcp/[::]:9000",
59+
"udp/[::]:9000"
60+
],
61+
"routes": [
12162
{
12263
"handle": [
12364
{
124-
"handler": "shadowsocks",
125-
"keys": [
126-
{
127-
"id": "user-0",
128-
"cipher": "chacha20-ietf-poly1305",
129-
"secret": "Secret0"
130-
},
131-
{
132-
"id": "user-1",
133-
"cipher": "chacha20-ietf-poly1305",
134-
"secret": "Secret1"
135-
}
136-
]
65+
"handler": "outline",
66+
"connection_handler": "ss1"
13767
}
13868
]
13969
}
14070
]
14171
},
142-
"2": {
72+
"ss2_no_replaycache": {
14373
"listen": [
14474
"tcp/[::]:9001",
14575
"udp/[::]:9001"
@@ -148,14 +78,8 @@
14878
{
14979
"handle": [
15080
{
151-
"handler": "shadowsocks",
152-
"keys": [
153-
{
154-
"id": "user-2",
155-
"cipher": "chacha20-ietf-poly1305",
156-
"secret": "Secret2"
157-
}
158-
]
81+
"handler": "outline",
82+
"connection_handler": "ss2"
15983
}
16084
]
16185
}
@@ -166,7 +90,40 @@
16690
"outline": {
16791
"shadowsocks": {
16892
"replay_history": 10000
169-
}
93+
},
94+
"connection_handlers": [
95+
{
96+
"name": "ss1",
97+
"handle": {
98+
"handler": "shadowsocks",
99+
"keys": [
100+
{
101+
"id": "user-0",
102+
"cipher": "chacha20-ietf-poly1305",
103+
"secret": "Secret0"
104+
},
105+
{
106+
"id": "user-1",
107+
"cipher": "chacha20-ietf-poly1305",
108+
"secret": "Secret1"
109+
}
110+
]
111+
}
112+
},
113+
{
114+
"name": "ss2",
115+
"handle": {
116+
"handler": "shadowsocks",
117+
"keys": [
118+
{
119+
"id": "user-2",
120+
"cipher": "chacha20-ietf-poly1305",
121+
"secret": "Secret2"
122+
}
123+
]
124+
}
125+
}
126+
]
170127
}
171128
}
172129
}

caddy/examples/simple.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
1+
# Copyright 2024 The Outline Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
115
---
216
admin:
317
disabled: true

caddy/examples/websocket.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
1+
# Copyright 2024 The Outline Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
115
---
216
admin:
317
disabled: true

0 commit comments

Comments
 (0)