mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-13 20:28:32 +10:00
Compare commits
13 Commits
v1.14.0-al
...
cloudflare
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f1ba549fd | ||
|
|
813b634d08 | ||
|
|
d9b435fb62 | ||
|
|
354b4b040e | ||
|
|
7ffdc48b49 | ||
|
|
e15bdf11eb | ||
|
|
e3bcb06c3e | ||
|
|
84d2280960 | ||
|
|
4fd2532b0a | ||
|
|
02ccde6c71 | ||
|
|
e98b4ad449 | ||
|
|
d09182614c | ||
|
|
6381de7bab |
@@ -4,6 +4,7 @@
|
|||||||
--license GPL-3.0-or-later
|
--license GPL-3.0-or-later
|
||||||
--description "The universal proxy platform."
|
--description "The universal proxy platform."
|
||||||
--url "https://sing-box.sagernet.org/"
|
--url "https://sing-box.sagernet.org/"
|
||||||
|
--vendor SagerNet
|
||||||
--maintainer "nekohasekai <contact-git@sekai.icu>"
|
--maintainer "nekohasekai <contact-git@sekai.icu>"
|
||||||
--deb-field "Bug: https://github.com/SagerNet/sing-box/issues"
|
--deb-field "Bug: https://github.com/SagerNet/sing-box/issues"
|
||||||
--no-deb-generate-changes
|
--no-deb-generate-changes
|
||||||
|
|||||||
2
.github/CRONET_GO_VERSION
vendored
2
.github/CRONET_GO_VERSION
vendored
@@ -1 +1 @@
|
|||||||
ea7cd33752aed62603775af3df946c1b83f4b0b3
|
2fef65f9dba90ddb89a87d00a6eb6165487c10c1
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
package certificate
|
|
||||||
|
|
||||||
type Adapter struct {
|
|
||||||
providerType string
|
|
||||||
providerTag string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewAdapter(providerType string, providerTag string) Adapter {
|
|
||||||
return Adapter{
|
|
||||||
providerType: providerType,
|
|
||||||
providerTag: providerTag,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Adapter) Type() string {
|
|
||||||
return a.providerType
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Adapter) Tag() string {
|
|
||||||
return a.providerTag
|
|
||||||
}
|
|
||||||
@@ -1,158 +0,0 @@
|
|||||||
package certificate
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"os"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
"github.com/sagernet/sing-box/common/taskmonitor"
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
F "github.com/sagernet/sing/common/format"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ adapter.CertificateProviderManager = (*Manager)(nil)
|
|
||||||
|
|
||||||
type Manager struct {
|
|
||||||
logger log.ContextLogger
|
|
||||||
registry adapter.CertificateProviderRegistry
|
|
||||||
access sync.Mutex
|
|
||||||
started bool
|
|
||||||
stage adapter.StartStage
|
|
||||||
providers []adapter.CertificateProviderService
|
|
||||||
providerByTag map[string]adapter.CertificateProviderService
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewManager(logger log.ContextLogger, registry adapter.CertificateProviderRegistry) *Manager {
|
|
||||||
return &Manager{
|
|
||||||
logger: logger,
|
|
||||||
registry: registry,
|
|
||||||
providerByTag: make(map[string]adapter.CertificateProviderService),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Start(stage adapter.StartStage) error {
|
|
||||||
m.access.Lock()
|
|
||||||
if m.started && m.stage >= stage {
|
|
||||||
panic("already started")
|
|
||||||
}
|
|
||||||
m.started = true
|
|
||||||
m.stage = stage
|
|
||||||
providers := m.providers
|
|
||||||
m.access.Unlock()
|
|
||||||
for _, provider := range providers {
|
|
||||||
name := "certificate-provider/" + provider.Type() + "[" + provider.Tag() + "]"
|
|
||||||
m.logger.Trace(stage, " ", name)
|
|
||||||
startTime := time.Now()
|
|
||||||
err := adapter.LegacyStart(provider, stage)
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, stage, " ", name)
|
|
||||||
}
|
|
||||||
m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Close() error {
|
|
||||||
m.access.Lock()
|
|
||||||
defer m.access.Unlock()
|
|
||||||
if !m.started {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
m.started = false
|
|
||||||
providers := m.providers
|
|
||||||
m.providers = nil
|
|
||||||
monitor := taskmonitor.New(m.logger, C.StopTimeout)
|
|
||||||
var err error
|
|
||||||
for _, provider := range providers {
|
|
||||||
name := "certificate-provider/" + provider.Type() + "[" + provider.Tag() + "]"
|
|
||||||
m.logger.Trace("close ", name)
|
|
||||||
startTime := time.Now()
|
|
||||||
monitor.Start("close ", name)
|
|
||||||
err = E.Append(err, provider.Close(), func(err error) error {
|
|
||||||
return E.Cause(err, "close ", name)
|
|
||||||
})
|
|
||||||
monitor.Finish()
|
|
||||||
m.logger.Trace("close ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) CertificateProviders() []adapter.CertificateProviderService {
|
|
||||||
m.access.Lock()
|
|
||||||
defer m.access.Unlock()
|
|
||||||
return m.providers
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Get(tag string) (adapter.CertificateProviderService, bool) {
|
|
||||||
m.access.Lock()
|
|
||||||
provider, found := m.providerByTag[tag]
|
|
||||||
m.access.Unlock()
|
|
||||||
return provider, found
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Remove(tag string) error {
|
|
||||||
m.access.Lock()
|
|
||||||
provider, found := m.providerByTag[tag]
|
|
||||||
if !found {
|
|
||||||
m.access.Unlock()
|
|
||||||
return os.ErrInvalid
|
|
||||||
}
|
|
||||||
delete(m.providerByTag, tag)
|
|
||||||
index := common.Index(m.providers, func(it adapter.CertificateProviderService) bool {
|
|
||||||
return it == provider
|
|
||||||
})
|
|
||||||
if index == -1 {
|
|
||||||
panic("invalid certificate provider index")
|
|
||||||
}
|
|
||||||
m.providers = append(m.providers[:index], m.providers[index+1:]...)
|
|
||||||
started := m.started
|
|
||||||
m.access.Unlock()
|
|
||||||
if started {
|
|
||||||
return provider.Close()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Create(ctx context.Context, logger log.ContextLogger, tag string, providerType string, options any) error {
|
|
||||||
provider, err := m.registry.Create(ctx, logger, tag, providerType, options)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
m.access.Lock()
|
|
||||||
defer m.access.Unlock()
|
|
||||||
if m.started {
|
|
||||||
name := "certificate-provider/" + provider.Type() + "[" + provider.Tag() + "]"
|
|
||||||
for _, stage := range adapter.ListStartStages {
|
|
||||||
m.logger.Trace(stage, " ", name)
|
|
||||||
startTime := time.Now()
|
|
||||||
err = adapter.LegacyStart(provider, stage)
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, stage, " ", name)
|
|
||||||
}
|
|
||||||
m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if existsProvider, loaded := m.providerByTag[tag]; loaded {
|
|
||||||
if m.started {
|
|
||||||
err = existsProvider.Close()
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "close certificate-provider/", existsProvider.Type(), "[", existsProvider.Tag(), "]")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
existsIndex := common.Index(m.providers, func(it adapter.CertificateProviderService) bool {
|
|
||||||
return it == existsProvider
|
|
||||||
})
|
|
||||||
if existsIndex == -1 {
|
|
||||||
panic("invalid certificate provider index")
|
|
||||||
}
|
|
||||||
m.providers = append(m.providers[:existsIndex], m.providers[existsIndex+1:]...)
|
|
||||||
}
|
|
||||||
m.providers = append(m.providers, provider)
|
|
||||||
m.providerByTag[tag] = provider
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
package certificate
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ConstructorFunc[T any] func(ctx context.Context, logger log.ContextLogger, tag string, options T) (adapter.CertificateProviderService, error)
|
|
||||||
|
|
||||||
func Register[Options any](registry *Registry, providerType string, constructor ConstructorFunc[Options]) {
|
|
||||||
registry.register(providerType, func() any {
|
|
||||||
return new(Options)
|
|
||||||
}, func(ctx context.Context, logger log.ContextLogger, tag string, rawOptions any) (adapter.CertificateProviderService, error) {
|
|
||||||
var options *Options
|
|
||||||
if rawOptions != nil {
|
|
||||||
options = rawOptions.(*Options)
|
|
||||||
}
|
|
||||||
return constructor(ctx, logger, tag, common.PtrValueOrDefault(options))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ adapter.CertificateProviderRegistry = (*Registry)(nil)
|
|
||||||
|
|
||||||
type (
|
|
||||||
optionsConstructorFunc func() any
|
|
||||||
constructorFunc func(ctx context.Context, logger log.ContextLogger, tag string, options any) (adapter.CertificateProviderService, error)
|
|
||||||
)
|
|
||||||
|
|
||||||
type Registry struct {
|
|
||||||
access sync.Mutex
|
|
||||||
optionsType map[string]optionsConstructorFunc
|
|
||||||
constructor map[string]constructorFunc
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRegistry() *Registry {
|
|
||||||
return &Registry{
|
|
||||||
optionsType: make(map[string]optionsConstructorFunc),
|
|
||||||
constructor: make(map[string]constructorFunc),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Registry) CreateOptions(providerType string) (any, bool) {
|
|
||||||
m.access.Lock()
|
|
||||||
defer m.access.Unlock()
|
|
||||||
optionsConstructor, loaded := m.optionsType[providerType]
|
|
||||||
if !loaded {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
return optionsConstructor(), true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Registry) Create(ctx context.Context, logger log.ContextLogger, tag string, providerType string, options any) (adapter.CertificateProviderService, error) {
|
|
||||||
m.access.Lock()
|
|
||||||
defer m.access.Unlock()
|
|
||||||
constructor, loaded := m.constructor[providerType]
|
|
||||||
if !loaded {
|
|
||||||
return nil, E.New("certificate provider type not found: " + providerType)
|
|
||||||
}
|
|
||||||
return constructor(ctx, logger, tag, options)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Registry) register(providerType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) {
|
|
||||||
m.access.Lock()
|
|
||||||
defer m.access.Unlock()
|
|
||||||
m.optionsType[providerType] = optionsConstructor
|
|
||||||
m.constructor[providerType] = constructor
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
package adapter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/tls"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
|
||||||
)
|
|
||||||
|
|
||||||
type CertificateProvider interface {
|
|
||||||
GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type ACMECertificateProvider interface {
|
|
||||||
CertificateProvider
|
|
||||||
GetACMENextProtos() []string
|
|
||||||
}
|
|
||||||
|
|
||||||
type CertificateProviderService interface {
|
|
||||||
Lifecycle
|
|
||||||
Type() string
|
|
||||||
Tag() string
|
|
||||||
CertificateProvider
|
|
||||||
}
|
|
||||||
|
|
||||||
type CertificateProviderRegistry interface {
|
|
||||||
option.CertificateProviderOptionsRegistry
|
|
||||||
Create(ctx context.Context, logger log.ContextLogger, tag string, providerType string, options any) (CertificateProviderService, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type CertificateProviderManager interface {
|
|
||||||
Lifecycle
|
|
||||||
CertificateProviders() []CertificateProviderService
|
|
||||||
Get(tag string) (CertificateProviderService, bool)
|
|
||||||
Remove(tag string) error
|
|
||||||
Create(ctx context.Context, logger log.ContextLogger, tag string, providerType string, options any) error
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,6 @@ package adapter
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net"
|
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -83,8 +82,6 @@ type InboundContext struct {
|
|||||||
SourceGeoIPCode string
|
SourceGeoIPCode string
|
||||||
GeoIPCode string
|
GeoIPCode string
|
||||||
ProcessInfo *ConnectionOwner
|
ProcessInfo *ConnectionOwner
|
||||||
SourceMACAddress net.HardwareAddr
|
|
||||||
SourceHostname string
|
|
||||||
QueryType uint16
|
QueryType uint16
|
||||||
FakeIP bool
|
FakeIP bool
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
package adapter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"net/netip"
|
|
||||||
)
|
|
||||||
|
|
||||||
type NeighborEntry struct {
|
|
||||||
Address netip.Addr
|
|
||||||
MACAddress net.HardwareAddr
|
|
||||||
Hostname string
|
|
||||||
}
|
|
||||||
|
|
||||||
type NeighborResolver interface {
|
|
||||||
LookupMAC(address netip.Addr) (net.HardwareAddr, bool)
|
|
||||||
LookupHostname(address netip.Addr) (string, bool)
|
|
||||||
Start() error
|
|
||||||
Close() error
|
|
||||||
}
|
|
||||||
|
|
||||||
type NeighborUpdateListener interface {
|
|
||||||
UpdateNeighborTable(entries []NeighborEntry)
|
|
||||||
}
|
|
||||||
@@ -36,10 +36,6 @@ type PlatformInterface interface {
|
|||||||
|
|
||||||
UsePlatformNotification() bool
|
UsePlatformNotification() bool
|
||||||
SendNotification(notification *Notification) error
|
SendNotification(notification *Notification) error
|
||||||
|
|
||||||
UsePlatformNeighborResolver() bool
|
|
||||||
StartNeighborMonitor(listener NeighborUpdateListener) error
|
|
||||||
CloseNeighborMonitor(listener NeighborUpdateListener) error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type FindConnectionOwnerRequest struct {
|
type FindConnectionOwnerRequest struct {
|
||||||
|
|||||||
@@ -26,8 +26,6 @@ type Router interface {
|
|||||||
RuleSet(tag string) (RuleSet, bool)
|
RuleSet(tag string) (RuleSet, bool)
|
||||||
Rules() []Rule
|
Rules() []Rule
|
||||||
NeedFindProcess() bool
|
NeedFindProcess() bool
|
||||||
NeedFindNeighbor() bool
|
|
||||||
NeighborResolver() NeighborResolver
|
|
||||||
AppendTracker(tracker ConnectionTracker)
|
AppendTracker(tracker ConnectionTracker)
|
||||||
ResetNetwork()
|
ResetNetwork()
|
||||||
}
|
}
|
||||||
|
|||||||
123
box.go
123
box.go
@@ -9,7 +9,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
boxCertificate "github.com/sagernet/sing-box/adapter/certificate"
|
|
||||||
"github.com/sagernet/sing-box/adapter/endpoint"
|
"github.com/sagernet/sing-box/adapter/endpoint"
|
||||||
"github.com/sagernet/sing-box/adapter/inbound"
|
"github.com/sagernet/sing-box/adapter/inbound"
|
||||||
"github.com/sagernet/sing-box/adapter/outbound"
|
"github.com/sagernet/sing-box/adapter/outbound"
|
||||||
@@ -38,21 +37,20 @@ import (
|
|||||||
var _ adapter.SimpleLifecycle = (*Box)(nil)
|
var _ adapter.SimpleLifecycle = (*Box)(nil)
|
||||||
|
|
||||||
type Box struct {
|
type Box struct {
|
||||||
createdAt time.Time
|
createdAt time.Time
|
||||||
logFactory log.Factory
|
logFactory log.Factory
|
||||||
logger log.ContextLogger
|
logger log.ContextLogger
|
||||||
network *route.NetworkManager
|
network *route.NetworkManager
|
||||||
endpoint *endpoint.Manager
|
endpoint *endpoint.Manager
|
||||||
inbound *inbound.Manager
|
inbound *inbound.Manager
|
||||||
outbound *outbound.Manager
|
outbound *outbound.Manager
|
||||||
service *boxService.Manager
|
service *boxService.Manager
|
||||||
certificateProvider *boxCertificate.Manager
|
dnsTransport *dns.TransportManager
|
||||||
dnsTransport *dns.TransportManager
|
dnsRouter *dns.Router
|
||||||
dnsRouter *dns.Router
|
connection *route.ConnectionManager
|
||||||
connection *route.ConnectionManager
|
router *route.Router
|
||||||
router *route.Router
|
internalService []adapter.LifecycleService
|
||||||
internalService []adapter.LifecycleService
|
done chan struct{}
|
||||||
done chan struct{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Options struct {
|
type Options struct {
|
||||||
@@ -68,7 +66,6 @@ func Context(
|
|||||||
endpointRegistry adapter.EndpointRegistry,
|
endpointRegistry adapter.EndpointRegistry,
|
||||||
dnsTransportRegistry adapter.DNSTransportRegistry,
|
dnsTransportRegistry adapter.DNSTransportRegistry,
|
||||||
serviceRegistry adapter.ServiceRegistry,
|
serviceRegistry adapter.ServiceRegistry,
|
||||||
certificateProviderRegistry adapter.CertificateProviderRegistry,
|
|
||||||
) context.Context {
|
) context.Context {
|
||||||
if service.FromContext[option.InboundOptionsRegistry](ctx) == nil ||
|
if service.FromContext[option.InboundOptionsRegistry](ctx) == nil ||
|
||||||
service.FromContext[adapter.InboundRegistry](ctx) == nil {
|
service.FromContext[adapter.InboundRegistry](ctx) == nil {
|
||||||
@@ -93,10 +90,6 @@ func Context(
|
|||||||
ctx = service.ContextWith[option.ServiceOptionsRegistry](ctx, serviceRegistry)
|
ctx = service.ContextWith[option.ServiceOptionsRegistry](ctx, serviceRegistry)
|
||||||
ctx = service.ContextWith[adapter.ServiceRegistry](ctx, serviceRegistry)
|
ctx = service.ContextWith[adapter.ServiceRegistry](ctx, serviceRegistry)
|
||||||
}
|
}
|
||||||
if service.FromContext[adapter.CertificateProviderRegistry](ctx) == nil {
|
|
||||||
ctx = service.ContextWith[option.CertificateProviderOptionsRegistry](ctx, certificateProviderRegistry)
|
|
||||||
ctx = service.ContextWith[adapter.CertificateProviderRegistry](ctx, certificateProviderRegistry)
|
|
||||||
}
|
|
||||||
return ctx
|
return ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,7 +106,6 @@ func New(options Options) (*Box, error) {
|
|||||||
outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx)
|
outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx)
|
||||||
dnsTransportRegistry := service.FromContext[adapter.DNSTransportRegistry](ctx)
|
dnsTransportRegistry := service.FromContext[adapter.DNSTransportRegistry](ctx)
|
||||||
serviceRegistry := service.FromContext[adapter.ServiceRegistry](ctx)
|
serviceRegistry := service.FromContext[adapter.ServiceRegistry](ctx)
|
||||||
certificateProviderRegistry := service.FromContext[adapter.CertificateProviderRegistry](ctx)
|
|
||||||
|
|
||||||
if endpointRegistry == nil {
|
if endpointRegistry == nil {
|
||||||
return nil, E.New("missing endpoint registry in context")
|
return nil, E.New("missing endpoint registry in context")
|
||||||
@@ -130,9 +122,6 @@ func New(options Options) (*Box, error) {
|
|||||||
if serviceRegistry == nil {
|
if serviceRegistry == nil {
|
||||||
return nil, E.New("missing service registry in context")
|
return nil, E.New("missing service registry in context")
|
||||||
}
|
}
|
||||||
if certificateProviderRegistry == nil {
|
|
||||||
return nil, E.New("missing certificate provider registry in context")
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx = pause.WithDefaultManager(ctx)
|
ctx = pause.WithDefaultManager(ctx)
|
||||||
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
|
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
|
||||||
@@ -190,13 +179,11 @@ func New(options Options) (*Box, error) {
|
|||||||
outboundManager := outbound.NewManager(logFactory.NewLogger("outbound"), outboundRegistry, endpointManager, routeOptions.Final)
|
outboundManager := outbound.NewManager(logFactory.NewLogger("outbound"), outboundRegistry, endpointManager, routeOptions.Final)
|
||||||
dnsTransportManager := dns.NewTransportManager(logFactory.NewLogger("dns/transport"), dnsTransportRegistry, outboundManager, dnsOptions.Final)
|
dnsTransportManager := dns.NewTransportManager(logFactory.NewLogger("dns/transport"), dnsTransportRegistry, outboundManager, dnsOptions.Final)
|
||||||
serviceManager := boxService.NewManager(logFactory.NewLogger("service"), serviceRegistry)
|
serviceManager := boxService.NewManager(logFactory.NewLogger("service"), serviceRegistry)
|
||||||
certificateProviderManager := boxCertificate.NewManager(logFactory.NewLogger("certificate-provider"), certificateProviderRegistry)
|
|
||||||
service.MustRegister[adapter.EndpointManager](ctx, endpointManager)
|
service.MustRegister[adapter.EndpointManager](ctx, endpointManager)
|
||||||
service.MustRegister[adapter.InboundManager](ctx, inboundManager)
|
service.MustRegister[adapter.InboundManager](ctx, inboundManager)
|
||||||
service.MustRegister[adapter.OutboundManager](ctx, outboundManager)
|
service.MustRegister[adapter.OutboundManager](ctx, outboundManager)
|
||||||
service.MustRegister[adapter.DNSTransportManager](ctx, dnsTransportManager)
|
service.MustRegister[adapter.DNSTransportManager](ctx, dnsTransportManager)
|
||||||
service.MustRegister[adapter.ServiceManager](ctx, serviceManager)
|
service.MustRegister[adapter.ServiceManager](ctx, serviceManager)
|
||||||
service.MustRegister[adapter.CertificateProviderManager](ctx, certificateProviderManager)
|
|
||||||
dnsRouter := dns.NewRouter(ctx, logFactory, dnsOptions)
|
dnsRouter := dns.NewRouter(ctx, logFactory, dnsOptions)
|
||||||
service.MustRegister[adapter.DNSRouter](ctx, dnsRouter)
|
service.MustRegister[adapter.DNSRouter](ctx, dnsRouter)
|
||||||
networkManager, err := route.NewNetworkManager(ctx, logFactory.NewLogger("network"), routeOptions, dnsOptions)
|
networkManager, err := route.NewNetworkManager(ctx, logFactory.NewLogger("network"), routeOptions, dnsOptions)
|
||||||
@@ -285,24 +272,6 @@ func New(options Options) (*Box, error) {
|
|||||||
return nil, E.Cause(err, "initialize inbound[", i, "]")
|
return nil, E.Cause(err, "initialize inbound[", i, "]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for i, serviceOptions := range options.Services {
|
|
||||||
var tag string
|
|
||||||
if serviceOptions.Tag != "" {
|
|
||||||
tag = serviceOptions.Tag
|
|
||||||
} else {
|
|
||||||
tag = F.ToString(i)
|
|
||||||
}
|
|
||||||
err = serviceManager.Create(
|
|
||||||
ctx,
|
|
||||||
logFactory.NewLogger(F.ToString("service/", serviceOptions.Type, "[", tag, "]")),
|
|
||||||
tag,
|
|
||||||
serviceOptions.Type,
|
|
||||||
serviceOptions.Options,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "initialize service[", i, "]")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for i, outboundOptions := range options.Outbounds {
|
for i, outboundOptions := range options.Outbounds {
|
||||||
var tag string
|
var tag string
|
||||||
if outboundOptions.Tag != "" {
|
if outboundOptions.Tag != "" {
|
||||||
@@ -329,22 +298,22 @@ func New(options Options) (*Box, error) {
|
|||||||
return nil, E.Cause(err, "initialize outbound[", i, "]")
|
return nil, E.Cause(err, "initialize outbound[", i, "]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for i, certificateProviderOptions := range options.CertificateProviders {
|
for i, serviceOptions := range options.Services {
|
||||||
var tag string
|
var tag string
|
||||||
if certificateProviderOptions.Tag != "" {
|
if serviceOptions.Tag != "" {
|
||||||
tag = certificateProviderOptions.Tag
|
tag = serviceOptions.Tag
|
||||||
} else {
|
} else {
|
||||||
tag = F.ToString(i)
|
tag = F.ToString(i)
|
||||||
}
|
}
|
||||||
err = certificateProviderManager.Create(
|
err = serviceManager.Create(
|
||||||
ctx,
|
ctx,
|
||||||
logFactory.NewLogger(F.ToString("certificate-provider/", certificateProviderOptions.Type, "[", tag, "]")),
|
logFactory.NewLogger(F.ToString("service/", serviceOptions.Type, "[", tag, "]")),
|
||||||
tag,
|
tag,
|
||||||
certificateProviderOptions.Type,
|
serviceOptions.Type,
|
||||||
certificateProviderOptions.Options,
|
serviceOptions.Options,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "initialize certificate provider[", i, "]")
|
return nil, E.Cause(err, "initialize service[", i, "]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
outboundManager.Initialize(func() (adapter.Outbound, error) {
|
outboundManager.Initialize(func() (adapter.Outbound, error) {
|
||||||
@@ -414,21 +383,20 @@ func New(options Options) (*Box, error) {
|
|||||||
internalServices = append(internalServices, adapter.NewLifecycleService(ntpService, "ntp service"))
|
internalServices = append(internalServices, adapter.NewLifecycleService(ntpService, "ntp service"))
|
||||||
}
|
}
|
||||||
return &Box{
|
return &Box{
|
||||||
network: networkManager,
|
network: networkManager,
|
||||||
endpoint: endpointManager,
|
endpoint: endpointManager,
|
||||||
inbound: inboundManager,
|
inbound: inboundManager,
|
||||||
outbound: outboundManager,
|
outbound: outboundManager,
|
||||||
dnsTransport: dnsTransportManager,
|
dnsTransport: dnsTransportManager,
|
||||||
service: serviceManager,
|
service: serviceManager,
|
||||||
certificateProvider: certificateProviderManager,
|
dnsRouter: dnsRouter,
|
||||||
dnsRouter: dnsRouter,
|
connection: connectionManager,
|
||||||
connection: connectionManager,
|
router: router,
|
||||||
router: router,
|
createdAt: createdAt,
|
||||||
createdAt: createdAt,
|
logFactory: logFactory,
|
||||||
logFactory: logFactory,
|
logger: logFactory.Logger(),
|
||||||
logger: logFactory.Logger(),
|
internalService: internalServices,
|
||||||
internalService: internalServices,
|
done: make(chan struct{}),
|
||||||
done: make(chan struct{}),
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -482,7 +450,7 @@ func (s *Box) preStart() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.Start(s.logger, adapter.StartStateInitialize, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint, s.service, s.certificateProvider)
|
err = adapter.Start(s.logger, adapter.StartStateInitialize, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint, s.service)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -502,19 +470,11 @@ func (s *Box) start() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.Start(s.logger, adapter.StartStateStart, s.endpoint)
|
err = adapter.Start(s.logger, adapter.StartStateStart, s.inbound, s.endpoint, s.service)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.Start(s.logger, adapter.StartStateStart, s.certificateProvider)
|
err = adapter.Start(s.logger, adapter.StartStatePostStart, s.outbound, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.inbound, s.endpoint, s.service)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = adapter.Start(s.logger, adapter.StartStateStart, s.inbound, s.service)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = adapter.Start(s.logger, adapter.StartStatePostStart, s.outbound, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.endpoint, s.certificateProvider, s.inbound, s.service)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -522,7 +482,7 @@ func (s *Box) start() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.Start(s.logger, adapter.StartStateStarted, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.endpoint, s.certificateProvider, s.inbound, s.service)
|
err = adapter.Start(s.logger, adapter.StartStateStarted, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint, s.service)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -546,9 +506,8 @@ func (s *Box) Close() error {
|
|||||||
service adapter.Lifecycle
|
service adapter.Lifecycle
|
||||||
}{
|
}{
|
||||||
{"service", s.service},
|
{"service", s.service},
|
||||||
{"inbound", s.inbound},
|
|
||||||
{"certificate-provider", s.certificateProvider},
|
|
||||||
{"endpoint", s.endpoint},
|
{"endpoint", s.endpoint},
|
||||||
|
{"inbound", s.inbound},
|
||||||
{"outbound", s.outbound},
|
{"outbound", s.outbound},
|
||||||
{"router", s.router},
|
{"router", s.router},
|
||||||
{"connection", s.connection},
|
{"connection", s.connection},
|
||||||
|
|||||||
Submodule clients/android updated: bbee108ffc...4f0826b94d
Submodule clients/apple updated: 523f9347ae...ffbf405b52
@@ -38,6 +38,37 @@ func (w *acmeWrapper) Close() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type acmeLogWriter struct {
|
||||||
|
logger logger.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *acmeLogWriter) Write(p []byte) (n int, err error) {
|
||||||
|
logLine := strings.ReplaceAll(string(p), " ", ": ")
|
||||||
|
switch {
|
||||||
|
case strings.HasPrefix(logLine, "error: "):
|
||||||
|
w.logger.Error(logLine[7:])
|
||||||
|
case strings.HasPrefix(logLine, "warn: "):
|
||||||
|
w.logger.Warn(logLine[6:])
|
||||||
|
case strings.HasPrefix(logLine, "info: "):
|
||||||
|
w.logger.Info(logLine[6:])
|
||||||
|
case strings.HasPrefix(logLine, "debug: "):
|
||||||
|
w.logger.Debug(logLine[7:])
|
||||||
|
default:
|
||||||
|
w.logger.Debug(logLine)
|
||||||
|
}
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *acmeLogWriter) Sync() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func encoderConfig() zapcore.EncoderConfig {
|
||||||
|
config := zap.NewProductionEncoderConfig()
|
||||||
|
config.TimeKey = zapcore.OmitKey
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
func startACME(ctx context.Context, logger logger.Logger, options option.InboundACMEOptions) (*tls.Config, adapter.SimpleLifecycle, error) {
|
func startACME(ctx context.Context, logger logger.Logger, options option.InboundACMEOptions) (*tls.Config, adapter.SimpleLifecycle, error) {
|
||||||
var acmeServer string
|
var acmeServer string
|
||||||
switch options.Provider {
|
switch options.Provider {
|
||||||
@@ -60,8 +91,8 @@ func startACME(ctx context.Context, logger logger.Logger, options option.Inbound
|
|||||||
storage = certmagic.Default.Storage
|
storage = certmagic.Default.Storage
|
||||||
}
|
}
|
||||||
zapLogger := zap.New(zapcore.NewCore(
|
zapLogger := zap.New(zapcore.NewCore(
|
||||||
zapcore.NewConsoleEncoder(ACMEEncoderConfig()),
|
zapcore.NewConsoleEncoder(encoderConfig()),
|
||||||
&ACMELogWriter{Logger: logger},
|
&acmeLogWriter{logger: logger},
|
||||||
zap.DebugLevel,
|
zap.DebugLevel,
|
||||||
))
|
))
|
||||||
config := &certmagic.Config{
|
config := &certmagic.Config{
|
||||||
@@ -127,7 +158,7 @@ func startACME(ctx context.Context, logger logger.Logger, options option.Inbound
|
|||||||
} else {
|
} else {
|
||||||
tlsConfig = &tls.Config{
|
tlsConfig = &tls.Config{
|
||||||
GetCertificate: config.GetCertificate,
|
GetCertificate: config.GetCertificate,
|
||||||
NextProtos: []string{C.ACMETLS1Protocol},
|
NextProtos: []string{ACMETLS1Protocol},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return tlsConfig, &acmeWrapper{ctx: ctx, cfg: config, cache: cache, domain: options.Domain}, nil
|
return tlsConfig, &acmeWrapper{ctx: ctx, cfg: config, cache: cache, domain: options.Domain}, nil
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
package constant
|
package tls
|
||||||
|
|
||||||
const ACMETLS1Protocol = "acme-tls/1"
|
const ACMETLS1Protocol = "acme-tls/1"
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
package tls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing/common/logger"
|
|
||||||
|
|
||||||
"go.uber.org/zap"
|
|
||||||
"go.uber.org/zap/zapcore"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ACMELogWriter struct {
|
|
||||||
Logger logger.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *ACMELogWriter) Write(p []byte) (n int, err error) {
|
|
||||||
logLine := strings.ReplaceAll(string(p), " ", ": ")
|
|
||||||
switch {
|
|
||||||
case strings.HasPrefix(logLine, "error: "):
|
|
||||||
w.Logger.Error(logLine[7:])
|
|
||||||
case strings.HasPrefix(logLine, "warn: "):
|
|
||||||
w.Logger.Warn(logLine[6:])
|
|
||||||
case strings.HasPrefix(logLine, "info: "):
|
|
||||||
w.Logger.Info(logLine[6:])
|
|
||||||
case strings.HasPrefix(logLine, "debug: "):
|
|
||||||
w.Logger.Debug(logLine[7:])
|
|
||||||
default:
|
|
||||||
w.Logger.Debug(logLine)
|
|
||||||
}
|
|
||||||
return len(p), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *ACMELogWriter) Sync() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ACMEEncoderConfig() zapcore.EncoderConfig {
|
|
||||||
config := zap.NewProductionEncoderConfig()
|
|
||||||
config.TimeKey = zapcore.OmitKey
|
|
||||||
return config
|
|
||||||
}
|
|
||||||
@@ -32,10 +32,6 @@ type RealityServerConfig struct {
|
|||||||
func NewRealityServer(ctx context.Context, logger log.ContextLogger, options option.InboundTLSOptions) (ServerConfig, error) {
|
func NewRealityServer(ctx context.Context, logger log.ContextLogger, options option.InboundTLSOptions) (ServerConfig, error) {
|
||||||
var tlsConfig utls.RealityConfig
|
var tlsConfig utls.RealityConfig
|
||||||
|
|
||||||
if options.CertificateProvider != nil {
|
|
||||||
return nil, E.New("certificate_provider is unavailable in reality")
|
|
||||||
}
|
|
||||||
//nolint:staticcheck
|
|
||||||
if options.ACME != nil && len(options.ACME.Domain) > 0 {
|
if options.ACME != nil && len(options.ACME.Domain) > 0 {
|
||||||
return nil, E.New("acme is unavailable in reality")
|
return nil, E.New("acme is unavailable in reality")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,87 +13,19 @@ import (
|
|||||||
"github.com/sagernet/fswatch"
|
"github.com/sagernet/fswatch"
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/experimental/deprecated"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/ntp"
|
"github.com/sagernet/sing/common/ntp"
|
||||||
"github.com/sagernet/sing/service"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var errInsecureUnused = E.New("tls: insecure unused")
|
var errInsecureUnused = E.New("tls: insecure unused")
|
||||||
|
|
||||||
type managedCertificateProvider interface {
|
|
||||||
adapter.CertificateProvider
|
|
||||||
adapter.SimpleLifecycle
|
|
||||||
}
|
|
||||||
|
|
||||||
type sharedCertificateProvider struct {
|
|
||||||
tag string
|
|
||||||
manager adapter.CertificateProviderManager
|
|
||||||
provider adapter.CertificateProviderService
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *sharedCertificateProvider) Start() error {
|
|
||||||
provider, found := p.manager.Get(p.tag)
|
|
||||||
if !found {
|
|
||||||
return E.New("certificate provider not found: ", p.tag)
|
|
||||||
}
|
|
||||||
p.provider = provider
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *sharedCertificateProvider) Close() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *sharedCertificateProvider) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
|
||||||
return p.provider.GetCertificate(hello)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *sharedCertificateProvider) GetACMENextProtos() []string {
|
|
||||||
return getACMENextProtos(p.provider)
|
|
||||||
}
|
|
||||||
|
|
||||||
type inlineCertificateProvider struct {
|
|
||||||
provider adapter.CertificateProviderService
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *inlineCertificateProvider) Start() error {
|
|
||||||
for _, stage := range adapter.ListStartStages {
|
|
||||||
err := adapter.LegacyStart(p.provider, stage)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *inlineCertificateProvider) Close() error {
|
|
||||||
return p.provider.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *inlineCertificateProvider) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
|
||||||
return p.provider.GetCertificate(hello)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *inlineCertificateProvider) GetACMENextProtos() []string {
|
|
||||||
return getACMENextProtos(p.provider)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getACMENextProtos(provider adapter.CertificateProvider) []string {
|
|
||||||
if acmeProvider, isACME := provider.(adapter.ACMECertificateProvider); isACME {
|
|
||||||
return acmeProvider.GetACMENextProtos()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type STDServerConfig struct {
|
type STDServerConfig struct {
|
||||||
access sync.RWMutex
|
access sync.RWMutex
|
||||||
config *tls.Config
|
config *tls.Config
|
||||||
logger log.Logger
|
logger log.Logger
|
||||||
certificateProvider managedCertificateProvider
|
|
||||||
acmeService adapter.SimpleLifecycle
|
acmeService adapter.SimpleLifecycle
|
||||||
certificate []byte
|
certificate []byte
|
||||||
key []byte
|
key []byte
|
||||||
@@ -121,17 +53,18 @@ func (c *STDServerConfig) SetServerName(serverName string) {
|
|||||||
func (c *STDServerConfig) NextProtos() []string {
|
func (c *STDServerConfig) NextProtos() []string {
|
||||||
c.access.RLock()
|
c.access.RLock()
|
||||||
defer c.access.RUnlock()
|
defer c.access.RUnlock()
|
||||||
if c.hasACMEALPN() && len(c.config.NextProtos) > 1 && c.config.NextProtos[0] == C.ACMETLS1Protocol {
|
if c.acmeService != nil && len(c.config.NextProtos) > 1 && c.config.NextProtos[0] == ACMETLS1Protocol {
|
||||||
return c.config.NextProtos[1:]
|
return c.config.NextProtos[1:]
|
||||||
|
} else {
|
||||||
|
return c.config.NextProtos
|
||||||
}
|
}
|
||||||
return c.config.NextProtos
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDServerConfig) SetNextProtos(nextProto []string) {
|
func (c *STDServerConfig) SetNextProtos(nextProto []string) {
|
||||||
c.access.Lock()
|
c.access.Lock()
|
||||||
defer c.access.Unlock()
|
defer c.access.Unlock()
|
||||||
config := c.config.Clone()
|
config := c.config.Clone()
|
||||||
if c.hasACMEALPN() && len(c.config.NextProtos) > 1 && c.config.NextProtos[0] == C.ACMETLS1Protocol {
|
if c.acmeService != nil && len(c.config.NextProtos) > 1 && c.config.NextProtos[0] == ACMETLS1Protocol {
|
||||||
config.NextProtos = append(c.config.NextProtos[:1], nextProto...)
|
config.NextProtos = append(c.config.NextProtos[:1], nextProto...)
|
||||||
} else {
|
} else {
|
||||||
config.NextProtos = nextProto
|
config.NextProtos = nextProto
|
||||||
@@ -139,18 +72,6 @@ func (c *STDServerConfig) SetNextProtos(nextProto []string) {
|
|||||||
c.config = config
|
c.config = config
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDServerConfig) hasACMEALPN() bool {
|
|
||||||
if c.acmeService != nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if c.certificateProvider != nil {
|
|
||||||
if acmeProvider, isACME := c.certificateProvider.(adapter.ACMECertificateProvider); isACME {
|
|
||||||
return len(acmeProvider.GetACMENextProtos()) > 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *STDServerConfig) STDConfig() (*STDConfig, error) {
|
func (c *STDServerConfig) STDConfig() (*STDConfig, error) {
|
||||||
return c.config, nil
|
return c.config, nil
|
||||||
}
|
}
|
||||||
@@ -170,39 +91,15 @@ func (c *STDServerConfig) Clone() Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDServerConfig) Start() error {
|
func (c *STDServerConfig) Start() error {
|
||||||
if c.certificateProvider != nil {
|
|
||||||
err := c.certificateProvider.Start()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if acmeProvider, isACME := c.certificateProvider.(adapter.ACMECertificateProvider); isACME {
|
|
||||||
nextProtos := acmeProvider.GetACMENextProtos()
|
|
||||||
if len(nextProtos) > 0 {
|
|
||||||
c.access.Lock()
|
|
||||||
config := c.config.Clone()
|
|
||||||
mergedNextProtos := append([]string{}, nextProtos...)
|
|
||||||
for _, nextProto := range config.NextProtos {
|
|
||||||
if !common.Contains(mergedNextProtos, nextProto) {
|
|
||||||
mergedNextProtos = append(mergedNextProtos, nextProto)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
config.NextProtos = mergedNextProtos
|
|
||||||
c.config = config
|
|
||||||
c.access.Unlock()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if c.acmeService != nil {
|
if c.acmeService != nil {
|
||||||
err := c.acmeService.Start()
|
return c.acmeService.Start()
|
||||||
|
} else {
|
||||||
|
err := c.startWatcher()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
c.logger.Warn("create fsnotify watcher: ", err)
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
err := c.startWatcher()
|
|
||||||
if err != nil {
|
|
||||||
c.logger.Warn("create fsnotify watcher: ", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDServerConfig) startWatcher() error {
|
func (c *STDServerConfig) startWatcher() error {
|
||||||
@@ -306,34 +203,23 @@ func (c *STDServerConfig) certificateUpdated(path string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDServerConfig) Close() error {
|
func (c *STDServerConfig) Close() error {
|
||||||
return common.Close(c.certificateProvider, c.acmeService, c.watcher)
|
if c.acmeService != nil {
|
||||||
|
return c.acmeService.Close()
|
||||||
|
}
|
||||||
|
if c.watcher != nil {
|
||||||
|
return c.watcher.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSTDServer(ctx context.Context, logger log.ContextLogger, options option.InboundTLSOptions) (ServerConfig, error) {
|
func NewSTDServer(ctx context.Context, logger log.ContextLogger, options option.InboundTLSOptions) (ServerConfig, error) {
|
||||||
if !options.Enabled {
|
if !options.Enabled {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
//nolint:staticcheck
|
|
||||||
if options.CertificateProvider != nil && options.ACME != nil {
|
|
||||||
return nil, E.New("certificate_provider and acme are mutually exclusive")
|
|
||||||
}
|
|
||||||
var tlsConfig *tls.Config
|
var tlsConfig *tls.Config
|
||||||
var certificateProvider managedCertificateProvider
|
|
||||||
var acmeService adapter.SimpleLifecycle
|
var acmeService adapter.SimpleLifecycle
|
||||||
var err error
|
var err error
|
||||||
if options.CertificateProvider != nil {
|
if options.ACME != nil && len(options.ACME.Domain) > 0 {
|
||||||
certificateProvider, err = newCertificateProvider(ctx, logger, options.CertificateProvider)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
tlsConfig = &tls.Config{
|
|
||||||
GetCertificate: certificateProvider.GetCertificate,
|
|
||||||
}
|
|
||||||
if options.Insecure {
|
|
||||||
return nil, errInsecureUnused
|
|
||||||
}
|
|
||||||
} else if options.ACME != nil && len(options.ACME.Domain) > 0 { //nolint:staticcheck
|
|
||||||
deprecated.Report(ctx, deprecated.OptionInlineACME)
|
|
||||||
//nolint:staticcheck
|
//nolint:staticcheck
|
||||||
tlsConfig, acmeService, err = startACME(ctx, logger, common.PtrValueOrDefault(options.ACME))
|
tlsConfig, acmeService, err = startACME(ctx, logger, common.PtrValueOrDefault(options.ACME))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -386,7 +272,7 @@ func NewSTDServer(ctx context.Context, logger log.ContextLogger, options option.
|
|||||||
certificate []byte
|
certificate []byte
|
||||||
key []byte
|
key []byte
|
||||||
)
|
)
|
||||||
if certificateProvider == nil && acmeService == nil {
|
if acmeService == nil {
|
||||||
if len(options.Certificate) > 0 {
|
if len(options.Certificate) > 0 {
|
||||||
certificate = []byte(strings.Join(options.Certificate, "\n"))
|
certificate = []byte(strings.Join(options.Certificate, "\n"))
|
||||||
} else if options.CertificatePath != "" {
|
} else if options.CertificatePath != "" {
|
||||||
@@ -474,7 +360,6 @@ func NewSTDServer(ctx context.Context, logger log.ContextLogger, options option.
|
|||||||
serverConfig := &STDServerConfig{
|
serverConfig := &STDServerConfig{
|
||||||
config: tlsConfig,
|
config: tlsConfig,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
certificateProvider: certificateProvider,
|
|
||||||
acmeService: acmeService,
|
acmeService: acmeService,
|
||||||
certificate: certificate,
|
certificate: certificate,
|
||||||
key: key,
|
key: key,
|
||||||
@@ -484,8 +369,8 @@ func NewSTDServer(ctx context.Context, logger log.ContextLogger, options option.
|
|||||||
echKeyPath: echKeyPath,
|
echKeyPath: echKeyPath,
|
||||||
}
|
}
|
||||||
serverConfig.config.GetConfigForClient = func(info *tls.ClientHelloInfo) (*tls.Config, error) {
|
serverConfig.config.GetConfigForClient = func(info *tls.ClientHelloInfo) (*tls.Config, error) {
|
||||||
serverConfig.access.RLock()
|
serverConfig.access.Lock()
|
||||||
defer serverConfig.access.RUnlock()
|
defer serverConfig.access.Unlock()
|
||||||
return serverConfig.config, nil
|
return serverConfig.config, nil
|
||||||
}
|
}
|
||||||
var config ServerConfig = serverConfig
|
var config ServerConfig = serverConfig
|
||||||
@@ -502,27 +387,3 @@ func NewSTDServer(ctx context.Context, logger log.ContextLogger, options option.
|
|||||||
}
|
}
|
||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func newCertificateProvider(ctx context.Context, logger log.ContextLogger, options *option.CertificateProviderOptions) (managedCertificateProvider, error) {
|
|
||||||
if options.IsShared() {
|
|
||||||
manager := service.FromContext[adapter.CertificateProviderManager](ctx)
|
|
||||||
if manager == nil {
|
|
||||||
return nil, E.New("missing certificate provider manager in context")
|
|
||||||
}
|
|
||||||
return &sharedCertificateProvider{
|
|
||||||
tag: options.Tag,
|
|
||||||
manager: manager,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
registry := service.FromContext[adapter.CertificateProviderRegistry](ctx)
|
|
||||||
if registry == nil {
|
|
||||||
return nil, E.New("missing certificate provider registry in context")
|
|
||||||
}
|
|
||||||
provider, err := registry.Create(ctx, logger, "", options.Type, options.Options)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "create inline certificate provider")
|
|
||||||
}
|
|
||||||
return &inlineCertificateProvider{
|
|
||||||
provider: provider,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,38 +1,37 @@
|
|||||||
package constant
|
package constant
|
||||||
|
|
||||||
const (
|
const (
|
||||||
TypeTun = "tun"
|
TypeTun = "tun"
|
||||||
TypeRedirect = "redirect"
|
TypeRedirect = "redirect"
|
||||||
TypeTProxy = "tproxy"
|
TypeTProxy = "tproxy"
|
||||||
TypeDirect = "direct"
|
TypeDirect = "direct"
|
||||||
TypeBlock = "block"
|
TypeBlock = "block"
|
||||||
TypeDNS = "dns"
|
TypeDNS = "dns"
|
||||||
TypeSOCKS = "socks"
|
TypeSOCKS = "socks"
|
||||||
TypeHTTP = "http"
|
TypeHTTP = "http"
|
||||||
TypeMixed = "mixed"
|
TypeMixed = "mixed"
|
||||||
TypeShadowsocks = "shadowsocks"
|
TypeShadowsocks = "shadowsocks"
|
||||||
TypeVMess = "vmess"
|
TypeVMess = "vmess"
|
||||||
TypeTrojan = "trojan"
|
TypeTrojan = "trojan"
|
||||||
TypeNaive = "naive"
|
TypeNaive = "naive"
|
||||||
TypeWireGuard = "wireguard"
|
TypeWireGuard = "wireguard"
|
||||||
TypeHysteria = "hysteria"
|
TypeHysteria = "hysteria"
|
||||||
TypeTor = "tor"
|
TypeTor = "tor"
|
||||||
TypeSSH = "ssh"
|
TypeSSH = "ssh"
|
||||||
TypeShadowTLS = "shadowtls"
|
TypeShadowTLS = "shadowtls"
|
||||||
TypeAnyTLS = "anytls"
|
TypeAnyTLS = "anytls"
|
||||||
TypeShadowsocksR = "shadowsocksr"
|
TypeShadowsocksR = "shadowsocksr"
|
||||||
TypeVLESS = "vless"
|
TypeVLESS = "vless"
|
||||||
TypeTUIC = "tuic"
|
TypeTUIC = "tuic"
|
||||||
TypeHysteria2 = "hysteria2"
|
TypeHysteria2 = "hysteria2"
|
||||||
TypeTailscale = "tailscale"
|
TypeTailscale = "tailscale"
|
||||||
TypeDERP = "derp"
|
TypeCloudflared = "cloudflared"
|
||||||
TypeResolved = "resolved"
|
TypeDERP = "derp"
|
||||||
TypeSSMAPI = "ssm-api"
|
TypeResolved = "resolved"
|
||||||
TypeCCM = "ccm"
|
TypeSSMAPI = "ssm-api"
|
||||||
TypeOCM = "ocm"
|
TypeCCM = "ccm"
|
||||||
TypeOOMKiller = "oom-killer"
|
TypeOCM = "ocm"
|
||||||
TypeACME = "acme"
|
TypeOOMKiller = "oom-killer"
|
||||||
TypeCloudflareOriginCA = "cloudflare-origin-ca"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -90,6 +89,8 @@ func ProxyDisplayName(proxyType string) string {
|
|||||||
return "AnyTLS"
|
return "AnyTLS"
|
||||||
case TypeTailscale:
|
case TypeTailscale:
|
||||||
return "Tailscale"
|
return "Tailscale"
|
||||||
|
case TypeCloudflared:
|
||||||
|
return "Cloudflared"
|
||||||
case TypeSelector:
|
case TypeSelector:
|
||||||
return "Selector"
|
return "Selector"
|
||||||
case TypeURLTest:
|
case TypeURLTest:
|
||||||
|
|||||||
@@ -2,33 +2,18 @@
|
|||||||
icon: material/alert-decagram
|
icon: material/alert-decagram
|
||||||
---
|
---
|
||||||
|
|
||||||
#### 1.14.0-alpha.6
|
#### 1.13.6
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
#### 1.13.4-beta.3
|
#### 1.13.5
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
#### 1.14.0-alpha.4
|
#### 1.13.4
|
||||||
|
|
||||||
* Refactor ACME support to certificate provider system **1**
|
|
||||||
* Add Cloudflare Origin CA certificate provider **2**
|
|
||||||
* Add Tailscale certificate provider **3**
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
**1**:
|
|
||||||
|
|
||||||
See [Certificate Provider](/configuration/shared/certificate-provider/) and [Migration](/migration/#migrate-inline-acme-to-certificate-provider).
|
|
||||||
|
|
||||||
**2**:
|
|
||||||
|
|
||||||
See [Cloudflare Origin CA](/configuration/shared/certificate-provider/cloudflare-origin-ca).
|
|
||||||
|
|
||||||
**3**:
|
|
||||||
|
|
||||||
See [Tailscale](/configuration/shared/certificate-provider/tailscale).
|
|
||||||
|
|
||||||
#### 1.13.3
|
#### 1.13.3
|
||||||
|
|
||||||
* Add OpenWrt and Alpine APK packages to release **1**
|
* Add OpenWrt and Alpine APK packages to release **1**
|
||||||
@@ -53,59 +38,6 @@ from [SagerNet/go](https://github.com/SagerNet/go).
|
|||||||
|
|
||||||
See [OCM](/configuration/service/ocm).
|
See [OCM](/configuration/service/ocm).
|
||||||
|
|
||||||
#### 1.12.24
|
|
||||||
|
|
||||||
* Fixes and improvements
|
|
||||||
|
|
||||||
#### 1.14.0-alpha.2
|
|
||||||
|
|
||||||
* Add OpenWrt and Alpine APK packages to release **1**
|
|
||||||
* Backport to macOS 10.13 High Sierra **2**
|
|
||||||
* OCM service: Add WebSocket support for Responses API **3**
|
|
||||||
* Fixes and improvements
|
|
||||||
|
|
||||||
**1**:
|
|
||||||
|
|
||||||
Alpine APK files use `linux` in the filename to distinguish from OpenWrt APKs which use the `openwrt` prefix:
|
|
||||||
|
|
||||||
- OpenWrt: `sing-box_{version}_openwrt_{architecture}.apk`
|
|
||||||
- Alpine: `sing-box_{version}_linux_{architecture}.apk`
|
|
||||||
|
|
||||||
**2**:
|
|
||||||
|
|
||||||
Legacy macOS binaries (with `-legacy-macos-10.13` suffix) now support
|
|
||||||
macOS 10.13 High Sierra, built using Go 1.25 with patches
|
|
||||||
from [SagerNet/go](https://github.com/SagerNet/go).
|
|
||||||
|
|
||||||
**3**:
|
|
||||||
|
|
||||||
See [OCM](/configuration/service/ocm).
|
|
||||||
|
|
||||||
#### 1.14.0-alpha.1
|
|
||||||
|
|
||||||
* Add `source_mac_address` and `source_hostname` rule items **1**
|
|
||||||
* Add `include_mac_address` and `exclude_mac_address` TUN options **2**
|
|
||||||
* Update NaiveProxy to 145.0.7632.159 **3**
|
|
||||||
* Fixes and improvements
|
|
||||||
|
|
||||||
**1**:
|
|
||||||
|
|
||||||
New rule items for matching LAN devices by MAC address and hostname via neighbor resolution.
|
|
||||||
Supported on Linux, macOS, or in graphical clients on Android and macOS.
|
|
||||||
|
|
||||||
See [Route Rule](/configuration/route/rule/#source_mac_address), [DNS Rule](/configuration/dns/rule/#source_mac_address) and [Neighbor Resolution](/configuration/shared/neighbor/).
|
|
||||||
|
|
||||||
**2**:
|
|
||||||
|
|
||||||
Limit or exclude devices from TUN routing by MAC address.
|
|
||||||
Only supported on Linux with `auto_route` and `auto_redirect` enabled.
|
|
||||||
|
|
||||||
See [TUN](/configuration/inbound/tun/#include_mac_address).
|
|
||||||
|
|
||||||
**3**:
|
|
||||||
|
|
||||||
This is not an official update from NaiveProxy. Instead, it's a Chromium codebase update maintained by Project S.
|
|
||||||
|
|
||||||
#### 1.13.2
|
#### 1.13.2
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|||||||
@@ -2,11 +2,6 @@
|
|||||||
icon: material/alert-decagram
|
icon: material/alert-decagram
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.14.0"
|
|
||||||
|
|
||||||
:material-plus: [source_mac_address](#source_mac_address)
|
|
||||||
:material-plus: [source_hostname](#source_hostname)
|
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.13.0"
|
!!! quote "Changes in sing-box 1.13.0"
|
||||||
|
|
||||||
:material-plus: [interface_address](#interface_address)
|
:material-plus: [interface_address](#interface_address)
|
||||||
@@ -154,12 +149,6 @@ icon: material/alert-decagram
|
|||||||
"default_interface_address": [
|
"default_interface_address": [
|
||||||
"2000::/3"
|
"2000::/3"
|
||||||
],
|
],
|
||||||
"source_mac_address": [
|
|
||||||
"00:11:22:33:44:55"
|
|
||||||
],
|
|
||||||
"source_hostname": [
|
|
||||||
"my-device"
|
|
||||||
],
|
|
||||||
"wifi_ssid": [
|
"wifi_ssid": [
|
||||||
"My WIFI"
|
"My WIFI"
|
||||||
],
|
],
|
||||||
@@ -419,26 +408,6 @@ Matches network interface (same values as `network_type`) address.
|
|||||||
|
|
||||||
Match default interface address.
|
Match default interface address.
|
||||||
|
|
||||||
#### source_mac_address
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.14.0"
|
|
||||||
|
|
||||||
!!! quote ""
|
|
||||||
|
|
||||||
Only supported on Linux, macOS, or in graphical clients on Android and macOS. See [Neighbor Resolution](/configuration/shared/neighbor/) for setup.
|
|
||||||
|
|
||||||
Match source device MAC address.
|
|
||||||
|
|
||||||
#### source_hostname
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.14.0"
|
|
||||||
|
|
||||||
!!! quote ""
|
|
||||||
|
|
||||||
Only supported on Linux, macOS, or in graphical clients on Android and macOS. See [Neighbor Resolution](/configuration/shared/neighbor/) for setup.
|
|
||||||
|
|
||||||
Match source device hostname from DHCP leases.
|
|
||||||
|
|
||||||
#### wifi_ssid
|
#### wifi_ssid
|
||||||
|
|
||||||
!!! quote ""
|
!!! quote ""
|
||||||
|
|||||||
@@ -2,11 +2,6 @@
|
|||||||
icon: material/alert-decagram
|
icon: material/alert-decagram
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! quote "sing-box 1.14.0 中的更改"
|
|
||||||
|
|
||||||
:material-plus: [source_mac_address](#source_mac_address)
|
|
||||||
:material-plus: [source_hostname](#source_hostname)
|
|
||||||
|
|
||||||
!!! quote "sing-box 1.13.0 中的更改"
|
!!! quote "sing-box 1.13.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [interface_address](#interface_address)
|
:material-plus: [interface_address](#interface_address)
|
||||||
@@ -154,12 +149,6 @@ icon: material/alert-decagram
|
|||||||
"default_interface_address": [
|
"default_interface_address": [
|
||||||
"2000::/3"
|
"2000::/3"
|
||||||
],
|
],
|
||||||
"source_mac_address": [
|
|
||||||
"00:11:22:33:44:55"
|
|
||||||
],
|
|
||||||
"source_hostname": [
|
|
||||||
"my-device"
|
|
||||||
],
|
|
||||||
"wifi_ssid": [
|
"wifi_ssid": [
|
||||||
"My WIFI"
|
"My WIFI"
|
||||||
],
|
],
|
||||||
@@ -418,26 +407,6 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
|
|||||||
|
|
||||||
匹配默认接口地址。
|
匹配默认接口地址。
|
||||||
|
|
||||||
#### source_mac_address
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.14.0 起"
|
|
||||||
|
|
||||||
!!! quote ""
|
|
||||||
|
|
||||||
仅支持 Linux、macOS,或在 Android 和 macOS 图形客户端中支持。参阅 [邻居解析](/configuration/shared/neighbor/) 了解设置方法。
|
|
||||||
|
|
||||||
匹配源设备 MAC 地址。
|
|
||||||
|
|
||||||
#### source_hostname
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.14.0 起"
|
|
||||||
|
|
||||||
!!! quote ""
|
|
||||||
|
|
||||||
仅支持 Linux、macOS,或在 Android 和 macOS 图形客户端中支持。参阅 [邻居解析](/configuration/shared/neighbor/) 了解设置方法。
|
|
||||||
|
|
||||||
匹配源设备从 DHCP 租约获取的主机名。
|
|
||||||
|
|
||||||
#### wifi_ssid
|
#### wifi_ssid
|
||||||
|
|
||||||
!!! quote ""
|
!!! quote ""
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ icon: material/new-box
|
|||||||
|
|
||||||
!!! quote "Changes in sing-box 1.14.0"
|
!!! quote "Changes in sing-box 1.14.0"
|
||||||
|
|
||||||
:material-plus: [include_mac_address](#include_mac_address)
|
:material-plus: [include_mac_address](#include_mac_address)
|
||||||
:material-plus: [exclude_mac_address](#exclude_mac_address)
|
:material-plus: [exclude_mac_address](#exclude_mac_address)
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.13.3"
|
!!! quote "Changes in sing-box 1.13.3"
|
||||||
@@ -134,12 +134,6 @@ icon: material/new-box
|
|||||||
"exclude_package": [
|
"exclude_package": [
|
||||||
"com.android.captiveportallogin"
|
"com.android.captiveportallogin"
|
||||||
],
|
],
|
||||||
"include_mac_address": [
|
|
||||||
"00:11:22:33:44:55"
|
|
||||||
],
|
|
||||||
"exclude_mac_address": [
|
|
||||||
"66:77:88:99:aa:bb"
|
|
||||||
],
|
|
||||||
"platform": {
|
"platform": {
|
||||||
"http_proxy": {
|
"http_proxy": {
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
@@ -566,30 +560,6 @@ Limit android packages in route.
|
|||||||
|
|
||||||
Exclude android packages in route.
|
Exclude android packages in route.
|
||||||
|
|
||||||
#### include_mac_address
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.14.0"
|
|
||||||
|
|
||||||
!!! quote ""
|
|
||||||
|
|
||||||
Only supported on Linux with `auto_route` and `auto_redirect` enabled.
|
|
||||||
|
|
||||||
Limit MAC addresses in route. Not limited by default.
|
|
||||||
|
|
||||||
Conflict with `exclude_mac_address`.
|
|
||||||
|
|
||||||
#### exclude_mac_address
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.14.0"
|
|
||||||
|
|
||||||
!!! quote ""
|
|
||||||
|
|
||||||
Only supported on Linux with `auto_route` and `auto_redirect` enabled.
|
|
||||||
|
|
||||||
Exclude MAC addresses in route.
|
|
||||||
|
|
||||||
Conflict with `include_mac_address`.
|
|
||||||
|
|
||||||
#### platform
|
#### platform
|
||||||
|
|
||||||
Platform-specific settings, provided by client applications.
|
Platform-specific settings, provided by client applications.
|
||||||
|
|||||||
@@ -2,11 +2,6 @@
|
|||||||
icon: material/new-box
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! quote "sing-box 1.14.0 中的更改"
|
|
||||||
|
|
||||||
:material-plus: [include_mac_address](#include_mac_address)
|
|
||||||
:material-plus: [exclude_mac_address](#exclude_mac_address)
|
|
||||||
|
|
||||||
!!! quote "sing-box 1.13.3 中的更改"
|
!!! quote "sing-box 1.13.3 中的更改"
|
||||||
|
|
||||||
:material-alert: [strict_route](#strict_route)
|
:material-alert: [strict_route](#strict_route)
|
||||||
@@ -135,12 +130,6 @@ icon: material/new-box
|
|||||||
"exclude_package": [
|
"exclude_package": [
|
||||||
"com.android.captiveportallogin"
|
"com.android.captiveportallogin"
|
||||||
],
|
],
|
||||||
"include_mac_address": [
|
|
||||||
"00:11:22:33:44:55"
|
|
||||||
],
|
|
||||||
"exclude_mac_address": [
|
|
||||||
"66:77:88:99:aa:bb"
|
|
||||||
],
|
|
||||||
"platform": {
|
"platform": {
|
||||||
"http_proxy": {
|
"http_proxy": {
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
@@ -554,30 +543,6 @@ TCP/IP 栈。
|
|||||||
|
|
||||||
排除路由的 Android 应用包名。
|
排除路由的 Android 应用包名。
|
||||||
|
|
||||||
#### include_mac_address
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.14.0 起"
|
|
||||||
|
|
||||||
!!! quote ""
|
|
||||||
|
|
||||||
仅支持 Linux,且需要 `auto_route` 和 `auto_redirect` 已启用。
|
|
||||||
|
|
||||||
限制被路由的 MAC 地址。默认不限制。
|
|
||||||
|
|
||||||
与 `exclude_mac_address` 冲突。
|
|
||||||
|
|
||||||
#### exclude_mac_address
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.14.0 起"
|
|
||||||
|
|
||||||
!!! quote ""
|
|
||||||
|
|
||||||
仅支持 Linux,且需要 `auto_route` 和 `auto_redirect` 已启用。
|
|
||||||
|
|
||||||
排除路由的 MAC 地址。
|
|
||||||
|
|
||||||
与 `include_mac_address` 冲突。
|
|
||||||
|
|
||||||
#### platform
|
#### platform
|
||||||
|
|
||||||
平台特定的设置,由客户端应用提供。
|
平台特定的设置,由客户端应用提供。
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# Introduction
|
# Introduction
|
||||||
|
|
||||||
sing-box uses JSON for configuration files.
|
sing-box uses JSON for configuration files.
|
||||||
|
|
||||||
### Structure
|
### Structure
|
||||||
|
|
||||||
```json
|
```json
|
||||||
@@ -9,7 +10,6 @@ sing-box uses JSON for configuration files.
|
|||||||
"dns": {},
|
"dns": {},
|
||||||
"ntp": {},
|
"ntp": {},
|
||||||
"certificate": {},
|
"certificate": {},
|
||||||
"certificate_providers": [],
|
|
||||||
"endpoints": [],
|
"endpoints": [],
|
||||||
"inbounds": [],
|
"inbounds": [],
|
||||||
"outbounds": [],
|
"outbounds": [],
|
||||||
@@ -27,7 +27,6 @@ sing-box uses JSON for configuration files.
|
|||||||
| `dns` | [DNS](./dns/) |
|
| `dns` | [DNS](./dns/) |
|
||||||
| `ntp` | [NTP](./ntp/) |
|
| `ntp` | [NTP](./ntp/) |
|
||||||
| `certificate` | [Certificate](./certificate/) |
|
| `certificate` | [Certificate](./certificate/) |
|
||||||
| `certificate_providers` | [Certificate Provider](./shared/certificate-provider/) |
|
|
||||||
| `endpoints` | [Endpoint](./endpoint/) |
|
| `endpoints` | [Endpoint](./endpoint/) |
|
||||||
| `inbounds` | [Inbound](./inbound/) |
|
| `inbounds` | [Inbound](./inbound/) |
|
||||||
| `outbounds` | [Outbound](./outbound/) |
|
| `outbounds` | [Outbound](./outbound/) |
|
||||||
@@ -51,4 +50,4 @@ sing-box format -w -c config.json -D config_directory
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
sing-box merge output.json -c config.json -D config_directory
|
sing-box merge output.json -c config.json -D config_directory
|
||||||
```
|
```
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
# 引言
|
# 引言
|
||||||
|
|
||||||
sing-box 使用 JSON 作为配置文件格式。
|
sing-box 使用 JSON 作为配置文件格式。
|
||||||
|
|
||||||
### 结构
|
### 结构
|
||||||
|
|
||||||
```json
|
```json
|
||||||
@@ -9,7 +10,6 @@ sing-box 使用 JSON 作为配置文件格式。
|
|||||||
"dns": {},
|
"dns": {},
|
||||||
"ntp": {},
|
"ntp": {},
|
||||||
"certificate": {},
|
"certificate": {},
|
||||||
"certificate_providers": [],
|
|
||||||
"endpoints": [],
|
"endpoints": [],
|
||||||
"inbounds": [],
|
"inbounds": [],
|
||||||
"outbounds": [],
|
"outbounds": [],
|
||||||
@@ -27,7 +27,6 @@ sing-box 使用 JSON 作为配置文件格式。
|
|||||||
| `dns` | [DNS](./dns/) |
|
| `dns` | [DNS](./dns/) |
|
||||||
| `ntp` | [NTP](./ntp/) |
|
| `ntp` | [NTP](./ntp/) |
|
||||||
| `certificate` | [证书](./certificate/) |
|
| `certificate` | [证书](./certificate/) |
|
||||||
| `certificate_providers` | [证书提供者](./shared/certificate-provider/) |
|
|
||||||
| `endpoints` | [端点](./endpoint/) |
|
| `endpoints` | [端点](./endpoint/) |
|
||||||
| `inbounds` | [入站](./inbound/) |
|
| `inbounds` | [入站](./inbound/) |
|
||||||
| `outbounds` | [出站](./outbound/) |
|
| `outbounds` | [出站](./outbound/) |
|
||||||
@@ -51,4 +50,4 @@ sing-box format -w -c config.json -D config_directory
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
sing-box merge output.json -c config.json -D config_directory
|
sing-box merge output.json -c config.json -D config_directory
|
||||||
```
|
```
|
||||||
@@ -4,11 +4,6 @@ icon: material/alert-decagram
|
|||||||
|
|
||||||
# Route
|
# Route
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.14.0"
|
|
||||||
|
|
||||||
:material-plus: [find_neighbor](#find_neighbor)
|
|
||||||
:material-plus: [dhcp_lease_files](#dhcp_lease_files)
|
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.12.0"
|
!!! quote "Changes in sing-box 1.12.0"
|
||||||
|
|
||||||
:material-plus: [default_domain_resolver](#default_domain_resolver)
|
:material-plus: [default_domain_resolver](#default_domain_resolver)
|
||||||
@@ -40,9 +35,6 @@ icon: material/alert-decagram
|
|||||||
"override_android_vpn": false,
|
"override_android_vpn": false,
|
||||||
"default_interface": "",
|
"default_interface": "",
|
||||||
"default_mark": 0,
|
"default_mark": 0,
|
||||||
"find_process": false,
|
|
||||||
"find_neighbor": false,
|
|
||||||
"dhcp_lease_files": [],
|
|
||||||
"default_domain_resolver": "", // or {}
|
"default_domain_resolver": "", // or {}
|
||||||
"default_network_strategy": "",
|
"default_network_strategy": "",
|
||||||
"default_network_type": [],
|
"default_network_type": [],
|
||||||
@@ -115,38 +107,6 @@ Set routing mark by default.
|
|||||||
|
|
||||||
Takes no effect if `outbound.routing_mark` is set.
|
Takes no effect if `outbound.routing_mark` is set.
|
||||||
|
|
||||||
#### find_process
|
|
||||||
|
|
||||||
!!! quote ""
|
|
||||||
|
|
||||||
Only supported on Linux, Windows, and macOS.
|
|
||||||
|
|
||||||
Enable process search for logging when no `process_name`, `process_path`, `package_name`, `user` or `user_id` rules exist.
|
|
||||||
|
|
||||||
#### find_neighbor
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.14.0"
|
|
||||||
|
|
||||||
!!! quote ""
|
|
||||||
|
|
||||||
Only supported on Linux and macOS.
|
|
||||||
|
|
||||||
Enable neighbor resolution for logging when no `source_mac_address` or `source_hostname` rules exist.
|
|
||||||
|
|
||||||
See [Neighbor Resolution](/configuration/shared/neighbor/) for setup.
|
|
||||||
|
|
||||||
#### dhcp_lease_files
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.14.0"
|
|
||||||
|
|
||||||
!!! quote ""
|
|
||||||
|
|
||||||
Only supported on Linux and macOS.
|
|
||||||
|
|
||||||
Custom DHCP lease file paths for hostname and MAC address resolution.
|
|
||||||
|
|
||||||
Automatically detected from common DHCP servers (dnsmasq, odhcpd, ISC dhcpd, Kea) if empty.
|
|
||||||
|
|
||||||
#### default_domain_resolver
|
#### default_domain_resolver
|
||||||
|
|
||||||
!!! question "Since sing-box 1.12.0"
|
!!! question "Since sing-box 1.12.0"
|
||||||
|
|||||||
@@ -4,11 +4,6 @@ icon: material/alert-decagram
|
|||||||
|
|
||||||
# 路由
|
# 路由
|
||||||
|
|
||||||
!!! quote "sing-box 1.14.0 中的更改"
|
|
||||||
|
|
||||||
:material-plus: [find_neighbor](#find_neighbor)
|
|
||||||
:material-plus: [dhcp_lease_files](#dhcp_lease_files)
|
|
||||||
|
|
||||||
!!! quote "sing-box 1.12.0 中的更改"
|
!!! quote "sing-box 1.12.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [default_domain_resolver](#default_domain_resolver)
|
:material-plus: [default_domain_resolver](#default_domain_resolver)
|
||||||
@@ -42,9 +37,6 @@ icon: material/alert-decagram
|
|||||||
"override_android_vpn": false,
|
"override_android_vpn": false,
|
||||||
"default_interface": "",
|
"default_interface": "",
|
||||||
"default_mark": 0,
|
"default_mark": 0,
|
||||||
"find_process": false,
|
|
||||||
"find_neighbor": false,
|
|
||||||
"dhcp_lease_files": [],
|
|
||||||
"default_network_strategy": "",
|
"default_network_strategy": "",
|
||||||
"default_fallback_delay": ""
|
"default_fallback_delay": ""
|
||||||
}
|
}
|
||||||
@@ -114,38 +106,6 @@ icon: material/alert-decagram
|
|||||||
|
|
||||||
如果设置了 `outbound.routing_mark` 设置,则不生效。
|
如果设置了 `outbound.routing_mark` 设置,则不生效。
|
||||||
|
|
||||||
#### find_process
|
|
||||||
|
|
||||||
!!! quote ""
|
|
||||||
|
|
||||||
仅支持 Linux、Windows 和 macOS。
|
|
||||||
|
|
||||||
在没有 `process_name`、`process_path`、`package_name`、`user` 或 `user_id` 规则时启用进程搜索以输出日志。
|
|
||||||
|
|
||||||
#### find_neighbor
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.14.0 起"
|
|
||||||
|
|
||||||
!!! quote ""
|
|
||||||
|
|
||||||
仅支持 Linux 和 macOS。
|
|
||||||
|
|
||||||
在没有 `source_mac_address` 或 `source_hostname` 规则时启用邻居解析以输出日志。
|
|
||||||
|
|
||||||
参阅 [邻居解析](/configuration/shared/neighbor/) 了解设置方法。
|
|
||||||
|
|
||||||
#### dhcp_lease_files
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.14.0 起"
|
|
||||||
|
|
||||||
!!! quote ""
|
|
||||||
|
|
||||||
仅支持 Linux 和 macOS。
|
|
||||||
|
|
||||||
用于主机名和 MAC 地址解析的自定义 DHCP 租约文件路径。
|
|
||||||
|
|
||||||
为空时自动从常见 DHCP 服务器(dnsmasq、odhcpd、ISC dhcpd、Kea)检测。
|
|
||||||
|
|
||||||
#### default_domain_resolver
|
#### default_domain_resolver
|
||||||
|
|
||||||
!!! question "自 sing-box 1.12.0 起"
|
!!! question "自 sing-box 1.12.0 起"
|
||||||
|
|||||||
@@ -2,11 +2,6 @@
|
|||||||
icon: material/new-box
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.14.0"
|
|
||||||
|
|
||||||
:material-plus: [source_mac_address](#source_mac_address)
|
|
||||||
:material-plus: [source_hostname](#source_hostname)
|
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.13.0"
|
!!! quote "Changes in sing-box 1.13.0"
|
||||||
|
|
||||||
:material-plus: [interface_address](#interface_address)
|
:material-plus: [interface_address](#interface_address)
|
||||||
@@ -164,12 +159,6 @@ icon: material/new-box
|
|||||||
"tailscale",
|
"tailscale",
|
||||||
"wireguard"
|
"wireguard"
|
||||||
],
|
],
|
||||||
"source_mac_address": [
|
|
||||||
"00:11:22:33:44:55"
|
|
||||||
],
|
|
||||||
"source_hostname": [
|
|
||||||
"my-device"
|
|
||||||
],
|
|
||||||
"rule_set": [
|
"rule_set": [
|
||||||
"geoip-cn",
|
"geoip-cn",
|
||||||
"geosite-cn"
|
"geosite-cn"
|
||||||
@@ -460,26 +449,6 @@ Match specified outbounds' preferred routes.
|
|||||||
| `tailscale` | Match MagicDNS domains and peers' allowed IPs |
|
| `tailscale` | Match MagicDNS domains and peers' allowed IPs |
|
||||||
| `wireguard` | Match peers's allowed IPs |
|
| `wireguard` | Match peers's allowed IPs |
|
||||||
|
|
||||||
#### source_mac_address
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.14.0"
|
|
||||||
|
|
||||||
!!! quote ""
|
|
||||||
|
|
||||||
Only supported on Linux, macOS, or in graphical clients on Android and macOS. See [Neighbor Resolution](/configuration/shared/neighbor/) for setup.
|
|
||||||
|
|
||||||
Match source device MAC address.
|
|
||||||
|
|
||||||
#### source_hostname
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.14.0"
|
|
||||||
|
|
||||||
!!! quote ""
|
|
||||||
|
|
||||||
Only supported on Linux, macOS, or in graphical clients on Android and macOS. See [Neighbor Resolution](/configuration/shared/neighbor/) for setup.
|
|
||||||
|
|
||||||
Match source device hostname from DHCP leases.
|
|
||||||
|
|
||||||
#### rule_set
|
#### rule_set
|
||||||
|
|
||||||
!!! question "Since sing-box 1.8.0"
|
!!! question "Since sing-box 1.8.0"
|
||||||
|
|||||||
@@ -2,11 +2,6 @@
|
|||||||
icon: material/new-box
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! quote "sing-box 1.14.0 中的更改"
|
|
||||||
|
|
||||||
:material-plus: [source_mac_address](#source_mac_address)
|
|
||||||
:material-plus: [source_hostname](#source_hostname)
|
|
||||||
|
|
||||||
!!! quote "sing-box 1.13.0 中的更改"
|
!!! quote "sing-box 1.13.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [interface_address](#interface_address)
|
:material-plus: [interface_address](#interface_address)
|
||||||
@@ -162,12 +157,6 @@ icon: material/new-box
|
|||||||
"tailscale",
|
"tailscale",
|
||||||
"wireguard"
|
"wireguard"
|
||||||
],
|
],
|
||||||
"source_mac_address": [
|
|
||||||
"00:11:22:33:44:55"
|
|
||||||
],
|
|
||||||
"source_hostname": [
|
|
||||||
"my-device"
|
|
||||||
],
|
|
||||||
"rule_set": [
|
"rule_set": [
|
||||||
"geoip-cn",
|
"geoip-cn",
|
||||||
"geosite-cn"
|
"geosite-cn"
|
||||||
@@ -458,26 +447,6 @@ icon: material/new-box
|
|||||||
| `tailscale` | 匹配 MagicDNS 域名和对端的 allowed IPs |
|
| `tailscale` | 匹配 MagicDNS 域名和对端的 allowed IPs |
|
||||||
| `wireguard` | 匹配对端的 allowed IPs |
|
| `wireguard` | 匹配对端的 allowed IPs |
|
||||||
|
|
||||||
#### source_mac_address
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.14.0 起"
|
|
||||||
|
|
||||||
!!! quote ""
|
|
||||||
|
|
||||||
仅支持 Linux、macOS,或在 Android 和 macOS 图形客户端中支持。参阅 [邻居解析](/configuration/shared/neighbor/) 了解设置方法。
|
|
||||||
|
|
||||||
匹配源设备 MAC 地址。
|
|
||||||
|
|
||||||
#### source_hostname
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.14.0 起"
|
|
||||||
|
|
||||||
!!! quote ""
|
|
||||||
|
|
||||||
仅支持 Linux、macOS,或在 Android 和 macOS 图形客户端中支持。参阅 [邻居解析](/configuration/shared/neighbor/) 了解设置方法。
|
|
||||||
|
|
||||||
匹配源设备从 DHCP 租约获取的主机名。
|
|
||||||
|
|
||||||
#### rule_set
|
#### rule_set
|
||||||
|
|
||||||
!!! question "自 sing-box 1.8.0 起"
|
!!! question "自 sing-box 1.8.0 起"
|
||||||
|
|||||||
@@ -1,150 +0,0 @@
|
|||||||
---
|
|
||||||
icon: material/new-box
|
|
||||||
---
|
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.14.0"
|
|
||||||
|
|
||||||
:material-plus: [account_key](#account_key)
|
|
||||||
:material-plus: [key_type](#key_type)
|
|
||||||
:material-plus: [detour](#detour)
|
|
||||||
|
|
||||||
# ACME
|
|
||||||
|
|
||||||
!!! quote ""
|
|
||||||
|
|
||||||
`with_acme` build tag required.
|
|
||||||
|
|
||||||
### Structure
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"type": "acme",
|
|
||||||
"tag": "",
|
|
||||||
|
|
||||||
"domain": [],
|
|
||||||
"data_directory": "",
|
|
||||||
"default_server_name": "",
|
|
||||||
"email": "",
|
|
||||||
"provider": "",
|
|
||||||
"account_key": "",
|
|
||||||
"disable_http_challenge": false,
|
|
||||||
"disable_tls_alpn_challenge": false,
|
|
||||||
"alternative_http_port": 0,
|
|
||||||
"alternative_tls_port": 0,
|
|
||||||
"external_account": {
|
|
||||||
"key_id": "",
|
|
||||||
"mac_key": ""
|
|
||||||
},
|
|
||||||
"dns01_challenge": {},
|
|
||||||
"key_type": "",
|
|
||||||
"detour": ""
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Fields
|
|
||||||
|
|
||||||
#### domain
|
|
||||||
|
|
||||||
==Required==
|
|
||||||
|
|
||||||
List of domains.
|
|
||||||
|
|
||||||
#### data_directory
|
|
||||||
|
|
||||||
The directory to store ACME data.
|
|
||||||
|
|
||||||
`$XDG_DATA_HOME/certmagic|$HOME/.local/share/certmagic` will be used if empty.
|
|
||||||
|
|
||||||
#### default_server_name
|
|
||||||
|
|
||||||
Server name to use when choosing a certificate if the ClientHello's ServerName field is empty.
|
|
||||||
|
|
||||||
#### email
|
|
||||||
|
|
||||||
The email address to use when creating or selecting an existing ACME server account.
|
|
||||||
|
|
||||||
#### provider
|
|
||||||
|
|
||||||
The ACME CA provider to use.
|
|
||||||
|
|
||||||
| Value | Provider |
|
|
||||||
|-------------------------|---------------|
|
|
||||||
| `letsencrypt (default)` | Let's Encrypt |
|
|
||||||
| `zerossl` | ZeroSSL |
|
|
||||||
| `https://...` | Custom |
|
|
||||||
|
|
||||||
When `provider` is `zerossl`, sing-box will automatically request ZeroSSL EAB credentials if `email` is set and
|
|
||||||
`external_account` is empty.
|
|
||||||
|
|
||||||
When `provider` is `zerossl`, at least one of `external_account`, `email`, or `account_key` is required.
|
|
||||||
|
|
||||||
#### account_key
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.14.0"
|
|
||||||
|
|
||||||
The PEM-encoded private key of an existing ACME account.
|
|
||||||
|
|
||||||
#### disable_http_challenge
|
|
||||||
|
|
||||||
Disable all HTTP challenges.
|
|
||||||
|
|
||||||
#### disable_tls_alpn_challenge
|
|
||||||
|
|
||||||
Disable all TLS-ALPN challenges
|
|
||||||
|
|
||||||
#### alternative_http_port
|
|
||||||
|
|
||||||
The alternate port to use for the ACME HTTP challenge; if non-empty, this port will be used instead of 80 to spin up a
|
|
||||||
listener for the HTTP challenge.
|
|
||||||
|
|
||||||
#### alternative_tls_port
|
|
||||||
|
|
||||||
The alternate port to use for the ACME TLS-ALPN challenge; the system must forward 443 to this port for challenge to
|
|
||||||
succeed.
|
|
||||||
|
|
||||||
#### external_account
|
|
||||||
|
|
||||||
EAB (External Account Binding) contains information necessary to bind or map an ACME account to some other account known
|
|
||||||
by the CA.
|
|
||||||
|
|
||||||
External account bindings are used to associate an ACME account with an existing account in a non-ACME system, such as
|
|
||||||
a CA customer database.
|
|
||||||
|
|
||||||
To enable ACME account binding, the CA operating the ACME server needs to provide the ACME client with a MAC key and a
|
|
||||||
key identifier, using some mechanism outside of ACME. §7.3.4
|
|
||||||
|
|
||||||
#### external_account.key_id
|
|
||||||
|
|
||||||
The key identifier.
|
|
||||||
|
|
||||||
#### external_account.mac_key
|
|
||||||
|
|
||||||
The MAC key.
|
|
||||||
|
|
||||||
#### dns01_challenge
|
|
||||||
|
|
||||||
ACME DNS01 challenge field. If configured, other challenge methods will be disabled.
|
|
||||||
|
|
||||||
See [DNS01 Challenge Fields](/configuration/shared/dns01_challenge/) for details.
|
|
||||||
|
|
||||||
#### key_type
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.14.0"
|
|
||||||
|
|
||||||
The private key type to generate for new certificates.
|
|
||||||
|
|
||||||
| Value | Type |
|
|
||||||
|------------|---------|
|
|
||||||
| `ed25519` | Ed25519 |
|
|
||||||
| `p256` | P-256 |
|
|
||||||
| `p384` | P-384 |
|
|
||||||
| `rsa2048` | RSA |
|
|
||||||
| `rsa4096` | RSA |
|
|
||||||
|
|
||||||
#### detour
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.14.0"
|
|
||||||
|
|
||||||
The tag of the upstream outbound.
|
|
||||||
|
|
||||||
All provider HTTP requests will use this outbound.
|
|
||||||
@@ -1,145 +0,0 @@
|
|||||||
---
|
|
||||||
icon: material/new-box
|
|
||||||
---
|
|
||||||
|
|
||||||
!!! quote "sing-box 1.14.0 中的更改"
|
|
||||||
|
|
||||||
:material-plus: [account_key](#account_key)
|
|
||||||
:material-plus: [key_type](#key_type)
|
|
||||||
:material-plus: [detour](#detour)
|
|
||||||
|
|
||||||
# ACME
|
|
||||||
|
|
||||||
!!! quote ""
|
|
||||||
|
|
||||||
需要 `with_acme` 构建标签。
|
|
||||||
|
|
||||||
### 结构
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"type": "acme",
|
|
||||||
"tag": "",
|
|
||||||
|
|
||||||
"domain": [],
|
|
||||||
"data_directory": "",
|
|
||||||
"default_server_name": "",
|
|
||||||
"email": "",
|
|
||||||
"provider": "",
|
|
||||||
"account_key": "",
|
|
||||||
"disable_http_challenge": false,
|
|
||||||
"disable_tls_alpn_challenge": false,
|
|
||||||
"alternative_http_port": 0,
|
|
||||||
"alternative_tls_port": 0,
|
|
||||||
"external_account": {
|
|
||||||
"key_id": "",
|
|
||||||
"mac_key": ""
|
|
||||||
},
|
|
||||||
"dns01_challenge": {},
|
|
||||||
"key_type": "",
|
|
||||||
"detour": ""
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 字段
|
|
||||||
|
|
||||||
#### domain
|
|
||||||
|
|
||||||
==必填==
|
|
||||||
|
|
||||||
域名列表。
|
|
||||||
|
|
||||||
#### data_directory
|
|
||||||
|
|
||||||
ACME 数据存储目录。
|
|
||||||
|
|
||||||
如果为空则使用 `$XDG_DATA_HOME/certmagic|$HOME/.local/share/certmagic`。
|
|
||||||
|
|
||||||
#### default_server_name
|
|
||||||
|
|
||||||
如果 ClientHello 的 ServerName 字段为空,则选择证书时要使用的服务器名称。
|
|
||||||
|
|
||||||
#### email
|
|
||||||
|
|
||||||
创建或选择现有 ACME 服务器帐户时使用的电子邮件地址。
|
|
||||||
|
|
||||||
#### provider
|
|
||||||
|
|
||||||
要使用的 ACME CA 提供商。
|
|
||||||
|
|
||||||
| 值 | 提供商 |
|
|
||||||
|--------------------|---------------|
|
|
||||||
| `letsencrypt (默认)` | Let's Encrypt |
|
|
||||||
| `zerossl` | ZeroSSL |
|
|
||||||
| `https://...` | 自定义 |
|
|
||||||
|
|
||||||
当 `provider` 为 `zerossl` 时,如果设置了 `email` 且未设置 `external_account`,
|
|
||||||
sing-box 会自动向 ZeroSSL 请求 EAB 凭据。
|
|
||||||
|
|
||||||
当 `provider` 为 `zerossl` 时,必须至少设置 `external_account`、`email` 或 `account_key` 之一。
|
|
||||||
|
|
||||||
#### account_key
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.14.0 起"
|
|
||||||
|
|
||||||
现有 ACME 帐户的 PEM 编码私钥。
|
|
||||||
|
|
||||||
#### disable_http_challenge
|
|
||||||
|
|
||||||
禁用所有 HTTP 质询。
|
|
||||||
|
|
||||||
#### disable_tls_alpn_challenge
|
|
||||||
|
|
||||||
禁用所有 TLS-ALPN 质询。
|
|
||||||
|
|
||||||
#### alternative_http_port
|
|
||||||
|
|
||||||
用于 ACME HTTP 质询的备用端口;如果非空,将使用此端口而不是 80 来启动 HTTP 质询的侦听器。
|
|
||||||
|
|
||||||
#### alternative_tls_port
|
|
||||||
|
|
||||||
用于 ACME TLS-ALPN 质询的备用端口; 系统必须将 443 转发到此端口以使质询成功。
|
|
||||||
|
|
||||||
#### external_account
|
|
||||||
|
|
||||||
EAB(外部帐户绑定)包含将 ACME 帐户绑定或映射到 CA 已知的其他帐户所需的信息。
|
|
||||||
|
|
||||||
外部帐户绑定用于将 ACME 帐户与非 ACME 系统中的现有帐户相关联,例如 CA 客户数据库。
|
|
||||||
|
|
||||||
为了启用 ACME 帐户绑定,运行 ACME 服务器的 CA 需要使用 ACME 之外的某种机制向 ACME 客户端提供 MAC 密钥和密钥标识符。§7.3.4
|
|
||||||
|
|
||||||
#### external_account.key_id
|
|
||||||
|
|
||||||
密钥标识符。
|
|
||||||
|
|
||||||
#### external_account.mac_key
|
|
||||||
|
|
||||||
MAC 密钥。
|
|
||||||
|
|
||||||
#### dns01_challenge
|
|
||||||
|
|
||||||
ACME DNS01 质询字段。如果配置,将禁用其他质询方法。
|
|
||||||
|
|
||||||
参阅 [DNS01 质询字段](/zh/configuration/shared/dns01_challenge/)。
|
|
||||||
|
|
||||||
#### key_type
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.14.0 起"
|
|
||||||
|
|
||||||
为新证书生成的私钥类型。
|
|
||||||
|
|
||||||
| 值 | 类型 |
|
|
||||||
|-----------|----------|
|
|
||||||
| `ed25519` | Ed25519 |
|
|
||||||
| `p256` | P-256 |
|
|
||||||
| `p384` | P-384 |
|
|
||||||
| `rsa2048` | RSA |
|
|
||||||
| `rsa4096` | RSA |
|
|
||||||
|
|
||||||
#### detour
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.14.0 起"
|
|
||||||
|
|
||||||
上游出站的标签。
|
|
||||||
|
|
||||||
所有提供者 HTTP 请求将使用此出站。
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
---
|
|
||||||
icon: material/new-box
|
|
||||||
---
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.14.0"
|
|
||||||
|
|
||||||
# Cloudflare Origin CA
|
|
||||||
|
|
||||||
### Structure
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"type": "cloudflare-origin-ca",
|
|
||||||
"tag": "",
|
|
||||||
|
|
||||||
"domain": [],
|
|
||||||
"data_directory": "",
|
|
||||||
"api_token": "",
|
|
||||||
"origin_ca_key": "",
|
|
||||||
"request_type": "",
|
|
||||||
"requested_validity": 0,
|
|
||||||
"detour": ""
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Fields
|
|
||||||
|
|
||||||
#### domain
|
|
||||||
|
|
||||||
==Required==
|
|
||||||
|
|
||||||
List of domain names or wildcard domain names to include in the certificate.
|
|
||||||
|
|
||||||
#### data_directory
|
|
||||||
|
|
||||||
Root directory used to store the issued certificate, private key, and metadata.
|
|
||||||
|
|
||||||
If empty, sing-box uses the same default data directory as the ACME certificate provider:
|
|
||||||
`$XDG_DATA_HOME/certmagic` or `$HOME/.local/share/certmagic`.
|
|
||||||
|
|
||||||
#### api_token
|
|
||||||
|
|
||||||
Cloudflare API token used to create the certificate.
|
|
||||||
|
|
||||||
Get or create one in [Cloudflare Dashboard > My Profile > API Tokens](https://dash.cloudflare.com/profile/api-tokens).
|
|
||||||
|
|
||||||
Requires the `Zone / SSL and Certificates / Edit` permission.
|
|
||||||
|
|
||||||
Conflict with `origin_ca_key`.
|
|
||||||
|
|
||||||
#### origin_ca_key
|
|
||||||
|
|
||||||
Cloudflare Origin CA Key.
|
|
||||||
|
|
||||||
Get it in [Cloudflare Dashboard > My Profile > API Tokens > API Keys > Origin CA Key](https://dash.cloudflare.com/profile/api-tokens).
|
|
||||||
|
|
||||||
Conflict with `api_token`.
|
|
||||||
|
|
||||||
#### request_type
|
|
||||||
|
|
||||||
The signature type to request from Cloudflare.
|
|
||||||
|
|
||||||
| Value | Type |
|
|
||||||
|----------------------|-------------|
|
|
||||||
| `origin-rsa` | RSA |
|
|
||||||
| `origin-ecc` | ECDSA P-256 |
|
|
||||||
|
|
||||||
`origin-rsa` is used if empty.
|
|
||||||
|
|
||||||
#### requested_validity
|
|
||||||
|
|
||||||
The requested certificate validity in days.
|
|
||||||
|
|
||||||
Available values: `7`, `30`, `90`, `365`, `730`, `1095`, `5475`.
|
|
||||||
|
|
||||||
`5475` days (15 years) is used if empty.
|
|
||||||
|
|
||||||
#### detour
|
|
||||||
|
|
||||||
The tag of the upstream outbound.
|
|
||||||
|
|
||||||
All provider HTTP requests will use this outbound.
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
---
|
|
||||||
icon: material/new-box
|
|
||||||
---
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.14.0 起"
|
|
||||||
|
|
||||||
# Cloudflare Origin CA
|
|
||||||
|
|
||||||
### 结构
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"type": "cloudflare-origin-ca",
|
|
||||||
"tag": "",
|
|
||||||
|
|
||||||
"domain": [],
|
|
||||||
"data_directory": "",
|
|
||||||
"api_token": "",
|
|
||||||
"origin_ca_key": "",
|
|
||||||
"request_type": "",
|
|
||||||
"requested_validity": 0,
|
|
||||||
"detour": ""
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 字段
|
|
||||||
|
|
||||||
#### domain
|
|
||||||
|
|
||||||
==必填==
|
|
||||||
|
|
||||||
要写入证书的域名或通配符域名列表。
|
|
||||||
|
|
||||||
#### data_directory
|
|
||||||
|
|
||||||
保存签发证书、私钥和元数据的根目录。
|
|
||||||
|
|
||||||
如果为空,sing-box 会使用与 ACME 证书提供者相同的默认数据目录:
|
|
||||||
`$XDG_DATA_HOME/certmagic` 或 `$HOME/.local/share/certmagic`。
|
|
||||||
|
|
||||||
#### api_token
|
|
||||||
|
|
||||||
用于创建证书的 Cloudflare API Token。
|
|
||||||
|
|
||||||
可在 [Cloudflare Dashboard > My Profile > API Tokens](https://dash.cloudflare.com/profile/api-tokens) 获取或创建。
|
|
||||||
|
|
||||||
需要 `Zone / SSL and Certificates / Edit` 权限。
|
|
||||||
|
|
||||||
与 `origin_ca_key` 冲突。
|
|
||||||
|
|
||||||
#### origin_ca_key
|
|
||||||
|
|
||||||
Cloudflare Origin CA Key。
|
|
||||||
|
|
||||||
可在 [Cloudflare Dashboard > My Profile > API Tokens > API Keys > Origin CA Key](https://dash.cloudflare.com/profile/api-tokens) 获取。
|
|
||||||
|
|
||||||
与 `api_token` 冲突。
|
|
||||||
|
|
||||||
#### request_type
|
|
||||||
|
|
||||||
向 Cloudflare 请求的签名类型。
|
|
||||||
|
|
||||||
| 值 | 类型 |
|
|
||||||
|----------------------|-------------|
|
|
||||||
| `origin-rsa` | RSA |
|
|
||||||
| `origin-ecc` | ECDSA P-256 |
|
|
||||||
|
|
||||||
如果为空,使用 `origin-rsa`。
|
|
||||||
|
|
||||||
#### requested_validity
|
|
||||||
|
|
||||||
请求的证书有效期,单位为天。
|
|
||||||
|
|
||||||
可用值:`7`、`30`、`90`、`365`、`730`、`1095`、`5475`。
|
|
||||||
|
|
||||||
如果为空,使用 `5475` 天(15 年)。
|
|
||||||
|
|
||||||
#### detour
|
|
||||||
|
|
||||||
上游出站的标签。
|
|
||||||
|
|
||||||
所有提供者 HTTP 请求将使用此出站。
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
---
|
|
||||||
icon: material/new-box
|
|
||||||
---
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.14.0"
|
|
||||||
|
|
||||||
# Certificate Provider
|
|
||||||
|
|
||||||
### Structure
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"certificate_providers": [
|
|
||||||
{
|
|
||||||
"type": "",
|
|
||||||
"tag": ""
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Fields
|
|
||||||
|
|
||||||
| Type | Format |
|
|
||||||
|--------|------------------|
|
|
||||||
| `acme` | [ACME](/configuration/shared/certificate-provider/acme) |
|
|
||||||
| `tailscale` | [Tailscale](/configuration/shared/certificate-provider/tailscale) |
|
|
||||||
| `cloudflare-origin-ca` | [Cloudflare Origin CA](/configuration/shared/certificate-provider/cloudflare-origin-ca) |
|
|
||||||
|
|
||||||
#### tag
|
|
||||||
|
|
||||||
The tag of the certificate provider.
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
---
|
|
||||||
icon: material/new-box
|
|
||||||
---
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.14.0 起"
|
|
||||||
|
|
||||||
# 证书提供者
|
|
||||||
|
|
||||||
### 结构
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"certificate_providers": [
|
|
||||||
{
|
|
||||||
"type": "",
|
|
||||||
"tag": ""
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 字段
|
|
||||||
|
|
||||||
| 类型 | 格式 |
|
|
||||||
|--------|------------------|
|
|
||||||
| `acme` | [ACME](/zh/configuration/shared/certificate-provider/acme) |
|
|
||||||
| `tailscale` | [Tailscale](/zh/configuration/shared/certificate-provider/tailscale) |
|
|
||||||
| `cloudflare-origin-ca` | [Cloudflare Origin CA](/zh/configuration/shared/certificate-provider/cloudflare-origin-ca) |
|
|
||||||
|
|
||||||
#### tag
|
|
||||||
|
|
||||||
证书提供者的标签。
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
---
|
|
||||||
icon: material/new-box
|
|
||||||
---
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.14.0"
|
|
||||||
|
|
||||||
# Tailscale
|
|
||||||
|
|
||||||
### Structure
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"type": "tailscale",
|
|
||||||
"tag": "ts-cert",
|
|
||||||
"endpoint": "ts-ep"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Fields
|
|
||||||
|
|
||||||
#### endpoint
|
|
||||||
|
|
||||||
==Required==
|
|
||||||
|
|
||||||
The tag of the [Tailscale endpoint](/configuration/endpoint/tailscale/) to reuse.
|
|
||||||
|
|
||||||
[MagicDNS and HTTPS](https://tailscale.com/kb/1153/enabling-https) must be enabled in the Tailscale admin console.
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
---
|
|
||||||
icon: material/new-box
|
|
||||||
---
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.14.0 起"
|
|
||||||
|
|
||||||
# Tailscale
|
|
||||||
|
|
||||||
### 结构
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"type": "tailscale",
|
|
||||||
"tag": "ts-cert",
|
|
||||||
"endpoint": "ts-ep"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 字段
|
|
||||||
|
|
||||||
#### endpoint
|
|
||||||
|
|
||||||
==必填==
|
|
||||||
|
|
||||||
要复用的 [Tailscale 端点](/zh/configuration/endpoint/tailscale/) 的标签。
|
|
||||||
|
|
||||||
必须在 Tailscale 管理控制台中启用 [MagicDNS 和 HTTPS](https://tailscale.com/kb/1153/enabling-https)。
|
|
||||||
@@ -2,14 +2,6 @@
|
|||||||
icon: material/new-box
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.14.0"
|
|
||||||
|
|
||||||
:material-plus: [ttl](#ttl)
|
|
||||||
:material-plus: [propagation_delay](#propagation_delay)
|
|
||||||
:material-plus: [propagation_timeout](#propagation_timeout)
|
|
||||||
:material-plus: [resolvers](#resolvers)
|
|
||||||
:material-plus: [override_domain](#override_domain)
|
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.13.0"
|
!!! quote "Changes in sing-box 1.13.0"
|
||||||
|
|
||||||
:material-plus: [alidns.security_token](#security_token)
|
:material-plus: [alidns.security_token](#security_token)
|
||||||
@@ -20,57 +12,12 @@ icon: material/new-box
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"ttl": "",
|
|
||||||
"propagation_delay": "",
|
|
||||||
"propagation_timeout": "",
|
|
||||||
"resolvers": [],
|
|
||||||
"override_domain": "",
|
|
||||||
"provider": "",
|
"provider": "",
|
||||||
|
|
||||||
... // Provider Fields
|
... // Provider Fields
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Fields
|
|
||||||
|
|
||||||
#### ttl
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.14.0"
|
|
||||||
|
|
||||||
The TTL of the temporary TXT record used for the DNS challenge.
|
|
||||||
|
|
||||||
#### propagation_delay
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.14.0"
|
|
||||||
|
|
||||||
How long to wait after creating the challenge record before starting propagation checks.
|
|
||||||
|
|
||||||
#### propagation_timeout
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.14.0"
|
|
||||||
|
|
||||||
The maximum time to wait for the challenge record to propagate.
|
|
||||||
|
|
||||||
Set to `-1` to disable propagation checks.
|
|
||||||
|
|
||||||
#### resolvers
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.14.0"
|
|
||||||
|
|
||||||
Preferred DNS resolvers to use for DNS propagation checks.
|
|
||||||
|
|
||||||
#### override_domain
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.14.0"
|
|
||||||
|
|
||||||
Override the domain name used for the DNS challenge record.
|
|
||||||
|
|
||||||
Useful when `_acme-challenge` is delegated to a different zone.
|
|
||||||
|
|
||||||
#### provider
|
|
||||||
|
|
||||||
The DNS provider. See below for provider-specific fields.
|
|
||||||
|
|
||||||
### Provider Fields
|
### Provider Fields
|
||||||
|
|
||||||
#### Alibaba Cloud DNS
|
#### Alibaba Cloud DNS
|
||||||
|
|||||||
@@ -2,14 +2,6 @@
|
|||||||
icon: material/new-box
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! quote "sing-box 1.14.0 中的更改"
|
|
||||||
|
|
||||||
:material-plus: [ttl](#ttl)
|
|
||||||
:material-plus: [propagation_delay](#propagation_delay)
|
|
||||||
:material-plus: [propagation_timeout](#propagation_timeout)
|
|
||||||
:material-plus: [resolvers](#resolvers)
|
|
||||||
:material-plus: [override_domain](#override_domain)
|
|
||||||
|
|
||||||
!!! quote "sing-box 1.13.0 中的更改"
|
!!! quote "sing-box 1.13.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [alidns.security_token](#security_token)
|
:material-plus: [alidns.security_token](#security_token)
|
||||||
@@ -20,57 +12,12 @@ icon: material/new-box
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"ttl": "",
|
|
||||||
"propagation_delay": "",
|
|
||||||
"propagation_timeout": "",
|
|
||||||
"resolvers": [],
|
|
||||||
"override_domain": "",
|
|
||||||
"provider": "",
|
"provider": "",
|
||||||
|
|
||||||
... // 提供商字段
|
... // 提供商字段
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 字段
|
|
||||||
|
|
||||||
#### ttl
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.14.0 起"
|
|
||||||
|
|
||||||
DNS 质询临时 TXT 记录的 TTL。
|
|
||||||
|
|
||||||
#### propagation_delay
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.14.0 起"
|
|
||||||
|
|
||||||
创建质询记录后,在开始传播检查前要等待的时间。
|
|
||||||
|
|
||||||
#### propagation_timeout
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.14.0 起"
|
|
||||||
|
|
||||||
等待质询记录传播完成的最长时间。
|
|
||||||
|
|
||||||
设为 `-1` 可禁用传播检查。
|
|
||||||
|
|
||||||
#### resolvers
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.14.0 起"
|
|
||||||
|
|
||||||
进行 DNS 传播检查时优先使用的 DNS 解析器。
|
|
||||||
|
|
||||||
#### override_domain
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.14.0 起"
|
|
||||||
|
|
||||||
覆盖 DNS 质询记录使用的域名。
|
|
||||||
|
|
||||||
适用于将 `_acme-challenge` 委托到其他 zone 的场景。
|
|
||||||
|
|
||||||
#### provider
|
|
||||||
|
|
||||||
DNS 提供商。提供商专有字段见下文。
|
|
||||||
|
|
||||||
### 提供商字段
|
### 提供商字段
|
||||||
|
|
||||||
#### Alibaba Cloud DNS
|
#### Alibaba Cloud DNS
|
||||||
|
|||||||
@@ -1,49 +0,0 @@
|
|||||||
---
|
|
||||||
icon: material/lan
|
|
||||||
---
|
|
||||||
|
|
||||||
# Neighbor Resolution
|
|
||||||
|
|
||||||
Match LAN devices by MAC address and hostname using
|
|
||||||
[`source_mac_address`](/configuration/route/rule/#source_mac_address) and
|
|
||||||
[`source_hostname`](/configuration/route/rule/#source_hostname) rule items.
|
|
||||||
|
|
||||||
Neighbor resolution is automatically enabled when these rule items exist.
|
|
||||||
Use [`route.find_neighbor`](/configuration/route/#find_neighbor) to force enable it for logging without rules.
|
|
||||||
|
|
||||||
## Linux
|
|
||||||
|
|
||||||
Works natively. No special setup required.
|
|
||||||
|
|
||||||
Hostname resolution requires DHCP lease files,
|
|
||||||
automatically detected from common DHCP servers (dnsmasq, odhcpd, ISC dhcpd, Kea).
|
|
||||||
Custom paths can be set via [`route.dhcp_lease_files`](/configuration/route/#dhcp_lease_files).
|
|
||||||
|
|
||||||
## Android
|
|
||||||
|
|
||||||
!!! quote ""
|
|
||||||
|
|
||||||
Only supported in graphical clients.
|
|
||||||
|
|
||||||
Requires Android 11 or above and ROOT.
|
|
||||||
|
|
||||||
Must use [VPNHotspot](https://github.com/Mygod/VPNHotspot) to share the VPN connection.
|
|
||||||
ROM built-in features like "Use VPN for connected devices" can share VPN
|
|
||||||
but cannot provide MAC address or hostname information.
|
|
||||||
|
|
||||||
Set **IP Masquerade Mode** to **None** in VPNHotspot settings.
|
|
||||||
|
|
||||||
Only route/DNS rules are supported. TUN include/exclude routes are not supported.
|
|
||||||
|
|
||||||
### Hostname Visibility
|
|
||||||
|
|
||||||
Hostname is only visible in sing-box if it is visible in VPNHotspot.
|
|
||||||
For Apple devices, change **Private Wi-Fi Address** from **Rotating** to **Fixed** in the Wi-Fi settings
|
|
||||||
of the connected network. Non-Apple devices are always visible.
|
|
||||||
|
|
||||||
## macOS
|
|
||||||
|
|
||||||
Requires the standalone version (macOS system extension).
|
|
||||||
The App Store version can share the VPN as a hotspot but does not support MAC address or hostname reading.
|
|
||||||
|
|
||||||
See [VPN Hotspot](/manual/misc/vpn-hotspot/#macos) for Internet Sharing setup.
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
---
|
|
||||||
icon: material/lan
|
|
||||||
---
|
|
||||||
|
|
||||||
# 邻居解析
|
|
||||||
|
|
||||||
通过
|
|
||||||
[`source_mac_address`](/configuration/route/rule/#source_mac_address) 和
|
|
||||||
[`source_hostname`](/configuration/route/rule/#source_hostname) 规则项匹配局域网设备的 MAC 地址和主机名。
|
|
||||||
|
|
||||||
当这些规则项存在时,邻居解析自动启用。
|
|
||||||
使用 [`route.find_neighbor`](/configuration/route/#find_neighbor) 可在没有规则时强制启用以输出日志。
|
|
||||||
|
|
||||||
## Linux
|
|
||||||
|
|
||||||
原生支持,无需特殊设置。
|
|
||||||
|
|
||||||
主机名解析需要 DHCP 租约文件,
|
|
||||||
自动从常见 DHCP 服务器(dnsmasq、odhcpd、ISC dhcpd、Kea)检测。
|
|
||||||
可通过 [`route.dhcp_lease_files`](/configuration/route/#dhcp_lease_files) 设置自定义路径。
|
|
||||||
|
|
||||||
## Android
|
|
||||||
|
|
||||||
!!! quote ""
|
|
||||||
|
|
||||||
仅在图形客户端中支持。
|
|
||||||
|
|
||||||
需要 Android 11 或以上版本和 ROOT。
|
|
||||||
|
|
||||||
必须使用 [VPNHotspot](https://github.com/Mygod/VPNHotspot) 共享 VPN 连接。
|
|
||||||
ROM 自带的「通过 VPN 共享连接」等功能可以共享 VPN,
|
|
||||||
但无法提供 MAC 地址或主机名信息。
|
|
||||||
|
|
||||||
在 VPNHotspot 设置中将 **IP 遮掩模式** 设为 **无**。
|
|
||||||
|
|
||||||
仅支持路由/DNS 规则。不支持 TUN 的 include/exclude 路由。
|
|
||||||
|
|
||||||
### 设备可见性
|
|
||||||
|
|
||||||
MAC 地址和主机名仅在 VPNHotspot 中可见时 sing-box 才能读取。
|
|
||||||
对于 Apple 设备,需要在所连接网络的 Wi-Fi 设置中将**私有无线局域网地址**从**轮替**改为**固定**。
|
|
||||||
非 Apple 设备始终可见。
|
|
||||||
|
|
||||||
## macOS
|
|
||||||
|
|
||||||
需要独立版本(macOS 系统扩展)。
|
|
||||||
App Store 版本可以共享 VPN 热点但不支持 MAC 地址或主机名读取。
|
|
||||||
|
|
||||||
参阅 [VPN 热点](/manual/misc/vpn-hotspot/#macos) 了解互联网共享设置。
|
|
||||||
@@ -2,11 +2,6 @@
|
|||||||
icon: material/new-box
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.14.0"
|
|
||||||
|
|
||||||
:material-plus: [certificate_provider](#certificate_provider)
|
|
||||||
:material-delete-clock: [acme](#acme-fields)
|
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.13.0"
|
!!! quote "Changes in sing-box 1.13.0"
|
||||||
|
|
||||||
:material-plus: [kernel_tx](#kernel_tx)
|
:material-plus: [kernel_tx](#kernel_tx)
|
||||||
@@ -54,10 +49,6 @@ icon: material/new-box
|
|||||||
"key_path": "",
|
"key_path": "",
|
||||||
"kernel_tx": false,
|
"kernel_tx": false,
|
||||||
"kernel_rx": false,
|
"kernel_rx": false,
|
||||||
"certificate_provider": "",
|
|
||||||
|
|
||||||
// Deprecated
|
|
||||||
|
|
||||||
"acme": {
|
"acme": {
|
||||||
"domain": [],
|
"domain": [],
|
||||||
"data_directory": "",
|
"data_directory": "",
|
||||||
@@ -417,18 +408,6 @@ Enable kernel TLS transmit support.
|
|||||||
|
|
||||||
Enable kernel TLS receive support.
|
Enable kernel TLS receive support.
|
||||||
|
|
||||||
#### certificate_provider
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.14.0"
|
|
||||||
|
|
||||||
==Server only==
|
|
||||||
|
|
||||||
A string or an object.
|
|
||||||
|
|
||||||
When string, the tag of a shared [Certificate Provider](/configuration/shared/certificate-provider/).
|
|
||||||
|
|
||||||
When object, an inline certificate provider. See [Certificate Provider](/configuration/shared/certificate-provider/) for available types and fields.
|
|
||||||
|
|
||||||
## Custom TLS support
|
## Custom TLS support
|
||||||
|
|
||||||
!!! info "QUIC support"
|
!!! info "QUIC support"
|
||||||
@@ -490,7 +469,7 @@ The ECH key and configuration can be generated by `sing-box generate ech-keypair
|
|||||||
|
|
||||||
!!! failure "Deprecated in sing-box 1.12.0"
|
!!! failure "Deprecated in sing-box 1.12.0"
|
||||||
|
|
||||||
`pq_signature_schemes_enabled` is deprecated in sing-box 1.12.0 and removed in sing-box 1.13.0.
|
ECH support has been migrated to use stdlib in sing-box 1.12.0, which does not come with support for PQ signature schemes, so `pq_signature_schemes_enabled` has been deprecated and no longer works.
|
||||||
|
|
||||||
Enable support for post-quantum peer certificate signature schemes.
|
Enable support for post-quantum peer certificate signature schemes.
|
||||||
|
|
||||||
@@ -498,7 +477,7 @@ Enable support for post-quantum peer certificate signature schemes.
|
|||||||
|
|
||||||
!!! failure "Deprecated in sing-box 1.12.0"
|
!!! failure "Deprecated in sing-box 1.12.0"
|
||||||
|
|
||||||
`dynamic_record_sizing_disabled` is deprecated in sing-box 1.12.0 and removed in sing-box 1.13.0.
|
`dynamic_record_sizing_disabled` has nothing to do with ECH, was added by mistake, has been deprecated and no longer works.
|
||||||
|
|
||||||
Disables adaptive sizing of TLS records.
|
Disables adaptive sizing of TLS records.
|
||||||
|
|
||||||
@@ -587,10 +566,6 @@ Fragment TLS handshake into multiple TLS records to bypass firewalls.
|
|||||||
|
|
||||||
### ACME Fields
|
### ACME Fields
|
||||||
|
|
||||||
!!! failure "Deprecated in sing-box 1.14.0"
|
|
||||||
|
|
||||||
Inline ACME options are deprecated in sing-box 1.14.0 and will be removed in sing-box 1.16.0, check [Migration](/migration/#migrate-inline-acme-to-certificate-provider).
|
|
||||||
|
|
||||||
#### domain
|
#### domain
|
||||||
|
|
||||||
List of domain.
|
List of domain.
|
||||||
@@ -702,4 +677,4 @@ A hexadecimal string with zero to eight digits.
|
|||||||
|
|
||||||
The maximum time difference between the server and the client.
|
The maximum time difference between the server and the client.
|
||||||
|
|
||||||
Check disabled if empty.
|
Check disabled if empty.
|
||||||
@@ -2,11 +2,6 @@
|
|||||||
icon: material/new-box
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! quote "sing-box 1.14.0 中的更改"
|
|
||||||
|
|
||||||
:material-plus: [certificate_provider](#certificate_provider)
|
|
||||||
:material-delete-clock: [acme](#acme-字段)
|
|
||||||
|
|
||||||
!!! quote "sing-box 1.13.0 中的更改"
|
!!! quote "sing-box 1.13.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [kernel_tx](#kernel_tx)
|
:material-plus: [kernel_tx](#kernel_tx)
|
||||||
@@ -54,10 +49,6 @@ icon: material/new-box
|
|||||||
"key_path": "",
|
"key_path": "",
|
||||||
"kernel_tx": false,
|
"kernel_tx": false,
|
||||||
"kernel_rx": false,
|
"kernel_rx": false,
|
||||||
"certificate_provider": "",
|
|
||||||
|
|
||||||
// 废弃的
|
|
||||||
|
|
||||||
"acme": {
|
"acme": {
|
||||||
"domain": [],
|
"domain": [],
|
||||||
"data_directory": "",
|
"data_directory": "",
|
||||||
@@ -416,18 +407,6 @@ echo | openssl s_client -servername example.com -connect example.com:443 2>/dev/
|
|||||||
|
|
||||||
启用内核 TLS 接收支持。
|
启用内核 TLS 接收支持。
|
||||||
|
|
||||||
#### certificate_provider
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.14.0 起"
|
|
||||||
|
|
||||||
==仅服务器==
|
|
||||||
|
|
||||||
字符串或对象。
|
|
||||||
|
|
||||||
为字符串时,共享[证书提供者](/zh/configuration/shared/certificate-provider/)的标签。
|
|
||||||
|
|
||||||
为对象时,内联的证书提供者。可用类型和字段参阅[证书提供者](/zh/configuration/shared/certificate-provider/)。
|
|
||||||
|
|
||||||
## 自定义 TLS 支持
|
## 自定义 TLS 支持
|
||||||
|
|
||||||
!!! info "QUIC 支持"
|
!!! info "QUIC 支持"
|
||||||
@@ -486,7 +465,7 @@ ECH 密钥和配置可以通过 `sing-box generate ech-keypair` 生成。
|
|||||||
|
|
||||||
!!! failure "已在 sing-box 1.12.0 废弃"
|
!!! failure "已在 sing-box 1.12.0 废弃"
|
||||||
|
|
||||||
`pq_signature_schemes_enabled` 已在 sing-box 1.12.0 废弃且已在 sing-box 1.13.0 中被移除。
|
ECH 支持已在 sing-box 1.12.0 迁移至使用标准库,但标准库不支持后量子对等证书签名方案,因此 `pq_signature_schemes_enabled` 已被弃用且不再工作。
|
||||||
|
|
||||||
启用对后量子对等证书签名方案的支持。
|
启用对后量子对等证书签名方案的支持。
|
||||||
|
|
||||||
@@ -494,7 +473,7 @@ ECH 密钥和配置可以通过 `sing-box generate ech-keypair` 生成。
|
|||||||
|
|
||||||
!!! failure "已在 sing-box 1.12.0 废弃"
|
!!! failure "已在 sing-box 1.12.0 废弃"
|
||||||
|
|
||||||
`dynamic_record_sizing_disabled` 已在 sing-box 1.12.0 废弃且已在 sing-box 1.13.0 中被移除。
|
`dynamic_record_sizing_disabled` 与 ECH 无关,是错误添加的,现已弃用且不再工作。
|
||||||
|
|
||||||
禁用 TLS 记录的自适应大小调整。
|
禁用 TLS 记录的自适应大小调整。
|
||||||
|
|
||||||
@@ -582,10 +561,6 @@ ECH 配置路径,PEM 格式。
|
|||||||
|
|
||||||
### ACME 字段
|
### ACME 字段
|
||||||
|
|
||||||
!!! failure "已在 sing-box 1.14.0 废弃"
|
|
||||||
|
|
||||||
内联 ACME 选项已在 sing-box 1.14.0 废弃且将在 sing-box 1.16.0 中被移除,参阅 [迁移指南](/zh/migration/#迁移内联-acme-到证书提供者)。
|
|
||||||
|
|
||||||
#### domain
|
#### domain
|
||||||
|
|
||||||
域名列表。
|
域名列表。
|
||||||
|
|||||||
@@ -4,16 +4,6 @@ icon: material/delete-alert
|
|||||||
|
|
||||||
# Deprecated Feature List
|
# Deprecated Feature List
|
||||||
|
|
||||||
## 1.14.0
|
|
||||||
|
|
||||||
#### Inline ACME options in TLS
|
|
||||||
|
|
||||||
Inline ACME options (`tls.acme`) are deprecated
|
|
||||||
and can be replaced by the ACME certificate provider,
|
|
||||||
check [Migration](../migration/#migrate-inline-acme-to-certificate-provider).
|
|
||||||
|
|
||||||
Old fields will be removed in sing-box 1.16.0.
|
|
||||||
|
|
||||||
## 1.12.0
|
## 1.12.0
|
||||||
|
|
||||||
#### Legacy DNS server formats
|
#### Legacy DNS server formats
|
||||||
@@ -38,7 +28,7 @@ so `pq_signature_schemes_enabled` has been deprecated and no longer works.
|
|||||||
Also, `dynamic_record_sizing_disabled` has nothing to do with ECH,
|
Also, `dynamic_record_sizing_disabled` has nothing to do with ECH,
|
||||||
was added by mistake, has been deprecated and no longer works.
|
was added by mistake, has been deprecated and no longer works.
|
||||||
|
|
||||||
These fields were removed in sing-box 1.13.0.
|
These fields will be removed in sing-box 1.13.0.
|
||||||
|
|
||||||
## 1.11.0
|
## 1.11.0
|
||||||
|
|
||||||
@@ -48,7 +38,7 @@ Legacy special outbounds (`block` / `dns`) are deprecated
|
|||||||
and can be replaced by rule actions,
|
and can be replaced by rule actions,
|
||||||
check [Migration](../migration/#migrate-legacy-special-outbounds-to-rule-actions).
|
check [Migration](../migration/#migrate-legacy-special-outbounds-to-rule-actions).
|
||||||
|
|
||||||
Old fields were removed in sing-box 1.13.0.
|
Old fields will be removed in sing-box 1.13.0.
|
||||||
|
|
||||||
#### Legacy inbound fields
|
#### Legacy inbound fields
|
||||||
|
|
||||||
@@ -56,7 +46,7 @@ Legacy inbound fields (`inbound.<sniff/domain_strategy/...>` are deprecated
|
|||||||
and can be replaced by rule actions,
|
and can be replaced by rule actions,
|
||||||
check [Migration](../migration/#migrate-legacy-inbound-fields-to-rule-actions).
|
check [Migration](../migration/#migrate-legacy-inbound-fields-to-rule-actions).
|
||||||
|
|
||||||
Old fields were removed in sing-box 1.13.0.
|
Old fields will be removed in sing-box 1.13.0.
|
||||||
|
|
||||||
#### Destination override fields in direct outbound
|
#### Destination override fields in direct outbound
|
||||||
|
|
||||||
@@ -64,20 +54,18 @@ Destination override fields (`override_address` / `override_port`) in direct out
|
|||||||
and can be replaced by rule actions,
|
and can be replaced by rule actions,
|
||||||
check [Migration](../migration/#migrate-destination-override-fields-to-route-options).
|
check [Migration](../migration/#migrate-destination-override-fields-to-route-options).
|
||||||
|
|
||||||
Old fields were removed in sing-box 1.13.0.
|
|
||||||
|
|
||||||
#### WireGuard outbound
|
#### WireGuard outbound
|
||||||
|
|
||||||
WireGuard outbound is deprecated and can be replaced by endpoint,
|
WireGuard outbound is deprecated and can be replaced by endpoint,
|
||||||
check [Migration](../migration/#migrate-wireguard-outbound-to-endpoint).
|
check [Migration](../migration/#migrate-wireguard-outbound-to-endpoint).
|
||||||
|
|
||||||
Old outbound was removed in sing-box 1.13.0.
|
Old outbound will be removed in sing-box 1.13.0.
|
||||||
|
|
||||||
#### GSO option in TUN
|
#### GSO option in TUN
|
||||||
|
|
||||||
GSO has no advantages for transparent proxy scenarios, is deprecated and no longer works in TUN.
|
GSO has no advantages for transparent proxy scenarios, is deprecated and no longer works in TUN.
|
||||||
|
|
||||||
Old fields were removed in sing-box 1.13.0.
|
Old fields will be removed in sing-box 1.13.0.
|
||||||
|
|
||||||
## 1.10.0
|
## 1.10.0
|
||||||
|
|
||||||
@@ -87,12 +75,12 @@ Old fields were removed in sing-box 1.13.0.
|
|||||||
`inet4_route_address` and `inet6_route_address` are merged into `route_address`,
|
`inet4_route_address` and `inet6_route_address` are merged into `route_address`,
|
||||||
`inet4_route_exclude_address` and `inet6_route_exclude_address` are merged into `route_exclude_address`.
|
`inet4_route_exclude_address` and `inet6_route_exclude_address` are merged into `route_exclude_address`.
|
||||||
|
|
||||||
Old fields were removed in sing-box 1.12.0.
|
Old fields will be removed in sing-box 1.12.0.
|
||||||
|
|
||||||
#### Match source rule items are renamed
|
#### Match source rule items are renamed
|
||||||
|
|
||||||
`rule_set_ipcidr_match_source` route and DNS rule items are renamed to
|
`rule_set_ipcidr_match_source` route and DNS rule items are renamed to
|
||||||
`rule_set_ip_cidr_match_source` and were removed in sing-box 1.11.0.
|
`rule_set_ip_cidr_match_source` and will be remove in sing-box 1.11.0.
|
||||||
|
|
||||||
#### Drop support for go1.18 and go1.19
|
#### Drop support for go1.18 and go1.19
|
||||||
|
|
||||||
@@ -107,7 +95,7 @@ check [Migration](/migration/#migrate-cache-file-from-clash-api-to-independent-o
|
|||||||
|
|
||||||
#### GeoIP
|
#### GeoIP
|
||||||
|
|
||||||
GeoIP is deprecated and was removed in sing-box 1.12.0.
|
GeoIP is deprecated and will be removed in sing-box 1.12.0.
|
||||||
|
|
||||||
The maxmind GeoIP National Database, as an IP classification database,
|
The maxmind GeoIP National Database, as an IP classification database,
|
||||||
is not entirely suitable for traffic bypassing,
|
is not entirely suitable for traffic bypassing,
|
||||||
@@ -118,7 +106,7 @@ check [Migration](/migration/#migrate-geoip-to-rule-sets).
|
|||||||
|
|
||||||
#### Geosite
|
#### Geosite
|
||||||
|
|
||||||
Geosite is deprecated and was removed in sing-box 1.12.0.
|
Geosite is deprecated and will be removed in sing-box 1.12.0.
|
||||||
|
|
||||||
Geosite, the `domain-list-community` project maintained by V2Ray as an early traffic bypassing solution,
|
Geosite, the `domain-list-community` project maintained by V2Ray as an early traffic bypassing solution,
|
||||||
suffers from a number of problems, including lack of maintenance, inaccurate rules, and difficult management.
|
suffers from a number of problems, including lack of maintenance, inaccurate rules, and difficult management.
|
||||||
|
|||||||
@@ -4,18 +4,6 @@ icon: material/delete-alert
|
|||||||
|
|
||||||
# 废弃功能列表
|
# 废弃功能列表
|
||||||
|
|
||||||
## 1.14.0
|
|
||||||
|
|
||||||
#### TLS 中的内联 ACME 选项
|
|
||||||
|
|
||||||
TLS 中的内联 ACME 选项(`tls.acme`)已废弃,
|
|
||||||
且可以通过 ACME 证书提供者替代,
|
|
||||||
参阅 [迁移指南](/zh/migration/#迁移内联-acme-到证书提供者)。
|
|
||||||
|
|
||||||
旧字段将在 sing-box 1.16.0 中被移除。
|
|
||||||
|
|
||||||
## 1.12.0
|
|
||||||
|
|
||||||
#### 旧的 DNS 服务器格式
|
#### 旧的 DNS 服务器格式
|
||||||
|
|
||||||
DNS 服务器已重构,
|
DNS 服务器已重构,
|
||||||
@@ -36,7 +24,7 @@ ECH 支持已在 sing-box 1.12.0 迁移至使用标准库,但标准库不支
|
|||||||
|
|
||||||
另外,`dynamic_record_sizing_disabled` 与 ECH 无关,是错误添加的,现已弃用且不再工作。
|
另外,`dynamic_record_sizing_disabled` 与 ECH 无关,是错误添加的,现已弃用且不再工作。
|
||||||
|
|
||||||
相关字段已在 sing-box 1.13.0 中被移除。
|
相关字段将在 sing-box 1.13.0 中被移除。
|
||||||
|
|
||||||
## 1.11.0
|
## 1.11.0
|
||||||
|
|
||||||
@@ -45,41 +33,41 @@ ECH 支持已在 sing-box 1.12.0 迁移至使用标准库,但标准库不支
|
|||||||
旧的特殊出站(`block` / `dns`)已废弃且可以通过规则动作替代,
|
旧的特殊出站(`block` / `dns`)已废弃且可以通过规则动作替代,
|
||||||
参阅 [迁移指南](/zh/migration/#迁移旧的特殊出站到规则动作)。
|
参阅 [迁移指南](/zh/migration/#迁移旧的特殊出站到规则动作)。
|
||||||
|
|
||||||
旧字段已在 sing-box 1.13.0 中被移除。
|
旧字段将在 sing-box 1.13.0 中被移除。
|
||||||
|
|
||||||
#### 旧的入站字段
|
#### 旧的入站字段
|
||||||
|
|
||||||
旧的入站字段(`inbound.<sniff/domain_strategy/...>`)已废弃且可以通过规则动作替代,
|
旧的入站字段(`inbound.<sniff/domain_strategy/...>`)已废弃且可以通过规则动作替代,
|
||||||
参阅 [迁移指南](/zh/migration/#迁移旧的入站字段到规则动作)。
|
参阅 [迁移指南](/zh/migration/#迁移旧的入站字段到规则动作)。
|
||||||
|
|
||||||
旧字段已在 sing-box 1.13.0 中被移除。
|
旧字段将在 sing-box 1.13.0 中被移除。
|
||||||
|
|
||||||
#### direct 出站中的目标地址覆盖字段
|
#### direct 出站中的目标地址覆盖字段
|
||||||
|
|
||||||
direct 出站中的目标地址覆盖字段(`override_address` / `override_port`)已废弃且可以通过规则动作替代,
|
direct 出站中的目标地址覆盖字段(`override_address` / `override_port`)已废弃且可以通过规则动作替代,
|
||||||
参阅 [迁移指南](/zh/migration/#迁移-direct-出站中的目标地址覆盖字段到路由字段)。
|
参阅 [迁移指南](/zh/migration/#迁移-direct-出站中的目标地址覆盖字段到路由字段)。
|
||||||
|
|
||||||
旧字段已在 sing-box 1.13.0 中被移除。
|
旧字段将在 sing-box 1.13.0 中被移除。
|
||||||
|
|
||||||
#### WireGuard 出站
|
#### WireGuard 出站
|
||||||
|
|
||||||
WireGuard 出站已废弃且可以通过端点替代,
|
WireGuard 出站已废弃且可以通过端点替代,
|
||||||
参阅 [迁移指南](/zh/migration/#迁移-wireguard-出站到端点)。
|
参阅 [迁移指南](/zh/migration/#迁移-wireguard-出站到端点)。
|
||||||
|
|
||||||
旧出站已在 sing-box 1.13.0 中被移除。
|
旧出站将在 sing-box 1.13.0 中被移除。
|
||||||
|
|
||||||
#### TUN 的 GSO 字段
|
#### TUN 的 GSO 字段
|
||||||
|
|
||||||
GSO 对透明代理场景没有优势,已废弃且在 TUN 中不再起作用。
|
GSO 对透明代理场景没有优势,已废弃且在 TUN 中不再起作用。
|
||||||
|
|
||||||
旧字段已在 sing-box 1.13.0 中被移除。
|
旧字段将在 sing-box 1.13.0 中被移除。
|
||||||
|
|
||||||
## 1.10.0
|
## 1.10.0
|
||||||
|
|
||||||
#### Match source 规则项已重命名
|
#### Match source 规则项已重命名
|
||||||
|
|
||||||
`rule_set_ipcidr_match_source` 路由和 DNS 规则项已被重命名为
|
`rule_set_ipcidr_match_source` 路由和 DNS 规则项已被重命名为
|
||||||
`rule_set_ip_cidr_match_source` 且已在 sing-box 1.11.0 中被移除。
|
`rule_set_ip_cidr_match_source` 且将在 sing-box 1.11.0 中被移除。
|
||||||
|
|
||||||
#### TUN 地址字段已合并
|
#### TUN 地址字段已合并
|
||||||
|
|
||||||
@@ -87,7 +75,7 @@ GSO 对透明代理场景没有优势,已废弃且在 TUN 中不再起作用
|
|||||||
`inet4_route_address` 和 `inet6_route_address` 已合并为 `route_address`,
|
`inet4_route_address` 和 `inet6_route_address` 已合并为 `route_address`,
|
||||||
`inet4_route_exclude_address` 和 `inet6_route_exclude_address` 已合并为 `route_exclude_address`。
|
`inet4_route_exclude_address` 和 `inet6_route_exclude_address` 已合并为 `route_exclude_address`。
|
||||||
|
|
||||||
旧字段已在 sing-box 1.12.0 中被移除。
|
旧字段将在 sing-box 1.11.0 中被移除。
|
||||||
|
|
||||||
#### 移除对 go1.18 和 go1.19 的支持
|
#### 移除对 go1.18 和 go1.19 的支持
|
||||||
|
|
||||||
@@ -102,7 +90,7 @@ Clash API 中的 `cache_file` 及相关功能已废弃且已迁移到独立的 `
|
|||||||
|
|
||||||
#### GeoIP
|
#### GeoIP
|
||||||
|
|
||||||
GeoIP 已废弃且已在 sing-box 1.12.0 中被移除。
|
GeoIP 已废弃且将在 sing-box 1.12.0 中被移除。
|
||||||
|
|
||||||
maxmind GeoIP 国家数据库作为 IP 分类数据库,不完全适合流量绕过,
|
maxmind GeoIP 国家数据库作为 IP 分类数据库,不完全适合流量绕过,
|
||||||
且现有的实现均存在内存使用大与管理困难的问题。
|
且现有的实现均存在内存使用大与管理困难的问题。
|
||||||
@@ -112,7 +100,7 @@ sing-box 1.8.0 引入了[规则集](/zh/configuration/rule-set/),
|
|||||||
|
|
||||||
#### Geosite
|
#### Geosite
|
||||||
|
|
||||||
Geosite 已废弃且已在 sing-box 1.12.0 中被移除。
|
Geosite 已废弃且将在 sing-box 1.12.0 中被移除。
|
||||||
|
|
||||||
Geosite,即由 V2Ray 维护的 domain-list-community 项目,作为早期流量绕过解决方案,
|
Geosite,即由 V2Ray 维护的 domain-list-community 项目,作为早期流量绕过解决方案,
|
||||||
存在着包括缺少维护、规则不准确和管理困难内的大量问题。
|
存在着包括缺少维护、规则不准确和管理困难内的大量问题。
|
||||||
|
|||||||
@@ -2,83 +2,6 @@
|
|||||||
icon: material/arrange-bring-forward
|
icon: material/arrange-bring-forward
|
||||||
---
|
---
|
||||||
|
|
||||||
## 1.14.0
|
|
||||||
|
|
||||||
### Migrate inline ACME to certificate provider
|
|
||||||
|
|
||||||
Inline ACME options in TLS are deprecated and can be replaced by certificate providers.
|
|
||||||
|
|
||||||
Most `tls.acme` fields can be moved into the ACME certificate provider unchanged.
|
|
||||||
See [ACME](/configuration/shared/certificate-provider/acme/) for fields newly added in sing-box 1.14.0.
|
|
||||||
|
|
||||||
!!! info "References"
|
|
||||||
|
|
||||||
[TLS](/configuration/shared/tls/#certificate_provider) /
|
|
||||||
[Certificate Provider](/configuration/shared/certificate-provider/)
|
|
||||||
|
|
||||||
=== ":material-card-remove: Deprecated"
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"inbounds": [
|
|
||||||
{
|
|
||||||
"type": "trojan",
|
|
||||||
"tls": {
|
|
||||||
"enabled": true,
|
|
||||||
"acme": {
|
|
||||||
"domain": ["example.com"],
|
|
||||||
"email": "admin@example.com"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
=== ":material-card-multiple: Inline"
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"inbounds": [
|
|
||||||
{
|
|
||||||
"type": "trojan",
|
|
||||||
"tls": {
|
|
||||||
"enabled": true,
|
|
||||||
"certificate_provider": {
|
|
||||||
"type": "acme",
|
|
||||||
"domain": ["example.com"],
|
|
||||||
"email": "admin@example.com"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
=== ":material-card-multiple: Shared"
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"certificate_providers": [
|
|
||||||
{
|
|
||||||
"type": "acme",
|
|
||||||
"tag": "my-cert",
|
|
||||||
"domain": ["example.com"],
|
|
||||||
"email": "admin@example.com"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"inbounds": [
|
|
||||||
{
|
|
||||||
"type": "trojan",
|
|
||||||
"tls": {
|
|
||||||
"enabled": true,
|
|
||||||
"certificate_provider": "my-cert"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 1.12.0
|
## 1.12.0
|
||||||
|
|
||||||
### Migrate to new DNS server formats
|
### Migrate to new DNS server formats
|
||||||
|
|||||||
@@ -2,83 +2,6 @@
|
|||||||
icon: material/arrange-bring-forward
|
icon: material/arrange-bring-forward
|
||||||
---
|
---
|
||||||
|
|
||||||
## 1.14.0
|
|
||||||
|
|
||||||
### 迁移内联 ACME 到证书提供者
|
|
||||||
|
|
||||||
TLS 中的内联 ACME 选项已废弃,且可以被证书提供者替代。
|
|
||||||
|
|
||||||
`tls.acme` 的大多数字段都可以原样迁移到 ACME 证书提供者中。
|
|
||||||
sing-box 1.14.0 新增字段参阅 [ACME](/zh/configuration/shared/certificate-provider/acme/) 页面。
|
|
||||||
|
|
||||||
!!! info "参考"
|
|
||||||
|
|
||||||
[TLS](/zh/configuration/shared/tls/#certificate_provider) /
|
|
||||||
[证书提供者](/zh/configuration/shared/certificate-provider/)
|
|
||||||
|
|
||||||
=== ":material-card-remove: 弃用的"
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"inbounds": [
|
|
||||||
{
|
|
||||||
"type": "trojan",
|
|
||||||
"tls": {
|
|
||||||
"enabled": true,
|
|
||||||
"acme": {
|
|
||||||
"domain": ["example.com"],
|
|
||||||
"email": "admin@example.com"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
=== ":material-card-multiple: 内联"
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"inbounds": [
|
|
||||||
{
|
|
||||||
"type": "trojan",
|
|
||||||
"tls": {
|
|
||||||
"enabled": true,
|
|
||||||
"certificate_provider": {
|
|
||||||
"type": "acme",
|
|
||||||
"domain": ["example.com"],
|
|
||||||
"email": "admin@example.com"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
=== ":material-card-multiple: 共享"
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"certificate_providers": [
|
|
||||||
{
|
|
||||||
"type": "acme",
|
|
||||||
"tag": "my-cert",
|
|
||||||
"domain": ["example.com"],
|
|
||||||
"email": "admin@example.com"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"inbounds": [
|
|
||||||
{
|
|
||||||
"type": "trojan",
|
|
||||||
"tls": {
|
|
||||||
"enabled": true,
|
|
||||||
"certificate_provider": "my-cert"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 1.12.0
|
## 1.12.0
|
||||||
|
|
||||||
### 迁移到新的 DNS 服务器格式
|
### 迁移到新的 DNS 服务器格式
|
||||||
|
|||||||
@@ -102,20 +102,10 @@ var OptionLegacyDomainStrategyOptions = Note{
|
|||||||
MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-domain-strategy-options",
|
MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-domain-strategy-options",
|
||||||
}
|
}
|
||||||
|
|
||||||
var OptionInlineACME = Note{
|
|
||||||
Name: "inline-acme-options",
|
|
||||||
Description: "inline ACME options in TLS",
|
|
||||||
DeprecatedVersion: "1.14.0",
|
|
||||||
ScheduledVersion: "1.16.0",
|
|
||||||
EnvName: "INLINE_ACME_OPTIONS",
|
|
||||||
MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-inline-acme-to-certificate-provider",
|
|
||||||
}
|
|
||||||
|
|
||||||
var Options = []Note{
|
var Options = []Note{
|
||||||
OptionLegacyDNSTransport,
|
OptionLegacyDNSTransport,
|
||||||
OptionLegacyDNSFakeIPOptions,
|
OptionLegacyDNSFakeIPOptions,
|
||||||
OptionOutboundDNSRuleItem,
|
OptionOutboundDNSRuleItem,
|
||||||
OptionMissingDomainResolver,
|
OptionMissingDomainResolver,
|
||||||
OptionLegacyDomainStrategyOptions,
|
OptionLegacyDomainStrategyOptions,
|
||||||
OptionInlineACME,
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ func baseContext(platformInterface PlatformInterface) context.Context {
|
|||||||
}
|
}
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
ctx = filemanager.WithDefault(ctx, sWorkingPath, sTempPath, sUserID, sGroupID)
|
ctx = filemanager.WithDefault(ctx, sWorkingPath, sTempPath, sUserID, sGroupID)
|
||||||
return box.Context(ctx, include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry(), dnsRegistry, include.ServiceRegistry(), include.CertificateProviderRegistry())
|
return box.Context(ctx, include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry(), dnsRegistry, include.ServiceRegistry())
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseConfig(ctx context.Context, configContent string) (option.Options, error) {
|
func parseConfig(ctx context.Context, configContent string) (option.Options, error) {
|
||||||
@@ -144,18 +144,6 @@ func (s *platformInterfaceStub) SendNotification(notification *adapter.Notificat
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *platformInterfaceStub) UsePlatformNeighborResolver() bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *platformInterfaceStub) StartNeighborMonitor(listener adapter.NeighborUpdateListener) error {
|
|
||||||
return os.ErrInvalid
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *platformInterfaceStub) CloseNeighborMonitor(listener adapter.NeighborUpdateListener) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *platformInterfaceStub) UsePlatformLocalDNSTransport() bool {
|
func (s *platformInterfaceStub) UsePlatformLocalDNSTransport() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,6 +52,11 @@ type HTTPRequest interface {
|
|||||||
type HTTPResponse interface {
|
type HTTPResponse interface {
|
||||||
GetContent() (*StringBox, error)
|
GetContent() (*StringBox, error)
|
||||||
WriteTo(path string) error
|
WriteTo(path string) error
|
||||||
|
WriteToWithProgress(path string, handler HTTPResponseWriteToProgressHandler) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type HTTPResponseWriteToProgressHandler interface {
|
||||||
|
Update(progress int64, total int64)
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -239,3 +244,31 @@ func (h *httpResponse) WriteTo(path string) error {
|
|||||||
defer file.Close()
|
defer file.Close()
|
||||||
return common.Error(bufio.Copy(file, h.Body))
|
return common.Error(bufio.Copy(file, h.Body))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *httpResponse) WriteToWithProgress(path string, handler HTTPResponseWriteToProgressHandler) error {
|
||||||
|
defer h.Body.Close()
|
||||||
|
file, err := os.Create(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
return common.Error(bufio.Copy(&progressWriter{
|
||||||
|
writer: file,
|
||||||
|
handler: handler,
|
||||||
|
total: h.ContentLength,
|
||||||
|
}, h.Body))
|
||||||
|
}
|
||||||
|
|
||||||
|
type progressWriter struct {
|
||||||
|
writer io.Writer
|
||||||
|
handler HTTPResponseWriteToProgressHandler
|
||||||
|
total int64
|
||||||
|
written int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *progressWriter) Write(p []byte) (int, error) {
|
||||||
|
n, err := w.writer.Write(p)
|
||||||
|
w.written += int64(n)
|
||||||
|
w.handler.Update(w.written, w.total)
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
package libbox
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"net/netip"
|
|
||||||
)
|
|
||||||
|
|
||||||
type NeighborEntry struct {
|
|
||||||
Address string
|
|
||||||
MacAddress string
|
|
||||||
Hostname string
|
|
||||||
}
|
|
||||||
|
|
||||||
type NeighborEntryIterator interface {
|
|
||||||
Next() *NeighborEntry
|
|
||||||
HasNext() bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type NeighborSubscription struct {
|
|
||||||
done chan struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *NeighborSubscription) Close() {
|
|
||||||
close(s.done)
|
|
||||||
}
|
|
||||||
|
|
||||||
func tableToIterator(table map[netip.Addr]net.HardwareAddr) NeighborEntryIterator {
|
|
||||||
entries := make([]*NeighborEntry, 0, len(table))
|
|
||||||
for address, mac := range table {
|
|
||||||
entries = append(entries, &NeighborEntry{
|
|
||||||
Address: address.String(),
|
|
||||||
MacAddress: mac.String(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return &neighborEntryIterator{entries}
|
|
||||||
}
|
|
||||||
|
|
||||||
type neighborEntryIterator struct {
|
|
||||||
entries []*NeighborEntry
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *neighborEntryIterator) HasNext() bool {
|
|
||||||
return len(i.entries) > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *neighborEntryIterator) Next() *NeighborEntry {
|
|
||||||
if len(i.entries) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
entry := i.entries[0]
|
|
||||||
i.entries = i.entries[1:]
|
|
||||||
return entry
|
|
||||||
}
|
|
||||||
@@ -1,123 +0,0 @@
|
|||||||
//go:build darwin
|
|
||||||
|
|
||||||
package libbox
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"net/netip"
|
|
||||||
"os"
|
|
||||||
"slices"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/route"
|
|
||||||
"github.com/sagernet/sing/common/buf"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
|
|
||||||
xroute "golang.org/x/net/route"
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
)
|
|
||||||
|
|
||||||
func SubscribeNeighborTable(listener NeighborUpdateListener) (*NeighborSubscription, error) {
|
|
||||||
entries, err := route.ReadNeighborEntries()
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "initial neighbor dump")
|
|
||||||
}
|
|
||||||
table := make(map[netip.Addr]net.HardwareAddr)
|
|
||||||
for _, entry := range entries {
|
|
||||||
table[entry.Address] = entry.MACAddress
|
|
||||||
}
|
|
||||||
listener.UpdateNeighborTable(tableToIterator(table))
|
|
||||||
routeSocket, err := unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW, 0)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "open route socket")
|
|
||||||
}
|
|
||||||
err = unix.SetNonblock(routeSocket, true)
|
|
||||||
if err != nil {
|
|
||||||
unix.Close(routeSocket)
|
|
||||||
return nil, E.Cause(err, "set route socket nonblock")
|
|
||||||
}
|
|
||||||
subscription := &NeighborSubscription{
|
|
||||||
done: make(chan struct{}),
|
|
||||||
}
|
|
||||||
go subscription.loop(listener, routeSocket, table)
|
|
||||||
return subscription, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *NeighborSubscription) loop(listener NeighborUpdateListener, routeSocket int, table map[netip.Addr]net.HardwareAddr) {
|
|
||||||
routeSocketFile := os.NewFile(uintptr(routeSocket), "route")
|
|
||||||
defer routeSocketFile.Close()
|
|
||||||
buffer := buf.NewPacket()
|
|
||||||
defer buffer.Release()
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-s.done:
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
tv := unix.NsecToTimeval(int64(3 * time.Second))
|
|
||||||
_ = unix.SetsockoptTimeval(routeSocket, unix.SOL_SOCKET, unix.SO_RCVTIMEO, &tv)
|
|
||||||
n, err := routeSocketFile.Read(buffer.FreeBytes())
|
|
||||||
if err != nil {
|
|
||||||
if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case <-s.done:
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
messages, err := xroute.ParseRIB(xroute.RIBTypeRoute, buffer.FreeBytes()[:n])
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
changed := false
|
|
||||||
for _, message := range messages {
|
|
||||||
routeMessage, isRouteMessage := message.(*xroute.RouteMessage)
|
|
||||||
if !isRouteMessage {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if routeMessage.Flags&unix.RTF_LLINFO == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
address, mac, isDelete, ok := route.ParseRouteNeighborMessage(routeMessage)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if isDelete {
|
|
||||||
if _, exists := table[address]; exists {
|
|
||||||
delete(table, address)
|
|
||||||
changed = true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
existing, exists := table[address]
|
|
||||||
if !exists || !slices.Equal(existing, mac) {
|
|
||||||
table[address] = mac
|
|
||||||
changed = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if changed {
|
|
||||||
listener.UpdateNeighborTable(tableToIterator(table))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ReadBootpdLeases() NeighborEntryIterator {
|
|
||||||
leaseIPToMAC, ipToHostname, macToHostname := route.ReloadLeaseFiles([]string{"/var/db/dhcpd_leases"})
|
|
||||||
entries := make([]*NeighborEntry, 0, len(leaseIPToMAC))
|
|
||||||
for address, mac := range leaseIPToMAC {
|
|
||||||
entry := &NeighborEntry{
|
|
||||||
Address: address.String(),
|
|
||||||
MacAddress: mac.String(),
|
|
||||||
}
|
|
||||||
hostname, found := ipToHostname[address]
|
|
||||||
if !found {
|
|
||||||
hostname = macToHostname[mac.String()]
|
|
||||||
}
|
|
||||||
entry.Hostname = hostname
|
|
||||||
entries = append(entries, entry)
|
|
||||||
}
|
|
||||||
return &neighborEntryIterator{entries}
|
|
||||||
}
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
//go:build linux
|
|
||||||
|
|
||||||
package libbox
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"net/netip"
|
|
||||||
"slices"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/route"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
|
|
||||||
"github.com/mdlayher/netlink"
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
)
|
|
||||||
|
|
||||||
func SubscribeNeighborTable(listener NeighborUpdateListener) (*NeighborSubscription, error) {
|
|
||||||
entries, err := route.ReadNeighborEntries()
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "initial neighbor dump")
|
|
||||||
}
|
|
||||||
table := make(map[netip.Addr]net.HardwareAddr)
|
|
||||||
for _, entry := range entries {
|
|
||||||
table[entry.Address] = entry.MACAddress
|
|
||||||
}
|
|
||||||
listener.UpdateNeighborTable(tableToIterator(table))
|
|
||||||
connection, err := netlink.Dial(unix.NETLINK_ROUTE, &netlink.Config{
|
|
||||||
Groups: 1 << (unix.RTNLGRP_NEIGH - 1),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "subscribe neighbor updates")
|
|
||||||
}
|
|
||||||
subscription := &NeighborSubscription{
|
|
||||||
done: make(chan struct{}),
|
|
||||||
}
|
|
||||||
go subscription.loop(listener, connection, table)
|
|
||||||
return subscription, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *NeighborSubscription) loop(listener NeighborUpdateListener, connection *netlink.Conn, table map[netip.Addr]net.HardwareAddr) {
|
|
||||||
defer connection.Close()
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-s.done:
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
err := connection.SetReadDeadline(time.Now().Add(3 * time.Second))
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
messages, err := connection.Receive()
|
|
||||||
if err != nil {
|
|
||||||
if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case <-s.done:
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
changed := false
|
|
||||||
for _, message := range messages {
|
|
||||||
address, mac, isDelete, ok := route.ParseNeighborMessage(message)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if isDelete {
|
|
||||||
if _, exists := table[address]; exists {
|
|
||||||
delete(table, address)
|
|
||||||
changed = true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
existing, exists := table[address]
|
|
||||||
if !exists || !slices.Equal(existing, mac) {
|
|
||||||
table[address] = mac
|
|
||||||
changed = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if changed {
|
|
||||||
listener.UpdateNeighborTable(tableToIterator(table))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
//go:build !linux && !darwin
|
|
||||||
|
|
||||||
package libbox
|
|
||||||
|
|
||||||
import "os"
|
|
||||||
|
|
||||||
func SubscribeNeighborTable(_ NeighborUpdateListener) (*NeighborSubscription, error) {
|
|
||||||
return nil, os.ErrInvalid
|
|
||||||
}
|
|
||||||
@@ -21,13 +21,6 @@ type PlatformInterface interface {
|
|||||||
SystemCertificates() StringIterator
|
SystemCertificates() StringIterator
|
||||||
ClearDNSCache()
|
ClearDNSCache()
|
||||||
SendNotification(notification *Notification) error
|
SendNotification(notification *Notification) error
|
||||||
StartNeighborMonitor(listener NeighborUpdateListener) error
|
|
||||||
CloseNeighborMonitor(listener NeighborUpdateListener) error
|
|
||||||
RegisterMyInterface(name string)
|
|
||||||
}
|
|
||||||
|
|
||||||
type NeighborUpdateListener interface {
|
|
||||||
UpdateNeighborTable(entries NeighborEntryIterator)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ConnectionOwner struct {
|
type ConnectionOwner struct {
|
||||||
|
|||||||
@@ -78,7 +78,6 @@ func (w *platformInterfaceWrapper) OpenInterface(options *tun.Options, platformO
|
|||||||
}
|
}
|
||||||
options.FileDescriptor = dupFd
|
options.FileDescriptor = dupFd
|
||||||
w.myTunName = options.Name
|
w.myTunName = options.Name
|
||||||
w.iif.RegisterMyInterface(options.Name)
|
|
||||||
return tun.New(*options)
|
return tun.New(*options)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,46 +220,6 @@ func (w *platformInterfaceWrapper) SendNotification(notification *adapter.Notifi
|
|||||||
return w.iif.SendNotification((*Notification)(notification))
|
return w.iif.SendNotification((*Notification)(notification))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *platformInterfaceWrapper) UsePlatformNeighborResolver() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *platformInterfaceWrapper) StartNeighborMonitor(listener adapter.NeighborUpdateListener) error {
|
|
||||||
return w.iif.StartNeighborMonitor(&neighborUpdateListenerWrapper{listener: listener})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *platformInterfaceWrapper) CloseNeighborMonitor(listener adapter.NeighborUpdateListener) error {
|
|
||||||
return w.iif.CloseNeighborMonitor(nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
type neighborUpdateListenerWrapper struct {
|
|
||||||
listener adapter.NeighborUpdateListener
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *neighborUpdateListenerWrapper) UpdateNeighborTable(entries NeighborEntryIterator) {
|
|
||||||
var result []adapter.NeighborEntry
|
|
||||||
for entries.HasNext() {
|
|
||||||
entry := entries.Next()
|
|
||||||
if entry == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
address, err := netip.ParseAddr(entry.Address)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
macAddress, err := net.ParseMAC(entry.MacAddress)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
result = append(result, adapter.NeighborEntry{
|
|
||||||
Address: address,
|
|
||||||
MACAddress: macAddress,
|
|
||||||
Hostname: entry.Hostname,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
w.listener.UpdateNeighborTable(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
func AvailablePort(startPort int32) (int32, error) {
|
func AvailablePort(startPort int32) (int32, error) {
|
||||||
for port := int(startPort); ; port++ {
|
for port := int(startPort); ; port++ {
|
||||||
if port > 65535 {
|
if port > 65535 {
|
||||||
|
|||||||
19
go.mod
19
go.mod
@@ -14,13 +14,11 @@ require (
|
|||||||
github.com/godbus/dbus/v5 v5.2.2
|
github.com/godbus/dbus/v5 v5.2.2
|
||||||
github.com/gofrs/uuid/v5 v5.4.0
|
github.com/gofrs/uuid/v5 v5.4.0
|
||||||
github.com/insomniacslk/dhcp v0.0.0-20260220084031-5adc3eb26f91
|
github.com/insomniacslk/dhcp v0.0.0-20260220084031-5adc3eb26f91
|
||||||
github.com/jsimonetti/rtnetlink v1.4.0
|
|
||||||
github.com/keybase/go-keychain v0.0.1
|
github.com/keybase/go-keychain v0.0.1
|
||||||
github.com/libdns/acmedns v0.5.0
|
github.com/libdns/acmedns v0.5.0
|
||||||
github.com/libdns/alidns v1.0.6
|
github.com/libdns/alidns v1.0.6
|
||||||
github.com/libdns/cloudflare v0.2.2
|
github.com/libdns/cloudflare v0.2.2
|
||||||
github.com/logrusorgru/aurora v2.0.3+incompatible
|
github.com/logrusorgru/aurora v2.0.3+incompatible
|
||||||
github.com/mdlayher/netlink v1.9.0
|
|
||||||
github.com/metacubex/utls v1.8.4
|
github.com/metacubex/utls v1.8.4
|
||||||
github.com/mholt/acmez/v3 v3.1.6
|
github.com/mholt/acmez/v3 v3.1.6
|
||||||
github.com/miekg/dns v1.1.72
|
github.com/miekg/dns v1.1.72
|
||||||
@@ -29,19 +27,20 @@ require (
|
|||||||
github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1
|
github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1
|
||||||
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a
|
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a
|
||||||
github.com/sagernet/cors v1.2.1
|
github.com/sagernet/cors v1.2.1
|
||||||
github.com/sagernet/cronet-go v0.0.0-20260309100020-c128886ff3fc
|
github.com/sagernet/cronet-go v0.0.0-20260309102448-2fef65f9dba9
|
||||||
github.com/sagernet/cronet-go/all v0.0.0-20260309100020-c128886ff3fc
|
github.com/sagernet/cronet-go/all v0.0.0-20260309102448-2fef65f9dba9
|
||||||
github.com/sagernet/fswatch v0.1.1
|
github.com/sagernet/fswatch v0.1.1
|
||||||
github.com/sagernet/gomobile v0.1.12
|
github.com/sagernet/gomobile v0.1.12
|
||||||
github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1
|
github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1
|
||||||
github.com/sagernet/quic-go v0.59.0-sing-box-mod.4
|
github.com/sagernet/quic-go v0.59.0-sing-box-mod.4
|
||||||
github.com/sagernet/sing v0.8.3-0.20260315153529-ed51f65fbfde
|
github.com/sagernet/sing v0.8.4
|
||||||
|
github.com/sagernet/sing-cloudflared v0.0.0-20260407120610-7715dc2523fa
|
||||||
github.com/sagernet/sing-mux v0.3.4
|
github.com/sagernet/sing-mux v0.3.4
|
||||||
github.com/sagernet/sing-quic v0.6.0
|
github.com/sagernet/sing-quic v0.6.1
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.8
|
github.com/sagernet/sing-shadowsocks v0.2.8
|
||||||
github.com/sagernet/sing-shadowsocks2 v0.2.1
|
github.com/sagernet/sing-shadowsocks2 v0.2.1
|
||||||
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11
|
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11
|
||||||
github.com/sagernet/sing-tun v0.8.7-0.20260323120017-8eb4e8acfc2d
|
github.com/sagernet/sing-tun v0.8.7-0.20260402180740-11f6e77ec6c6
|
||||||
github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1
|
github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1
|
||||||
github.com/sagernet/smux v1.5.50-sing-box-mod.1
|
github.com/sagernet/smux v1.5.50-sing-box-mod.1
|
||||||
github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.7
|
github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.7
|
||||||
@@ -72,6 +71,7 @@ require (
|
|||||||
github.com/caddyserver/zerossl v0.1.5 // indirect
|
github.com/caddyserver/zerossl v0.1.5 // indirect
|
||||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||||
github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 // indirect
|
github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 // indirect
|
||||||
|
github.com/coreos/go-oidc/v3 v3.17.0 // indirect
|
||||||
github.com/database64128/netx-go v0.1.1 // indirect
|
github.com/database64128/netx-go v0.1.1 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||||
github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa // indirect
|
github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa // indirect
|
||||||
@@ -81,6 +81,7 @@ require (
|
|||||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||||
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
|
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
|
||||||
github.com/gaissmai/bart v0.18.0 // indirect
|
github.com/gaissmai/bart v0.18.0 // indirect
|
||||||
|
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
|
||||||
github.com/go-json-experiment/json v0.0.0-20250813024750-ebf49471dced // indirect
|
github.com/go-json-experiment/json v0.0.0-20250813024750-ebf49471dced // indirect
|
||||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||||
github.com/gobwas/httphead v0.1.0 // indirect
|
github.com/gobwas/httphead v0.1.0 // indirect
|
||||||
@@ -94,11 +95,14 @@ require (
|
|||||||
github.com/hashicorp/yamux v0.1.2 // indirect
|
github.com/hashicorp/yamux v0.1.2 // indirect
|
||||||
github.com/hdevalence/ed25519consensus v0.2.0 // indirect
|
github.com/hdevalence/ed25519consensus v0.2.0 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
|
github.com/jsimonetti/rtnetlink v1.4.0 // indirect
|
||||||
github.com/klauspost/compress v1.18.0 // indirect
|
github.com/klauspost/compress v1.18.0 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||||
github.com/libdns/libdns v1.1.1 // indirect
|
github.com/libdns/libdns v1.1.1 // indirect
|
||||||
|
github.com/mdlayher/netlink v1.9.0 // indirect
|
||||||
github.com/mdlayher/socket v0.5.1 // indirect
|
github.com/mdlayher/socket v0.5.1 // indirect
|
||||||
github.com/mitchellh/go-ps v1.0.0 // indirect
|
github.com/mitchellh/go-ps v1.0.0 // indirect
|
||||||
|
github.com/philhofer/fwd v1.2.0 // indirect
|
||||||
github.com/pierrec/lz4/v4 v4.1.21 // indirect
|
github.com/pierrec/lz4/v4 v4.1.21 // indirect
|
||||||
github.com/pires/go-proxyproto v0.8.1 // indirect
|
github.com/pires/go-proxyproto v0.8.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||||
@@ -165,4 +169,5 @@ require (
|
|||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
lukechampine.com/blake3 v1.3.0 // indirect
|
lukechampine.com/blake3 v1.3.0 // indirect
|
||||||
|
zombiezen.com/go/capnproto2 v2.18.2+incompatible // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
32
go.sum
32
go.sum
@@ -28,6 +28,8 @@ github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9
|
|||||||
github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
|
github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
|
||||||
github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 h1:8h5+bWd7R6AYUslN6c6iuZWTKsKxUFDlpnmilO6R2n0=
|
github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 h1:8h5+bWd7R6AYUslN6c6iuZWTKsKxUFDlpnmilO6R2n0=
|
||||||
github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
|
github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
|
||||||
|
github.com/coreos/go-oidc/v3 v3.17.0 h1:hWBGaQfbi0iVviX4ibC7bk8OKT5qNr4klBaCHVNvehc=
|
||||||
|
github.com/coreos/go-oidc/v3 v3.17.0/go.mod h1:wqPbKFrVnE90vty060SB40FCJ8fTHTxSwyXJqZH+sI8=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||||
github.com/cretz/bine v0.2.0 h1:8GiDRGlTgz+o8H9DSnsl+5MeBK4HsExxgl6WgzOCuZo=
|
github.com/cretz/bine v0.2.0 h1:8GiDRGlTgz+o8H9DSnsl+5MeBK4HsExxgl6WgzOCuZo=
|
||||||
github.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbetI=
|
github.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbetI=
|
||||||
@@ -110,6 +112,8 @@ github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zt
|
|||||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||||
|
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||||
|
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||||
github.com/letsencrypt/challtestsrv v1.4.2 h1:0ON3ldMhZyWlfVNYYpFuWRTmZNnyfiL9Hh5YzC3JVwU=
|
github.com/letsencrypt/challtestsrv v1.4.2 h1:0ON3ldMhZyWlfVNYYpFuWRTmZNnyfiL9Hh5YzC3JVwU=
|
||||||
github.com/letsencrypt/challtestsrv v1.4.2/go.mod h1:GhqMqcSoeGpYd5zX5TgwA6er/1MbWzx/o7yuuVya+Wk=
|
github.com/letsencrypt/challtestsrv v1.4.2/go.mod h1:GhqMqcSoeGpYd5zX5TgwA6er/1MbWzx/o7yuuVya+Wk=
|
||||||
github.com/letsencrypt/pebble/v2 v2.10.0 h1:Wq6gYXlsY6ubqI3hhxsTzdyotvfdjFBxuwYqCLCnj/U=
|
github.com/letsencrypt/pebble/v2 v2.10.0 h1:Wq6gYXlsY6ubqI3hhxsTzdyotvfdjFBxuwYqCLCnj/U=
|
||||||
@@ -142,6 +146,8 @@ github.com/openai/openai-go/v3 v3.26.0 h1:bRt6H/ozMNt/dDkN4gobnLqaEGrRGBzmbVs0xx
|
|||||||
github.com/openai/openai-go/v3 v3.26.0/go.mod h1:cdufnVK14cWcT9qA1rRtrXx4FTRsgbDPW7Ia7SS5cZo=
|
github.com/openai/openai-go/v3 v3.26.0/go.mod h1:cdufnVK14cWcT9qA1rRtrXx4FTRsgbDPW7Ia7SS5cZo=
|
||||||
github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5MbwsmL4MRQE=
|
github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5MbwsmL4MRQE=
|
||||||
github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8=
|
github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8=
|
||||||
|
github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
|
||||||
|
github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
|
||||||
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
|
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
|
||||||
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||||
github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0=
|
github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0=
|
||||||
@@ -162,10 +168,10 @@ github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkk
|
|||||||
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM=
|
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM=
|
||||||
github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ=
|
github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ=
|
||||||
github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI=
|
github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI=
|
||||||
github.com/sagernet/cronet-go v0.0.0-20260309100020-c128886ff3fc h1:YK7PwJT0irRAEui9ASdXSxcE2BOVQipWMF/A1Ogt+7c=
|
github.com/sagernet/cronet-go v0.0.0-20260309102448-2fef65f9dba9 h1:xq5Yr10jXEppD3cnGjE3WENaB6D0YsZu6KptZ8d3054=
|
||||||
github.com/sagernet/cronet-go v0.0.0-20260309100020-c128886ff3fc/go.mod h1:hwFHBEjjthyEquDULbr4c4ucMedp8Drb6Jvm2kt/0Bw=
|
github.com/sagernet/cronet-go v0.0.0-20260309102448-2fef65f9dba9/go.mod h1:hwFHBEjjthyEquDULbr4c4ucMedp8Drb6Jvm2kt/0Bw=
|
||||||
github.com/sagernet/cronet-go/all v0.0.0-20260309100020-c128886ff3fc h1:EJPHOqk23IuBsTjXK9OXqkNxPbKOBWKRmviQoCcriAs=
|
github.com/sagernet/cronet-go/all v0.0.0-20260309102448-2fef65f9dba9 h1:uxQyy6Y/boOuecVA66tf79JgtoRGfeDJcfYZZLKVA5E=
|
||||||
github.com/sagernet/cronet-go/all v0.0.0-20260309100020-c128886ff3fc/go.mod h1:8aty0RW96DrJSMWXO6bRPMBJEjuqq5JWiOIi4bCRzFA=
|
github.com/sagernet/cronet-go/all v0.0.0-20260309102448-2fef65f9dba9/go.mod h1:Xm6cCvs0/twozC1JYNq0sVlOVmcSGzV7YON1XGcD97w=
|
||||||
github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260309101654-0cbdcfddded9 h1:Qi0IKBpoPP3qZqIXuOKMsT2dv+l/MLWMyBHDMLRw2EA=
|
github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260309101654-0cbdcfddded9 h1:Qi0IKBpoPP3qZqIXuOKMsT2dv+l/MLWMyBHDMLRw2EA=
|
||||||
github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260309101654-0cbdcfddded9/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw=
|
github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260309101654-0cbdcfddded9/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw=
|
||||||
github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260309101654-0cbdcfddded9 h1:p+wCMjOhj46SpSD/AJeTGgkCcbyA76FyH631XZatyU8=
|
github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260309101654-0cbdcfddded9 h1:p+wCMjOhj46SpSD/AJeTGgkCcbyA76FyH631XZatyU8=
|
||||||
@@ -236,20 +242,22 @@ github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNen
|
|||||||
github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8=
|
github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8=
|
||||||
github.com/sagernet/quic-go v0.59.0-sing-box-mod.4 h1:6qvrUW79S+CrPwWz6cMePXohgjHoKxLo3c+MDhNwc3o=
|
github.com/sagernet/quic-go v0.59.0-sing-box-mod.4 h1:6qvrUW79S+CrPwWz6cMePXohgjHoKxLo3c+MDhNwc3o=
|
||||||
github.com/sagernet/quic-go v0.59.0-sing-box-mod.4/go.mod h1:OqILvS182CyOol5zNNo6bguvOGgXzV459+chpRaUC+4=
|
github.com/sagernet/quic-go v0.59.0-sing-box-mod.4/go.mod h1:OqILvS182CyOol5zNNo6bguvOGgXzV459+chpRaUC+4=
|
||||||
github.com/sagernet/sing v0.8.3-0.20260315153529-ed51f65fbfde h1:RNQzlpnsXIuu1HGts/fIzJ1PR7RhrzaNlU52MDyiX1c=
|
github.com/sagernet/sing v0.8.4 h1:Fj+jlY3F8vhcRfz/G/P3Dwcs5wqnmyNPT7u1RVVmjFI=
|
||||||
github.com/sagernet/sing v0.8.3-0.20260315153529-ed51f65fbfde/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
github.com/sagernet/sing v0.8.4/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||||
|
github.com/sagernet/sing-cloudflared v0.0.0-20260407120610-7715dc2523fa h1:165HiOfgfofJIirEp1NGSmsoJAi+++WhR29IhtAu4A4=
|
||||||
|
github.com/sagernet/sing-cloudflared v0.0.0-20260407120610-7715dc2523fa/go.mod h1:bH2NKX+NpDTY1Zkxfboxw6MXB/ZywaNLmrDJYgKMJ2Y=
|
||||||
github.com/sagernet/sing-mux v0.3.4 h1:ZQplKl8MNXutjzbMVtWvWG31fohhgOfCuUZR4dVQ8+s=
|
github.com/sagernet/sing-mux v0.3.4 h1:ZQplKl8MNXutjzbMVtWvWG31fohhgOfCuUZR4dVQ8+s=
|
||||||
github.com/sagernet/sing-mux v0.3.4/go.mod h1:QvlKMyNBNrQoyX4x+gq028uPbLM2XeRpWtDsWBJbFSk=
|
github.com/sagernet/sing-mux v0.3.4/go.mod h1:QvlKMyNBNrQoyX4x+gq028uPbLM2XeRpWtDsWBJbFSk=
|
||||||
github.com/sagernet/sing-quic v0.6.0 h1:dhrFnP45wgVKEOT1EvtsToxdzRnHIDIAgj6WHV9pLyM=
|
github.com/sagernet/sing-quic v0.6.1 h1:lx0tcm99wIA1RkyvILNzRSsMy1k7TTQYIhx71E/WBlw=
|
||||||
github.com/sagernet/sing-quic v0.6.0/go.mod h1:K5bWvITOm4vE10fwLfrWpw27bCoVJ+tfQ79tOWg+Ko8=
|
github.com/sagernet/sing-quic v0.6.1/go.mod h1:K5bWvITOm4vE10fwLfrWpw27bCoVJ+tfQ79tOWg+Ko8=
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE=
|
github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE=
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.8/go.mod h1:lo7TWEMDcN5/h5B8S0ew+r78ZODn6SwVaFhvB6H+PTI=
|
github.com/sagernet/sing-shadowsocks v0.2.8/go.mod h1:lo7TWEMDcN5/h5B8S0ew+r78ZODn6SwVaFhvB6H+PTI=
|
||||||
github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnqqs2gQ2/Qioo=
|
github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnqqs2gQ2/Qioo=
|
||||||
github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
|
github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
|
||||||
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w=
|
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w=
|
||||||
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA=
|
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA=
|
||||||
github.com/sagernet/sing-tun v0.8.7-0.20260323120017-8eb4e8acfc2d h1:vi0j6301f6H8t2GYgAC2PA2AdnGdMwkP34B4+N03Qt4=
|
github.com/sagernet/sing-tun v0.8.7-0.20260402180740-11f6e77ec6c6 h1:HV2I7DicF5Ar8v6F55f03W5FviBB7jgvLhJSDwbFvbk=
|
||||||
github.com/sagernet/sing-tun v0.8.7-0.20260323120017-8eb4e8acfc2d/go.mod h1:pLCo4o+LacXEzz0bhwhJkKBjLlKOGPBNOAZ97ZVZWzs=
|
github.com/sagernet/sing-tun v0.8.7-0.20260402180740-11f6e77ec6c6/go.mod h1:pLCo4o+LacXEzz0bhwhJkKBjLlKOGPBNOAZ97ZVZWzs=
|
||||||
github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 h1:aSwUNYUkVyVvdmBSufR8/nRFonwJeKSIROxHcm5br9o=
|
github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 h1:aSwUNYUkVyVvdmBSufR8/nRFonwJeKSIROxHcm5br9o=
|
||||||
github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1/go.mod h1:P11scgTxMxVVQ8dlM27yNm3Cro40mD0+gHbnqrNGDuY=
|
github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1/go.mod h1:P11scgTxMxVVQ8dlM27yNm3Cro40mD0+gHbnqrNGDuY=
|
||||||
github.com/sagernet/smux v1.5.50-sing-box-mod.1 h1:XkJcivBC9V4wBjiGXIXZ229aZCU1hzcbp6kSkkyQ478=
|
github.com/sagernet/smux v1.5.50-sing-box-mod.1 h1:XkJcivBC9V4wBjiGXIXZ229aZCU1hzcbp6kSkkyQ478=
|
||||||
@@ -294,6 +302,8 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
|||||||
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||||
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
|
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
|
||||||
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
|
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
|
||||||
|
github.com/tinylib/msgp v1.6.3 h1:bCSxiTz386UTgyT1i0MSCvdbWjVW+8sG3PjkGsZQt4s=
|
||||||
|
github.com/tinylib/msgp v1.6.3/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA=
|
||||||
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM=
|
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM=
|
||||||
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA=
|
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA=
|
||||||
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||||
@@ -401,3 +411,5 @@ lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE=
|
|||||||
lukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
|
lukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
|
||||||
software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k=
|
software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k=
|
||||||
software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=
|
software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=
|
||||||
|
zombiezen.com/go/capnproto2 v2.18.2+incompatible h1:v3BD1zbruvffn7zjJUU5Pn8nZAB11bhZSQC4W+YnnKo=
|
||||||
|
zombiezen.com/go/capnproto2 v2.18.2+incompatible/go.mod h1:XO5Pr2SbXgqZwn0m0Ru54QBqpOf4K5AYBO+8LAOBQEQ=
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
//go:build with_acme
|
|
||||||
|
|
||||||
package include
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/sagernet/sing-box/adapter/certificate"
|
|
||||||
"github.com/sagernet/sing-box/service/acme"
|
|
||||||
)
|
|
||||||
|
|
||||||
func registerACMECertificateProvider(registry *certificate.Registry) {
|
|
||||||
acme.RegisterCertificateProvider(registry)
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
//go:build !with_acme
|
|
||||||
|
|
||||||
package include
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
"github.com/sagernet/sing-box/adapter/certificate"
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
)
|
|
||||||
|
|
||||||
func registerACMECertificateProvider(registry *certificate.Registry) {
|
|
||||||
certificate.Register[option.ACMECertificateProviderOptions](registry, C.TypeACME, func(ctx context.Context, logger log.ContextLogger, tag string, options option.ACMECertificateProviderOptions) (adapter.CertificateProviderService, error) {
|
|
||||||
return nil, E.New(`ACME is not included in this build, rebuild with -tags with_acme`)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
12
include/cloudflared.go
Normal file
12
include/cloudflared.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
//go:build with_cloudflared
|
||||||
|
|
||||||
|
package include
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/sagernet/sing-box/adapter/inbound"
|
||||||
|
"github.com/sagernet/sing-box/protocol/cloudflare"
|
||||||
|
)
|
||||||
|
|
||||||
|
func registerCloudflaredInbound(registry *inbound.Registry) {
|
||||||
|
cloudflare.RegisterInbound(registry)
|
||||||
|
}
|
||||||
20
include/cloudflared_stub.go
Normal file
20
include/cloudflared_stub.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
//go:build !with_cloudflared
|
||||||
|
|
||||||
|
package include
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/adapter/inbound"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
func registerCloudflaredInbound(registry *inbound.Registry) {
|
||||||
|
inbound.Register[option.CloudflaredInboundOptions](registry, C.TypeCloudflared, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.CloudflaredInboundOptions) (adapter.Inbound, error) {
|
||||||
|
return nil, E.New(`Cloudflared is not included in this build, rebuild with -tags with_cloudflared`)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
|
|
||||||
"github.com/sagernet/sing-box"
|
"github.com/sagernet/sing-box"
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/adapter/certificate"
|
|
||||||
"github.com/sagernet/sing-box/adapter/endpoint"
|
"github.com/sagernet/sing-box/adapter/endpoint"
|
||||||
"github.com/sagernet/sing-box/adapter/inbound"
|
"github.com/sagernet/sing-box/adapter/inbound"
|
||||||
"github.com/sagernet/sing-box/adapter/outbound"
|
"github.com/sagernet/sing-box/adapter/outbound"
|
||||||
@@ -35,14 +34,13 @@ import (
|
|||||||
"github.com/sagernet/sing-box/protocol/tun"
|
"github.com/sagernet/sing-box/protocol/tun"
|
||||||
"github.com/sagernet/sing-box/protocol/vless"
|
"github.com/sagernet/sing-box/protocol/vless"
|
||||||
"github.com/sagernet/sing-box/protocol/vmess"
|
"github.com/sagernet/sing-box/protocol/vmess"
|
||||||
originca "github.com/sagernet/sing-box/service/origin_ca"
|
|
||||||
"github.com/sagernet/sing-box/service/resolved"
|
"github.com/sagernet/sing-box/service/resolved"
|
||||||
"github.com/sagernet/sing-box/service/ssmapi"
|
"github.com/sagernet/sing-box/service/ssmapi"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Context(ctx context.Context) context.Context {
|
func Context(ctx context.Context) context.Context {
|
||||||
return box.Context(ctx, InboundRegistry(), OutboundRegistry(), EndpointRegistry(), DNSTransportRegistry(), ServiceRegistry(), CertificateProviderRegistry())
|
return box.Context(ctx, InboundRegistry(), OutboundRegistry(), EndpointRegistry(), DNSTransportRegistry(), ServiceRegistry())
|
||||||
}
|
}
|
||||||
|
|
||||||
func InboundRegistry() *inbound.Registry {
|
func InboundRegistry() *inbound.Registry {
|
||||||
@@ -66,6 +64,7 @@ func InboundRegistry() *inbound.Registry {
|
|||||||
anytls.RegisterInbound(registry)
|
anytls.RegisterInbound(registry)
|
||||||
|
|
||||||
registerQUICInbounds(registry)
|
registerQUICInbounds(registry)
|
||||||
|
registerCloudflaredInbound(registry)
|
||||||
registerStubForRemovedInbounds(registry)
|
registerStubForRemovedInbounds(registry)
|
||||||
|
|
||||||
return registry
|
return registry
|
||||||
@@ -141,16 +140,6 @@ func ServiceRegistry() *service.Registry {
|
|||||||
return registry
|
return registry
|
||||||
}
|
}
|
||||||
|
|
||||||
func CertificateProviderRegistry() *certificate.Registry {
|
|
||||||
registry := certificate.NewRegistry()
|
|
||||||
|
|
||||||
registerACMECertificateProvider(registry)
|
|
||||||
registerTailscaleCertificateProvider(registry)
|
|
||||||
originca.RegisterCertificateProvider(registry)
|
|
||||||
|
|
||||||
return registry
|
|
||||||
}
|
|
||||||
|
|
||||||
func registerStubForRemovedInbounds(registry *inbound.Registry) {
|
func registerStubForRemovedInbounds(registry *inbound.Registry) {
|
||||||
inbound.Register[option.ShadowsocksInboundOptions](registry, C.TypeShadowsocksR, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksInboundOptions) (adapter.Inbound, error) {
|
inbound.Register[option.ShadowsocksInboundOptions](registry, C.TypeShadowsocksR, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksInboundOptions) (adapter.Inbound, error) {
|
||||||
return nil, E.New("ShadowsocksR is deprecated and removed in sing-box 1.6.0")
|
return nil, E.New("ShadowsocksR is deprecated and removed in sing-box 1.6.0")
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
package include
|
package include
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/sagernet/sing-box/adapter/certificate"
|
|
||||||
"github.com/sagernet/sing-box/adapter/endpoint"
|
"github.com/sagernet/sing-box/adapter/endpoint"
|
||||||
"github.com/sagernet/sing-box/adapter/service"
|
"github.com/sagernet/sing-box/adapter/service"
|
||||||
"github.com/sagernet/sing-box/dns"
|
"github.com/sagernet/sing-box/dns"
|
||||||
@@ -19,10 +18,6 @@ func registerTailscaleTransport(registry *dns.TransportRegistry) {
|
|||||||
tailscale.RegistryTransport(registry)
|
tailscale.RegistryTransport(registry)
|
||||||
}
|
}
|
||||||
|
|
||||||
func registerTailscaleCertificateProvider(registry *certificate.Registry) {
|
|
||||||
tailscale.RegisterCertificateProvider(registry)
|
|
||||||
}
|
|
||||||
|
|
||||||
func registerDERPService(registry *service.Registry) {
|
func registerDERPService(registry *service.Registry) {
|
||||||
derp.Register(registry)
|
derp.Register(registry)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/adapter/certificate"
|
|
||||||
"github.com/sagernet/sing-box/adapter/endpoint"
|
"github.com/sagernet/sing-box/adapter/endpoint"
|
||||||
"github.com/sagernet/sing-box/adapter/service"
|
"github.com/sagernet/sing-box/adapter/service"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
@@ -28,12 +27,6 @@ func registerTailscaleTransport(registry *dns.TransportRegistry) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func registerTailscaleCertificateProvider(registry *certificate.Registry) {
|
|
||||||
certificate.Register[option.TailscaleCertificateProviderOptions](registry, C.TypeTailscale, func(ctx context.Context, logger log.ContextLogger, tag string, options option.TailscaleCertificateProviderOptions) (adapter.CertificateProviderService, error) {
|
|
||||||
return nil, E.New(`Tailscale is not included in this build, rebuild with -tags with_tailscale`)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func registerDERPService(registry *service.Registry) {
|
func registerDERPService(registry *service.Registry) {
|
||||||
service.Register[option.DERPServiceOptions](registry, C.TypeDERP, func(ctx context.Context, logger log.ContextLogger, tag string, options option.DERPServiceOptions) (adapter.Service, error) {
|
service.Register[option.DERPServiceOptions](registry, C.TypeDERP, func(ctx context.Context, logger log.ContextLogger, tag string, options option.DERPServiceOptions) (adapter.Service, error) {
|
||||||
return nil, E.New(`DERP is not included in this build, rebuild with -tags with_tailscale`)
|
return nil, E.New(`DERP is not included in this build, rebuild with -tags with_tailscale`)
|
||||||
|
|||||||
@@ -122,11 +122,6 @@ nav:
|
|||||||
- Listen Fields: configuration/shared/listen.md
|
- Listen Fields: configuration/shared/listen.md
|
||||||
- Dial Fields: configuration/shared/dial.md
|
- Dial Fields: configuration/shared/dial.md
|
||||||
- TLS: configuration/shared/tls.md
|
- TLS: configuration/shared/tls.md
|
||||||
- Certificate Provider:
|
|
||||||
- configuration/shared/certificate-provider/index.md
|
|
||||||
- ACME: configuration/shared/certificate-provider/acme.md
|
|
||||||
- Tailscale: configuration/shared/certificate-provider/tailscale.md
|
|
||||||
- Cloudflare Origin CA: configuration/shared/certificate-provider/cloudflare-origin-ca.md
|
|
||||||
- DNS01 Challenge Fields: configuration/shared/dns01_challenge.md
|
- DNS01 Challenge Fields: configuration/shared/dns01_challenge.md
|
||||||
- Pre-match: configuration/shared/pre-match.md
|
- Pre-match: configuration/shared/pre-match.md
|
||||||
- Multiplex: configuration/shared/multiplex.md
|
- Multiplex: configuration/shared/multiplex.md
|
||||||
@@ -134,7 +129,6 @@ nav:
|
|||||||
- UDP over TCP: configuration/shared/udp-over-tcp.md
|
- UDP over TCP: configuration/shared/udp-over-tcp.md
|
||||||
- TCP Brutal: configuration/shared/tcp-brutal.md
|
- TCP Brutal: configuration/shared/tcp-brutal.md
|
||||||
- Wi-Fi State: configuration/shared/wifi-state.md
|
- Wi-Fi State: configuration/shared/wifi-state.md
|
||||||
- Neighbor Resolution: configuration/shared/neighbor.md
|
|
||||||
- Endpoint:
|
- Endpoint:
|
||||||
- configuration/endpoint/index.md
|
- configuration/endpoint/index.md
|
||||||
- WireGuard: configuration/endpoint/wireguard.md
|
- WireGuard: configuration/endpoint/wireguard.md
|
||||||
@@ -278,7 +272,6 @@ plugins:
|
|||||||
Shared: 通用
|
Shared: 通用
|
||||||
Listen Fields: 监听字段
|
Listen Fields: 监听字段
|
||||||
Dial Fields: 拨号字段
|
Dial Fields: 拨号字段
|
||||||
Certificate Provider Fields: 证书提供者字段
|
|
||||||
DNS01 Challenge Fields: DNS01 验证字段
|
DNS01 Challenge Fields: DNS01 验证字段
|
||||||
Multiplex: 多路复用
|
Multiplex: 多路复用
|
||||||
V2Ray Transport: V2Ray 传输层
|
V2Ray Transport: V2Ray 传输层
|
||||||
@@ -287,7 +280,6 @@ plugins:
|
|||||||
Endpoint: 端点
|
Endpoint: 端点
|
||||||
Inbound: 入站
|
Inbound: 入站
|
||||||
Outbound: 出站
|
Outbound: 出站
|
||||||
Certificate Provider: 证书提供者
|
|
||||||
|
|
||||||
Manual: 手册
|
Manual: 手册
|
||||||
reconfigure_material: true
|
reconfigure_material: true
|
||||||
|
|||||||
106
option/acme.go
106
option/acme.go
@@ -1,106 +0,0 @@
|
|||||||
package option
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
"github.com/sagernet/sing/common/json"
|
|
||||||
"github.com/sagernet/sing/common/json/badjson"
|
|
||||||
"github.com/sagernet/sing/common/json/badoption"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ACMECertificateProviderOptions struct {
|
|
||||||
Domain badoption.Listable[string] `json:"domain,omitempty"`
|
|
||||||
DataDirectory string `json:"data_directory,omitempty"`
|
|
||||||
DefaultServerName string `json:"default_server_name,omitempty"`
|
|
||||||
Email string `json:"email,omitempty"`
|
|
||||||
Provider string `json:"provider,omitempty"`
|
|
||||||
AccountKey string `json:"account_key,omitempty"`
|
|
||||||
DisableHTTPChallenge bool `json:"disable_http_challenge,omitempty"`
|
|
||||||
DisableTLSALPNChallenge bool `json:"disable_tls_alpn_challenge,omitempty"`
|
|
||||||
AlternativeHTTPPort uint16 `json:"alternative_http_port,omitempty"`
|
|
||||||
AlternativeTLSPort uint16 `json:"alternative_tls_port,omitempty"`
|
|
||||||
ExternalAccount *ACMEExternalAccountOptions `json:"external_account,omitempty"`
|
|
||||||
DNS01Challenge *ACMEProviderDNS01ChallengeOptions `json:"dns01_challenge,omitempty"`
|
|
||||||
KeyType ACMEKeyType `json:"key_type,omitempty"`
|
|
||||||
Detour string `json:"detour,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type _ACMEProviderDNS01ChallengeOptions struct {
|
|
||||||
TTL badoption.Duration `json:"ttl,omitempty"`
|
|
||||||
PropagationDelay badoption.Duration `json:"propagation_delay,omitempty"`
|
|
||||||
PropagationTimeout badoption.Duration `json:"propagation_timeout,omitempty"`
|
|
||||||
Resolvers badoption.Listable[string] `json:"resolvers,omitempty"`
|
|
||||||
OverrideDomain string `json:"override_domain,omitempty"`
|
|
||||||
Provider string `json:"provider,omitempty"`
|
|
||||||
AliDNSOptions ACMEDNS01AliDNSOptions `json:"-"`
|
|
||||||
CloudflareOptions ACMEDNS01CloudflareOptions `json:"-"`
|
|
||||||
ACMEDNSOptions ACMEDNS01ACMEDNSOptions `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ACMEProviderDNS01ChallengeOptions _ACMEProviderDNS01ChallengeOptions
|
|
||||||
|
|
||||||
func (o ACMEProviderDNS01ChallengeOptions) MarshalJSON() ([]byte, error) {
|
|
||||||
var v any
|
|
||||||
switch o.Provider {
|
|
||||||
case C.DNSProviderAliDNS:
|
|
||||||
v = o.AliDNSOptions
|
|
||||||
case C.DNSProviderCloudflare:
|
|
||||||
v = o.CloudflareOptions
|
|
||||||
case C.DNSProviderACMEDNS:
|
|
||||||
v = o.ACMEDNSOptions
|
|
||||||
case "":
|
|
||||||
return nil, E.New("missing provider type")
|
|
||||||
default:
|
|
||||||
return nil, E.New("unknown provider type: ", o.Provider)
|
|
||||||
}
|
|
||||||
return badjson.MarshallObjects((_ACMEProviderDNS01ChallengeOptions)(o), v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *ACMEProviderDNS01ChallengeOptions) UnmarshalJSON(bytes []byte) error {
|
|
||||||
err := json.Unmarshal(bytes, (*_ACMEProviderDNS01ChallengeOptions)(o))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var v any
|
|
||||||
switch o.Provider {
|
|
||||||
case C.DNSProviderAliDNS:
|
|
||||||
v = &o.AliDNSOptions
|
|
||||||
case C.DNSProviderCloudflare:
|
|
||||||
v = &o.CloudflareOptions
|
|
||||||
case C.DNSProviderACMEDNS:
|
|
||||||
v = &o.ACMEDNSOptions
|
|
||||||
case "":
|
|
||||||
return E.New("missing provider type")
|
|
||||||
default:
|
|
||||||
return E.New("unknown provider type: ", o.Provider)
|
|
||||||
}
|
|
||||||
return badjson.UnmarshallExcluded(bytes, (*_ACMEProviderDNS01ChallengeOptions)(o), v)
|
|
||||||
}
|
|
||||||
|
|
||||||
type ACMEKeyType string
|
|
||||||
|
|
||||||
const (
|
|
||||||
ACMEKeyTypeED25519 = ACMEKeyType("ed25519")
|
|
||||||
ACMEKeyTypeP256 = ACMEKeyType("p256")
|
|
||||||
ACMEKeyTypeP384 = ACMEKeyType("p384")
|
|
||||||
ACMEKeyTypeRSA2048 = ACMEKeyType("rsa2048")
|
|
||||||
ACMEKeyTypeRSA4096 = ACMEKeyType("rsa4096")
|
|
||||||
)
|
|
||||||
|
|
||||||
func (t *ACMEKeyType) UnmarshalJSON(data []byte) error {
|
|
||||||
var value string
|
|
||||||
err := json.Unmarshal(data, &value)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
value = strings.ToLower(value)
|
|
||||||
switch ACMEKeyType(value) {
|
|
||||||
case "", ACMEKeyTypeED25519, ACMEKeyTypeP256, ACMEKeyTypeP384, ACMEKeyTypeRSA2048, ACMEKeyTypeRSA4096:
|
|
||||||
*t = ACMEKeyType(value)
|
|
||||||
default:
|
|
||||||
return E.New("unknown ACME key type: ", value)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
package option
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
"github.com/sagernet/sing/common/json"
|
|
||||||
"github.com/sagernet/sing/common/json/badjson"
|
|
||||||
"github.com/sagernet/sing/service"
|
|
||||||
)
|
|
||||||
|
|
||||||
type CertificateProviderOptionsRegistry interface {
|
|
||||||
CreateOptions(providerType string) (any, bool)
|
|
||||||
}
|
|
||||||
|
|
||||||
type _CertificateProvider struct {
|
|
||||||
Type string `json:"type"`
|
|
||||||
Tag string `json:"tag,omitempty"`
|
|
||||||
Options any `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CertificateProvider _CertificateProvider
|
|
||||||
|
|
||||||
func (h *CertificateProvider) MarshalJSONContext(ctx context.Context) ([]byte, error) {
|
|
||||||
return badjson.MarshallObjectsContext(ctx, (*_CertificateProvider)(h), h.Options)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *CertificateProvider) UnmarshalJSONContext(ctx context.Context, content []byte) error {
|
|
||||||
err := json.UnmarshalContext(ctx, content, (*_CertificateProvider)(h))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
registry := service.FromContext[CertificateProviderOptionsRegistry](ctx)
|
|
||||||
if registry == nil {
|
|
||||||
return E.New("missing certificate provider options registry in context")
|
|
||||||
}
|
|
||||||
options, loaded := registry.CreateOptions(h.Type)
|
|
||||||
if !loaded {
|
|
||||||
return E.New("unknown certificate provider type: ", h.Type)
|
|
||||||
}
|
|
||||||
err = badjson.UnmarshallExcludedContext(ctx, content, (*_CertificateProvider)(h), options)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
h.Options = options
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type CertificateProviderOptions struct {
|
|
||||||
Tag string `json:"-"`
|
|
||||||
Type string `json:"-"`
|
|
||||||
Options any `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type _CertificateProviderInline struct {
|
|
||||||
Type string `json:"type"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *CertificateProviderOptions) MarshalJSONContext(ctx context.Context) ([]byte, error) {
|
|
||||||
if o.Tag != "" {
|
|
||||||
return json.Marshal(o.Tag)
|
|
||||||
}
|
|
||||||
return badjson.MarshallObjectsContext(ctx, _CertificateProviderInline{Type: o.Type}, o.Options)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *CertificateProviderOptions) UnmarshalJSONContext(ctx context.Context, content []byte) error {
|
|
||||||
if len(content) == 0 {
|
|
||||||
return E.New("empty certificate_provider value")
|
|
||||||
}
|
|
||||||
if content[0] == '"' {
|
|
||||||
return json.UnmarshalContext(ctx, content, &o.Tag)
|
|
||||||
}
|
|
||||||
var inline _CertificateProviderInline
|
|
||||||
err := json.UnmarshalContext(ctx, content, &inline)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
o.Type = inline.Type
|
|
||||||
if o.Type == "" {
|
|
||||||
return E.New("missing certificate provider type")
|
|
||||||
}
|
|
||||||
registry := service.FromContext[CertificateProviderOptionsRegistry](ctx)
|
|
||||||
if registry == nil {
|
|
||||||
return E.New("missing certificate provider options registry in context")
|
|
||||||
}
|
|
||||||
options, loaded := registry.CreateOptions(o.Type)
|
|
||||||
if !loaded {
|
|
||||||
return E.New("unknown certificate provider type: ", o.Type)
|
|
||||||
}
|
|
||||||
err = badjson.UnmarshallExcludedContext(ctx, content, &inline, options)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
o.Options = options
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *CertificateProviderOptions) IsShared() bool {
|
|
||||||
return o.Tag != ""
|
|
||||||
}
|
|
||||||
16
option/cloudflared.go
Normal file
16
option/cloudflared.go
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package option
|
||||||
|
|
||||||
|
import "github.com/sagernet/sing/common/json/badoption"
|
||||||
|
|
||||||
|
type CloudflaredInboundOptions struct {
|
||||||
|
Token string `json:"token,omitempty"`
|
||||||
|
HAConnections int `json:"ha_connections,omitempty"`
|
||||||
|
Protocol string `json:"protocol,omitempty"`
|
||||||
|
PostQuantum bool `json:"post_quantum,omitempty"`
|
||||||
|
ControlDialer DialerOptions `json:"control_dialer,omitempty"`
|
||||||
|
TunnelDialer DialerOptions `json:"tunnel_dialer,omitempty"`
|
||||||
|
EdgeIPVersion int `json:"edge_ip_version,omitempty"`
|
||||||
|
DatagramVersion string `json:"datagram_version,omitempty"`
|
||||||
|
GracePeriod *badoption.Duration `json:"grace_period,omitempty"`
|
||||||
|
Region string `json:"region,omitempty"`
|
||||||
|
}
|
||||||
@@ -10,19 +10,18 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type _Options struct {
|
type _Options struct {
|
||||||
RawMessage json.RawMessage `json:"-"`
|
RawMessage json.RawMessage `json:"-"`
|
||||||
Schema string `json:"$schema,omitempty"`
|
Schema string `json:"$schema,omitempty"`
|
||||||
Log *LogOptions `json:"log,omitempty"`
|
Log *LogOptions `json:"log,omitempty"`
|
||||||
DNS *DNSOptions `json:"dns,omitempty"`
|
DNS *DNSOptions `json:"dns,omitempty"`
|
||||||
NTP *NTPOptions `json:"ntp,omitempty"`
|
NTP *NTPOptions `json:"ntp,omitempty"`
|
||||||
Certificate *CertificateOptions `json:"certificate,omitempty"`
|
Certificate *CertificateOptions `json:"certificate,omitempty"`
|
||||||
CertificateProviders []CertificateProvider `json:"certificate_providers,omitempty"`
|
Endpoints []Endpoint `json:"endpoints,omitempty"`
|
||||||
Endpoints []Endpoint `json:"endpoints,omitempty"`
|
Inbounds []Inbound `json:"inbounds,omitempty"`
|
||||||
Inbounds []Inbound `json:"inbounds,omitempty"`
|
Outbounds []Outbound `json:"outbounds,omitempty"`
|
||||||
Outbounds []Outbound `json:"outbounds,omitempty"`
|
Route *RouteOptions `json:"route,omitempty"`
|
||||||
Route *RouteOptions `json:"route,omitempty"`
|
Services []Service `json:"services,omitempty"`
|
||||||
Services []Service `json:"services,omitempty"`
|
Experimental *ExperimentalOptions `json:"experimental,omitempty"`
|
||||||
Experimental *ExperimentalOptions `json:"experimental,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Options _Options
|
type Options _Options
|
||||||
@@ -57,25 +56,6 @@ func checkOptions(options *Options) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = checkCertificateProviders(options.CertificateProviders)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkCertificateProviders(providers []CertificateProvider) error {
|
|
||||||
seen := make(map[string]bool)
|
|
||||||
for i, provider := range providers {
|
|
||||||
tag := provider.Tag
|
|
||||||
if tag == "" {
|
|
||||||
tag = F.ToString(i)
|
|
||||||
}
|
|
||||||
if seen[tag] {
|
|
||||||
return E.New("duplicate certificate provider tag: ", tag)
|
|
||||||
}
|
|
||||||
seen[tag] = true
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,76 +0,0 @@
|
|||||||
package option
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
"github.com/sagernet/sing/common/json"
|
|
||||||
"github.com/sagernet/sing/common/json/badoption"
|
|
||||||
)
|
|
||||||
|
|
||||||
type CloudflareOriginCACertificateProviderOptions struct {
|
|
||||||
Domain badoption.Listable[string] `json:"domain,omitempty"`
|
|
||||||
DataDirectory string `json:"data_directory,omitempty"`
|
|
||||||
APIToken string `json:"api_token,omitempty"`
|
|
||||||
OriginCAKey string `json:"origin_ca_key,omitempty"`
|
|
||||||
RequestType CloudflareOriginCARequestType `json:"request_type,omitempty"`
|
|
||||||
RequestedValidity CloudflareOriginCARequestValidity `json:"requested_validity,omitempty"`
|
|
||||||
Detour string `json:"detour,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CloudflareOriginCARequestType string
|
|
||||||
|
|
||||||
const (
|
|
||||||
CloudflareOriginCARequestTypeOriginRSA = CloudflareOriginCARequestType("origin-rsa")
|
|
||||||
CloudflareOriginCARequestTypeOriginECC = CloudflareOriginCARequestType("origin-ecc")
|
|
||||||
)
|
|
||||||
|
|
||||||
func (t *CloudflareOriginCARequestType) UnmarshalJSON(data []byte) error {
|
|
||||||
var value string
|
|
||||||
err := json.Unmarshal(data, &value)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
value = strings.ToLower(value)
|
|
||||||
switch CloudflareOriginCARequestType(value) {
|
|
||||||
case "", CloudflareOriginCARequestTypeOriginRSA, CloudflareOriginCARequestTypeOriginECC:
|
|
||||||
*t = CloudflareOriginCARequestType(value)
|
|
||||||
default:
|
|
||||||
return E.New("unsupported Cloudflare Origin CA request type: ", value)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type CloudflareOriginCARequestValidity uint16
|
|
||||||
|
|
||||||
const (
|
|
||||||
CloudflareOriginCARequestValidity7 = CloudflareOriginCARequestValidity(7)
|
|
||||||
CloudflareOriginCARequestValidity30 = CloudflareOriginCARequestValidity(30)
|
|
||||||
CloudflareOriginCARequestValidity90 = CloudflareOriginCARequestValidity(90)
|
|
||||||
CloudflareOriginCARequestValidity365 = CloudflareOriginCARequestValidity(365)
|
|
||||||
CloudflareOriginCARequestValidity730 = CloudflareOriginCARequestValidity(730)
|
|
||||||
CloudflareOriginCARequestValidity1095 = CloudflareOriginCARequestValidity(1095)
|
|
||||||
CloudflareOriginCARequestValidity5475 = CloudflareOriginCARequestValidity(5475)
|
|
||||||
)
|
|
||||||
|
|
||||||
func (v *CloudflareOriginCARequestValidity) UnmarshalJSON(data []byte) error {
|
|
||||||
var value uint16
|
|
||||||
err := json.Unmarshal(data, &value)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
switch CloudflareOriginCARequestValidity(value) {
|
|
||||||
case 0,
|
|
||||||
CloudflareOriginCARequestValidity7,
|
|
||||||
CloudflareOriginCARequestValidity30,
|
|
||||||
CloudflareOriginCARequestValidity90,
|
|
||||||
CloudflareOriginCARequestValidity365,
|
|
||||||
CloudflareOriginCARequestValidity730,
|
|
||||||
CloudflareOriginCARequestValidity1095,
|
|
||||||
CloudflareOriginCARequestValidity5475:
|
|
||||||
*v = CloudflareOriginCARequestValidity(value)
|
|
||||||
default:
|
|
||||||
return E.New("unsupported Cloudflare Origin CA requested validity: ", value)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -9,8 +9,6 @@ type RouteOptions struct {
|
|||||||
RuleSet []RuleSet `json:"rule_set,omitempty"`
|
RuleSet []RuleSet `json:"rule_set,omitempty"`
|
||||||
Final string `json:"final,omitempty"`
|
Final string `json:"final,omitempty"`
|
||||||
FindProcess bool `json:"find_process,omitempty"`
|
FindProcess bool `json:"find_process,omitempty"`
|
||||||
FindNeighbor bool `json:"find_neighbor,omitempty"`
|
|
||||||
DHCPLeaseFiles badoption.Listable[string] `json:"dhcp_lease_files,omitempty"`
|
|
||||||
AutoDetectInterface bool `json:"auto_detect_interface,omitempty"`
|
AutoDetectInterface bool `json:"auto_detect_interface,omitempty"`
|
||||||
OverrideAndroidVPN bool `json:"override_android_vpn,omitempty"`
|
OverrideAndroidVPN bool `json:"override_android_vpn,omitempty"`
|
||||||
DefaultInterface string `json:"default_interface,omitempty"`
|
DefaultInterface string `json:"default_interface,omitempty"`
|
||||||
|
|||||||
@@ -103,8 +103,6 @@ type RawDefaultRule struct {
|
|||||||
InterfaceAddress *badjson.TypedMap[string, badoption.Listable[*badoption.Prefixable]] `json:"interface_address,omitempty"`
|
InterfaceAddress *badjson.TypedMap[string, badoption.Listable[*badoption.Prefixable]] `json:"interface_address,omitempty"`
|
||||||
NetworkInterfaceAddress *badjson.TypedMap[InterfaceType, badoption.Listable[*badoption.Prefixable]] `json:"network_interface_address,omitempty"`
|
NetworkInterfaceAddress *badjson.TypedMap[InterfaceType, badoption.Listable[*badoption.Prefixable]] `json:"network_interface_address,omitempty"`
|
||||||
DefaultInterfaceAddress badoption.Listable[*badoption.Prefixable] `json:"default_interface_address,omitempty"`
|
DefaultInterfaceAddress badoption.Listable[*badoption.Prefixable] `json:"default_interface_address,omitempty"`
|
||||||
SourceMACAddress badoption.Listable[string] `json:"source_mac_address,omitempty"`
|
|
||||||
SourceHostname badoption.Listable[string] `json:"source_hostname,omitempty"`
|
|
||||||
PreferredBy badoption.Listable[string] `json:"preferred_by,omitempty"`
|
PreferredBy badoption.Listable[string] `json:"preferred_by,omitempty"`
|
||||||
RuleSet badoption.Listable[string] `json:"rule_set,omitempty"`
|
RuleSet badoption.Listable[string] `json:"rule_set,omitempty"`
|
||||||
RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"`
|
RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"`
|
||||||
|
|||||||
@@ -106,8 +106,6 @@ type RawDefaultDNSRule struct {
|
|||||||
InterfaceAddress *badjson.TypedMap[string, badoption.Listable[*badoption.Prefixable]] `json:"interface_address,omitempty"`
|
InterfaceAddress *badjson.TypedMap[string, badoption.Listable[*badoption.Prefixable]] `json:"interface_address,omitempty"`
|
||||||
NetworkInterfaceAddress *badjson.TypedMap[InterfaceType, badoption.Listable[*badoption.Prefixable]] `json:"network_interface_address,omitempty"`
|
NetworkInterfaceAddress *badjson.TypedMap[InterfaceType, badoption.Listable[*badoption.Prefixable]] `json:"network_interface_address,omitempty"`
|
||||||
DefaultInterfaceAddress badoption.Listable[*badoption.Prefixable] `json:"default_interface_address,omitempty"`
|
DefaultInterfaceAddress badoption.Listable[*badoption.Prefixable] `json:"default_interface_address,omitempty"`
|
||||||
SourceMACAddress badoption.Listable[string] `json:"source_mac_address,omitempty"`
|
|
||||||
SourceHostname badoption.Listable[string] `json:"source_hostname,omitempty"`
|
|
||||||
RuleSet badoption.Listable[string] `json:"rule_set,omitempty"`
|
RuleSet badoption.Listable[string] `json:"rule_set,omitempty"`
|
||||||
RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"`
|
RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"`
|
||||||
RuleSetIPCIDRAcceptEmpty bool `json:"rule_set_ip_cidr_accept_empty,omitempty"`
|
RuleSetIPCIDRAcceptEmpty bool `json:"rule_set_ip_cidr_accept_empty,omitempty"`
|
||||||
|
|||||||
@@ -36,10 +36,6 @@ type TailscaleDNSServerOptions struct {
|
|||||||
AcceptDefaultResolvers bool `json:"accept_default_resolvers,omitempty"`
|
AcceptDefaultResolvers bool `json:"accept_default_resolvers,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type TailscaleCertificateProviderOptions struct {
|
|
||||||
Endpoint string `json:"endpoint,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type DERPServiceOptions struct {
|
type DERPServiceOptions struct {
|
||||||
ListenOptions
|
ListenOptions
|
||||||
InboundTLSOptionsContainer
|
InboundTLSOptionsContainer
|
||||||
|
|||||||
@@ -28,13 +28,9 @@ type InboundTLSOptions struct {
|
|||||||
KeyPath string `json:"key_path,omitempty"`
|
KeyPath string `json:"key_path,omitempty"`
|
||||||
KernelTx bool `json:"kernel_tx,omitempty"`
|
KernelTx bool `json:"kernel_tx,omitempty"`
|
||||||
KernelRx bool `json:"kernel_rx,omitempty"`
|
KernelRx bool `json:"kernel_rx,omitempty"`
|
||||||
CertificateProvider *CertificateProviderOptions `json:"certificate_provider,omitempty"`
|
ACME *InboundACMEOptions `json:"acme,omitempty"`
|
||||||
|
ECH *InboundECHOptions `json:"ech,omitempty"`
|
||||||
// Deprecated: use certificate_provider
|
Reality *InboundRealityOptions `json:"reality,omitempty"`
|
||||||
ACME *InboundACMEOptions `json:"acme,omitempty"`
|
|
||||||
|
|
||||||
ECH *InboundECHOptions `json:"ech,omitempty"`
|
|
||||||
Reality *InboundRealityOptions `json:"reality,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ClientAuthType tls.ClientAuthType
|
type ClientAuthType tls.ClientAuthType
|
||||||
|
|||||||
@@ -39,8 +39,6 @@ type TunInboundOptions struct {
|
|||||||
IncludeAndroidUser badoption.Listable[int] `json:"include_android_user,omitempty"`
|
IncludeAndroidUser badoption.Listable[int] `json:"include_android_user,omitempty"`
|
||||||
IncludePackage badoption.Listable[string] `json:"include_package,omitempty"`
|
IncludePackage badoption.Listable[string] `json:"include_package,omitempty"`
|
||||||
ExcludePackage badoption.Listable[string] `json:"exclude_package,omitempty"`
|
ExcludePackage badoption.Listable[string] `json:"exclude_package,omitempty"`
|
||||||
IncludeMACAddress badoption.Listable[string] `json:"include_mac_address,omitempty"`
|
|
||||||
ExcludeMACAddress badoption.Listable[string] `json:"exclude_mac_address,omitempty"`
|
|
||||||
UDPTimeout UDPTimeoutCompat `json:"udp_timeout,omitempty"`
|
UDPTimeout UDPTimeoutCompat `json:"udp_timeout,omitempty"`
|
||||||
Stack string `json:"stack,omitempty"`
|
Stack string `json:"stack,omitempty"`
|
||||||
Platform *TunPlatformOptions `json:"platform,omitempty"`
|
Platform *TunPlatformOptions `json:"platform,omitempty"`
|
||||||
|
|||||||
176
protocol/cloudflare/inbound.go
Normal file
176
protocol/cloudflare/inbound.go
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
//go:build with_cloudflared
|
||||||
|
|
||||||
|
package cloudflare
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
cloudflared "github.com/sagernet/sing-cloudflared"
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/adapter/inbound"
|
||||||
|
boxDialer "github.com/sagernet/sing-box/common/dialer"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
"github.com/sagernet/sing/common/bufio"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/json/badoption"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
"github.com/sagernet/sing/common/pipe"
|
||||||
|
tun "github.com/sagernet/sing-tun"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RegisterInbound(registry *inbound.Registry) {
|
||||||
|
inbound.Register[option.CloudflaredInboundOptions](registry, C.TypeCloudflared, NewInbound)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.CloudflaredInboundOptions) (adapter.Inbound, error) {
|
||||||
|
controlDialer, err := boxDialer.NewWithOptions(boxDialer.Options{
|
||||||
|
Context: ctx,
|
||||||
|
Options: options.ControlDialer,
|
||||||
|
RemoteIsDomain: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "build cloudflared control dialer")
|
||||||
|
}
|
||||||
|
tunnelDialer, err := boxDialer.NewWithOptions(boxDialer.Options{
|
||||||
|
Context: ctx,
|
||||||
|
Options: options.TunnelDialer,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "build cloudflared tunnel dialer")
|
||||||
|
}
|
||||||
|
|
||||||
|
service, err := cloudflared.NewService(cloudflared.ServiceOptions{
|
||||||
|
Logger: logger,
|
||||||
|
ConnectionDialer: &routerDialer{router: router, tag: tag},
|
||||||
|
ControlDialer: controlDialer,
|
||||||
|
TunnelDialer: tunnelDialer,
|
||||||
|
ICMPHandler: &icmpRouterHandler{router: router, tag: tag},
|
||||||
|
ConnContext: func(ctx context.Context) context.Context {
|
||||||
|
return adapter.WithContext(ctx, &adapter.InboundContext{
|
||||||
|
Inbound: tag,
|
||||||
|
InboundType: C.TypeCloudflared,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
Token: options.Token,
|
||||||
|
HAConnections: options.HAConnections,
|
||||||
|
Protocol: options.Protocol,
|
||||||
|
PostQuantum: options.PostQuantum,
|
||||||
|
EdgeIPVersion: options.EdgeIPVersion,
|
||||||
|
DatagramVersion: options.DatagramVersion,
|
||||||
|
GracePeriod: resolveGracePeriod(options.GracePeriod),
|
||||||
|
Region: options.Region,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Inbound{
|
||||||
|
Adapter: inbound.NewAdapter(C.TypeCloudflared, tag),
|
||||||
|
service: service,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Inbound struct {
|
||||||
|
inbound.Adapter
|
||||||
|
service *cloudflared.Service
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Inbound) Start(stage adapter.StartStage) error {
|
||||||
|
if stage != adapter.StartStateStart {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return i.service.Start()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Inbound) Close() error {
|
||||||
|
return i.service.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolveGracePeriod(value *badoption.Duration) time.Duration {
|
||||||
|
if value == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return time.Duration(*value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// routerDialer bridges N.Dialer to the sing-box router for origin connections.
|
||||||
|
type routerDialer struct {
|
||||||
|
router adapter.Router
|
||||||
|
tag string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *routerDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||||
|
input, output := pipe.Pipe()
|
||||||
|
done := make(chan struct{})
|
||||||
|
metadata := adapter.InboundContext{
|
||||||
|
Inbound: d.tag,
|
||||||
|
InboundType: C.TypeCloudflared,
|
||||||
|
Network: N.NetworkTCP,
|
||||||
|
Destination: destination,
|
||||||
|
}
|
||||||
|
var closeOnce sync.Once
|
||||||
|
closePipe := func() {
|
||||||
|
closeOnce.Do(func() {
|
||||||
|
common.Close(input, output)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
go d.router.RouteConnectionEx(ctx, output, metadata, N.OnceClose(func(it error) {
|
||||||
|
closePipe()
|
||||||
|
close(done)
|
||||||
|
}))
|
||||||
|
return input, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *routerDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||||
|
originDialer, ok := d.router.(routedOriginPacketDialer)
|
||||||
|
if !ok {
|
||||||
|
return nil, E.New("router does not support cloudflare routed packet dialing")
|
||||||
|
}
|
||||||
|
packetConn, err := originDialer.DialRoutePacketConnection(ctx, adapter.InboundContext{
|
||||||
|
Inbound: d.tag,
|
||||||
|
InboundType: C.TypeCloudflared,
|
||||||
|
Network: N.NetworkUDP,
|
||||||
|
Destination: destination,
|
||||||
|
UDPConnect: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return bufio.NewNetPacketConn(packetConn), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type routedOriginPacketDialer interface {
|
||||||
|
DialRoutePacketConnection(ctx context.Context, metadata adapter.InboundContext) (N.PacketConn, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// icmpRouterHandler bridges cloudflared.ICMPHandler to router.PreMatch.
|
||||||
|
type icmpRouterHandler struct {
|
||||||
|
router adapter.Router
|
||||||
|
tag string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *icmpRouterHandler) RouteICMPConnection(ctx context.Context, session tun.DirectRouteSession, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
|
||||||
|
var ipVersion uint8
|
||||||
|
if session.Source.Is4() {
|
||||||
|
ipVersion = 4
|
||||||
|
} else {
|
||||||
|
ipVersion = 6
|
||||||
|
}
|
||||||
|
metadata := adapter.InboundContext{
|
||||||
|
Inbound: h.tag,
|
||||||
|
InboundType: C.TypeCloudflared,
|
||||||
|
IPVersion: ipVersion,
|
||||||
|
Network: N.NetworkICMP,
|
||||||
|
Source: M.SocksaddrFrom(session.Source, 0),
|
||||||
|
Destination: M.SocksaddrFrom(session.Destination, 0),
|
||||||
|
OriginDestination: M.SocksaddrFrom(session.Destination, 0),
|
||||||
|
}
|
||||||
|
return h.router.PreMatch(metadata, routeContext, timeout, false)
|
||||||
|
}
|
||||||
@@ -29,7 +29,10 @@ import (
|
|||||||
"golang.org/x/net/http2/h2c"
|
"golang.org/x/net/http2/h2c"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ConfigureHTTP3ListenerFunc func(ctx context.Context, logger logger.Logger, listener *listener.Listener, handler http.Handler, tlsConfig tls.ServerConfig, options option.NaiveInboundOptions) (io.Closer, error)
|
var (
|
||||||
|
ConfigureHTTP3ListenerFunc func(ctx context.Context, logger logger.Logger, listener *listener.Listener, handler http.Handler, tlsConfig tls.ServerConfig, options option.NaiveInboundOptions) (io.Closer, error)
|
||||||
|
WrapError func(error) error
|
||||||
|
)
|
||||||
|
|
||||||
func RegisterInbound(registry *inbound.Registry) {
|
func RegisterInbound(registry *inbound.Registry) {
|
||||||
inbound.Register[option.NaiveInboundOptions](registry, C.TypeNaive, NewInbound)
|
inbound.Register[option.NaiveInboundOptions](registry, C.TypeNaive, NewInbound)
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ func (p *paddingConn) writeWithPadding(writer io.Writer, data []byte) (n int, er
|
|||||||
binary.BigEndian.PutUint16(header, uint16(len(data)))
|
binary.BigEndian.PutUint16(header, uint16(len(data)))
|
||||||
header[2] = byte(paddingSize)
|
header[2] = byte(paddingSize)
|
||||||
common.Must1(buffer.Write(data))
|
common.Must1(buffer.Write(data))
|
||||||
buffer.Extend(paddingSize)
|
common.Must(buffer.WriteZeroN(paddingSize))
|
||||||
_, err = writer.Write(buffer.Bytes())
|
_, err = writer.Write(buffer.Bytes())
|
||||||
if err == nil {
|
if err == nil {
|
||||||
n = len(data)
|
n = len(data)
|
||||||
@@ -117,7 +117,7 @@ func (p *paddingConn) writeBufferWithPadding(writer io.Writer, buffer *buf.Buffe
|
|||||||
header := buffer.ExtendHeader(3)
|
header := buffer.ExtendHeader(3)
|
||||||
binary.BigEndian.PutUint16(header, uint16(bufferLen))
|
binary.BigEndian.PutUint16(header, uint16(bufferLen))
|
||||||
header[2] = byte(paddingSize)
|
header[2] = byte(paddingSize)
|
||||||
buffer.Extend(paddingSize)
|
common.Must(buffer.WriteZeroN(paddingSize))
|
||||||
p.writePadding++
|
p.writePadding++
|
||||||
}
|
}
|
||||||
return common.Error(writer.Write(buffer.Bytes()))
|
return common.Error(writer.Write(buffer.Bytes()))
|
||||||
@@ -179,18 +179,18 @@ type naiveConn struct {
|
|||||||
|
|
||||||
func (c *naiveConn) Read(p []byte) (n int, err error) {
|
func (c *naiveConn) Read(p []byte) (n int, err error) {
|
||||||
n, err = c.readWithPadding(c.Conn, p)
|
n, err = c.readWithPadding(c.Conn, p)
|
||||||
return n, baderror.WrapH2(err)
|
return n, wrapError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *naiveConn) Write(p []byte) (n int, err error) {
|
func (c *naiveConn) Write(p []byte) (n int, err error) {
|
||||||
n, err = c.writeChunked(c.Conn, p)
|
n, err = c.writeChunked(c.Conn, p)
|
||||||
return n, baderror.WrapH2(err)
|
return n, wrapError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *naiveConn) WriteBuffer(buffer *buf.Buffer) error {
|
func (c *naiveConn) WriteBuffer(buffer *buf.Buffer) error {
|
||||||
defer buffer.Release()
|
defer buffer.Release()
|
||||||
err := c.writeBufferWithPadding(c.Conn, buffer)
|
err := c.writeBufferWithPadding(c.Conn, buffer)
|
||||||
return baderror.WrapH2(err)
|
return wrapError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *naiveConn) FrontHeadroom() int { return c.frontHeadroom() }
|
func (c *naiveConn) FrontHeadroom() int { return c.frontHeadroom() }
|
||||||
@@ -210,7 +210,7 @@ type naiveH2Conn struct {
|
|||||||
|
|
||||||
func (c *naiveH2Conn) Read(p []byte) (n int, err error) {
|
func (c *naiveH2Conn) Read(p []byte) (n int, err error) {
|
||||||
n, err = c.readWithPadding(c.reader, p)
|
n, err = c.readWithPadding(c.reader, p)
|
||||||
return n, baderror.WrapH2(err)
|
return n, wrapError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *naiveH2Conn) Write(p []byte) (n int, err error) {
|
func (c *naiveH2Conn) Write(p []byte) (n int, err error) {
|
||||||
@@ -218,7 +218,7 @@ func (c *naiveH2Conn) Write(p []byte) (n int, err error) {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
c.flusher.Flush()
|
c.flusher.Flush()
|
||||||
}
|
}
|
||||||
return n, baderror.WrapH2(err)
|
return n, wrapError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *naiveH2Conn) WriteBuffer(buffer *buf.Buffer) error {
|
func (c *naiveH2Conn) WriteBuffer(buffer *buf.Buffer) error {
|
||||||
@@ -227,7 +227,15 @@ func (c *naiveH2Conn) WriteBuffer(buffer *buf.Buffer) error {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
c.flusher.Flush()
|
c.flusher.Flush()
|
||||||
}
|
}
|
||||||
return baderror.WrapH2(err)
|
return wrapError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func wrapError(err error) error {
|
||||||
|
err = baderror.WrapH2(err)
|
||||||
|
if WrapError != nil {
|
||||||
|
err = WrapError(err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *naiveH2Conn) Close() error {
|
func (c *naiveH2Conn) Close() error {
|
||||||
|
|||||||
@@ -124,4 +124,5 @@ func init() {
|
|||||||
|
|
||||||
return quicListener, nil
|
return quicListener, nil
|
||||||
}
|
}
|
||||||
|
naive.WrapError = qtls.WrapError
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,98 +0,0 @@
|
|||||||
//go:build with_gvisor
|
|
||||||
|
|
||||||
package tailscale
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/tls"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
"github.com/sagernet/sing-box/adapter/certificate"
|
|
||||||
"github.com/sagernet/sing-box/common/dialer"
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
"github.com/sagernet/sing/service"
|
|
||||||
"github.com/sagernet/tailscale/client/local"
|
|
||||||
)
|
|
||||||
|
|
||||||
func RegisterCertificateProvider(registry *certificate.Registry) {
|
|
||||||
certificate.Register[option.TailscaleCertificateProviderOptions](registry, C.TypeTailscale, NewCertificateProvider)
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ adapter.CertificateProviderService = (*CertificateProvider)(nil)
|
|
||||||
|
|
||||||
type CertificateProvider struct {
|
|
||||||
certificate.Adapter
|
|
||||||
endpointTag string
|
|
||||||
endpoint *Endpoint
|
|
||||||
dialer N.Dialer
|
|
||||||
localClient *local.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewCertificateProvider(ctx context.Context, _ log.ContextLogger, tag string, options option.TailscaleCertificateProviderOptions) (adapter.CertificateProviderService, error) {
|
|
||||||
if options.Endpoint == "" {
|
|
||||||
return nil, E.New("missing tailscale endpoint tag")
|
|
||||||
}
|
|
||||||
endpointManager := service.FromContext[adapter.EndpointManager](ctx)
|
|
||||||
if endpointManager == nil {
|
|
||||||
return nil, E.New("missing endpoint manager in context")
|
|
||||||
}
|
|
||||||
rawEndpoint, loaded := endpointManager.Get(options.Endpoint)
|
|
||||||
if !loaded {
|
|
||||||
return nil, E.New("endpoint not found: ", options.Endpoint)
|
|
||||||
}
|
|
||||||
endpoint, isTailscale := rawEndpoint.(*Endpoint)
|
|
||||||
if !isTailscale {
|
|
||||||
return nil, E.New("endpoint is not Tailscale: ", options.Endpoint)
|
|
||||||
}
|
|
||||||
providerDialer, err := dialer.NewWithOptions(dialer.Options{
|
|
||||||
Context: ctx,
|
|
||||||
Options: option.DialerOptions{},
|
|
||||||
RemoteIsDomain: true,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "create tailscale certificate provider dialer")
|
|
||||||
}
|
|
||||||
return &CertificateProvider{
|
|
||||||
Adapter: certificate.NewAdapter(C.TypeTailscale, tag),
|
|
||||||
endpointTag: options.Endpoint,
|
|
||||||
endpoint: endpoint,
|
|
||||||
dialer: providerDialer,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *CertificateProvider) Start(stage adapter.StartStage) error {
|
|
||||||
if stage != adapter.StartStateStart {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
localClient, err := p.endpoint.Server().LocalClient()
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "initialize tailscale local client for endpoint ", p.endpointTag)
|
|
||||||
}
|
|
||||||
originalDial := localClient.Dial
|
|
||||||
localClient.Dial = func(ctx context.Context, network, addr string) (net.Conn, error) {
|
|
||||||
if originalDial != nil && addr == "local-tailscaled.sock:80" {
|
|
||||||
return originalDial(ctx, network, addr)
|
|
||||||
}
|
|
||||||
return p.dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
|
||||||
}
|
|
||||||
p.localClient = localClient
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *CertificateProvider) Close() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *CertificateProvider) GetCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
|
||||||
localClient := p.localClient
|
|
||||||
if localClient == nil {
|
|
||||||
return nil, E.New("Tailscale is not ready yet")
|
|
||||||
}
|
|
||||||
return localClient.GetCertificate(clientHello)
|
|
||||||
}
|
|
||||||
@@ -160,22 +160,6 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
|
|||||||
if nfQueue == 0 {
|
if nfQueue == 0 {
|
||||||
nfQueue = tun.DefaultAutoRedirectNFQueue
|
nfQueue = tun.DefaultAutoRedirectNFQueue
|
||||||
}
|
}
|
||||||
var includeMACAddress []net.HardwareAddr
|
|
||||||
for i, macString := range options.IncludeMACAddress {
|
|
||||||
mac, macErr := net.ParseMAC(macString)
|
|
||||||
if macErr != nil {
|
|
||||||
return nil, E.Cause(macErr, "parse include_mac_address[", i, "]")
|
|
||||||
}
|
|
||||||
includeMACAddress = append(includeMACAddress, mac)
|
|
||||||
}
|
|
||||||
var excludeMACAddress []net.HardwareAddr
|
|
||||||
for i, macString := range options.ExcludeMACAddress {
|
|
||||||
mac, macErr := net.ParseMAC(macString)
|
|
||||||
if macErr != nil {
|
|
||||||
return nil, E.Cause(macErr, "parse exclude_mac_address[", i, "]")
|
|
||||||
}
|
|
||||||
excludeMACAddress = append(excludeMACAddress, mac)
|
|
||||||
}
|
|
||||||
networkManager := service.FromContext[adapter.NetworkManager](ctx)
|
networkManager := service.FromContext[adapter.NetworkManager](ctx)
|
||||||
multiPendingPackets := C.IsDarwin && ((options.Stack == "gvisor" && tunMTU < 32768) || (options.Stack != "gvisor" && options.MTU <= 9000))
|
multiPendingPackets := C.IsDarwin && ((options.Stack == "gvisor" && tunMTU < 32768) || (options.Stack != "gvisor" && options.MTU <= 9000))
|
||||||
inbound := &Inbound{
|
inbound := &Inbound{
|
||||||
@@ -213,8 +197,6 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
|
|||||||
IncludeAndroidUser: options.IncludeAndroidUser,
|
IncludeAndroidUser: options.IncludeAndroidUser,
|
||||||
IncludePackage: options.IncludePackage,
|
IncludePackage: options.IncludePackage,
|
||||||
ExcludePackage: options.ExcludePackage,
|
ExcludePackage: options.ExcludePackage,
|
||||||
IncludeMACAddress: includeMACAddress,
|
|
||||||
ExcludeMACAddress: excludeMACAddress,
|
|
||||||
InterfaceMonitor: networkManager.InterfaceMonitor(),
|
InterfaceMonitor: networkManager.InterfaceMonitor(),
|
||||||
EXP_MultiPendingPackets: multiPendingPackets,
|
EXP_MultiPendingPackets: multiPendingPackets,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,with_naive_outbound,badlinkname,tfogo_checklinkname0
|
with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,with_cloudflared,with_naive_outbound,badlinkname,tfogo_checklinkname0
|
||||||
@@ -1 +1 @@
|
|||||||
with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,badlinkname,tfogo_checklinkname0
|
with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,with_cloudflared,badlinkname,tfogo_checklinkname0
|
||||||
@@ -1 +1 @@
|
|||||||
with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,with_naive_outbound,with_purego,badlinkname,tfogo_checklinkname0
|
with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,with_cloudflared,with_naive_outbound,with_purego,badlinkname,tfogo_checklinkname0
|
||||||
109
route/dial.go
Normal file
109
route/dial.go
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
package route
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/dialer"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
R "github.com/sagernet/sing-box/route/rule"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
"github.com/sagernet/sing/common/buf"
|
||||||
|
"github.com/sagernet/sing/common/bufio"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DialRoutePacketConnection dials a routed connected UDP packet connection for metadata.
|
||||||
|
func (r *Router) DialRoutePacketConnection(ctx context.Context, metadata adapter.InboundContext) (N.PacketConn, error) {
|
||||||
|
metadata.Network = N.NetworkUDP
|
||||||
|
metadata.UDPConnect = true
|
||||||
|
ctx = adapter.WithContext(ctx, &metadata)
|
||||||
|
|
||||||
|
selectedRule, selectedOutbound, err := r.selectRoutedOutbound(ctx, &metadata, N.NetworkUDP)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var remoteConn net.Conn
|
||||||
|
if len(metadata.DestinationAddresses) > 0 || metadata.Destination.IsIP() {
|
||||||
|
remoteConn, err = dialer.DialSerialNetwork(
|
||||||
|
ctx,
|
||||||
|
selectedOutbound,
|
||||||
|
N.NetworkUDP,
|
||||||
|
metadata.Destination,
|
||||||
|
metadata.DestinationAddresses,
|
||||||
|
metadata.NetworkStrategy,
|
||||||
|
metadata.NetworkType,
|
||||||
|
metadata.FallbackNetworkType,
|
||||||
|
metadata.FallbackDelay,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
remoteConn, err = selectedOutbound.DialContext(ctx, N.NetworkUDP, metadata.Destination)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var packetConn N.PacketConn = bufio.NewUnbindPacketConn(remoteConn)
|
||||||
|
for _, tracker := range r.trackers {
|
||||||
|
packetConn = tracker.RoutedPacketConnection(ctx, packetConn, metadata, selectedRule, selectedOutbound)
|
||||||
|
}
|
||||||
|
if metadata.FakeIP {
|
||||||
|
packetConn = bufio.NewNATPacketConn(bufio.NewNetPacketConn(packetConn), metadata.OriginDestination, metadata.Destination)
|
||||||
|
}
|
||||||
|
return packetConn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) selectRoutedOutbound(
|
||||||
|
ctx context.Context,
|
||||||
|
metadata *adapter.InboundContext,
|
||||||
|
network string,
|
||||||
|
) (adapter.Rule, adapter.Outbound, error) {
|
||||||
|
selectedRule, _, buffers, packetBuffers, err := r.matchRule(ctx, metadata, false, false, nil, nil)
|
||||||
|
if len(buffers) > 0 {
|
||||||
|
buf.ReleaseMulti(buffers)
|
||||||
|
}
|
||||||
|
if len(packetBuffers) > 0 {
|
||||||
|
N.ReleaseMultiPacketBuffer(packetBuffers)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var selectedOutbound adapter.Outbound
|
||||||
|
if selectedRule != nil {
|
||||||
|
switch action := selectedRule.Action().(type) {
|
||||||
|
case *R.RuleActionRoute:
|
||||||
|
var loaded bool
|
||||||
|
selectedOutbound, loaded = r.outbound.Outbound(action.Outbound)
|
||||||
|
if !loaded {
|
||||||
|
return nil, nil, E.New("outbound not found: ", action.Outbound)
|
||||||
|
}
|
||||||
|
case *R.RuleActionBypass:
|
||||||
|
if action.Outbound != "" {
|
||||||
|
var loaded bool
|
||||||
|
selectedOutbound, loaded = r.outbound.Outbound(action.Outbound)
|
||||||
|
if !loaded {
|
||||||
|
return nil, nil, E.New("outbound not found: ", action.Outbound)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case *R.RuleActionReject:
|
||||||
|
if action.Method == C.RuleActionRejectMethodReply {
|
||||||
|
return nil, nil, E.New("reject method `reply` is not supported for dialed connections")
|
||||||
|
}
|
||||||
|
return nil, nil, action.Error(ctx)
|
||||||
|
case *R.RuleActionHijackDNS:
|
||||||
|
return nil, nil, E.New("DNS hijack is not supported for dialed connections")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if selectedOutbound == nil {
|
||||||
|
selectedOutbound = r.outbound.Default()
|
||||||
|
}
|
||||||
|
if !common.Contains(selectedOutbound.Network(), network) {
|
||||||
|
return nil, nil, E.New(network, " is not supported by outbound: ", selectedOutbound.Tag())
|
||||||
|
}
|
||||||
|
return selectedRule, selectedOutbound, nil
|
||||||
|
}
|
||||||
@@ -1,239 +0,0 @@
|
|||||||
//go:build darwin
|
|
||||||
|
|
||||||
package route
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"net/netip"
|
|
||||||
"os"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/fswatch"
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
"github.com/sagernet/sing/common/buf"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
"github.com/sagernet/sing/common/logger"
|
|
||||||
|
|
||||||
"golang.org/x/net/route"
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
)
|
|
||||||
|
|
||||||
var defaultLeaseFiles = []string{
|
|
||||||
"/var/db/dhcpd_leases",
|
|
||||||
"/tmp/dhcp.leases",
|
|
||||||
}
|
|
||||||
|
|
||||||
type neighborResolver struct {
|
|
||||||
logger logger.ContextLogger
|
|
||||||
leaseFiles []string
|
|
||||||
access sync.RWMutex
|
|
||||||
neighborIPToMAC map[netip.Addr]net.HardwareAddr
|
|
||||||
leaseIPToMAC map[netip.Addr]net.HardwareAddr
|
|
||||||
ipToHostname map[netip.Addr]string
|
|
||||||
macToHostname map[string]string
|
|
||||||
watcher *fswatch.Watcher
|
|
||||||
done chan struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newNeighborResolver(resolverLogger logger.ContextLogger, leaseFiles []string) (adapter.NeighborResolver, error) {
|
|
||||||
if len(leaseFiles) == 0 {
|
|
||||||
for _, path := range defaultLeaseFiles {
|
|
||||||
info, err := os.Stat(path)
|
|
||||||
if err == nil && info.Size() > 0 {
|
|
||||||
leaseFiles = append(leaseFiles, path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &neighborResolver{
|
|
||||||
logger: resolverLogger,
|
|
||||||
leaseFiles: leaseFiles,
|
|
||||||
neighborIPToMAC: make(map[netip.Addr]net.HardwareAddr),
|
|
||||||
leaseIPToMAC: make(map[netip.Addr]net.HardwareAddr),
|
|
||||||
ipToHostname: make(map[netip.Addr]string),
|
|
||||||
macToHostname: make(map[string]string),
|
|
||||||
done: make(chan struct{}),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *neighborResolver) Start() error {
|
|
||||||
err := r.loadNeighborTable()
|
|
||||||
if err != nil {
|
|
||||||
r.logger.Warn(E.Cause(err, "load neighbor table"))
|
|
||||||
}
|
|
||||||
r.doReloadLeaseFiles()
|
|
||||||
go r.subscribeNeighborUpdates()
|
|
||||||
if len(r.leaseFiles) > 0 {
|
|
||||||
watcher, err := fswatch.NewWatcher(fswatch.Options{
|
|
||||||
Path: r.leaseFiles,
|
|
||||||
Logger: r.logger,
|
|
||||||
Callback: func(_ string) {
|
|
||||||
r.doReloadLeaseFiles()
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
r.logger.Warn(E.Cause(err, "create lease file watcher"))
|
|
||||||
} else {
|
|
||||||
r.watcher = watcher
|
|
||||||
err = watcher.Start()
|
|
||||||
if err != nil {
|
|
||||||
r.logger.Warn(E.Cause(err, "start lease file watcher"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *neighborResolver) Close() error {
|
|
||||||
close(r.done)
|
|
||||||
if r.watcher != nil {
|
|
||||||
return r.watcher.Close()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *neighborResolver) LookupMAC(address netip.Addr) (net.HardwareAddr, bool) {
|
|
||||||
r.access.RLock()
|
|
||||||
defer r.access.RUnlock()
|
|
||||||
mac, found := r.neighborIPToMAC[address]
|
|
||||||
if found {
|
|
||||||
return mac, true
|
|
||||||
}
|
|
||||||
mac, found = r.leaseIPToMAC[address]
|
|
||||||
if found {
|
|
||||||
return mac, true
|
|
||||||
}
|
|
||||||
mac, found = extractMACFromEUI64(address)
|
|
||||||
if found {
|
|
||||||
return mac, true
|
|
||||||
}
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *neighborResolver) LookupHostname(address netip.Addr) (string, bool) {
|
|
||||||
r.access.RLock()
|
|
||||||
defer r.access.RUnlock()
|
|
||||||
hostname, found := r.ipToHostname[address]
|
|
||||||
if found {
|
|
||||||
return hostname, true
|
|
||||||
}
|
|
||||||
mac, macFound := r.neighborIPToMAC[address]
|
|
||||||
if !macFound {
|
|
||||||
mac, macFound = r.leaseIPToMAC[address]
|
|
||||||
}
|
|
||||||
if !macFound {
|
|
||||||
mac, macFound = extractMACFromEUI64(address)
|
|
||||||
}
|
|
||||||
if macFound {
|
|
||||||
hostname, found = r.macToHostname[mac.String()]
|
|
||||||
if found {
|
|
||||||
return hostname, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *neighborResolver) loadNeighborTable() error {
|
|
||||||
entries, err := ReadNeighborEntries()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r.access.Lock()
|
|
||||||
defer r.access.Unlock()
|
|
||||||
for _, entry := range entries {
|
|
||||||
r.neighborIPToMAC[entry.Address] = entry.MACAddress
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *neighborResolver) subscribeNeighborUpdates() {
|
|
||||||
routeSocket, err := unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW, 0)
|
|
||||||
if err != nil {
|
|
||||||
r.logger.Warn(E.Cause(err, "subscribe neighbor updates"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = unix.SetNonblock(routeSocket, true)
|
|
||||||
if err != nil {
|
|
||||||
unix.Close(routeSocket)
|
|
||||||
r.logger.Warn(E.Cause(err, "set route socket nonblock"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
routeSocketFile := os.NewFile(uintptr(routeSocket), "route")
|
|
||||||
defer routeSocketFile.Close()
|
|
||||||
buffer := buf.NewPacket()
|
|
||||||
defer buffer.Release()
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-r.done:
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
err = setReadDeadline(routeSocketFile, 3*time.Second)
|
|
||||||
if err != nil {
|
|
||||||
r.logger.Warn(E.Cause(err, "set route socket read deadline"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
n, err := routeSocketFile.Read(buffer.FreeBytes())
|
|
||||||
if err != nil {
|
|
||||||
if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case <-r.done:
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
r.logger.Warn(E.Cause(err, "receive neighbor update"))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
messages, err := route.ParseRIB(route.RIBTypeRoute, buffer.FreeBytes()[:n])
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for _, message := range messages {
|
|
||||||
routeMessage, isRouteMessage := message.(*route.RouteMessage)
|
|
||||||
if !isRouteMessage {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if routeMessage.Flags&unix.RTF_LLINFO == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
address, mac, isDelete, ok := ParseRouteNeighborMessage(routeMessage)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
r.access.Lock()
|
|
||||||
if isDelete {
|
|
||||||
delete(r.neighborIPToMAC, address)
|
|
||||||
} else {
|
|
||||||
r.neighborIPToMAC[address] = mac
|
|
||||||
}
|
|
||||||
r.access.Unlock()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *neighborResolver) doReloadLeaseFiles() {
|
|
||||||
leaseIPToMAC, ipToHostname, macToHostname := ReloadLeaseFiles(r.leaseFiles)
|
|
||||||
r.access.Lock()
|
|
||||||
r.leaseIPToMAC = leaseIPToMAC
|
|
||||||
r.ipToHostname = ipToHostname
|
|
||||||
r.macToHostname = macToHostname
|
|
||||||
r.access.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func setReadDeadline(file *os.File, timeout time.Duration) error {
|
|
||||||
rawConn, err := file.SyscallConn()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var controlErr error
|
|
||||||
err = rawConn.Control(func(fd uintptr) {
|
|
||||||
tv := unix.NsecToTimeval(int64(timeout))
|
|
||||||
controlErr = unix.SetsockoptTimeval(int(fd), unix.SOL_SOCKET, unix.SO_RCVTIMEO, &tv)
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return controlErr
|
|
||||||
}
|
|
||||||
@@ -1,386 +0,0 @@
|
|||||||
package route
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"encoding/hex"
|
|
||||||
"net"
|
|
||||||
"net/netip"
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func parseLeaseFile(path string, ipToMAC map[netip.Addr]net.HardwareAddr, ipToHostname map[netip.Addr]string, macToHostname map[string]string) {
|
|
||||||
file, err := os.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
if strings.HasSuffix(path, "dhcpd_leases") {
|
|
||||||
parseBootpdLeases(file, ipToMAC, ipToHostname, macToHostname)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if strings.HasSuffix(path, "kea-leases4.csv") {
|
|
||||||
parseKeaCSV4(file, ipToMAC, ipToHostname, macToHostname)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if strings.HasSuffix(path, "kea-leases6.csv") {
|
|
||||||
parseKeaCSV6(file, ipToMAC, ipToHostname, macToHostname)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if strings.HasSuffix(path, "dhcpd.leases") {
|
|
||||||
parseISCDhcpd(file, ipToMAC, ipToHostname, macToHostname)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
parseDnsmasqOdhcpd(file, ipToMAC, ipToHostname, macToHostname)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ReloadLeaseFiles(leaseFiles []string) (leaseIPToMAC map[netip.Addr]net.HardwareAddr, ipToHostname map[netip.Addr]string, macToHostname map[string]string) {
|
|
||||||
leaseIPToMAC = make(map[netip.Addr]net.HardwareAddr)
|
|
||||||
ipToHostname = make(map[netip.Addr]string)
|
|
||||||
macToHostname = make(map[string]string)
|
|
||||||
for _, path := range leaseFiles {
|
|
||||||
parseLeaseFile(path, leaseIPToMAC, ipToHostname, macToHostname)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseDnsmasqOdhcpd(file *os.File, ipToMAC map[netip.Addr]net.HardwareAddr, ipToHostname map[netip.Addr]string, macToHostname map[string]string) {
|
|
||||||
now := time.Now().Unix()
|
|
||||||
scanner := bufio.NewScanner(file)
|
|
||||||
for scanner.Scan() {
|
|
||||||
line := scanner.Text()
|
|
||||||
if strings.HasPrefix(line, "duid ") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(line, "# ") {
|
|
||||||
parseOdhcpdLine(line[2:], ipToMAC, ipToHostname, macToHostname)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
fields := strings.Fields(line)
|
|
||||||
if len(fields) < 4 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
expiry, err := strconv.ParseInt(fields[0], 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if expiry != 0 && expiry < now {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if strings.Contains(fields[1], ":") {
|
|
||||||
mac, macErr := net.ParseMAC(fields[1])
|
|
||||||
if macErr != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
address, addrOK := netip.AddrFromSlice(net.ParseIP(fields[2]))
|
|
||||||
if !addrOK {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
address = address.Unmap()
|
|
||||||
ipToMAC[address] = mac
|
|
||||||
hostname := fields[3]
|
|
||||||
if hostname != "*" {
|
|
||||||
ipToHostname[address] = hostname
|
|
||||||
macToHostname[mac.String()] = hostname
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
var mac net.HardwareAddr
|
|
||||||
if len(fields) >= 5 {
|
|
||||||
duid, duidErr := parseDUID(fields[4])
|
|
||||||
if duidErr == nil {
|
|
||||||
mac, _ = extractMACFromDUID(duid)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
address, addrOK := netip.AddrFromSlice(net.ParseIP(fields[2]))
|
|
||||||
if !addrOK {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
address = address.Unmap()
|
|
||||||
if mac != nil {
|
|
||||||
ipToMAC[address] = mac
|
|
||||||
}
|
|
||||||
hostname := fields[3]
|
|
||||||
if hostname != "*" {
|
|
||||||
ipToHostname[address] = hostname
|
|
||||||
if mac != nil {
|
|
||||||
macToHostname[mac.String()] = hostname
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseOdhcpdLine(line string, ipToMAC map[netip.Addr]net.HardwareAddr, ipToHostname map[netip.Addr]string, macToHostname map[string]string) {
|
|
||||||
fields := strings.Fields(line)
|
|
||||||
if len(fields) < 5 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
validTime, err := strconv.ParseInt(fields[4], 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if validTime == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if validTime > 0 && validTime < time.Now().Unix() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
hostname := fields[3]
|
|
||||||
if hostname == "-" || strings.HasPrefix(hostname, `broken\x20`) {
|
|
||||||
hostname = ""
|
|
||||||
}
|
|
||||||
if len(fields) >= 8 && fields[2] == "ipv4" {
|
|
||||||
mac, macErr := net.ParseMAC(fields[1])
|
|
||||||
if macErr != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
addressField := fields[7]
|
|
||||||
slashIndex := strings.IndexByte(addressField, '/')
|
|
||||||
if slashIndex >= 0 {
|
|
||||||
addressField = addressField[:slashIndex]
|
|
||||||
}
|
|
||||||
address, addrOK := netip.AddrFromSlice(net.ParseIP(addressField))
|
|
||||||
if !addrOK {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
address = address.Unmap()
|
|
||||||
ipToMAC[address] = mac
|
|
||||||
if hostname != "" {
|
|
||||||
ipToHostname[address] = hostname
|
|
||||||
macToHostname[mac.String()] = hostname
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var mac net.HardwareAddr
|
|
||||||
duidHex := fields[1]
|
|
||||||
duidBytes, hexErr := hex.DecodeString(duidHex)
|
|
||||||
if hexErr == nil {
|
|
||||||
mac, _ = extractMACFromDUID(duidBytes)
|
|
||||||
}
|
|
||||||
for i := 7; i < len(fields); i++ {
|
|
||||||
addressField := fields[i]
|
|
||||||
slashIndex := strings.IndexByte(addressField, '/')
|
|
||||||
if slashIndex >= 0 {
|
|
||||||
addressField = addressField[:slashIndex]
|
|
||||||
}
|
|
||||||
address, addrOK := netip.AddrFromSlice(net.ParseIP(addressField))
|
|
||||||
if !addrOK {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
address = address.Unmap()
|
|
||||||
if mac != nil {
|
|
||||||
ipToMAC[address] = mac
|
|
||||||
}
|
|
||||||
if hostname != "" {
|
|
||||||
ipToHostname[address] = hostname
|
|
||||||
if mac != nil {
|
|
||||||
macToHostname[mac.String()] = hostname
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseISCDhcpd(file *os.File, ipToMAC map[netip.Addr]net.HardwareAddr, ipToHostname map[netip.Addr]string, macToHostname map[string]string) {
|
|
||||||
scanner := bufio.NewScanner(file)
|
|
||||||
var currentIP netip.Addr
|
|
||||||
var currentMAC net.HardwareAddr
|
|
||||||
var currentHostname string
|
|
||||||
var currentActive bool
|
|
||||||
var inLease bool
|
|
||||||
for scanner.Scan() {
|
|
||||||
line := strings.TrimSpace(scanner.Text())
|
|
||||||
if strings.HasPrefix(line, "lease ") && strings.HasSuffix(line, "{") {
|
|
||||||
ipString := strings.TrimSuffix(strings.TrimPrefix(line, "lease "), " {")
|
|
||||||
parsed, addrOK := netip.AddrFromSlice(net.ParseIP(ipString))
|
|
||||||
if addrOK {
|
|
||||||
currentIP = parsed.Unmap()
|
|
||||||
inLease = true
|
|
||||||
currentMAC = nil
|
|
||||||
currentHostname = ""
|
|
||||||
currentActive = false
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if line == "}" && inLease {
|
|
||||||
if currentActive && currentMAC != nil {
|
|
||||||
ipToMAC[currentIP] = currentMAC
|
|
||||||
if currentHostname != "" {
|
|
||||||
ipToHostname[currentIP] = currentHostname
|
|
||||||
macToHostname[currentMAC.String()] = currentHostname
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
delete(ipToMAC, currentIP)
|
|
||||||
delete(ipToHostname, currentIP)
|
|
||||||
}
|
|
||||||
inLease = false
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if !inLease {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(line, "hardware ethernet ") {
|
|
||||||
macString := strings.TrimSuffix(strings.TrimPrefix(line, "hardware ethernet "), ";")
|
|
||||||
parsed, macErr := net.ParseMAC(macString)
|
|
||||||
if macErr == nil {
|
|
||||||
currentMAC = parsed
|
|
||||||
}
|
|
||||||
} else if strings.HasPrefix(line, "client-hostname ") {
|
|
||||||
hostname := strings.TrimSuffix(strings.TrimPrefix(line, "client-hostname "), ";")
|
|
||||||
hostname = strings.Trim(hostname, "\"")
|
|
||||||
if hostname != "" {
|
|
||||||
currentHostname = hostname
|
|
||||||
}
|
|
||||||
} else if strings.HasPrefix(line, "binding state ") {
|
|
||||||
state := strings.TrimSuffix(strings.TrimPrefix(line, "binding state "), ";")
|
|
||||||
currentActive = state == "active"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseKeaCSV4(file *os.File, ipToMAC map[netip.Addr]net.HardwareAddr, ipToHostname map[netip.Addr]string, macToHostname map[string]string) {
|
|
||||||
scanner := bufio.NewScanner(file)
|
|
||||||
firstLine := true
|
|
||||||
for scanner.Scan() {
|
|
||||||
if firstLine {
|
|
||||||
firstLine = false
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
fields := strings.Split(scanner.Text(), ",")
|
|
||||||
if len(fields) < 10 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if fields[9] != "0" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
address, addrOK := netip.AddrFromSlice(net.ParseIP(fields[0]))
|
|
||||||
if !addrOK {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
address = address.Unmap()
|
|
||||||
mac, macErr := net.ParseMAC(fields[1])
|
|
||||||
if macErr != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
ipToMAC[address] = mac
|
|
||||||
hostname := ""
|
|
||||||
if len(fields) > 8 {
|
|
||||||
hostname = fields[8]
|
|
||||||
}
|
|
||||||
if hostname != "" {
|
|
||||||
ipToHostname[address] = hostname
|
|
||||||
macToHostname[mac.String()] = hostname
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseKeaCSV6(file *os.File, ipToMAC map[netip.Addr]net.HardwareAddr, ipToHostname map[netip.Addr]string, macToHostname map[string]string) {
|
|
||||||
scanner := bufio.NewScanner(file)
|
|
||||||
firstLine := true
|
|
||||||
for scanner.Scan() {
|
|
||||||
if firstLine {
|
|
||||||
firstLine = false
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
fields := strings.Split(scanner.Text(), ",")
|
|
||||||
if len(fields) < 14 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if fields[13] != "0" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
address, addrOK := netip.AddrFromSlice(net.ParseIP(fields[0]))
|
|
||||||
if !addrOK {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
address = address.Unmap()
|
|
||||||
var mac net.HardwareAddr
|
|
||||||
if fields[12] != "" {
|
|
||||||
mac, _ = net.ParseMAC(fields[12])
|
|
||||||
}
|
|
||||||
if mac == nil {
|
|
||||||
duid, duidErr := hex.DecodeString(strings.ReplaceAll(fields[1], ":", ""))
|
|
||||||
if duidErr == nil {
|
|
||||||
mac, _ = extractMACFromDUID(duid)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
hostname := ""
|
|
||||||
if len(fields) > 11 {
|
|
||||||
hostname = fields[11]
|
|
||||||
}
|
|
||||||
if mac != nil {
|
|
||||||
ipToMAC[address] = mac
|
|
||||||
}
|
|
||||||
if hostname != "" {
|
|
||||||
ipToHostname[address] = hostname
|
|
||||||
if mac != nil {
|
|
||||||
macToHostname[mac.String()] = hostname
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseBootpdLeases(file *os.File, ipToMAC map[netip.Addr]net.HardwareAddr, ipToHostname map[netip.Addr]string, macToHostname map[string]string) {
|
|
||||||
now := time.Now().Unix()
|
|
||||||
scanner := bufio.NewScanner(file)
|
|
||||||
var currentName string
|
|
||||||
var currentIP netip.Addr
|
|
||||||
var currentMAC net.HardwareAddr
|
|
||||||
var currentLease int64
|
|
||||||
var inBlock bool
|
|
||||||
for scanner.Scan() {
|
|
||||||
line := strings.TrimSpace(scanner.Text())
|
|
||||||
if line == "{" {
|
|
||||||
inBlock = true
|
|
||||||
currentName = ""
|
|
||||||
currentIP = netip.Addr{}
|
|
||||||
currentMAC = nil
|
|
||||||
currentLease = 0
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if line == "}" && inBlock {
|
|
||||||
if currentMAC != nil && currentIP.IsValid() {
|
|
||||||
if currentLease == 0 || currentLease >= now {
|
|
||||||
ipToMAC[currentIP] = currentMAC
|
|
||||||
if currentName != "" {
|
|
||||||
ipToHostname[currentIP] = currentName
|
|
||||||
macToHostname[currentMAC.String()] = currentName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
inBlock = false
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if !inBlock {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
key, value, found := strings.Cut(line, "=")
|
|
||||||
if !found {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
switch key {
|
|
||||||
case "name":
|
|
||||||
currentName = value
|
|
||||||
case "ip_address":
|
|
||||||
parsed, addrOK := netip.AddrFromSlice(net.ParseIP(value))
|
|
||||||
if addrOK {
|
|
||||||
currentIP = parsed.Unmap()
|
|
||||||
}
|
|
||||||
case "hw_address":
|
|
||||||
typeAndMAC, hasSep := strings.CutPrefix(value, "1,")
|
|
||||||
if hasSep {
|
|
||||||
mac, macErr := net.ParseMAC(typeAndMAC)
|
|
||||||
if macErr == nil {
|
|
||||||
currentMAC = mac
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case "lease":
|
|
||||||
leaseHex := strings.TrimPrefix(value, "0x")
|
|
||||||
parsed, parseErr := strconv.ParseInt(leaseHex, 16, 64)
|
|
||||||
if parseErr == nil {
|
|
||||||
currentLease = parsed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,224 +0,0 @@
|
|||||||
//go:build linux
|
|
||||||
|
|
||||||
package route
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"net/netip"
|
|
||||||
"os"
|
|
||||||
"slices"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/fswatch"
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
"github.com/sagernet/sing/common/logger"
|
|
||||||
|
|
||||||
"github.com/jsimonetti/rtnetlink"
|
|
||||||
"github.com/mdlayher/netlink"
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
)
|
|
||||||
|
|
||||||
var defaultLeaseFiles = []string{
|
|
||||||
"/tmp/dhcp.leases",
|
|
||||||
"/var/lib/dhcp/dhcpd.leases",
|
|
||||||
"/var/lib/dhcpd/dhcpd.leases",
|
|
||||||
"/var/lib/kea/kea-leases4.csv",
|
|
||||||
"/var/lib/kea/kea-leases6.csv",
|
|
||||||
}
|
|
||||||
|
|
||||||
type neighborResolver struct {
|
|
||||||
logger logger.ContextLogger
|
|
||||||
leaseFiles []string
|
|
||||||
access sync.RWMutex
|
|
||||||
neighborIPToMAC map[netip.Addr]net.HardwareAddr
|
|
||||||
leaseIPToMAC map[netip.Addr]net.HardwareAddr
|
|
||||||
ipToHostname map[netip.Addr]string
|
|
||||||
macToHostname map[string]string
|
|
||||||
watcher *fswatch.Watcher
|
|
||||||
done chan struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newNeighborResolver(resolverLogger logger.ContextLogger, leaseFiles []string) (adapter.NeighborResolver, error) {
|
|
||||||
if len(leaseFiles) == 0 {
|
|
||||||
for _, path := range defaultLeaseFiles {
|
|
||||||
info, err := os.Stat(path)
|
|
||||||
if err == nil && info.Size() > 0 {
|
|
||||||
leaseFiles = append(leaseFiles, path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &neighborResolver{
|
|
||||||
logger: resolverLogger,
|
|
||||||
leaseFiles: leaseFiles,
|
|
||||||
neighborIPToMAC: make(map[netip.Addr]net.HardwareAddr),
|
|
||||||
leaseIPToMAC: make(map[netip.Addr]net.HardwareAddr),
|
|
||||||
ipToHostname: make(map[netip.Addr]string),
|
|
||||||
macToHostname: make(map[string]string),
|
|
||||||
done: make(chan struct{}),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *neighborResolver) Start() error {
|
|
||||||
err := r.loadNeighborTable()
|
|
||||||
if err != nil {
|
|
||||||
r.logger.Warn(E.Cause(err, "load neighbor table"))
|
|
||||||
}
|
|
||||||
r.doReloadLeaseFiles()
|
|
||||||
go r.subscribeNeighborUpdates()
|
|
||||||
if len(r.leaseFiles) > 0 {
|
|
||||||
watcher, err := fswatch.NewWatcher(fswatch.Options{
|
|
||||||
Path: r.leaseFiles,
|
|
||||||
Logger: r.logger,
|
|
||||||
Callback: func(_ string) {
|
|
||||||
r.doReloadLeaseFiles()
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
r.logger.Warn(E.Cause(err, "create lease file watcher"))
|
|
||||||
} else {
|
|
||||||
r.watcher = watcher
|
|
||||||
err = watcher.Start()
|
|
||||||
if err != nil {
|
|
||||||
r.logger.Warn(E.Cause(err, "start lease file watcher"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *neighborResolver) Close() error {
|
|
||||||
close(r.done)
|
|
||||||
if r.watcher != nil {
|
|
||||||
return r.watcher.Close()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *neighborResolver) LookupMAC(address netip.Addr) (net.HardwareAddr, bool) {
|
|
||||||
r.access.RLock()
|
|
||||||
defer r.access.RUnlock()
|
|
||||||
mac, found := r.neighborIPToMAC[address]
|
|
||||||
if found {
|
|
||||||
return mac, true
|
|
||||||
}
|
|
||||||
mac, found = r.leaseIPToMAC[address]
|
|
||||||
if found {
|
|
||||||
return mac, true
|
|
||||||
}
|
|
||||||
mac, found = extractMACFromEUI64(address)
|
|
||||||
if found {
|
|
||||||
return mac, true
|
|
||||||
}
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *neighborResolver) LookupHostname(address netip.Addr) (string, bool) {
|
|
||||||
r.access.RLock()
|
|
||||||
defer r.access.RUnlock()
|
|
||||||
hostname, found := r.ipToHostname[address]
|
|
||||||
if found {
|
|
||||||
return hostname, true
|
|
||||||
}
|
|
||||||
mac, macFound := r.neighborIPToMAC[address]
|
|
||||||
if !macFound {
|
|
||||||
mac, macFound = r.leaseIPToMAC[address]
|
|
||||||
}
|
|
||||||
if !macFound {
|
|
||||||
mac, macFound = extractMACFromEUI64(address)
|
|
||||||
}
|
|
||||||
if macFound {
|
|
||||||
hostname, found = r.macToHostname[mac.String()]
|
|
||||||
if found {
|
|
||||||
return hostname, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *neighborResolver) loadNeighborTable() error {
|
|
||||||
connection, err := rtnetlink.Dial(nil)
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "dial rtnetlink")
|
|
||||||
}
|
|
||||||
defer connection.Close()
|
|
||||||
neighbors, err := connection.Neigh.List()
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "list neighbors")
|
|
||||||
}
|
|
||||||
r.access.Lock()
|
|
||||||
defer r.access.Unlock()
|
|
||||||
for _, neigh := range neighbors {
|
|
||||||
if neigh.Attributes == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if neigh.Attributes.LLAddress == nil || len(neigh.Attributes.Address) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
address, ok := netip.AddrFromSlice(neigh.Attributes.Address)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
r.neighborIPToMAC[address] = slices.Clone(neigh.Attributes.LLAddress)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *neighborResolver) subscribeNeighborUpdates() {
|
|
||||||
connection, err := netlink.Dial(unix.NETLINK_ROUTE, &netlink.Config{
|
|
||||||
Groups: 1 << (unix.RTNLGRP_NEIGH - 1),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
r.logger.Warn(E.Cause(err, "subscribe neighbor updates"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer connection.Close()
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-r.done:
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
err = connection.SetReadDeadline(time.Now().Add(3 * time.Second))
|
|
||||||
if err != nil {
|
|
||||||
r.logger.Warn(E.Cause(err, "set netlink read deadline"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
messages, err := connection.Receive()
|
|
||||||
if err != nil {
|
|
||||||
if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case <-r.done:
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
r.logger.Warn(E.Cause(err, "receive neighbor update"))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for _, message := range messages {
|
|
||||||
address, mac, isDelete, ok := ParseNeighborMessage(message)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
r.access.Lock()
|
|
||||||
if isDelete {
|
|
||||||
delete(r.neighborIPToMAC, address)
|
|
||||||
} else {
|
|
||||||
r.neighborIPToMAC[address] = mac
|
|
||||||
}
|
|
||||||
r.access.Unlock()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *neighborResolver) doReloadLeaseFiles() {
|
|
||||||
leaseIPToMAC, ipToHostname, macToHostname := ReloadLeaseFiles(r.leaseFiles)
|
|
||||||
r.access.Lock()
|
|
||||||
r.leaseIPToMAC = leaseIPToMAC
|
|
||||||
r.ipToHostname = ipToHostname
|
|
||||||
r.macToHostname = macToHostname
|
|
||||||
r.access.Unlock()
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
package route
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"encoding/hex"
|
|
||||||
"net"
|
|
||||||
"net/netip"
|
|
||||||
"slices"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func extractMACFromDUID(duid []byte) (net.HardwareAddr, bool) {
|
|
||||||
if len(duid) < 4 {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
duidType := binary.BigEndian.Uint16(duid[0:2])
|
|
||||||
hwType := binary.BigEndian.Uint16(duid[2:4])
|
|
||||||
if hwType != 1 {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
switch duidType {
|
|
||||||
case 1:
|
|
||||||
if len(duid) < 14 {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
return net.HardwareAddr(slices.Clone(duid[8:14])), true
|
|
||||||
case 3:
|
|
||||||
if len(duid) < 10 {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
return net.HardwareAddr(slices.Clone(duid[4:10])), true
|
|
||||||
}
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
func extractMACFromEUI64(address netip.Addr) (net.HardwareAddr, bool) {
|
|
||||||
if !address.Is6() {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
b := address.As16()
|
|
||||||
if b[11] != 0xff || b[12] != 0xfe {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
return net.HardwareAddr{b[8] ^ 0x02, b[9], b[10], b[13], b[14], b[15]}, true
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseDUID(s string) ([]byte, error) {
|
|
||||||
cleaned := strings.ReplaceAll(s, ":", "")
|
|
||||||
return hex.DecodeString(cleaned)
|
|
||||||
}
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
package route
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"net/netip"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
"github.com/sagernet/sing/common/logger"
|
|
||||||
)
|
|
||||||
|
|
||||||
type platformNeighborResolver struct {
|
|
||||||
logger logger.ContextLogger
|
|
||||||
platform adapter.PlatformInterface
|
|
||||||
access sync.RWMutex
|
|
||||||
ipToMAC map[netip.Addr]net.HardwareAddr
|
|
||||||
ipToHostname map[netip.Addr]string
|
|
||||||
macToHostname map[string]string
|
|
||||||
}
|
|
||||||
|
|
||||||
func newPlatformNeighborResolver(resolverLogger logger.ContextLogger, platform adapter.PlatformInterface) adapter.NeighborResolver {
|
|
||||||
return &platformNeighborResolver{
|
|
||||||
logger: resolverLogger,
|
|
||||||
platform: platform,
|
|
||||||
ipToMAC: make(map[netip.Addr]net.HardwareAddr),
|
|
||||||
ipToHostname: make(map[netip.Addr]string),
|
|
||||||
macToHostname: make(map[string]string),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *platformNeighborResolver) Start() error {
|
|
||||||
return r.platform.StartNeighborMonitor(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *platformNeighborResolver) Close() error {
|
|
||||||
return r.platform.CloseNeighborMonitor(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *platformNeighborResolver) LookupMAC(address netip.Addr) (net.HardwareAddr, bool) {
|
|
||||||
r.access.RLock()
|
|
||||||
defer r.access.RUnlock()
|
|
||||||
mac, found := r.ipToMAC[address]
|
|
||||||
if found {
|
|
||||||
return mac, true
|
|
||||||
}
|
|
||||||
return extractMACFromEUI64(address)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *platformNeighborResolver) LookupHostname(address netip.Addr) (string, bool) {
|
|
||||||
r.access.RLock()
|
|
||||||
defer r.access.RUnlock()
|
|
||||||
hostname, found := r.ipToHostname[address]
|
|
||||||
if found {
|
|
||||||
return hostname, true
|
|
||||||
}
|
|
||||||
mac, found := r.ipToMAC[address]
|
|
||||||
if !found {
|
|
||||||
mac, found = extractMACFromEUI64(address)
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
hostname, found = r.macToHostname[mac.String()]
|
|
||||||
return hostname, found
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *platformNeighborResolver) UpdateNeighborTable(entries []adapter.NeighborEntry) {
|
|
||||||
ipToMAC := make(map[netip.Addr]net.HardwareAddr)
|
|
||||||
ipToHostname := make(map[netip.Addr]string)
|
|
||||||
macToHostname := make(map[string]string)
|
|
||||||
for _, entry := range entries {
|
|
||||||
ipToMAC[entry.Address] = entry.MACAddress
|
|
||||||
if entry.Hostname != "" {
|
|
||||||
ipToHostname[entry.Address] = entry.Hostname
|
|
||||||
macToHostname[entry.MACAddress.String()] = entry.Hostname
|
|
||||||
}
|
|
||||||
}
|
|
||||||
r.access.Lock()
|
|
||||||
r.ipToMAC = ipToMAC
|
|
||||||
r.ipToHostname = ipToHostname
|
|
||||||
r.macToHostname = macToHostname
|
|
||||||
r.access.Unlock()
|
|
||||||
r.logger.Info("updated neighbor table: ", len(entries), " entries")
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
//go:build !linux && !darwin
|
|
||||||
|
|
||||||
package route
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
"github.com/sagernet/sing/common/logger"
|
|
||||||
)
|
|
||||||
|
|
||||||
func newNeighborResolver(_ logger.ContextLogger, _ []string) (adapter.NeighborResolver, error) {
|
|
||||||
return nil, os.ErrInvalid
|
|
||||||
}
|
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
//go:build darwin
|
|
||||||
|
|
||||||
package route
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"net/netip"
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
|
|
||||||
"golang.org/x/net/route"
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ReadNeighborEntries() ([]adapter.NeighborEntry, error) {
|
|
||||||
var entries []adapter.NeighborEntry
|
|
||||||
ipv4Entries, err := readNeighborEntriesAF(syscall.AF_INET)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "read IPv4 neighbors")
|
|
||||||
}
|
|
||||||
entries = append(entries, ipv4Entries...)
|
|
||||||
ipv6Entries, err := readNeighborEntriesAF(syscall.AF_INET6)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "read IPv6 neighbors")
|
|
||||||
}
|
|
||||||
entries = append(entries, ipv6Entries...)
|
|
||||||
return entries, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func readNeighborEntriesAF(addressFamily int) ([]adapter.NeighborEntry, error) {
|
|
||||||
rib, err := route.FetchRIB(addressFamily, route.RIBType(syscall.NET_RT_FLAGS), syscall.RTF_LLINFO)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
messages, err := route.ParseRIB(route.RIBType(syscall.NET_RT_FLAGS), rib)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var entries []adapter.NeighborEntry
|
|
||||||
for _, message := range messages {
|
|
||||||
routeMessage, isRouteMessage := message.(*route.RouteMessage)
|
|
||||||
if !isRouteMessage {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
address, macAddress, ok := parseRouteNeighborEntry(routeMessage)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
entries = append(entries, adapter.NeighborEntry{
|
|
||||||
Address: address,
|
|
||||||
MACAddress: macAddress,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return entries, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseRouteNeighborEntry(message *route.RouteMessage) (address netip.Addr, macAddress net.HardwareAddr, ok bool) {
|
|
||||||
if len(message.Addrs) <= unix.RTAX_GATEWAY {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
gateway, isLinkAddr := message.Addrs[unix.RTAX_GATEWAY].(*route.LinkAddr)
|
|
||||||
if !isLinkAddr || len(gateway.Addr) < 6 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
switch destination := message.Addrs[unix.RTAX_DST].(type) {
|
|
||||||
case *route.Inet4Addr:
|
|
||||||
address = netip.AddrFrom4(destination.IP)
|
|
||||||
case *route.Inet6Addr:
|
|
||||||
address = netip.AddrFrom16(destination.IP)
|
|
||||||
default:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
macAddress = net.HardwareAddr(make([]byte, len(gateway.Addr)))
|
|
||||||
copy(macAddress, gateway.Addr)
|
|
||||||
ok = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseRouteNeighborMessage(message *route.RouteMessage) (address netip.Addr, macAddress net.HardwareAddr, isDelete bool, ok bool) {
|
|
||||||
isDelete = message.Type == unix.RTM_DELETE
|
|
||||||
if len(message.Addrs) <= unix.RTAX_GATEWAY {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
switch destination := message.Addrs[unix.RTAX_DST].(type) {
|
|
||||||
case *route.Inet4Addr:
|
|
||||||
address = netip.AddrFrom4(destination.IP)
|
|
||||||
case *route.Inet6Addr:
|
|
||||||
address = netip.AddrFrom16(destination.IP)
|
|
||||||
default:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !isDelete {
|
|
||||||
gateway, isLinkAddr := message.Addrs[unix.RTAX_GATEWAY].(*route.LinkAddr)
|
|
||||||
if !isLinkAddr || len(gateway.Addr) < 6 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
macAddress = net.HardwareAddr(make([]byte, len(gateway.Addr)))
|
|
||||||
copy(macAddress, gateway.Addr)
|
|
||||||
}
|
|
||||||
ok = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
//go:build linux
|
|
||||||
|
|
||||||
package route
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"net/netip"
|
|
||||||
"slices"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
|
|
||||||
"github.com/jsimonetti/rtnetlink"
|
|
||||||
"github.com/mdlayher/netlink"
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ReadNeighborEntries() ([]adapter.NeighborEntry, error) {
|
|
||||||
connection, err := rtnetlink.Dial(nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "dial rtnetlink")
|
|
||||||
}
|
|
||||||
defer connection.Close()
|
|
||||||
neighbors, err := connection.Neigh.List()
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "list neighbors")
|
|
||||||
}
|
|
||||||
var entries []adapter.NeighborEntry
|
|
||||||
for _, neighbor := range neighbors {
|
|
||||||
if neighbor.Attributes == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if neighbor.Attributes.LLAddress == nil || len(neighbor.Attributes.Address) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
address, ok := netip.AddrFromSlice(neighbor.Attributes.Address)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
entries = append(entries, adapter.NeighborEntry{
|
|
||||||
Address: address,
|
|
||||||
MACAddress: slices.Clone(neighbor.Attributes.LLAddress),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return entries, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseNeighborMessage(message netlink.Message) (address netip.Addr, macAddress net.HardwareAddr, isDelete bool, ok bool) {
|
|
||||||
var neighMessage rtnetlink.NeighMessage
|
|
||||||
err := neighMessage.UnmarshalBinary(message.Data)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if neighMessage.Attributes == nil || len(neighMessage.Attributes.Address) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
address, ok = netip.AddrFromSlice(neighMessage.Attributes.Address)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
isDelete = message.Header.Type == unix.RTM_DELNEIGH
|
|
||||||
if !isDelete && neighMessage.Attributes.LLAddress == nil {
|
|
||||||
ok = false
|
|
||||||
return
|
|
||||||
}
|
|
||||||
macAddress = slices.Clone(neighMessage.Attributes.LLAddress)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
@@ -438,23 +438,6 @@ func (r *Router) matchRule(
|
|||||||
metadata.ProcessInfo = processInfo
|
metadata.ProcessInfo = processInfo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if r.neighborResolver != nil && metadata.SourceMACAddress == nil && metadata.Source.Addr.IsValid() {
|
|
||||||
mac, macFound := r.neighborResolver.LookupMAC(metadata.Source.Addr)
|
|
||||||
if macFound {
|
|
||||||
metadata.SourceMACAddress = mac
|
|
||||||
}
|
|
||||||
hostname, hostnameFound := r.neighborResolver.LookupHostname(metadata.Source.Addr)
|
|
||||||
if hostnameFound {
|
|
||||||
metadata.SourceHostname = hostname
|
|
||||||
if macFound {
|
|
||||||
r.logger.InfoContext(ctx, "found neighbor: ", mac, ", hostname: ", hostname)
|
|
||||||
} else {
|
|
||||||
r.logger.InfoContext(ctx, "found neighbor hostname: ", hostname)
|
|
||||||
}
|
|
||||||
} else if macFound {
|
|
||||||
r.logger.InfoContext(ctx, "found neighbor: ", mac)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if metadata.Destination.Addr.IsValid() && r.dnsTransport.FakeIP() != nil && r.dnsTransport.FakeIP().Store().Contains(metadata.Destination.Addr) {
|
if metadata.Destination.Addr.IsValid() && r.dnsTransport.FakeIP() != nil && r.dnsTransport.FakeIP().Store().Contains(metadata.Destination.Addr) {
|
||||||
domain, loaded := r.dnsTransport.FakeIP().Store().Lookup(metadata.Destination.Addr)
|
domain, loaded := r.dnsTransport.FakeIP().Store().Lookup(metadata.Destination.Addr)
|
||||||
if !loaded {
|
if !loaded {
|
||||||
|
|||||||
@@ -35,13 +35,10 @@ type Router struct {
|
|||||||
network adapter.NetworkManager
|
network adapter.NetworkManager
|
||||||
rules []adapter.Rule
|
rules []adapter.Rule
|
||||||
needFindProcess bool
|
needFindProcess bool
|
||||||
needFindNeighbor bool
|
|
||||||
leaseFiles []string
|
|
||||||
ruleSets []adapter.RuleSet
|
ruleSets []adapter.RuleSet
|
||||||
ruleSetMap map[string]adapter.RuleSet
|
ruleSetMap map[string]adapter.RuleSet
|
||||||
processSearcher process.Searcher
|
processSearcher process.Searcher
|
||||||
processCache freelru.Cache[processCacheKey, processCacheEntry]
|
processCache freelru.Cache[processCacheKey, processCacheEntry]
|
||||||
neighborResolver adapter.NeighborResolver
|
|
||||||
pauseManager pause.Manager
|
pauseManager pause.Manager
|
||||||
trackers []adapter.ConnectionTracker
|
trackers []adapter.ConnectionTracker
|
||||||
platformInterface adapter.PlatformInterface
|
platformInterface adapter.PlatformInterface
|
||||||
@@ -61,8 +58,6 @@ func NewRouter(ctx context.Context, logFactory log.Factory, options option.Route
|
|||||||
rules: make([]adapter.Rule, 0, len(options.Rules)),
|
rules: make([]adapter.Rule, 0, len(options.Rules)),
|
||||||
ruleSetMap: make(map[string]adapter.RuleSet),
|
ruleSetMap: make(map[string]adapter.RuleSet),
|
||||||
needFindProcess: hasRule(options.Rules, isProcessRule) || hasDNSRule(dnsOptions.Rules, isProcessDNSRule) || options.FindProcess,
|
needFindProcess: hasRule(options.Rules, isProcessRule) || hasDNSRule(dnsOptions.Rules, isProcessDNSRule) || options.FindProcess,
|
||||||
needFindNeighbor: hasRule(options.Rules, isNeighborRule) || hasDNSRule(dnsOptions.Rules, isNeighborDNSRule) || options.FindNeighbor,
|
|
||||||
leaseFiles: options.DHCPLeaseFiles,
|
|
||||||
pauseManager: service.FromContext[pause.Manager](ctx),
|
pauseManager: service.FromContext[pause.Manager](ctx),
|
||||||
platformInterface: service.FromContext[adapter.PlatformInterface](ctx),
|
platformInterface: service.FromContext[adapter.PlatformInterface](ctx),
|
||||||
}
|
}
|
||||||
@@ -122,7 +117,6 @@ func (r *Router) Start(stage adapter.StartStage) error {
|
|||||||
}
|
}
|
||||||
r.network.Initialize(r.ruleSets)
|
r.network.Initialize(r.ruleSets)
|
||||||
needFindProcess := r.needFindProcess
|
needFindProcess := r.needFindProcess
|
||||||
needFindNeighbor := r.needFindNeighbor
|
|
||||||
for _, ruleSet := range r.ruleSets {
|
for _, ruleSet := range r.ruleSets {
|
||||||
metadata := ruleSet.Metadata()
|
metadata := ruleSet.Metadata()
|
||||||
if metadata.ContainsProcessRule {
|
if metadata.ContainsProcessRule {
|
||||||
@@ -157,36 +151,6 @@ func (r *Router) Start(stage adapter.StartStage) error {
|
|||||||
processCache.SetLifetime(200 * time.Millisecond)
|
processCache.SetLifetime(200 * time.Millisecond)
|
||||||
r.processCache = processCache
|
r.processCache = processCache
|
||||||
}
|
}
|
||||||
r.needFindNeighbor = needFindNeighbor
|
|
||||||
if needFindNeighbor {
|
|
||||||
if r.platformInterface != nil && r.platformInterface.UsePlatformNeighborResolver() {
|
|
||||||
monitor.Start("initialize neighbor resolver")
|
|
||||||
resolver := newPlatformNeighborResolver(r.logger, r.platformInterface)
|
|
||||||
err := resolver.Start()
|
|
||||||
monitor.Finish()
|
|
||||||
if err != nil {
|
|
||||||
r.logger.Error(E.Cause(err, "start neighbor resolver"))
|
|
||||||
} else {
|
|
||||||
r.neighborResolver = resolver
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
monitor.Start("initialize neighbor resolver")
|
|
||||||
resolver, err := newNeighborResolver(r.logger, r.leaseFiles)
|
|
||||||
monitor.Finish()
|
|
||||||
if err != nil {
|
|
||||||
if err != os.ErrInvalid {
|
|
||||||
r.logger.Error(E.Cause(err, "create neighbor resolver"))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
err = resolver.Start()
|
|
||||||
if err != nil {
|
|
||||||
r.logger.Error(E.Cause(err, "start neighbor resolver"))
|
|
||||||
} else {
|
|
||||||
r.neighborResolver = resolver
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case adapter.StartStatePostStart:
|
case adapter.StartStatePostStart:
|
||||||
for i, rule := range r.rules {
|
for i, rule := range r.rules {
|
||||||
monitor.Start("initialize rule[", i, "]")
|
monitor.Start("initialize rule[", i, "]")
|
||||||
@@ -218,13 +182,6 @@ func (r *Router) Start(stage adapter.StartStage) error {
|
|||||||
func (r *Router) Close() error {
|
func (r *Router) Close() error {
|
||||||
monitor := taskmonitor.New(r.logger, C.StopTimeout)
|
monitor := taskmonitor.New(r.logger, C.StopTimeout)
|
||||||
var err error
|
var err error
|
||||||
if r.neighborResolver != nil {
|
|
||||||
monitor.Start("close neighbor resolver")
|
|
||||||
err = E.Append(err, r.neighborResolver.Close(), func(closeErr error) error {
|
|
||||||
return E.Cause(closeErr, "close neighbor resolver")
|
|
||||||
})
|
|
||||||
monitor.Finish()
|
|
||||||
}
|
|
||||||
for i, rule := range r.rules {
|
for i, rule := range r.rules {
|
||||||
monitor.Start("close rule[", i, "]")
|
monitor.Start("close rule[", i, "]")
|
||||||
err = E.Append(err, rule.Close(), func(err error) error {
|
err = E.Append(err, rule.Close(), func(err error) error {
|
||||||
@@ -266,14 +223,6 @@ func (r *Router) NeedFindProcess() bool {
|
|||||||
return r.needFindProcess
|
return r.needFindProcess
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Router) NeedFindNeighbor() bool {
|
|
||||||
return r.needFindNeighbor
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Router) NeighborResolver() adapter.NeighborResolver {
|
|
||||||
return r.neighborResolver
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Router) ResetNetwork() {
|
func (r *Router) ResetNetwork() {
|
||||||
r.network.ResetNetwork()
|
r.network.ResetNetwork()
|
||||||
r.dns.ResetNetwork()
|
r.dns.ResetNetwork()
|
||||||
|
|||||||
@@ -264,16 +264,6 @@ func NewDefaultRule(ctx context.Context, logger log.ContextLogger, options optio
|
|||||||
rule.items = append(rule.items, item)
|
rule.items = append(rule.items, item)
|
||||||
rule.allItems = append(rule.allItems, item)
|
rule.allItems = append(rule.allItems, item)
|
||||||
}
|
}
|
||||||
if len(options.SourceMACAddress) > 0 {
|
|
||||||
item := NewSourceMACAddressItem(options.SourceMACAddress)
|
|
||||||
rule.items = append(rule.items, item)
|
|
||||||
rule.allItems = append(rule.allItems, item)
|
|
||||||
}
|
|
||||||
if len(options.SourceHostname) > 0 {
|
|
||||||
item := NewSourceHostnameItem(options.SourceHostname)
|
|
||||||
rule.items = append(rule.items, item)
|
|
||||||
rule.allItems = append(rule.allItems, item)
|
|
||||||
}
|
|
||||||
if len(options.PreferredBy) > 0 {
|
if len(options.PreferredBy) > 0 {
|
||||||
item := NewPreferredByItem(ctx, options.PreferredBy)
|
item := NewPreferredByItem(ctx, options.PreferredBy)
|
||||||
rule.items = append(rule.items, item)
|
rule.items = append(rule.items, item)
|
||||||
|
|||||||
@@ -265,16 +265,6 @@ func NewDefaultDNSRule(ctx context.Context, logger log.ContextLogger, options op
|
|||||||
rule.items = append(rule.items, item)
|
rule.items = append(rule.items, item)
|
||||||
rule.allItems = append(rule.allItems, item)
|
rule.allItems = append(rule.allItems, item)
|
||||||
}
|
}
|
||||||
if len(options.SourceMACAddress) > 0 {
|
|
||||||
item := NewSourceMACAddressItem(options.SourceMACAddress)
|
|
||||||
rule.items = append(rule.items, item)
|
|
||||||
rule.allItems = append(rule.allItems, item)
|
|
||||||
}
|
|
||||||
if len(options.SourceHostname) > 0 {
|
|
||||||
item := NewSourceHostnameItem(options.SourceHostname)
|
|
||||||
rule.items = append(rule.items, item)
|
|
||||||
rule.allItems = append(rule.allItems, item)
|
|
||||||
}
|
|
||||||
if len(options.RuleSet) > 0 {
|
if len(options.RuleSet) > 0 {
|
||||||
//nolint:staticcheck
|
//nolint:staticcheck
|
||||||
if options.Deprecated_RulesetIPCIDRMatchSource {
|
if options.Deprecated_RulesetIPCIDRMatchSource {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user