Skip to content

Commit 80b25b1

Browse files
committed
Use tcp and udp types for direct listeners.
1 parent 1f097be commit 80b25b1

File tree

6 files changed

+50
-135
lines changed

6 files changed

+50
-135
lines changed

cmd/outline-ss-server/config.go

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ type ServiceConfig struct {
2828

2929
type ListenerType string
3030

31-
const listenerTypeDirect ListenerType = "direct"
31+
const listenerTypeTCP ListenerType = "tcp"
32+
const listenerTypeUDP ListenerType = "udp"
3233

3334
type ListenerConfig struct {
3435
Type ListenerType
@@ -59,17 +60,14 @@ func (c *Config) Validate() error {
5960
for _, serviceConfig := range c.Services {
6061
for _, listenerConfig := range serviceConfig.Listeners {
6162
// TODO: Support more listener types.
62-
if listenerConfig.Type != listenerTypeDirect {
63+
if listenerConfig.Type != listenerTypeTCP && listenerConfig.Type != listenerTypeUDP {
6364
return fmt.Errorf("unsupported listener type: %s", listenerConfig.Type)
6465
}
6566

66-
network, host, _, err := SplitNetworkAddr(listenerConfig.Address)
67+
host, _, err := net.SplitHostPort(listenerConfig.Address)
6768
if err != nil {
6869
return fmt.Errorf("invalid listener address `%s`: %v", listenerConfig.Address, err)
6970
}
70-
if network != "tcp" && network != "udp" {
71-
return fmt.Errorf("unsupported network: %s", network)
72-
}
7371
if ip := net.ParseIP(host); ip == nil {
7472
return fmt.Errorf("address must be IP, found: %s", host)
7573
}

cmd/outline-ss-server/config_example.yml

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ services:
22
- listeners:
33
# TODO(sbruens): Allow a string-based listener config, as a convenient short-form
44
# to create a direct listener, e.g. `- tcp/[::]:9000`.
5-
- type: direct
6-
address: "tcp/[::]:9000"
7-
- type: direct
8-
address: "udp/[::]:9000"
5+
- type: tcp
6+
address: "[::]:9000"
7+
- type: udp
8+
address: "[::]:9000"
99
keys:
1010
- id: user-0
1111
cipher: chacha20-ietf-poly1305
@@ -15,10 +15,10 @@ services:
1515
secret: Secret1
1616

1717
- listeners:
18-
- type: direct
19-
address: "tcp/[::]:9001"
20-
- type: direct
21-
address: "udp/[::]:9001"
18+
- type: tcp
19+
address: "[::]:9001"
20+
- type: udp
21+
address: "[::]:9001"
2222
keys:
2323
- id: user-2
2424
cipher: chacha20-ietf-poly1305

cmd/outline-ss-server/config_test.go

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ func TestValidateConfigFails(t *testing.T) {
3232
Services: []ServiceConfig{
3333
ServiceConfig{
3434
Listeners: []ListenerConfig{
35-
ListenerConfig{Type: "foo", Address: "tcp/[::]:9000"},
35+
ListenerConfig{Type: "foo", Address: "[::]:9000"},
3636
},
3737
},
3838
},
@@ -44,19 +44,7 @@ func TestValidateConfigFails(t *testing.T) {
4444
Services: []ServiceConfig{
4545
ServiceConfig{
4646
Listeners: []ListenerConfig{
47-
ListenerConfig{Type: listenerTypeDirect, Address: "tcp//[::]:9000"},
48-
},
49-
},
50-
},
51-
},
52-
},
53-
{
54-
name: "WithUnsupportedNetworkType",
55-
cfg: &Config{
56-
Services: []ServiceConfig{
57-
ServiceConfig{
58-
Listeners: []ListenerConfig{
59-
ListenerConfig{Type: listenerTypeDirect, Address: "foo/[::]:9000"},
47+
ListenerConfig{Type: listenerTypeTCP, Address: "tcp/[::]:9000"},
6048
},
6149
},
6250
},
@@ -68,7 +56,7 @@ func TestValidateConfigFails(t *testing.T) {
6856
Services: []ServiceConfig{
6957
ServiceConfig{
7058
Listeners: []ListenerConfig{
71-
ListenerConfig{Type: listenerTypeDirect, Address: "tcp/example.com:9000"},
59+
ListenerConfig{Type: listenerTypeTCP, Address: "example.com:9000"},
7260
},
7361
},
7462
},
@@ -92,8 +80,8 @@ func TestReadConfig(t *testing.T) {
9280
Services: []ServiceConfig{
9381
ServiceConfig{
9482
Listeners: []ListenerConfig{
95-
ListenerConfig{Type: listenerTypeDirect, Address: "tcp/[::]:9000"},
96-
ListenerConfig{Type: listenerTypeDirect, Address: "udp/[::]:9000"},
83+
ListenerConfig{Type: listenerTypeTCP, Address: "[::]:9000"},
84+
ListenerConfig{Type: listenerTypeUDP, Address: "[::]:9000"},
9785
},
9886
Keys: []KeyConfig{
9987
KeyConfig{"user-0", "chacha20-ietf-poly1305", "Secret0"},
@@ -102,8 +90,8 @@ func TestReadConfig(t *testing.T) {
10290
},
10391
ServiceConfig{
10492
Listeners: []ListenerConfig{
105-
ListenerConfig{Type: listenerTypeDirect, Address: "tcp/[::]:9001"},
106-
ListenerConfig{Type: listenerTypeDirect, Address: "udp/[::]:9001"},
93+
ListenerConfig{Type: listenerTypeTCP, Address: "[::]:9001"},
94+
ListenerConfig{Type: listenerTypeUDP, Address: "[::]:9001"},
10795
},
10896
Keys: []KeyConfig{
10997
KeyConfig{"user-2", "chacha20-ietf-poly1305", "Secret2"},

cmd/outline-ss-server/listeners.go

Lines changed: 16 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,8 @@ package main
1616

1717
import (
1818
"context"
19-
"errors"
2019
"fmt"
2120
"net"
22-
"strconv"
23-
"strings"
2421
"sync"
2522
"sync/atomic"
2623
"time"
@@ -152,143 +149,79 @@ type globalListener struct {
152149
deadlineMu sync.Mutex
153150
}
154151

155-
type NetworkAddr struct {
156-
network string
157-
Host string
158-
Port uint
159-
}
160-
161-
// String returns a human-readable representation of the [NetworkAddr].
162-
func (na *NetworkAddr) Network() string {
163-
return na.network
164-
}
165-
166-
// String returns a human-readable representation of the [NetworkAddr].
167-
func (na *NetworkAddr) String() string {
168-
return na.JoinHostPort()
169-
}
170-
171-
// JoinHostPort is a convenience wrapper around [net.JoinHostPort].
172-
func (na *NetworkAddr) JoinHostPort() string {
173-
return net.JoinHostPort(na.Host, strconv.Itoa(int(na.Port)))
174-
}
175-
176-
// Key returns a representative string useful to retrieve this entity from a
177-
// map. This is used to uniquely identify reusable listeners.
178-
func (na *NetworkAddr) Key() string {
179-
return na.network + "/" + na.JoinHostPort()
180-
}
181-
182-
// Listen creates a new listener for the [NetworkAddr].
152+
// Listen creates a new listener for a given network and address.
183153
//
184154
// Listeners can overlap one another, because during config changes the new
185155
// config is started before the old config is destroyed. This is done by using
186156
// reusable listener wrappers, which do not actually close the underlying socket
187157
// until all uses of the shared listener have been closed.
188-
func (na *NetworkAddr) Listen(ctx context.Context, config net.ListenConfig) (any, error) {
189-
switch na.network {
158+
func Listen(ctx context.Context, network string, addr string, config net.ListenConfig) (any, error) {
159+
lnKey := network + "/" + addr
160+
161+
switch network {
190162

191163
case "tcp":
192164
listenersMu.Lock()
193165
defer listenersMu.Unlock()
194166

195-
if lnGlobal, ok := listeners[na.Key()]; ok {
167+
if lnGlobal, ok := listeners[lnKey]; ok {
196168
lnGlobal.usage.Add(1)
197169
return &sharedListener{
198170
usage: &lnGlobal.usage,
199171
deadline: &lnGlobal.deadline,
200172
deadlineMu: &lnGlobal.deadlineMu,
201-
key: na.Key(),
173+
key: lnKey,
202174
listener: lnGlobal.ln,
203175
}, nil
204176
}
205177

206-
ln, err := config.Listen(ctx, na.network, na.JoinHostPort())
178+
ln, err := config.Listen(ctx, network, addr)
207179
if err != nil {
208180
return nil, err
209181
}
210182

211183
lnGlobal := &globalListener{ln: ln}
212184
lnGlobal.usage.Store(1)
213-
listeners[na.Key()] = lnGlobal
185+
listeners[lnKey] = lnGlobal
214186

215187
return &sharedListener{
216188
usage: &lnGlobal.usage,
217189
deadline: &lnGlobal.deadline,
218190
deadlineMu: &lnGlobal.deadlineMu,
219-
key: na.Key(),
191+
key: lnKey,
220192
listener: ln,
221193
}, nil
222194

223195
case "udp":
224196
listenersMu.Lock()
225197
defer listenersMu.Unlock()
226198

227-
if lnGlobal, ok := listeners[na.Key()]; ok {
199+
if lnGlobal, ok := listeners[lnKey]; ok {
228200
lnGlobal.usage.Add(1)
229201
return &sharedPacketConn{
230202
usage: &lnGlobal.usage,
231-
key: na.Key(),
203+
key: lnKey,
232204
PacketConn: lnGlobal.pc,
233205
}, nil
234206
}
235207

236-
pc, err := config.ListenPacket(ctx, na.network, na.JoinHostPort())
208+
pc, err := config.ListenPacket(ctx, network, addr)
237209
if err != nil {
238210
return nil, err
239211
}
240212

241213
lnGlobal := &globalListener{pc: pc}
242214
lnGlobal.usage.Store(1)
243-
listeners[na.Key()] = lnGlobal
215+
listeners[lnKey] = lnGlobal
244216

245217
return &sharedPacketConn{
246218
usage: &lnGlobal.usage,
247-
key: na.Key(),
219+
key: lnKey,
248220
PacketConn: pc,
249221
}, nil
250222

251223
default:
252-
return nil, fmt.Errorf("unsupported network: %s", na.network)
253-
254-
}
255-
}
256-
257-
// ParseNetworkAddr parses an address into a [NetworkAddr]. The input
258-
// string is expected to be of the form "network/host:port" where any part is
259-
// optional.
260-
//
261-
// Examples:
262-
//
263-
// tcp/127.0.0.1:8000
264-
// udp/127.0.0.1:9000
265-
func ParseNetworkAddr(addr string) (NetworkAddr, error) {
266-
var host, port string
267-
network, host, port, err := SplitNetworkAddr(addr)
268-
if err != nil {
269-
return NetworkAddr{}, err
270-
}
271-
if network == "" {
272-
return NetworkAddr{}, errors.New("missing network")
273-
}
274-
p, err := strconv.ParseUint(port, 10, 16)
275-
if err != nil {
276-
return NetworkAddr{}, fmt.Errorf("invalid port: %v", err)
277-
}
278-
return NetworkAddr{
279-
network: network,
280-
Host: host,
281-
Port: uint(p),
282-
}, nil
283-
}
224+
return nil, fmt.Errorf("unsupported network: %s", network)
284225

285-
// SplitNetworkAddr splits a into its network, host, and port components.
286-
func SplitNetworkAddr(a string) (network, host, port string, err error) {
287-
beforeSlash, afterSlash, slashFound := strings.Cut(a, "/")
288-
if slashFound {
289-
network = strings.ToLower(strings.TrimSpace(beforeSlash))
290-
a = afterSlash
291226
}
292-
host, port, err = net.SplitHostPort(a)
293-
return
294227
}

cmd/outline-ss-server/main.go

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,11 @@ import (
1818
"container/list"
1919
"flag"
2020
"fmt"
21+
"net"
2122
"net/http"
2223
"os"
2324
"os/signal"
25+
"strconv"
2426
"strings"
2527
"syscall"
2628
"time"
@@ -92,12 +94,8 @@ func (s *SSServer) loadConfig(filename string) error {
9294
ciphers: list.New(),
9395
}
9496
for _, network := range []string{"tcp", "udp"} {
95-
addr := NetworkAddr{
96-
network: network,
97-
Host: "::",
98-
Port: uint(legacyKeyServiceConfig.Port),
99-
}
100-
if err := legacyService.AddListener(addr); err != nil {
97+
addr := net.JoinHostPort("::", strconv.Itoa(legacyKeyServiceConfig.Port))
98+
if err := legacyService.AddListener(network, addr); err != nil {
10199
return err
102100
}
103101
}

cmd/outline-ss-server/service.go

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,12 @@ type Service struct {
4141
ciphers *list.List // Values are *List of *service.CipherEntry.
4242
}
4343

44-
func (s *Service) Serve(addr NetworkAddr, listener Listener, cipherList service.CipherList) error {
44+
func (s *Service) Serve(lnKey string, listener Listener, cipherList service.CipherList) error {
4545
switch ln := listener.(type) {
4646
case net.Listener:
4747
authFunc := service.NewShadowsocksStreamAuthenticator(cipherList, s.replayCache, s.m)
4848
// TODO: Register initial data metrics at zero.
49-
tcpHandler := service.NewTCPHandler(addr.Key(), authFunc, s.m, tcpReadTimeout)
49+
tcpHandler := service.NewTCPHandler(lnKey, authFunc, s.m, tcpReadTimeout)
5050
accept := func() (transport.StreamConn, error) {
5151
c, err := ln.Accept()
5252
if err == nil {
@@ -85,20 +85,21 @@ func (s *Service) Stop() error {
8585
}
8686

8787
// AddListener adds a new listener to the service.
88-
func (s *Service) AddListener(addr NetworkAddr) error {
88+
func (s *Service) AddListener(network string, addr string) error {
8989
// Create new listeners based on the configured network addresses.
9090
cipherList := service.NewCipherList()
9191
cipherList.Update(s.ciphers)
9292

93-
listener, err := addr.Listen(context.TODO(), net.ListenConfig{KeepAlive: 0})
93+
listener, err := Listen(context.TODO(), network, addr, net.ListenConfig{KeepAlive: 0})
9494
if err != nil {
9595
//lint:ignore ST1005 Shadowsocks is capitalized.
96-
return fmt.Errorf("Shadowsocks %s service failed to start on address %s: %w", addr.Network(), addr.String(), err)
96+
return fmt.Errorf("Shadowsocks %s service failed to start on address %s: %w", network, addr, err)
9797
}
9898
s.listeners = append(s.listeners, listener)
99-
logger.Infof("Shadowsocks %s service listening on %s", addr.Network(), addr.String())
100-
if err = s.Serve(addr, listener, cipherList); err != nil {
101-
return fmt.Errorf("failed to serve on %s listener on address %s: %w", addr.Network(), addr.String(), err)
99+
logger.Infof("Shadowsocks %s service listening on %s", network, addr)
100+
lnKey := network + "/" + addr
101+
if err = s.Serve(lnKey, listener, cipherList); err != nil {
102+
return fmt.Errorf("failed to serve on %s listener on address %s: %w", network, addr, err)
102103
}
103104
return nil
104105
}
@@ -145,11 +146,8 @@ func NewService(config ServiceConfig, natTimeout time.Duration, m *outlineMetric
145146
}
146147

147148
for _, listener := range config.Listeners {
148-
addr, err := ParseNetworkAddr(listener.Address)
149-
if err != nil {
150-
return nil, fmt.Errorf("error parsing listener address `%s`: %v", listener.Address, err)
151-
}
152-
if err := s.AddListener(addr); err != nil {
149+
network := string(listener.Type)
150+
if err := s.AddListener(network, listener.Address); err != nil {
153151
return nil, err
154152
}
155153
}

0 commit comments

Comments
 (0)