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
- **mKCP** — Reliable UDP-based transport
- **XHTTP** — Modern XRAY transport
- **rmux** — Improved smux multiplex protocol
- **Rmux** — Improved smux multiplexer
### Services
- **Admin Panel** — Web-based management interface

View File

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

View File

@@ -19,11 +19,7 @@
"amnezia": {
"jc": 120,
"jmin": 23,
"jmax": 911,
"h1": 1,
"h2": 2,
"h3": 3,
"h4": 4
"jmax": 911
},
"profile": {
"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",
"address": "10.0.0.2",
"key": "1c9b2ccf-b0c0-4c26-868d-a55a4edad3fe",
"default_gateway": "10.0.0.3",
"outbound": {
"type": "vless",
"tag": "vless-out",
@@ -40,13 +41,7 @@
}
],
"route": {
"rules": [
{
"outbound": "vpn",
"override_gateway": "10.0.0.3"
}
],
"final": "direct-out",
"final": "vpn",
"auto_detect_interface": true
}
}

View File

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

View File

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

View File

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

View File

@@ -11,7 +11,24 @@ type WARPEndpointOptions struct {
Workers int `json:"workers,omitempty"`
PreallocatedBuffersPerPool uint32 `json:"preallocated_buffers_per_pool,omitempty"`
DisablePauses bool `json:"disable_pauses,omitempty"`
Amnezia *WireGuardAmnezia `json:"amnezia,omitempty"`
Amnezia *WARPAmnezia `json:"amnezia,omitempty"`
Profile CloudflareProfile `json:"profile,omitempty"`
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 {
JC int `json:"jc,omitempty"`
JMin int `json:"jmin,omitempty"`
JMax int `json:"jmax,omitempty"`
S1 int `json:"s1,omitempty"`
S2 int `json:"s2,omitempty"`
S3 int `json:"s3,omitempty"`
S4 int `json:"s4,omitempty"`
H1 *badoption.Range[uint32] `json:"h1,omitempty"`
H2 *badoption.Range[uint32] `json:"h2,omitempty"`
H3 *badoption.Range[uint32] `json:"h3,omitempty"`
H4 *badoption.Range[uint32] `json:"h4,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"`
J1 string `json:"j1,omitempty"`
J2 string `json:"j2,omitempty"`
J3 string `json:"j3,omitempty"`
ITime int64 `json:"itime,omitempty"`
JC int `json:"jc,omitempty"`
JMin int `json:"jmin,omitempty"`
JMax int `json:"jmax,omitempty"`
S1 int `json:"s1,omitempty"`
S2 int `json:"s2,omitempty"`
S3 int `json:"s3,omitempty"`
S4 int `json:"s4,omitempty"`
H1 *badoption.Range[uint32] `json:"h1,omitempty"`
H2 *badoption.Range[uint32] `json:"h2,omitempty"`
H3 *badoption.Range[uint32] `json:"h3,omitempty"`
H4 *badoption.Range[uint32] `json:"h4,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"`
HeaderProtectionKey string `json:"header_protection_key,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

@@ -106,7 +106,12 @@ func (h *Outbound) DialContext(ctx context.Context, network string, destination
if err != nil {
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) {
@@ -114,12 +119,17 @@ func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (n
if err != nil {
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) {
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 {
h.logger.ErrorContext(ctx, 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.InboundType = h.Type()
h.router.RouteConnectionEx(ctx, conn, metadata, onClose)
h.router.RouteConnectionEx(ctx, wrappedConn, metadata, onClose)
return
}

View File

@@ -2,6 +2,7 @@ package bandwidth
import (
"context"
"io"
"net"
"strconv"
"sync"
@@ -191,7 +192,7 @@ func (s *UsersBandwidthStrategy) getStrategy(ctx context.Context, metadata *adap
}
type bwConnEntry struct {
conn net.Conn
conn io.Closer
}
type ManagerBandwidthStrategy struct {
@@ -251,7 +252,25 @@ func (s *ManagerBandwidthStrategy) wrapPacketConn(ctx context.Context, conn net.
if !ok {
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) {

View File

@@ -82,7 +82,12 @@ func (h *Outbound) DialContext(ctx context.Context, network string, destination
if err != nil {
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) {
@@ -90,11 +95,16 @@ func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (n
if err != nil {
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) {
conn, err := h.strategy.wrapConn(ctx, conn, &metadata, false)
wrappedConn, err := h.strategy.wrapConn(ctx, conn, &metadata, false)
if err != nil {
if err.Error() != "traffic limit exceeded" {
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.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) {

View File

@@ -2,6 +2,7 @@ package traffic
import (
"context"
"io"
"net"
"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) {
limiter, err := s.limiterStrategy.getLimiter(ctx, metadata)
if err != nil {
return conn, err
return nil, err
}
_, err = limiter.Reserve(0)
if err != nil {
return conn, err
return nil, err
}
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) {
limiter, err := s.limiterStrategy.getLimiter(ctx, metadata)
if err != nil {
return conn, err
return nil, err
}
_, err = limiter.Reserve(0)
if err != nil {
return conn, err
return nil, err
}
return s.packetConnWrapper(ctx, conn, limiter, reverse), nil
}
@@ -73,7 +74,7 @@ func (s *GlobalTrafficStrategy) getLimiter(ctx context.Context, metadata *adapte
}
type connEntry struct {
conn net.Conn
conn io.Closer
}
type ManagerTrafficStrategy struct {
@@ -94,7 +95,7 @@ func (s *ManagerTrafficStrategy) wrapConn(ctx context.Context, conn net.Conn, me
if err != nil {
return nil, err
}
wrapped, err := strategy.wrapConn(ctx, conn, metadata, reverse)
wrappedConn, err := strategy.wrapConn(ctx, conn, metadata, reverse)
if err != nil {
return nil, err
}
@@ -102,7 +103,7 @@ func (s *ManagerTrafficStrategy) wrapConn(ctx context.Context, conn net.Conn, me
s.mtx.Lock()
s.conns[user] = append(s.conns[user], entry)
s.mtx.Unlock()
return onclose.NewConn(wrapped, func() {
return onclose.NewConn(wrappedConn, func() {
s.mtx.Lock()
entries := s.conns[user]
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) {
strategy, _, err := s.getStrategy(ctx, metadata)
strategy, user, err := s.getStrategy(ctx, metadata)
if err != nil {
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) {

View File

@@ -29,13 +29,14 @@ func RegisterClientEndpoint(registry *endpoint.Registry) {
type ClientEndpoint struct {
outbound.Adapter
ctx context.Context
outbound adapter.Outbound
router adapter.ConnectionRouterEx
logger logger.ContextLogger
address IPv4
key uuid.UUID
uotClient *uot.Client
ctx context.Context
outbound adapter.Outbound
router adapter.ConnectionRouterEx
logger logger.ContextLogger
address IPv4
key uuid.UUID
defaultGateway IPv4
uotClient *uot.Client
}
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 {
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{
Adapter: outbound.NewAdapter(C.TypeVPNClient, tag, []string{N.NetworkTCP, N.NetworkUDP}, []string{}),
ctx: ctx,
router: sbUot.NewRouter(router, logger),
logger: logger,
address: address.As4(),
key: key,
Adapter: outbound.NewAdapter(C.TypeVPNClient, tag, []string{N.NetworkTCP, N.NetworkUDP}, []string{}),
ctx: ctx,
router: sbUot.NewRouter(router, logger),
logger: logger,
address: address.As4(),
key: key,
defaultGateway: defaultGateway,
}
outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx)
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 {
return nil, err
}
gateway := Loopback.As4()
gateway := c.defaultGateway
if metadata := adapter.ContextFrom(ctx); metadata != nil {
if metadata.Gateway != nil {
gateway = metadata.Gateway.As4()

View File

@@ -29,15 +29,16 @@ func RegisterServerEndpoint(registry *endpoint.Registry) {
type ServerEndpoint struct {
outbound.Adapter
logger logger.ContextLogger
inbounds []adapter.Inbound
router adapter.ConnectionRouterEx
address IPv4
addresses map[uuid.UUID]IPv4
keys map[IPv4]uuid.UUID
conns map[IPv4]chan net.Conn
timeout time.Duration
uotClient *uot.Client
logger logger.ContextLogger
inbounds []adapter.Inbound
router adapter.ConnectionRouterEx
address IPv4
addresses map[uuid.UUID]IPv4
keys map[IPv4]uuid.UUID
conns map[IPv4]chan net.Conn
timeout time.Duration
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) {
@@ -51,6 +52,13 @@ func NewServerEndpoint(ctx context.Context, router adapter.Router, logger log.Co
router: sbUot.NewRouter(router, logger),
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)
inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx)
inbounds := make([]adapter.Inbound, len(options.Inbounds))
@@ -124,6 +132,8 @@ func (s *ServerEndpoint) DialContext(ctx context.Context, network string, destin
Addr: Loopback,
Port: destination.Port,
}
} else if s.defaultGateway != nil {
gateway = s.defaultGateway
} else {
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]
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(
ctx,
router,
@@ -101,7 +120,7 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL
Workers: options.Workers,
PreallocatedBuffersPerPool: options.PreallocatedBuffersPerPool,
DisablePauses: options.DisablePauses,
Amnezia: options.Amnezia,
Amnezia: amnezia,
DialerOptions: options.DialerOptions,
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
if options.Amnezia != nil {
amnezia = &wireguard.AmneziaOptions{
JC: options.Amnezia.JC,
JMin: options.Amnezia.JMin,
JMax: options.Amnezia.JMax,
S1: options.Amnezia.S1,
S2: options.Amnezia.S2,
S3: options.Amnezia.S3,
S4: options.Amnezia.S4,
H1: options.Amnezia.H1,
H2: options.Amnezia.H2,
H3: options.Amnezia.H3,
H4: options.Amnezia.H4,
I1: options.Amnezia.I1,
I2: options.Amnezia.I2,
I3: options.Amnezia.I3,
I4: options.Amnezia.I4,
I5: options.Amnezia.I5,
J1: options.Amnezia.J1,
J2: options.Amnezia.J2,
J3: options.Amnezia.J3,
ITime: options.Amnezia.ITime,
JC: options.Amnezia.JC,
JMin: options.Amnezia.JMin,
JMax: options.Amnezia.JMax,
S1: options.Amnezia.S1,
S2: options.Amnezia.S2,
S3: options.Amnezia.S3,
S4: options.Amnezia.S4,
H1: options.Amnezia.H1,
H2: options.Amnezia.H2,
H3: options.Amnezia.H3,
H4: options.Amnezia.H4,
I1: options.Amnezia.I1,
I2: options.Amnezia.I2,
I3: options.Amnezia.I3,
I4: options.Amnezia.I4,
I5: options.Amnezia.I5,
HeaderProtectionKey: options.Amnezia.HeaderProtectionKey,
ContentPaddingAddition: options.Amnezia.ContentPaddingAddition,
RekeyAfterTime: options.Amnezia.RekeyAfterTime,
RekeyTimeout: options.Amnezia.RekeyTimeout,
RejectAfterTime: options.Amnezia.RejectAfterTime,
KeepaliveTimeout: options.Amnezia.KeepaliveTimeout,
MaxHandshakeAttempts: options.Amnezia.MaxHandshakeAttempts,
}
}
wgEndpoint, err := wireguard.NewEndpoint(wireguard.EndpointOptions{

View File

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

View File

@@ -235,17 +235,30 @@ func (e *Endpoint) Start(resolve bool) error {
if e.options.Amnezia.I5 != "" {
ipcConf.WriteString("\ni5=" + e.options.Amnezia.I5)
}
if e.options.Amnezia.J1 != "" {
ipcConf.WriteString("\nj1=" + e.options.Amnezia.J1)
if e.options.Amnezia.HeaderProtectionKey != "" {
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 != "" {
ipcConf.WriteString("\nj2=" + e.options.Amnezia.J2)
if e.options.Amnezia.ContentPaddingAddition != nil {
ipcConf.WriteString("\ncontent_padding_addition=" + e.options.Amnezia.ContentPaddingAddition.String())
}
if e.options.Amnezia.J3 != "" {
ipcConf.WriteString("\nj3=" + e.options.Amnezia.J3)
if e.options.Amnezia.RekeyAfterTime != nil {
ipcConf.WriteString("\nrekey_after_time=" + e.options.Amnezia.RekeyAfterTime.String())
}
if e.options.Amnezia.ITime > 0 {
ipcConf.WriteString("\nitime=" + strconv.FormatInt(e.options.Amnezia.ITime, 10))
if e.options.Amnezia.RekeyTimeout != nil {
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 {

View File

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