mirror of
https://github.com/SagerNet/sing-box.git
synced 2025-09-10 13:24:08 +08:00
Compare commits
11 Commits
b03359ec47
...
a4c600d94a
Author | SHA1 | Date | |
---|---|---|---|
![]() |
a4c600d94a | ||
![]() |
900a2c1ef1 | ||
![]() |
4b3861232b | ||
![]() |
31a0bc94ae | ||
![]() |
8c1d1e9b0e | ||
![]() |
735b52fee5 | ||
![]() |
9fc9dccf46 | ||
![]() |
c64f7fd8a1 | ||
![]() |
de10bb00a9 | ||
![]() |
fdc181106d | ||
![]() |
8752b631bd |
@ -1,4 +1,4 @@
|
|||||||
FROM --platform=$BUILDPLATFORM golang:1.24-alpine AS builder
|
FROM --platform=$BUILDPLATFORM golang:1.25-alpine AS builder
|
||||||
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
|
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
|
||||||
COPY . /go/src/github.com/sagernet/sing-box
|
COPY . /go/src/github.com/sagernet/sing-box
|
||||||
WORKDIR /go/src/github.com/sagernet/sing-box
|
WORKDIR /go/src/github.com/sagernet/sing-box
|
||||||
|
@ -2,6 +2,7 @@ package adapter
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"net/netip"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
@ -18,6 +19,11 @@ type Outbound interface {
|
|||||||
N.Dialer
|
N.Dialer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type OutboundWithPreferredRoutes interface {
|
||||||
|
PreferredDomain(domain string) bool
|
||||||
|
PreferredAddress(address netip.Addr) bool
|
||||||
|
}
|
||||||
|
|
||||||
type OutboundRegistry interface {
|
type OutboundRegistry interface {
|
||||||
option.OutboundOptionsRegistry
|
option.OutboundOptionsRegistry
|
||||||
CreateOutbound(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) (Outbound, error)
|
CreateOutbound(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) (Outbound, error)
|
||||||
|
@ -6,8 +6,10 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/srs"
|
"github.com/sagernet/sing-box/common/srs"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"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-box/route/rule"
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@ -69,7 +71,7 @@ func compileRuleSet(sourcePath string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = srs.Write(outputFile, plainRuleSet.Options, plainRuleSet.Version)
|
err = srs.Write(outputFile, plainRuleSet.Options, downgradeRuleSetVersion(plainRuleSet.Version, plainRuleSet.Options))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
outputFile.Close()
|
outputFile.Close()
|
||||||
os.Remove(outputPath)
|
os.Remove(outputPath)
|
||||||
@ -78,3 +80,18 @@ func compileRuleSet(sourcePath string) error {
|
|||||||
outputFile.Close()
|
outputFile.Close()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func downgradeRuleSetVersion(version uint8, options option.PlainRuleSet) uint8 {
|
||||||
|
if version == C.RuleSetVersion4 && !rule.HasHeadlessRule(options.Rules, func(rule option.DefaultHeadlessRule) bool {
|
||||||
|
return rule.NetworkInterfaceAddress != nil && rule.NetworkInterfaceAddress.Size() > 0 ||
|
||||||
|
len(rule.DefaultInterfaceAddress) > 0
|
||||||
|
}) {
|
||||||
|
version = C.RuleSetVersion3
|
||||||
|
}
|
||||||
|
if version == C.RuleSetVersion3 && !rule.HasHeadlessRule(options.Rules, func(rule option.DefaultHeadlessRule) bool {
|
||||||
|
return len(rule.NetworkType) > 0 || rule.NetworkIsExpensive || rule.NetworkIsConstrained
|
||||||
|
}) {
|
||||||
|
version = C.RuleSetVersion2
|
||||||
|
}
|
||||||
|
return version
|
||||||
|
}
|
||||||
|
@ -10,6 +10,8 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
"github.com/sagernet/sing/common/atomic"
|
||||||
"github.com/sagernet/sing/common/bufio"
|
"github.com/sagernet/sing/common/bufio"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
@ -22,7 +24,7 @@ type slowOpenConn struct {
|
|||||||
ctx context.Context
|
ctx context.Context
|
||||||
network string
|
network string
|
||||||
destination M.Socksaddr
|
destination M.Socksaddr
|
||||||
conn net.Conn
|
conn atomic.Pointer[net.TCPConn]
|
||||||
create chan struct{}
|
create chan struct{}
|
||||||
done chan struct{}
|
done chan struct{}
|
||||||
access sync.Mutex
|
access sync.Mutex
|
||||||
@ -50,22 +52,25 @@ func DialSlowContext(dialer *tcpDialer, ctx context.Context, network string, des
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) Read(b []byte) (n int, err error) {
|
func (c *slowOpenConn) Read(b []byte) (n int, err error) {
|
||||||
if c.conn == nil {
|
conn := c.conn.Load()
|
||||||
select {
|
if conn != nil {
|
||||||
case <-c.create:
|
return conn.Read(b)
|
||||||
if c.err != nil {
|
}
|
||||||
return 0, c.err
|
select {
|
||||||
}
|
case <-c.create:
|
||||||
case <-c.done:
|
if c.err != nil {
|
||||||
return 0, os.ErrClosed
|
return 0, c.err
|
||||||
}
|
}
|
||||||
|
return c.conn.Load().Read(b)
|
||||||
|
case <-c.done:
|
||||||
|
return 0, os.ErrClosed
|
||||||
}
|
}
|
||||||
return c.conn.Read(b)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) Write(b []byte) (n int, err error) {
|
func (c *slowOpenConn) Write(b []byte) (n int, err error) {
|
||||||
if c.conn != nil {
|
tcpConn := c.conn.Load()
|
||||||
return c.conn.Write(b)
|
if tcpConn != nil {
|
||||||
|
return tcpConn.Write(b)
|
||||||
}
|
}
|
||||||
c.access.Lock()
|
c.access.Lock()
|
||||||
defer c.access.Unlock()
|
defer c.access.Unlock()
|
||||||
@ -74,7 +79,7 @@ func (c *slowOpenConn) Write(b []byte) (n int, err error) {
|
|||||||
if c.err != nil {
|
if c.err != nil {
|
||||||
return 0, c.err
|
return 0, c.err
|
||||||
}
|
}
|
||||||
return c.conn.Write(b)
|
return c.conn.Load().Write(b)
|
||||||
case <-c.done:
|
case <-c.done:
|
||||||
return 0, os.ErrClosed
|
return 0, os.ErrClosed
|
||||||
default:
|
default:
|
||||||
@ -83,7 +88,7 @@ func (c *slowOpenConn) Write(b []byte) (n int, err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
c.err = err
|
c.err = err
|
||||||
} else {
|
} else {
|
||||||
c.conn = conn
|
c.conn.Store(conn.(*net.TCPConn))
|
||||||
}
|
}
|
||||||
n = len(b)
|
n = len(b)
|
||||||
close(c.create)
|
close(c.create)
|
||||||
@ -93,70 +98,77 @@ func (c *slowOpenConn) Write(b []byte) (n int, err error) {
|
|||||||
func (c *slowOpenConn) Close() error {
|
func (c *slowOpenConn) Close() error {
|
||||||
c.closeOnce.Do(func() {
|
c.closeOnce.Do(func() {
|
||||||
close(c.done)
|
close(c.done)
|
||||||
if c.conn != nil {
|
conn := c.conn.Load()
|
||||||
c.conn.Close()
|
if conn != nil {
|
||||||
|
conn.Close()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) LocalAddr() net.Addr {
|
func (c *slowOpenConn) LocalAddr() net.Addr {
|
||||||
if c.conn == nil {
|
conn := c.conn.Load()
|
||||||
|
if conn == nil {
|
||||||
return M.Socksaddr{}
|
return M.Socksaddr{}
|
||||||
}
|
}
|
||||||
return c.conn.LocalAddr()
|
return conn.LocalAddr()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) RemoteAddr() net.Addr {
|
func (c *slowOpenConn) RemoteAddr() net.Addr {
|
||||||
if c.conn == nil {
|
conn := c.conn.Load()
|
||||||
|
if conn == nil {
|
||||||
return M.Socksaddr{}
|
return M.Socksaddr{}
|
||||||
}
|
}
|
||||||
return c.conn.RemoteAddr()
|
return conn.RemoteAddr()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) SetDeadline(t time.Time) error {
|
func (c *slowOpenConn) SetDeadline(t time.Time) error {
|
||||||
if c.conn == nil {
|
conn := c.conn.Load()
|
||||||
|
if conn == nil {
|
||||||
return os.ErrInvalid
|
return os.ErrInvalid
|
||||||
}
|
}
|
||||||
return c.conn.SetDeadline(t)
|
return conn.SetDeadline(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) SetReadDeadline(t time.Time) error {
|
func (c *slowOpenConn) SetReadDeadline(t time.Time) error {
|
||||||
if c.conn == nil {
|
conn := c.conn.Load()
|
||||||
|
if conn == nil {
|
||||||
return os.ErrInvalid
|
return os.ErrInvalid
|
||||||
}
|
}
|
||||||
return c.conn.SetReadDeadline(t)
|
return conn.SetReadDeadline(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) SetWriteDeadline(t time.Time) error {
|
func (c *slowOpenConn) SetWriteDeadline(t time.Time) error {
|
||||||
if c.conn == nil {
|
conn := c.conn.Load()
|
||||||
|
if conn == nil {
|
||||||
return os.ErrInvalid
|
return os.ErrInvalid
|
||||||
}
|
}
|
||||||
return c.conn.SetWriteDeadline(t)
|
return conn.SetWriteDeadline(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) Upstream() any {
|
func (c *slowOpenConn) Upstream() any {
|
||||||
return c.conn
|
return common.PtrOrNil(c.conn.Load())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) ReaderReplaceable() bool {
|
func (c *slowOpenConn) ReaderReplaceable() bool {
|
||||||
return c.conn != nil
|
return c.conn.Load() != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) WriterReplaceable() bool {
|
func (c *slowOpenConn) WriterReplaceable() bool {
|
||||||
return c.conn != nil
|
return c.conn.Load() != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) LazyHeadroom() bool {
|
func (c *slowOpenConn) LazyHeadroom() bool {
|
||||||
return c.conn == nil
|
return c.conn.Load() == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) NeedHandshake() bool {
|
func (c *slowOpenConn) NeedHandshake() bool {
|
||||||
return c.conn == nil
|
return c.conn.Load() == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) WriteTo(w io.Writer) (n int64, err error) {
|
func (c *slowOpenConn) WriteTo(w io.Writer) (n int64, err error) {
|
||||||
if c.conn == nil {
|
conn := c.conn.Load()
|
||||||
|
if conn == nil {
|
||||||
select {
|
select {
|
||||||
case <-c.create:
|
case <-c.create:
|
||||||
if c.err != nil {
|
if c.err != nil {
|
||||||
@ -166,5 +178,5 @@ func (c *slowOpenConn) WriteTo(w io.Writer) (n int64, err error) {
|
|||||||
return 0, c.err
|
return 0, c.err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return bufio.Copy(w, c.conn)
|
return bufio.Copy(w, c.conn.Load())
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,8 @@ import (
|
|||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/domain"
|
"github.com/sagernet/sing/common/domain"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/json/badjson"
|
||||||
|
"github.com/sagernet/sing/common/json/badoption"
|
||||||
"github.com/sagernet/sing/common/varbin"
|
"github.com/sagernet/sing/common/varbin"
|
||||||
|
|
||||||
"go4.org/netipx"
|
"go4.org/netipx"
|
||||||
@ -41,6 +43,8 @@ const (
|
|||||||
ruleItemNetworkType
|
ruleItemNetworkType
|
||||||
ruleItemNetworkIsExpensive
|
ruleItemNetworkIsExpensive
|
||||||
ruleItemNetworkIsConstrained
|
ruleItemNetworkIsConstrained
|
||||||
|
ruleItemNetworkInterfaceAddress
|
||||||
|
ruleItemDefaultInterfaceAddress
|
||||||
ruleItemFinal uint8 = 0xFF
|
ruleItemFinal uint8 = 0xFF
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -230,6 +234,51 @@ func readDefaultRule(reader varbin.Reader, recover bool) (rule option.DefaultHea
|
|||||||
rule.NetworkIsExpensive = true
|
rule.NetworkIsExpensive = true
|
||||||
case ruleItemNetworkIsConstrained:
|
case ruleItemNetworkIsConstrained:
|
||||||
rule.NetworkIsConstrained = true
|
rule.NetworkIsConstrained = true
|
||||||
|
case ruleItemNetworkInterfaceAddress:
|
||||||
|
rule.NetworkInterfaceAddress = new(badjson.TypedMap[option.InterfaceType, badoption.Listable[badoption.Prefixable]])
|
||||||
|
var size uint64
|
||||||
|
size, err = binary.ReadUvarint(reader)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for i := uint64(0); i < size; i++ {
|
||||||
|
var key uint8
|
||||||
|
err = binary.Read(reader, binary.BigEndian, &key)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var value []badoption.Prefixable
|
||||||
|
var prefixCount uint64
|
||||||
|
prefixCount, err = binary.ReadUvarint(reader)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for j := uint64(0); j < prefixCount; j++ {
|
||||||
|
var prefix netip.Prefix
|
||||||
|
prefix, err = readPrefix(reader)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
value = append(value, badoption.Prefixable(prefix))
|
||||||
|
}
|
||||||
|
rule.NetworkInterfaceAddress.Put(option.InterfaceType(key), value)
|
||||||
|
}
|
||||||
|
case ruleItemDefaultInterfaceAddress:
|
||||||
|
var value []badoption.Prefixable
|
||||||
|
var prefixCount uint64
|
||||||
|
prefixCount, err = binary.ReadUvarint(reader)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for j := uint64(0); j < prefixCount; j++ {
|
||||||
|
var prefix netip.Prefix
|
||||||
|
prefix, err = readPrefix(reader)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
value = append(value, badoption.Prefixable(prefix))
|
||||||
|
}
|
||||||
|
rule.DefaultInterfaceAddress = value
|
||||||
case ruleItemFinal:
|
case ruleItemFinal:
|
||||||
err = binary.Read(reader, binary.BigEndian, &rule.Invert)
|
err = binary.Read(reader, binary.BigEndian, &rule.Invert)
|
||||||
return
|
return
|
||||||
@ -346,7 +395,7 @@ func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, gen
|
|||||||
}
|
}
|
||||||
if len(rule.NetworkType) > 0 {
|
if len(rule.NetworkType) > 0 {
|
||||||
if generateVersion < C.RuleSetVersion3 {
|
if generateVersion < C.RuleSetVersion3 {
|
||||||
return E.New("network_type rule item is only supported in version 3 or later")
|
return E.New("`network_type` rule item is only supported in version 3 or later")
|
||||||
}
|
}
|
||||||
err = writeRuleItemUint8(writer, ruleItemNetworkType, rule.NetworkType)
|
err = writeRuleItemUint8(writer, ruleItemNetworkType, rule.NetworkType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -354,17 +403,67 @@ func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, gen
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if rule.NetworkIsExpensive {
|
if rule.NetworkIsExpensive {
|
||||||
|
if generateVersion < C.RuleSetVersion3 {
|
||||||
|
return E.New("`network_is_expensive` rule item is only supported in version 3 or later")
|
||||||
|
}
|
||||||
err = binary.Write(writer, binary.BigEndian, ruleItemNetworkIsExpensive)
|
err = binary.Write(writer, binary.BigEndian, ruleItemNetworkIsExpensive)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if rule.NetworkIsConstrained {
|
if rule.NetworkIsConstrained {
|
||||||
|
if generateVersion < C.RuleSetVersion3 {
|
||||||
|
return E.New("`network_is_constrained` rule item is only supported in version 3 or later")
|
||||||
|
}
|
||||||
err = binary.Write(writer, binary.BigEndian, ruleItemNetworkIsConstrained)
|
err = binary.Write(writer, binary.BigEndian, ruleItemNetworkIsConstrained)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if rule.NetworkInterfaceAddress != nil && rule.NetworkInterfaceAddress.Size() > 0 {
|
||||||
|
if generateVersion < C.RuleSetVersion4 {
|
||||||
|
return E.New("`network_interface_address` rule item is only supported in version 4 or later")
|
||||||
|
}
|
||||||
|
err = writer.WriteByte(ruleItemNetworkInterfaceAddress)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = varbin.WriteUvarint(writer, uint64(rule.NetworkInterfaceAddress.Size()))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, entry := range rule.NetworkInterfaceAddress.Entries() {
|
||||||
|
err = binary.Write(writer, binary.BigEndian, uint8(entry.Key.Build()))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, rawPrefix := range entry.Value {
|
||||||
|
err = writePrefix(writer, rawPrefix.Build(netip.Prefix{}))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(rule.DefaultInterfaceAddress) > 0 {
|
||||||
|
if generateVersion < C.RuleSetVersion4 {
|
||||||
|
return E.New("`default_interface_address` rule item is only supported in version 4 or later")
|
||||||
|
}
|
||||||
|
err = writer.WriteByte(ruleItemDefaultInterfaceAddress)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = varbin.WriteUvarint(writer, uint64(len(rule.DefaultInterfaceAddress)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, rawPrefix := range rule.DefaultInterfaceAddress {
|
||||||
|
err = writePrefix(writer, rawPrefix.Build(netip.Prefix{}))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if len(rule.WIFISSID) > 0 {
|
if len(rule.WIFISSID) > 0 {
|
||||||
err = writeRuleItemString(writer, ruleItemWIFISSID, rule.WIFISSID)
|
err = writeRuleItemString(writer, ruleItemWIFISSID, rule.WIFISSID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
33
common/srs/ip_cidr.go
Normal file
33
common/srs/ip_cidr.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package srs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"net/netip"
|
||||||
|
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
"github.com/sagernet/sing/common/varbin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func readPrefix(reader varbin.Reader) (netip.Prefix, error) {
|
||||||
|
addrSlice, err := varbin.ReadValue[[]byte](reader, binary.BigEndian)
|
||||||
|
if err != nil {
|
||||||
|
return netip.Prefix{}, err
|
||||||
|
}
|
||||||
|
prefixBits, err := varbin.ReadValue[uint8](reader, binary.BigEndian)
|
||||||
|
if err != nil {
|
||||||
|
return netip.Prefix{}, err
|
||||||
|
}
|
||||||
|
return netip.PrefixFrom(M.AddrFromIP(addrSlice), int(prefixBits)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func writePrefix(writer varbin.Writer, prefix netip.Prefix) error {
|
||||||
|
err := varbin.Write(writer, binary.BigEndian, prefix.Addr().AsSlice())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = binary.Write(writer, binary.BigEndian, uint8(prefix.Bits()))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
@ -2,10 +2,11 @@ package tls
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
"github.com/sagernet/sing-box/common/badtls"
|
"github.com/sagernet/sing-box/common/badtls"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
@ -14,7 +15,7 @@ import (
|
|||||||
aTLS "github.com/sagernet/sing/common/tls"
|
aTLS "github.com/sagernet/sing/common/tls"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewDialerFromOptions(ctx context.Context, router adapter.Router, dialer N.Dialer, serverAddress string, options option.OutboundTLSOptions) (N.Dialer, error) {
|
func NewDialerFromOptions(ctx context.Context, dialer N.Dialer, serverAddress string, options option.OutboundTLSOptions) (N.Dialer, error) {
|
||||||
if !options.Enabled {
|
if !options.Enabled {
|
||||||
return dialer, nil
|
return dialer, nil
|
||||||
}
|
}
|
||||||
@ -53,26 +54,57 @@ func ClientHandshake(ctx context.Context, conn net.Conn, config Config) (Conn, e
|
|||||||
return tlsConn, nil
|
return tlsConn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type Dialer struct {
|
type Dialer interface {
|
||||||
|
N.Dialer
|
||||||
|
DialTLSContext(ctx context.Context, destination M.Socksaddr) (Conn, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type defaultDialer struct {
|
||||||
dialer N.Dialer
|
dialer N.Dialer
|
||||||
config Config
|
config Config
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDialer(dialer N.Dialer, config Config) N.Dialer {
|
func NewDialer(dialer N.Dialer, config Config) Dialer {
|
||||||
return &Dialer{dialer, config}
|
return &defaultDialer{dialer, config}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
func (d *defaultDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||||
if network != N.NetworkTCP {
|
if N.NetworkName(network) != N.NetworkTCP {
|
||||||
return nil, os.ErrInvalid
|
return nil, os.ErrInvalid
|
||||||
}
|
}
|
||||||
conn, err := d.dialer.DialContext(ctx, network, destination)
|
return d.DialTLSContext(ctx, destination)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *defaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||||
|
return nil, os.ErrInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *defaultDialer) DialTLSContext(ctx context.Context, destination M.Socksaddr) (Conn, error) {
|
||||||
|
return d.dialContext(ctx, destination, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *defaultDialer) dialContext(ctx context.Context, destination M.Socksaddr, echRetry bool) (Conn, error) {
|
||||||
|
conn, err := d.dialer.DialContext(ctx, N.NetworkTCP, destination)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return ClientHandshake(ctx, conn, d.config)
|
tlsConn, err := ClientHandshake(ctx, conn, d.config)
|
||||||
|
if err == nil {
|
||||||
|
return tlsConn, nil
|
||||||
|
}
|
||||||
|
conn.Close()
|
||||||
|
if echRetry {
|
||||||
|
var echErr *tls.ECHRejectionError
|
||||||
|
if errors.As(err, &echErr) && len(echErr.RetryConfigList) > 0 {
|
||||||
|
if echConfig, isECH := d.config.(ECHCapableConfig); isECH {
|
||||||
|
echConfig.SetECHConfigList(echErr.RetryConfigList)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return d.dialContext(ctx, destination, false)
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
func (d *defaultDialer) Upstream() any {
|
||||||
return nil, os.ErrInvalid
|
return d.dialer
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,8 @@ const (
|
|||||||
RuleSetVersion1 = 1 + iota
|
RuleSetVersion1 = 1 + iota
|
||||||
RuleSetVersion2
|
RuleSetVersion2
|
||||||
RuleSetVersion3
|
RuleSetVersion3
|
||||||
RuleSetVersionCurrent = RuleSetVersion3
|
RuleSetVersion4
|
||||||
|
RuleSetVersionCurrent = RuleSetVersion4
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -30,7 +30,7 @@ func RegisterTLS(registry *dns.TransportRegistry) {
|
|||||||
type TLSTransport struct {
|
type TLSTransport struct {
|
||||||
dns.TransportAdapter
|
dns.TransportAdapter
|
||||||
logger logger.ContextLogger
|
logger logger.ContextLogger
|
||||||
dialer N.Dialer
|
dialer tls.Dialer
|
||||||
serverAddr M.Socksaddr
|
serverAddr M.Socksaddr
|
||||||
tlsConfig tls.Config
|
tlsConfig tls.Config
|
||||||
access sync.Mutex
|
access sync.Mutex
|
||||||
@ -67,7 +67,7 @@ func NewTLSRaw(logger logger.ContextLogger, adapter dns.TransportAdapter, dialer
|
|||||||
return &TLSTransport{
|
return &TLSTransport{
|
||||||
TransportAdapter: adapter,
|
TransportAdapter: adapter,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
dialer: dialer,
|
dialer: tls.NewDialer(dialer, tlsConfig),
|
||||||
serverAddr: serverAddr,
|
serverAddr: serverAddr,
|
||||||
tlsConfig: tlsConfig,
|
tlsConfig: tlsConfig,
|
||||||
}
|
}
|
||||||
@ -100,15 +100,10 @@ func (t *TLSTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.M
|
|||||||
return response, nil
|
return response, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tcpConn, err := t.dialer.DialContext(ctx, N.NetworkTCP, t.serverAddr)
|
tlsConn, err := t.dialer.DialTLSContext(ctx, t.serverAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
tlsConn, err := tls.ClientHandshake(ctx, tcpConn, t.tlsConfig)
|
|
||||||
if err != nil {
|
|
||||||
tcpConn.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return t.exchange(message, &tlsDNSConn{Conn: tlsConn})
|
return t.exchange(message, &tlsDNSConn{Conn: tlsConn})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,11 +2,35 @@
|
|||||||
icon: material/alert-decagram
|
icon: material/alert-decagram
|
||||||
---
|
---
|
||||||
|
|
||||||
|
#### 1.13.0-alpha.2
|
||||||
|
|
||||||
|
* Add `preferred_by` rule item **1**
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
The new `preferred_by` routing rule item allows you to
|
||||||
|
match preferred domains and addresses for specific outbounds.
|
||||||
|
|
||||||
|
See [Route Rule](/configuration/route/rule/#preferred_by).
|
||||||
|
|
||||||
|
#### 1.13.0-alpha.1
|
||||||
|
|
||||||
|
* Add interface address rule items **1**
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
New interface address rules allow you to dynamically adjust rules based on your network environment.
|
||||||
|
|
||||||
|
See [Route Rule](/configuration/route/rule/), [DNS Route Rule](/configuration/dns/rule/)
|
||||||
|
and [Headless Rule](/configuration/rule-set/headless-rule/).
|
||||||
|
|
||||||
#### 1.12.1
|
#### 1.12.1
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
#### 1.12.0
|
### 1.12.0
|
||||||
|
|
||||||
* Refactor DNS servers **1**
|
* Refactor DNS servers **1**
|
||||||
* Add domain resolver options**2**
|
* Add domain resolver options**2**
|
||||||
@ -157,7 +181,7 @@ We continue to experience issues updating our sing-box apps on the App Store and
|
|||||||
Until we rewrite and resubmit the apps, they are considered irrecoverable.
|
Until we rewrite and resubmit the apps, they are considered irrecoverable.
|
||||||
Therefore, after this release, we will not be repeating this notice unless there is new information.
|
Therefore, after this release, we will not be repeating this notice unless there is new information.
|
||||||
|
|
||||||
### 1.11.15
|
#### 1.11.15
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
@ -173,7 +197,7 @@ violated the rules (TestFlight users are not affected)._
|
|||||||
|
|
||||||
We have significantly improved the performance of tun inbound on Apple platforms, especially in the gVisor stack.
|
We have significantly improved the performance of tun inbound on Apple platforms, especially in the gVisor stack.
|
||||||
|
|
||||||
### 1.11.14
|
#### 1.11.14
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
@ -223,7 +247,7 @@ You can now choose what the DERP home page shows, just like with derper's `-home
|
|||||||
|
|
||||||
See [DERP](/configuration/service/derp/#home).
|
See [DERP](/configuration/service/derp/#home).
|
||||||
|
|
||||||
### 1.11.13
|
#### 1.11.13
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
@ -261,7 +285,7 @@ SSM API service is a RESTful API server for managing Shadowsocks servers.
|
|||||||
|
|
||||||
See [SSM API Service](/configuration/service/ssm-api/).
|
See [SSM API Service](/configuration/service/ssm-api/).
|
||||||
|
|
||||||
### 1.11.11
|
#### 1.11.11
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
@ -293,7 +317,7 @@ You can now set `bind_interface`, `routing_mark` and `reuse_addr` in Listen Fiel
|
|||||||
|
|
||||||
See [Listen Fields](/configuration/shared/listen/).
|
See [Listen Fields](/configuration/shared/listen/).
|
||||||
|
|
||||||
### 1.11.10
|
#### 1.11.10
|
||||||
|
|
||||||
* Undeprecate the `block` outbound **1**
|
* Undeprecate the `block` outbound **1**
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
@ -311,7 +335,7 @@ violated the rules (TestFlight users are not affected)._
|
|||||||
* Update quic-go to v0.51.0
|
* Update quic-go to v0.51.0
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
### 1.11.9
|
#### 1.11.9
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
@ -322,7 +346,7 @@ violated the rules (TestFlight users are not affected)._
|
|||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
### 1.11.8
|
#### 1.11.8
|
||||||
|
|
||||||
* Improve `auto_redirect` **1**
|
* Improve `auto_redirect` **1**
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
@ -339,7 +363,7 @@ violated the rules (TestFlight users are not affected)._
|
|||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
### 1.11.7
|
#### 1.11.7
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
@ -355,7 +379,7 @@ violated the rules (TestFlight users are not affected)._
|
|||||||
Now `auto_redirect` fixes compatibility issues between tun and Docker bridge networks,
|
Now `auto_redirect` fixes compatibility issues between tun and Docker bridge networks,
|
||||||
see [Tun](/configuration/inbound/tun/#auto_redirect).
|
see [Tun](/configuration/inbound/tun/#auto_redirect).
|
||||||
|
|
||||||
### 1.11.6
|
#### 1.11.6
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
@ -396,7 +420,7 @@ See [Protocol Sniff](/configuration/route/sniff/).
|
|||||||
|
|
||||||
See [Dial Fields](/configuration/shared/dial/#domain_resolver).
|
See [Dial Fields](/configuration/shared/dial/#domain_resolver).
|
||||||
|
|
||||||
### 1.11.5
|
#### 1.11.5
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
@ -412,7 +436,7 @@ violated the rules (TestFlight users are not affected)._
|
|||||||
|
|
||||||
See [DNS Rule Action](/configuration/dns/rule_action/#predefined).
|
See [DNS Rule Action](/configuration/dns/rule_action/#predefined).
|
||||||
|
|
||||||
### 1.11.4
|
#### 1.11.4
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
@ -468,7 +492,7 @@ Due to maintenance difficulties, sing-box 1.12.0 requires at least Go 1.23 to co
|
|||||||
|
|
||||||
For Windows 7 users, legacy binaries now continue to compile with Go 1.23 and patches from [MetaCubeX/go](https://github.com/MetaCubeX/go).
|
For Windows 7 users, legacy binaries now continue to compile with Go 1.23 and patches from [MetaCubeX/go](https://github.com/MetaCubeX/go).
|
||||||
|
|
||||||
### 1.11.3
|
#### 1.11.3
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
@ -479,7 +503,7 @@ process._
|
|||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
### 1.11.1
|
#### 1.11.1
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
@ -658,7 +682,7 @@ See [Hysteria2](/configuration/outbound/hysteria2/).
|
|||||||
|
|
||||||
When `up_mbps` and `down_mbps` are set, `ignore_client_bandwidth` instead denies clients from using BBR CC.
|
When `up_mbps` and `down_mbps` are set, `ignore_client_bandwidth` instead denies clients from using BBR CC.
|
||||||
|
|
||||||
### 1.10.7
|
#### 1.10.7
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
@ -753,7 +777,7 @@ and the old outbound will be removed in sing-box 1.13.0.
|
|||||||
See [Endpoint](/configuration/endpoint/), [WireGuard Endpoint](/configuration/endpoint/wireguard/)
|
See [Endpoint](/configuration/endpoint/), [WireGuard Endpoint](/configuration/endpoint/wireguard/)
|
||||||
and [Migrate WireGuard outbound fields to route options](/migration/#migrate-wireguard-outbound-to-endpoint).
|
and [Migrate WireGuard outbound fields to route options](/migration/#migrate-wireguard-outbound-to-endpoint).
|
||||||
|
|
||||||
### 1.10.2
|
#### 1.10.2
|
||||||
|
|
||||||
* Add deprecated warnings
|
* Add deprecated warnings
|
||||||
* Fix proxying websocket connections in HTTP/mixed inbounds
|
* Fix proxying websocket connections in HTTP/mixed inbounds
|
||||||
@ -890,7 +914,7 @@ See [Rule Action](/configuration/route/rule_action/).
|
|||||||
* Update quic-go to v0.48.0
|
* Update quic-go to v0.48.0
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
### 1.10.1
|
#### 1.10.1
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
|
@ -2,6 +2,12 @@
|
|||||||
icon: material/alert-decagram
|
icon: material/alert-decagram
|
||||||
---
|
---
|
||||||
|
|
||||||
|
!!! quote "Changes in sing-box 1.13.0"
|
||||||
|
|
||||||
|
:material-plus: [interface_address](#interface_address)
|
||||||
|
:material-plus: [network_interface_address](#network_interface_address)
|
||||||
|
:material-plus: [default_interface_address](#default_interface_address)
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.12.0"
|
!!! quote "Changes in sing-box 1.12.0"
|
||||||
|
|
||||||
:material-plus: [ip_accept_any](#ip_accept_any)
|
:material-plus: [ip_accept_any](#ip_accept_any)
|
||||||
@ -130,6 +136,19 @@ icon: material/alert-decagram
|
|||||||
],
|
],
|
||||||
"network_is_expensive": false,
|
"network_is_expensive": false,
|
||||||
"network_is_constrained": false,
|
"network_is_constrained": false,
|
||||||
|
"interface_address": {
|
||||||
|
"en0": [
|
||||||
|
"2000::/3"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"network_interface_address": {
|
||||||
|
"wifi": [
|
||||||
|
"2000::/3"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default_interface_address": [
|
||||||
|
"2000::/3"
|
||||||
|
],
|
||||||
"wifi_ssid": [
|
"wifi_ssid": [
|
||||||
"My WIFI"
|
"My WIFI"
|
||||||
],
|
],
|
||||||
@ -359,6 +378,36 @@ such as Cellular or a Personal Hotspot (on Apple platforms).
|
|||||||
|
|
||||||
Match if network is in Low Data Mode.
|
Match if network is in Low Data Mode.
|
||||||
|
|
||||||
|
#### interface_address
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.13.0"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
Only supported on Linux, Windows, and macOS.
|
||||||
|
|
||||||
|
Match interface address.
|
||||||
|
|
||||||
|
#### network_interface_address
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.13.0"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
Only supported in graphical clients on Android and Apple platforms.
|
||||||
|
|
||||||
|
Matches network interface (same values as `network_type`) address.
|
||||||
|
|
||||||
|
#### default_interface_address
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.13.0"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
Only supported on Linux, Windows, and macOS.
|
||||||
|
|
||||||
|
Match default interface address.
|
||||||
|
|
||||||
#### wifi_ssid
|
#### wifi_ssid
|
||||||
|
|
||||||
!!! quote ""
|
!!! quote ""
|
||||||
|
@ -2,6 +2,12 @@
|
|||||||
icon: material/alert-decagram
|
icon: material/alert-decagram
|
||||||
---
|
---
|
||||||
|
|
||||||
|
!!! quote "sing-box 1.13.0 中的更改"
|
||||||
|
|
||||||
|
:material-plus: [interface_address](#interface_address)
|
||||||
|
:material-plus: [network_interface_address](#network_interface_address)
|
||||||
|
:material-plus: [default_interface_address](#default_interface_address)
|
||||||
|
|
||||||
!!! quote "sing-box 1.12.0 中的更改"
|
!!! quote "sing-box 1.12.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [ip_accept_any](#ip_accept_any)
|
:material-plus: [ip_accept_any](#ip_accept_any)
|
||||||
@ -130,6 +136,19 @@ icon: material/alert-decagram
|
|||||||
],
|
],
|
||||||
"network_is_expensive": false,
|
"network_is_expensive": false,
|
||||||
"network_is_constrained": false,
|
"network_is_constrained": false,
|
||||||
|
"interface_address": {
|
||||||
|
"en0": [
|
||||||
|
"2000::/3"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"network_interface_address": {
|
||||||
|
"wifi": [
|
||||||
|
"2000::/3"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default_interface_address": [
|
||||||
|
"2000::/3"
|
||||||
|
],
|
||||||
"wifi_ssid": [
|
"wifi_ssid": [
|
||||||
"My WIFI"
|
"My WIFI"
|
||||||
],
|
],
|
||||||
@ -358,6 +377,36 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
|
|||||||
|
|
||||||
匹配如果网络在低数据模式下。
|
匹配如果网络在低数据模式下。
|
||||||
|
|
||||||
|
#### interface_address
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.13.0 起"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
仅支持 Linux、Windows 和 macOS.
|
||||||
|
|
||||||
|
匹配接口地址。
|
||||||
|
|
||||||
|
#### network_interface_address
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.13.0 起"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
仅在 Android 与 Apple 平台图形客户端中支持。
|
||||||
|
|
||||||
|
匹配网络接口(可用值同 `network_type`)地址。
|
||||||
|
|
||||||
|
#### default_interface_address
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.13.0 起"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
仅支持 Linux、Windows 和 macOS.
|
||||||
|
|
||||||
|
匹配默认接口地址。
|
||||||
|
|
||||||
#### wifi_ssid
|
#### wifi_ssid
|
||||||
|
|
||||||
!!! quote ""
|
!!! quote ""
|
||||||
|
@ -2,6 +2,13 @@
|
|||||||
icon: material/new-box
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
|
!!! quote "Changes in sing-box 1.13.0"
|
||||||
|
|
||||||
|
:material-plus: [interface_address](#interface_address)
|
||||||
|
:material-plus: [network_interface_address](#network_interface_address)
|
||||||
|
:material-plus: [default_interface_address](#default_interface_address)
|
||||||
|
:material-plus: [preferred_by](#preferred_by)
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.11.0"
|
!!! quote "Changes in sing-box 1.11.0"
|
||||||
|
|
||||||
:material-plus: [action](#action)
|
:material-plus: [action](#action)
|
||||||
@ -128,12 +135,29 @@ icon: material/new-box
|
|||||||
],
|
],
|
||||||
"network_is_expensive": false,
|
"network_is_expensive": false,
|
||||||
"network_is_constrained": false,
|
"network_is_constrained": false,
|
||||||
|
"interface_address": {
|
||||||
|
"en0": [
|
||||||
|
"2000::/3"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"network_interface_address": {
|
||||||
|
"wifi": [
|
||||||
|
"2000::/3"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default_interface_address": [
|
||||||
|
"2000::/3"
|
||||||
|
],
|
||||||
"wifi_ssid": [
|
"wifi_ssid": [
|
||||||
"My WIFI"
|
"My WIFI"
|
||||||
],
|
],
|
||||||
"wifi_bssid": [
|
"wifi_bssid": [
|
||||||
"00:00:00:00:00:00"
|
"00:00:00:00:00:00"
|
||||||
],
|
],
|
||||||
|
"preferred_by": [
|
||||||
|
"tailscale",
|
||||||
|
"wireguard"
|
||||||
|
],
|
||||||
"rule_set": [
|
"rule_set": [
|
||||||
"geoip-cn",
|
"geoip-cn",
|
||||||
"geosite-cn"
|
"geosite-cn"
|
||||||
@ -363,6 +387,36 @@ such as Cellular or a Personal Hotspot (on Apple platforms).
|
|||||||
|
|
||||||
Match if network is in Low Data Mode.
|
Match if network is in Low Data Mode.
|
||||||
|
|
||||||
|
#### interface_address
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.13.0"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
Only supported on Linux, Windows, and macOS.
|
||||||
|
|
||||||
|
Match interface address.
|
||||||
|
|
||||||
|
#### network_interface_address
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.13.0"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
Only supported in graphical clients on Android and Apple platforms.
|
||||||
|
|
||||||
|
Matches network interface (same values as `network_type`) address.
|
||||||
|
|
||||||
|
#### default_interface_address
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.13.0"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
Only supported on Linux, Windows, and macOS.
|
||||||
|
|
||||||
|
Match default interface address.
|
||||||
|
|
||||||
#### wifi_ssid
|
#### wifi_ssid
|
||||||
|
|
||||||
!!! quote ""
|
!!! quote ""
|
||||||
@ -379,6 +433,17 @@ Match WiFi SSID.
|
|||||||
|
|
||||||
Match WiFi BSSID.
|
Match WiFi BSSID.
|
||||||
|
|
||||||
|
#### preferred_by
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.13.0"
|
||||||
|
|
||||||
|
Match specified outbounds' preferred routes.
|
||||||
|
|
||||||
|
| Type | Match |
|
||||||
|
|-------------|-----------------------------------------------|
|
||||||
|
| `tailscale` | Match MagicDNS domains and peers' allowed IPs |
|
||||||
|
| `wireguard` | Match peers's allowed IPs |
|
||||||
|
|
||||||
#### rule_set
|
#### rule_set
|
||||||
|
|
||||||
!!! question "Since sing-box 1.8.0"
|
!!! question "Since sing-box 1.8.0"
|
||||||
|
@ -2,6 +2,13 @@
|
|||||||
icon: material/new-box
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
|
!!! quote "sing-box 1.13.0 中的更改"
|
||||||
|
|
||||||
|
:material-plus: [interface_address](#interface_address)
|
||||||
|
:material-plus: [network_interface_address](#network_interface_address)
|
||||||
|
:material-plus: [default_interface_address](#default_interface_address)
|
||||||
|
:material-plus: [preferred_by](#preferred_by)
|
||||||
|
|
||||||
!!! quote "sing-box 1.11.0 中的更改"
|
!!! quote "sing-box 1.11.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [action](#action)
|
:material-plus: [action](#action)
|
||||||
@ -125,12 +132,29 @@ icon: material/new-box
|
|||||||
],
|
],
|
||||||
"network_is_expensive": false,
|
"network_is_expensive": false,
|
||||||
"network_is_constrained": false,
|
"network_is_constrained": false,
|
||||||
|
"interface_address": {
|
||||||
|
"en0": [
|
||||||
|
"2000::/3"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"network_interface_address": {
|
||||||
|
"wifi": [
|
||||||
|
"2000::/3"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default_interface_address": [
|
||||||
|
"2000::/3"
|
||||||
|
],
|
||||||
"wifi_ssid": [
|
"wifi_ssid": [
|
||||||
"My WIFI"
|
"My WIFI"
|
||||||
],
|
],
|
||||||
"wifi_bssid": [
|
"wifi_bssid": [
|
||||||
"00:00:00:00:00:00"
|
"00:00:00:00:00:00"
|
||||||
],
|
],
|
||||||
|
"preferred_by": [
|
||||||
|
"tailscale",
|
||||||
|
"wireguard"
|
||||||
|
],
|
||||||
"rule_set": [
|
"rule_set": [
|
||||||
"geoip-cn",
|
"geoip-cn",
|
||||||
"geosite-cn"
|
"geosite-cn"
|
||||||
@ -337,7 +361,7 @@ icon: material/new-box
|
|||||||
|
|
||||||
匹配网络类型。
|
匹配网络类型。
|
||||||
|
|
||||||
Available values: `wifi`, `cellular`, `ethernet` and `other`.
|
可用值: `wifi`, `cellular`, `ethernet` and `other`.
|
||||||
|
|
||||||
#### network_is_expensive
|
#### network_is_expensive
|
||||||
|
|
||||||
@ -360,6 +384,36 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
|
|||||||
|
|
||||||
匹配如果网络在低数据模式下。
|
匹配如果网络在低数据模式下。
|
||||||
|
|
||||||
|
#### interface_address
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.13.0 起"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
仅支持 Linux、Windows 和 macOS.
|
||||||
|
|
||||||
|
匹配接口地址。
|
||||||
|
|
||||||
|
#### network_interface_address
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.13.0 起"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
仅在 Android 与 Apple 平台图形客户端中支持。
|
||||||
|
|
||||||
|
匹配网络接口(可用值同 `network_type`)地址。
|
||||||
|
|
||||||
|
#### default_interface_address
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.13.0 起"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
仅支持 Linux、Windows 和 macOS.
|
||||||
|
|
||||||
|
匹配默认接口地址。
|
||||||
|
|
||||||
#### wifi_ssid
|
#### wifi_ssid
|
||||||
|
|
||||||
!!! quote ""
|
!!! quote ""
|
||||||
@ -376,6 +430,17 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
|
|||||||
|
|
||||||
匹配 WiFi BSSID。
|
匹配 WiFi BSSID。
|
||||||
|
|
||||||
|
#### preferred_by
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.13.0 起"
|
||||||
|
|
||||||
|
匹配制定出站的首选路由。
|
||||||
|
|
||||||
|
| 类型 | 匹配 |
|
||||||
|
|-------------|--------------------------------|
|
||||||
|
| `tailscale` | 匹配 MagicDNS 域名和对端的 allowed IPs |
|
||||||
|
| `wireguard` | 匹配对端的 allowed IPs |
|
||||||
|
|
||||||
#### rule_set
|
#### rule_set
|
||||||
|
|
||||||
!!! question "自 sing-box 1.8.0 起"
|
!!! question "自 sing-box 1.8.0 起"
|
||||||
|
@ -2,6 +2,11 @@
|
|||||||
icon: material/new-box
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
|
!!! quote "Changes in sing-box 1.13.0"
|
||||||
|
|
||||||
|
:material-plus: [network_interface_address](#network_interface_address)
|
||||||
|
:material-plus: [default_interface_address](#default_interface_address)
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.11.0"
|
!!! quote "Changes in sing-box 1.11.0"
|
||||||
|
|
||||||
:material-plus: [network_type](#network_type)
|
:material-plus: [network_type](#network_type)
|
||||||
@ -78,6 +83,14 @@ icon: material/new-box
|
|||||||
],
|
],
|
||||||
"network_is_expensive": false,
|
"network_is_expensive": false,
|
||||||
"network_is_constrained": false,
|
"network_is_constrained": false,
|
||||||
|
"network_interface_address": {
|
||||||
|
"wifi": [
|
||||||
|
"2000::/3"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default_interface_address": [
|
||||||
|
"2000::/3"
|
||||||
|
],
|
||||||
"wifi_ssid": [
|
"wifi_ssid": [
|
||||||
"My WIFI"
|
"My WIFI"
|
||||||
],
|
],
|
||||||
@ -225,6 +238,26 @@ such as Cellular or a Personal Hotspot (on Apple platforms).
|
|||||||
|
|
||||||
Match if network is in Low Data Mode.
|
Match if network is in Low Data Mode.
|
||||||
|
|
||||||
|
#### network_interface_address
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.13.0"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
Only supported in graphical clients on Android and Apple platforms.
|
||||||
|
|
||||||
|
Matches network interface (same values as `network_type`) address.
|
||||||
|
|
||||||
|
#### default_interface_address
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.13.0"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
Only supported on Linux, Windows, and macOS.
|
||||||
|
|
||||||
|
Match default interface address.
|
||||||
|
|
||||||
#### wifi_ssid
|
#### wifi_ssid
|
||||||
|
|
||||||
!!! quote ""
|
!!! quote ""
|
||||||
|
@ -2,6 +2,11 @@
|
|||||||
icon: material/new-box
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
|
!!! quote "sing-box 1.13.0 中的更改"
|
||||||
|
|
||||||
|
:material-plus: [network_interface_address](#network_interface_address)
|
||||||
|
:material-plus: [default_interface_address](#default_interface_address)
|
||||||
|
|
||||||
!!! quote "sing-box 1.11.0 中的更改"
|
!!! quote "sing-box 1.11.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [network_type](#network_type)
|
:material-plus: [network_type](#network_type)
|
||||||
@ -78,6 +83,14 @@ icon: material/new-box
|
|||||||
],
|
],
|
||||||
"network_is_expensive": false,
|
"network_is_expensive": false,
|
||||||
"network_is_constrained": false,
|
"network_is_constrained": false,
|
||||||
|
"network_interface_address": {
|
||||||
|
"wifi": [
|
||||||
|
"2000::/3"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default_interface_address": [
|
||||||
|
"2000::/3"
|
||||||
|
],
|
||||||
"wifi_ssid": [
|
"wifi_ssid": [
|
||||||
"My WIFI"
|
"My WIFI"
|
||||||
],
|
],
|
||||||
@ -221,6 +234,26 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
|
|||||||
|
|
||||||
匹配如果网络在低数据模式下。
|
匹配如果网络在低数据模式下。
|
||||||
|
|
||||||
|
#### network_interface_address
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.13.0 起"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
仅在 Android 与 Apple 平台图形客户端中支持。
|
||||||
|
|
||||||
|
匹配网络接口(可用值同 `network_type`)地址。
|
||||||
|
|
||||||
|
#### default_interface_address
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.13.0 起"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
仅支持 Linux、Windows 和 macOS.
|
||||||
|
|
||||||
|
匹配默认接口地址。
|
||||||
|
|
||||||
#### wifi_ssid
|
#### wifi_ssid
|
||||||
|
|
||||||
!!! quote ""
|
!!! quote ""
|
||||||
|
@ -2,6 +2,10 @@
|
|||||||
icon: material/new-box
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
|
!!! quote "Changes in sing-box 1.13.0"
|
||||||
|
|
||||||
|
:material-plus: version `4`
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.11.0"
|
!!! quote "Changes in sing-box 1.11.0"
|
||||||
|
|
||||||
:material-plus: version `3`
|
:material-plus: version `3`
|
||||||
@ -36,6 +40,7 @@ Version of rule-set.
|
|||||||
* 1: sing-box 1.8.0: Initial rule-set version.
|
* 1: sing-box 1.8.0: Initial rule-set version.
|
||||||
* 2: sing-box 1.10.0: Optimized memory usages of `domain_suffix` rules in binary rule-sets.
|
* 2: sing-box 1.10.0: Optimized memory usages of `domain_suffix` rules in binary rule-sets.
|
||||||
* 3: sing-box 1.11.0: Added `network_type`, `network_is_expensive` and `network_is_constrainted` rule items.
|
* 3: sing-box 1.11.0: Added `network_type`, `network_is_expensive` and `network_is_constrainted` rule items.
|
||||||
|
* 4: sing-box 1.13.0: Added `network_interface_address` and `default_interface_address` rule items.
|
||||||
|
|
||||||
#### rules
|
#### rules
|
||||||
|
|
||||||
|
@ -2,6 +2,10 @@
|
|||||||
icon: material/new-box
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
|
!!! quote "sing-box 1.13.0 中的更改"
|
||||||
|
|
||||||
|
:material-plus: version `4`
|
||||||
|
|
||||||
!!! quote "sing-box 1.11.0 中的更改"
|
!!! quote "sing-box 1.11.0 中的更改"
|
||||||
|
|
||||||
:material-plus: version `3`
|
:material-plus: version `3`
|
||||||
@ -36,6 +40,7 @@ icon: material/new-box
|
|||||||
* 1: sing-box 1.8.0: 初始规则集版本。
|
* 1: sing-box 1.8.0: 初始规则集版本。
|
||||||
* 2: sing-box 1.10.0: 优化了二进制规则集中 `domain_suffix` 规则的内存使用。
|
* 2: sing-box 1.10.0: 优化了二进制规则集中 `domain_suffix` 规则的内存使用。
|
||||||
* 3: sing-box 1.11.0: 添加了 `network_type`、 `network_is_expensive` 和 `network_is_constrainted` 规则项。
|
* 3: sing-box 1.11.0: 添加了 `network_type`、 `network_is_expensive` 和 `network_is_constrainted` 规则项。
|
||||||
|
* 4: sing-box 1.13.0: 添加了 `network_interface_address` 和 `default_interface_address` 规则项。
|
||||||
|
|
||||||
#### rules
|
#### rules
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ import (
|
|||||||
func cacheRouter(ctx context.Context) http.Handler {
|
func cacheRouter(ctx context.Context) http.Handler {
|
||||||
r := chi.NewRouter()
|
r := chi.NewRouter()
|
||||||
r.Post("/fakeip/flush", flushFakeip(ctx))
|
r.Post("/fakeip/flush", flushFakeip(ctx))
|
||||||
|
r.Post("/dns/flush", flushDNS(ctx))
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,3 +32,13 @@ func flushFakeip(ctx context.Context) func(w http.ResponseWriter, r *http.Reques
|
|||||||
render.NoContent(w, r)
|
render.NoContent(w, r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func flushDNS(ctx context.Context) func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
dnsRouter := service.FromContext[adapter.DNSRouter](ctx)
|
||||||
|
if dnsRouter != nil {
|
||||||
|
dnsRouter.ClearCache()
|
||||||
|
}
|
||||||
|
render.NoContent(w, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -115,7 +115,7 @@ func (s *StatsService) RoutedPacketConnection(ctx context.Context, conn N.Packet
|
|||||||
writeCounter = append(writeCounter, s.loadOrCreateCounter("user>>>"+user+">>>traffic>>>downlink"))
|
writeCounter = append(writeCounter, s.loadOrCreateCounter("user>>>"+user+">>>traffic>>>downlink"))
|
||||||
}
|
}
|
||||||
s.access.Unlock()
|
s.access.Unlock()
|
||||||
return bufio.NewInt64CounterPacketConn(conn, readCounter, writeCounter)
|
return bufio.NewInt64CounterPacketConn(conn, readCounter, nil, writeCounter, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StatsService) GetStats(ctx context.Context, request *GetStatsRequest) (*GetStatsResponse, error) {
|
func (s *StatsService) GetStats(ctx context.Context, request *GetStatsRequest) (*GetStatsResponse, error) {
|
||||||
|
4
go.mod
4
go.mod
@ -27,9 +27,9 @@ require (
|
|||||||
github.com/sagernet/gomobile v0.1.8
|
github.com/sagernet/gomobile v0.1.8
|
||||||
github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb
|
github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb
|
||||||
github.com/sagernet/quic-go v0.52.0-beta.1
|
github.com/sagernet/quic-go v0.52.0-beta.1
|
||||||
github.com/sagernet/sing v0.7.5
|
github.com/sagernet/sing v0.7.6-0.20250815070458-d33ece7a184f
|
||||||
github.com/sagernet/sing-mux v0.3.3
|
github.com/sagernet/sing-mux v0.3.3
|
||||||
github.com/sagernet/sing-quic v0.5.0-beta.3
|
github.com/sagernet/sing-quic v0.5.0
|
||||||
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
|
||||||
|
8
go.sum
8
go.sum
@ -167,12 +167,12 @@ github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/l
|
|||||||
github.com/sagernet/quic-go v0.52.0-beta.1 h1:hWkojLg64zjV+MJOvJU/kOeWndm3tiEfBLx5foisszs=
|
github.com/sagernet/quic-go v0.52.0-beta.1 h1:hWkojLg64zjV+MJOvJU/kOeWndm3tiEfBLx5foisszs=
|
||||||
github.com/sagernet/quic-go v0.52.0-beta.1/go.mod h1:OV+V5kEBb8kJS7k29MzDu6oj9GyMc7HA07sE1tedxz4=
|
github.com/sagernet/quic-go v0.52.0-beta.1/go.mod h1:OV+V5kEBb8kJS7k29MzDu6oj9GyMc7HA07sE1tedxz4=
|
||||||
github.com/sagernet/sing v0.6.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
github.com/sagernet/sing v0.6.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||||
github.com/sagernet/sing v0.7.5 h1:gNMwZCLPqR+4e0g6dwi0sSsrvOmoMjpZgqxKsuJZatc=
|
github.com/sagernet/sing v0.7.6-0.20250815070458-d33ece7a184f h1:HIBo8l+tsS3wLwuI1E56uRTQw46QytXSUpZTP3vwG/U=
|
||||||
github.com/sagernet/sing v0.7.5/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
github.com/sagernet/sing v0.7.6-0.20250815070458-d33ece7a184f/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||||
github.com/sagernet/sing-mux v0.3.3 h1:YFgt9plMWzH994BMZLmyKL37PdIVaIilwP0Jg+EcLfw=
|
github.com/sagernet/sing-mux v0.3.3 h1:YFgt9plMWzH994BMZLmyKL37PdIVaIilwP0Jg+EcLfw=
|
||||||
github.com/sagernet/sing-mux v0.3.3/go.mod h1:pht8iFY4c9Xltj7rhVd208npkNaeCxzyXCgulDPLUDA=
|
github.com/sagernet/sing-mux v0.3.3/go.mod h1:pht8iFY4c9Xltj7rhVd208npkNaeCxzyXCgulDPLUDA=
|
||||||
github.com/sagernet/sing-quic v0.5.0-beta.3 h1:X/acRNsqQNfDlmwE7SorHfaZiny5e67hqIzM/592ric=
|
github.com/sagernet/sing-quic v0.5.0 h1:jNLIyVk24lFPvu8A4x+ZNEnZdI+Tg1rp7eCJ6v0Csak=
|
||||||
github.com/sagernet/sing-quic v0.5.0-beta.3/go.mod h1:SAv/qdeDN+75msGG5U5ZIwG+3Ua50jVIKNrRSY8pkx0=
|
github.com/sagernet/sing-quic v0.5.0/go.mod h1:SAv/qdeDN+75msGG5U5ZIwG+3Ua50jVIKNrRSY8pkx0=
|
||||||
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=
|
||||||
|
@ -67,42 +67,46 @@ func (r Rule) IsValid() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type RawDefaultRule struct {
|
type RawDefaultRule struct {
|
||||||
Inbound badoption.Listable[string] `json:"inbound,omitempty"`
|
Inbound badoption.Listable[string] `json:"inbound,omitempty"`
|
||||||
IPVersion int `json:"ip_version,omitempty"`
|
IPVersion int `json:"ip_version,omitempty"`
|
||||||
Network badoption.Listable[string] `json:"network,omitempty"`
|
Network badoption.Listable[string] `json:"network,omitempty"`
|
||||||
AuthUser badoption.Listable[string] `json:"auth_user,omitempty"`
|
AuthUser badoption.Listable[string] `json:"auth_user,omitempty"`
|
||||||
Protocol badoption.Listable[string] `json:"protocol,omitempty"`
|
Protocol badoption.Listable[string] `json:"protocol,omitempty"`
|
||||||
Client badoption.Listable[string] `json:"client,omitempty"`
|
Client badoption.Listable[string] `json:"client,omitempty"`
|
||||||
Domain badoption.Listable[string] `json:"domain,omitempty"`
|
Domain badoption.Listable[string] `json:"domain,omitempty"`
|
||||||
DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"`
|
DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"`
|
||||||
DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"`
|
DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"`
|
||||||
DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"`
|
DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"`
|
||||||
Geosite badoption.Listable[string] `json:"geosite,omitempty"`
|
Geosite badoption.Listable[string] `json:"geosite,omitempty"`
|
||||||
SourceGeoIP badoption.Listable[string] `json:"source_geoip,omitempty"`
|
SourceGeoIP badoption.Listable[string] `json:"source_geoip,omitempty"`
|
||||||
GeoIP badoption.Listable[string] `json:"geoip,omitempty"`
|
GeoIP badoption.Listable[string] `json:"geoip,omitempty"`
|
||||||
SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"`
|
SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"`
|
||||||
SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"`
|
SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"`
|
||||||
IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"`
|
IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"`
|
||||||
IPIsPrivate bool `json:"ip_is_private,omitempty"`
|
IPIsPrivate bool `json:"ip_is_private,omitempty"`
|
||||||
SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"`
|
SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"`
|
||||||
SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"`
|
SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"`
|
||||||
Port badoption.Listable[uint16] `json:"port,omitempty"`
|
Port badoption.Listable[uint16] `json:"port,omitempty"`
|
||||||
PortRange badoption.Listable[string] `json:"port_range,omitempty"`
|
PortRange badoption.Listable[string] `json:"port_range,omitempty"`
|
||||||
ProcessName badoption.Listable[string] `json:"process_name,omitempty"`
|
ProcessName badoption.Listable[string] `json:"process_name,omitempty"`
|
||||||
ProcessPath badoption.Listable[string] `json:"process_path,omitempty"`
|
ProcessPath badoption.Listable[string] `json:"process_path,omitempty"`
|
||||||
ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"`
|
ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"`
|
||||||
PackageName badoption.Listable[string] `json:"package_name,omitempty"`
|
PackageName badoption.Listable[string] `json:"package_name,omitempty"`
|
||||||
User badoption.Listable[string] `json:"user,omitempty"`
|
User badoption.Listable[string] `json:"user,omitempty"`
|
||||||
UserID badoption.Listable[int32] `json:"user_id,omitempty"`
|
UserID badoption.Listable[int32] `json:"user_id,omitempty"`
|
||||||
ClashMode string `json:"clash_mode,omitempty"`
|
ClashMode string `json:"clash_mode,omitempty"`
|
||||||
NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"`
|
NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"`
|
||||||
NetworkIsExpensive bool `json:"network_is_expensive,omitempty"`
|
NetworkIsExpensive bool `json:"network_is_expensive,omitempty"`
|
||||||
NetworkIsConstrained bool `json:"network_is_constrained,omitempty"`
|
NetworkIsConstrained bool `json:"network_is_constrained,omitempty"`
|
||||||
WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"`
|
WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"`
|
||||||
WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"`
|
WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"`
|
||||||
RuleSet badoption.Listable[string] `json:"rule_set,omitempty"`
|
InterfaceAddress *badjson.TypedMap[string, badoption.Listable[badoption.Prefixable]] `json:"interface_address,omitempty"`
|
||||||
RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"`
|
NetworkInterfaceAddress *badjson.TypedMap[InterfaceType, badoption.Listable[badoption.Prefixable]] `json:"network_interface_address,omitempty"`
|
||||||
Invert bool `json:"invert,omitempty"`
|
DefaultInterfaceAddress badoption.Listable[badoption.Prefixable] `json:"default_interface_address,omitempty"`
|
||||||
|
PreferredBy badoption.Listable[string] `json:"preferred_by,omitempty"`
|
||||||
|
RuleSet badoption.Listable[string] `json:"rule_set,omitempty"`
|
||||||
|
RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"`
|
||||||
|
Invert bool `json:"invert,omitempty"`
|
||||||
|
|
||||||
// Deprecated: renamed to rule_set_ip_cidr_match_source
|
// Deprecated: renamed to rule_set_ip_cidr_match_source
|
||||||
Deprecated_RulesetIPCIDRMatchSource bool `json:"rule_set_ipcidr_match_source,omitempty"`
|
Deprecated_RulesetIPCIDRMatchSource bool `json:"rule_set_ipcidr_match_source,omitempty"`
|
||||||
|
@ -68,45 +68,48 @@ func (r DNSRule) IsValid() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type RawDefaultDNSRule struct {
|
type RawDefaultDNSRule struct {
|
||||||
Inbound badoption.Listable[string] `json:"inbound,omitempty"`
|
Inbound badoption.Listable[string] `json:"inbound,omitempty"`
|
||||||
IPVersion int `json:"ip_version,omitempty"`
|
IPVersion int `json:"ip_version,omitempty"`
|
||||||
QueryType badoption.Listable[DNSQueryType] `json:"query_type,omitempty"`
|
QueryType badoption.Listable[DNSQueryType] `json:"query_type,omitempty"`
|
||||||
Network badoption.Listable[string] `json:"network,omitempty"`
|
Network badoption.Listable[string] `json:"network,omitempty"`
|
||||||
AuthUser badoption.Listable[string] `json:"auth_user,omitempty"`
|
AuthUser badoption.Listable[string] `json:"auth_user,omitempty"`
|
||||||
Protocol badoption.Listable[string] `json:"protocol,omitempty"`
|
Protocol badoption.Listable[string] `json:"protocol,omitempty"`
|
||||||
Domain badoption.Listable[string] `json:"domain,omitempty"`
|
Domain badoption.Listable[string] `json:"domain,omitempty"`
|
||||||
DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"`
|
DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"`
|
||||||
DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"`
|
DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"`
|
||||||
DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"`
|
DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"`
|
||||||
Geosite badoption.Listable[string] `json:"geosite,omitempty"`
|
Geosite badoption.Listable[string] `json:"geosite,omitempty"`
|
||||||
SourceGeoIP badoption.Listable[string] `json:"source_geoip,omitempty"`
|
SourceGeoIP badoption.Listable[string] `json:"source_geoip,omitempty"`
|
||||||
GeoIP badoption.Listable[string] `json:"geoip,omitempty"`
|
GeoIP badoption.Listable[string] `json:"geoip,omitempty"`
|
||||||
IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"`
|
IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"`
|
||||||
IPIsPrivate bool `json:"ip_is_private,omitempty"`
|
IPIsPrivate bool `json:"ip_is_private,omitempty"`
|
||||||
IPAcceptAny bool `json:"ip_accept_any,omitempty"`
|
IPAcceptAny bool `json:"ip_accept_any,omitempty"`
|
||||||
SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"`
|
SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"`
|
||||||
SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"`
|
SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"`
|
||||||
SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"`
|
SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"`
|
||||||
SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"`
|
SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"`
|
||||||
Port badoption.Listable[uint16] `json:"port,omitempty"`
|
Port badoption.Listable[uint16] `json:"port,omitempty"`
|
||||||
PortRange badoption.Listable[string] `json:"port_range,omitempty"`
|
PortRange badoption.Listable[string] `json:"port_range,omitempty"`
|
||||||
ProcessName badoption.Listable[string] `json:"process_name,omitempty"`
|
ProcessName badoption.Listable[string] `json:"process_name,omitempty"`
|
||||||
ProcessPath badoption.Listable[string] `json:"process_path,omitempty"`
|
ProcessPath badoption.Listable[string] `json:"process_path,omitempty"`
|
||||||
ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"`
|
ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"`
|
||||||
PackageName badoption.Listable[string] `json:"package_name,omitempty"`
|
PackageName badoption.Listable[string] `json:"package_name,omitempty"`
|
||||||
User badoption.Listable[string] `json:"user,omitempty"`
|
User badoption.Listable[string] `json:"user,omitempty"`
|
||||||
UserID badoption.Listable[int32] `json:"user_id,omitempty"`
|
UserID badoption.Listable[int32] `json:"user_id,omitempty"`
|
||||||
Outbound badoption.Listable[string] `json:"outbound,omitempty"`
|
Outbound badoption.Listable[string] `json:"outbound,omitempty"`
|
||||||
ClashMode string `json:"clash_mode,omitempty"`
|
ClashMode string `json:"clash_mode,omitempty"`
|
||||||
NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"`
|
NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"`
|
||||||
NetworkIsExpensive bool `json:"network_is_expensive,omitempty"`
|
NetworkIsExpensive bool `json:"network_is_expensive,omitempty"`
|
||||||
NetworkIsConstrained bool `json:"network_is_constrained,omitempty"`
|
NetworkIsConstrained bool `json:"network_is_constrained,omitempty"`
|
||||||
WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"`
|
WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"`
|
||||||
WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"`
|
WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"`
|
||||||
RuleSet badoption.Listable[string] `json:"rule_set,omitempty"`
|
InterfaceAddress *badjson.TypedMap[string, badoption.Listable[badoption.Prefixable]] `json:"interface_address,omitempty"`
|
||||||
RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"`
|
NetworkInterfaceAddress *badjson.TypedMap[InterfaceType, badoption.Listable[badoption.Prefixable]] `json:"network_interface_address,omitempty"`
|
||||||
RuleSetIPCIDRAcceptEmpty bool `json:"rule_set_ip_cidr_accept_empty,omitempty"`
|
DefaultInterfaceAddress badoption.Listable[badoption.Prefixable] `json:"default_interface_address,omitempty"`
|
||||||
Invert bool `json:"invert,omitempty"`
|
RuleSet badoption.Listable[string] `json:"rule_set,omitempty"`
|
||||||
|
RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"`
|
||||||
|
RuleSetIPCIDRAcceptEmpty bool `json:"rule_set_ip_cidr_accept_empty,omitempty"`
|
||||||
|
Invert bool `json:"invert,omitempty"`
|
||||||
|
|
||||||
// Deprecated: renamed to rule_set_ip_cidr_match_source
|
// Deprecated: renamed to rule_set_ip_cidr_match_source
|
||||||
Deprecated_RulesetIPCIDRMatchSource bool `json:"rule_set_ipcidr_match_source,omitempty"`
|
Deprecated_RulesetIPCIDRMatchSource bool `json:"rule_set_ipcidr_match_source,omitempty"`
|
||||||
|
@ -182,28 +182,31 @@ func (r HeadlessRule) IsValid() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type DefaultHeadlessRule struct {
|
type DefaultHeadlessRule struct {
|
||||||
QueryType badoption.Listable[DNSQueryType] `json:"query_type,omitempty"`
|
QueryType badoption.Listable[DNSQueryType] `json:"query_type,omitempty"`
|
||||||
Network badoption.Listable[string] `json:"network,omitempty"`
|
Network badoption.Listable[string] `json:"network,omitempty"`
|
||||||
Domain badoption.Listable[string] `json:"domain,omitempty"`
|
Domain badoption.Listable[string] `json:"domain,omitempty"`
|
||||||
DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"`
|
DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"`
|
||||||
DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"`
|
DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"`
|
||||||
DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"`
|
DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"`
|
||||||
SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"`
|
SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"`
|
||||||
IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"`
|
IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"`
|
||||||
SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"`
|
SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"`
|
||||||
SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"`
|
SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"`
|
||||||
Port badoption.Listable[uint16] `json:"port,omitempty"`
|
Port badoption.Listable[uint16] `json:"port,omitempty"`
|
||||||
PortRange badoption.Listable[string] `json:"port_range,omitempty"`
|
PortRange badoption.Listable[string] `json:"port_range,omitempty"`
|
||||||
ProcessName badoption.Listable[string] `json:"process_name,omitempty"`
|
ProcessName badoption.Listable[string] `json:"process_name,omitempty"`
|
||||||
ProcessPath badoption.Listable[string] `json:"process_path,omitempty"`
|
ProcessPath badoption.Listable[string] `json:"process_path,omitempty"`
|
||||||
ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"`
|
ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"`
|
||||||
PackageName badoption.Listable[string] `json:"package_name,omitempty"`
|
PackageName badoption.Listable[string] `json:"package_name,omitempty"`
|
||||||
NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"`
|
NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"`
|
||||||
NetworkIsExpensive bool `json:"network_is_expensive,omitempty"`
|
NetworkIsExpensive bool `json:"network_is_expensive,omitempty"`
|
||||||
NetworkIsConstrained bool `json:"network_is_constrained,omitempty"`
|
NetworkIsConstrained bool `json:"network_is_constrained,omitempty"`
|
||||||
WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"`
|
WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"`
|
||||||
WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"`
|
WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"`
|
||||||
Invert bool `json:"invert,omitempty"`
|
NetworkInterfaceAddress *badjson.TypedMap[InterfaceType, badoption.Listable[badoption.Prefixable]] `json:"network_interface_address,omitempty"`
|
||||||
|
DefaultInterfaceAddress badoption.Listable[badoption.Prefixable] `json:"default_interface_address,omitempty"`
|
||||||
|
|
||||||
|
Invert bool `json:"invert,omitempty"`
|
||||||
|
|
||||||
DomainMatcher *domain.Matcher `json:"-"`
|
DomainMatcher *domain.Matcher `json:"-"`
|
||||||
SourceIPSet *netipx.IPSet `json:"-"`
|
SourceIPSet *netipx.IPSet `json:"-"`
|
||||||
@ -240,7 +243,7 @@ type PlainRuleSetCompat _PlainRuleSetCompat
|
|||||||
func (r PlainRuleSetCompat) MarshalJSON() ([]byte, error) {
|
func (r PlainRuleSetCompat) MarshalJSON() ([]byte, error) {
|
||||||
var v any
|
var v any
|
||||||
switch r.Version {
|
switch r.Version {
|
||||||
case C.RuleSetVersion1, C.RuleSetVersion2, C.RuleSetVersion3:
|
case C.RuleSetVersion1, C.RuleSetVersion2, C.RuleSetVersion3, C.RuleSetVersion4:
|
||||||
v = r.Options
|
v = r.Options
|
||||||
default:
|
default:
|
||||||
return nil, E.New("unknown rule-set version: ", r.Version)
|
return nil, E.New("unknown rule-set version: ", r.Version)
|
||||||
@ -255,7 +258,7 @@ func (r *PlainRuleSetCompat) UnmarshalJSON(bytes []byte) error {
|
|||||||
}
|
}
|
||||||
var v any
|
var v any
|
||||||
switch r.Version {
|
switch r.Version {
|
||||||
case C.RuleSetVersion1, C.RuleSetVersion2, C.RuleSetVersion3:
|
case C.RuleSetVersion1, C.RuleSetVersion2, C.RuleSetVersion3, C.RuleSetVersion4:
|
||||||
v = &r.Options
|
v = &r.Options
|
||||||
case 0:
|
case 0:
|
||||||
return E.New("missing rule-set version")
|
return E.New("missing rule-set version")
|
||||||
@ -272,7 +275,7 @@ func (r *PlainRuleSetCompat) UnmarshalJSON(bytes []byte) error {
|
|||||||
|
|
||||||
func (r PlainRuleSetCompat) Upgrade() (PlainRuleSet, error) {
|
func (r PlainRuleSetCompat) Upgrade() (PlainRuleSet, error) {
|
||||||
switch r.Version {
|
switch r.Version {
|
||||||
case C.RuleSetVersion1, C.RuleSetVersion2, C.RuleSetVersion3:
|
case C.RuleSetVersion1, C.RuleSetVersion2, C.RuleSetVersion3, C.RuleSetVersion4:
|
||||||
default:
|
default:
|
||||||
return PlainRuleSet{}, E.New("unknown rule-set version: " + F.ToString(r.Version))
|
return PlainRuleSet{}, E.New("unknown rule-set version: " + F.ToString(r.Version))
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ func RegisterOutbound(registry *outbound.Registry) {
|
|||||||
|
|
||||||
type Outbound struct {
|
type Outbound struct {
|
||||||
outbound.Adapter
|
outbound.Adapter
|
||||||
dialer N.Dialer
|
dialer tls.Dialer
|
||||||
server M.Socksaddr
|
server M.Socksaddr
|
||||||
tlsConfig tls.Config
|
tlsConfig tls.Config
|
||||||
client *anytls.Client
|
client *anytls.Client
|
||||||
@ -58,7 +58,8 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
outbound.dialer = outboundDialer
|
|
||||||
|
outbound.dialer = tls.NewDialer(outboundDialer, tlsConfig)
|
||||||
|
|
||||||
client, err := anytls.NewClient(ctx, anytls.ClientConfig{
|
client, err := anytls.NewClient(ctx, anytls.ClientConfig{
|
||||||
Password: options.Password,
|
Password: options.Password,
|
||||||
@ -91,16 +92,7 @@ func (d anytlsDialer) ListenPacket(ctx context.Context, destination M.Socksaddr)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *Outbound) dialOut(ctx context.Context) (net.Conn, error) {
|
func (h *Outbound) dialOut(ctx context.Context) (net.Conn, error) {
|
||||||
conn, err := h.dialer.DialContext(ctx, N.NetworkTCP, h.server)
|
return h.dialer.DialTLSContext(ctx, h.server)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
tlsConn, err := tls.ClientHandshake(ctx, conn, h.tlsConfig)
|
|
||||||
if err != nil {
|
|
||||||
common.Close(tlsConn, conn)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return tlsConn, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||||
|
@ -34,7 +34,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
detour, err := tls.NewDialerFromOptions(ctx, router, outboundDialer, options.Server, common.PtrValueOrDefault(options.TLS))
|
detour, err := tls.NewDialerFromOptions(ctx, outboundDialer, options.Server, common.PtrValueOrDefault(options.TLS))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,6 @@ import (
|
|||||||
"net/netip"
|
"net/netip"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
@ -47,8 +46,6 @@ type DNSTransport struct {
|
|||||||
acceptDefaultResolvers bool
|
acceptDefaultResolvers bool
|
||||||
dnsRouter adapter.DNSRouter
|
dnsRouter adapter.DNSRouter
|
||||||
endpointManager adapter.EndpointManager
|
endpointManager adapter.EndpointManager
|
||||||
cfg *wgcfg.Config
|
|
||||||
dnsCfg *nDNS.Config
|
|
||||||
endpoint *Endpoint
|
endpoint *Endpoint
|
||||||
routePrefixes []netip.Prefix
|
routePrefixes []netip.Prefix
|
||||||
routes map[string][]adapter.DNSTransport
|
routes map[string][]adapter.DNSTransport
|
||||||
@ -83,10 +80,10 @@ func (t *DNSTransport) Start(stage adapter.StartStage) error {
|
|||||||
if !isTailscale {
|
if !isTailscale {
|
||||||
return E.New("endpoint is not Tailscale: ", t.endpointTag)
|
return E.New("endpoint is not Tailscale: ", t.endpointTag)
|
||||||
}
|
}
|
||||||
if ep.onReconfig != nil {
|
if ep.onReconfigHook != nil {
|
||||||
return E.New("only one Tailscale DNS server is allowed for single endpoint")
|
return E.New("only one Tailscale DNS server is allowed for single endpoint")
|
||||||
}
|
}
|
||||||
ep.onReconfig = t.onReconfig
|
ep.onReconfigHook = t.onReconfig
|
||||||
t.endpoint = ep
|
t.endpoint = ep
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -95,14 +92,6 @@ func (t *DNSTransport) Reset() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *DNSTransport) onReconfig(cfg *wgcfg.Config, routerCfg *router.Config, dnsCfg *nDNS.Config) {
|
func (t *DNSTransport) onReconfig(cfg *wgcfg.Config, routerCfg *router.Config, dnsCfg *nDNS.Config) {
|
||||||
if cfg == nil || dnsCfg == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (t.cfg != nil && reflect.DeepEqual(t.cfg, cfg)) && (t.dnsCfg != nil && reflect.DeepEqual(t.dnsCfg, dnsCfg)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
t.cfg = cfg
|
|
||||||
t.dnsCfg = dnsCfg
|
|
||||||
err := t.updateDNSServers(routerCfg, dnsCfg)
|
err := t.updateDNSServers(routerCfg, dnsCfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.logger.Error(E.Cause(err, "update DNS servers"))
|
t.logger.Error(E.Cause(err, "update DNS servers"))
|
||||||
|
@ -10,9 +10,9 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"sync/atomic"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -31,6 +31,7 @@ import (
|
|||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-tun"
|
"github.com/sagernet/sing-tun"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
|
"github.com/sagernet/sing/common/atomic"
|
||||||
"github.com/sagernet/sing/common/bufio"
|
"github.com/sagernet/sing/common/bufio"
|
||||||
"github.com/sagernet/sing/common/control"
|
"github.com/sagernet/sing/common/control"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
@ -49,8 +50,14 @@ import (
|
|||||||
"github.com/sagernet/tailscale/version"
|
"github.com/sagernet/tailscale/version"
|
||||||
"github.com/sagernet/tailscale/wgengine"
|
"github.com/sagernet/tailscale/wgengine"
|
||||||
"github.com/sagernet/tailscale/wgengine/filter"
|
"github.com/sagernet/tailscale/wgengine/filter"
|
||||||
|
"github.com/sagernet/tailscale/wgengine/router"
|
||||||
|
"github.com/sagernet/tailscale/wgengine/wgcfg"
|
||||||
|
|
||||||
|
"go4.org/netipx"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var _ adapter.OutboundWithPreferredRoutes = (*Endpoint)(nil)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
version.SetVersion("sing-box " + C.Version)
|
version.SetVersion("sing-box " + C.Version)
|
||||||
}
|
}
|
||||||
@ -70,7 +77,12 @@ type Endpoint struct {
|
|||||||
server *tsnet.Server
|
server *tsnet.Server
|
||||||
stack *stack.Stack
|
stack *stack.Stack
|
||||||
filter *atomic.Pointer[filter.Filter]
|
filter *atomic.Pointer[filter.Filter]
|
||||||
onReconfig wgengine.ReconfigListener
|
onReconfigHook wgengine.ReconfigListener
|
||||||
|
|
||||||
|
cfg *wgcfg.Config
|
||||||
|
dnsCfg *tsDNS.Config
|
||||||
|
routeDomains atomic.TypedValue[map[string]bool]
|
||||||
|
routePrefixes atomic.Pointer[netipx.IPSet]
|
||||||
|
|
||||||
acceptRoutes bool
|
acceptRoutes bool
|
||||||
exitNode string
|
exitNode string
|
||||||
@ -216,9 +228,7 @@ func (t *Endpoint) Start(stage adapter.StartStage) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if t.onReconfig != nil {
|
t.server.ExportLocalBackend().ExportEngine().(wgengine.ExportedUserspaceEngine).SetOnReconfigListener(t.onReconfig)
|
||||||
t.server.ExportLocalBackend().ExportEngine().(wgengine.ExportedUserspaceEngine).SetOnReconfigListener(t.onReconfig)
|
|
||||||
}
|
|
||||||
|
|
||||||
ipStack := t.server.ExportNetstack().ExportIPStack()
|
ipStack := t.server.ExportNetstack().ExportIPStack()
|
||||||
gErr := ipStack.SetSpoofing(tun.DefaultNIC, true)
|
gErr := ipStack.SetSpoofing(tun.DefaultNIC, true)
|
||||||
@ -253,8 +263,7 @@ func (t *Endpoint) Start(stage adapter.StartStage) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "update prefs")
|
return E.Cause(err, "update prefs")
|
||||||
}
|
}
|
||||||
t.filter = localBackend.ExportFilter()
|
t.filter = atomic.PointerForm(localBackend.ExportFilter())
|
||||||
|
|
||||||
go t.watchState()
|
go t.watchState()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -473,10 +482,58 @@ func (t *Endpoint) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn,
|
|||||||
t.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose)
|
t.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Endpoint) PreferredDomain(domain string) bool {
|
||||||
|
routeDomains := t.routeDomains.Load()
|
||||||
|
if routeDomains == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return routeDomains[strings.ToLower(domain)]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Endpoint) PreferredAddress(address netip.Addr) bool {
|
||||||
|
routePrefixes := t.routePrefixes.Load()
|
||||||
|
if routePrefixes == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return routePrefixes.Contains(address)
|
||||||
|
}
|
||||||
|
|
||||||
func (t *Endpoint) Server() *tsnet.Server {
|
func (t *Endpoint) Server() *tsnet.Server {
|
||||||
return t.server
|
return t.server
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Endpoint) onReconfig(cfg *wgcfg.Config, routerCfg *router.Config, dnsCfg *tsDNS.Config) {
|
||||||
|
if cfg == nil || dnsCfg == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (t.cfg != nil && reflect.DeepEqual(t.cfg, cfg)) && (t.dnsCfg != nil && reflect.DeepEqual(t.dnsCfg, dnsCfg)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.cfg = cfg
|
||||||
|
t.dnsCfg = dnsCfg
|
||||||
|
|
||||||
|
routeDomains := make(map[string]bool)
|
||||||
|
for fqdn := range dnsCfg.Routes {
|
||||||
|
routeDomains[fqdn.WithoutTrailingDot()] = true
|
||||||
|
}
|
||||||
|
for _, fqdn := range dnsCfg.SearchDomains {
|
||||||
|
routeDomains[fqdn.WithoutTrailingDot()] = true
|
||||||
|
}
|
||||||
|
t.routeDomains.Store(routeDomains)
|
||||||
|
|
||||||
|
var builder netipx.IPSetBuilder
|
||||||
|
for _, peer := range cfg.Peers {
|
||||||
|
for _, allowedIP := range peer.AllowedIPs {
|
||||||
|
builder.AddPrefix(allowedIP)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.routePrefixes.Store(common.Must1(builder.IPSet()))
|
||||||
|
|
||||||
|
if t.onReconfigHook != nil {
|
||||||
|
t.onReconfigHook(cfg, routerCfg, dnsCfg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func addressFromAddr(destination netip.Addr) tcpip.Address {
|
func addressFromAddr(destination netip.Addr) tcpip.Address {
|
||||||
if destination.Is6() {
|
if destination.Is6() {
|
||||||
return tcpip.AddrFrom16(destination.As16())
|
return tcpip.AddrFrom16(destination.As16())
|
||||||
|
@ -34,6 +34,7 @@ type Outbound struct {
|
|||||||
key [56]byte
|
key [56]byte
|
||||||
multiplexDialer *mux.Client
|
multiplexDialer *mux.Client
|
||||||
tlsConfig tls.Config
|
tlsConfig tls.Config
|
||||||
|
tlsDialer tls.Dialer
|
||||||
transport adapter.V2RayClientTransport
|
transport adapter.V2RayClientTransport
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,6 +55,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
outbound.tlsDialer = tls.NewDialer(outboundDialer, outbound.tlsConfig)
|
||||||
}
|
}
|
||||||
if options.Transport != nil {
|
if options.Transport != nil {
|
||||||
outbound.transport, err = v2ray.NewClientTransport(ctx, outbound.dialer, outbound.serverAddr, common.PtrValueOrDefault(options.Transport), outbound.tlsConfig)
|
outbound.transport, err = v2ray.NewClientTransport(ctx, outbound.dialer, outbound.serverAddr, common.PtrValueOrDefault(options.Transport), outbound.tlsConfig)
|
||||||
@ -121,11 +123,10 @@ func (h *trojanDialer) DialContext(ctx context.Context, network string, destinat
|
|||||||
var err error
|
var err error
|
||||||
if h.transport != nil {
|
if h.transport != nil {
|
||||||
conn, err = h.transport.DialContext(ctx)
|
conn, err = h.transport.DialContext(ctx)
|
||||||
|
} else if h.tlsDialer != nil {
|
||||||
|
conn, err = h.tlsDialer.DialTLSContext(ctx, h.serverAddr)
|
||||||
} else {
|
} else {
|
||||||
conn, err = h.dialer.DialContext(ctx, N.NetworkTCP, h.serverAddr)
|
conn, err = h.dialer.DialContext(ctx, N.NetworkTCP, h.serverAddr)
|
||||||
if err == nil && h.tlsConfig != nil {
|
|
||||||
conn, err = tls.ClientHandshake(ctx, conn, h.tlsConfig)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.Close(conn)
|
common.Close(conn)
|
||||||
|
@ -35,6 +35,7 @@ type Outbound struct {
|
|||||||
serverAddr M.Socksaddr
|
serverAddr M.Socksaddr
|
||||||
multiplexDialer *mux.Client
|
multiplexDialer *mux.Client
|
||||||
tlsConfig tls.Config
|
tlsConfig tls.Config
|
||||||
|
tlsDialer tls.Dialer
|
||||||
transport adapter.V2RayClientTransport
|
transport adapter.V2RayClientTransport
|
||||||
packetAddr bool
|
packetAddr bool
|
||||||
xudp bool
|
xudp bool
|
||||||
@ -56,6 +57,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
outbound.tlsDialer = tls.NewDialer(outboundDialer, outbound.tlsConfig)
|
||||||
}
|
}
|
||||||
if options.Transport != nil {
|
if options.Transport != nil {
|
||||||
outbound.transport, err = v2ray.NewClientTransport(ctx, outbound.dialer, outbound.serverAddr, common.PtrValueOrDefault(options.Transport), outbound.tlsConfig)
|
outbound.transport, err = v2ray.NewClientTransport(ctx, outbound.dialer, outbound.serverAddr, common.PtrValueOrDefault(options.Transport), outbound.tlsConfig)
|
||||||
@ -140,11 +142,10 @@ func (h *vlessDialer) DialContext(ctx context.Context, network string, destinati
|
|||||||
var err error
|
var err error
|
||||||
if h.transport != nil {
|
if h.transport != nil {
|
||||||
conn, err = h.transport.DialContext(ctx)
|
conn, err = h.transport.DialContext(ctx)
|
||||||
|
} else if h.tlsDialer != nil {
|
||||||
|
conn, err = h.tlsDialer.DialTLSContext(ctx, h.serverAddr)
|
||||||
} else {
|
} else {
|
||||||
conn, err = h.dialer.DialContext(ctx, N.NetworkTCP, h.serverAddr)
|
conn, err = h.dialer.DialContext(ctx, N.NetworkTCP, h.serverAddr)
|
||||||
if err == nil && h.tlsConfig != nil {
|
|
||||||
conn, err = tls.ClientHandshake(ctx, conn, h.tlsConfig)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -183,11 +184,10 @@ func (h *vlessDialer) ListenPacket(ctx context.Context, destination M.Socksaddr)
|
|||||||
var err error
|
var err error
|
||||||
if h.transport != nil {
|
if h.transport != nil {
|
||||||
conn, err = h.transport.DialContext(ctx)
|
conn, err = h.transport.DialContext(ctx)
|
||||||
|
} else if h.tlsDialer != nil {
|
||||||
|
conn, err = h.tlsDialer.DialTLSContext(ctx, h.serverAddr)
|
||||||
} else {
|
} else {
|
||||||
conn, err = h.dialer.DialContext(ctx, N.NetworkTCP, h.serverAddr)
|
conn, err = h.dialer.DialContext(ctx, N.NetworkTCP, h.serverAddr)
|
||||||
if err == nil && h.tlsConfig != nil {
|
|
||||||
conn, err = tls.ClientHandshake(ctx, conn, h.tlsConfig)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.Close(conn)
|
common.Close(conn)
|
||||||
|
@ -35,6 +35,7 @@ type Outbound struct {
|
|||||||
serverAddr M.Socksaddr
|
serverAddr M.Socksaddr
|
||||||
multiplexDialer *mux.Client
|
multiplexDialer *mux.Client
|
||||||
tlsConfig tls.Config
|
tlsConfig tls.Config
|
||||||
|
tlsDialer tls.Dialer
|
||||||
transport adapter.V2RayClientTransport
|
transport adapter.V2RayClientTransport
|
||||||
packetAddr bool
|
packetAddr bool
|
||||||
xudp bool
|
xudp bool
|
||||||
@ -56,6 +57,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
outbound.tlsDialer = tls.NewDialer(outboundDialer, outbound.tlsConfig)
|
||||||
}
|
}
|
||||||
if options.Transport != nil {
|
if options.Transport != nil {
|
||||||
outbound.transport, err = v2ray.NewClientTransport(ctx, outbound.dialer, outbound.serverAddr, common.PtrValueOrDefault(options.Transport), outbound.tlsConfig)
|
outbound.transport, err = v2ray.NewClientTransport(ctx, outbound.dialer, outbound.serverAddr, common.PtrValueOrDefault(options.Transport), outbound.tlsConfig)
|
||||||
@ -154,11 +156,10 @@ func (h *vmessDialer) DialContext(ctx context.Context, network string, destinati
|
|||||||
var err error
|
var err error
|
||||||
if h.transport != nil {
|
if h.transport != nil {
|
||||||
conn, err = h.transport.DialContext(ctx)
|
conn, err = h.transport.DialContext(ctx)
|
||||||
|
} else if h.tlsDialer != nil {
|
||||||
|
conn, err = h.tlsDialer.DialTLSContext(ctx, h.serverAddr)
|
||||||
} else {
|
} else {
|
||||||
conn, err = h.dialer.DialContext(ctx, N.NetworkTCP, h.serverAddr)
|
conn, err = h.dialer.DialContext(ctx, N.NetworkTCP, h.serverAddr)
|
||||||
if err == nil && h.tlsConfig != nil {
|
|
||||||
conn, err = tls.ClientHandshake(ctx, conn, h.tlsConfig)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.Close(conn)
|
common.Close(conn)
|
||||||
@ -182,11 +183,10 @@ func (h *vmessDialer) ListenPacket(ctx context.Context, destination M.Socksaddr)
|
|||||||
var err error
|
var err error
|
||||||
if h.transport != nil {
|
if h.transport != nil {
|
||||||
conn, err = h.transport.DialContext(ctx)
|
conn, err = h.transport.DialContext(ctx)
|
||||||
|
} else if h.tlsDialer != nil {
|
||||||
|
conn, err = h.tlsDialer.DialTLSContext(ctx, h.serverAddr)
|
||||||
} else {
|
} else {
|
||||||
conn, err = h.dialer.DialContext(ctx, N.NetworkTCP, h.serverAddr)
|
conn, err = h.dialer.DialContext(ctx, N.NetworkTCP, h.serverAddr)
|
||||||
if err == nil && h.tlsConfig != nil {
|
|
||||||
conn, err = tls.ClientHandshake(ctx, conn, h.tlsConfig)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -22,6 +22,8 @@ import (
|
|||||||
"github.com/sagernet/sing/service"
|
"github.com/sagernet/sing/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var _ adapter.OutboundWithPreferredRoutes = (*Endpoint)(nil)
|
||||||
|
|
||||||
func RegisterEndpoint(registry *endpoint.Registry) {
|
func RegisterEndpoint(registry *endpoint.Registry) {
|
||||||
endpoint.Register[option.WireGuardEndpointOptions](registry, C.TypeWireGuard, NewEndpoint)
|
endpoint.Register[option.WireGuardEndpointOptions](registry, C.TypeWireGuard, NewEndpoint)
|
||||||
}
|
}
|
||||||
@ -210,3 +212,11 @@ func (w *Endpoint) ListenPacket(ctx context.Context, destination M.Socksaddr) (n
|
|||||||
}
|
}
|
||||||
return w.endpoint.ListenPacket(ctx, destination)
|
return w.endpoint.ListenPacket(ctx, destination)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *Endpoint) PreferredDomain(domain string) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Endpoint) PreferredAddress(address netip.Addr) bool {
|
||||||
|
return w.endpoint.Lookup(address) != nil
|
||||||
|
}
|
||||||
|
@ -21,6 +21,8 @@ import (
|
|||||||
"github.com/sagernet/sing/service"
|
"github.com/sagernet/sing/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var _ adapter.OutboundWithPreferredRoutes = (*Outbound)(nil)
|
||||||
|
|
||||||
func RegisterOutbound(registry *outbound.Registry) {
|
func RegisterOutbound(registry *outbound.Registry) {
|
||||||
outbound.Register[option.LegacyWireGuardOutboundOptions](registry, C.TypeWireGuard, NewOutbound)
|
outbound.Register[option.LegacyWireGuardOutboundOptions](registry, C.TypeWireGuard, NewOutbound)
|
||||||
}
|
}
|
||||||
@ -158,3 +160,11 @@ func (o *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (n
|
|||||||
}
|
}
|
||||||
return o.endpoint.ListenPacket(ctx, destination)
|
return o.endpoint.ListenPacket(ctx, destination)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o *Outbound) PreferredDomain(domain string) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Outbound) PreferredAddress(address netip.Addr) bool {
|
||||||
|
return o.endpoint.Lookup(address) != nil
|
||||||
|
}
|
||||||
|
@ -117,7 +117,7 @@ func NewDefaultRule(ctx context.Context, logger log.ContextLogger, options optio
|
|||||||
if len(options.DomainRegex) > 0 {
|
if len(options.DomainRegex) > 0 {
|
||||||
item, err := NewDomainRegexItem(options.DomainRegex)
|
item, err := NewDomainRegexItem(options.DomainRegex)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "domain_regex")
|
return nil, err
|
||||||
}
|
}
|
||||||
rule.destinationAddressItems = append(rule.destinationAddressItems, item)
|
rule.destinationAddressItems = append(rule.destinationAddressItems, item)
|
||||||
rule.allItems = append(rule.allItems, item)
|
rule.allItems = append(rule.allItems, item)
|
||||||
@ -246,6 +246,26 @@ 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 options.InterfaceAddress != nil && options.InterfaceAddress.Size() > 0 {
|
||||||
|
item := NewInterfaceAddressItem(networkManager, options.InterfaceAddress)
|
||||||
|
rule.items = append(rule.items, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
|
if options.NetworkInterfaceAddress != nil && options.NetworkInterfaceAddress.Size() > 0 {
|
||||||
|
item := NewNetworkInterfaceAddressItem(networkManager, options.NetworkInterfaceAddress)
|
||||||
|
rule.items = append(rule.items, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
|
if len(options.DefaultInterfaceAddress) > 0 {
|
||||||
|
item := NewDefaultInterfaceAddressItem(networkManager, options.DefaultInterfaceAddress)
|
||||||
|
rule.items = append(rule.items, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
|
if len(options.PreferredBy) > 0 {
|
||||||
|
item := NewPreferredByItem(ctx, options.PreferredBy)
|
||||||
|
rule.items = append(rule.items, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
if len(options.RuleSet) > 0 {
|
if len(options.RuleSet) > 0 {
|
||||||
var matchSource bool
|
var matchSource bool
|
||||||
if options.RuleSetIPCIDRMatchSource {
|
if options.RuleSetIPCIDRMatchSource {
|
||||||
|
56
route/rule/rule_default_interface_address.go
Normal file
56
route/rule/rule_default_interface_address.go
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
package rule
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/netip"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-tun"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
"github.com/sagernet/sing/common/json/badoption"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ RuleItem = (*DefaultInterfaceAddressItem)(nil)
|
||||||
|
|
||||||
|
type DefaultInterfaceAddressItem struct {
|
||||||
|
interfaceMonitor tun.DefaultInterfaceMonitor
|
||||||
|
interfaceAddresses []netip.Prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDefaultInterfaceAddressItem(networkManager adapter.NetworkManager, interfaceAddresses badoption.Listable[badoption.Prefixable]) *DefaultInterfaceAddressItem {
|
||||||
|
item := &DefaultInterfaceAddressItem{
|
||||||
|
interfaceMonitor: networkManager.InterfaceMonitor(),
|
||||||
|
interfaceAddresses: make([]netip.Prefix, 0, len(interfaceAddresses)),
|
||||||
|
}
|
||||||
|
for _, prefixable := range interfaceAddresses {
|
||||||
|
item.interfaceAddresses = append(item.interfaceAddresses, prefixable.Build(netip.Prefix{}))
|
||||||
|
}
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *DefaultInterfaceAddressItem) Match(metadata *adapter.InboundContext) bool {
|
||||||
|
defaultInterface := r.interfaceMonitor.DefaultInterface()
|
||||||
|
if defaultInterface == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, address := range r.interfaceAddresses {
|
||||||
|
if common.All(defaultInterface.Addresses, func(it netip.Prefix) bool {
|
||||||
|
return !address.Overlaps(it)
|
||||||
|
}) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *DefaultInterfaceAddressItem) String() string {
|
||||||
|
addressLen := len(r.interfaceAddresses)
|
||||||
|
switch {
|
||||||
|
case addressLen == 1:
|
||||||
|
return "default_interface_address=" + r.interfaceAddresses[0].String()
|
||||||
|
case addressLen > 3:
|
||||||
|
return "default_interface_address=[" + strings.Join(common.Map(r.interfaceAddresses[:3], netip.Prefix.String), " ") + "...]"
|
||||||
|
default:
|
||||||
|
return "default_interface_address=[" + strings.Join(common.Map(r.interfaceAddresses, netip.Prefix.String), " ") + "]"
|
||||||
|
}
|
||||||
|
}
|
@ -247,6 +247,21 @@ 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 options.InterfaceAddress != nil && options.InterfaceAddress.Size() > 0 {
|
||||||
|
item := NewInterfaceAddressItem(networkManager, options.InterfaceAddress)
|
||||||
|
rule.items = append(rule.items, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
|
if options.NetworkInterfaceAddress != nil && options.NetworkInterfaceAddress.Size() > 0 {
|
||||||
|
item := NewNetworkInterfaceAddressItem(networkManager, options.NetworkInterfaceAddress)
|
||||||
|
rule.items = append(rule.items, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
|
if len(options.DefaultInterfaceAddress) > 0 {
|
||||||
|
item := NewDefaultInterfaceAddressItem(networkManager, options.DefaultInterfaceAddress)
|
||||||
|
rule.items = append(rule.items, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
if len(options.RuleSet) > 0 {
|
if len(options.RuleSet) > 0 {
|
||||||
var matchSource bool
|
var matchSource bool
|
||||||
if options.RuleSetIPCIDRMatchSource {
|
if options.RuleSetIPCIDRMatchSource {
|
||||||
|
@ -164,13 +164,21 @@ func NewDefaultHeadlessRule(ctx context.Context, options option.DefaultHeadlessR
|
|||||||
item := NewWIFISSIDItem(networkManager, options.WIFISSID)
|
item := NewWIFISSIDItem(networkManager, options.WIFISSID)
|
||||||
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.WIFIBSSID) > 0 {
|
if len(options.WIFIBSSID) > 0 {
|
||||||
item := NewWIFIBSSIDItem(networkManager, options.WIFIBSSID)
|
item := NewWIFIBSSIDItem(networkManager, options.WIFIBSSID)
|
||||||
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 options.NetworkInterfaceAddress != nil && options.NetworkInterfaceAddress.Size() > 0 {
|
||||||
|
item := NewNetworkInterfaceAddressItem(networkManager, options.NetworkInterfaceAddress)
|
||||||
|
rule.items = append(rule.items, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
|
if len(options.DefaultInterfaceAddress) > 0 {
|
||||||
|
item := NewDefaultInterfaceAddressItem(networkManager, options.DefaultInterfaceAddress)
|
||||||
|
rule.items = append(rule.items, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(options.AdGuardDomain) > 0 {
|
if len(options.AdGuardDomain) > 0 {
|
||||||
|
62
route/rule/rule_interface_address.go
Normal file
62
route/rule/rule_interface_address.go
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
package rule
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/netip"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
"github.com/sagernet/sing/common/control"
|
||||||
|
"github.com/sagernet/sing/common/json/badjson"
|
||||||
|
"github.com/sagernet/sing/common/json/badoption"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ RuleItem = (*InterfaceAddressItem)(nil)
|
||||||
|
|
||||||
|
type InterfaceAddressItem struct {
|
||||||
|
networkManager adapter.NetworkManager
|
||||||
|
interfaceAddresses map[string][]netip.Prefix
|
||||||
|
description string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewInterfaceAddressItem(networkManager adapter.NetworkManager, interfaceAddresses *badjson.TypedMap[string, badoption.Listable[badoption.Prefixable]]) *InterfaceAddressItem {
|
||||||
|
item := &InterfaceAddressItem{
|
||||||
|
networkManager: networkManager,
|
||||||
|
interfaceAddresses: make(map[string][]netip.Prefix, interfaceAddresses.Size()),
|
||||||
|
}
|
||||||
|
var entryDescriptions []string
|
||||||
|
for _, entry := range interfaceAddresses.Entries() {
|
||||||
|
prefixes := make([]netip.Prefix, 0, len(entry.Value))
|
||||||
|
for _, prefixable := range entry.Value {
|
||||||
|
prefixes = append(prefixes, prefixable.Build(netip.Prefix{}))
|
||||||
|
}
|
||||||
|
item.interfaceAddresses[entry.Key] = prefixes
|
||||||
|
entryDescriptions = append(entryDescriptions, entry.Key+"="+strings.Join(common.Map(prefixes, netip.Prefix.String), ","))
|
||||||
|
}
|
||||||
|
item.description = "interface_address=[" + strings.Join(entryDescriptions, " ") + "]"
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *InterfaceAddressItem) Match(metadata *adapter.InboundContext) bool {
|
||||||
|
interfaces := r.networkManager.InterfaceFinder().Interfaces()
|
||||||
|
for ifName, addresses := range r.interfaceAddresses {
|
||||||
|
iface := common.Find(interfaces, func(it control.Interface) bool {
|
||||||
|
return it.Name == ifName
|
||||||
|
})
|
||||||
|
if iface.Name == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if common.All(addresses, func(address netip.Prefix) bool {
|
||||||
|
return common.All(iface.Addresses, func(it netip.Prefix) bool {
|
||||||
|
return !address.Overlaps(it)
|
||||||
|
})
|
||||||
|
}) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *InterfaceAddressItem) String() string {
|
||||||
|
return r.description
|
||||||
|
}
|
86
route/rule/rule_item_preferred_by.go
Normal file
86
route/rule/rule_item_preferred_by.go
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
package rule
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
F "github.com/sagernet/sing/common/format"
|
||||||
|
"github.com/sagernet/sing/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ RuleItem = (*PreferredByItem)(nil)
|
||||||
|
|
||||||
|
type PreferredByItem struct {
|
||||||
|
ctx context.Context
|
||||||
|
outboundTags []string
|
||||||
|
outbounds []adapter.OutboundWithPreferredRoutes
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPreferredByItem(ctx context.Context, outboundTags []string) *PreferredByItem {
|
||||||
|
return &PreferredByItem{
|
||||||
|
ctx: ctx,
|
||||||
|
outboundTags: outboundTags,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PreferredByItem) Start() error {
|
||||||
|
outboundManager := service.FromContext[adapter.OutboundManager](r.ctx)
|
||||||
|
for _, outboundTag := range r.outboundTags {
|
||||||
|
rawOutbound, loaded := outboundManager.Outbound(outboundTag)
|
||||||
|
if !loaded {
|
||||||
|
return E.New("outbound not found: ", outboundTag)
|
||||||
|
}
|
||||||
|
outboundWithPreferredRoutes, withRoutes := rawOutbound.(adapter.OutboundWithPreferredRoutes)
|
||||||
|
if !withRoutes {
|
||||||
|
return E.New("outbound type does not support preferred routes: ", rawOutbound.Type())
|
||||||
|
}
|
||||||
|
r.outbounds = append(r.outbounds, outboundWithPreferredRoutes)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PreferredByItem) Match(metadata *adapter.InboundContext) bool {
|
||||||
|
var domainHost string
|
||||||
|
if metadata.Domain != "" {
|
||||||
|
domainHost = metadata.Domain
|
||||||
|
} else {
|
||||||
|
domainHost = metadata.Destination.Fqdn
|
||||||
|
}
|
||||||
|
if domainHost != "" {
|
||||||
|
for _, outbound := range r.outbounds {
|
||||||
|
if outbound.PreferredDomain(domainHost) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if metadata.Destination.IsIP() {
|
||||||
|
for _, outbound := range r.outbounds {
|
||||||
|
if outbound.PreferredAddress(metadata.Destination.Addr) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(metadata.DestinationAddresses) > 0 {
|
||||||
|
for _, address := range metadata.DestinationAddresses {
|
||||||
|
for _, outbound := range r.outbounds {
|
||||||
|
if outbound.PreferredAddress(address) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PreferredByItem) String() string {
|
||||||
|
description := "preferred_by="
|
||||||
|
pLen := len(r.outboundTags)
|
||||||
|
if pLen == 1 {
|
||||||
|
description += F.ToString(r.outboundTags[0])
|
||||||
|
} else {
|
||||||
|
description += "[" + strings.Join(F.MapToString(r.outboundTags), " ") + "]"
|
||||||
|
}
|
||||||
|
return description
|
||||||
|
}
|
64
route/rule/rule_network_interface_address.go
Normal file
64
route/rule/rule_network_interface_address.go
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
package rule
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/netip"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
"github.com/sagernet/sing/common/json/badjson"
|
||||||
|
"github.com/sagernet/sing/common/json/badoption"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ RuleItem = (*NetworkInterfaceAddressItem)(nil)
|
||||||
|
|
||||||
|
type NetworkInterfaceAddressItem struct {
|
||||||
|
networkManager adapter.NetworkManager
|
||||||
|
interfaceAddresses map[C.InterfaceType][]netip.Prefix
|
||||||
|
description string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewNetworkInterfaceAddressItem(networkManager adapter.NetworkManager, interfaceAddresses *badjson.TypedMap[option.InterfaceType, badoption.Listable[badoption.Prefixable]]) *NetworkInterfaceAddressItem {
|
||||||
|
item := &NetworkInterfaceAddressItem{
|
||||||
|
networkManager: networkManager,
|
||||||
|
interfaceAddresses: make(map[C.InterfaceType][]netip.Prefix, interfaceAddresses.Size()),
|
||||||
|
}
|
||||||
|
var entryDescriptions []string
|
||||||
|
for _, entry := range interfaceAddresses.Entries() {
|
||||||
|
prefixes := make([]netip.Prefix, 0, len(entry.Value))
|
||||||
|
for _, prefixable := range entry.Value {
|
||||||
|
prefixes = append(prefixes, prefixable.Build(netip.Prefix{}))
|
||||||
|
}
|
||||||
|
item.interfaceAddresses[entry.Key.Build()] = prefixes
|
||||||
|
entryDescriptions = append(entryDescriptions, entry.Key.Build().String()+"="+strings.Join(common.Map(prefixes, netip.Prefix.String), ","))
|
||||||
|
}
|
||||||
|
item.description = "network_interface_address=[" + strings.Join(entryDescriptions, " ") + "]"
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *NetworkInterfaceAddressItem) Match(metadata *adapter.InboundContext) bool {
|
||||||
|
interfaces := r.networkManager.NetworkInterfaces()
|
||||||
|
match:
|
||||||
|
for ifType, addresses := range r.interfaceAddresses {
|
||||||
|
for _, networkInterface := range interfaces {
|
||||||
|
if networkInterface.Type != ifType {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if common.Any(networkInterface.Addresses, func(it netip.Prefix) bool {
|
||||||
|
return common.Any(addresses, func(prefix netip.Prefix) bool {
|
||||||
|
return prefix.Overlaps(it)
|
||||||
|
})
|
||||||
|
}) {
|
||||||
|
continue match
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *NetworkInterfaceAddressItem) String() string {
|
||||||
|
return r.description
|
||||||
|
}
|
@ -42,7 +42,7 @@ func extractIPSetFromRule(rawRule adapter.HeadlessRule) []*netipx.IPSet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func hasHeadlessRule(rules []option.HeadlessRule, cond func(rule option.DefaultHeadlessRule) bool) bool {
|
func HasHeadlessRule(rules []option.HeadlessRule, cond func(rule option.DefaultHeadlessRule) bool) bool {
|
||||||
for _, rule := range rules {
|
for _, rule := range rules {
|
||||||
switch rule.Type {
|
switch rule.Type {
|
||||||
case C.RuleTypeDefault:
|
case C.RuleTypeDefault:
|
||||||
@ -50,7 +50,7 @@ func hasHeadlessRule(rules []option.HeadlessRule, cond func(rule option.DefaultH
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
case C.RuleTypeLogical:
|
case C.RuleTypeLogical:
|
||||||
if hasHeadlessRule(rule.LogicalOptions.Rules, cond) {
|
if HasHeadlessRule(rule.LogicalOptions.Rules, cond) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -138,9 +138,9 @@ func (s *LocalRuleSet) reloadRules(headlessRules []option.HeadlessRule) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
var metadata adapter.RuleSetMetadata
|
var metadata adapter.RuleSetMetadata
|
||||||
metadata.ContainsProcessRule = hasHeadlessRule(headlessRules, isProcessHeadlessRule)
|
metadata.ContainsProcessRule = HasHeadlessRule(headlessRules, isProcessHeadlessRule)
|
||||||
metadata.ContainsWIFIRule = hasHeadlessRule(headlessRules, isWIFIHeadlessRule)
|
metadata.ContainsWIFIRule = HasHeadlessRule(headlessRules, isWIFIHeadlessRule)
|
||||||
metadata.ContainsIPCIDRRule = hasHeadlessRule(headlessRules, isIPCIDRHeadlessRule)
|
metadata.ContainsIPCIDRRule = HasHeadlessRule(headlessRules, isIPCIDRHeadlessRule)
|
||||||
s.rules = rules
|
s.rules = rules
|
||||||
s.metadata = metadata
|
s.metadata = metadata
|
||||||
s.callbackAccess.Lock()
|
s.callbackAccess.Lock()
|
||||||
|
@ -185,9 +185,9 @@ func (s *RemoteRuleSet) loadBytes(content []byte) error {
|
|||||||
return E.Cause(err, "parse rule_set.rules.[", i, "]")
|
return E.Cause(err, "parse rule_set.rules.[", i, "]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s.metadata.ContainsProcessRule = hasHeadlessRule(plainRuleSet.Rules, isProcessHeadlessRule)
|
s.metadata.ContainsProcessRule = HasHeadlessRule(plainRuleSet.Rules, isProcessHeadlessRule)
|
||||||
s.metadata.ContainsWIFIRule = hasHeadlessRule(plainRuleSet.Rules, isWIFIHeadlessRule)
|
s.metadata.ContainsWIFIRule = HasHeadlessRule(plainRuleSet.Rules, isWIFIHeadlessRule)
|
||||||
s.metadata.ContainsIPCIDRRule = hasHeadlessRule(plainRuleSet.Rules, isIPCIDRHeadlessRule)
|
s.metadata.ContainsIPCIDRRule = HasHeadlessRule(plainRuleSet.Rules, isIPCIDRHeadlessRule)
|
||||||
s.rules = rules
|
s.rules = rules
|
||||||
s.callbackAccess.Lock()
|
s.callbackAccess.Lock()
|
||||||
callbacks := s.callbacks.Array()
|
callbacks := s.callbacks.Array()
|
||||||
|
@ -156,18 +156,14 @@ func (s *APIServer) deleteUser(writer http.ResponseWriter, request *http.Request
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *APIServer) getStats(writer http.ResponseWriter, request *http.Request) {
|
func (s *APIServer) getStats(writer http.ResponseWriter, request *http.Request) {
|
||||||
requireClear := chi.URLParam(request, "clear") == "true"
|
requireClear := request.URL.Query().Get("clear") == "true"
|
||||||
|
|
||||||
users := s.user.List()
|
users := s.user.List()
|
||||||
s.traffic.ReadUsers(users)
|
s.traffic.ReadUsers(users, requireClear)
|
||||||
for i := range users {
|
for i := range users {
|
||||||
users[i].Password = ""
|
users[i].Password = ""
|
||||||
}
|
}
|
||||||
uplinkBytes, downlinkBytes, uplinkPackets, downlinkPackets, tcpSessions, udpSessions := s.traffic.ReadGlobal()
|
uplinkBytes, downlinkBytes, uplinkPackets, downlinkPackets, tcpSessions, udpSessions := s.traffic.ReadGlobal(requireClear)
|
||||||
|
|
||||||
if requireClear {
|
|
||||||
s.traffic.Clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
render.JSON(writer, request, render.M{
|
render.JSON(writer, request, render.M{
|
||||||
"uplinkBytes": uplinkBytes,
|
"uplinkBytes": uplinkBytes,
|
||||||
|
@ -142,85 +142,82 @@ func (s *TrafficManager) TrackPacketConnection(conn N.PacketConn, metadata adapt
|
|||||||
readPacketCounter = append(readPacketCounter, upPacketsCounter)
|
readPacketCounter = append(readPacketCounter, upPacketsCounter)
|
||||||
writePacketCounter = append(writePacketCounter, downPacketsCounter)
|
writePacketCounter = append(writePacketCounter, downPacketsCounter)
|
||||||
udpSessionCounter.Add(1)
|
udpSessionCounter.Add(1)
|
||||||
return bufio.NewInt64CounterPacketConn(conn, append(readCounter, readPacketCounter...), append(writeCounter, writePacketCounter...))
|
return bufio.NewInt64CounterPacketConn(conn, readCounter, readPacketCounter, writeCounter, writePacketCounter)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TrafficManager) ReadUser(user *UserObject) {
|
func (s *TrafficManager) ReadUser(user *UserObject) {
|
||||||
s.userAccess.Lock()
|
s.userAccess.Lock()
|
||||||
defer s.userAccess.Unlock()
|
defer s.userAccess.Unlock()
|
||||||
s.readUser(user)
|
s.readUser(user, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TrafficManager) readUser(user *UserObject) {
|
func (s *TrafficManager) readUser(user *UserObject, swap bool) {
|
||||||
if counter, loaded := s.userUplink[user.UserName]; loaded {
|
if counter, loaded := s.userUplink[user.UserName]; loaded {
|
||||||
user.UplinkBytes = counter.Load()
|
if swap {
|
||||||
|
user.UplinkBytes = counter.Swap(0)
|
||||||
|
} else {
|
||||||
|
user.UplinkBytes = counter.Load()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if counter, loaded := s.userDownlink[user.UserName]; loaded {
|
if counter, loaded := s.userDownlink[user.UserName]; loaded {
|
||||||
user.DownlinkBytes = counter.Load()
|
if swap {
|
||||||
|
user.DownlinkBytes = counter.Swap(0)
|
||||||
|
} else {
|
||||||
|
user.DownlinkBytes = counter.Load()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if counter, loaded := s.userUplinkPackets[user.UserName]; loaded {
|
if counter, loaded := s.userUplinkPackets[user.UserName]; loaded {
|
||||||
user.UplinkPackets = counter.Load()
|
if swap {
|
||||||
|
user.UplinkPackets = counter.Swap(0)
|
||||||
|
} else {
|
||||||
|
user.UplinkPackets = counter.Load()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if counter, loaded := s.userDownlinkPackets[user.UserName]; loaded {
|
if counter, loaded := s.userDownlinkPackets[user.UserName]; loaded {
|
||||||
user.DownlinkPackets = counter.Load()
|
if swap {
|
||||||
|
user.DownlinkPackets = counter.Swap(0)
|
||||||
|
} else {
|
||||||
|
user.DownlinkPackets = counter.Load()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if counter, loaded := s.userTCPSessions[user.UserName]; loaded {
|
if counter, loaded := s.userTCPSessions[user.UserName]; loaded {
|
||||||
user.TCPSessions = counter.Load()
|
if swap {
|
||||||
|
user.TCPSessions = counter.Swap(0)
|
||||||
|
} else {
|
||||||
|
user.TCPSessions = counter.Load()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if counter, loaded := s.userUDPSessions[user.UserName]; loaded {
|
if counter, loaded := s.userUDPSessions[user.UserName]; loaded {
|
||||||
user.UDPSessions = counter.Load()
|
if swap {
|
||||||
|
user.UDPSessions = counter.Swap(0)
|
||||||
|
} else {
|
||||||
|
user.UDPSessions = counter.Load()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TrafficManager) ReadUsers(users []*UserObject) {
|
func (s *TrafficManager) ReadUsers(users []*UserObject, swap bool) {
|
||||||
s.userAccess.Lock()
|
s.userAccess.Lock()
|
||||||
defer s.userAccess.Unlock()
|
defer s.userAccess.Unlock()
|
||||||
for _, user := range users {
|
for _, user := range users {
|
||||||
s.readUser(user)
|
s.readUser(user, swap)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TrafficManager) ReadGlobal() (
|
func (s *TrafficManager) ReadGlobal(swap bool) (uplinkBytes int64, downlinkBytes int64, uplinkPackets int64, downlinkPackets int64, tcpSessions int64, udpSessions int64) {
|
||||||
uplinkBytes int64,
|
if swap {
|
||||||
downlinkBytes int64,
|
return s.globalUplink.Swap(0),
|
||||||
uplinkPackets int64,
|
s.globalDownlink.Swap(0),
|
||||||
downlinkPackets int64,
|
s.globalUplinkPackets.Swap(0),
|
||||||
tcpSessions int64,
|
s.globalDownlinkPackets.Swap(0),
|
||||||
udpSessions int64,
|
s.globalTCPSessions.Swap(0),
|
||||||
) {
|
s.globalUDPSessions.Swap(0)
|
||||||
return s.globalUplink.Load(),
|
} else {
|
||||||
s.globalDownlink.Load(),
|
return s.globalUplink.Load(),
|
||||||
s.globalUplinkPackets.Load(),
|
s.globalDownlink.Load(),
|
||||||
s.globalDownlinkPackets.Load(),
|
s.globalUplinkPackets.Load(),
|
||||||
s.globalTCPSessions.Load(),
|
s.globalDownlinkPackets.Load(),
|
||||||
s.globalUDPSessions.Load()
|
s.globalTCPSessions.Load(),
|
||||||
}
|
s.globalUDPSessions.Load()
|
||||||
|
|
||||||
func (s *TrafficManager) Clear() {
|
|
||||||
s.globalUplink.Store(0)
|
|
||||||
s.globalDownlink.Store(0)
|
|
||||||
s.globalUplinkPackets.Store(0)
|
|
||||||
s.globalDownlinkPackets.Store(0)
|
|
||||||
s.globalTCPSessions.Store(0)
|
|
||||||
s.globalUDPSessions.Store(0)
|
|
||||||
s.userAccess.Lock()
|
|
||||||
defer s.userAccess.Unlock()
|
|
||||||
for _, counter := range s.userUplink {
|
|
||||||
counter.Store(0)
|
|
||||||
}
|
|
||||||
for _, counter := range s.userDownlink {
|
|
||||||
counter.Store(0)
|
|
||||||
}
|
|
||||||
for _, counter := range s.userUplinkPackets {
|
|
||||||
counter.Store(0)
|
|
||||||
}
|
|
||||||
for _, counter := range s.userDownlinkPackets {
|
|
||||||
counter.Store(0)
|
|
||||||
}
|
|
||||||
for _, counter := range s.userTCPSessions {
|
|
||||||
counter.Store(0)
|
|
||||||
}
|
|
||||||
for _, counter := range s.userUDPSessions {
|
|
||||||
counter.Store(0)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
@ -29,7 +30,7 @@ type Client struct {
|
|||||||
serverAddr string
|
serverAddr string
|
||||||
serviceName string
|
serviceName string
|
||||||
dialOptions []grpc.DialOption
|
dialOptions []grpc.DialOption
|
||||||
conn *grpc.ClientConn
|
conn atomic.Pointer[grpc.ClientConn]
|
||||||
connAccess sync.Mutex
|
connAccess sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,13 +75,13 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) connect() (*grpc.ClientConn, error) {
|
func (c *Client) connect() (*grpc.ClientConn, error) {
|
||||||
conn := c.conn
|
conn := c.conn.Load()
|
||||||
if conn != nil && conn.GetState() != connectivity.Shutdown {
|
if conn != nil && conn.GetState() != connectivity.Shutdown {
|
||||||
return conn, nil
|
return conn, nil
|
||||||
}
|
}
|
||||||
c.connAccess.Lock()
|
c.connAccess.Lock()
|
||||||
defer c.connAccess.Unlock()
|
defer c.connAccess.Unlock()
|
||||||
conn = c.conn
|
conn = c.conn.Load()
|
||||||
if conn != nil && conn.GetState() != connectivity.Shutdown {
|
if conn != nil && conn.GetState() != connectivity.Shutdown {
|
||||||
return conn, nil
|
return conn, nil
|
||||||
}
|
}
|
||||||
@ -89,7 +90,7 @@ func (c *Client) connect() (*grpc.ClientConn, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
c.conn = conn
|
c.conn.Store(conn)
|
||||||
return conn, nil
|
return conn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,11 +110,9 @@ func (c *Client) DialContext(ctx context.Context) (net.Conn, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) Close() error {
|
func (c *Client) Close() error {
|
||||||
c.connAccess.Lock()
|
conn := c.conn.Swap(nil)
|
||||||
defer c.connAccess.Unlock()
|
if conn != nil {
|
||||||
if c.conn != nil {
|
conn.Close()
|
||||||
c.conn.Close()
|
|
||||||
c.conn = nil
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,6 @@ var defaultClientHeader = http.Header{
|
|||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
dialer N.Dialer
|
|
||||||
serverAddr M.Socksaddr
|
serverAddr M.Socksaddr
|
||||||
transport *http2.Transport
|
transport *http2.Transport
|
||||||
options option.V2RayGRPCOptions
|
options option.V2RayGRPCOptions
|
||||||
@ -46,7 +45,6 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt
|
|||||||
}
|
}
|
||||||
client := &Client{
|
client := &Client{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
dialer: dialer,
|
|
||||||
serverAddr: serverAddr,
|
serverAddr: serverAddr,
|
||||||
options: options,
|
options: options,
|
||||||
transport: &http2.Transport{
|
transport: &http2.Transport{
|
||||||
@ -62,7 +60,6 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt
|
|||||||
},
|
},
|
||||||
host: host,
|
host: host,
|
||||||
}
|
}
|
||||||
|
|
||||||
if tlsConfig == nil {
|
if tlsConfig == nil {
|
||||||
client.transport.DialTLSContext = func(ctx context.Context, network, addr string, cfg *tls.STDConfig) (net.Conn, error) {
|
client.transport.DialTLSContext = func(ctx context.Context, network, addr string, cfg *tls.STDConfig) (net.Conn, error) {
|
||||||
return dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
return dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
||||||
@ -71,12 +68,9 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt
|
|||||||
if len(tlsConfig.NextProtos()) == 0 {
|
if len(tlsConfig.NextProtos()) == 0 {
|
||||||
tlsConfig.SetNextProtos([]string{http2.NextProtoTLS})
|
tlsConfig.SetNextProtos([]string{http2.NextProtoTLS})
|
||||||
}
|
}
|
||||||
|
tlsDialer := tls.NewDialer(dialer, tlsConfig)
|
||||||
client.transport.DialTLSContext = func(ctx context.Context, network, addr string, cfg *tls.STDConfig) (net.Conn, error) {
|
client.transport.DialTLSContext = func(ctx context.Context, network, addr string, cfg *tls.STDConfig) (net.Conn, error) {
|
||||||
conn, err := dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
return tlsDialer.DialTLSContext(ctx, M.ParseSocksaddr(addr))
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return tls.ClientHandshake(ctx, conn, tlsConfig)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,15 +47,12 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt
|
|||||||
if len(tlsConfig.NextProtos()) == 0 {
|
if len(tlsConfig.NextProtos()) == 0 {
|
||||||
tlsConfig.SetNextProtos([]string{http2.NextProtoTLS})
|
tlsConfig.SetNextProtos([]string{http2.NextProtoTLS})
|
||||||
}
|
}
|
||||||
|
tlsDialer := tls.NewDialer(dialer, tlsConfig)
|
||||||
transport = &http2.Transport{
|
transport = &http2.Transport{
|
||||||
ReadIdleTimeout: time.Duration(options.IdleTimeout),
|
ReadIdleTimeout: time.Duration(options.IdleTimeout),
|
||||||
PingTimeout: time.Duration(options.PingTimeout),
|
PingTimeout: time.Duration(options.PingTimeout),
|
||||||
DialTLSContext: func(ctx context.Context, network, addr string, cfg *tls.STDConfig) (net.Conn, error) {
|
DialTLSContext: func(ctx context.Context, network, addr string, cfg *tls.STDConfig) (net.Conn, error) {
|
||||||
conn, err := dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
return tlsDialer.DialTLSContext(ctx, M.ParseSocksaddr(addr))
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return tls.ClientHandshake(ctx, conn, tlsConfig)
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,6 @@ var _ adapter.V2RayClientTransport = (*Client)(nil)
|
|||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
dialer N.Dialer
|
dialer N.Dialer
|
||||||
tlsConfig tls.Config
|
|
||||||
serverAddr M.Socksaddr
|
serverAddr M.Socksaddr
|
||||||
requestURL url.URL
|
requestURL url.URL
|
||||||
headers http.Header
|
headers http.Header
|
||||||
@ -35,6 +34,7 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt
|
|||||||
if len(tlsConfig.NextProtos()) == 0 {
|
if len(tlsConfig.NextProtos()) == 0 {
|
||||||
tlsConfig.SetNextProtos([]string{"http/1.1"})
|
tlsConfig.SetNextProtos([]string{"http/1.1"})
|
||||||
}
|
}
|
||||||
|
dialer = tls.NewDialer(dialer, tlsConfig)
|
||||||
}
|
}
|
||||||
var host string
|
var host string
|
||||||
if options.Host != "" {
|
if options.Host != "" {
|
||||||
@ -65,7 +65,6 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt
|
|||||||
}
|
}
|
||||||
return &Client{
|
return &Client{
|
||||||
dialer: dialer,
|
dialer: dialer,
|
||||||
tlsConfig: tlsConfig,
|
|
||||||
serverAddr: serverAddr,
|
serverAddr: serverAddr,
|
||||||
requestURL: requestURL,
|
requestURL: requestURL,
|
||||||
headers: headers,
|
headers: headers,
|
||||||
@ -78,12 +77,6 @@ func (c *Client) DialContext(ctx context.Context) (net.Conn, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if c.tlsConfig != nil {
|
|
||||||
conn, err = tls.ClientHandshake(ctx, conn, c.tlsConfig)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
request := &http.Request{
|
request := &http.Request{
|
||||||
Method: http.MethodGet,
|
Method: http.MethodGet,
|
||||||
URL: &c.requestURL,
|
URL: &c.requestURL,
|
||||||
|
@ -15,6 +15,7 @@ import (
|
|||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-quic"
|
"github.com/sagernet/sing-quic"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
|
"github.com/sagernet/sing/common/atomic"
|
||||||
"github.com/sagernet/sing/common/bufio"
|
"github.com/sagernet/sing/common/bufio"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
@ -29,7 +30,7 @@ type Client struct {
|
|||||||
tlsConfig tls.Config
|
tlsConfig tls.Config
|
||||||
quicConfig *quic.Config
|
quicConfig *quic.Config
|
||||||
connAccess sync.Mutex
|
connAccess sync.Mutex
|
||||||
conn quic.Connection
|
conn atomic.TypedValue[quic.Connection]
|
||||||
rawConn net.Conn
|
rawConn net.Conn
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,13 +51,13 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) offer() (quic.Connection, error) {
|
func (c *Client) offer() (quic.Connection, error) {
|
||||||
conn := c.conn
|
conn := c.conn.Load()
|
||||||
if conn != nil && !common.Done(conn.Context()) {
|
if conn != nil && !common.Done(conn.Context()) {
|
||||||
return conn, nil
|
return conn, nil
|
||||||
}
|
}
|
||||||
c.connAccess.Lock()
|
c.connAccess.Lock()
|
||||||
defer c.connAccess.Unlock()
|
defer c.connAccess.Unlock()
|
||||||
conn = c.conn
|
conn = c.conn.Load()
|
||||||
if conn != nil && !common.Done(conn.Context()) {
|
if conn != nil && !common.Done(conn.Context()) {
|
||||||
return conn, nil
|
return conn, nil
|
||||||
}
|
}
|
||||||
@ -78,7 +79,7 @@ func (c *Client) offerNew() (quic.Connection, error) {
|
|||||||
packetConn.Close()
|
packetConn.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
c.conn = quicConn
|
c.conn.Store(quicConn)
|
||||||
c.rawConn = udpConn
|
c.rawConn = udpConn
|
||||||
return quicConn, nil
|
return quicConn, nil
|
||||||
}
|
}
|
||||||
@ -98,13 +99,13 @@ func (c *Client) DialContext(ctx context.Context) (net.Conn, error) {
|
|||||||
func (c *Client) Close() error {
|
func (c *Client) Close() error {
|
||||||
c.connAccess.Lock()
|
c.connAccess.Lock()
|
||||||
defer c.connAccess.Unlock()
|
defer c.connAccess.Unlock()
|
||||||
if c.conn != nil {
|
conn := c.conn.Swap(nil)
|
||||||
c.conn.CloseWithError(0, "")
|
if conn != nil {
|
||||||
|
conn.CloseWithError(0, "")
|
||||||
}
|
}
|
||||||
if c.rawConn != nil {
|
if c.rawConn != nil {
|
||||||
c.rawConn.Close()
|
c.rawConn.Close()
|
||||||
}
|
}
|
||||||
c.conn = nil
|
|
||||||
c.rawConn = nil
|
c.rawConn = nil
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,6 @@ var _ adapter.V2RayClientTransport = (*Client)(nil)
|
|||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
dialer N.Dialer
|
dialer N.Dialer
|
||||||
tlsConfig tls.Config
|
|
||||||
serverAddr M.Socksaddr
|
serverAddr M.Socksaddr
|
||||||
requestURL url.URL
|
requestURL url.URL
|
||||||
headers http.Header
|
headers http.Header
|
||||||
@ -39,6 +38,7 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt
|
|||||||
if len(tlsConfig.NextProtos()) == 0 {
|
if len(tlsConfig.NextProtos()) == 0 {
|
||||||
tlsConfig.SetNextProtos([]string{"http/1.1"})
|
tlsConfig.SetNextProtos([]string{"http/1.1"})
|
||||||
}
|
}
|
||||||
|
dialer = tls.NewDialer(dialer, tlsConfig)
|
||||||
}
|
}
|
||||||
var requestURL url.URL
|
var requestURL url.URL
|
||||||
if tlsConfig == nil {
|
if tlsConfig == nil {
|
||||||
@ -65,7 +65,6 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt
|
|||||||
}
|
}
|
||||||
return &Client{
|
return &Client{
|
||||||
dialer,
|
dialer,
|
||||||
tlsConfig,
|
|
||||||
serverAddr,
|
serverAddr,
|
||||||
requestURL,
|
requestURL,
|
||||||
headers,
|
headers,
|
||||||
@ -79,12 +78,6 @@ func (c *Client) dialContext(ctx context.Context, requestURL *url.URL, headers h
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if c.tlsConfig != nil {
|
|
||||||
conn, err = tls.ClientHandshake(ctx, conn, c.tlsConfig)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var deadlineConn net.Conn
|
var deadlineConn net.Conn
|
||||||
if deadline.NeedAdditionalReadDeadline(conn) {
|
if deadline.NeedAdditionalReadDeadline(conn) {
|
||||||
deadlineConn = deadline.NewConn(conn)
|
deadlineConn = deadline.NewConn(conn)
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
@ -135,20 +136,22 @@ func (c *WebsocketConn) Upstream() any {
|
|||||||
type EarlyWebsocketConn struct {
|
type EarlyWebsocketConn struct {
|
||||||
*Client
|
*Client
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
conn *WebsocketConn
|
conn atomic.Pointer[WebsocketConn]
|
||||||
access sync.Mutex
|
access sync.Mutex
|
||||||
create chan struct{}
|
create chan struct{}
|
||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *EarlyWebsocketConn) Read(b []byte) (n int, err error) {
|
func (c *EarlyWebsocketConn) Read(b []byte) (n int, err error) {
|
||||||
if c.conn == nil {
|
conn := c.conn.Load()
|
||||||
|
if conn == nil {
|
||||||
<-c.create
|
<-c.create
|
||||||
if c.err != nil {
|
if c.err != nil {
|
||||||
return 0, c.err
|
return 0, c.err
|
||||||
}
|
}
|
||||||
|
conn = c.conn.Load()
|
||||||
}
|
}
|
||||||
return wrapWsError0(c.conn.Read(b))
|
return wrapWsError0(conn.Read(b))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *EarlyWebsocketConn) writeRequest(content []byte) error {
|
func (c *EarlyWebsocketConn) writeRequest(content []byte) error {
|
||||||
@ -187,21 +190,23 @@ func (c *EarlyWebsocketConn) writeRequest(content []byte) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c.conn = conn
|
c.conn.Store(conn)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *EarlyWebsocketConn) Write(b []byte) (n int, err error) {
|
func (c *EarlyWebsocketConn) Write(b []byte) (n int, err error) {
|
||||||
if c.conn != nil {
|
conn := c.conn.Load()
|
||||||
return wrapWsError0(c.conn.Write(b))
|
if conn != nil {
|
||||||
|
return wrapWsError0(conn.Write(b))
|
||||||
}
|
}
|
||||||
c.access.Lock()
|
c.access.Lock()
|
||||||
defer c.access.Unlock()
|
defer c.access.Unlock()
|
||||||
|
conn = c.conn.Load()
|
||||||
if c.err != nil {
|
if c.err != nil {
|
||||||
return 0, c.err
|
return 0, c.err
|
||||||
}
|
}
|
||||||
if c.conn != nil {
|
if conn != nil {
|
||||||
return wrapWsError0(c.conn.Write(b))
|
return wrapWsError0(conn.Write(b))
|
||||||
}
|
}
|
||||||
err = c.writeRequest(b)
|
err = c.writeRequest(b)
|
||||||
c.err = err
|
c.err = err
|
||||||
@ -213,17 +218,19 @@ func (c *EarlyWebsocketConn) Write(b []byte) (n int, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *EarlyWebsocketConn) WriteBuffer(buffer *buf.Buffer) error {
|
func (c *EarlyWebsocketConn) WriteBuffer(buffer *buf.Buffer) error {
|
||||||
if c.conn != nil {
|
conn := c.conn.Load()
|
||||||
return wrapWsError(c.conn.WriteBuffer(buffer))
|
if conn != nil {
|
||||||
|
return wrapWsError(conn.WriteBuffer(buffer))
|
||||||
}
|
}
|
||||||
c.access.Lock()
|
c.access.Lock()
|
||||||
defer c.access.Unlock()
|
defer c.access.Unlock()
|
||||||
if c.conn != nil {
|
|
||||||
return wrapWsError(c.conn.WriteBuffer(buffer))
|
|
||||||
}
|
|
||||||
if c.err != nil {
|
if c.err != nil {
|
||||||
return c.err
|
return c.err
|
||||||
}
|
}
|
||||||
|
conn = c.conn.Load()
|
||||||
|
if conn != nil {
|
||||||
|
return wrapWsError(conn.WriteBuffer(buffer))
|
||||||
|
}
|
||||||
err := c.writeRequest(buffer.Bytes())
|
err := c.writeRequest(buffer.Bytes())
|
||||||
c.err = err
|
c.err = err
|
||||||
close(c.create)
|
close(c.create)
|
||||||
@ -231,24 +238,27 @@ func (c *EarlyWebsocketConn) WriteBuffer(buffer *buf.Buffer) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *EarlyWebsocketConn) Close() error {
|
func (c *EarlyWebsocketConn) Close() error {
|
||||||
if c.conn == nil {
|
conn := c.conn.Load()
|
||||||
|
if conn == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return c.conn.Close()
|
return conn.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *EarlyWebsocketConn) LocalAddr() net.Addr {
|
func (c *EarlyWebsocketConn) LocalAddr() net.Addr {
|
||||||
if c.conn == nil {
|
conn := c.conn.Load()
|
||||||
|
if conn == nil {
|
||||||
return M.Socksaddr{}
|
return M.Socksaddr{}
|
||||||
}
|
}
|
||||||
return c.conn.LocalAddr()
|
return conn.LocalAddr()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *EarlyWebsocketConn) RemoteAddr() net.Addr {
|
func (c *EarlyWebsocketConn) RemoteAddr() net.Addr {
|
||||||
if c.conn == nil {
|
conn := c.conn.Load()
|
||||||
|
if conn == nil {
|
||||||
return M.Socksaddr{}
|
return M.Socksaddr{}
|
||||||
}
|
}
|
||||||
return c.conn.RemoteAddr()
|
return conn.RemoteAddr()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *EarlyWebsocketConn) SetDeadline(t time.Time) error {
|
func (c *EarlyWebsocketConn) SetDeadline(t time.Time) error {
|
||||||
@ -268,11 +278,11 @@ func (c *EarlyWebsocketConn) NeedAdditionalReadDeadline() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *EarlyWebsocketConn) Upstream() any {
|
func (c *EarlyWebsocketConn) Upstream() any {
|
||||||
return common.PtrOrNil(c.conn)
|
return common.PtrOrNil(c.conn.Load())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *EarlyWebsocketConn) LazyHeadroom() bool {
|
func (c *EarlyWebsocketConn) LazyHeadroom() bool {
|
||||||
return c.conn == nil
|
return c.conn.Load() == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func wrapWsError(err error) error {
|
func wrapWsError(err error) error {
|
||||||
|
@ -8,7 +8,9 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
@ -30,6 +32,7 @@ type Endpoint struct {
|
|||||||
allowedAddress []netip.Prefix
|
allowedAddress []netip.Prefix
|
||||||
tunDevice Device
|
tunDevice Device
|
||||||
device *device.Device
|
device *device.Device
|
||||||
|
allowedIPs *device.AllowedIPs
|
||||||
pause pause.Manager
|
pause pause.Manager
|
||||||
pauseCallback *list.Element[pause.Callback]
|
pauseCallback *list.Element[pause.Callback]
|
||||||
}
|
}
|
||||||
@ -191,6 +194,7 @@ func (e *Endpoint) Start(resolve bool) error {
|
|||||||
if e.pause != nil {
|
if e.pause != nil {
|
||||||
e.pauseCallback = e.pause.RegisterCallback(e.onPauseUpdated)
|
e.pauseCallback = e.pause.RegisterCallback(e.onPauseUpdated)
|
||||||
}
|
}
|
||||||
|
e.allowedIPs = (*device.AllowedIPs)(unsafe.Pointer(reflect.Indirect(reflect.ValueOf(wgDevice)).FieldByName("allowedips").UnsafeAddr()))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -218,6 +222,10 @@ func (e *Endpoint) Close() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *Endpoint) Lookup(address netip.Addr) *device.Peer {
|
||||||
|
return e.allowedIPs.Lookup(address.AsSlice())
|
||||||
|
}
|
||||||
|
|
||||||
func (e *Endpoint) onPauseUpdated(event int) {
|
func (e *Endpoint) onPauseUpdated(event int) {
|
||||||
switch event {
|
switch event {
|
||||||
case pause.EventDevicePaused, pause.EventNetworkPause:
|
case pause.EventDevicePaused, pause.EventNetworkPause:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user