Compare commits

...

15 Commits

Author SHA1 Message Date
Shtorm
4d0eedd474 Merge pull request #124 from anzix/extended-anytls-parser
feat(parser): Adding support parsing AnyTLS outbound
2026-08-03 07:47:44 +03:00
anzix
2979fe6e7f feat(parser): update README.md to mention AnyTLS 2026-08-01 12:42:14 +05:00
anzix
a22671d7c4 feat(parser): update the parsing documentation for provider 2026-08-01 12:14:50 +05:00
anzix
1dd63d4dcb feat(parser): update the parsing documentation 2026-08-01 11:47:12 +05:00
anzix
9d1745d29d feat(parser): adding support AnyTLS 2026-08-01 10:57:04 +05:00
Shtorm
c243a5b6e5 Merge branch 'extended' of https://github.com/shtorm-7/sing-box-extended into extended 2026-07-25 21:18:16 +03:00
Shtorm
b4ec688f1a Fix MASQUE endpoint parsing 2026-07-25 21:17:48 +03:00
Shtorm
2e083b2d8f Update sing-vmess 2026-07-25 21:16:01 +03:00
Shtorm
909f19640c Add Dial Fields comment to client.json
Signed-off-by: Shtorm <108103062+shtorm-7@users.noreply.github.com>
2026-07-24 15:59:49 +03:00
Shtorm
a79e16c03c Merge pull request #118 from embogomolov/fix/interrupt-group-deadlock-extended
common/interrupt: close connections outside group lock
2026-07-22 14:22:08 +03:00
embogomolov
d2805ffcc9 common/interrupt: close connections outside group lock 2026-07-22 14:09:56 +03:00
Shtorm
7c19b660b4 Merge pull request #116 from embogomolov/fix/xhttp-h3-udp-socket-leak
transport/v2rayxhttp: close UDP connection when HTTP/3 dial fails
2026-07-22 13:35:15 +03:00
embogomolov
33907d2e23 transport/v2rayxhttp: close UDP connection when HTTP/3 dial fails 2026-07-22 11:41:41 +03:00
Shtorm
ea48501b1d Merge branch 'extended' of https://github.com/shtorm-7/sing-box-extended into extended 2026-07-18 16:40:41 +03:00
Shtorm
425bfaf779 Update Telegram badge to include logo
Signed-off-by: Shtorm <108103062+shtorm-7@users.noreply.github.com>
2026-07-17 16:58:08 +03:00
12 changed files with 160 additions and 14 deletions

View File

@@ -3,7 +3,7 @@
[![license](https://img.shields.io/badge/license-GPLv3-blue.svg)](LICENSE)
[![go](https://img.shields.io/badge/go-1.26-00ADD8.svg)](go.mod)
[![codeberg](https://img.shields.io/badge/mirror-codeberg-2185D0.svg)](https://codeberg.org/shtorm-7/sing-box-extended)
[![telegram](https://img.shields.io/badge/telegram-chat-26A5E4.svg)](https://t.me/sing_box_extended)
[![telegram](https://img.shields.io/badge/telegram-chat-26A5E4.svg?logo=telegram)](https://t.me/sing_box_extended)
Sing-box with extended features.
@@ -50,7 +50,7 @@ Sing-box with extended features.
### Miscellaneous
- **Providers** — Outbound subscriptions from local files, inline lists, or remote URLs (sing-box JSON, Clash YAML, SIP008, share links)
- **Link Parser** — Outbound configured from a share link (VLESS, VMess, Shadowsocks, Trojan, Hysteria, Hysteria2, TUIC)
- **Link Parser** — Outbound configured from a share link (VLESS, VMess, Shadowsocks, Trojan, Hysteria, Hysteria2, TUIC, AnyTLS)
- **Extended WireGuard options** — Advanced configuration capabilities
- **Unified Delay** — Unified latency measurement

View File

@@ -29,8 +29,8 @@ type Conn struct {
func (c *Conn) Close() error {
c.group.access.Lock()
defer c.group.access.Unlock()
c.group.connections.Remove(c.element)
c.group.access.Unlock()
return c.Conn.Close()
}
@@ -58,8 +58,8 @@ type PacketConn struct {
func (c *PacketConn) Close() error {
c.group.access.Lock()
defer c.group.access.Unlock()
c.group.connections.Remove(c.element)
c.group.access.Unlock()
return c.PacketConn.Close()
}
@@ -87,8 +87,8 @@ type SingPacketConn struct {
func (c *SingPacketConn) Close() error {
c.group.access.Lock()
defer c.group.access.Unlock()
c.group.connections.Remove(c.element)
c.group.access.Unlock()
return c.PacketConn.Close()
}

View File

@@ -47,15 +47,19 @@ func (g *Group) NewSingPacketConn(conn N.PacketConn, isExternal bool, isProvider
func (g *Group) Interrupt(interruptExternalConnections bool) {
g.access.Lock()
defer g.access.Unlock()
var toDelete []*list.Element[*groupConnItem]
var toClose []io.Closer
for element := g.connections.Front(); element != nil; element = element.Next() {
if !element.Value.isExternal || interruptExternalConnections {
element.Value.conn.Close()
toDelete = append(toDelete, element)
toClose = append(toClose, element.Value.conn)
}
}
for _, element := range toDelete {
g.connections.Remove(element)
}
g.access.Unlock()
for _, conn := range toClose {
_ = conn.Close()
}
}

View File

@@ -0,0 +1,66 @@
package interrupt
import (
"net"
"sync"
"testing"
"time"
)
type closeBarrierConn struct {
net.Conn
barrier *sync.WaitGroup
}
func (c *closeBarrierConn) Close() error {
c.barrier.Done()
c.barrier.Wait()
return c.Conn.Close()
}
func TestNestedGroupsInterruptWithoutDeadlock(t *testing.T) {
groupA := NewGroup()
groupB := NewGroup()
barrier := &sync.WaitGroup{}
barrier.Add(2)
barrierA, barrierAPeer := net.Pipe()
barrierB, barrierBPeer := net.Pipe()
t.Cleanup(func() {
barrierAPeer.Close()
barrierBPeer.Close()
})
groupA.NewConn(&closeBarrierConn{Conn: barrierA, barrier: barrier}, true, false)
groupB.NewConn(&closeBarrierConn{Conn: barrierB, barrier: barrier}, true, false)
connA, connAPeer := net.Pipe()
connB, connBPeer := net.Pipe()
t.Cleanup(func() {
connAPeer.Close()
connBPeer.Close()
})
wrapperA := groupA.NewConn(connA, true, false)
wrapperB := groupB.NewConn(connB, true, false)
groupA.NewConn(wrapperB, true, false)
groupB.NewConn(wrapperA, true, false)
done := make(chan struct{}, 2)
go func() {
groupA.Interrupt(true)
done <- struct{}{}
}()
go func() {
groupB.Interrupt(true)
done <- struct{}{}
}()
timeout := time.NewTimer(time.Second)
defer timeout.Stop()
for range 2 {
select {
case <-done:
case <-timeout.C:
t.Fatal("nested group interrupt deadlocked")
}
}
}

View File

@@ -25,8 +25,9 @@
{
"type": "parser",
"tag": "vless-out",
// Supported protocols: hysteria, hysteria2, shadowsocks, trojan, tuic, vless, vmess
// Supported protocols: hysteria, hysteria2, shadowsocks, trojan, tuic, vless, vmess, anytls
"link": "vless://b5e41c8c-c437-4689-b863-76208a3efb4b@0.0.0.0:443?..."
// Dial Fields
}
],
"route": {

View File

@@ -46,7 +46,7 @@
// - sing-box JSON ({ "outbounds": [...] })
// - Clash YAML
// - SIP008 (shadowsocks)
// - Raw shareable links (vless://, vmess://, ss://, trojan://, ...)
// - Raw shareable links (vless://, vmess://, ss://, trojan://, anytls://, ...)
"path": "subscriptions/my-sub.txt",
// Remove emoji flags from proxy names.
"remove_emojis": true,

2
go.mod
View File

@@ -231,7 +231,7 @@ replace github.com/sagernet/sing-mux => github.com/shtorm-7/sing-mux v0.3.4-exte
replace github.com/ameshkov/dnscrypt/v2 => github.com/shtorm-7/dnscrypt/v2 v2.4.0-extended-1.0.0
replace github.com/sagernet/sing-vmess => github.com/shtorm-7/sing-vmess v0.2.7-extended-1.0.0
replace github.com/sagernet/sing-vmess => github.com/shtorm-7/sing-vmess v0.2.8-extended-1.0.0
replace github.com/dolonet/mtg-multi => github.com/shtorm-7/mtg-multi v1.11.0-extended-1.0.0

4
go.sum
View File

@@ -387,8 +387,8 @@ github.com/shtorm-7/sing v0.8.10-extended-1.2.0 h1:5yw9j0+P2QkRWvxBvb71wvNdpAlHm
github.com/shtorm-7/sing v0.8.10-extended-1.2.0/go.mod h1:olXxWQNqRW/l2Q6JI3b2Qmz8iQnIFlOeeH8bx6JhgUA=
github.com/shtorm-7/sing-mux v0.3.4-extended-1.0.0 h1:a5OoXr3e2ACbM6vDIaaGL44IdHQ6wPjcSoU13vfC0Sw=
github.com/shtorm-7/sing-mux v0.3.4-extended-1.0.0/go.mod h1:QvlKMyNBNrQoyX4x+gq028uPbLM2XeRpWtDsWBJbFSk=
github.com/shtorm-7/sing-vmess v0.2.7-extended-1.0.0 h1:WVheKmQH5hSQbJU1ZTKthKSutkTLWSb2hp4JuQhJBow=
github.com/shtorm-7/sing-vmess v0.2.7-extended-1.0.0/go.mod h1:5aYoOtYksAyS0NXDm0qKeTYW1yoE1bJVcv+XLcVoyJs=
github.com/shtorm-7/sing-vmess v0.2.8-extended-1.0.0 h1:OjvqZOhYSi9eIJGYC0iPDPqvgo/asujvhAGkFzbZ5/Y=
github.com/shtorm-7/sing-vmess v0.2.8-extended-1.0.0/go.mod h1:P11scgTxMxVVQ8dlM27yNm3Cro40mD0+gHbnqrNGDuY=
github.com/shtorm-7/tailscale v1.92.4-sing-box-1.13-mod.7-extended-1.0.2 h1:hSMjh97OszszOd8HrzpaYUQH9dWRRBluJCbwQyz8ZOk=
github.com/shtorm-7/tailscale v1.92.4-sing-box-1.13-mod.7-extended-1.0.2/go.mod h1:TYIIqO5sZpWq873rLIeO2usszSMUpR3h6WdqVVs65ug=
github.com/shtorm-7/wireguard-go v0.0.2-beta.1-extended-1.4.3 h1:jtOA73D4F5qRV70//ahOt20KBnWvQimAFjtIiOtt0ps=

63
parser/link/anytls.go Normal file
View File

@@ -0,0 +1,63 @@
package link
import (
"net/url"
"strings"
"github.com/sagernet/sing-box/common"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
)
func parseAnyTLSLink(link string) (option.Outbound, error) {
linkURL, err := url.Parse(link)
if err != nil {
return option.Outbound{}, err
}
if linkURL.User == nil || linkURL.User.Username() == "" {
return option.Outbound{}, E.New("missing password")
}
var options option.AnyTLSOutboundOptions
TLSOptions := option.OutboundTLSOptions{
Enabled: true,
ECH: &option.OutboundECHOptions{},
UTLS: &option.OutboundUTLSOptions{},
Reality: &option.OutboundRealityOptions{},
}
options.Server = linkURL.Hostname()
TLSOptions.ServerName = linkURL.Hostname()
options.ServerPort = common.StringToType[uint16](linkURL.Port())
options.Password = linkURL.User.Username()
proxy := map[string]string{}
for key, values := range linkURL.Query() {
value := values[0]
proxy[key] = value
}
for key, value := range proxy {
switch key {
case "insecure":
if value == "1" || value == "true" {
TLSOptions.Insecure = true
}
case "sni":
TLSOptions.ServerName = value
case "alpn":
TLSOptions.ALPN = strings.Split(value, ",")
case "fp":
TLSOptions.UTLS.Enabled = true
TLSOptions.UTLS.Fingerprint = value
case "tfo", "tcp-fast-open", "tcp_fast_open":
if value == "1" || value == "true" {
options.TCPFastOpen = true
}
}
}
outbound := option.Outbound{
Type: C.TypeAnyTLS,
Tag: linkURL.Fragment,
}
options.TLS = &TLSOptions
outbound.Options = &options
return outbound, nil
}

View File

@@ -28,6 +28,8 @@ func ParseSubscriptionLink(link string) (option.Outbound, error) {
return parseHysteriaLink(link)
case "hy2", "hysteria2":
return parseHysteria2Link(link)
case "anytls":
return parseAnyTLSLink(link)
}
result[3], _ = common.DecodeBase64URLSafe(result[3])
link = strings.Join(result[1:], "")

View File

@@ -77,7 +77,16 @@ func ConnectTunnel(ctx context.Context, dialer N.Dialer, tlsConfig aTLS.Config,
if !ok || quicEndpoint == nil {
return nil, nil, nil, errors.New("missing HTTP/3 UDP endpoint")
}
udpConn, err := dialer.ListenPacket(ctx, M.SocksaddrFromNetIP(quicEndpoint.AddrPort()))
endpointAddrPort := quicEndpoint.AddrPort()
udpConn, err := dialer.ListenPacket(
ctx,
M.SocksaddrFromNetIP(
netip.AddrPortFrom(
endpointAddrPort.Addr().Unmap(),
endpointAddrPort.Port(),
),
),
)
if err != nil {
return nil, nil, nil, err
}

View File

@@ -348,6 +348,7 @@ func createHTTPClient(ctx context.Context, dest M.Socksaddr, dialer N.Dialer, op
}
conn, dErr := qtls.DialEarly(ctx, bufio.NewUnbindPacketConn(udpConn), udpConn.RemoteAddr(), tlsConfig, cfg)
if dErr != nil {
_ = udpConn.Close()
return nil, dErr
}
if congestionControlFactory != nil {