Skip to content

Commit 5813e0c

Browse files
authored
Add shadowtls (SagerNet#49)
* Add shadowtls outbound * Add shadowtls inbound * Add shadowtls example * Add shadowtls documentation
1 parent 5a9c2b1 commit 5813e0c

19 files changed

+586
-15
lines changed

constant/proxy.go

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const (
1818
TypeHysteria = "hysteria"
1919
TypeTor = "tor"
2020
TypeSSH = "ssh"
21+
TypeShadowTLS = "shadowtls"
2122
)
2223

2324
const (
+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
### Structure
2+
3+
```json
4+
{
5+
"type": "shadowtls",
6+
"tag": "st-in",
7+
8+
... // Listen Fields
9+
10+
"handshake": {
11+
"server": "google.com",
12+
"server_port": 443,
13+
14+
... // Dial Fields
15+
}
16+
}
17+
```
18+
19+
### Listen Fields
20+
21+
See [Listen Fields](/configuration/shared/listen) for details.
22+
23+
24+
### Fields
25+
26+
#### handshake
27+
28+
==Required==
29+
30+
Handshake server address and [dial options](/configuration/shared/dial).
31+
+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
### 结构
2+
3+
```json
4+
{
5+
"type": "shadowtls",
6+
"tag": "st-in",
7+
8+
... // 监听字段
9+
10+
"handshake": {
11+
"server": "google.com",
12+
"server_port": 443,
13+
14+
... // 拨号字段
15+
}
16+
}
17+
```
18+
19+
### 监听字段
20+
21+
参阅 [监听字段](/zh/configuration/shared/listen/)
22+
23+
### 字段
24+
25+
#### handshake
26+
27+
==必填==
28+
29+
握手服务器地址和 [拨号参数](/zh/configuration/shared/dial/)
+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
### Structure
2+
3+
```json
4+
{
5+
"type": "shadowtls",
6+
"tag": "st-out",
7+
8+
"server": "127.0.0.1",
9+
"server_port": 1080,
10+
"tls": {},
11+
12+
... // Dial Fields
13+
}
14+
```
15+
16+
### Fields
17+
18+
#### server
19+
20+
==Required==
21+
22+
The server address.
23+
24+
#### server_port
25+
26+
==Required==
27+
28+
The server port.
29+
30+
#### tls
31+
32+
==Required==
33+
34+
TLS configuration, see [TLS](/configuration/shared/tls/#outbound).
35+
36+
### Dial Fields
37+
38+
See [Dial Fields](/configuration/shared/dial) for details.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
### 结构
2+
3+
```json
4+
{
5+
"type": "shadowtls",
6+
"tag": "st-out",
7+
8+
"server": "127.0.0.1",
9+
"server_port": 1080,
10+
"tls": {},
11+
12+
... // 拨号字段
13+
}
14+
```
15+
16+
### 字段
17+
18+
#### server
19+
20+
==必填==
21+
22+
服务器地址。
23+
24+
#### server_port
25+
26+
==必填==
27+
28+
服务器端口。
29+
30+
#### tls
31+
32+
==必填==
33+
34+
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#outbound)
35+
36+
### 拨号字段
37+
38+
参阅 [拨号字段](/zh/configuration/shared/dial/)

docs/examples/shadowtls.md

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
#### Server
2+
3+
```json
4+
{
5+
"inbounds": [
6+
{
7+
"type": "shadowtls",
8+
"listen": "::",
9+
"listen_port": 4443,
10+
"handshake": {
11+
"server": "google.com",
12+
"server_port": 443
13+
},
14+
"detour": "shadowsocks-in"
15+
},
16+
{
17+
"type": "shadowsocks",
18+
"tag": "shadowsocks-in",
19+
"listen": "127.0.0.1",
20+
"method": "2022-blake3-aes-128-gcm",
21+
"password": "8JCsPssfgS8tiRwiMlhARg=="
22+
}
23+
]
24+
}
25+
```
26+
27+
#### Client
28+
29+
```json
30+
{
31+
"outbounds": [
32+
{
33+
"type": "shadowsocks",
34+
"method": "2022-blake3-aes-128-gcm",
35+
"password": "8JCsPssfgS8tiRwiMlhARg==",
36+
"detour": "shadowtls-out",
37+
"multiplex": {
38+
"enabled": 1,
39+
"max_connections": 4,
40+
"min_streams": 4
41+
}
42+
},
43+
{
44+
"type": "shadowtls",
45+
"tag": "shadowtls-out",
46+
"server": "127.0.0.1",
47+
"server_port": 4443,
48+
"tls": {
49+
"enabled": true,
50+
"server_name": "google.com"
51+
}
52+
}
53+
]
54+
}
55+
```

inbound/builder.go

+2
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, o
3939
return NewNaive(ctx, router, logger, options.Tag, options.NaiveOptions)
4040
case C.TypeHysteria:
4141
return NewHysteria(ctx, router, logger, options.Tag, options.HysteriaOptions)
42+
case C.TypeShadowTLS:
43+
return NewShadowTLS(ctx, router, logger, options.Tag, options.ShadowTLSOptions)
4244
default:
4345
return nil, E.New("unknown inbound type: ", options.Type)
4446
}

inbound/shadowsocks.go

-12
Original file line numberDiff line numberDiff line change
@@ -87,15 +87,3 @@ func (h *Shadowsocks) NewPacket(ctx context.Context, conn N.PacketConn, buffer *
8787
func (h *Shadowsocks) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
8888
return os.ErrInvalid
8989
}
90-
91-
func (h *Shadowsocks) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
92-
h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination)
93-
return h.router.RouteConnection(ctx, conn, metadata)
94-
}
95-
96-
func (h *Shadowsocks) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
97-
ctx = log.ContextWithNewID(ctx)
98-
h.logger.InfoContext(ctx, "inbound packet connection from ", metadata.Source)
99-
h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination)
100-
return h.router.RoutePacketConnection(ctx, conn, metadata)
101-
}

inbound/shadowtls.go

+93
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package inbound
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"encoding/binary"
7+
"io"
8+
"net"
9+
10+
"github.com/sagernet/sing-box/adapter"
11+
"github.com/sagernet/sing-box/common/dialer"
12+
C "github.com/sagernet/sing-box/constant"
13+
"github.com/sagernet/sing-box/log"
14+
"github.com/sagernet/sing-box/option"
15+
E "github.com/sagernet/sing/common/exceptions"
16+
M "github.com/sagernet/sing/common/metadata"
17+
N "github.com/sagernet/sing/common/network"
18+
"github.com/sagernet/sing/common/task"
19+
)
20+
21+
type ShadowTLS struct {
22+
myInboundAdapter
23+
handshakeDialer N.Dialer
24+
handshakeAddr M.Socksaddr
25+
}
26+
27+
func NewShadowTLS(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowTLSInboundOptions) (*ShadowTLS, error) {
28+
inbound := &ShadowTLS{
29+
myInboundAdapter: myInboundAdapter{
30+
protocol: C.TypeShadowTLS,
31+
network: []string{N.NetworkTCP},
32+
ctx: ctx,
33+
router: router,
34+
logger: logger,
35+
tag: tag,
36+
listenOptions: options.ListenOptions,
37+
},
38+
handshakeDialer: dialer.New(router, options.Handshake.DialerOptions),
39+
handshakeAddr: options.Handshake.ServerOptions.Build(),
40+
}
41+
inbound.connHandler = inbound
42+
return inbound, nil
43+
}
44+
45+
func (s *ShadowTLS) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
46+
handshakeConn, err := s.handshakeDialer.DialContext(ctx, N.NetworkTCP, s.handshakeAddr)
47+
if err != nil {
48+
return err
49+
}
50+
var handshake task.Group
51+
handshake.Append("client handshake", func(ctx context.Context) error {
52+
return s.copyUntilHandshakeFinished(handshakeConn, conn)
53+
})
54+
handshake.Append("server handshake", func(ctx context.Context) error {
55+
return s.copyUntilHandshakeFinished(conn, handshakeConn)
56+
})
57+
handshake.FastFail()
58+
err = handshake.Run(ctx)
59+
if err != nil {
60+
return err
61+
}
62+
return s.newConnection(ctx, conn, metadata)
63+
}
64+
65+
func (s *ShadowTLS) copyUntilHandshakeFinished(dst io.Writer, src io.Reader) error {
66+
const handshake = 0x16
67+
const changeCipherSpec = 0x14
68+
var hasSeenChangeCipherSpec bool
69+
var tlsHdr [5]byte
70+
for {
71+
_, err := io.ReadFull(src, tlsHdr[:])
72+
if err != nil {
73+
return err
74+
}
75+
length := binary.BigEndian.Uint16(tlsHdr[3:])
76+
_, err = io.Copy(dst, io.MultiReader(bytes.NewReader(tlsHdr[:]), io.LimitReader(src, int64(length))))
77+
if err != nil {
78+
return err
79+
}
80+
if tlsHdr[0] != handshake {
81+
if tlsHdr[0] != changeCipherSpec {
82+
return E.New("unexpected tls frame type: ", tlsHdr[0])
83+
}
84+
if !hasSeenChangeCipherSpec {
85+
hasSeenChangeCipherSpec = true
86+
continue
87+
}
88+
}
89+
if hasSeenChangeCipherSpec {
90+
return nil
91+
}
92+
}
93+
}

mkdocs.yml

+3
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ nav:
6868
- Trojan: configuration/inbound/trojan.md
6969
- Naive: configuration/inbound/naive.md
7070
- Hysteria: configuration/inbound/hysteria.md
71+
- ShadowTLS: configuration/inbound/shadowtls.md
7172
- Tun: configuration/inbound/tun.md
7273
- Redirect: configuration/inbound/redirect.md
7374
- TProxy: configuration/inbound/tproxy.md
@@ -82,6 +83,7 @@ nav:
8283
- Trojan: configuration/outbound/trojan.md
8384
- WireGuard: configuration/outbound/wireguard.md
8485
- Hysteria: configuration/outbound/hysteria.md
86+
- ShadowTLS: configuration/outbound/shadowtls.md
8587
- Tor: configuration/outbound/tor.md
8688
- SSH: configuration/outbound/ssh.md
8789
- DNS: configuration/outbound/dns.md
@@ -95,6 +97,7 @@ nav:
9597
- Shadowsocks Server: examples/ss-server.md
9698
- Shadowsocks Client: examples/ss-client.md
9799
- Shadowsocks Tun: examples/ss-tun.md
100+
- ShadowTLS: examples/shadowtls.md
98101
- DNS Hijack: examples/dns-hijack.md
99102
- Contributing:
100103
- contributing/index.md

option/inbound.go

+5
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ type _Inbound struct {
2121
TrojanOptions TrojanInboundOptions `json:"-"`
2222
NaiveOptions NaiveInboundOptions `json:"-"`
2323
HysteriaOptions HysteriaInboundOptions `json:"-"`
24+
ShadowTLSOptions ShadowTLSInboundOptions `json:"-"`
2425
}
2526

2627
type Inbound _Inbound
@@ -52,6 +53,8 @@ func (h Inbound) MarshalJSON() ([]byte, error) {
5253
v = h.NaiveOptions
5354
case C.TypeHysteria:
5455
v = h.HysteriaOptions
56+
case C.TypeShadowTLS:
57+
v = h.ShadowTLSOptions
5558
default:
5659
return nil, E.New("unknown inbound type: ", h.Type)
5760
}
@@ -89,6 +92,8 @@ func (h *Inbound) UnmarshalJSON(bytes []byte) error {
8992
v = &h.NaiveOptions
9093
case C.TypeHysteria:
9194
v = &h.HysteriaOptions
95+
case C.TypeShadowTLS:
96+
v = &h.ShadowTLSOptions
9297
default:
9398
return E.New("unknown inbound type: ", h.Type)
9499
}

option/outbound.go

+5
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ type _Outbound struct {
2020
HysteriaOptions HysteriaOutboundOptions `json:"-"`
2121
TorOptions TorOutboundOptions `json:"-"`
2222
SSHOptions SSHOutboundOptions `json:"-"`
23+
ShadowTLSOptions ShadowTLSOutboundOptions `json:"-"`
2324
SelectorOptions SelectorOutboundOptions `json:"-"`
2425
}
2526

@@ -50,6 +51,8 @@ func (h Outbound) MarshalJSON() ([]byte, error) {
5051
v = h.TorOptions
5152
case C.TypeSSH:
5253
v = h.SSHOptions
54+
case C.TypeShadowTLS:
55+
v = h.ShadowTLSOptions
5356
case C.TypeSelector:
5457
v = h.SelectorOptions
5558
default:
@@ -87,6 +90,8 @@ func (h *Outbound) UnmarshalJSON(bytes []byte) error {
8790
v = &h.TorOptions
8891
case C.TypeSSH:
8992
v = &h.SSHOptions
93+
case C.TypeShadowTLS:
94+
v = &h.ShadowTLSOptions
9095
case C.TypeSelector:
9196
v = &h.SelectorOptions
9297
default:

0 commit comments

Comments
 (0)