diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8e34d6cb..3bb8a435 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -189,13 +189,14 @@ jobs: fi echo "DIR_NAME=${DIR_NAME}" >> "${GITHUB_ENV}" PKG_VERSION="${{ needs.calculate_version.outputs.version }}" - PKG_VERSION="${PKG_VERSION//-/\~}-1" + PKG_VERSION="${PKG_VERSION//-/\~}" echo "PKG_VERSION=${PKG_VERSION}" >> "${GITHUB_ENV}" - name: Package DEB if: matrix.debian != '' run: | set -xeuo pipefail sudo gem install fpm + sudo apt-get update sudo apt-get install -y debsigs cp .fpm_systemd .fpm fpm -t deb \ @@ -234,6 +235,7 @@ jobs: run: |- set -xeuo pipefail sudo gem install fpm + sudo apt-get update sudo apt-get install -y libarchive-tools cp .fpm_systemd .fpm fpm -t pacman \ diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 4c0af758..51292cee 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -120,6 +120,7 @@ jobs: set -xeuo pipefail sudo gem install fpm sudo apt-get install -y debsigs + cp .fpm_systemd .fpm fpm -t deb \ --name "${NAME}" \ -v "$PKG_VERSION" \ @@ -138,6 +139,7 @@ jobs: run: |- set -xeuo pipefail sudo gem install fpm + cp .fpm_systemd .fpm fpm -t rpm \ --name "${NAME}" \ -v "$PKG_VERSION" \ diff --git a/clients/android b/clients/android index 55f31c29..6a15780c 160000 --- a/clients/android +++ b/clients/android @@ -1 +1 @@ -Subproject commit 55f31c29bb68895ce544e0dfbf852b4b3e32b530 +Subproject commit 6a15780ce1659a234816f7248cbc09e8ea54a1be diff --git a/common/dialer/default.go b/common/dialer/default.go index 2ebb83d9..b076f049 100644 --- a/common/dialer/default.go +++ b/common/dialer/default.go @@ -341,7 +341,17 @@ func (d *DefaultDialer) ListenSerialInterfacePacket(ctx context.Context, destina } func (d *DefaultDialer) ListenPacketCompat(network, address string) (net.PacketConn, error) { - return d.udpListener.ListenPacket(context.Background(), network, address) + udpListener := d.udpListener + udpListener.Control = control.Append(udpListener.Control, func(network, address string, conn syscall.RawConn) error { + for _, wgControlFn := range WgControlFns { + err := wgControlFn(network, address, conn) + if err != nil { + return err + } + } + return nil + }) + return udpListener.ListenPacket(context.Background(), network, address) } func trackConn(conn net.Conn, err error) (net.Conn, error) { diff --git a/common/sniff/bittorrent.go b/common/sniff/bittorrent.go index 39c19598..e4d9f4b8 100644 --- a/common/sniff/bittorrent.go +++ b/common/sniff/bittorrent.go @@ -31,13 +31,18 @@ func BitTorrent(_ context.Context, metadata *adapter.InboundContext, reader io.R return os.ErrInvalid } + const header = "BitTorrent protocol" var protocol [19]byte - _, err = reader.Read(protocol[:]) + var n int + n, err = reader.Read(protocol[:]) + if string(protocol[:n]) != header[:n] { + return os.ErrInvalid + } if err != nil { return E.Cause1(ErrNeedMoreData, err) } - if string(protocol[:]) != "BitTorrent protocol" { - return os.ErrInvalid + if n < 19 { + return ErrNeedMoreData } metadata.Protocol = C.ProtocolBitTorrent diff --git a/common/sniff/bittorrent_test.go b/common/sniff/bittorrent_test.go index f4762e32..fcb5f6fa 100644 --- a/common/sniff/bittorrent_test.go +++ b/common/sniff/bittorrent_test.go @@ -32,6 +32,27 @@ func TestSniffBittorrent(t *testing.T) { } } +func TestSniffIncompleteBittorrent(t *testing.T) { + t.Parallel() + + pkt, err := hex.DecodeString("13426974546f7272656e74") + require.NoError(t, err) + var metadata adapter.InboundContext + err = sniff.BitTorrent(context.TODO(), &metadata, bytes.NewReader(pkt)) + require.ErrorIs(t, err, sniff.ErrNeedMoreData) +} + +func TestSniffNotBittorrent(t *testing.T) { + t.Parallel() + + pkt, err := hex.DecodeString("13426974546f7272656e75") + require.NoError(t, err) + var metadata adapter.InboundContext + err = sniff.BitTorrent(context.TODO(), &metadata, bytes.NewReader(pkt)) + require.NotEmpty(t, err) + require.NotErrorIs(t, err, sniff.ErrNeedMoreData) +} + func TestSniffUTP(t *testing.T) { t.Parallel() diff --git a/common/sniff/dns.go b/common/sniff/dns.go index 2e22f3d7..7125a08e 100644 --- a/common/sniff/dns.go +++ b/common/sniff/dns.go @@ -20,22 +20,36 @@ func StreamDomainNameQuery(readCtx context.Context, metadata *adapter.InboundCon if err != nil { return E.Cause1(ErrNeedMoreData, err) } - if length == 0 { + if length < 12 { return os.ErrInvalid } buffer := buf.NewSize(int(length)) defer buffer.Release() - _, err = buffer.ReadFullFrom(reader, buffer.FreeLen()) + var n int + n, err = buffer.ReadFullFrom(reader, buffer.FreeLen()) + packet := buffer.Bytes() + if n > 2 && packet[2]&0x80 != 0 { // QR + return os.ErrInvalid + } + if n > 5 && packet[4] == 0 && packet[5] == 0 { // QDCOUNT + return os.ErrInvalid + } + for i := 6; i < 10; i++ { + // ANCOUNT, NSCOUNT + if n > i && packet[i] != 0 { + return os.ErrInvalid + } + } if err != nil { return E.Cause1(ErrNeedMoreData, err) } - return DomainNameQuery(readCtx, metadata, buffer.Bytes()) + return DomainNameQuery(readCtx, metadata, packet) } func DomainNameQuery(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error { var msg mDNS.Msg err := msg.Unpack(packet) - if err != nil { + if err != nil || msg.Response || len(msg.Question) == 0 || len(msg.Answer) > 0 || len(msg.Ns) > 0 { return err } metadata.Protocol = C.ProtocolDNS diff --git a/common/sniff/dns_test.go b/common/sniff/dns_test.go index eaf4dd1a..d78b0bf5 100644 --- a/common/sniff/dns_test.go +++ b/common/sniff/dns_test.go @@ -1,6 +1,7 @@ package sniff_test import ( + "bytes" "context" "encoding/hex" "testing" @@ -21,3 +22,32 @@ func TestSniffDNS(t *testing.T) { require.NoError(t, err) require.Equal(t, C.ProtocolDNS, metadata.Protocol) } + +func TestSniffStreamDNS(t *testing.T) { + t.Parallel() + query, err := hex.DecodeString("001e740701000001000000000000012a06676f6f676c6503636f6d0000010001") + require.NoError(t, err) + var metadata adapter.InboundContext + err = sniff.StreamDomainNameQuery(context.TODO(), &metadata, bytes.NewReader(query)) + require.NoError(t, err) + require.Equal(t, C.ProtocolDNS, metadata.Protocol) +} + +func TestSniffIncompleteStreamDNS(t *testing.T) { + t.Parallel() + query, err := hex.DecodeString("001e740701000001000000000000") + require.NoError(t, err) + var metadata adapter.InboundContext + err = sniff.StreamDomainNameQuery(context.TODO(), &metadata, bytes.NewReader(query)) + require.ErrorIs(t, err, sniff.ErrNeedMoreData) +} + +func TestSniffNotStreamDNS(t *testing.T) { + t.Parallel() + query, err := hex.DecodeString("001e740701000000000000000000") + require.NoError(t, err) + var metadata adapter.InboundContext + err = sniff.StreamDomainNameQuery(context.TODO(), &metadata, bytes.NewReader(query)) + require.NotEmpty(t, err) + require.NotErrorIs(t, err, sniff.ErrNeedMoreData) +} diff --git a/common/sniff/sniff.go b/common/sniff/sniff.go index 59e81aaa..b3651e1f 100644 --- a/common/sniff/sniff.go +++ b/common/sniff/sniff.go @@ -68,7 +68,7 @@ func PeekStream(ctx context.Context, metadata *adapter.InboundContext, conn net. } sniffError = E.Errors(sniffError, err) } - if !errors.Is(err, ErrNeedMoreData) { + if !errors.Is(sniffError, ErrNeedMoreData) { break } } diff --git a/common/sniff/ssh.go b/common/sniff/ssh.go index d373d292..dce5d54f 100644 --- a/common/sniff/ssh.go +++ b/common/sniff/ssh.go @@ -15,10 +15,11 @@ func SSH(_ context.Context, metadata *adapter.InboundContext, reader io.Reader) const sshPrefix = "SSH-2.0-" bReader := bufio.NewReader(reader) prefix, err := bReader.Peek(len(sshPrefix)) + if string(prefix[:]) != sshPrefix[:len(prefix)] { + return os.ErrInvalid + } if err != nil { return E.Cause1(ErrNeedMoreData, err) - } else if string(prefix) != sshPrefix { - return os.ErrInvalid } fistLine, _, err := bReader.ReadLine() if err != nil { diff --git a/common/sniff/ssh_test.go b/common/sniff/ssh_test.go index be530980..7cea5aab 100644 --- a/common/sniff/ssh_test.go +++ b/common/sniff/ssh_test.go @@ -24,3 +24,24 @@ func TestSniffSSH(t *testing.T) { require.Equal(t, C.ProtocolSSH, metadata.Protocol) require.Equal(t, "dropbear", metadata.Client) } + +func TestSniffIncompleteSSH(t *testing.T) { + t.Parallel() + + pkt, err := hex.DecodeString("5353482d322e30") + require.NoError(t, err) + var metadata adapter.InboundContext + err = sniff.SSH(context.TODO(), &metadata, bytes.NewReader(pkt)) + require.ErrorIs(t, err, sniff.ErrNeedMoreData) +} + +func TestSniffNotSSH(t *testing.T) { + t.Parallel() + + pkt, err := hex.DecodeString("5353482d322e31") + require.NoError(t, err) + var metadata adapter.InboundContext + err = sniff.SSH(context.TODO(), &metadata, bytes.NewReader(pkt)) + require.NotEmpty(t, err) + require.NotErrorIs(t, err, sniff.ErrNeedMoreData) +} diff --git a/dns/client.go b/dns/client.go index f456f878..90c950b4 100644 --- a/dns/client.go +++ b/dns/client.go @@ -483,7 +483,7 @@ func (c *Client) loadResponse(question dns.Question, transport adapter.DNSTransp } func MessageToAddresses(response *dns.Msg) ([]netip.Addr, error) { - if response.Rcode != dns.RcodeSuccess && response.Rcode != dns.RcodeNameError { + if response.Rcode != dns.RcodeSuccess { return nil, RcodeError(response.Rcode) } addresses := make([]netip.Addr, 0, len(response.Answer)) diff --git a/dns/router.go b/dns/router.go index 44edadbd..bf4361ce 100644 --- a/dns/router.go +++ b/dns/router.go @@ -323,6 +323,9 @@ func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQ err error ) printResult := func() { + if err == nil && len(responseAddrs) == 0 { + err = E.New("empty result") + } if err != nil { if errors.Is(err, ErrResponseRejectedCached) { r.logger.DebugContext(ctx, "response rejected for ", domain, " (cached)") @@ -331,15 +334,15 @@ func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQ } else { r.logger.ErrorContext(ctx, E.Cause(err, "lookup failed for ", domain)) } - } else if len(responseAddrs) == 0 { - r.logger.ErrorContext(ctx, "lookup failed for ", domain, ": empty result") - err = RcodeNameError + } + if err != nil { + err = E.Cause(err, "lookup ", domain) } } responseAddrs, cached = r.client.LookupCache(domain, options.Strategy) if cached { if len(responseAddrs) == 0 { - return nil, RcodeNameError + return nil, E.New("lookup ", domain, ": empty result (cached)") } return responseAddrs, nil } diff --git a/dns/transport/https.go b/dns/transport/https.go index 1750fd26..a13d9116 100644 --- a/dns/transport/https.go +++ b/dns/transport/https.go @@ -96,6 +96,9 @@ func NewHTTPS(ctx context.Context, logger log.ContextLogger, tag string, options if serverAddr.Port == 0 { serverAddr.Port = 443 } + if !serverAddr.IsValid() { + return nil, E.New("invalid server address: ", serverAddr) + } return NewHTTPSRaw( dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeHTTPS, tag, options.RemoteDNSServerOptions), logger, diff --git a/dns/transport/local/local.go b/dns/transport/local/local.go index df473b3c..15be54c3 100644 --- a/dns/transport/local/local.go +++ b/dns/transport/local/local.go @@ -3,6 +3,7 @@ package local import ( "context" "math/rand" + "net/netip" "time" "github.com/sagernet/sing-box/adapter" @@ -90,8 +91,9 @@ func (t *Transport) exchangeParallel(ctx context.Context, systemConfig *dnsConfi startRacer := func(ctx context.Context, fqdn string) { response, err := t.tryOneName(ctx, systemConfig, fqdn, message) if err == nil { - addresses, _ := dns.MessageToAddresses(response) - if len(addresses) == 0 { + var addresses []netip.Addr + addresses, err = dns.MessageToAddresses(response) + if err == nil && len(addresses) == 0 { err = E.New(fqdn, ": empty result") } } diff --git a/dns/transport/quic/http3.go b/dns/transport/quic/http3.go index 0d871741..fd1591a3 100644 --- a/dns/transport/quic/http3.go +++ b/dns/transport/quic/http3.go @@ -92,6 +92,9 @@ func NewHTTP3(ctx context.Context, logger log.ContextLogger, tag string, options if serverAddr.Port == 0 { serverAddr.Port = 443 } + if !serverAddr.IsValid() { + return nil, E.New("invalid server address: ", serverAddr) + } return &HTTP3Transport{ TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeHTTP3, tag, options.RemoteDNSServerOptions), logger: logger, diff --git a/dns/transport/quic/quic.go b/dns/transport/quic/quic.go index 18f8b9fe..515aff58 100644 --- a/dns/transport/quic/quic.go +++ b/dns/transport/quic/quic.go @@ -16,6 +16,7 @@ import ( sQUIC "github.com/sagernet/sing-quic" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/bufio" + E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" @@ -58,6 +59,9 @@ func NewQUIC(ctx context.Context, logger log.ContextLogger, tag string, options if serverAddr.Port == 0 { serverAddr.Port = 853 } + if !serverAddr.IsValid() { + return nil, E.New("invalid server address: ", serverAddr) + } return &Transport{ TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeQUIC, tag, options.RemoteDNSServerOptions), ctx: ctx, diff --git a/dns/transport/tcp.go b/dns/transport/tcp.go index a814c030..3039c574 100644 --- a/dns/transport/tcp.go +++ b/dns/transport/tcp.go @@ -13,6 +13,7 @@ import ( "github.com/sagernet/sing-box/option" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/buf" + E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" @@ -40,6 +41,9 @@ func NewTCP(ctx context.Context, logger log.ContextLogger, tag string, options o if serverAddr.Port == 0 { serverAddr.Port = 53 } + if !serverAddr.IsValid() { + return nil, E.New("invalid server address: ", serverAddr) + } return &TCPTransport{ TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeTCP, tag, options), dialer: transportDialer, diff --git a/dns/transport/tls.go b/dns/transport/tls.go index a99bf2f7..b6ac294a 100644 --- a/dns/transport/tls.go +++ b/dns/transport/tls.go @@ -57,6 +57,9 @@ func NewTLS(ctx context.Context, logger log.ContextLogger, tag string, options o if serverAddr.Port == 0 { serverAddr.Port = 853 } + if !serverAddr.IsValid() { + return nil, E.New("invalid server address: ", serverAddr) + } return &TLSTransport{ TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeTLS, tag, options.RemoteDNSServerOptions), logger: logger, diff --git a/dns/transport/udp.go b/dns/transport/udp.go index e2ad9db7..a9c1d4d9 100644 --- a/dns/transport/udp.go +++ b/dns/transport/udp.go @@ -13,6 +13,7 @@ import ( "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing/common/buf" + E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" @@ -47,6 +48,9 @@ func NewUDP(ctx context.Context, logger log.ContextLogger, tag string, options o if serverAddr.Port == 0 { serverAddr.Port = 53 } + if !serverAddr.IsValid() { + return nil, E.New("invalid server address: ", serverAddr) + } return NewUDPRaw(logger, dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeUDP, tag, options), transportDialer, serverAddr), nil } diff --git a/docs/changelog.md b/docs/changelog.md index c7d86cb1..628dad4d 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,7 +2,17 @@ icon: material/alert-decagram --- -#### 1.12.0-beta.4 +#### 1.12.0-beta.7 + +* Fixes and improvements + +### 1.11.9 + +* Fixes and improvements + +_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we violated the rules (TestFlight users are not affected)._ + +#### 1.12.0-beta.5 * Fixes and improvements diff --git a/docs/configuration/inbound/anytls.md b/docs/configuration/inbound/anytls.md index 55790810..f3780119 100644 --- a/docs/configuration/inbound/anytls.md +++ b/docs/configuration/inbound/anytls.md @@ -42,16 +42,18 @@ AnyTLS padding scheme line array. Default padding scheme: -``` -stop=8 -0=30-30 -1=100-400 -2=400-500,c,500-1000,c,500-1000,c,500-1000,c,500-1000 -3=9-9,500-1000 -4=500-1000 -5=500-1000 -6=500-1000 -7=500-1000 +```json +[ + "stop=8", + "0=30-30", + "1=100-400", + "2=400-500,c,500-1000,c,500-1000,c,500-1000,c,500-1000", + "3=9-9,500-1000", + "4=500-1000", + "5=500-1000", + "6=500-1000", + "7=500-1000" +] ``` #### tls diff --git a/docs/configuration/inbound/anytls.zh.md b/docs/configuration/inbound/anytls.zh.md index 099da777..55b6749e 100644 --- a/docs/configuration/inbound/anytls.zh.md +++ b/docs/configuration/inbound/anytls.zh.md @@ -42,16 +42,18 @@ AnyTLS 填充方案行数组。 默认填充方案: -``` -stop=8 -0=30-30 -1=100-400 -2=400-500,c,500-1000,c,500-1000,c,500-1000,c,500-1000 -3=9-9,500-1000 -4=500-1000 -5=500-1000 -6=500-1000 -7=500-1000 +```json +[ + "stop=8", + "0=30-30", + "1=100-400", + "2=400-500,c,500-1000,c,500-1000,c,500-1000,c,500-1000", + "3=9-9,500-1000", + "4=500-1000", + "5=500-1000", + "6=500-1000", + "7=500-1000" +] ``` #### tls diff --git a/docs/installation/package-manager.md b/docs/installation/package-manager.md index c54eee87..fe59c73a 100644 --- a/docs/installation/package-manager.md +++ b/docs/installation/package-manager.md @@ -8,56 +8,57 @@ icon: material/package === ":material-debian: Debian / APT" - ```bash - sudo mkdir -p /etc/apt/keyrings && - sudo curl -fsSL https://sing-box.app/gpg.key -o /etc/apt/keyrings/sagernet.asc && - sudo chmod a+r /etc/apt/keyrings/sagernet.asc && - echo ' - Types: deb - URIs: https://deb.sagernet.org/ - Suites: * - Components: * - Enabled: yes - Signed-By: /etc/apt/keyrings/sagernet.asc - ' | sudo tee /etc/apt/sources.list.d/sagernet.sources && - sudo apt-get update && - sudo apt-get install sing-box # or sing-box-beta - ``` + ```bash + sudo mkdir -p /etc/apt/keyrings && + sudo curl -fsSL https://sing-box.app/gpg.key -o /etc/apt/keyrings/sagernet.asc && + sudo chmod a+r /etc/apt/keyrings/sagernet.asc && + echo ' + Types: deb + URIs: https://deb.sagernet.org/ + Suites: * + Components: * + Enabled: yes + Signed-By: /etc/apt/keyrings/sagernet.asc + ' | sudo tee /etc/apt/sources.list.d/sagernet.sources && + sudo apt-get update && + sudo apt-get install sing-box # or sing-box-beta + ``` === ":material-redhat: Redhat / DNF 5" - ```bash - sudo dnf config-manager addrepo --from-repofile=https://sing-box.app/sing-box.repo && - sudo dnf install sing-box # or sing-box-beta - ``` + ```bash + sudo dnf config-manager addrepo --from-repofile=https://sing-box.app/sing-box.repo && + sudo dnf install sing-box # or sing-box-beta + ``` === ":material-redhat: Redhat / DNF 4" - ```bash - sudo dnf config-manager --add-repo https://sing-box.app/sing-box.repo && - sudo dnf -y install dnf-plugins-core && - sudo dnf install sing-box # or sing-box-beta - ``` + ```bash + sudo dnf config-manager --add-repo https://sing-box.app/sing-box.repo && + sudo dnf -y install dnf-plugins-core && + sudo dnf install sing-box # or sing-box-beta + ``` ## :material-download-box: Manual Installation - The script download and install the latest package from GitHub releases for deb or rpm based Linux distributions, ArchLinux and OpenWrt. - - ```shell - curl -fsSL https://sing-box.app/install.sh | sh - ``` - - or latest beta: - - ```shell - curl -fsSL https://sing-box.app/install.sh | sh -s -- --beta - ``` - - or specific version: - - ```shell - curl -fsSL https://sing-box.app/install.sh | sh -s -- --version - ``` +The script download and install the latest package from GitHub releases +for deb or rpm based Linux distributions, ArchLinux and OpenWrt. + +```shell +curl -fsSL https://sing-box.app/install.sh | sh +``` + +or latest beta: + +```shell +curl -fsSL https://sing-box.app/install.sh | sh -s -- --beta +``` + +or specific version: + +```shell +curl -fsSL https://sing-box.app/install.sh | sh -s -- --version +``` ## :material-book-lock-open: Managed Installation diff --git a/docs/installation/package-manager.zh.md b/docs/installation/package-manager.zh.md index ec1df9a2..f942e924 100644 --- a/docs/installation/package-manager.zh.md +++ b/docs/installation/package-manager.zh.md @@ -26,39 +26,38 @@ icon: material/package === ":material-redhat: Redhat / DNF 5" - ```bash - sudo dnf config-manager addrepo --from-repofile=https://sing-box.app/sing-box.repo && - sudo dnf install sing-box # or sing-box-beta - ``` + ```bash + sudo dnf config-manager addrepo --from-repofile=https://sing-box.app/sing-box.repo && + sudo dnf install sing-box # or sing-box-beta + ``` === ":material-redhat: Redhat / DNF 4" - ```bash - sudo dnf config-manager --add-repo https://sing-box.app/sing-box.repo && - sudo dnf -y install dnf-plugins-core && - sudo dnf install sing-box # or sing-box-beta - ``` + ```bash + sudo dnf config-manager --add-repo https://sing-box.app/sing-box.repo && + sudo dnf -y install dnf-plugins-core && + sudo dnf install sing-box # or sing-box-beta + ``` ## :material-download-box: 手动安装 -=== ":material-debian: Debian / DEB" +该脚本从 GitHub 发布中下载并安装最新的软件包,适用于基于 deb 或 rpm 的 Linux 发行版、ArchLinux 和 OpenWrt。 - ```bash - bash <(curl -fsSL https://sing-box.app/deb-install.sh) - ``` +```shell +curl -fsSL https://sing-box.app/install.sh | sh +``` -=== ":material-redhat: Redhat / RPM" +或最新测试版: - ```bash - bash <(curl -fsSL https://sing-box.app/rpm-install.sh) - ``` - (这适用于任何使用 `rpm` 和 `systemd` 的发行版。由于 `rpm` 定义依赖关系的方式,如果安装成功,就多半能用。) +```shell +curl -fsSL https://sing-box.app/install.sh | sh -s -- --beta +``` -=== ":simple-archlinux: Archlinux / PKG" +或指定版本: - ```bash - bash <(curl -fsSL https://sing-box.app/arch-install.sh) - ``` +```shell +curl -fsSL https://sing-box.app/install.sh | sh -s -- --version +``` ## :material-book-lock-open: 托管安装 diff --git a/docs/installation/tools/install.sh b/docs/installation/tools/install.sh index e042dde8..74166f02 100755 --- a/docs/installation/tools/install.sh +++ b/docs/installation/tools/install.sh @@ -3,76 +3,92 @@ download_beta=false download_version="" -for arg in "$@"; do - if [[ "$arg" == "--beta" ]]; then - download_beta=true - elif [[ "$arg" == "--version" ]]; then - download_version=true - elif [[ "$download_version" == 'true' ]]; then - download_version="$arg" - else - echo "Unknown argument: $arg" - echo "Usage: $0 [--beta] [--version ]" - exit 1 - fi +while [ $# -gt 0 ]; do + case "$1" in + --beta) + download_beta=true + shift + ;; + --version) + shift + if [ $# -eq 0 ]; then + echo "Missing argument for --version" + echo "Usage: $0 [--beta] [--version ]" + exit 1 + fi + download_version="$1" + shift + ;; + *) + echo "Unknown argument: $1" + echo "Usage: $0 [--beta] [--version ]" + exit 1 + ;; + esac done -if [[ $(command -v dpkg) ]]; then - os="linux" - arch=$(dpkg --print-architecture) - package_suffix=".deb" - package_install="dpkg -i" -elif [[ $(command -v dnf) ]]; then - os="linux" - arch=$(uname -m) - package_suffix=".rpm" - package_install="dnf install -y" -elif [[ $(command -v rpm) ]]; then - os="linux" - arch=$(uname -m) - package_suffix=".rpm" - package_install="rpm -i" -elif [[ $(command -v pacman) ]]; then +if command -v pacman >/dev/null 2>&1; then os="linux" arch=$(uname -m) package_suffix=".pkg.tar.zst" package_install="pacman -U --noconfirm" -elif [[ $(command -v opkg) ]]; then +elif command -v dpkg >/dev/null 2>&1; then + os="linux" + arch=$(dpkg --print-architecture) + package_suffix=".deb" + package_install="dpkg -i" +elif command -v dnf >/dev/null 2>&1; then + os="linux" + arch=$(uname -m) + package_suffix=".rpm" + package_install="dnf install -y" +elif command -v rpm >/dev/null 2>&1; then + os="linux" + arch=$(uname -m) + package_suffix=".rpm" + package_install="rpm -i" +elif command -v opkg >/dev/null 2>&1; then os="openwrt" - source /etc/os-release + . /etc/os-release arch="$OPENWRT_ARCH" package_suffix=".ipk" - package_install="opkg update && opkg install -y" + package_install="opkg update && opkg install" else echo "Missing supported package manager." exit 1 fi -if [[ -z "$download_version" ]]; then - if [[ "$download_beta" != 'true' ]]; then - if [[ -n "$GITHUB_TOKEN" ]]; then - latest_release=$(curl -s --fail-with-body -H "Authorization: token ${GITHUB_TOKEN}" https://api.github.com/repos/SagerNet/sing-box/releases/latest) +if [ -z "$download_version" ]; then + if [ "$download_beta" != "true" ]; then + if [ -n "$GITHUB_TOKEN" ]; then + latest_release=$(curl -s -H "Authorization: token ${GITHUB_TOKEN}" https://api.github.com/repos/SagerNet/sing-box/releases/latest) else - latest_release=$(curl -s --fail-with-body https://api.github.com/repos/SagerNet/sing-box/releases/latest) + latest_release=$(curl -s https://api.github.com/repos/SagerNet/sing-box/releases/latest) fi curl_exit_status=$? - if [[ $curl_exit_status -ne 0 ]]; then - echo "$latest_release" - exit $? + if [ $curl_exit_status -ne 0 ]; then + exit $curl_exit_status fi - download_version=$(echo "$latest_release" | grep tag_name | cut -d ":" -f2 | sed 's/\"//g;s/\,//g;s/\ //g;s/v//') + if [ "$(echo "$latest_release" | grep tag_name | wc -l)" -eq 0 ]; then + echo "$latest_release" + exit 1 + fi + download_version=$(echo "$latest_release" | grep tag_name | head -n 1 | awk -F: '{print $2}' | sed 's/[", v]//g') else - if [[ -n "$GITHUB_TOKEN" ]]; then - latest_release=$(curl -s --fail-with-body -H "Authorization: token ${GITHUB_TOKEN}" https://api.github.com/repos/SagerNet/sing-box/releases) + if [ -n "$GITHUB_TOKEN" ]; then + latest_release=$(curl -s -H "Authorization: token ${GITHUB_TOKEN}" https://api.github.com/repos/SagerNet/sing-box/releases) else - latest_release=$(curl -s --fail-with-body https://api.github.com/repos/SagerNet/sing-box/releases) + latest_release=$(curl -s https://api.github.com/repos/SagerNet/sing-box/releases) fi curl_exit_status=$? - if [[ $? -ne 0 ]]; then - echo "$latest_release" - exit $? + if [ $curl_exit_status -ne 0 ]; then + exit $curl_exit_status fi - download_version=$(echo "$latest_release" | grep tag_name | head -n 1 | cut -d ":" -f2 | sed 's/\"//g;s/\,//g;s/\ //g;s/v//') + if [ "$(echo "$latest_release" | grep tag_name | wc -l)" -eq 0 ]; then + echo "$latest_release" + exit 1 + fi + download_version=$(echo "$latest_release" | grep tag_name | head -n 1 | awk -F: '{print $2}' | sed 's/[", v]//g') fi fi @@ -80,18 +96,21 @@ package_name="sing-box_${download_version}_${os}_${arch}${package_suffix}" package_url="https://github.com/SagerNet/sing-box/releases/download/v${download_version}/${package_name}" echo "Downloading $package_url" -if [[ -n "$GITHUB_TOKEN" ]]; then - curl --fail-with-body -Lo "$package_name" -H "Authorization: token ${GITHUB_TOKEN}" "$package_url" +if [ -n "$GITHUB_TOKEN" ]; then + curl --fail -Lo "$package_name" -H "Authorization: token ${GITHUB_TOKEN}" "$package_url" else - curl --fail-with-body -Lo "$package_name" "$package_url" + curl --fail -Lo "$package_name" "$package_url" fi -if [[ $? -ne 0 ]]; then - exit $? +curl_exit_status=$? +if [ $curl_exit_status -ne 0 ]; then + exit $curl_exit_status fi -if [[ $(command -v sudo) ]]; then +if command -v sudo >/dev/null 2>&1; then package_install="sudo $package_install" fi -echo "$package_install $package_name" && $package_install "$package_name" && rm "$package_name" +echo "$package_install $package_name" +sh -c "$package_install \"$package_name\"" +rm -f "$package_name" diff --git a/experimental/clashapi/api_meta.go b/experimental/clashapi/api_meta.go index 29add8ae..77dad797 100644 --- a/experimental/clashapi/api_meta.go +++ b/experimental/clashapi/api_meta.go @@ -4,6 +4,7 @@ import ( "bytes" "net" "net/http" + "runtime/debug" "time" "github.com/sagernet/sing-box/experimental/clashapi/trafficontrol" @@ -12,14 +13,23 @@ import ( "github.com/sagernet/ws/wsutil" "github.com/go-chi/chi/v5" + "github.com/go-chi/chi/v5/middleware" "github.com/go-chi/render" ) // API created by Clash.Meta func (s *Server) setupMetaAPI(r chi.Router) { + if s.logDebug { + r := chi.NewRouter() + r.Put("/gc", func(w http.ResponseWriter, r *http.Request) { + debug.FreeOSMemory() + }) + r.Mount("/", middleware.Profiler()) + } r.Get("/memory", memory(s.trafficManager)) r.Mount("/group", groupRouter(s)) + r.Mount("/upgrade", upgradeRouter(s)) } type Memory struct { diff --git a/experimental/clashapi/api_meta_upgrade.go b/experimental/clashapi/api_meta_upgrade.go new file mode 100644 index 00000000..df70088e --- /dev/null +++ b/experimental/clashapi/api_meta_upgrade.go @@ -0,0 +1,36 @@ +package clashapi + +import ( + "net/http" + + E "github.com/sagernet/sing/common/exceptions" + + "github.com/go-chi/chi/v5" + "github.com/go-chi/render" +) + +func upgradeRouter(server *Server) http.Handler { + r := chi.NewRouter() + r.Post("/ui", updateExternalUI(server)) + return r +} + +func updateExternalUI(server *Server) func(w http.ResponseWriter, r *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + if server.externalUI == "" { + render.Status(r, http.StatusNotFound) + render.JSON(w, r, newError("external UI not enabled")) + return + } + server.logger.Info("upgrading external UI") + err := server.downloadExternalUI() + if err != nil { + server.logger.Error(E.Cause(err, "upgrade external ui")) + render.Status(r, http.StatusInternalServerError) + render.JSON(w, r, newError(err.Error())) + return + } + server.logger.Info("updated external UI") + render.JSON(w, r, render.M{"status": "ok"}) + } +} diff --git a/experimental/clashapi/server.go b/experimental/clashapi/server.go index e6d8c4cf..3a2d4827 100644 --- a/experimental/clashapi/server.go +++ b/experimental/clashapi/server.go @@ -49,6 +49,8 @@ type Server struct { httpServer *http.Server trafficManager *trafficontrol.Manager urlTestHistory adapter.URLTestHistoryStorage + logDebug bool + mode string modeList []string modeUpdateHook chan<- struct{} @@ -74,6 +76,7 @@ func NewServer(ctx context.Context, logFactory log.ObservableFactory, options op Handler: chiRouter, }, trafficManager: trafficManager, + logDebug: logFactory.Level() >= log.LevelDebug, modeList: options.ModeList, externalController: options.ExternalController != "", externalUIDownloadURL: options.ExternalUIDownloadURL, diff --git a/go.mod b/go.mod index f8037164..d4dd9fba 100644 --- a/go.mod +++ b/go.mod @@ -26,18 +26,18 @@ require ( github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb github.com/sagernet/quic-go v0.49.0-beta.1 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 - github.com/sagernet/sing v0.6.7-0.20250409030945-77e2a1bb577c + github.com/sagernet/sing v0.6.8-0.20250425035333-84184da91a3a github.com/sagernet/sing-mux v0.3.1 - github.com/sagernet/sing-quic v0.4.1 + github.com/sagernet/sing-quic v0.4.1-0.20250423030647-0eb05f373a76 github.com/sagernet/sing-shadowsocks v0.2.7 github.com/sagernet/sing-shadowsocks2 v0.2.0 github.com/sagernet/sing-shadowtls v0.2.1-0.20250316154757-6f9e732e5056 github.com/sagernet/sing-tun v0.6.5-0.20250412112220-15069fc1c20a - github.com/sagernet/sing-vmess v0.2.0 + github.com/sagernet/sing-vmess v0.2.1 github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 - github.com/sagernet/tailscale v1.80.3-mod.2.0.20250422104142-2421001a70ff + github.com/sagernet/tailscale v1.80.3-mod.4 github.com/sagernet/utls v1.6.7 - github.com/sagernet/wireguard-go v0.0.1-beta.5 + github.com/sagernet/wireguard-go v0.0.1-beta.7 github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 github.com/spf13/cobra v1.8.1 github.com/stretchr/testify v1.10.0 diff --git a/go.sum b/go.sum index 96d89f8a..c67128b8 100644 --- a/go.sum +++ b/go.sum @@ -178,12 +178,12 @@ github.com/sagernet/quic-go v0.49.0-beta.1/go.mod h1:uesWD1Ihrldq1M3XtjuEvIUqi8W github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc= github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU= github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo= -github.com/sagernet/sing v0.6.7-0.20250409030945-77e2a1bb577c h1:Zi+WR7f9SQ96yNHmyxj42BtaVb3kTouQ8bQLBHReTSI= -github.com/sagernet/sing v0.6.7-0.20250409030945-77e2a1bb577c/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/sing v0.6.8-0.20250425035333-84184da91a3a h1:oE67hmp5rzLlE6clE7FpK4Hg6yLXsa1Zu3A01vcazb0= +github.com/sagernet/sing v0.6.8-0.20250425035333-84184da91a3a/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-mux v0.3.1 h1:kvCc8HyGAskDHDQ0yQvoTi/7J4cZPB/VJMsAM3MmdQI= github.com/sagernet/sing-mux v0.3.1/go.mod h1:Mkdz8LnDstthz0HWuA/5foncnDIdcNN5KZ6AdJX+x78= -github.com/sagernet/sing-quic v0.4.1 h1:pxlMa4efZu/M07RgGagNNDDyl6ZUwpmNUjRTpgHOWK4= -github.com/sagernet/sing-quic v0.4.1/go.mod h1:tqPa0/Wqa19MkkSlKVZZX5sHxtiDR9BROcn4ufcbVdY= +github.com/sagernet/sing-quic v0.4.1-0.20250423030647-0eb05f373a76 h1:iwpCX6H3nZEOGUGwx0q5azcgYOA9f6v9YssihXoRKHk= +github.com/sagernet/sing-quic v0.4.1-0.20250423030647-0eb05f373a76/go.mod h1:tqPa0/Wqa19MkkSlKVZZX5sHxtiDR9BROcn4ufcbVdY= github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8= github.com/sagernet/sing-shadowsocks v0.2.7/go.mod h1:0rIKJZBR65Qi0zwdKezt4s57y/Tl1ofkaq6NlkzVuyE= github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wKFHi+8XwgADg= @@ -192,16 +192,16 @@ github.com/sagernet/sing-shadowtls v0.2.1-0.20250316154757-6f9e732e5056 h1:GFNJQ github.com/sagernet/sing-shadowtls v0.2.1-0.20250316154757-6f9e732e5056/go.mod h1:HyacBPIFiKihJQR8LQp56FM4hBtd/7MZXnRxxQIOPsc= github.com/sagernet/sing-tun v0.6.5-0.20250412112220-15069fc1c20a h1:2aLxZFD2HPCLrnFGpH+KBuPqMOk0cuaDE2dgEvANuMk= github.com/sagernet/sing-tun v0.6.5-0.20250412112220-15069fc1c20a/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE= -github.com/sagernet/sing-vmess v0.2.0 h1:pCMGUXN2k7RpikQV65/rtXtDHzb190foTfF9IGTMZrI= -github.com/sagernet/sing-vmess v0.2.0/go.mod h1:jDAZ0A0St1zVRkyvhAPRySOFfhC+4SQtO5VYyeFotgA= +github.com/sagernet/sing-vmess v0.2.1 h1:6izHC2+B68aQCxTagki6eZZc+g5eh4dYwxOV5a2Lhug= +github.com/sagernet/sing-vmess v0.2.1/go.mod h1:jDAZ0A0St1zVRkyvhAPRySOFfhC+4SQtO5VYyeFotgA= github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ= github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7/go.mod h1:FP9X2xjT/Az1EsG/orYYoC+5MojWnuI7hrffz8fGwwo= -github.com/sagernet/tailscale v1.80.3-mod.2.0.20250422104142-2421001a70ff h1:PK4zmYqcMpW1a416jKR/y7XTqeyANTcbvux8cAA0Vw8= -github.com/sagernet/tailscale v1.80.3-mod.2.0.20250422104142-2421001a70ff/go.mod h1:EBxXsWu4OH2ELbQLq32WoBeIubG8KgDrg4/Oaxjs6lI= +github.com/sagernet/tailscale v1.80.3-mod.4 h1:9UgYq8m9mwX5dbTbueVxbRh+bq7AayxemJGM2PkJQnE= +github.com/sagernet/tailscale v1.80.3-mod.4/go.mod h1:EBxXsWu4OH2ELbQLq32WoBeIubG8KgDrg4/Oaxjs6lI= github.com/sagernet/utls v1.6.7 h1:Ep3+aJ8FUGGta+II2IEVNUc3EDhaRCZINWkj/LloIA8= github.com/sagernet/utls v1.6.7/go.mod h1:Uua1TKO/FFuAhLr9rkaVnnrTmmiItzDjv1BUb2+ERwM= -github.com/sagernet/wireguard-go v0.0.1-beta.5 h1:aBEsxJUMEONwOZqKPIkuAcv4zJV5p6XlzEN04CF0FXc= -github.com/sagernet/wireguard-go v0.0.1-beta.5/go.mod h1:jGXij2Gn2wbrWuYNUmmNhf1dwcZtvyAvQoe8Xd8MbUo= +github.com/sagernet/wireguard-go v0.0.1-beta.7 h1:ltgBwYHfr+9Wz1eG59NiWnHrYEkDKHG7otNZvu85DXI= +github.com/sagernet/wireguard-go v0.0.1-beta.7/go.mod h1:jGXij2Gn2wbrWuYNUmmNhf1dwcZtvyAvQoe8Xd8MbUo= github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc= github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854/go.mod h1:LtfoSK3+NG57tvnVEHgcuBW9ujgE8enPSgzgwStwCAA= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= diff --git a/option/outbound.go b/option/outbound.go index 1b852d26..e4475ee0 100644 --- a/option/outbound.go +++ b/option/outbound.go @@ -83,7 +83,6 @@ type DialerOptions struct { NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"` FallbackNetworkType badoption.Listable[InterfaceType] `json:"fallback_network_type,omitempty"` FallbackDelay badoption.Duration `json:"fallback_delay,omitempty"` - IsWireGuardListener bool `json:"-"` // Deprecated: migrated to domain resolver DomainStrategy DomainStrategy `json:"domain_strategy,omitempty"` diff --git a/protocol/ssh/outbound.go b/protocol/ssh/outbound.go index 304ea389..aeb43d34 100644 --- a/protocol/ssh/outbound.go +++ b/protocol/ssh/outbound.go @@ -10,6 +10,7 @@ import ( "strconv" "strings" "sync" + "time" "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter/outbound" @@ -191,9 +192,29 @@ func (s *Outbound) DialContext(ctx context.Context, network string, destination if err != nil { return nil, err } - return client.Dial(network, destination.String()) + conn, err := client.Dial(network, destination.String()) + if err != nil { + return nil, err + } + return &chanConnWrapper{Conn: conn}, nil } func (s *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { return nil, os.ErrInvalid } + +type chanConnWrapper struct { + net.Conn +} + +func (c *chanConnWrapper) SetDeadline(t time.Time) error { + return os.ErrInvalid +} + +func (c *chanConnWrapper) SetReadDeadline(t time.Time) error { + return os.ErrInvalid +} + +func (c *chanConnWrapper) SetWriteDeadline(t time.Time) error { + return os.ErrInvalid +} diff --git a/protocol/wireguard/endpoint.go b/protocol/wireguard/endpoint.go index 29acbf12..4165d126 100644 --- a/protocol/wireguard/endpoint.go +++ b/protocol/wireguard/endpoint.go @@ -45,8 +45,8 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL logger: logger, localAddresses: options.Address, } - if options.Detour == "" { - options.IsWireGuardListener = true + if options.Detour != "" && options.ListenPort != 0 { + return nil, E.New("`listen_port` is conflict with `detour`") } outboundDialer, err := dialer.NewWithOptions(dialer.Options{ Context: ctx, diff --git a/protocol/wireguard/outbound.go b/protocol/wireguard/outbound.go index 1e7a32c7..129e69b8 100644 --- a/protocol/wireguard/outbound.go +++ b/protocol/wireguard/outbound.go @@ -46,9 +46,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL logger: logger, localAddresses: options.LocalAddress, } - if options.Detour == "" { - options.IsWireGuardListener = true - } else if options.GSO { + if options.Detour != "" && options.GSO { return nil, E.New("gso is conflict with detour") } outboundDialer, err := dialer.NewWithOptions(dialer.Options{ diff --git a/route/conn.go b/route/conn.go index 582562eb..f46283ad 100644 --- a/route/conn.go +++ b/route/conn.go @@ -7,6 +7,7 @@ import ( "net" "net/netip" "os" + "strings" "sync" "sync/atomic" "time" @@ -66,7 +67,17 @@ func (m *ConnectionManager) NewConnection(ctx context.Context, this N.Dialer, co remoteConn, err = this.DialContext(ctx, N.NetworkTCP, metadata.Destination) } if err != nil { - err = E.Cause(err, "open outbound connection") + var remoteString string + if len(metadata.DestinationAddresses) > 0 { + remoteString = "[" + strings.Join(common.Map(metadata.DestinationAddresses, netip.Addr.String), ",") + "]" + } else { + remoteString = metadata.Destination.String() + } + var dialerString string + if outbound, isOutbound := this.(adapter.Outbound); isOutbound { + dialerString = " using outbound/" + outbound.Type() + "[" + outbound.Tag() + "]" + } + err = E.Cause(err, "open connection to ", remoteString, dialerString) N.CloseOnHandshakeFailure(conn, onClose, err) m.logger.ErrorContext(ctx, err) return @@ -133,8 +144,19 @@ func (m *ConnectionManager) NewPacketConnection(ctx context.Context, this N.Dial remoteConn, err = this.DialContext(ctx, N.NetworkUDP, metadata.Destination) } if err != nil { + var remoteString string + if len(metadata.DestinationAddresses) > 0 { + remoteString = "[" + strings.Join(common.Map(metadata.DestinationAddresses, netip.Addr.String), ",") + "]" + } else { + remoteString = metadata.Destination.String() + } + var dialerString string + if outbound, isOutbound := this.(adapter.Outbound); isOutbound { + dialerString = " using outbound/" + outbound.Type() + "[" + outbound.Tag() + "]" + } + err = E.Cause(err, "open packet connection to ", remoteString, dialerString) N.CloseOnHandshakeFailure(conn, onClose, err) - m.logger.ErrorContext(ctx, "open outbound packet connection: ", err) + m.logger.ErrorContext(ctx, err) return } remotePacketConn = bufio.NewUnbindPacketConn(remoteConn) @@ -149,8 +171,13 @@ func (m *ConnectionManager) NewPacketConnection(ctx context.Context, this N.Dial remotePacketConn, err = this.ListenPacket(ctx, metadata.Destination) } if err != nil { + var dialerString string + if outbound, isOutbound := this.(adapter.Outbound); isOutbound { + dialerString = " using outbound/" + outbound.Type() + "[" + outbound.Tag() + "]" + } + err = E.Cause(err, "listen packet connection using ", dialerString) N.CloseOnHandshakeFailure(conn, onClose, err) - m.logger.ErrorContext(ctx, "listen outbound packet connection: ", err) + m.logger.ErrorContext(ctx, err) return } } diff --git a/route/route.go b/route/route.go index 50c38201..38f07412 100644 --- a/route/route.go +++ b/route/route.go @@ -418,6 +418,7 @@ match: Port: metadata.Destination.Port, Fqdn: routeOptions.OverrideAddress.Fqdn, } + metadata.DestinationAddresses = nil } if routeOptions.OverridePort > 0 { metadata.Destination = M.Socksaddr{ diff --git a/route/rule/rule_default.go b/route/rule/rule_default.go index c5f4a2dc..f19eee58 100644 --- a/route/rule/rule_default.go +++ b/route/rule/rule_default.go @@ -102,7 +102,10 @@ func NewDefaultRule(ctx context.Context, logger log.ContextLogger, options optio rule.allItems = append(rule.allItems, item) } if len(options.Domain) > 0 || len(options.DomainSuffix) > 0 { - item := NewDomainItem(options.Domain, options.DomainSuffix) + item, err := NewDomainItem(options.Domain, options.DomainSuffix) + if err != nil { + return nil, err + } rule.destinationAddressItems = append(rule.destinationAddressItems, item) rule.allItems = append(rule.allItems, item) } diff --git a/route/rule/rule_dns.go b/route/rule/rule_dns.go index 8a4f1705..596b98f4 100644 --- a/route/rule/rule_dns.go +++ b/route/rule/rule_dns.go @@ -93,7 +93,10 @@ func NewDefaultDNSRule(ctx context.Context, logger log.ContextLogger, options op rule.allItems = append(rule.allItems, item) } if len(options.Domain) > 0 || len(options.DomainSuffix) > 0 { - item := NewDomainItem(options.Domain, options.DomainSuffix) + item, err := NewDomainItem(options.Domain, options.DomainSuffix) + if err != nil { + return nil, err + } rule.destinationAddressItems = append(rule.destinationAddressItems, item) rule.allItems = append(rule.allItems, item) } diff --git a/route/rule/rule_headless.go b/route/rule/rule_headless.go index 5f9973e3..869fb64a 100644 --- a/route/rule/rule_headless.go +++ b/route/rule/rule_headless.go @@ -47,7 +47,10 @@ func NewDefaultHeadlessRule(ctx context.Context, options option.DefaultHeadlessR rule.allItems = append(rule.allItems, item) } if len(options.Domain) > 0 || len(options.DomainSuffix) > 0 { - item := NewDomainItem(options.Domain, options.DomainSuffix) + item, err := NewDomainItem(options.Domain, options.DomainSuffix) + if err != nil { + return nil, err + } rule.destinationAddressItems = append(rule.destinationAddressItems, item) rule.allItems = append(rule.allItems, item) } else if options.DomainMatcher != nil { diff --git a/route/rule/rule_item_domain.go b/route/rule/rule_item_domain.go index b7655a79..af790aa3 100644 --- a/route/rule/rule_item_domain.go +++ b/route/rule/rule_item_domain.go @@ -5,6 +5,7 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing/common/domain" + E "github.com/sagernet/sing/common/exceptions" ) var _ RuleItem = (*DomainItem)(nil) @@ -14,7 +15,17 @@ type DomainItem struct { description string } -func NewDomainItem(domains []string, domainSuffixes []string) *DomainItem { +func NewDomainItem(domains []string, domainSuffixes []string) (*DomainItem, error) { + for _, domainItem := range domains { + if domainItem == "" { + return nil, E.New("domain: empty item is not allowed") + } + } + for _, domainSuffixItem := range domainSuffixes { + if domainSuffixItem == "" { + return nil, E.New("domain_suffix: empty item is not allowed") + } + } var description string if dLen := len(domains); dLen > 0 { if dLen == 1 { @@ -40,7 +51,7 @@ func NewDomainItem(domains []string, domainSuffixes []string) *DomainItem { return &DomainItem{ domain.NewMatcher(domains, domainSuffixes, false), description, - } + }, nil } func NewRawDomainItem(matcher *domain.Matcher) *DomainItem { diff --git a/transport/v2raywebsocket/client.go b/transport/v2raywebsocket/client.go index 9c495076..748bae4c 100644 --- a/transport/v2raywebsocket/client.go +++ b/transport/v2raywebsocket/client.go @@ -91,10 +91,7 @@ func (c *Client) dialContext(ctx context.Context, requestURL *url.URL, headers h } else { deadlineConn = conn } - err = deadlineConn.SetDeadline(time.Now().Add(C.TCPTimeout)) - if err != nil { - return nil, E.Cause(err, "set read deadline") - } + deadlineConn.SetDeadline(time.Now().Add(C.TCPTimeout)) var protocols []string if protocolHeader := headers.Get("Sec-WebSocket-Protocol"); protocolHeader != "" { protocols = []string{protocolHeader} diff --git a/transport/wireguard/endpoint.go b/transport/wireguard/endpoint.go index bddd2a12..3801640f 100644 --- a/transport/wireguard/endpoint.go +++ b/transport/wireguard/endpoint.go @@ -141,7 +141,7 @@ func (e *Endpoint) Start(resolve bool) error { return nil } var bind conn.Bind - wgListener, isWgListener := e.options.Dialer.(conn.Listener) + wgListener, isWgListener := common.Cast[conn.Listener](e.options.Dialer) if isWgListener { bind = conn.NewStdNetBind(wgListener) } else {