Compare commits

...

7 Commits

Author SHA1 Message Date
Shtorm
da4c532efb Merge branch 'extended' of https://github.com/shtorm-7/sing-box-extended into extended 2026-08-08 10:33:36 +03:00
Shtorm
97c981764d Add DefaultGateway 2026-08-08 10:33:31 +03:00
Shtorm
3310474137 Fix limiters 2026-08-08 10:11:10 +03:00
Shtorm
6c81ddd00f Fix Wireguard options 2026-08-08 10:11:01 +03:00
Shtorm
da50e2969d Fix XHTTP dialing 2026-08-08 10:10:05 +03:00
Shtorm
1e35987658 Fix typo in Rmux description in README.md
Signed-off-by: Shtorm <108103062+shtorm-7@users.noreply.github.com>
2026-08-07 06:33:09 +03:00
Shtorm
44c9c36c65 Fix typo
Signed-off-by: Shtorm <108103062+shtorm-7@users.noreply.github.com>
2026-08-06 17:14:08 +03:00
23 changed files with 435 additions and 155 deletions

View File

@@ -42,7 +42,7 @@ Sing-box with extended features.
### Transports ### Transports
- **mKCP** — Reliable UDP-based transport - **mKCP** — Reliable UDP-based transport
- **XHTTP** — Modern XRAY transport - **XHTTP** — Modern XRAY transport
- **rmux** — Improved smux multiplex protocol - **Rmux** — Improved smux multiplexer
### Services ### Services
- **Admin Panel** — Web-based management interface - **Admin Panel** — Web-based management interface

View File

@@ -44,10 +44,13 @@
"i3": "<b 0xc70000000108...", "i3": "<b 0xc70000000108...",
"i4": "<b 0xc70000000108...", "i4": "<b 0xc70000000108...",
"i5": "<b 0xc70000000108...", "i5": "<b 0xc70000000108...",
"j1": "<b 0xc70000000108...", "header_protection_key": "QGg8AFRn6qKfTB7cT3FWH1WGx3np+OKzlNuQUrqIBmI=",
"j2": "<b 0xc70000000108...", "content_padding_addition": "50-100",
"j3": "<b 0xc70000000108...", "rekey_after_time": 120,
"itime": 50, "rekey_timeout": 5,
"reject_after_time": 180,
"keepalive_timeout": 10,
"max_handshake_attempts": 20
} }
} }
], ],

View File

@@ -19,11 +19,7 @@
"amnezia": { "amnezia": {
"jc": 120, "jc": 120,
"jmin": 23, "jmin": 23,
"jmax": 911, "jmax": 911
"h1": 1,
"h2": 2,
"h3": 3,
"h4": 4
}, },
"profile": { "profile": {
"detour": "direct" "detour": "direct"

View File

@@ -0,0 +1,53 @@
{
"log": {
"level": "error"
},
"dns": {
"servers": [
{
"type": "local",
"tag": "default"
}
]
},
"endpoints": [
{
"type": "vpn-client",
"tag": "vpn",
"address": "10.0.0.2",
"key": "1c9b2ccf-b0c0-4c26-868d-a55a4edad3fe",
"outbound": {
"type": "vless",
"tag": "vless-out",
"server": "0.0.0.0",
"server_port": 8000,
"uuid": "9b65b7e1-04c8-4717-8f45-2aa61fd25937",
"network": "tcp"
}
}
],
"inbounds": [
{
"type": "mixed",
"tag": "mixed-in",
"listen_port": 7897
}
],
"outbounds": [
{
"type": "direct",
"tag": "direct-out"
}
],
"route": {
"rules": [
{
"domain_suffix": ".internal",
"outbound": "vpn",
"override_gateway": "10.0.0.3"
}
],
"final": "direct-out",
"auto_detect_interface": true
}
}

View File

@@ -0,0 +1,40 @@
{
"log": {
"level": "error"
},
"dns": {
"servers": [
{
"type": "local",
"tag": "default"
}
]
},
"endpoints": [
{
"type": "vpn-client",
"tag": "vpn",
"address": "10.0.0.3",
"key": "3d74d616-2502-4c17-9cc3-92c366550f4f",
"outbound": {
"type": "vless",
"tag": "vless-out",
"server": "0.0.0.0",
"server_port": 8000,
"uuid": "9b65b7e1-04c8-4717-8f45-2aa61fd25937",
"network": "tcp"
}
}
],
"outbounds": [
{
"type": "direct",
"tag": "direct-out"
}
],
"route": {
"final": "direct-out",
"default_domain_resolver": "default",
"auto_detect_interface": true
}
}

View File

@@ -0,0 +1,61 @@
{
"log": {
"level": "error"
},
"dns": {
"servers": [
{
"type": "local",
"tag": "default"
}
]
},
"endpoints": [
{
"type": "vpn-server",
"tag": "vpn",
"address": "10.0.0.1",
"users": [
{
"address": "10.0.0.2",
"key": "1c9b2ccf-b0c0-4c26-868d-a55a4edad3fe"
},
{
"address": "10.0.0.3",
"key": "3d74d616-2502-4c17-9cc3-92c366550f4f"
}
],
"inbounds": [
{
"type": "vless",
"tag": "vless-in",
"listen": "0.0.0.0",
"listen_port": 8000,
"users": [
{
"name": "vless",
"uuid": "9b65b7e1-04c8-4717-8f45-2aa61fd25937"
}
]
}
]
}
],
"outbounds": [
{
"type": "direct",
"tag": "direct-out"
}
],
"route": {
"rules": [
{
"source_ip_cidr": "10.0.0.0/24",
"outbound": "vpn"
}
],
"final": "direct-out",
"default_domain_resolver": "default",
"auto_detect_interface": true
}
}

View File

@@ -16,6 +16,7 @@
"tag": "vpn", "tag": "vpn",
"address": "10.0.0.2", "address": "10.0.0.2",
"key": "1c9b2ccf-b0c0-4c26-868d-a55a4edad3fe", "key": "1c9b2ccf-b0c0-4c26-868d-a55a4edad3fe",
"default_gateway": "10.0.0.3",
"outbound": { "outbound": {
"type": "vless", "type": "vless",
"tag": "vless-out", "tag": "vless-out",
@@ -40,13 +41,7 @@
} }
], ],
"route": { "route": {
"rules": [ "final": "vpn",
{
"outbound": "vpn",
"override_gateway": "10.0.0.3"
}
],
"final": "direct-out",
"auto_detect_interface": true "auto_detect_interface": true
} }
} }

View File

@@ -15,6 +15,7 @@
"type": "vpn-server", "type": "vpn-server",
"tag": "vpn", "tag": "vpn",
"address": "10.0.0.1", "address": "10.0.0.1",
"default_gateway": "10.0.0.2",
"users": [ "users": [
{ {
"address": "10.0.0.2", "address": "10.0.0.2",
@@ -47,8 +48,7 @@
"rules": [ "rules": [
{ {
"inbound": "vless-in", "inbound": "vless-in",
"outbound": "vpn", "outbound": "vpn"
"override_gateway": "10.0.0.2"
} }
], ],
"final": "direct-out", "final": "direct-out",

View File

@@ -15,6 +15,7 @@
"type": "vpn-server", "type": "vpn-server",
"tag": "vpn", "tag": "vpn",
"address": "10.0.0.1", "address": "10.0.0.1",
"default_gateway": "10.0.0.2",
"users": [ "users": [
{ {
"address": "10.0.0.2", "address": "10.0.0.2",
@@ -51,13 +52,7 @@
} }
], ],
"route": { "route": {
"rules": [ "final": "vpn",
{
"outbound": "vpn",
"override_gateway": "10.0.0.2"
}
],
"final": "direct-out",
"default_domain_resolver": "default", "default_domain_resolver": "default",
"auto_detect_interface": true "auto_detect_interface": true
} }

View File

@@ -26,23 +26,17 @@
"jc": 120, "jc": 120,
"jmin": 23, "jmin": 23,
"jmax": 911, "jmax": 911,
"s1": 1,
"s2": 2,
"s3": 3,
"s4": 4,
"h1": 1,
"h2": 2,
"h3": 3,
"h4": 4,
"i1": "<b 0xc70000000108...", "i1": "<b 0xc70000000108...",
"i2": "<b 0xc70000000108...", "i2": "<b 0xc70000000108...",
"i3": "<b 0xc70000000108...", "i3": "<b 0xc70000000108...",
"i4": "<b 0xc70000000108...", "i4": "<b 0xc70000000108...",
"i5": "<b 0xc70000000108...", "i5": "<b 0xc70000000108...",
"j1": "<b 0xc70000000108...", "content_padding_addition": "50-100",
"j2": "<b 0xc70000000108...", "rekey_after_time": 120,
"j3": "<b 0xc70000000108...", "rekey_timeout": 5,
"itime": 50 "reject_after_time": 180,
"keepalive_timeout": 10,
"max_handshake_attempts": 20
}, },
"profile": { "profile": {
"detour": "direct", "detour": "direct",

View File

@@ -11,7 +11,24 @@ type WARPEndpointOptions struct {
Workers int `json:"workers,omitempty"` Workers int `json:"workers,omitempty"`
PreallocatedBuffersPerPool uint32 `json:"preallocated_buffers_per_pool,omitempty"` PreallocatedBuffersPerPool uint32 `json:"preallocated_buffers_per_pool,omitempty"`
DisablePauses bool `json:"disable_pauses,omitempty"` DisablePauses bool `json:"disable_pauses,omitempty"`
Amnezia *WireGuardAmnezia `json:"amnezia,omitempty"` Amnezia *WARPAmnezia `json:"amnezia,omitempty"`
Profile CloudflareProfile `json:"profile,omitempty"` Profile CloudflareProfile `json:"profile,omitempty"`
DialerOptions DialerOptions
} }
type WARPAmnezia struct {
JC int `json:"jc,omitempty"`
JMin int `json:"jmin,omitempty"`
JMax int `json:"jmax,omitempty"`
I1 string `json:"i1,omitempty"`
I2 string `json:"i2,omitempty"`
I3 string `json:"i3,omitempty"`
I4 string `json:"i4,omitempty"`
I5 string `json:"i5,omitempty"`
ContentPaddingAddition *badoption.Range[uint32] `json:"content_padding_addition,omitempty"`
RekeyAfterTime *badoption.Range[uint32] `json:"rekey_after_time,omitempty"`
RekeyTimeout *badoption.Range[uint32] `json:"rekey_timeout,omitempty"`
RejectAfterTime *badoption.Range[uint32] `json:"reject_after_time,omitempty"`
KeepaliveTimeout *badoption.Range[uint32] `json:"keepalive_timeout,omitempty"`
MaxHandshakeAttempts *badoption.Range[uint32] `json:"max_handshake_attempts,omitempty"`
}

View File

@@ -32,24 +32,27 @@ type WireGuardPeer struct {
} }
type WireGuardAmnezia struct { type WireGuardAmnezia struct {
JC int `json:"jc,omitempty"` JC int `json:"jc,omitempty"`
JMin int `json:"jmin,omitempty"` JMin int `json:"jmin,omitempty"`
JMax int `json:"jmax,omitempty"` JMax int `json:"jmax,omitempty"`
S1 int `json:"s1,omitempty"` S1 int `json:"s1,omitempty"`
S2 int `json:"s2,omitempty"` S2 int `json:"s2,omitempty"`
S3 int `json:"s3,omitempty"` S3 int `json:"s3,omitempty"`
S4 int `json:"s4,omitempty"` S4 int `json:"s4,omitempty"`
H1 *badoption.Range[uint32] `json:"h1,omitempty"` H1 *badoption.Range[uint32] `json:"h1,omitempty"`
H2 *badoption.Range[uint32] `json:"h2,omitempty"` H2 *badoption.Range[uint32] `json:"h2,omitempty"`
H3 *badoption.Range[uint32] `json:"h3,omitempty"` H3 *badoption.Range[uint32] `json:"h3,omitempty"`
H4 *badoption.Range[uint32] `json:"h4,omitempty"` H4 *badoption.Range[uint32] `json:"h4,omitempty"`
I1 string `json:"i1,omitempty"` I1 string `json:"i1,omitempty"`
I2 string `json:"i2,omitempty"` I2 string `json:"i2,omitempty"`
I3 string `json:"i3,omitempty"` I3 string `json:"i3,omitempty"`
I4 string `json:"i4,omitempty"` I4 string `json:"i4,omitempty"`
I5 string `json:"i5,omitempty"` I5 string `json:"i5,omitempty"`
J1 string `json:"j1,omitempty"` HeaderProtectionKey string `json:"header_protection_key,omitempty"`
J2 string `json:"j2,omitempty"` ContentPaddingAddition *badoption.Range[uint32] `json:"content_padding_addition,omitempty"`
J3 string `json:"j3,omitempty"` RekeyAfterTime *badoption.Range[uint32] `json:"rekey_after_time,omitempty"`
ITime int64 `json:"itime,omitempty"` RekeyTimeout *badoption.Range[uint32] `json:"rekey_timeout,omitempty"`
RejectAfterTime *badoption.Range[uint32] `json:"reject_after_time,omitempty"`
KeepaliveTimeout *badoption.Range[uint32] `json:"keepalive_timeout,omitempty"`
MaxHandshakeAttempts *badoption.Range[uint32] `json:"max_handshake_attempts,omitempty"`
} }

View File

@@ -106,7 +106,12 @@ func (h *Outbound) DialContext(ctx context.Context, network string, destination
if err != nil { if err != nil {
return nil, err return nil, err
} }
return h.strategy.wrapConn(ctx, conn, adapter.ContextFrom(ctx), true) wrappedConn, err := h.strategy.wrapConn(ctx, conn, adapter.ContextFrom(ctx), true)
if err != nil {
conn.Close()
return nil, err
}
return wrappedConn, nil
} }
func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
@@ -114,12 +119,17 @@ func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (n
if err != nil { if err != nil {
return nil, err return nil, err
} }
return h.strategy.wrapPacketConn(ctx, conn, adapter.ContextFrom(ctx), true) wrappedConn, err := h.strategy.wrapPacketConn(ctx, conn, adapter.ContextFrom(ctx), true)
if err != nil {
conn.Close()
return nil, err
}
return wrappedConn, nil
} }
func (h *Outbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { func (h *Outbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
ctx = adapter.WithContext(ctx, &metadata) ctx = adapter.WithContext(ctx, &metadata)
conn, err := h.strategy.wrapConn(ctx, conn, &metadata, false) wrappedConn, err := h.strategy.wrapConn(ctx, conn, &metadata, false)
if err != nil { if err != nil {
h.logger.ErrorContext(ctx, err) h.logger.ErrorContext(ctx, err)
N.CloseOnHandshakeFailure(conn, onClose, err) N.CloseOnHandshakeFailure(conn, onClose, err)
@@ -127,7 +137,7 @@ func (h *Outbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata
} }
metadata.Inbound = h.Tag() metadata.Inbound = h.Tag()
metadata.InboundType = h.Type() metadata.InboundType = h.Type()
h.router.RouteConnectionEx(ctx, conn, metadata, onClose) h.router.RouteConnectionEx(ctx, wrappedConn, metadata, onClose)
return return
} }

View File

@@ -2,6 +2,7 @@ package bandwidth
import ( import (
"context" "context"
"io"
"net" "net"
"strconv" "strconv"
"sync" "sync"
@@ -191,7 +192,7 @@ func (s *UsersBandwidthStrategy) getStrategy(ctx context.Context, metadata *adap
} }
type bwConnEntry struct { type bwConnEntry struct {
conn net.Conn conn io.Closer
} }
type ManagerBandwidthStrategy struct { type ManagerBandwidthStrategy struct {
@@ -251,7 +252,25 @@ func (s *ManagerBandwidthStrategy) wrapPacketConn(ctx context.Context, conn net.
if !ok { if !ok {
return nil, E.New("user strategy not found: ", user) return nil, E.New("user strategy not found: ", user)
} }
return strategy.wrapPacketConn(ctx, conn, metadata, reverse) wrapped, err := strategy.wrapPacketConn(ctx, conn, metadata, reverse)
if err != nil {
return nil, err
}
entry := &bwConnEntry{conn: conn}
s.mtx.Lock()
s.conns[user] = append(s.conns[user], entry)
s.mtx.Unlock()
return onclose.NewPacketConn(wrapped, func() {
s.mtx.Lock()
entries := s.conns[user]
for i, e := range entries {
if e == entry {
s.conns[user] = append(entries[:i], entries[i+1:]...)
break
}
}
s.mtx.Unlock()
}), nil
} }
func (s *ManagerBandwidthStrategy) UpdateStrategies(strategies map[string]BandwidthStrategy) { func (s *ManagerBandwidthStrategy) UpdateStrategies(strategies map[string]BandwidthStrategy) {

View File

@@ -82,7 +82,12 @@ func (h *Outbound) DialContext(ctx context.Context, network string, destination
if err != nil { if err != nil {
return nil, err return nil, err
} }
return h.strategy.wrapConn(ctx, conn, adapter.ContextFrom(ctx), true) wrappedConn, err := h.strategy.wrapConn(ctx, conn, adapter.ContextFrom(ctx), true)
if err != nil {
conn.Close()
return nil, err
}
return wrappedConn, nil
} }
func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
@@ -90,11 +95,16 @@ func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (n
if err != nil { if err != nil {
return nil, err return nil, err
} }
return h.strategy.wrapPacketConn(ctx, conn, adapter.ContextFrom(ctx), true) wrappedConn, err := h.strategy.wrapPacketConn(ctx, conn, adapter.ContextFrom(ctx), true)
if err != nil {
conn.Close()
return nil, err
}
return wrappedConn, nil
} }
func (h *Outbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { func (h *Outbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
conn, err := h.strategy.wrapConn(ctx, conn, &metadata, false) wrappedConn, err := h.strategy.wrapConn(ctx, conn, &metadata, false)
if err != nil { if err != nil {
if err.Error() != "traffic limit exceeded" { if err.Error() != "traffic limit exceeded" {
h.logger.ErrorContext(ctx, err) h.logger.ErrorContext(ctx, err)
@@ -104,7 +114,7 @@ func (h *Outbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata
} }
metadata.Inbound = h.Tag() metadata.Inbound = h.Tag()
metadata.InboundType = h.Type() metadata.InboundType = h.Type()
h.router.RouteConnectionEx(ctx, conn, metadata, onClose) h.router.RouteConnectionEx(ctx, wrappedConn, metadata, onClose)
} }
func (h *Outbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { func (h *Outbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {

View File

@@ -2,6 +2,7 @@ package traffic
import ( import (
"context" "context"
"io"
"net" "net"
"sync" "sync"
@@ -37,11 +38,11 @@ func NewDefaultWrapStrategy(limiterStrategy TrafficLimiterStrategy, connWrapper
func (s *DefaultWrapStrategy) wrapConn(ctx context.Context, conn net.Conn, metadata *adapter.InboundContext, reverse bool) (net.Conn, error) { func (s *DefaultWrapStrategy) wrapConn(ctx context.Context, conn net.Conn, metadata *adapter.InboundContext, reverse bool) (net.Conn, error) {
limiter, err := s.limiterStrategy.getLimiter(ctx, metadata) limiter, err := s.limiterStrategy.getLimiter(ctx, metadata)
if err != nil { if err != nil {
return conn, err return nil, err
} }
_, err = limiter.Reserve(0) _, err = limiter.Reserve(0)
if err != nil { if err != nil {
return conn, err return nil, err
} }
return s.connWrapper(ctx, conn, limiter, reverse), nil return s.connWrapper(ctx, conn, limiter, reverse), nil
} }
@@ -49,11 +50,11 @@ func (s *DefaultWrapStrategy) wrapConn(ctx context.Context, conn net.Conn, metad
func (s *DefaultWrapStrategy) wrapPacketConn(ctx context.Context, conn net.PacketConn, metadata *adapter.InboundContext, reverse bool) (net.PacketConn, error) { func (s *DefaultWrapStrategy) wrapPacketConn(ctx context.Context, conn net.PacketConn, metadata *adapter.InboundContext, reverse bool) (net.PacketConn, error) {
limiter, err := s.limiterStrategy.getLimiter(ctx, metadata) limiter, err := s.limiterStrategy.getLimiter(ctx, metadata)
if err != nil { if err != nil {
return conn, err return nil, err
} }
_, err = limiter.Reserve(0) _, err = limiter.Reserve(0)
if err != nil { if err != nil {
return conn, err return nil, err
} }
return s.packetConnWrapper(ctx, conn, limiter, reverse), nil return s.packetConnWrapper(ctx, conn, limiter, reverse), nil
} }
@@ -73,7 +74,7 @@ func (s *GlobalTrafficStrategy) getLimiter(ctx context.Context, metadata *adapte
} }
type connEntry struct { type connEntry struct {
conn net.Conn conn io.Closer
} }
type ManagerTrafficStrategy struct { type ManagerTrafficStrategy struct {
@@ -94,7 +95,7 @@ func (s *ManagerTrafficStrategy) wrapConn(ctx context.Context, conn net.Conn, me
if err != nil { if err != nil {
return nil, err return nil, err
} }
wrapped, err := strategy.wrapConn(ctx, conn, metadata, reverse) wrappedConn, err := strategy.wrapConn(ctx, conn, metadata, reverse)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -102,7 +103,7 @@ func (s *ManagerTrafficStrategy) wrapConn(ctx context.Context, conn net.Conn, me
s.mtx.Lock() s.mtx.Lock()
s.conns[user] = append(s.conns[user], entry) s.conns[user] = append(s.conns[user], entry)
s.mtx.Unlock() s.mtx.Unlock()
return onclose.NewConn(wrapped, func() { return onclose.NewConn(wrappedConn, func() {
s.mtx.Lock() s.mtx.Lock()
entries := s.conns[user] entries := s.conns[user]
for i, e := range entries { for i, e := range entries {
@@ -116,11 +117,29 @@ func (s *ManagerTrafficStrategy) wrapConn(ctx context.Context, conn net.Conn, me
} }
func (s *ManagerTrafficStrategy) wrapPacketConn(ctx context.Context, conn net.PacketConn, metadata *adapter.InboundContext, reverse bool) (net.PacketConn, error) { func (s *ManagerTrafficStrategy) wrapPacketConn(ctx context.Context, conn net.PacketConn, metadata *adapter.InboundContext, reverse bool) (net.PacketConn, error) {
strategy, _, err := s.getStrategy(ctx, metadata) strategy, user, err := s.getStrategy(ctx, metadata)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return strategy.wrapPacketConn(ctx, conn, metadata, reverse) wrappedConn, err := strategy.wrapPacketConn(ctx, conn, metadata, reverse)
if err != nil {
return nil, err
}
entry := &connEntry{conn: conn}
s.mtx.Lock()
s.conns[user] = append(s.conns[user], entry)
s.mtx.Unlock()
return onclose.NewPacketConn(wrappedConn, func() {
s.mtx.Lock()
entries := s.conns[user]
for i, e := range entries {
if e == entry {
s.conns[user] = append(entries[:i], entries[i+1:]...)
break
}
}
s.mtx.Unlock()
}), nil
} }
func (s *ManagerTrafficStrategy) getStrategy(ctx context.Context, metadata *adapter.InboundContext) (TrafficStrategy, string, error) { func (s *ManagerTrafficStrategy) getStrategy(ctx context.Context, metadata *adapter.InboundContext) (TrafficStrategy, string, error) {

View File

@@ -29,13 +29,14 @@ func RegisterClientEndpoint(registry *endpoint.Registry) {
type ClientEndpoint struct { type ClientEndpoint struct {
outbound.Adapter outbound.Adapter
ctx context.Context ctx context.Context
outbound adapter.Outbound outbound adapter.Outbound
router adapter.ConnectionRouterEx router adapter.ConnectionRouterEx
logger logger.ContextLogger logger logger.ContextLogger
address IPv4 address IPv4
key uuid.UUID key uuid.UUID
uotClient *uot.Client defaultGateway IPv4
uotClient *uot.Client
} }
func NewClientEndpoint(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VPNClientEndpointOptions) (adapter.Endpoint, error) { func NewClientEndpoint(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VPNClientEndpointOptions) (adapter.Endpoint, error) {
@@ -47,13 +48,21 @@ func NewClientEndpoint(ctx context.Context, router adapter.Router, logger log.Co
if err != nil { if err != nil {
return nil, err return nil, err
} }
defaultGateway := Loopback.As4()
if options.DefaultGateway.IsValid() {
if !options.DefaultGateway.Is4() {
return nil, E.New("invalid default_gateway: ", options.DefaultGateway)
}
defaultGateway = options.DefaultGateway.As4()
}
client := &ClientEndpoint{ client := &ClientEndpoint{
Adapter: outbound.NewAdapter(C.TypeVPNClient, tag, []string{N.NetworkTCP, N.NetworkUDP}, []string{}), Adapter: outbound.NewAdapter(C.TypeVPNClient, tag, []string{N.NetworkTCP, N.NetworkUDP}, []string{}),
ctx: ctx, ctx: ctx,
router: sbUot.NewRouter(router, logger), router: sbUot.NewRouter(router, logger),
logger: logger, logger: logger,
address: address.As4(), address: address.As4(),
key: key, key: key,
defaultGateway: defaultGateway,
} }
outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx) outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx)
outbound, err := outboundRegistry.CreateOutbound(ctx, router, logger, options.Outbound.Tag, options.Outbound.Type, options.Outbound.Options) outbound, err := outboundRegistry.CreateOutbound(ctx, router, logger, options.Outbound.Tag, options.Outbound.Type, options.Outbound.Options)
@@ -102,7 +111,7 @@ func (c *ClientEndpoint) DialContext(ctx context.Context, network string, destin
if err != nil { if err != nil {
return nil, err return nil, err
} }
gateway := Loopback.As4() gateway := c.defaultGateway
if metadata := adapter.ContextFrom(ctx); metadata != nil { if metadata := adapter.ContextFrom(ctx); metadata != nil {
if metadata.Gateway != nil { if metadata.Gateway != nil {
gateway = metadata.Gateway.As4() gateway = metadata.Gateway.As4()

View File

@@ -29,15 +29,16 @@ func RegisterServerEndpoint(registry *endpoint.Registry) {
type ServerEndpoint struct { type ServerEndpoint struct {
outbound.Adapter outbound.Adapter
logger logger.ContextLogger logger logger.ContextLogger
inbounds []adapter.Inbound inbounds []adapter.Inbound
router adapter.ConnectionRouterEx router adapter.ConnectionRouterEx
address IPv4 address IPv4
addresses map[uuid.UUID]IPv4 addresses map[uuid.UUID]IPv4
keys map[IPv4]uuid.UUID keys map[IPv4]uuid.UUID
conns map[IPv4]chan net.Conn conns map[IPv4]chan net.Conn
timeout time.Duration timeout time.Duration
uotClient *uot.Client defaultGateway *netip.Addr
uotClient *uot.Client
} }
func NewServerEndpoint(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VPNServerEndpointOptions) (adapter.Endpoint, error) { func NewServerEndpoint(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VPNServerEndpointOptions) (adapter.Endpoint, error) {
@@ -51,6 +52,13 @@ func NewServerEndpoint(ctx context.Context, router adapter.Router, logger log.Co
router: sbUot.NewRouter(router, logger), router: sbUot.NewRouter(router, logger),
address: address.As4(), address: address.As4(),
} }
if options.DefaultGateway.IsValid() {
if !options.DefaultGateway.Is4() {
return nil, E.New("invalid default_gateway: ", options.DefaultGateway)
}
defaultGateway := options.DefaultGateway
server.defaultGateway = &defaultGateway
}
router = NewRouter(router, logger, server.connHandler) router = NewRouter(router, logger, server.connHandler)
inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx) inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx)
inbounds := make([]adapter.Inbound, len(options.Inbounds)) inbounds := make([]adapter.Inbound, len(options.Inbounds))
@@ -124,6 +132,8 @@ func (s *ServerEndpoint) DialContext(ctx context.Context, network string, destin
Addr: Loopback, Addr: Loopback,
Port: destination.Port, Port: destination.Port,
} }
} else if s.defaultGateway != nil {
gateway = s.defaultGateway
} else { } else {
return nil, E.New("missing gateway") return nil, E.New("missing gateway")
} }

View File

@@ -88,6 +88,25 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL
} }
peer := config.Peers[0] peer := config.Peers[0]
hostParts := strings.Split(peer.Endpoint.Host, ":") hostParts := strings.Split(peer.Endpoint.Host, ":")
var amnezia *option.WireGuardAmnezia
if options.Amnezia != nil {
amnezia = &option.WireGuardAmnezia{
JC: options.Amnezia.JC,
JMin: options.Amnezia.JMin,
JMax: options.Amnezia.JMax,
I1: options.Amnezia.I1,
I2: options.Amnezia.I2,
I3: options.Amnezia.I3,
I4: options.Amnezia.I4,
I5: options.Amnezia.I5,
ContentPaddingAddition: options.Amnezia.ContentPaddingAddition,
RekeyAfterTime: options.Amnezia.RekeyAfterTime,
RekeyTimeout: options.Amnezia.RekeyTimeout,
RejectAfterTime: options.Amnezia.RejectAfterTime,
KeepaliveTimeout: options.Amnezia.KeepaliveTimeout,
MaxHandshakeAttempts: options.Amnezia.MaxHandshakeAttempts,
}
}
endpoint.endpoint, err = wireguard.NewEndpoint( endpoint.endpoint, err = wireguard.NewEndpoint(
ctx, ctx,
router, router,
@@ -101,7 +120,7 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL
Workers: options.Workers, Workers: options.Workers,
PreallocatedBuffersPerPool: options.PreallocatedBuffersPerPool, PreallocatedBuffersPerPool: options.PreallocatedBuffersPerPool,
DisablePauses: options.DisablePauses, DisablePauses: options.DisablePauses,
Amnezia: options.Amnezia, Amnezia: amnezia,
DialerOptions: options.DialerOptions, DialerOptions: options.DialerOptions,
Address: badoption.Listable[netip.Prefix]{ Address: badoption.Listable[netip.Prefix]{

View File

@@ -77,26 +77,29 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL
var amnezia *wireguard.AmneziaOptions var amnezia *wireguard.AmneziaOptions
if options.Amnezia != nil { if options.Amnezia != nil {
amnezia = &wireguard.AmneziaOptions{ amnezia = &wireguard.AmneziaOptions{
JC: options.Amnezia.JC, JC: options.Amnezia.JC,
JMin: options.Amnezia.JMin, JMin: options.Amnezia.JMin,
JMax: options.Amnezia.JMax, JMax: options.Amnezia.JMax,
S1: options.Amnezia.S1, S1: options.Amnezia.S1,
S2: options.Amnezia.S2, S2: options.Amnezia.S2,
S3: options.Amnezia.S3, S3: options.Amnezia.S3,
S4: options.Amnezia.S4, S4: options.Amnezia.S4,
H1: options.Amnezia.H1, H1: options.Amnezia.H1,
H2: options.Amnezia.H2, H2: options.Amnezia.H2,
H3: options.Amnezia.H3, H3: options.Amnezia.H3,
H4: options.Amnezia.H4, H4: options.Amnezia.H4,
I1: options.Amnezia.I1, I1: options.Amnezia.I1,
I2: options.Amnezia.I2, I2: options.Amnezia.I2,
I3: options.Amnezia.I3, I3: options.Amnezia.I3,
I4: options.Amnezia.I4, I4: options.Amnezia.I4,
I5: options.Amnezia.I5, I5: options.Amnezia.I5,
J1: options.Amnezia.J1, HeaderProtectionKey: options.Amnezia.HeaderProtectionKey,
J2: options.Amnezia.J2, ContentPaddingAddition: options.Amnezia.ContentPaddingAddition,
J3: options.Amnezia.J3, RekeyAfterTime: options.Amnezia.RekeyAfterTime,
ITime: options.Amnezia.ITime, RekeyTimeout: options.Amnezia.RekeyTimeout,
RejectAfterTime: options.Amnezia.RejectAfterTime,
KeepaliveTimeout: options.Amnezia.KeepaliveTimeout,
MaxHandshakeAttempts: options.Amnezia.MaxHandshakeAttempts,
} }
} }
wgEndpoint, err := wireguard.NewEndpoint(wireguard.EndpointOptions{ wgEndpoint, err := wireguard.NewEndpoint(wireguard.EndpointOptions{

View File

@@ -3,6 +3,7 @@ package xhttp
import ( import (
"bytes" "bytes"
"context" "context"
"errors"
"fmt" "fmt"
"io" "io"
"net" "net"
@@ -110,15 +111,17 @@ func (c *DefaultDialerClient) OpenStream(ctx context.Context, url string, sessio
if body != nil { if body != nil {
method = c.options.GetNormalizedUplinkHTTPMethod() // stream-up/one method = c.options.GetNormalizedUplinkHTTPMethod() // stream-up/one
} }
req, _ := http.NewRequestWithContext(context.WithoutCancel(ctx), method, url, body) reqCtx, cancel := context.WithCancel(context.WithoutCancel(ctx))
req, _ := http.NewRequestWithContext(reqCtx, method, url, body)
FillStreamRequest(req, sessionId, "", c.options) FillStreamRequest(req, sessionId, "", c.options)
wrc = &WaitReadCloser{Wait: make(chan struct{})} wrc = &WaitReadCloser{Wait: make(chan struct{}), Cancel: cancel}
go func() { go func() {
resp, err := c.client.Do(req) resp, err := c.client.Do(req)
if err != nil { if err != nil {
if !uploadOnly { // stream-down is enough if !uploadOnly && !errors.Is(err, context.Canceled) { // stream-down is enough
c.Close() c.Close()
} }
cancel()
gotConn.Close() gotConn.Close()
common.Close(body) common.Close(body)
wrc.Close() wrc.Close()
@@ -128,6 +131,7 @@ func (c *DefaultDialerClient) OpenStream(ctx context.Context, url string, sessio
io.Copy(io.Discard, resp.Body) io.Copy(io.Discard, resp.Body)
resp.Body.Close() // if it is called immediately, the upload will be interrupted also resp.Body.Close() // if it is called immediately, the upload will be interrupted also
common.Close(body) common.Close(body)
cancel()
wrc.Close() wrc.Close()
return return
} }
@@ -210,7 +214,8 @@ func (c *DefaultDialerClient) PostPacket(ctx context.Context, url string, sessio
} }
type WaitReadCloser struct { type WaitReadCloser struct {
Wait chan struct{} Wait chan struct{}
Cancel context.CancelFunc
io.ReadCloser io.ReadCloser
} }
@@ -233,6 +238,9 @@ func (w *WaitReadCloser) Read(b []byte) (int, error) {
} }
func (w *WaitReadCloser) Close() error { func (w *WaitReadCloser) Close() error {
if w.Cancel != nil {
w.Cancel()
}
if w.ReadCloser != nil { if w.ReadCloser != nil {
return w.ReadCloser.Close() return w.ReadCloser.Close()
} }

View File

@@ -235,17 +235,30 @@ func (e *Endpoint) Start(resolve bool) error {
if e.options.Amnezia.I5 != "" { if e.options.Amnezia.I5 != "" {
ipcConf.WriteString("\ni5=" + e.options.Amnezia.I5) ipcConf.WriteString("\ni5=" + e.options.Amnezia.I5)
} }
if e.options.Amnezia.J1 != "" { if e.options.Amnezia.HeaderProtectionKey != "" {
ipcConf.WriteString("\nj1=" + e.options.Amnezia.J1) headerProtectionKeyBytes, err := base64.StdEncoding.DecodeString(e.options.Amnezia.HeaderProtectionKey)
if err != nil {
return E.Cause(err, "decode header protection key")
}
ipcConf.WriteString("\nheader_protection_key=" + hex.EncodeToString(headerProtectionKeyBytes))
} }
if e.options.Amnezia.J2 != "" { if e.options.Amnezia.ContentPaddingAddition != nil {
ipcConf.WriteString("\nj2=" + e.options.Amnezia.J2) ipcConf.WriteString("\ncontent_padding_addition=" + e.options.Amnezia.ContentPaddingAddition.String())
} }
if e.options.Amnezia.J3 != "" { if e.options.Amnezia.RekeyAfterTime != nil {
ipcConf.WriteString("\nj3=" + e.options.Amnezia.J3) ipcConf.WriteString("\nrekey_after_time=" + e.options.Amnezia.RekeyAfterTime.String())
} }
if e.options.Amnezia.ITime > 0 { if e.options.Amnezia.RekeyTimeout != nil {
ipcConf.WriteString("\nitime=" + strconv.FormatInt(e.options.Amnezia.ITime, 10)) ipcConf.WriteString("\nrekey_timeout=" + e.options.Amnezia.RekeyTimeout.String())
}
if e.options.Amnezia.RejectAfterTime != nil {
ipcConf.WriteString("\nreject_after_time=" + e.options.Amnezia.RejectAfterTime.String())
}
if e.options.Amnezia.KeepaliveTimeout != nil {
ipcConf.WriteString("\nkeepalive_timeout=" + e.options.Amnezia.KeepaliveTimeout.String())
}
if e.options.Amnezia.MaxHandshakeAttempts != nil {
ipcConf.WriteString("\nmax_handshake_attempts=" + e.options.Amnezia.MaxHandshakeAttempts.String())
} }
} }
for _, peer := range e.peers { for _, peer := range e.peers {

View File

@@ -5,8 +5,8 @@ import (
"net/netip" "net/netip"
"time" "time"
"github.com/sagernet/sing/common/json/badoption"
tun "github.com/sagernet/sing-tun" tun "github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common/json/badoption"
"github.com/sagernet/sing/common/logger" "github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
@@ -43,24 +43,27 @@ type PeerOptions struct {
} }
type AmneziaOptions struct { type AmneziaOptions struct {
JC int JC int
JMin int JMin int
JMax int JMax int
S1 int S1 int
S2 int S2 int
S3 int S3 int
S4 int S4 int
H1 *badoption.Range[uint32] H1 *badoption.Range[uint32]
H2 *badoption.Range[uint32] H2 *badoption.Range[uint32]
H3 *badoption.Range[uint32] H3 *badoption.Range[uint32]
H4 *badoption.Range[uint32] H4 *badoption.Range[uint32]
I1 string I1 string
I2 string I2 string
I3 string I3 string
I4 string I4 string
I5 string I5 string
J1 string HeaderProtectionKey string
J2 string ContentPaddingAddition *badoption.Range[uint32]
J3 string RekeyAfterTime *badoption.Range[uint32]
ITime int64 RekeyTimeout *badoption.Range[uint32]
RejectAfterTime *badoption.Range[uint32]
KeepaliveTimeout *badoption.Range[uint32]
MaxHandshakeAttempts *badoption.Range[uint32]
} }