mirror of
https://github.com/SagerNet/sing-box.git
synced 2025-08-25 03:27:35 +08:00
Compare commits
89 Commits
230ac25f2c
...
139a48780f
Author | SHA1 | Date | |
---|---|---|---|
![]() |
139a48780f | ||
![]() |
bc63a6028d | ||
![]() |
284f10b296 | ||
![]() |
7a109569f9 | ||
![]() |
ea342f257a | ||
![]() |
8814994368 | ||
![]() |
20d338a06b | ||
![]() |
7527134ef2 | ||
![]() |
43230a89ef | ||
![]() |
967d84825f | ||
![]() |
c38000d94f | ||
![]() |
ff8e11dfe3 | ||
![]() |
eaaa70f118 | ||
![]() |
13870fb4b5 | ||
![]() |
961d586e47 | ||
![]() |
4f4b7def78 | ||
![]() |
4acb3065b8 | ||
![]() |
0ab2ef8c44 | ||
![]() |
56c144fc2b | ||
![]() |
7f0fec0bb3 | ||
![]() |
448d534d25 | ||
![]() |
f9f60bb9ea | ||
![]() |
9554908971 | ||
![]() |
74be568a93 | ||
![]() |
b37f4b68d5 | ||
![]() |
bbe6ded13c | ||
![]() |
9111cfa5d7 | ||
![]() |
546218c0f8 | ||
![]() |
680f8dd008 | ||
![]() |
13c251287b | ||
![]() |
8051bb4f3b | ||
![]() |
24c7b040c5 | ||
![]() |
ac74bbf5dd | ||
![]() |
2d72940096 | ||
![]() |
b2d6708f96 | ||
![]() |
5108103f3f | ||
![]() |
e875eebf9c | ||
![]() |
445eae88ab | ||
![]() |
3f4f804e3e | ||
![]() |
1347d92ebe | ||
![]() |
2819ec16f3 | ||
![]() |
aa5e7822a4 | ||
![]() |
fa7d03542d | ||
![]() |
dbbab063fe | ||
![]() |
ded7d45dee | ||
![]() |
96bf78ddc1 | ||
![]() |
5af81fa969 | ||
![]() |
a4ff6f2075 | ||
![]() |
3ea9468107 | ||
![]() |
a7c7703fd3 | ||
![]() |
db33a68eb2 | ||
![]() |
c3e4bf6a4c | ||
![]() |
3e455902c9 | ||
![]() |
aaba387a8a | ||
![]() |
4d1be802cb | ||
![]() |
83ccc72e2b | ||
![]() |
f9e850867a | ||
![]() |
342c2361f1 | ||
![]() |
ac4e30b190 | ||
![]() |
cf008519fd | ||
![]() |
91d9fc63b7 | ||
![]() |
7d054da0b1 | ||
![]() |
40d35829e1 | ||
![]() |
41ad180be1 | ||
![]() |
9cb07a2387 | ||
![]() |
086784baf3 | ||
![]() |
0e8cd7d3be | ||
![]() |
58f712d222 | ||
![]() |
dfb7569d11 | ||
![]() |
638179fd7c | ||
![]() |
69fff5591f | ||
![]() |
7c3c3ddbeb | ||
![]() |
d88e87b9a4 | ||
![]() |
1f529be0f1 | ||
![]() |
f87808f8c3 | ||
![]() |
11e3634a63 | ||
![]() |
c46f70a4c8 | ||
![]() |
4ed2e1ee1b | ||
![]() |
aa8a85b4bc | ||
![]() |
6103ecb771 | ||
![]() |
3dbe90c535 | ||
![]() |
0eac73a8fd | ||
![]() |
f9cb268ee7 | ||
![]() |
d7085167e7 | ||
![]() |
c80d62968c | ||
![]() |
e6d19de58a | ||
![]() |
f2bbf6b2aa | ||
![]() |
c54d50fd36 | ||
![]() |
6a051054db |
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
@ -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 \
|
||||
|
2
.github/workflows/linux.yml
vendored
2
.github/workflows/linux.yml
vendored
@ -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" \
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -10,6 +10,8 @@ import (
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/dns"
|
||||
@ -46,7 +48,10 @@ func parseECHClientConfig(ctx context.Context, options option.OutboundTLSOptions
|
||||
tlsConfig.EncryptedClientHelloConfigList = block.Bytes
|
||||
return &STDClientConfig{tlsConfig}, nil
|
||||
} else {
|
||||
return &STDECHClientConfig{STDClientConfig{tlsConfig}, service.FromContext[adapter.DNSRouter](ctx)}, nil
|
||||
return &STDECHClientConfig{
|
||||
STDClientConfig: STDClientConfig{tlsConfig},
|
||||
dnsRouter: service.FromContext[adapter.DNSRouter](ctx),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -99,11 +104,28 @@ func reloadECHKeys(echKeyPath string, tlsConfig *tls.Config) error {
|
||||
|
||||
type STDECHClientConfig struct {
|
||||
STDClientConfig
|
||||
dnsRouter adapter.DNSRouter
|
||||
access sync.Mutex
|
||||
dnsRouter adapter.DNSRouter
|
||||
lastTTL time.Duration
|
||||
lastUpdate time.Time
|
||||
}
|
||||
|
||||
func (s *STDECHClientConfig) ClientHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) {
|
||||
if len(s.config.EncryptedClientHelloConfigList) == 0 {
|
||||
tlsConn, err := s.fetchAndHandshake(ctx, conn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = tlsConn.HandshakeContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tlsConn, nil
|
||||
}
|
||||
|
||||
func (s *STDECHClientConfig) fetchAndHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) {
|
||||
s.access.Lock()
|
||||
defer s.access.Unlock()
|
||||
if len(s.config.EncryptedClientHelloConfigList) == 0 || s.lastTTL == 0 || time.Now().Sub(s.lastUpdate) > s.lastTTL {
|
||||
message := &mDNS.Msg{
|
||||
MsgHdr: mDNS.MsgHdr{
|
||||
RecursionDesired: true,
|
||||
@ -133,6 +155,8 @@ func (s *STDECHClientConfig) ClientHandshake(ctx context.Context, conn net.Conn)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "decode ECH config")
|
||||
}
|
||||
s.lastTTL = time.Duration(rr.Header().Ttl) * time.Second
|
||||
s.lastUpdate = time.Now()
|
||||
s.config.EncryptedClientHelloConfigList = echConfigList
|
||||
break match
|
||||
}
|
||||
@ -143,19 +167,11 @@ func (s *STDECHClientConfig) ClientHandshake(ctx context.Context, conn net.Conn)
|
||||
return nil, E.New("no ECH config found in DNS records")
|
||||
}
|
||||
}
|
||||
tlsConn, err := s.Client(conn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = tlsConn.HandshakeContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tlsConn, nil
|
||||
return s.Client(conn)
|
||||
}
|
||||
|
||||
func (s *STDECHClientConfig) Clone() Config {
|
||||
return &STDECHClientConfig{STDClientConfig{s.config.Clone()}, s.dnsRouter}
|
||||
return &STDECHClientConfig{STDClientConfig: STDClientConfig{s.config.Clone()}, dnsRouter: s.dnsRouter, lastUpdate: s.lastUpdate}
|
||||
}
|
||||
|
||||
func UnmarshalECHKeys(raw []byte) ([]tls.EncryptedClientHelloKey, error) {
|
||||
|
@ -268,10 +268,13 @@ func (c *Client) Lookup(ctx context.Context, transport adapter.DNSTransport, dom
|
||||
return nil
|
||||
})
|
||||
err := group.Run(ctx)
|
||||
if len(response4) == 0 && len(response6) == 0 {
|
||||
if len(response4) > 0 || len(response6) > 0 {
|
||||
return sortAddresses(response4, response6, options.Strategy), nil
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return nil, RcodeError(dns.RcodeNameError)
|
||||
}
|
||||
return sortAddresses(response4, response6, options.Strategy), nil
|
||||
}
|
||||
|
||||
func (c *Client) ClearCache() {
|
||||
@ -483,7 +486,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))
|
||||
|
@ -332,8 +332,7 @@ func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQ
|
||||
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
|
||||
panic("unexpected empty result")
|
||||
}
|
||||
}
|
||||
responseAddrs, cached = r.client.LookupCache(domain, options.Strategy)
|
||||
|
@ -2,7 +2,7 @@
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
#### 1.12.0-beta.4
|
||||
#### 1.12.0-beta.5
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
|
@ -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 <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 <version>
|
||||
```
|
||||
|
||||
## :material-book-lock-open: Managed Installation
|
||||
|
||||
|
@ -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 <version>
|
||||
```
|
||||
|
||||
## :material-book-lock-open: 托管安装
|
||||
|
||||
|
@ -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 <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 <version>]"
|
||||
exit 1
|
||||
fi
|
||||
download_version="$1"
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
echo "Unknown argument: $1"
|
||||
echo "Usage: $0 [--beta] [--version <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"
|
||||
|
@ -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 {
|
||||
|
36
experimental/clashapi/api_meta_upgrade.go
Normal file
36
experimental/clashapi/api_meta_upgrade.go
Normal file
@ -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"})
|
||||
}
|
||||
}
|
@ -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,
|
||||
|
10
go.mod
10
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
|
||||
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
|
||||
|
20
go.sum
20
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 h1:hT0CI74q727EuCcgQ+T4pvon8V0aoi4vTAxah7GsNMQ=
|
||||
github.com/sagernet/tailscale v1.80.3-mod.2/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=
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -418,6 +418,7 @@ match:
|
||||
Port: metadata.Destination.Port,
|
||||
Fqdn: routeOptions.OverrideAddress.Fqdn,
|
||||
}
|
||||
metadata.DestinationAddresses = nil
|
||||
}
|
||||
if routeOptions.OverridePort > 0 {
|
||||
metadata.Destination = M.Socksaddr{
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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}
|
||||
|
Loading…
x
Reference in New Issue
Block a user