Update On Sat Sep 6 20:35:54 CEST 2025

This commit is contained in:
github-action[bot]
2025-09-06 20:35:55 +02:00
parent 20396b5039
commit 93741b781f
164 changed files with 7460 additions and 3057 deletions

View File

@@ -79,12 +79,15 @@ type BoxInstance struct {
pauseManager pause.Manager
}
func NewSingBoxInstance(config string) (b *BoxInstance, err error) {
func NewSingBoxInstance(config string, localTransport LocalDNSTransport) (b *BoxInstance, err error) {
defer device.DeferPanicToError("NewSingBoxInstance", func(err_ error) { err = err_ })
// create box context
ctx, cancel := context.WithCancel(context.Background())
ctx = box.Context(ctx, nekoboxAndroidInboundRegistry(), nekoboxAndroidOutboundRegistry(), nekoboxAndroidEndpointRegistry(), nekoboxAndroidDNSTransportRegistry(), nekoboxAndroidServiceRegistry())
ctx = box.Context(ctx,
nekoboxAndroidInboundRegistry(), nekoboxAndroidOutboundRegistry(), nekoboxAndroidEndpointRegistry(),
nekoboxAndroidDNSTransportRegistry(localTransport), nekoboxAndroidServiceRegistry(),
)
ctx = service.ContextWithDefaultRegistry(ctx)
service.MustRegister[platform.Interface](ctx, boxPlatformInterfaceInstance)

View File

@@ -1,6 +1,9 @@
package libcore
import (
"context"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/adapter/endpoint"
"github.com/sagernet/sing-box/adapter/inbound"
"github.com/sagernet/sing-box/adapter/outbound"
@@ -11,6 +14,8 @@ import (
"github.com/sagernet/sing-box/dns/transport/hosts"
"github.com/sagernet/sing-box/dns/transport/local"
"github.com/sagernet/sing-box/dns/transport/quic"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/protocol/anytls"
"github.com/sagernet/sing-box/protocol/block"
"github.com/sagernet/sing-box/protocol/direct"
@@ -35,7 +40,6 @@ import (
_ "github.com/sagernet/sing-box/experimental/clashapi"
_ "github.com/sagernet/sing-box/transport/v2rayquic"
_ "github.com/sagernet/sing-dns/quic"
)
func nekoboxAndroidInboundRegistry() *inbound.Registry {
@@ -92,7 +96,7 @@ func nekoboxAndroidEndpointRegistry() *endpoint.Registry {
return registry
}
func nekoboxAndroidDNSTransportRegistry() *dns.TransportRegistry {
func nekoboxAndroidDNSTransportRegistry(localTransport LocalDNSTransport) *dns.TransportRegistry {
registry := dns.NewTransportRegistry()
transport.RegisterTCP(registry)
@@ -100,12 +104,23 @@ func nekoboxAndroidDNSTransportRegistry() *dns.TransportRegistry {
transport.RegisterTLS(registry)
transport.RegisterHTTPS(registry)
hosts.RegisterTransport(registry)
local.RegisterTransport(registry)
// local.RegisterTransport(registry)
fakeip.RegisterTransport(registry)
quic.RegisterTransport(registry)
quic.RegisterHTTP3Transport(registry)
if localTransport == nil {
local.RegisterTransport(registry)
} else {
dns.RegisterTransport(registry, "local", func(ctx context.Context, logger log.ContextLogger, tag string, options option.LocalDNSServerOptions) (adapter.DNSTransport, error) {
return &platformLocalDNSTransport{
iif: localTransport,
tag: tag,
}, nil
})
}
return registry
}

View File

@@ -13,9 +13,11 @@ func GoDebug(any interface{}) {
}
}
func DeferPanicToError(name string, err func(error)) {
func DeferPanicToError(name string, onError func(error)) {
if r := recover(); r != nil {
s := fmt.Errorf("%s panic: %s\n%s", name, r, string(debug.Stack()))
err(s)
if onError != nil {
s := fmt.Errorf("%s panic: %s\n%s", name, r, string(debug.Stack()))
onError(s)
}
}
}

View File

@@ -8,7 +8,9 @@ import (
"strings"
"syscall"
dns "github.com/sagernet/sing-dns"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/dns"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
@@ -23,29 +25,14 @@ type LocalDNSTransport interface {
Exchange(ctx *ExchangeContext, message []byte) error
}
func RegisterLocalDNSTransport(transport LocalDNSTransport) {
if transport == nil {
dns.RegisterTransport([]string{"local"}, dns.CreateTransport)
} else {
dns.RegisterTransport([]string{"local"}, func(options dns.TransportOptions) (dns.Transport, error) {
return &platformLocalDNSTransport{
iif: transport,
}, nil
})
}
}
var _ dns.Transport = (*platformLocalDNSTransport)(nil)
var gLocalDNSTransport *platformLocalDNSTransport = nil
type platformLocalDNSTransport struct {
iif LocalDNSTransport
tag string
}
func (p *platformLocalDNSTransport) Name() string {
return "local"
}
func (p *platformLocalDNSTransport) Start() error {
func (p *platformLocalDNSTransport) Start(adapter.StartStage) error {
return nil
}
@@ -82,12 +69,12 @@ func (p *platformLocalDNSTransport) Exchange(ctx context.Context, message *mDNS.
})
}
func (p *platformLocalDNSTransport) Lookup(ctx context.Context, domain string, strategy dns.DomainStrategy) ([]netip.Addr, error) {
func (p *platformLocalDNSTransport) Lookup(ctx context.Context, domain string, strategy constant.DomainStrategy) ([]netip.Addr, error) {
var network string
switch strategy {
case dns.DomainStrategyUseIPv4:
case constant.DomainStrategyIPv4Only:
network = "ip4"
case dns.DomainStrategyPreferIPv6:
case constant.DomainStrategyPreferIPv6:
network = "ip6"
default:
network = "ip"
@@ -105,11 +92,11 @@ func (p *platformLocalDNSTransport) Lookup(ctx context.Context, domain string, s
return response.error
}
switch strategy {
case dns.DomainStrategyUseIPv4:
case constant.DomainStrategyIPv4Only:
responseAddr = common.Filter(response.addresses, func(it netip.Addr) bool {
return it.Is4()
})
case dns.DomainStrategyPreferIPv6:
case constant.DomainStrategyPreferIPv6:
responseAddr = common.Filter(response.addresses, func(it netip.Addr) bool {
return it.Is6()
})
@@ -123,6 +110,18 @@ func (p *platformLocalDNSTransport) Lookup(ctx context.Context, domain string, s
})
}
func (p *platformLocalDNSTransport) Tag() string {
return p.tag
}
func (p *platformLocalDNSTransport) Type() string {
return "local"
}
func (p *platformLocalDNSTransport) Dependencies() []string {
return nil
}
type Func interface {
Invoke() error
}
@@ -157,7 +156,7 @@ func (c *ExchangeContext) RawSuccess(result []byte) {
}
func (c *ExchangeContext) ErrorCode(code int32) {
c.error = dns.RCodeError(code)
c.error = dns.RcodeError(code)
}
func (c *ExchangeContext) ErrnoCode(code int32) {

View File

@@ -0,0 +1,83 @@
package ech
import (
"context"
"crypto/tls"
"encoding/base64"
"net"
"os"
mDNS "github.com/miekg/dns"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/dns"
"github.com/sagernet/sing/common/exceptions"
)
type ECHClientConfig struct {
*tls.Config
domain string
localDnsTransport adapter.DNSTransport
}
func NewECHClientConfig(domain string, tlsConfig *tls.Config, localDnsTransport adapter.DNSTransport) *ECHClientConfig {
config := tlsConfig.Clone()
config.ServerName = domain
return &ECHClientConfig{
Config: config,
domain: domain,
localDnsTransport: localDnsTransport,
}
}
// ClientHandshake 封装 TLS 握手
func (s *ECHClientConfig) ClientHandshake(ctx context.Context, conn net.Conn) (*tls.Conn, error) {
tlsConn, err := s.fetchAndHandshake(ctx, conn)
if err != nil {
return nil, err
}
err = tlsConn.HandshakeContext(ctx)
if err != nil {
return nil, err
}
return tlsConn, nil
}
// fetchAndHandshake 查询 ECHConfigList 并完成 TLS 连接
func (s *ECHClientConfig) fetchAndHandshake(ctx context.Context, conn net.Conn) (*tls.Conn, error) {
message := &mDNS.Msg{
MsgHdr: mDNS.MsgHdr{
RecursionDesired: true,
},
Question: []mDNS.Question{
{
Name: mDNS.Fqdn(s.domain),
Qtype: mDNS.TypeHTTPS,
Qclass: mDNS.ClassINET,
},
},
}
if s.localDnsTransport == nil {
return nil, os.ErrInvalid
}
response, err := s.localDnsTransport.Exchange(ctx, message)
if err != nil {
return nil, exceptions.Cause(err, "fetch ECH config list")
}
if response.Rcode != mDNS.RcodeSuccess {
return nil, exceptions.Cause(dns.RcodeError(response.Rcode), "fetch ECH config list")
}
for _, rr := range response.Answer {
switch resource := rr.(type) {
case *mDNS.HTTPS:
for _, value := range resource.Value {
if value.Key().String() == "ech" {
echConfigList, err := base64.StdEncoding.DecodeString(value.String())
if err == nil {
s.Config.EncryptedClientHelloConfigList = echConfigList
}
}
}
}
}
return tls.Client(conn, s.Config), nil
}

View File

@@ -8,9 +8,9 @@ require (
github.com/matsuridayo/libneko v1.0.0 // replaced
github.com/miekg/dns v1.1.67
github.com/oschwald/maxminddb-golang v1.13.1
github.com/sagernet/quic-go v0.52.0-beta.1
github.com/sagernet/sing v0.7.6-0.20250825114712-2aeec120ce28
github.com/sagernet/sing-box v1.0.0 // replaced
github.com/sagernet/sing-dns v0.4.1
github.com/sagernet/sing-tun v0.7.0-beta.1
github.com/ulikunitz/xz v0.5.11
golang.org/x/mobile v0.0.0-20231108233038-35478a0c49da
@@ -52,7 +52,6 @@ require (
github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb // indirect
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect
github.com/sagernet/nftables v0.3.0-beta.4 // indirect
github.com/sagernet/quic-go v0.52.0-beta.1 // indirect
github.com/sagernet/sing-mux v0.3.3 // indirect
github.com/sagernet/sing-quic v0.5.0 // indirect
github.com/sagernet/sing-shadowsocks v0.2.8 // indirect
@@ -90,5 +89,3 @@ replace github.com/sagernet/sing-box => ../../sing-box
// replace github.com/sagernet/sing-quic => ../../sing-quic
// replace github.com/sagernet/sing => ../../sing
// replace github.com/sagernet/sing-dns => ../../sing-dns

View File

@@ -91,8 +91,6 @@ github.com/sagernet/quic-go v0.52.0-beta.1/go.mod h1:OV+V5kEBb8kJS7k29MzDu6oj9Gy
github.com/sagernet/sing v0.6.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing v0.7.6-0.20250825114712-2aeec120ce28 h1:C8Lnqd0Q+C15kwaMiDsfq5S45rhhaQMBG91TT+6oFVo=
github.com/sagernet/sing v0.7.6-0.20250825114712-2aeec120ce28/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing-dns v0.4.1 h1:nozS7iqpxZ7aV73oHbkD/8haOvf3XXDCgT//8NdYirk=
github.com/sagernet/sing-dns v0.4.1/go.mod h1:dweQs54ng2YGzoJfz+F9dGuDNdP5pJ3PLeggnK5VWc8=
github.com/sagernet/sing-mux v0.3.3 h1:YFgt9plMWzH994BMZLmyKL37PdIVaIilwP0Jg+EcLfw=
github.com/sagernet/sing-mux v0.3.3/go.mod h1:pht8iFY4c9Xltj7rhVd208npkNaeCxzyXCgulDPLUDA=
github.com/sagernet/sing-quic v0.5.0 h1:jNLIyVk24lFPvu8A4x+ZNEnZdI+Tg1rp7eCJ6v0Csak=

View File

@@ -10,24 +10,34 @@ import (
"errors"
"fmt"
"io"
"libcore/device"
"libcore/ech"
"log"
"net"
"net/http"
"net/url"
"os"
"strconv"
"sync"
"sync/atomic"
"time"
"github.com/sagernet/quic-go"
"github.com/sagernet/quic-go/http3"
"github.com/sagernet/sing/common/metadata"
"github.com/sagernet/sing/protocol/socks"
"github.com/sagernet/sing/protocol/socks/socks5"
)
var errFailConnectSocks5 = errors.New("fail connect socks5")
type HTTPClient interface {
RestrictedTLS()
ModernTLS()
PinnedTLS12()
PinnedSHA256(sumHex string)
TrySocks5(port int32)
TryH3Direct()
KeepAlive()
NewRequest() HTTPRequest
Close()
@@ -58,16 +68,18 @@ var (
)
type httpClient struct {
tls tls.Config
client http.Client
transport http.Transport
tls tls.Config
h1h2Transport http.Transport
h1h2Client http.Client
trySocks5 bool
tryH3Direct bool
}
func NewHttpClient() HTTPClient {
client := new(httpClient)
client.client.Transport = &client.transport
client.transport.TLSClientConfig = &client.tls
client.transport.DisableKeepAlives = true
client.h1h2Client.Transport = &client.h1h2Transport
client.h1h2Transport.TLSClientConfig = &client.tls
client.h1h2Transport.DisableKeepAlives = true
return client
}
@@ -104,25 +116,36 @@ func (c *httpClient) PinnedSHA256(sumHex string) {
func (c *httpClient) TrySocks5(port int32) {
dialer := new(net.Dialer)
c.transport.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
c.h1h2Transport.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
for {
socksConn, err := dialer.DialContext(ctx, "tcp", "127.0.0.1:"+strconv.Itoa(int(port)))
if err != nil {
if c.tryH3Direct {
return nil, errFailConnectSocks5
}
break
}
_, err = socks.ClientHandshake5(socksConn, socks5.CommandConnect, metadata.ParseSocksaddr(addr), "", "")
if err != nil {
if c.tryH3Direct {
return nil, errFailConnectSocks5
}
break
}
return socksConn, err
}
return dialer.DialContext(ctx, network, addr)
}
c.trySocks5 = true
}
func (c *httpClient) TryH3Direct() {
c.tryH3Direct = true
}
func (c *httpClient) KeepAlive() {
c.transport.ForceAttemptHTTP2 = true
c.transport.DisableKeepAlives = false
c.h1h2Transport.ForceAttemptHTTP2 = true
c.h1h2Transport.DisableKeepAlives = false
}
func (c *httpClient) NewRequest() HTTPRequest {
@@ -135,7 +158,7 @@ func (c *httpClient) NewRequest() HTTPRequest {
}
func (c *httpClient) Close() {
c.transport.CloseIdleConnections()
c.h1h2Transport.CloseIdleConnections()
}
type httpRequest struct {
@@ -184,8 +207,17 @@ func (r *httpRequest) SetContentString(content string) {
}
func (r *httpRequest) Execute() (HTTPResponse, error) {
response, err := r.client.Do(&r.request)
defer device.DeferPanicToError("http execute", func(err error) { log.Println(err) })
// full direct
if r.tryH3Direct && !r.trySocks5 {
return r.doH3Direct()
}
response, err := r.h1h2Client.Do(&r.request)
if err != nil {
// trySocks5 && tryH3Direct
if r.tryH3Direct && errors.Is(err, errFailConnectSocks5) {
return r.doH3Direct()
}
return nil, err
}
httpResp := &httpResponse{Response: response}
@@ -195,6 +227,133 @@ func (r *httpRequest) Execute() (HTTPResponse, error) {
return httpResp, nil
}
type requestFunc func() (response *http.Response, err error)
func (r *httpRequest) doH3Direct() (HTTPResponse, error) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
successCh := make(chan *http.Response, 1)
var finalErr error
var failedCount atomic.Uint32
var successCount atomic.Uint32
var mu sync.Mutex
funcs := []requestFunc{
// 普通,不再重试 socks5
func() (response *http.Response, err error) {
request := r.request.Clone(context.Background())
h1h2Client := &http.Client{
Transport: &http.Transport{
DisableKeepAlives: true,
},
}
return h1h2Client.Do(request)
},
// ECH HTTPS
func() (response *http.Response, err error) {
request := r.request.Clone(context.Background())
echClient := &http.Client{
Transport: &http.Transport{
DialTLSContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
var d net.Dialer
c, err := d.DialContext(ctx, network, addr)
if err != nil {
return c, err
}
domain := addr
if host, _, _ := net.SplitHostPort(addr); host != "" {
domain = host
}
echTls := ech.NewECHClientConfig(domain, &r.tls, gLocalDNSTransport)
return echTls.ClientHandshake(ctx, c)
},
DisableKeepAlives: true,
},
}
return echClient.Do(request)
},
// H3 HTTPS
func() (response *http.Response, err error) {
request := r.request.Clone(context.Background())
h3Client := &http.Client{
Transport: &http3.Transport{
TLSClientConfig: r.tls.Clone(),
QUICConfig: &quic.Config{
MaxIdleTimeout: time.Second,
},
},
}
return h3Client.Do(request)
},
}
if r.request.URL.Scheme == "http" {
funcs = funcs[:1]
}
for i, f := range funcs {
go func(f requestFunc) {
defer device.DeferPanicToError("http", func(err error) { log.Println(err) })
defer func() {
if successCount.Load() == 0 {
if failedCount.Add(1) >= uint32(len(funcs)) {
// 全部失败了
cancel()
}
}
}()
var t string
switch i {
case 0:
t = "h1h2"
case 1:
t = "ech"
case 2:
t = "h3"
}
// 执行HTTP请求
rsp, err := f()
if rsp == nil || err != nil {
mu.Lock()
finalErr = errors.Join(finalErr, fmt.Errorf("%s: %w", t, err))
mu.Unlock()
if rsp != nil && rsp.Body != nil {
rsp.Body.Close()
}
return
}
// 处理 HTTP 状态码
if rsp.StatusCode != http.StatusOK {
hr := &httpResponse{Response: rsp}
err = fmt.Errorf("%s: %s", t, hr.errorString())
mu.Lock()
finalErr = errors.Join(finalErr, err)
mu.Unlock()
return
}
select {
case successCh <- rsp:
// 第一个成功的请求,不要关闭 body
successCount.Add(1)
default:
rsp.Body.Close()
}
}(f)
}
select {
case result := <-successCh:
return &httpResponse{Response: result}, nil
case <-ctx.Done():
return nil, finalErr
}
}
type httpResponse struct {
*http.Response

View File

@@ -5,7 +5,7 @@ import (
"libcore/device"
"os"
"path/filepath"
"runtime"
"runtime/debug"
"strings"
_ "unsafe"
@@ -29,12 +29,12 @@ func NekoLogClear() {
}
func ForceGc() {
go runtime.GC()
go debug.FreeOSMemory()
}
func InitCore(process, cachePath, internalAssets, externalAssets string,
maxLogSizeKb int32, logEnable bool,
if1 NB4AInterface, if2 BoxPlatformInterface,
if1 NB4AInterface, if2 BoxPlatformInterface, if3 LocalDNSTransport,
) {
defer device.DeferPanicToError("InitCore", func(err error) { log.Println(err) })
isBgProcess = strings.HasSuffix(process, ":bg")
@@ -43,6 +43,7 @@ func InitCore(process, cachePath, internalAssets, externalAssets string,
intfNB4A = if1
intfBox = if2
useProcfs = intfBox.UseProcFS()
gLocalDNSTransport = &platformLocalDNSTransport{iif: if3}
// Working dir
tmp := filepath.Join(cachePath, "../no_backup")
@@ -51,6 +52,8 @@ func InitCore(process, cachePath, internalAssets, externalAssets string,
// sing-box fs
resourcePaths = append(resourcePaths, externalAssets)
externalAssetsPath = externalAssets
internalAssetsPath = internalAssets
// Set up log
if maxLogSizeKb < 50 {
@@ -68,9 +71,6 @@ func InitCore(process, cachePath, internalAssets, externalAssets string,
defer device.DeferPanicToError("InitCore-go", func(err error) { log.Println(err) })
device.GoDebug(process)
externalAssetsPath = externalAssets
internalAssetsPath = internalAssets
// certs
pem, err := os.ReadFile(externalAssetsPath + "ca.pem")
if err == nil {