mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-12 01:57:18 +10:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e3f8567690 | ||
|
|
40c7f3e170 | ||
|
|
c506255e0f | ||
|
|
87c6fd4c0f | ||
|
|
19c445d28e | ||
|
|
9119a5209b | ||
|
|
46c8d6e61f | ||
|
|
ea17c2786d | ||
|
|
12ababd911 | ||
|
|
0523845833 | ||
|
|
57794919fa | ||
|
|
f5bb5cf343 | ||
|
|
3eed614dea | ||
|
|
76a295a660 | ||
|
|
082e3fb8df | ||
|
|
a0cab4f563 |
19
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
19
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -61,7 +61,22 @@ body:
|
||||
attributes:
|
||||
label: Logs
|
||||
description: |-
|
||||
If you encounter a crash with the graphical client, please provide crash logs.
|
||||
In addition, if you encounter a crash with the graphical client, please also provide crash logs.
|
||||
For Apple platform clients, please check `Settings - View Service Log` for crash logs.
|
||||
For the Android client, please check the `/sdcard/Android/data/io.nekohasekai.sfa/files/stderr.log` file for crash logs.
|
||||
render: shell
|
||||
render: shell
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Integrity requirements
|
||||
description: |-
|
||||
Please check all of the following options to prove that you have read and understood the requirements, otherwise this issue will be closed.
|
||||
Sing-box is not a project aimed to please users who can't make any meaningful contributions and gain unethical influence. If you deceive here to deliberately waste the time of the developers, you will be permanently blocked.
|
||||
options:
|
||||
- label: I confirm that I have read the documentation, understand the meaning of all the configuration items I wrote, and did not pile up seemingly useful options or default values.
|
||||
required: true
|
||||
- label: I confirm that I have provided the server and client configuration files and process that can be reproduced locally, instead of a complicated client configuration file that has been stripped of sensitive data.
|
||||
required: true
|
||||
- label: I confirm that I have provided the simplest configuration that can be used to reproduce the error I reported, instead of depending on remote servers, TUN, graphical interface clients, or other closed-source software.
|
||||
required: true
|
||||
- label: I confirm that I have provided the complete configuration files and logs, rather than just providing parts I think are useful out of confidence in my own intelligence.
|
||||
required: true
|
||||
21
.github/ISSUE_TEMPLATE/bug_report_zh.yml
vendored
21
.github/ISSUE_TEMPLATE/bug_report_zh.yml
vendored
@@ -61,21 +61,22 @@ body:
|
||||
attributes:
|
||||
label: 日志
|
||||
description: |-
|
||||
如果您遭遇图形界面应用程序崩溃,请提供崩溃日志。
|
||||
此外,如果您遭遇图形界面应用程序崩溃,请附加提供崩溃日志。
|
||||
对于 Apple 平台图形客户端程序,请检查 `Settings - View Service Log` 以导出崩溃日志。
|
||||
对于 Android 图形客户端程序,请检查 `/sdcard/Android/data/io.nekohasekai.sfa/files/stderr.log` 文件以导出崩溃日志。
|
||||
render: shell
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: 完整性要求
|
||||
description: 我保证我提供了完整的可以在本地重现该问题的服务器、客户端配置文件与流程,而不是一个脱敏的复杂客户端配置文件,否则该 issue 将被关闭。
|
||||
description: |-
|
||||
请勾选以下所有选项以证明您已经阅读并理解了以下要求,否则该 issue 将被关闭。
|
||||
sing-box 不是讨好无法作出任何意义上的贡献的最终用户并获取非道德影响力的项目,如果您在此处欺骗以故意浪费开发者的时间,您将被永久封锁。
|
||||
options:
|
||||
- label: 我保证
|
||||
- label: 我保证阅读了文档,了解所有我编写的配置文件项的含义,而不是大量堆砌看似有用的选项或默认值。
|
||||
required: true
|
||||
- label: 我保证提供了可以在本地重现该问题的服务器、客户端配置文件与流程,而不是一个脱敏的复杂客户端配置文件。
|
||||
required: true
|
||||
- label: 我保证提供了可用于重现我报告的错误的最简配置,而不是依赖远程服务器、TUN、图形界面客户端或者其他闭源软件。
|
||||
required: true
|
||||
- label: 我保证提供了完整的配置文件与日志,而不是出于对自身智力的自信而仅提供了部分认为有用的部分。
|
||||
required: true
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: 负责性要求
|
||||
description: 我保证我阅读了文档,了解所有我编写的配置文件项的含义,而不是大量堆砌看似有用的选项或默认值,否则该 issue 将被关闭。
|
||||
options:
|
||||
- label: 我保证
|
||||
required: true
|
||||
2
.github/workflows/debug.yml
vendored
2
.github/workflows/debug.yml
vendored
@@ -216,7 +216,7 @@ jobs:
|
||||
id: build
|
||||
run: make
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sing-box-${{ matrix.name }}
|
||||
path: sing-box*
|
||||
|
||||
@@ -32,3 +32,7 @@ func (r *Reader) Lookup(addr netip.Addr) string {
|
||||
}
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
func (r *Reader) Close() error {
|
||||
return r.reader.Close()
|
||||
}
|
||||
|
||||
@@ -105,5 +105,16 @@ func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Con
|
||||
},
|
||||
})
|
||||
config = certmagic.New(cache, *config)
|
||||
return config.TLSConfig(), &acmeWrapper{ctx: ctx, cfg: config, cache: cache, domain: options.Domain}, nil
|
||||
var tlsConfig *tls.Config
|
||||
if acmeConfig.DisableTLSALPNChallenge || acmeConfig.DNS01Solver != nil {
|
||||
tlsConfig = &tls.Config{
|
||||
GetCertificate: config.GetCertificate,
|
||||
}
|
||||
} else {
|
||||
tlsConfig = &tls.Config{
|
||||
GetCertificate: config.GetCertificate,
|
||||
NextProtos: []string{ACMETLS1Protocol},
|
||||
}
|
||||
}
|
||||
return tlsConfig, &acmeWrapper{ctx: ctx, cfg: config, cache: cache, domain: options.Domain}, nil
|
||||
}
|
||||
|
||||
3
common/tls/acme_contstant.go
Normal file
3
common/tls/acme_contstant.go
Normal file
@@ -0,0 +1,3 @@
|
||||
package tls
|
||||
|
||||
const ACMETLS1Protocol = "acme-tls/1"
|
||||
@@ -39,11 +39,19 @@ func (c *STDServerConfig) SetServerName(serverName string) {
|
||||
}
|
||||
|
||||
func (c *STDServerConfig) NextProtos() []string {
|
||||
return c.config.NextProtos
|
||||
if c.acmeService != nil && len(c.config.NextProtos) > 1 && c.config.NextProtos[0] == ACMETLS1Protocol {
|
||||
return c.config.NextProtos[1:]
|
||||
} else {
|
||||
return c.config.NextProtos
|
||||
}
|
||||
}
|
||||
|
||||
func (c *STDServerConfig) SetNextProtos(nextProto []string) {
|
||||
c.config.NextProtos = nextProto
|
||||
if c.acmeService != nil && len(c.config.NextProtos) > 1 && c.config.NextProtos[0] == ACMETLS1Protocol {
|
||||
c.config.NextProtos = append(c.config.NextProtos[:1], nextProto...)
|
||||
} else {
|
||||
c.config.NextProtos = nextProto
|
||||
}
|
||||
}
|
||||
|
||||
func (c *STDServerConfig) Config() (*STDConfig, error) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"net/http/pprof"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
|
||||
"github.com/sagernet/sing-box/common/badjson"
|
||||
"github.com/sagernet/sing-box/common/humanize"
|
||||
@@ -47,12 +48,20 @@ func applyDebugListenOption(options option.DebugOptions) {
|
||||
encoder.SetIndent("", " ")
|
||||
encoder.Encode(memObject)
|
||||
})
|
||||
r.HandleFunc("/pprof", pprof.Index)
|
||||
r.HandleFunc("/pprof/*", pprof.Index)
|
||||
r.HandleFunc("/pprof/cmdline", pprof.Cmdline)
|
||||
r.HandleFunc("/pprof/profile", pprof.Profile)
|
||||
r.HandleFunc("/pprof/symbol", pprof.Symbol)
|
||||
r.HandleFunc("/pprof/trace", pprof.Trace)
|
||||
r.Route("/pprof", func(r chi.Router) {
|
||||
r.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
|
||||
if !strings.HasSuffix(request.URL.Path, "/") {
|
||||
http.Redirect(writer, request, request.URL.Path+"/", http.StatusMovedPermanently)
|
||||
} else {
|
||||
pprof.Index(writer, request)
|
||||
}
|
||||
})
|
||||
r.HandleFunc("/*", pprof.Index)
|
||||
r.HandleFunc("/cmdline", pprof.Cmdline)
|
||||
r.HandleFunc("/profile", pprof.Profile)
|
||||
r.HandleFunc("/symbol", pprof.Symbol)
|
||||
r.HandleFunc("/trace", pprof.Trace)
|
||||
})
|
||||
})
|
||||
debugHTTPServer = &http.Server{
|
||||
Addr: options.Listen,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build !linux
|
||||
//go:build !(linux || darwin)
|
||||
|
||||
package box
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//go:build linux || darwin
|
||||
|
||||
package box
|
||||
|
||||
import (
|
||||
@@ -4,6 +4,19 @@ icon: material/alert-decagram
|
||||
|
||||
# ChangeLog
|
||||
|
||||
#### 1.7.8
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.7.7
|
||||
|
||||
* Fix V2Ray transport `path` validation behavior **1**
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
See [V2Ray transport](/configuration/shared/v2ray-transport/).
|
||||
|
||||
#### 1.7.6
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
@@ -164,10 +164,9 @@ By default, the maximum version is currently TLS 1.3.
|
||||
|
||||
#### cipher_suites
|
||||
|
||||
The elliptic curves that will be used in an ECDHE handshake, in preference order.
|
||||
A list of enabled TLS 1.0–1.2 cipher suites. The order of the list is ignored. Note that TLS 1.3 cipher suites are not configurable.
|
||||
|
||||
If empty, the default will be used. The client will use the first preference as the type for its key share in TLS 1.3.
|
||||
This may change in the future.
|
||||
If empty, a safe default list is used. The default cipher suites might change over time.
|
||||
|
||||
#### certificate
|
||||
|
||||
|
||||
@@ -162,12 +162,9 @@ TLS 版本值:
|
||||
|
||||
#### cipher_suites
|
||||
|
||||
将在 ECDHE 握手中使用的椭圆曲线,按优先顺序排列。
|
||||
启用的 TLS 1.0-1.2密码套件的列表。列表的顺序被忽略。请注意,TLS 1.3 的密码套件是不可配置的。
|
||||
|
||||
如果为空,将使用默认值。
|
||||
|
||||
客户端将使用第一个首选项作为其在 TLS 1.3 中的密钥共享类型。
|
||||
这在未来可能会改变。
|
||||
如果为空,则使用安全的默认列表。默认密码套件可能会随着时间的推移而改变。
|
||||
|
||||
#### certificate
|
||||
|
||||
|
||||
@@ -53,9 +53,15 @@ The client will choose randomly and the server will verify if not empty.
|
||||
|
||||
#### path
|
||||
|
||||
!!! warning
|
||||
|
||||
V2Ray's documentation says that the path between the server and the client must be consistent,
|
||||
but the actual code allows the client to add any suffix to the path.
|
||||
sing-box uses the same behavior as V2Ray, but note that the behavior does not exist in `WebSocket` and `HTTPUpgrade` transport.
|
||||
|
||||
Path of HTTP request.
|
||||
|
||||
The server will verify if not empty.
|
||||
The server will verify.
|
||||
|
||||
#### method
|
||||
|
||||
@@ -77,7 +83,10 @@ Specifies the time until idle clients should be closed with a GOAWAY frame. PING
|
||||
|
||||
In HTTP2 client:
|
||||
|
||||
Specifies the period of time after which a health check will be performed using a ping frame if no frames have been received on the connection. Please note that a ping response is considered a received frame, so if there is no other traffic on the connection, the health check will be executed every interval. If the value is zero, no health check will be performed.
|
||||
Specifies the period of time after which a health check will be performed using a ping frame if no frames have been
|
||||
received on the connection.Please note that a ping response is considered a received frame, so if there is no other
|
||||
traffic on the connection, the health check will be executed every interval. If the value is zero, no health check will
|
||||
be performed.
|
||||
|
||||
Zero is used by default.
|
||||
|
||||
@@ -85,7 +94,9 @@ Zero is used by default.
|
||||
|
||||
In HTTP2 client:
|
||||
|
||||
Specifies the timeout duration after sending a PING frame, within which a response must be received. If a response to the PING frame is not received within the specified timeout duration, the connection will be closed. The default timeout duration is 15 seconds.
|
||||
Specifies the timeout duration after sending a PING frame, within which a response must be received.
|
||||
If a response to the PING frame is not received within the specified timeout duration, the connection will be closed.
|
||||
The default timeout duration is 15 seconds.
|
||||
|
||||
### WebSocket
|
||||
|
||||
@@ -103,12 +114,14 @@ Specifies the timeout duration after sending a PING frame, within which a respon
|
||||
|
||||
Path of HTTP request.
|
||||
|
||||
The server will verify if not empty.
|
||||
The server will verify.
|
||||
|
||||
#### headers
|
||||
|
||||
Extra headers of HTTP request.
|
||||
|
||||
The server will write in response if not empty.
|
||||
|
||||
#### max_early_data
|
||||
|
||||
Allowed payload size is in the request. Enabled if not zero.
|
||||
@@ -162,7 +175,8 @@ Service name of gRPC.
|
||||
|
||||
In standard gRPC server/client:
|
||||
|
||||
If the transport doesn't see any activity after a duration of this time, it pings the client to check if the connection is still active.
|
||||
If the transport doesn't see any activity after a duration of this time,
|
||||
it pings the client to check if the connection is still active.
|
||||
|
||||
In default gRPC server/client:
|
||||
|
||||
@@ -172,7 +186,8 @@ It has the same behavior as the corresponding setting in HTTP transport.
|
||||
|
||||
In standard gRPC server/client:
|
||||
|
||||
The timeout that after performing a keepalive check, the client will wait for activity. If no activity is detected, the connection will be closed.
|
||||
The timeout that after performing a keepalive check, the client will wait for activity.
|
||||
If no activity is detected, the connection will be closed.
|
||||
|
||||
In default gRPC server/client:
|
||||
|
||||
@@ -182,7 +197,9 @@ It has the same behavior as the corresponding setting in HTTP transport.
|
||||
|
||||
In standard gRPC client:
|
||||
|
||||
If enabled, the client transport sends keepalive pings even with no active connections. If disabled, when there are no active connections, `idle_timeout` and `ping_timeout` will be ignored and no keepalive pings will be sent.
|
||||
If enabled, the client transport sends keepalive pings even with no active connections.
|
||||
If disabled, when there are no active connections, `idle_timeout` and `ping_timeout` will be ignored and no keepalive
|
||||
pings will be sent.
|
||||
|
||||
Disabled by default.
|
||||
|
||||
@@ -207,7 +224,7 @@ The server will verify if not empty.
|
||||
|
||||
Path of HTTP request.
|
||||
|
||||
The server will verify if not empty.
|
||||
The server will verify.
|
||||
|
||||
#### headers
|
||||
|
||||
|
||||
@@ -48,25 +48,30 @@ V2Ray Transport 是 v2ray 发明的一组私有协议,并污染了其他协议
|
||||
|
||||
主机域名列表。
|
||||
|
||||
客户端将随机选择,默认服务器将验证。
|
||||
如果设置,客户端将随机选择,服务器将验证。
|
||||
|
||||
#### path
|
||||
|
||||
!!! warning
|
||||
|
||||
V2Ray 文档称服务端和客户端的路径必须一致,但实际代码允许客户端向路径添加任何后缀。
|
||||
sing-box 使用与 V2Ray 相同的行为,但请注意,该行为在 `WebSocket` 和 `HTTPUpgrade` 传输层中不存在。
|
||||
|
||||
HTTP 请求路径
|
||||
|
||||
默认服务器将验证。
|
||||
服务器将验证。
|
||||
|
||||
#### method
|
||||
|
||||
HTTP 请求方法
|
||||
|
||||
默认服务器将验证。
|
||||
如果设置,服务器将验证。
|
||||
|
||||
#### headers
|
||||
|
||||
HTTP 请求的额外标头
|
||||
|
||||
默认服务器将写入响应。
|
||||
如果设置,服务器将写入响应。
|
||||
|
||||
#### idle_timeout
|
||||
|
||||
@@ -102,11 +107,13 @@ HTTP 请求的额外标头
|
||||
|
||||
HTTP 请求路径
|
||||
|
||||
默认服务器将验证。
|
||||
服务器将验证。
|
||||
|
||||
#### headers
|
||||
|
||||
HTTP 请求的额外标头。
|
||||
HTTP 请求的额外标头
|
||||
|
||||
如果设置,服务器将写入响应。
|
||||
|
||||
#### max_early_data
|
||||
|
||||
@@ -200,16 +207,16 @@ gRPC 服务名称。
|
||||
|
||||
主机域名。
|
||||
|
||||
默认服务器将验证。
|
||||
服务器将验证。
|
||||
|
||||
#### path
|
||||
|
||||
HTTP 请求路径
|
||||
|
||||
默认服务器将验证。
|
||||
服务器将验证。
|
||||
|
||||
#### headers
|
||||
|
||||
HTTP 请求的额外标头。
|
||||
|
||||
默认服务器将写入响应。
|
||||
如果设置,服务器将写入响应。
|
||||
|
||||
10
go.mod
10
go.mod
@@ -8,7 +8,7 @@ require (
|
||||
github.com/cloudflare/circl v1.3.6
|
||||
github.com/cretz/bine v0.2.0
|
||||
github.com/fsnotify/fsnotify v1.7.0
|
||||
github.com/go-chi/chi/v5 v5.0.10
|
||||
github.com/go-chi/chi/v5 v5.0.11
|
||||
github.com/go-chi/cors v1.2.1
|
||||
github.com/go-chi/render v1.0.3
|
||||
github.com/gofrs/uuid/v5 v5.0.0
|
||||
@@ -28,7 +28,7 @@ require (
|
||||
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691
|
||||
github.com/sagernet/sing v0.2.20
|
||||
github.com/sagernet/sing-dns v0.1.12
|
||||
github.com/sagernet/sing-mux v0.1.6
|
||||
github.com/sagernet/sing-mux v0.1.7
|
||||
github.com/sagernet/sing-quic v0.1.6
|
||||
github.com/sagernet/sing-shadowsocks v0.2.6
|
||||
github.com/sagernet/sing-shadowsocks2 v0.1.5
|
||||
@@ -48,8 +48,8 @@ require (
|
||||
golang.org/x/net v0.19.0
|
||||
golang.org/x/sys v0.15.0
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
|
||||
google.golang.org/grpc v1.59.0
|
||||
google.golang.org/protobuf v1.31.0
|
||||
google.golang.org/grpc v1.60.1
|
||||
google.golang.org/protobuf v1.32.0
|
||||
howett.net/plist v1.0.1
|
||||
)
|
||||
|
||||
@@ -91,7 +91,7 @@ require (
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/time v0.4.0 // indirect
|
||||
golang.org/x/tools v0.16.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
lukechampine.com/blake3 v1.2.1 // indirect
|
||||
|
||||
20
go.sum
20
go.sum
@@ -17,8 +17,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk=
|
||||
github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-chi/chi/v5 v5.0.11 h1:BnpYbFZ3T3S1WMpD79r7R5ThWX40TaFB7L31Y8xqSwA=
|
||||
github.com/go-chi/chi/v5 v5.0.11/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
|
||||
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
||||
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
|
||||
@@ -114,8 +114,8 @@ github.com/sagernet/sing v0.2.20 h1:ckcCB/5xu8G8wElNeH74IF6Soac5xWN+eQUXRuonjPQ=
|
||||
github.com/sagernet/sing v0.2.20/go.mod h1:Ce5LNojQOgOiWhiD8pPD6E9H7e2KgtOe3Zxx4Ou5u80=
|
||||
github.com/sagernet/sing-dns v0.1.12 h1:1HqZ+ln+Rezx/aJMStaS0d7oPeX2EobSV1NT537kyj4=
|
||||
github.com/sagernet/sing-dns v0.1.12/go.mod h1:rx/DTOisneQpCgNQ4jbFU/JNEtnz0lYcHXenlVzpjEU=
|
||||
github.com/sagernet/sing-mux v0.1.6 h1:9+LsHgrtG/hgKpJOhtGcEFPeWHXaWeJDO3x4DeDQk5g=
|
||||
github.com/sagernet/sing-mux v0.1.6/go.mod h1:UmcVSPrVjsOGe95jDXmGgOyKKIXOcjz6FKbFy+0LeDU=
|
||||
github.com/sagernet/sing-mux v0.1.7 h1:+48spVReBwIrv6ZdUujiRFCCnblZFwxmbPgrs5zezlI=
|
||||
github.com/sagernet/sing-mux v0.1.7/go.mod h1:UmcVSPrVjsOGe95jDXmGgOyKKIXOcjz6FKbFy+0LeDU=
|
||||
github.com/sagernet/sing-quic v0.1.6 h1:yNkZiNOlmEGpS+A7I4/Zavhe/fRrLz7yCO/dVMZzt+k=
|
||||
github.com/sagernet/sing-quic v0.1.6/go.mod h1:g1Ogcy2KSwKvC7eDXEUu9AnHbjotC+2xsSP+A1i/VOA=
|
||||
github.com/sagernet/sing-shadowsocks v0.2.6 h1:xr7ylAS/q1cQYS8oxKKajhuQcchd5VJJ4K4UZrrpp0s=
|
||||
@@ -205,14 +205,14 @@ golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 h1:CawjfCvYQH2OU3/TnxLx97WDSUDRABfT18pCOYwc2GE=
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6/go.mod h1:3rxYc4HtVcSG9gVaTs2GEBdehh+sYPOwKtyUWEOTb80=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d h1:uvYuEyMHKNt+lT4K3bN6fGswmK8qSvcreM3BwjDh+y4=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M=
|
||||
google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
|
||||
google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97 h1:6GQBEOdGkX6MMTLT9V+TjtIRZCw9VPD5Z+yHY9wMgS0=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97/go.mod h1:v7nGkzlmW8P3n/bKmWBn2WpBjpOEx8Q6gMueudAmKfY=
|
||||
google.golang.org/grpc v1.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU=
|
||||
google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
|
||||
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
||||
@@ -145,12 +145,16 @@ type ListenOptions struct {
|
||||
|
||||
type UDPTimeoutCompat Duration
|
||||
|
||||
func (u *UDPTimeoutCompat) UnmarshalJSON(data []byte) error {
|
||||
func (c UDPTimeoutCompat) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal((time.Duration)(c).String())
|
||||
}
|
||||
|
||||
func (c *UDPTimeoutCompat) UnmarshalJSON(data []byte) error {
|
||||
var valueNumber int64
|
||||
err := json.Unmarshal(data, &valueNumber)
|
||||
if err == nil {
|
||||
*u = UDPTimeoutCompat(time.Second * time.Duration(valueNumber))
|
||||
*c = UDPTimeoutCompat(time.Second * time.Duration(valueNumber))
|
||||
return nil
|
||||
}
|
||||
return json.Unmarshal(data, (*Duration)(u))
|
||||
return json.Unmarshal(data, (*Duration)(c))
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ func NewURLTest(ctx context.Context, router adapter.Router, logger log.ContextLo
|
||||
outbound := &URLTest{
|
||||
myOutboundAdapter: myOutboundAdapter{
|
||||
protocol: C.TypeURLTest,
|
||||
network: []string{N.NetworkTCP, N.NetworkUDP},
|
||||
router: router,
|
||||
logger: logger,
|
||||
tag: tag,
|
||||
@@ -61,13 +62,6 @@ func NewURLTest(ctx context.Context, router adapter.Router, logger log.ContextLo
|
||||
return outbound, nil
|
||||
}
|
||||
|
||||
func (s *URLTest) Network() []string {
|
||||
if s.group == nil {
|
||||
return []string{N.NetworkTCP, N.NetworkUDP}
|
||||
}
|
||||
return s.group.Select(N.NetworkTCP).Network()
|
||||
}
|
||||
|
||||
func (s *URLTest) Start() error {
|
||||
outbounds := make([]adapter.Outbound, 0, len(s.tags))
|
||||
for i, tag := range s.tags {
|
||||
@@ -93,7 +87,12 @@ func (s *URLTest) Close() error {
|
||||
}
|
||||
|
||||
func (s *URLTest) Now() string {
|
||||
return s.group.Select(N.NetworkTCP).Tag()
|
||||
if s.group.selectedOutboundTCP != nil {
|
||||
return s.group.selectedOutboundTCP.Tag()
|
||||
} else if s.group.selectedOutboundUDP != nil {
|
||||
return s.group.selectedOutboundUDP.Tag()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (s *URLTest) All() []string {
|
||||
@@ -111,6 +110,9 @@ func (s *URLTest) CheckOutbounds() {
|
||||
func (s *URLTest) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||
s.group.Touch()
|
||||
outbound := s.group.Select(network)
|
||||
if outbound == nil {
|
||||
return nil, E.New("missing supported outbound")
|
||||
}
|
||||
conn, err := outbound.DialContext(ctx, network, destination)
|
||||
if err == nil {
|
||||
return s.group.interruptGroup.NewConn(conn, interrupt.IsExternalConnectionFromContext(ctx)), nil
|
||||
@@ -123,6 +125,9 @@ func (s *URLTest) DialContext(ctx context.Context, network string, destination M
|
||||
func (s *URLTest) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||
s.group.Touch()
|
||||
outbound := s.group.Select(N.NetworkUDP)
|
||||
if outbound == nil {
|
||||
return nil, E.New("missing supported outbound")
|
||||
}
|
||||
conn, err := outbound.ListenPacket(ctx, destination)
|
||||
if err == nil {
|
||||
return s.group.interruptGroup.NewPacketConn(conn, interrupt.IsExternalConnectionFromContext(ctx)), nil
|
||||
@@ -346,12 +351,12 @@ func (g *URLTestGroup) urlTest(ctx context.Context, force bool) (map[string]uint
|
||||
func (g *URLTestGroup) performUpdateCheck() {
|
||||
outbound := g.Select(N.NetworkTCP)
|
||||
var updated bool
|
||||
if outbound != g.selectedOutboundTCP {
|
||||
if outbound != nil && outbound != g.selectedOutboundTCP {
|
||||
g.selectedOutboundTCP = outbound
|
||||
updated = true
|
||||
}
|
||||
outbound = g.Select(N.NetworkUDP)
|
||||
if outbound != g.selectedOutboundUDP {
|
||||
if outbound != nil && outbound != g.selectedOutboundUDP {
|
||||
g.selectedOutboundUDP = outbound
|
||||
updated = true
|
||||
}
|
||||
|
||||
@@ -535,7 +535,7 @@ func (r *Router) Close() error {
|
||||
}
|
||||
if r.geoIPReader != nil {
|
||||
r.logger.Trace("closing geoip reader")
|
||||
err = E.Append(err, common.Close(r.geoIPReader), func(err error) error {
|
||||
err = E.Append(err, r.geoIPReader.Close(), func(err error) error {
|
||||
return E.Cause(err, "close geoip reader")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -75,6 +75,9 @@ func testShadowsocksMux(t *testing.T, options option.OutboundMultiplexOptions) {
|
||||
},
|
||||
Method: method,
|
||||
Password: password,
|
||||
Multiplex: &option.InboundMultiplexOptions{
|
||||
Enabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -2,19 +2,16 @@ package v2raygrpclite
|
||||
|
||||
import (
|
||||
std_bufio "bufio"
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/baderror"
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
"github.com/sagernet/sing/common/rw"
|
||||
)
|
||||
@@ -30,7 +27,6 @@ type GunConn struct {
|
||||
create chan struct{}
|
||||
err error
|
||||
readRemaining int
|
||||
writeAccess sync.Mutex
|
||||
}
|
||||
|
||||
func newGunConn(reader io.Reader, writer io.Writer, flusher http.Flusher) *GunConn {
|
||||
@@ -100,19 +96,22 @@ func (c *GunConn) read(b []byte) (n int, err error) {
|
||||
}
|
||||
|
||||
func (c *GunConn) Write(b []byte) (n int, err error) {
|
||||
protobufHeader := [1 + binary.MaxVarintLen64]byte{0x0A}
|
||||
varuintLen := binary.PutUvarint(protobufHeader[1:], uint64(len(b)))
|
||||
grpcHeader := buf.Get(5)
|
||||
grpcPayloadLen := uint32(1 + varuintLen + len(b))
|
||||
binary.BigEndian.PutUint32(grpcHeader[1:5], grpcPayloadLen)
|
||||
c.writeAccess.Lock()
|
||||
_, err = bufio.Copy(c.writer, io.MultiReader(bytes.NewReader(grpcHeader), bytes.NewReader(protobufHeader[:varuintLen+1]), bytes.NewReader(b)))
|
||||
c.writeAccess.Unlock()
|
||||
buf.Put(grpcHeader)
|
||||
if err == nil && c.flusher != nil {
|
||||
varLen := rw.UVariantLen(uint64(len(b)))
|
||||
buffer := buf.NewSize(6 + varLen + len(b))
|
||||
header := buffer.Extend(6 + varLen)
|
||||
header[0] = 0x00
|
||||
binary.BigEndian.PutUint32(header[1:5], uint32(1+varLen+len(b)))
|
||||
header[5] = 0x0A
|
||||
binary.PutUvarint(header[6:], uint64(len(b)))
|
||||
common.Must1(buffer.Write(b))
|
||||
_, err = c.writer.Write(buffer.Bytes())
|
||||
if err != nil {
|
||||
return 0, baderror.WrapH2(err)
|
||||
}
|
||||
if c.flusher != nil {
|
||||
c.flusher.Flush()
|
||||
}
|
||||
return len(b), baderror.WrapH2(err)
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
func (c *GunConn) WriteBuffer(buffer *buf.Buffer) error {
|
||||
@@ -120,16 +119,18 @@ func (c *GunConn) WriteBuffer(buffer *buf.Buffer) error {
|
||||
dataLen := buffer.Len()
|
||||
varLen := rw.UVariantLen(uint64(dataLen))
|
||||
header := buffer.ExtendHeader(6 + varLen)
|
||||
_ = header[6]
|
||||
header[0] = 0x00
|
||||
binary.BigEndian.PutUint32(header[1:5], uint32(1+varLen+dataLen))
|
||||
header[5] = 0x0A
|
||||
binary.PutUvarint(header[6:], uint64(dataLen))
|
||||
err := rw.WriteBytes(c.writer, buffer.Bytes())
|
||||
if err == nil && c.flusher != nil {
|
||||
if err != nil {
|
||||
return baderror.WrapH2(err)
|
||||
}
|
||||
if c.flusher != nil {
|
||||
c.flusher.Flush()
|
||||
}
|
||||
return baderror.WrapH2(err)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *GunConn) FrontHeadroom() int {
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
@@ -28,7 +29,7 @@ type Client struct {
|
||||
serverAddr M.Socksaddr
|
||||
transport http.RoundTripper
|
||||
http2 bool
|
||||
url *url.URL
|
||||
requestURL url.URL
|
||||
host []string
|
||||
method string
|
||||
headers http.Header
|
||||
@@ -58,33 +59,35 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt
|
||||
},
|
||||
}
|
||||
}
|
||||
client := &Client{
|
||||
if options.Method == "" {
|
||||
options.Method = http.MethodPut
|
||||
}
|
||||
var requestURL url.URL
|
||||
if tlsConfig == nil {
|
||||
requestURL.Scheme = "http"
|
||||
} else {
|
||||
requestURL.Scheme = "https"
|
||||
}
|
||||
requestURL.Host = serverAddr.String()
|
||||
requestURL.Path = options.Path
|
||||
err := sHTTP.URLSetPath(&requestURL, options.Path)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "parse path")
|
||||
}
|
||||
if !strings.HasPrefix(requestURL.Path, "/") {
|
||||
requestURL.Path = "/" + requestURL.Path
|
||||
}
|
||||
return &Client{
|
||||
ctx: ctx,
|
||||
dialer: dialer,
|
||||
serverAddr: serverAddr,
|
||||
requestURL: requestURL,
|
||||
host: options.Host,
|
||||
method: options.Method,
|
||||
headers: options.Headers.Build(),
|
||||
transport: transport,
|
||||
http2: tlsConfig != nil,
|
||||
}
|
||||
if client.method == "" {
|
||||
client.method = "PUT"
|
||||
}
|
||||
var uri url.URL
|
||||
if tlsConfig == nil {
|
||||
uri.Scheme = "http"
|
||||
} else {
|
||||
uri.Scheme = "https"
|
||||
}
|
||||
uri.Host = serverAddr.String()
|
||||
uri.Path = options.Path
|
||||
err := sHTTP.URLSetPath(&uri, options.Path)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "parse path")
|
||||
}
|
||||
client.url = &uri
|
||||
return client, nil
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Client) DialContext(ctx context.Context) (net.Conn, error) {
|
||||
@@ -103,7 +106,7 @@ func (c *Client) dialHTTP(ctx context.Context) (net.Conn, error) {
|
||||
|
||||
request := &http.Request{
|
||||
Method: c.method,
|
||||
URL: c.url,
|
||||
URL: &c.requestURL,
|
||||
Header: c.headers.Clone(),
|
||||
}
|
||||
switch hostLen := len(c.host); hostLen {
|
||||
@@ -123,7 +126,7 @@ func (c *Client) dialHTTP2(ctx context.Context) (net.Conn, error) {
|
||||
request := &http.Request{
|
||||
Method: c.method,
|
||||
Body: pipeInReader,
|
||||
URL: c.url,
|
||||
URL: &c.requestURL,
|
||||
Header: c.headers.Clone(),
|
||||
}
|
||||
request = request.WithContext(ctx)
|
||||
|
||||
@@ -65,7 +65,7 @@ func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
|
||||
s.invalidRequest(writer, request, http.StatusBadRequest, E.New("bad host: ", host))
|
||||
return
|
||||
}
|
||||
if !strings.HasPrefix(request.URL.Path, s.path) {
|
||||
if request.URL.Path != s.path {
|
||||
s.invalidRequest(writer, request, http.StatusNotFound, E.New("bad path: ", request.URL.Path))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -55,15 +55,10 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt
|
||||
if !strings.HasPrefix(requestURL.Path, "/") {
|
||||
requestURL.Path = "/" + requestURL.Path
|
||||
}
|
||||
headers := make(http.Header)
|
||||
for key, value := range options.Headers {
|
||||
headers[key] = value
|
||||
if key == "Host" {
|
||||
if len(value) > 1 {
|
||||
return nil, E.New("multiple Host headers")
|
||||
}
|
||||
requestURL.Host = value[0]
|
||||
}
|
||||
headers := options.Headers.Build()
|
||||
if host := headers.Get("Host"); host != "" {
|
||||
headers.Del("Host")
|
||||
requestURL.Host = host
|
||||
}
|
||||
if headers.Get("User-Agent") == "" {
|
||||
headers.Set("User-Agent", "Go-http-client/1.1")
|
||||
|
||||
@@ -33,6 +33,7 @@ type Server struct {
|
||||
path string
|
||||
maxEarlyData uint32
|
||||
earlyDataHeaderName string
|
||||
upgrader ws.HTTPUpgrader
|
||||
}
|
||||
|
||||
func NewServer(ctx context.Context, options option.V2RayWebsocketOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (*Server, error) {
|
||||
@@ -43,6 +44,10 @@ func NewServer(ctx context.Context, options option.V2RayWebsocketOptions, tlsCon
|
||||
path: options.Path,
|
||||
maxEarlyData: options.MaxEarlyData,
|
||||
earlyDataHeaderName: options.EarlyDataHeaderName,
|
||||
upgrader: ws.HTTPUpgrader{
|
||||
Timeout: C.TCPTimeout,
|
||||
Header: options.Headers.Build(),
|
||||
},
|
||||
}
|
||||
if !strings.HasPrefix(server.path, "/") {
|
||||
server.path = "/" + server.path
|
||||
@@ -79,6 +84,10 @@ func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if request.URL.Path != s.path {
|
||||
s.invalidRequest(writer, request, http.StatusNotFound, E.New("bad path: ", request.URL.Path))
|
||||
return
|
||||
}
|
||||
earlyDataStr := request.Header.Get(s.earlyDataHeaderName)
|
||||
if earlyDataStr != "" {
|
||||
earlyData, err = base64.RawURLEncoding.DecodeString(earlyDataStr)
|
||||
|
||||
Reference in New Issue
Block a user