diff --git a/README.md b/README.md index ea5b6371..35d50eb5 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/examples/parser/client.json b/examples/parser/client.json index 7c1bf774..2d589719 100644 --- a/examples/parser/client.json +++ b/examples/parser/client.json @@ -25,7 +25,7 @@ { "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 } diff --git a/examples/provider/local.json b/examples/provider/local.json index 5f85e917..d16e08ae 100644 --- a/examples/provider/local.json +++ b/examples/provider/local.json @@ -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, diff --git a/parser/link/anytls.go b/parser/link/anytls.go new file mode 100644 index 00000000..3eea385e --- /dev/null +++ b/parser/link/anytls.go @@ -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 +} diff --git a/parser/link/parser.go b/parser/link/parser.go index 72e35ab6..092df20c 100644 --- a/parser/link/parser.go +++ b/parser/link/parser.go @@ -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:], "")