Update sing-box core

This commit is contained in:
Shtorm
2026-08-06 14:09:46 +03:00
57 changed files with 1628 additions and 429 deletions

View File

@@ -0,0 +1,64 @@
package anytls
import (
"encoding/binary"
"net"
"reflect"
"strings"
"sync"
"unsafe"
"github.com/sagernet/sing/common"
anytls "github.com/anytls/sing-anytls"
"github.com/anytls/sing-anytls/session"
)
const (
commandSettings = 4
frameHeaderSize = 7
)
var (
clientSessionField, _ = reflect.TypeFor[anytls.Client]().FieldByName("sessionClient")
streamSessionField, _ = reflect.TypeFor[session.Stream]().FieldByName("sess")
sessionConnLockField, _ = reflect.TypeFor[session.Session]().FieldByName("connLock")
sessionBufferField, _ = reflect.TypeFor[session.Session]().FieldByName("buffer")
)
func sessionClientOf(client *anytls.Client) *session.Client {
return *(**session.Client)(unsafe.Add(unsafe.Pointer(client), clientSessionField.Offset))
}
func (h *Outbound) rewriteClientMetadata(conn net.Conn) {
sess := *(**session.Session)(unsafe.Add(unsafe.Pointer(conn.(*session.Stream)), streamSessionField.Offset))
connLock := (*sync.Mutex)(unsafe.Add(unsafe.Pointer(sess), sessionConnLockField.Offset))
bufferPointer := (*[]byte)(unsafe.Add(unsafe.Pointer(sess), sessionBufferField.Offset))
connLock.Lock()
defer connLock.Unlock()
buffer := *bufferPointer
offset := 0
for offset+frameHeaderSize <= len(buffer) {
dataLength := int(binary.BigEndian.Uint16(buffer[offset+5 : offset+7]))
frameEnd := offset + frameHeaderSize + dataLength
if frameEnd > len(buffer) {
return
}
if buffer[offset] == commandSettings {
data := []byte(strings.Join(common.Map(strings.Split(string(buffer[offset+frameHeaderSize:frameEnd]), "\n"), func(line string) string {
if strings.HasPrefix(line, "client=") {
return "client=" + h.clientMetadata
}
return line
}), "\n"))
newBuffer := make([]byte, 0, offset+frameHeaderSize+len(data)+len(buffer)-frameEnd)
newBuffer = append(newBuffer, buffer[:offset+5]...)
newBuffer = binary.BigEndian.AppendUint16(newBuffer, uint16(len(data)))
newBuffer = append(newBuffer, data...)
newBuffer = append(newBuffer, buffer[frameEnd:]...)
*bufferPointer = newBuffer
return
}
offset = frameEnd
}
}

View File

@@ -19,20 +19,25 @@ import (
"github.com/sagernet/sing/common/uot"
anytls "github.com/anytls/sing-anytls"
"github.com/anytls/sing-anytls/session"
)
func RegisterOutbound(registry *outbound.Registry) {
outbound.Register[option.AnyTLSOutboundOptions](registry, C.TypeAnyTLS, NewOutbound)
}
var _ adapter.OutboundWithMultiplex = (*Outbound)(nil)
type Outbound struct {
outbound.Adapter
dialer tls.Dialer
server M.Socksaddr
tlsConfig tls.Config
client *anytls.Client
uotClient *uot.Client
logger log.ContextLogger
dialer tls.Dialer
server M.Socksaddr
tlsConfig tls.Config
clientMetadata string
client *anytls.Client
sessionClient *session.Client
uotClient *uot.Client
logger log.ContextLogger
}
func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.AnyTLSOutboundOptions) (adapter.Outbound, error) {
@@ -81,14 +86,30 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
return nil, err
}
outbound.client = client
outbound.clientMetadata = options.ClientMetadata
outbound.sessionClient = sessionClientOf(client)
outbound.uotClient = &uot.Client{
Dialer: anytlsDialer(client.CreateProxy),
Dialer: (anytlsDialer)(outbound.createProxy),
Version: uot.Version,
}
return outbound, nil
}
func (h *Outbound) createProxy(ctx context.Context, destination M.Socksaddr) (net.Conn, error) {
conn, err := h.sessionClient.CreateStream(ctx)
if err != nil {
return nil, err
}
h.rewriteClientMetadata(conn)
err = M.SocksaddrSerializer.WriteAddrPort(conn, destination)
if err != nil {
conn.Close()
return nil, err
}
return conn, nil
}
type anytlsDialer func(ctx context.Context, destination M.Socksaddr) (net.Conn, error)
func (d anytlsDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
@@ -103,6 +124,10 @@ func (h *Outbound) dialOut(ctx context.Context) (net.Conn, error) {
return h.dialer.DialTLSContext(ctx, h.server)
}
func (h *Outbound) MultiplexEnabled() bool {
return true
}
func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
ctx, metadata := adapter.ExtendContext(ctx)
metadata.Outbound = h.Tag()
@@ -110,7 +135,7 @@ func (h *Outbound) DialContext(ctx context.Context, network string, destination
switch N.NetworkName(network) {
case N.NetworkTCP:
h.logger.InfoContext(ctx, "outbound connection to ", destination)
return h.client.CreateProxy(ctx, destination)
return h.createProxy(ctx, destination)
case N.NetworkUDP:
h.logger.InfoContext(ctx, "outbound UoT packet connection to ", destination)
return h.uotClient.DialContext(ctx, network, destination)

View File

@@ -108,7 +108,10 @@ func (h *Outbound) fetchMyAddresses() {
func (h *Outbound) isMyLoopbackAddress(addresses ...netip.Addr) bool {
for _, prefix := range h.myAddresses.Load() {
for _, address := range addresses {
if prefix.Addr() != address && prefix.Contains(address) {
if !C.IsDarwin && prefix.Addr() == address {
continue
}
if prefix.Contains(address) {
return true
}
}

View File

@@ -225,7 +225,7 @@ func (n *Inbound) newConnection(ctx context.Context, waitForClose bool, conn net
} else {
done := make(chan struct{})
wrapper := v2rayhttp.NewHTTP2Wrapper(conn)
n.router.RouteConnectionEx(ctx, conn, metadata, N.OnceClose(func(it error) {
n.router.RouteConnectionEx(ctx, wrapper, metadata, N.OnceClose(func(it error) {
close(done)
}))
<-done

View File

@@ -26,6 +26,8 @@ func RegisterOutbound(registry *outbound.Registry) {
outbound.Register[option.ShadowsocksOutboundOptions](registry, C.TypeShadowsocks, NewOutbound)
}
var _ adapter.OutboundWithMultiplex = (*Outbound)(nil)
type Outbound struct {
outbound.Adapter
logger logger.ContextLogger
@@ -124,6 +126,10 @@ func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (n
}
}
func (h *Outbound) MultiplexEnabled() bool {
return h.multiplexDialer != nil
}
func (h *Outbound) InterfaceUpdated() {
if h.multiplexDialer != nil {
h.multiplexDialer.Reset()

View File

@@ -103,7 +103,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
for _, hostKey := range options.HostKey {
key, _, _, _, err := ssh.ParseAuthorizedKey([]byte(hostKey))
if err != nil {
return nil, E.New("parse host key ", key)
return nil, E.Cause(err, "parse host key: ", hostKey)
}
outbound.hostKey = append(outbound.hostKey, key)
}

View File

@@ -161,8 +161,9 @@ func (t *DNSTransport) updateDNSServers(routeConfig *router.Config, dnsConfig *n
func (t *DNSTransport) createResolver(directDialer func() N.Dialer, resolver *dnstype.Resolver) (adapter.DNSTransport, error) {
serverURL, parseURLErr := url.Parse(resolver.Addr)
isHTTPScheme := parseURLErr == nil && (serverURL.Scheme == "http" || serverURL.Scheme == "https")
var myDialer N.Dialer
if parseURLErr == nil && serverURL.Scheme == "http" {
if isHTTPScheme && serverURL.Scheme == "http" {
myDialer = t.endpoint
} else {
myDialer = directDialer()
@@ -170,36 +171,39 @@ func (t *DNSTransport) createResolver(directDialer func() N.Dialer, resolver *dn
if len(resolver.BootstrapResolution) > 0 {
bootstrapTransport := transport.NewUDPRaw(t.logger, t.TransportAdapter, myDialer, M.SocksaddrFrom(resolver.BootstrapResolution[0], 53))
myDialer = dialer.NewResolveDialer(t.ctx, myDialer, false, "", adapter.DNSQueryOptions{Transport: bootstrapTransport}, 0)
}
if serverAddr := M.ParseSocksaddr(resolver.Addr); serverAddr.IsValid() {
if serverAddr.Port == 0 {
serverAddr.Port = 53
}
return transport.NewUDPRaw(t.logger, t.TransportAdapter, myDialer, serverAddr), nil
} else if parseURLErr != nil {
return nil, E.Cause(parseURLErr, "parse resolver address")
} else {
myDialer = dialer.NewResolveDialer(t.ctx, myDialer, false, "", t.endpoint.queryOptions, 0)
}
if isHTTPScheme {
serverAddr := M.ParseSocksaddrHostPortStr(serverURL.Hostname(), serverURL.Port())
switch serverURL.Scheme {
case "https":
serverAddr = M.ParseSocksaddrHostPortStr(serverURL.Hostname(), serverURL.Port())
if serverAddr.Port == 0 {
serverAddr.Port = 443
}
tlsConfig := common.Must1(tls.NewClient(t.ctx, t.logger, serverAddr.AddrString(), option.OutboundTLSOptions{
ALPN: []string{http2.NextProtoTLS, "http/1.1"},
Enabled: true,
ALPN: []string{http2.NextProtoTLS, "http/1.1"},
}))
return transport.NewHTTPSRaw(t.TransportAdapter, t.logger, myDialer, serverURL, http.Header{}, serverAddr, tlsConfig), nil
case "http":
serverAddr = M.ParseSocksaddrHostPortStr(serverURL.Hostname(), serverURL.Port())
if serverAddr.Port == 0 {
serverAddr.Port = 80
}
return transport.NewHTTPSRaw(t.TransportAdapter, t.logger, myDialer, serverURL, http.Header{}, serverAddr, nil), nil
// case "tls":
default:
return nil, E.New("unknown resolver scheme: ", serverURL.Scheme)
}
}
serverAddr := M.ParseSocksaddr(resolver.Addr)
if !serverAddr.IsValid() {
if parseURLErr != nil {
return nil, E.Cause(parseURLErr, "parse resolver address")
}
return nil, E.New("invalid resolver address: ", resolver.Addr)
}
if serverAddr.Port == 0 {
serverAddr.Port = 53
}
return transport.NewUDPRaw(t.logger, t.TransportAdapter, myDialer, serverAddr), nil
}
func buildRoutePrefixes(routeConfig *router.Config) []netip.Prefix {
@@ -279,7 +283,7 @@ func (t *DNSTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.M
}
}
for domainSuffix, transports := range routes {
if strings.HasSuffix(question.Name, domainSuffix) {
if mDNS.IsSubDomain(domainSuffix, question.Name) {
if len(transports) == 0 {
return &mDNS.Msg{
MsgHdr: mDNS.MsgHdr{

View File

@@ -9,7 +9,6 @@ import (
"net"
"net/http"
"net/netip"
"net/url"
"os"
"path/filepath"
"reflect"
@@ -71,7 +70,7 @@ var (
)
func init() {
version.SetVersion(tailscaleroot.VersionDotTxt + " (sing-box " + C.Version + ")")
version.SetVersion(strings.TrimSpace(tailscaleroot.VersionDotTxt) + "-0-(sing-box " + C.Version + ")")
}
func RegisterEndpoint(registry *endpoint.Registry) {
@@ -83,6 +82,7 @@ type Endpoint struct {
ctx context.Context
router adapter.Router
logger logger.ContextLogger
queryOptions adapter.DNSQueryOptions
dnsRouter adapter.DNSRouter
network adapter.NetworkManager
platformInterface adapter.PlatformInterface
@@ -93,6 +93,7 @@ type Endpoint struct {
onReconfigHook wgengine.ReconfigListener
cfg *wgcfg.Config
routerCfg *router.Config
dnsCfg *tsDNS.Config
routeDomains common.TypedValue[map[string]bool]
routePrefixes atomic.Pointer[netipx.IPSet]
@@ -188,27 +189,17 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL
} else {
udpTimeout = C.UDPTimeout
}
var remoteIsDomain bool
if options.ControlURL != "" {
controlURL, err := url.Parse(options.ControlURL)
if err != nil {
return nil, E.Cause(err, "parse control URL")
}
remoteIsDomain = M.ParseSocksaddr(controlURL.Hostname()).IsDomain()
} else {
// controlplane.tailscale.com
remoteIsDomain = true
}
outboundDialer, err := dialer.NewWithOptions(dialer.Options{
Context: ctx,
Options: options.DialerOptions,
RemoteIsDomain: remoteIsDomain,
RemoteIsDomain: true,
ResolverOnDetour: true,
NewDialer: true,
})
if err != nil {
return nil, err
}
dialerQueryOptions := outboundDialer.(dialer.ResolveDialer).QueryOptions()
dnsRouter := service.FromContext[adapter.DNSRouter](ctx)
server := &tsnet.Server{
Dir: stateDirectory,
@@ -225,7 +216,7 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL
AdvertiseTags: options.AdvertiseTags,
Dialer: &endpointDialer{Dialer: outboundDialer, logger: logger},
LookupHook: func(ctx context.Context, host string) ([]netip.Addr, error) {
return dnsRouter.Lookup(ctx, host, outboundDialer.(dialer.ResolveDialer).QueryOptions())
return dnsRouter.Lookup(ctx, host, dialerQueryOptions)
},
DNS: &dnsConfigurtor{},
HTTPClient: &http.Client{
@@ -242,10 +233,11 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL
},
}
return &Endpoint{
Adapter: endpoint.NewAdapter(C.TypeTailscale, tag, []string{N.NetworkTCP, N.NetworkUDP, N.NetworkICMP}, nil),
Adapter: endpoint.NewAdapterWithDialerOptions(C.TypeTailscale, tag, []string{N.NetworkTCP, N.NetworkUDP, N.NetworkICMP}, options.DialerOptions),
ctx: ctx,
router: router,
logger: logger,
queryOptions: dialerQueryOptions,
dnsRouter: dnsRouter,
network: service.FromContext[adapter.NetworkManager](ctx),
platformInterface: service.FromContext[adapter.PlatformInterface](ctx),
@@ -704,12 +696,17 @@ func (t *Endpoint) NewConnectionEx(ctx context.Context, conn net.Conn, source M.
metadata.Inbound = t.Tag()
metadata.InboundType = t.Type()
metadata.Source = source
addr4, addr6 := t.server.TailscaleIPs()
switch destination.Addr {
case addr4:
destination.Addr = netip.AddrFrom4([4]uint8{127, 0, 0, 1})
case addr6:
destination.Addr = netip.IPv6Loopback()
destinationAddress := tsaddr.UnmapVia(destination.Addr)
if destinationAddress != destination.Addr {
destination.Addr = destinationAddress
} else {
addr4, addr6 := t.server.TailscaleIPs()
switch destination.Addr {
case addr4:
destination.Addr = netip.AddrFrom4([4]uint8{127, 0, 0, 1})
case addr6:
destination.Addr = netip.IPv6Loopback()
}
}
metadata.Destination = destination
t.logger.InfoContext(ctx, "inbound connection from ", source)
@@ -722,16 +719,22 @@ func (t *Endpoint) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn,
metadata.Inbound = t.Tag()
metadata.InboundType = t.Type()
metadata.Source = source
addr4, addr6 := t.server.TailscaleIPs()
switch destination.Addr {
case addr4:
metadata.OriginDestination = destination
destination.Addr = netip.AddrFrom4([4]uint8{127, 0, 0, 1})
conn = bufio.NewNATPacketConn(bufio.NewNetPacketConn(conn), metadata.OriginDestination, destination)
case addr6:
metadata.OriginDestination = destination
destination.Addr = netip.IPv6Loopback()
conn = bufio.NewNATPacketConn(bufio.NewNetPacketConn(conn), metadata.OriginDestination, destination)
originDestination := destination
destinationAddress := tsaddr.UnmapVia(destination.Addr)
if destinationAddress != destination.Addr {
destination.Addr = destinationAddress
} else {
addr4, addr6 := t.server.TailscaleIPs()
switch destination.Addr {
case addr4:
destination.Addr = netip.AddrFrom4([4]uint8{127, 0, 0, 1})
case addr6:
destination.Addr = netip.IPv6Loopback()
}
}
if destination != originDestination {
metadata.OriginDestination = originDestination
conn = bufio.NewNATPacketConn(bufio.NewNetPacketConn(conn), originDestination, destination)
}
metadata.Destination = destination
t.logger.InfoContext(ctx, "inbound packet connection from ", source)
@@ -797,7 +800,9 @@ func (t *Endpoint) onReconfig(cfg *wgcfg.Config, routerCfg *router.Config, dnsCf
if cfg == nil || dnsCfg == nil {
return
}
if (t.cfg != nil && reflect.DeepEqual(t.cfg, cfg)) && (t.dnsCfg != nil && reflect.DeepEqual(t.dnsCfg, dnsCfg)) {
if t.cfg != nil && reflect.DeepEqual(t.cfg, cfg) &&
t.routerCfg != nil && reflect.DeepEqual(t.routerCfg, routerCfg) &&
t.dnsCfg != nil && reflect.DeepEqual(t.dnsCfg, dnsCfg) {
return
}
var inet4Address, inet6Address netip.Addr
@@ -810,6 +815,7 @@ func (t *Endpoint) onReconfig(cfg *wgcfg.Config, routerCfg *router.Config, dnsCf
}
t.icmpForwarder.SetLocalAddresses(inet4Address, inet6Address)
t.cfg = cfg
t.routerCfg = routerCfg
t.dnsCfg = dnsCfg
routeDomains := make(map[string]bool)

View File

@@ -11,6 +11,9 @@ import (
singTun "github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common/logger"
wgTun "github.com/sagernet/wireguard-go/tun"
"github.com/dblohm7/wingoes/com"
"golang.org/x/sys/windows/svc"
)
type tunDeviceAdapter struct {
@@ -21,7 +24,23 @@ type tunDeviceAdapter struct {
closeOnce sync.Once
}
func newTunDeviceAdapter(tun singTun.Tun, mtu int, _ logger.ContextLogger) (wgTun.Device, error) {
var comRuntimeOnce sync.Once
func newTunDeviceAdapter(tun singTun.Tun, mtu int, contextLogger logger.ContextLogger) (wgTun.Device, error) {
// wgengine/router/osrouter/ifconfig_windows.go setPrivateNetwork assumes COM
// is initialized process-wide, which upstream performs only in tailscaled's
// main package; library consumers must do it themselves.
comRuntimeOnce.Do(func() {
processType := com.ConsoleApp
isService, serviceErr := svc.IsWindowsService()
if serviceErr == nil && isService {
processType = com.Service
}
err := com.StartRuntime(processType)
if err != nil {
contextLogger.Warn("initialize COM runtime: ", err)
}
})
winTun, ok := tun.(singTun.WinTun)
if !ok {
return nil, errors.New("not a windows tun device")

View File

@@ -26,6 +26,8 @@ func RegisterOutbound(registry *outbound.Registry) {
outbound.Register[option.TrojanOutboundOptions](registry, C.TypeTrojan, NewOutbound)
}
var _ adapter.OutboundWithMultiplex = (*Outbound)(nil)
type Outbound struct {
outbound.Adapter
logger logger.ContextLogger
@@ -107,6 +109,10 @@ func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (n
}
}
func (h *Outbound) MultiplexEnabled() bool {
return h.multiplexDialer != nil
}
func (h *Outbound) InterfaceUpdated() {
if h.transport != nil {
h.transport.Close()

View File

@@ -198,6 +198,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
IncludePackage: options.IncludePackage,
ExcludePackage: options.ExcludePackage,
InterfaceMonitor: networkManager.InterfaceMonitor(),
Logger: logger,
EXP_MultiPendingPackets: multiPendingPackets,
},
udpTimeout: udpTimeout,

View File

@@ -33,6 +33,8 @@ func RegisterOutbound(registry *outbound.Registry) {
outbound.Register[option.VLESSOutboundOptions](registry, C.TypeVLESS, NewOutbound)
}
var _ adapter.OutboundWithMultiplex = (*Outbound)(nil)
type Outbound struct {
outbound.Adapter
logger logger.ContextLogger
@@ -152,6 +154,10 @@ func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (n
}
}
func (h *Outbound) MultiplexEnabled() bool {
return h.multiplexDialer != nil
}
func (h *Outbound) InterfaceUpdated() {
if h.transport != nil {
h.transport.Close()

View File

@@ -27,6 +27,8 @@ func RegisterOutbound(registry *outbound.Registry) {
outbound.Register[option.VMessOutboundOptions](registry, C.TypeVMess, NewOutbound)
}
var _ adapter.OutboundWithMultiplex = (*Outbound)(nil)
type Outbound struct {
outbound.Adapter
logger logger.ContextLogger
@@ -105,6 +107,10 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
return outbound, nil
}
func (h *Outbound) MultiplexEnabled() bool {
return h.multiplexDialer != nil
}
func (h *Outbound) InterfaceUpdated() {
if h.transport != nil {
h.transport.Close()