mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-10-20 00:04:24 +00:00
Reset network on DNS server changes
This commit is contained in:
@@ -2,129 +2,14 @@
|
||||
|
||||
package systemconfig
|
||||
|
||||
/*
|
||||
#include <dlfcn.h>
|
||||
#include <notify.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
// dnsinfo.h is not shipped in any SDK. The layouts below are DNSINFO_VERSION
|
||||
// 20170629 from apple-oss-distributions/configd (#pragma pack(4)), the format
|
||||
// libsystem_configuration unpacks into at runtime. dns_configuration_copy,
|
||||
// dns_configuration_free and dns_configuration_notify_key are private
|
||||
// libSystem exports. cgo silently drops packed struct fields that fall on
|
||||
// unaligned offsets.
|
||||
|
||||
#pragma pack(4)
|
||||
typedef struct {
|
||||
struct in_addr address;
|
||||
struct in_addr mask;
|
||||
} box_dns_sortaddr_t;
|
||||
|
||||
typedef struct {
|
||||
char *domain;
|
||||
int32_t n_nameserver;
|
||||
struct sockaddr **nameserver;
|
||||
uint16_t port;
|
||||
int32_t n_search;
|
||||
char **search;
|
||||
int32_t n_sortaddr;
|
||||
box_dns_sortaddr_t **sortaddr;
|
||||
char *options;
|
||||
uint32_t timeout;
|
||||
uint32_t search_order;
|
||||
uint32_t if_index;
|
||||
uint32_t flags;
|
||||
uint32_t reach_flags;
|
||||
uint32_t service_identifier;
|
||||
char *cid;
|
||||
char *if_name;
|
||||
} box_dns_resolver_t;
|
||||
|
||||
typedef struct {
|
||||
int32_t n_resolver;
|
||||
box_dns_resolver_t **resolver;
|
||||
int32_t n_scoped_resolver;
|
||||
box_dns_resolver_t **scoped_resolver;
|
||||
uint64_t generation;
|
||||
int32_t n_service_specific_resolver;
|
||||
box_dns_resolver_t **service_specific_resolver;
|
||||
uint32_t version;
|
||||
} box_dns_config_t;
|
||||
#pragma pack()
|
||||
|
||||
static box_dns_config_t *(*box_dns_configuration_copy)(void);
|
||||
static void (*box_dns_configuration_free)(box_dns_config_t *);
|
||||
|
||||
static void box_reverse_string(char *s) {
|
||||
size_t length = strlen(s);
|
||||
for (size_t i = 0; i < length / 2; i++) {
|
||||
char tmp = s[i];
|
||||
s[i] = s[length - 1 - i];
|
||||
s[length - 1 - i] = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
static int box_dnsinfo_load(void) {
|
||||
if (box_dns_configuration_copy != NULL && box_dns_configuration_free != NULL) {
|
||||
return 1;
|
||||
}
|
||||
char copy_name[] = "ypoc_noitarugifnoc_snd";
|
||||
char free_name[] = "eerf_noitarugifnoc_snd";
|
||||
box_reverse_string(copy_name);
|
||||
box_reverse_string(free_name);
|
||||
box_dns_configuration_copy = (box_dns_config_t * (*)(void)) dlsym(RTLD_DEFAULT, copy_name);
|
||||
box_dns_configuration_free = (void (*)(box_dns_config_t *))dlsym(RTLD_DEFAULT, free_name);
|
||||
return box_dns_configuration_copy != NULL && box_dns_configuration_free != NULL;
|
||||
}
|
||||
|
||||
static box_dns_config_t *box_dnsinfo_copy(void) {
|
||||
return box_dns_configuration_copy();
|
||||
}
|
||||
|
||||
static void box_dnsinfo_free(box_dns_config_t *config) {
|
||||
box_dns_configuration_free(config);
|
||||
}
|
||||
|
||||
static const char *box_dnsinfo_notify_key(void) {
|
||||
const char *(*notify_key)(void) = (const char *(*)(void))dlsym(RTLD_DEFAULT, "dns_configuration_notify_key");
|
||||
if (notify_key != NULL) {
|
||||
return notify_key();
|
||||
}
|
||||
return "com.apple.system.SystemConfiguration.dns_configuration";
|
||||
}
|
||||
|
||||
static box_dns_resolver_t *box_dnsinfo_default_resolver(box_dns_config_t *config, int32_t index) {
|
||||
return config->resolver[index];
|
||||
}
|
||||
|
||||
static box_dns_resolver_t *box_dnsinfo_scoped_resolver(box_dns_config_t *config, int32_t index) {
|
||||
return config->scoped_resolver[index];
|
||||
}
|
||||
|
||||
static struct sockaddr *box_dnsinfo_nameserver(box_dns_resolver_t *resolver, int32_t index) {
|
||||
return resolver->nameserver[index];
|
||||
}
|
||||
|
||||
static const char *box_dnsinfo_search_domain(box_dns_resolver_t *resolver, int32_t index) {
|
||||
return resolver->search[index];
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing-tun/dnsinfo"
|
||||
"github.com/sagernet/sing/common"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
"github.com/sagernet/sing/service"
|
||||
@@ -135,25 +20,15 @@ import (
|
||||
type Source struct {
|
||||
interfaceMonitor tun.DefaultInterfaceMonitor
|
||||
access sync.Mutex
|
||||
notifyToken C.int
|
||||
notifyValid bool
|
||||
stale bool
|
||||
interfaceIndex int
|
||||
config *Config
|
||||
}
|
||||
|
||||
func NewSource(ctx context.Context) *Source {
|
||||
source := &Source{
|
||||
return &Source{
|
||||
interfaceMonitor: service.FromContext[adapter.NetworkManager](ctx).InterfaceMonitor(),
|
||||
}
|
||||
if C.box_dnsinfo_load() != 0 {
|
||||
var token C.int
|
||||
if C.notify_register_check(C.box_dnsinfo_notify_key(), &token) == 0 {
|
||||
source.notifyToken = token
|
||||
source.notifyValid = true
|
||||
}
|
||||
}
|
||||
return source
|
||||
}
|
||||
|
||||
func (s *Source) Configuration() *Config {
|
||||
@@ -162,19 +37,18 @@ func (s *Source) Configuration() *Config {
|
||||
defer s.access.Unlock()
|
||||
interfaceChanged := s.interfaceIndex != interfaceIndex
|
||||
s.interfaceIndex = interfaceIndex
|
||||
changed := s.changedLocked()
|
||||
if s.config != nil && !s.stale && !interfaceChanged && !changed {
|
||||
if s.config != nil && !s.stale && !interfaceChanged && s.interfaceMonitor != nil {
|
||||
return s.config
|
||||
}
|
||||
s.stale = false
|
||||
systemInfo := copyDNSInfo()
|
||||
if systemInfo == nil {
|
||||
dnsConfiguration := dnsinfo.Copy()
|
||||
if dnsConfiguration == nil {
|
||||
if s.config == nil {
|
||||
s.config = new(dnsInfoConfig).build(interfaceIndex)
|
||||
s.config = newConfig(dnsinfo.Resolver{})
|
||||
}
|
||||
return s.config
|
||||
}
|
||||
config := systemInfo.build(interfaceIndex)
|
||||
config := newConfig(dnsConfiguration.Select(interfaceIndex))
|
||||
if s.config != nil && config.Equal(s.config) {
|
||||
return s.config
|
||||
}
|
||||
@@ -182,18 +56,6 @@ func (s *Source) Configuration() *Config {
|
||||
return config
|
||||
}
|
||||
|
||||
func (s *Source) changedLocked() bool {
|
||||
if !s.notifyValid {
|
||||
return true
|
||||
}
|
||||
var changed C.int
|
||||
status := C.notify_check(s.notifyToken, &changed)
|
||||
if status != 0 {
|
||||
return true
|
||||
}
|
||||
return changed != 0
|
||||
}
|
||||
|
||||
func (s *Source) Reset() {
|
||||
s.access.Lock()
|
||||
s.stale = true
|
||||
@@ -201,12 +63,6 @@ func (s *Source) Reset() {
|
||||
}
|
||||
|
||||
func (s *Source) Close() error {
|
||||
s.access.Lock()
|
||||
defer s.access.Unlock()
|
||||
if s.notifyValid {
|
||||
C.notify_cancel(s.notifyToken)
|
||||
s.notifyValid = false
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -221,144 +77,30 @@ func (s *Source) defaultInterfaceIndex() int {
|
||||
return defaultInterface.Index
|
||||
}
|
||||
|
||||
type dnsInfoResolver struct {
|
||||
interfaceIndex int
|
||||
domain string
|
||||
servers []M.Socksaddr
|
||||
search []string
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
type dnsInfoConfig struct {
|
||||
resolvers []dnsInfoResolver
|
||||
scopedResolvers []dnsInfoResolver
|
||||
}
|
||||
|
||||
func (c *dnsInfoConfig) build(interfaceIndex int) *Config {
|
||||
var selected dnsInfoResolver
|
||||
if interfaceIndex != 0 {
|
||||
selected = common.Find(c.scopedResolvers, func(it dnsInfoResolver) bool {
|
||||
return it.interfaceIndex == interfaceIndex && len(it.servers) > 0
|
||||
})
|
||||
}
|
||||
if len(selected.servers) == 0 {
|
||||
selected = common.Find(c.resolvers, func(it dnsInfoResolver) bool {
|
||||
return it.domain == "" && len(it.servers) > 0
|
||||
})
|
||||
}
|
||||
func newConfig(resolver dnsinfo.Resolver) *Config {
|
||||
config := &Config{
|
||||
Ndots: 1,
|
||||
Timeout: 5 * time.Second,
|
||||
Attempts: 2,
|
||||
}
|
||||
if len(selected.servers) == 0 {
|
||||
if len(resolver.Servers) == 0 {
|
||||
config.Servers = defaultServers
|
||||
config.Search = defaultSearch()
|
||||
return config
|
||||
}
|
||||
config.Servers = selected.servers
|
||||
if len(selected.search) > 0 {
|
||||
config.Search = selected.search
|
||||
} else {
|
||||
config.Search = defaultSearch()
|
||||
}
|
||||
if selected.timeout > 0 {
|
||||
config.Timeout = selected.timeout
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
func copyDNSInfo() *dnsInfoConfig {
|
||||
if C.box_dnsinfo_load() == 0 {
|
||||
return nil
|
||||
}
|
||||
rawConfig := C.box_dnsinfo_copy()
|
||||
if rawConfig == nil {
|
||||
return nil
|
||||
}
|
||||
defer C.box_dnsinfo_free(rawConfig)
|
||||
systemInfo := new(dnsInfoConfig)
|
||||
for i := C.int32_t(0); i < rawConfig.n_resolver; i++ {
|
||||
rawResolver := C.box_dnsinfo_default_resolver(rawConfig, i)
|
||||
if rawResolver == nil {
|
||||
continue
|
||||
}
|
||||
systemInfo.resolvers = append(systemInfo.resolvers, parseResolver(rawResolver))
|
||||
}
|
||||
for i := C.int32_t(0); i < rawConfig.n_scoped_resolver; i++ {
|
||||
rawResolver := C.box_dnsinfo_scoped_resolver(rawConfig, i)
|
||||
if rawResolver == nil {
|
||||
continue
|
||||
}
|
||||
systemInfo.scopedResolvers = append(systemInfo.scopedResolvers, parseResolver(rawResolver))
|
||||
}
|
||||
return systemInfo
|
||||
}
|
||||
|
||||
func parseResolver(rawResolver *C.box_dns_resolver_t) dnsInfoResolver {
|
||||
resolver := dnsInfoResolver{
|
||||
interfaceIndex: int(rawResolver.if_index),
|
||||
domain: C.GoString(rawResolver.domain),
|
||||
timeout: time.Duration(rawResolver.timeout) * time.Second,
|
||||
}
|
||||
interfaceName := C.GoString(rawResolver.if_name)
|
||||
resolverPort := uint16(rawResolver.port)
|
||||
if resolverPort == 0 {
|
||||
resolverPort = 53
|
||||
}
|
||||
for i := C.int32_t(0); i < rawResolver.n_nameserver; i++ {
|
||||
rawSockaddr := C.box_dnsinfo_nameserver(rawResolver, i)
|
||||
if rawSockaddr == nil {
|
||||
continue
|
||||
}
|
||||
serverAddr, loaded := parseSockaddr(rawSockaddr, resolverPort, interfaceName)
|
||||
if !loaded {
|
||||
continue
|
||||
}
|
||||
resolver.servers = append(resolver.servers, M.SocksaddrFromNetIP(serverAddr))
|
||||
}
|
||||
for i := C.int32_t(0); i < rawResolver.n_search; i++ {
|
||||
searchDomain := C.GoString(C.box_dnsinfo_search_domain(rawResolver, i))
|
||||
if searchDomain == "" {
|
||||
continue
|
||||
}
|
||||
config.Servers = common.Map(resolver.Servers, M.SocksaddrFromNetIP)
|
||||
for _, searchDomain := range resolver.Search {
|
||||
searchDomain = mDNS.Fqdn(searchDomain)
|
||||
if searchDomain == "." {
|
||||
continue
|
||||
}
|
||||
resolver.search = append(resolver.search, searchDomain)
|
||||
config.Search = append(config.Search, searchDomain)
|
||||
}
|
||||
return resolver
|
||||
}
|
||||
|
||||
func parseSockaddr(rawSockaddr *C.struct_sockaddr, fallbackPort uint16, zone string) (netip.AddrPort, bool) {
|
||||
switch rawSockaddr.sa_family {
|
||||
case C.AF_INET:
|
||||
sockaddrInet := (*C.struct_sockaddr_in)(unsafe.Pointer(rawSockaddr))
|
||||
addr := netip.AddrFrom4(*(*[4]byte)(unsafe.Pointer(&sockaddrInet.sin_addr)))
|
||||
return netip.AddrPortFrom(addr, sockaddrPort(unsafe.Pointer(&sockaddrInet.sin_port), fallbackPort)), true
|
||||
case C.AF_INET6:
|
||||
sockaddrInet6 := (*C.struct_sockaddr_in6)(unsafe.Pointer(rawSockaddr))
|
||||
addr := netip.AddrFrom16(*(*[16]byte)(unsafe.Pointer(&sockaddrInet6.sin6_addr)))
|
||||
if addr.IsLinkLocalUnicast() {
|
||||
scopeId := uint32(sockaddrInet6.sin6_scope_id)
|
||||
if zone == "" && scopeId != 0 {
|
||||
zone = strconv.FormatUint(uint64(scopeId), 10)
|
||||
}
|
||||
if zone != "" {
|
||||
addr = addr.WithZone(zone)
|
||||
}
|
||||
}
|
||||
return netip.AddrPortFrom(addr, sockaddrPort(unsafe.Pointer(&sockaddrInet6.sin6_port), fallbackPort)), true
|
||||
default:
|
||||
return netip.AddrPort{}, false
|
||||
if len(config.Search) == 0 {
|
||||
config.Search = defaultSearch()
|
||||
}
|
||||
}
|
||||
|
||||
func sockaddrPort(rawPort unsafe.Pointer, fallbackPort uint16) uint16 {
|
||||
port := binary.BigEndian.Uint16((*[2]byte)(rawPort)[:])
|
||||
if port == 0 {
|
||||
return fallbackPort
|
||||
if resolver.Timeout > 0 {
|
||||
config.Timeout = resolver.Timeout
|
||||
}
|
||||
return port
|
||||
return config
|
||||
}
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
package libbox
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"slices"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/service/powerreport"
|
||||
tun "github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing-tun/dnsinfo"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/control"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
@@ -20,15 +27,35 @@ type platformDefaultInterfaceMonitor struct {
|
||||
callbacks list.List[tun.DefaultInterfaceUpdateCallback]
|
||||
myInterfaces []string
|
||||
defaultInterfaceInitialized bool
|
||||
defaultDNSServers []string
|
||||
dnsWatcher *dnsinfo.Watcher
|
||||
lastNetworkPath string
|
||||
}
|
||||
|
||||
func (m *platformDefaultInterfaceMonitor) Start() error {
|
||||
return m.iif.StartDefaultInterfaceMonitor(m)
|
||||
if C.IsDarwin {
|
||||
dnsWatcher, err := dnsinfo.NewWatcher(m.updateDNSServers, m.logger)
|
||||
if err != nil {
|
||||
return E.Cause(err, "watch DNS configuration")
|
||||
}
|
||||
m.dnsWatcher = dnsWatcher
|
||||
}
|
||||
err := m.iif.StartDefaultInterfaceMonitor(m)
|
||||
if err != nil {
|
||||
if C.IsDarwin {
|
||||
m.dnsWatcher.Close()
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *platformDefaultInterfaceMonitor) Close() error {
|
||||
return m.iif.CloseDefaultInterfaceMonitor(m)
|
||||
err := m.iif.CloseDefaultInterfaceMonitor(m)
|
||||
if C.IsDarwin {
|
||||
err = E.Errors(err, m.dnsWatcher.Close())
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *platformDefaultInterfaceMonitor) DefaultInterface() *control.Interface {
|
||||
@@ -113,6 +140,7 @@ func (m *platformDefaultInterfaceMonitor) updateDefaultInterface(interfaceName s
|
||||
m.defaultInterfaceAccess.Lock()
|
||||
if interfaceIndex32 == -1 {
|
||||
m.defaultInterface = nil
|
||||
m.defaultDNSServers = nil
|
||||
m.defaultInterfaceInitialized = true
|
||||
callbacks := m.callbacks.Array()
|
||||
m.defaultInterfaceAccess.Unlock()
|
||||
@@ -128,8 +156,10 @@ func (m *platformDefaultInterfaceMonitor) updateDefaultInterface(interfaceName s
|
||||
m.logger.Error(E.Cause(err, "find updated interface: ", interfaceName))
|
||||
return
|
||||
}
|
||||
oldDNSServers := m.defaultDNSServers
|
||||
m.defaultInterface = newInterface
|
||||
if m.defaultInterfaceInitialized && oldInterface != nil && oldInterface.Name == m.defaultInterface.Name && oldInterface.Index == m.defaultInterface.Index {
|
||||
m.defaultDNSServers = m.readDNSServers(newInterface.Index)
|
||||
if m.defaultInterfaceInitialized && oldInterface != nil && oldInterface.Name == m.defaultInterface.Name && oldInterface.Index == m.defaultInterface.Index && slices.Equal(oldDNSServers, m.defaultDNSServers) {
|
||||
m.defaultInterfaceAccess.Unlock()
|
||||
return
|
||||
}
|
||||
@@ -141,6 +171,41 @@ func (m *platformDefaultInterfaceMonitor) updateDefaultInterface(interfaceName s
|
||||
}
|
||||
}
|
||||
|
||||
func (m *platformDefaultInterfaceMonitor) updateDNSServers() {
|
||||
m.defaultInterfaceAccess.Lock()
|
||||
defaultInterface := m.defaultInterface
|
||||
if defaultInterface == nil {
|
||||
m.defaultInterfaceAccess.Unlock()
|
||||
return
|
||||
}
|
||||
dnsServers := m.readDNSServers(defaultInterface.Index)
|
||||
if slices.Equal(m.defaultDNSServers, dnsServers) {
|
||||
m.defaultInterfaceAccess.Unlock()
|
||||
return
|
||||
}
|
||||
m.defaultDNSServers = dnsServers
|
||||
callbacks := m.callbacks.Array()
|
||||
m.defaultInterfaceAccess.Unlock()
|
||||
for _, callback := range callbacks {
|
||||
callback(defaultInterface, 0)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *platformDefaultInterfaceMonitor) readDNSServers(interfaceIndex int) []string {
|
||||
if C.IsDarwin {
|
||||
dnsConfiguration := dnsinfo.Copy()
|
||||
if dnsConfiguration == nil {
|
||||
return nil
|
||||
}
|
||||
return common.Map(dnsConfiguration.Select(interfaceIndex).Servers, func(it netip.AddrPort) string {
|
||||
return it.Addr().String()
|
||||
})
|
||||
}
|
||||
return common.Find(m.networkManager.NetworkInterfaces(), func(it adapter.NetworkInterface) bool {
|
||||
return it.Index == interfaceIndex
|
||||
}).DNSServers
|
||||
}
|
||||
|
||||
func (m *platformDefaultInterfaceMonitor) RegisterMyInterface(interfaceName string) {
|
||||
m.defaultInterfaceAccess.Lock()
|
||||
defer m.defaultInterfaceAccess.Unlock()
|
||||
|
||||
@@ -55,7 +55,7 @@ require (
|
||||
github.com/sagernet/sing-shadowsocks2 v0.2.2-0.20260929152114-a69d1086332b
|
||||
github.com/sagernet/sing-shadowtls v0.2.2-0.20260928201441-a9c0127d5c99
|
||||
github.com/sagernet/sing-snell v0.0.0-20260904135315-bc5a12ac736f
|
||||
github.com/sagernet/sing-tun v0.9.7-0.20261007151649-0e9e4a586ece
|
||||
github.com/sagernet/sing-tun v0.9.7-0.20261009020158-bca4bf029237
|
||||
github.com/sagernet/sing-usbip v0.0.0-20260817040617-28bd42667eca
|
||||
github.com/sagernet/sing-vmess v0.2.9-0.20260929152519-9b95ab8c9478
|
||||
github.com/sagernet/smux v1.5.50-sing-box-mod.1
|
||||
|
||||
@@ -338,8 +338,8 @@ github.com/sagernet/sing-shadowtls v0.2.2-0.20260928201441-a9c0127d5c99 h1:l/tdZ
|
||||
github.com/sagernet/sing-shadowtls v0.2.2-0.20260928201441-a9c0127d5c99/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA=
|
||||
github.com/sagernet/sing-snell v0.0.0-20260904135315-bc5a12ac736f h1:tbTCI+iis5sTmua7a6Q7vebcSRURok/LGgnEafnQAsc=
|
||||
github.com/sagernet/sing-snell v0.0.0-20260904135315-bc5a12ac736f/go.mod h1:et8Lws4f5QbOrY65DmjevHGup3mijJkhswkto6cwciM=
|
||||
github.com/sagernet/sing-tun v0.9.7-0.20261007151649-0e9e4a586ece h1://BO1bRUuh7JjQ2NFRvXt+A2VXd1mBbp5G2lhv5fCag=
|
||||
github.com/sagernet/sing-tun v0.9.7-0.20261007151649-0e9e4a586ece/go.mod h1:3EgPst7agntRO7D6GOsiZ1l9FoqdLeuWmKT5TnWkmf0=
|
||||
github.com/sagernet/sing-tun v0.9.7-0.20261009020158-bca4bf029237 h1:B9tUcRf1FAatPyzN8sKEvIHMvKNkFMBZikeIOL4gqtQ=
|
||||
github.com/sagernet/sing-tun v0.9.7-0.20261009020158-bca4bf029237/go.mod h1:3EgPst7agntRO7D6GOsiZ1l9FoqdLeuWmKT5TnWkmf0=
|
||||
github.com/sagernet/sing-usbip v0.0.0-20260817040617-28bd42667eca h1:5wA+IE0Fq1CGVLOgSpm0gKKZ03HzugcJ2JyhmDqeX6A=
|
||||
github.com/sagernet/sing-usbip v0.0.0-20260817040617-28bd42667eca/go.mod h1:ADAZZU85MnM91XNhd2TdZRTaIbA7RjTZuQ1KCHcUbNg=
|
||||
github.com/sagernet/sing-vmess v0.2.9-0.20260929152519-9b95ab8c9478 h1:q2eQn4nq8oWGxRm9zXesXSlPr9GEgPrOGzf4iPtzG9A=
|
||||
|
||||
Reference in New Issue
Block a user