mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-11 17:47:20 +10:00
Compare commits
20 Commits
v1.3-beta4
...
v1.3-beta5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
32875e7cbc | ||
|
|
3de56620ce | ||
|
|
3651605d3b | ||
|
|
7d1174e545 | ||
|
|
bd9e6e5cd9 | ||
|
|
ed37cb858b | ||
|
|
62bcf22c26 | ||
|
|
84bd997742 | ||
|
|
a548e45ad7 | ||
|
|
5c1de2bb06 | ||
|
|
e5f0add1ab | ||
|
|
70e47df295 | ||
|
|
f20642d6fd | ||
|
|
73fa926b48 | ||
|
|
5d9dce8078 | ||
|
|
e20e2d57c9 | ||
|
|
25f31890ed | ||
|
|
194b36b987 | ||
|
|
1e39196bc9 | ||
|
|
da82a41697 |
12
.github/workflows/debug.yml
vendored
12
.github/workflows/debug.yml
vendored
@@ -31,12 +31,6 @@ jobs:
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: ${{ steps.version.outputs.go_version }}
|
||||
- name: Cache go module
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/go/pkg/mod
|
||||
key: go-${{ hashFiles('**/go.sum') }}
|
||||
- name: Add cache to Go proxy
|
||||
run: |
|
||||
version=`git rev-parse HEAD`
|
||||
@@ -196,12 +190,6 @@ jobs:
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: ${{ steps.version.outputs.go_version }}
|
||||
- name: Cache go module
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/go/pkg/mod
|
||||
key: go-${{ hashFiles('**/go.sum') }}
|
||||
- name: Build
|
||||
id: build
|
||||
run: make
|
||||
|
||||
6
.github/workflows/lint.yml
vendored
6
.github/workflows/lint.yml
vendored
@@ -31,12 +31,6 @@ jobs:
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: ${{ steps.version.outputs.go_version }}
|
||||
- name: Cache go module
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/go/pkg/mod
|
||||
key: go-${{ hashFiles('**/go.sum') }}
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v3
|
||||
with:
|
||||
|
||||
13
Makefile
13
Makefile
@@ -77,13 +77,20 @@ test_stdio:
|
||||
go mod tidy && \
|
||||
go test -v -tags "$(TAGS_TEST),force_stdio" .
|
||||
|
||||
android:
|
||||
go run ./cmd/internal/build_libbox -target android
|
||||
|
||||
ios:
|
||||
go run ./cmd/internal/build_libbox -target ios
|
||||
|
||||
lib:
|
||||
go run ./cmd/internal/build_libbox
|
||||
go run ./cmd/internal/build_libbox -target android
|
||||
go run ./cmd/internal/build_libbox -target ios
|
||||
|
||||
lib_install:
|
||||
go get -v -d
|
||||
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.0.0-20221130124640-349ebaa752ca
|
||||
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.0.0-20221130124640-349ebaa752ca
|
||||
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.0.0-20230413023804-244d7ff07035
|
||||
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.0.0-20230413023804-244d7ff07035
|
||||
|
||||
clean:
|
||||
rm -rf bin dist sing-box
|
||||
|
||||
@@ -35,6 +35,11 @@ type OutboundGroup interface {
|
||||
All() []string
|
||||
}
|
||||
|
||||
type URLTestGroup interface {
|
||||
OutboundGroup
|
||||
URLTest(ctx context.Context, url string) (map[string]uint16, error)
|
||||
}
|
||||
|
||||
func OutboundTag(detour Outbound) string {
|
||||
if group, isGroup := detour.(OutboundGroup); isGroup {
|
||||
return group.Now()
|
||||
|
||||
@@ -12,7 +12,7 @@ type Conn struct {
|
||||
element *list.Element[io.Closer]
|
||||
}
|
||||
|
||||
func NewConn(conn net.Conn) (*Conn, error) {
|
||||
func NewConn(conn net.Conn) (net.Conn, error) {
|
||||
connAccess.Lock()
|
||||
element := openConnection.PushBack(conn)
|
||||
connAccess.Unlock()
|
||||
|
||||
@@ -12,7 +12,7 @@ type PacketConn struct {
|
||||
element *list.Element[io.Closer]
|
||||
}
|
||||
|
||||
func NewPacketConn(conn net.PacketConn) (*PacketConn, error) {
|
||||
func NewPacketConn(conn net.PacketConn) (net.PacketConn, error) {
|
||||
connAccess.Lock()
|
||||
element := openConnection.PushBack(conn)
|
||||
connAccess.Unlock()
|
||||
|
||||
@@ -73,7 +73,7 @@ func (d *ResolveDialer) ListenPacket(ctx context.Context, destination M.Socksadd
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bufio.NewNATPacketConn(bufio.NewPacketConn(conn), destination, M.SocksaddrFrom(destinationAddress, destination.Port)), nil
|
||||
return bufio.NewNATPacketConn(bufio.NewPacketConn(conn), M.SocksaddrFrom(destinationAddress, destination.Port), destination), nil
|
||||
}
|
||||
|
||||
func (d *ResolveDialer) Upstream() any {
|
||||
|
||||
@@ -414,7 +414,11 @@ func (c *ClientPacketAddrConn) ReadFrom(p []byte) (n int, addr net.Addr, err err
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
addr = destination.UDPAddr()
|
||||
if destination.IsFqdn() {
|
||||
addr = destination
|
||||
} else {
|
||||
addr = destination.UDPAddr()
|
||||
}
|
||||
var length uint16
|
||||
err = binary.Read(c.ExtendedConn, binary.BigEndian, &length)
|
||||
if err != nil {
|
||||
|
||||
@@ -3,10 +3,12 @@ package process
|
||||
import (
|
||||
"context"
|
||||
"net/netip"
|
||||
"os/user"
|
||||
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-tun"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
)
|
||||
|
||||
type Searcher interface {
|
||||
@@ -28,5 +30,15 @@ type Info struct {
|
||||
}
|
||||
|
||||
func FindProcessInfo(searcher Searcher, ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
|
||||
return findProcessInfo(searcher, ctx, network, source, destination)
|
||||
info, err := searcher.FindProcessInfo(ctx, network, source, destination)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if info.UserId != -1 {
|
||||
osUser, _ := user.LookupId(F.ToString(info.UserId))
|
||||
if osUser != nil {
|
||||
info.User = osUser.Username
|
||||
}
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
//go:build linux && !android
|
||||
|
||||
package process
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/netip"
|
||||
"os/user"
|
||||
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
)
|
||||
|
||||
func findProcessInfo(searcher Searcher, ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
|
||||
info, err := searcher.FindProcessInfo(ctx, network, source, destination)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if info.UserId != -1 {
|
||||
osUser, _ := user.LookupId(F.ToString(info.UserId))
|
||||
if osUser != nil {
|
||||
info.User = osUser.Username
|
||||
}
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
//go:build !linux || android
|
||||
|
||||
package process
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/netip"
|
||||
)
|
||||
|
||||
func findProcessInfo(searcher Searcher, ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
|
||||
return searcher.FindProcessInfo(ctx, network, source, destination)
|
||||
}
|
||||
@@ -24,12 +24,12 @@ func PeekStream(ctx context.Context, conn net.Conn, buffer *buf.Buffer, timeout
|
||||
}
|
||||
err := conn.SetReadDeadline(time.Now().Add(timeout))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, E.Cause(err, "set read deadline")
|
||||
}
|
||||
_, err = buffer.ReadOnceFrom(conn)
|
||||
err = E.Errors(err, conn.SetReadDeadline(time.Time{}))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, E.Cause(err, "read payload")
|
||||
}
|
||||
var metadata *adapter.InboundContext
|
||||
var errors []error
|
||||
|
||||
@@ -180,7 +180,7 @@ func NewSTDServer(ctx context.Context, router adapter.Router, logger log.Logger,
|
||||
tlsConfig.ServerName = options.ServerName
|
||||
}
|
||||
if len(options.ALPN) > 0 {
|
||||
tlsConfig.NextProtos = append(tlsConfig.NextProtos, options.ALPN...)
|
||||
tlsConfig.NextProtos = append(options.ALPN, tlsConfig.NextProtos...)
|
||||
}
|
||||
if options.MinVersion != "" {
|
||||
minVersion, err := ParseTLSVersion(options.MinVersion)
|
||||
|
||||
@@ -50,6 +50,9 @@ func (s *HistoryStorage) StoreURLTestHistory(tag string, history *History) {
|
||||
}
|
||||
|
||||
func URLTest(ctx context.Context, link string, detour N.Dialer) (t uint16, err error) {
|
||||
if link == "" {
|
||||
link = "https://www.gstatic.com/generate_204"
|
||||
}
|
||||
linkURL, err := url.Parse(link)
|
||||
if err != nil {
|
||||
return
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
#### 1.3-beta5
|
||||
|
||||
* Add Clash.Meta API compatibility for Clash API
|
||||
* Download Yacd-meta by default if the specified Clash `external_ui` directory is empty
|
||||
* Add path and headers option for HTTP outbound
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.3-beta4
|
||||
|
||||
* Fix bugs
|
||||
|
||||
@@ -59,7 +59,7 @@ serve it at `http://{{external-controller}}/ui`.
|
||||
|
||||
ZIP download URL for the external UI, will be used if the specified `external_ui` directory is empty.
|
||||
|
||||
`https://github.com/Dreamacro/clash-dashboard/archive/refs/heads/gh-pages.zip` will be used if empty.
|
||||
`https://github.com/MetaCubeX/Yacd-meta/archive/gh-pages.zip` will be used if empty.
|
||||
|
||||
#### external_ui_download_detour
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ RESTful web API 监听地址。如果为空,则禁用 Clash API。
|
||||
|
||||
静态网页资源的 ZIP 下载 URL,如果指定的 `external_ui` 目录为空,将使用。
|
||||
|
||||
默认使用 `https://github.com/Dreamacro/clash-dashboard/archive/refs/heads/gh-pages.zip`。
|
||||
默认使用 `https://github.com/MetaCubeX/Yacd-meta/archive/gh-pages.zip`。
|
||||
|
||||
#### external_ui_download_detour
|
||||
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
"server_port": 1080,
|
||||
"username": "sekai",
|
||||
"password": "admin",
|
||||
"path": "",
|
||||
"headers": {},
|
||||
"tls": {},
|
||||
|
||||
... // Dial Fields
|
||||
@@ -39,6 +41,14 @@ Basic authorization username.
|
||||
|
||||
Basic authorization password.
|
||||
|
||||
#### path
|
||||
|
||||
Path of HTTP request.
|
||||
|
||||
#### headers
|
||||
|
||||
Extra headers of HTTP request.
|
||||
|
||||
#### tls
|
||||
|
||||
TLS configuration, see [TLS](/configuration/shared/tls/#outbound).
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
"server_port": 1080,
|
||||
"username": "sekai",
|
||||
"password": "admin",
|
||||
"path": "",
|
||||
"headers": {},
|
||||
"tls": {},
|
||||
|
||||
... // 拨号字段
|
||||
@@ -39,6 +41,14 @@ Basic 认证用户名。
|
||||
|
||||
Basic 认证密码。
|
||||
|
||||
#### path
|
||||
|
||||
HTTP 请求路径。
|
||||
|
||||
#### headers
|
||||
|
||||
HTTP 请求的额外标头。
|
||||
|
||||
#### tls
|
||||
|
||||
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#outbound)。
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"proxy-b",
|
||||
"proxy-c"
|
||||
],
|
||||
"url": "http://www.gstatic.com/generate_204",
|
||||
"url": "https://www.gstatic.com/generate_204",
|
||||
"interval": "1m",
|
||||
"tolerance": 50
|
||||
}
|
||||
@@ -26,7 +26,7 @@ List of outbound tags to test.
|
||||
|
||||
#### url
|
||||
|
||||
The URL to test. `http://www.gstatic.com/generate_204` will be used if empty.
|
||||
The URL to test. `https://www.gstatic.com/generate_204` will be used if empty.
|
||||
|
||||
#### interval
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"proxy-b",
|
||||
"proxy-c"
|
||||
],
|
||||
"url": "http://www.gstatic.com/generate_204",
|
||||
"url": "https://www.gstatic.com/generate_204",
|
||||
"interval": "1m",
|
||||
"tolerance": 50
|
||||
}
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
#### url
|
||||
|
||||
用于测试的链接。默认使用 `http://www.gstatic.com/generate_204`。
|
||||
用于测试的链接。默认使用 `https://www.gstatic.com/generate_204`。
|
||||
|
||||
#### interval
|
||||
|
||||
|
||||
78
experimental/clashapi/api_meta.go
Normal file
78
experimental/clashapi/api_meta.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package clashapi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/common/json"
|
||||
"github.com/sagernet/sing-box/experimental/clashapi/trafficontrol"
|
||||
"github.com/sagernet/websocket"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/render"
|
||||
)
|
||||
|
||||
// API created by Clash.Meta
|
||||
|
||||
func (s *Server) setupMetaAPI(r chi.Router) {
|
||||
r.Get("/memory", memory(s.trafficManager))
|
||||
r.Mount("/group", groupRouter(s))
|
||||
}
|
||||
|
||||
type Memory struct {
|
||||
Inuse uint64 `json:"inuse"`
|
||||
OSLimit uint64 `json:"oslimit"` // maybe we need it in the future
|
||||
}
|
||||
|
||||
func memory(trafficManager *trafficontrol.Manager) func(w http.ResponseWriter, r *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var wsConn *websocket.Conn
|
||||
if websocket.IsWebSocketUpgrade(r) {
|
||||
var err error
|
||||
wsConn, err = upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if wsConn == nil {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
render.Status(r, http.StatusOK)
|
||||
}
|
||||
|
||||
tick := time.NewTicker(time.Second)
|
||||
defer tick.Stop()
|
||||
buf := &bytes.Buffer{}
|
||||
var err error
|
||||
first := true
|
||||
for range tick.C {
|
||||
buf.Reset()
|
||||
|
||||
inuse := trafficManager.Snapshot().Memory
|
||||
|
||||
// make chat.js begin with zero
|
||||
// this is shit var,but we need output 0 for first time
|
||||
if first {
|
||||
first = false
|
||||
inuse = 0
|
||||
}
|
||||
if err := json.NewEncoder(buf).Encode(Memory{
|
||||
Inuse: inuse,
|
||||
OSLimit: 0,
|
||||
}); err != nil {
|
||||
break
|
||||
}
|
||||
if wsConn == nil {
|
||||
_, err = w.Write(buf.Bytes())
|
||||
w.(http.Flusher).Flush()
|
||||
} else {
|
||||
err = wsConn.WriteMessage(websocket.TextMessage, buf.Bytes())
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
136
experimental/clashapi/api_meta_group.go
Normal file
136
experimental/clashapi/api_meta_group.go
Normal file
@@ -0,0 +1,136 @@
|
||||
package clashapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/badjson"
|
||||
"github.com/sagernet/sing-box/common/urltest"
|
||||
"github.com/sagernet/sing-box/outbound"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/batch"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/render"
|
||||
)
|
||||
|
||||
func groupRouter(server *Server) http.Handler {
|
||||
r := chi.NewRouter()
|
||||
r.Get("/", getGroups(server))
|
||||
r.Route("/{name}", func(r chi.Router) {
|
||||
r.Use(parseProxyName, findProxyByName(server.router))
|
||||
r.Get("/", getGroup(server))
|
||||
r.Get("/delay", getGroupDelay(server))
|
||||
})
|
||||
return r
|
||||
}
|
||||
|
||||
func getGroups(server *Server) func(w http.ResponseWriter, r *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
groups := common.Map(common.Filter(server.router.Outbounds(), func(it adapter.Outbound) bool {
|
||||
_, isGroup := it.(adapter.OutboundGroup)
|
||||
return isGroup
|
||||
}), func(it adapter.Outbound) *badjson.JSONObject {
|
||||
return proxyInfo(server, it)
|
||||
})
|
||||
render.JSON(w, r, render.M{
|
||||
"proxies": groups,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func getGroup(server *Server) func(w http.ResponseWriter, r *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
proxy := r.Context().Value(CtxKeyProxy).(adapter.Outbound)
|
||||
if _, ok := proxy.(adapter.OutboundGroup); ok {
|
||||
render.JSON(w, r, proxyInfo(server, proxy))
|
||||
return
|
||||
}
|
||||
render.Status(r, http.StatusNotFound)
|
||||
render.JSON(w, r, ErrNotFound)
|
||||
}
|
||||
}
|
||||
|
||||
func getGroupDelay(server *Server) func(w http.ResponseWriter, r *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
proxy := r.Context().Value(CtxKeyProxy).(adapter.Outbound)
|
||||
group, ok := proxy.(adapter.OutboundGroup)
|
||||
if !ok {
|
||||
render.Status(r, http.StatusNotFound)
|
||||
render.JSON(w, r, ErrNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
query := r.URL.Query()
|
||||
url := query.Get("url")
|
||||
if strings.HasPrefix(url, "http://") {
|
||||
url = ""
|
||||
}
|
||||
timeout, err := strconv.ParseInt(query.Get("timeout"), 10, 32)
|
||||
if err != nil {
|
||||
render.Status(r, http.StatusBadRequest)
|
||||
render.JSON(w, r, ErrBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(r.Context(), time.Millisecond*time.Duration(timeout))
|
||||
defer cancel()
|
||||
|
||||
var result map[string]uint16
|
||||
if urlTestGroup, isURLTestGroup := group.(adapter.URLTestGroup); isURLTestGroup {
|
||||
result, err = urlTestGroup.URLTest(ctx, url)
|
||||
} else {
|
||||
outbounds := common.FilterNotNil(common.Map(group.All(), func(it string) adapter.Outbound {
|
||||
itOutbound, _ := server.router.Outbound(it)
|
||||
return itOutbound
|
||||
}))
|
||||
b, _ := batch.New(ctx, batch.WithConcurrencyNum[any](10))
|
||||
checked := make(map[string]bool)
|
||||
result = make(map[string]uint16)
|
||||
var resultAccess sync.Mutex
|
||||
for _, detour := range outbounds {
|
||||
tag := detour.Tag()
|
||||
realTag := outbound.RealTag(detour)
|
||||
if checked[realTag] {
|
||||
continue
|
||||
}
|
||||
checked[realTag] = true
|
||||
p, loaded := server.router.Outbound(realTag)
|
||||
if !loaded {
|
||||
continue
|
||||
}
|
||||
b.Go(realTag, func() (any, error) {
|
||||
t, err := urltest.URLTest(ctx, url, p)
|
||||
if err != nil {
|
||||
server.logger.Debug("outbound ", tag, " unavailable: ", err)
|
||||
server.urlTestHistory.DeleteURLTestHistory(realTag)
|
||||
} else {
|
||||
server.logger.Debug("outbound ", tag, " available: ", t, "ms")
|
||||
server.urlTestHistory.StoreURLTestHistory(realTag, &urltest.History{
|
||||
Time: time.Now(),
|
||||
Delay: t,
|
||||
})
|
||||
resultAccess.Lock()
|
||||
result[tag] = t
|
||||
resultAccess.Unlock()
|
||||
}
|
||||
return nil, nil
|
||||
})
|
||||
}
|
||||
b.Wait()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
render.Status(r, http.StatusGatewayTimeout)
|
||||
render.JSON(w, r, newError(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
render.JSON(w, r, result)
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"net/http"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
@@ -214,6 +215,9 @@ func getProxyDelay(server *Server) func(w http.ResponseWriter, r *http.Request)
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
query := r.URL.Query()
|
||||
url := query.Get("url")
|
||||
if strings.HasPrefix(url, "http://") {
|
||||
url = ""
|
||||
}
|
||||
timeout, err := strconv.ParseInt(query.Get("timeout"), 10, 16)
|
||||
if err != nil {
|
||||
render.Status(r, http.StatusBadRequest)
|
||||
|
||||
@@ -109,6 +109,8 @@ func NewServer(router adapter.Router, logFactory log.ObservableFactory, options
|
||||
r.Mount("/profile", profileRouter())
|
||||
r.Mount("/cache", cacheRouter(router))
|
||||
r.Mount("/dns", dnsRouter(router))
|
||||
|
||||
server.setupMetaAPI(r)
|
||||
})
|
||||
if options.ExternalUI != "" {
|
||||
server.externalUI = C.BasePath(os.ExpandEnv(options.ExternalUI))
|
||||
@@ -406,5 +408,5 @@ func getLogs(logFactory log.ObservableFactory) func(w http.ResponseWriter, r *ht
|
||||
}
|
||||
|
||||
func version(w http.ResponseWriter, r *http.Request) {
|
||||
render.JSON(w, r, render.M{"version": "sing-box " + C.Version, "premium": true})
|
||||
render.JSON(w, r, render.M{"version": "sing-box " + C.Version, "premium": true, "meta": true})
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ func (s *Server) downloadExternalUI() error {
|
||||
if s.externalUIDownloadURL != "" {
|
||||
downloadURL = s.externalUIDownloadURL
|
||||
} else {
|
||||
downloadURL = "https://github.com/Dreamacro/clash-dashboard/archive/refs/heads/gh-pages.zip"
|
||||
downloadURL = "https://github.com/MetaCubeX/Yacd-meta/archive/gh-pages.zip"
|
||||
}
|
||||
s.logger.Info("downloading external ui")
|
||||
var detour adapter.Outbound
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package trafficontrol
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/experimental/clashapi/compatible"
|
||||
@@ -18,12 +19,15 @@ type Manager struct {
|
||||
connections compatible.Map[string, tracker]
|
||||
ticker *time.Ticker
|
||||
done chan struct{}
|
||||
// process *process.Process
|
||||
memory uint64
|
||||
}
|
||||
|
||||
func NewManager() *Manager {
|
||||
manager := &Manager{
|
||||
ticker: time.NewTicker(time.Second),
|
||||
done: make(chan struct{}),
|
||||
// process: &process.Process{Pid: int32(os.Getpid())},
|
||||
}
|
||||
go manager.handle()
|
||||
return manager
|
||||
@@ -58,10 +62,18 @@ func (m *Manager) Snapshot() *Snapshot {
|
||||
return true
|
||||
})
|
||||
|
||||
//if memoryInfo, err := m.process.MemoryInfo(); err == nil {
|
||||
// m.memory = memoryInfo.RSS
|
||||
//} else {
|
||||
var memStats runtime.MemStats
|
||||
runtime.ReadMemStats(&memStats)
|
||||
m.memory = memStats.StackInuse + memStats.HeapInuse + memStats.HeapIdle - memStats.HeapReleased
|
||||
|
||||
return &Snapshot{
|
||||
UploadTotal: m.uploadTotal.Load(),
|
||||
DownloadTotal: m.downloadTotal.Load(),
|
||||
Connections: connections,
|
||||
Memory: m.memory,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,4 +112,5 @@ type Snapshot struct {
|
||||
DownloadTotal int64 `json:"downloadTotal"`
|
||||
UploadTotal int64 `json:"uploadTotal"`
|
||||
Connections []tracker `json:"connections"`
|
||||
Memory uint64 `json:"memory"`
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ func (s *CommandServer) handleLogConn(conn net.Conn) error {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
return ctx.Err()
|
||||
case message := <-subscription:
|
||||
err = writeLog(conn, []byte(message))
|
||||
if err != nil {
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/debug"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/observable"
|
||||
"github.com/sagernet/sing/common/x/list"
|
||||
@@ -71,7 +72,9 @@ func (s *CommandServer) loopConnection(listener net.Listener) {
|
||||
go func() {
|
||||
hErr := s.handleConnection(conn)
|
||||
if hErr != nil && !E.IsClosed(err) {
|
||||
log.Warn("log-server: process connection: ", hErr)
|
||||
if debug.Enabled {
|
||||
log.Warn("log-server: process connection: ", hErr)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ func (s *CommandServer) handleStatusConn(conn net.Conn) error {
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
return ctx.Err()
|
||||
case <-ticker.C:
|
||||
}
|
||||
}
|
||||
|
||||
8
go.mod
8
go.mod
@@ -22,15 +22,15 @@ require (
|
||||
github.com/oschwald/maxminddb-golang v1.10.0
|
||||
github.com/pires/go-proxyproto v0.7.0
|
||||
github.com/sagernet/cloudflare-tls v0.0.0-20221031050923-d70792f4c3a0
|
||||
github.com/sagernet/gomobile v0.0.0-20221130124640-349ebaa752ca
|
||||
github.com/sagernet/gomobile v0.0.0-20230413023804-244d7ff07035
|
||||
github.com/sagernet/quic-go v0.0.0-20230202071646-a8c8afb18b32
|
||||
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691
|
||||
github.com/sagernet/sing v0.2.3-0.20230409145127-a82d82e55914
|
||||
github.com/sagernet/sing v0.2.3-0.20230413023204-48b019b13e78
|
||||
github.com/sagernet/sing-dns v0.1.5-0.20230408004833-5adaf486d440
|
||||
github.com/sagernet/sing-shadowsocks v0.2.1-0.20230409094647-5c830455eb9b
|
||||
github.com/sagernet/sing-shadowsocks v0.2.1-0.20230412123110-1a7c32b4e2e7
|
||||
github.com/sagernet/sing-shadowtls v0.1.1-0.20230409094821-9abef019436f
|
||||
github.com/sagernet/sing-tun v0.1.4-0.20230326080954-8848c0e4cbab
|
||||
github.com/sagernet/sing-vmess v0.1.4-0.20230409094745-dc784c704250
|
||||
github.com/sagernet/sing-vmess v0.1.4-0.20230412122845-9470e68f5e45
|
||||
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37
|
||||
github.com/sagernet/tfo-go v0.0.0-20230303015439-ffcfd8c41cf9
|
||||
github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2
|
||||
|
||||
18
go.sum
18
go.sum
@@ -101,8 +101,10 @@ github.com/sagernet/cloudflare-tls v0.0.0-20221031050923-d70792f4c3a0 h1:KyhtFFt
|
||||
github.com/sagernet/cloudflare-tls v0.0.0-20221031050923-d70792f4c3a0/go.mod h1:D4SFEOkJK+4W1v86ZhX0jPM0rAL498fyQAChqMtes/I=
|
||||
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 h1:5+m7c6AkmAylhauulqN/c5dnh8/KssrE9c93TQrXldA=
|
||||
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61/go.mod h1:QUQ4RRHD6hGGHdFMEtR8T2P6GS6R3D/CXKdaYHKKXms=
|
||||
github.com/sagernet/gomobile v0.0.0-20221130124640-349ebaa752ca h1:w56+kf8BeqLqllrRJ1tdwKc3sCdWOn/DuNHpY9fAiqs=
|
||||
github.com/sagernet/gomobile v0.0.0-20221130124640-349ebaa752ca/go.mod h1:5YE39YkJkCcMsfq1jMKkjsrM2GfBoF9JVWnvU89hmvU=
|
||||
github.com/sagernet/gomobile v0.0.0-20230413023437-ec061884b992 h1:WkcHhOX3ce9ElLKDUQKJrAt7SjpKNnASsPbMfqfZEPc=
|
||||
github.com/sagernet/gomobile v0.0.0-20230413023437-ec061884b992/go.mod h1:5YE39YkJkCcMsfq1jMKkjsrM2GfBoF9JVWnvU89hmvU=
|
||||
github.com/sagernet/gomobile v0.0.0-20230413023804-244d7ff07035 h1:KttYh6bBhIw8Y6/Ljn7CGwC3CKZn788rzMJmeAKjY+8=
|
||||
github.com/sagernet/gomobile v0.0.0-20230413023804-244d7ff07035/go.mod h1:5YE39YkJkCcMsfq1jMKkjsrM2GfBoF9JVWnvU89hmvU=
|
||||
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE=
|
||||
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
|
||||
github.com/sagernet/quic-go v0.0.0-20230202071646-a8c8afb18b32 h1:tztuJB+giOWNRKQEBVY2oI3PsheTooMdh+/yxemYQYY=
|
||||
@@ -111,18 +113,18 @@ github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byL
|
||||
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU=
|
||||
github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
|
||||
github.com/sagernet/sing v0.1.8/go.mod h1:jt1w2u7lJQFFSGLiRrRIs5YWmx4kAPfWuOejuDW9qMk=
|
||||
github.com/sagernet/sing v0.2.3-0.20230409145127-a82d82e55914 h1:ReHDfZgEMKNVh0xvgvOc5j7M2kX9J+3nNQ8DrSwx0Hs=
|
||||
github.com/sagernet/sing v0.2.3-0.20230409145127-a82d82e55914/go.mod h1:Ta8nHnDLAwqySzKhGoKk4ZIB+vJ3GTKj7UPrWYvM+4w=
|
||||
github.com/sagernet/sing v0.2.3-0.20230413023204-48b019b13e78 h1:bTE9RgURmiFiTjXaYN6q9BYPTBPKIzlFYNy2p+WOubI=
|
||||
github.com/sagernet/sing v0.2.3-0.20230413023204-48b019b13e78/go.mod h1:Ta8nHnDLAwqySzKhGoKk4ZIB+vJ3GTKj7UPrWYvM+4w=
|
||||
github.com/sagernet/sing-dns v0.1.5-0.20230408004833-5adaf486d440 h1:VH8/BcOVuApHtS+vKP+khxlGRcXH7KKhgkTDtNynqSQ=
|
||||
github.com/sagernet/sing-dns v0.1.5-0.20230408004833-5adaf486d440/go.mod h1:69PNSHyEmXdjf6C+bXBOdr2GQnPeEyWjIzo/MV8gmz8=
|
||||
github.com/sagernet/sing-shadowsocks v0.2.1-0.20230409094647-5c830455eb9b h1:nmP+V4nlc8lqEMpwjjbny8ISkrFIjvKWIETsjs7nSic=
|
||||
github.com/sagernet/sing-shadowsocks v0.2.1-0.20230409094647-5c830455eb9b/go.mod h1:WoVjGUvRqsx5yhYeDAB5CijCHpNDi0LUPHl3cf7u8Lc=
|
||||
github.com/sagernet/sing-shadowsocks v0.2.1-0.20230412123110-1a7c32b4e2e7 h1:3WDMIF1aE/twc5gJ+9PF2ZJqUxwZ80MPtNBKE3yBevU=
|
||||
github.com/sagernet/sing-shadowsocks v0.2.1-0.20230412123110-1a7c32b4e2e7/go.mod h1:WoVjGUvRqsx5yhYeDAB5CijCHpNDi0LUPHl3cf7u8Lc=
|
||||
github.com/sagernet/sing-shadowtls v0.1.1-0.20230409094821-9abef019436f h1:qzQvpcDm60zPW8UlZa8UEaBoFORFeGAnhDncPc3VWT4=
|
||||
github.com/sagernet/sing-shadowtls v0.1.1-0.20230409094821-9abef019436f/go.mod h1:MxB+Q9H0pAHcrlvNmwSs1crljRwHFFVhtXyOMBy44Nw=
|
||||
github.com/sagernet/sing-tun v0.1.4-0.20230326080954-8848c0e4cbab h1:a9oeWuPBuIZ70qMhIIH6RrYhp886xN9jJIwsuu4ZFUo=
|
||||
github.com/sagernet/sing-tun v0.1.4-0.20230326080954-8848c0e4cbab/go.mod h1:4YxIDEkkCjGXDOTMPw1SXpLmCQUFAWuaQN250oo+928=
|
||||
github.com/sagernet/sing-vmess v0.1.4-0.20230409094745-dc784c704250 h1:Q5X89Z7wV201pLhPm+viAzzVgPcrYD1PniAVGt9Le/0=
|
||||
github.com/sagernet/sing-vmess v0.1.4-0.20230409094745-dc784c704250/go.mod h1:eULig3LgaeNiWSquSlzXF42Joypsj3fO1W+Qy93o6hk=
|
||||
github.com/sagernet/sing-vmess v0.1.4-0.20230412122845-9470e68f5e45 h1:QqYhWah3u+o2tvLRuTfEu3BwsGpf/wNnVK/VNQV2YBM=
|
||||
github.com/sagernet/sing-vmess v0.1.4-0.20230412122845-9470e68f5e45/go.mod h1:eULig3LgaeNiWSquSlzXF42Joypsj3fO1W+Qy93o6hk=
|
||||
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 h1:HuE6xSwco/Xed8ajZ+coeYLmioq0Qp1/Z2zczFaV8as=
|
||||
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37/go.mod h1:3skNSftZDJWTGVtVaM2jfbce8qHnmH/AGDRe62iNOg0=
|
||||
github.com/sagernet/tfo-go v0.0.0-20230303015439-ffcfd8c41cf9 h1:2ItpW1nMNkPzmBTxV0/eClCklHrFSQMnUGcpUmJxVeE=
|
||||
|
||||
@@ -90,6 +90,9 @@ func (n *Naive) Start() error {
|
||||
n.httpServer = &http.Server{
|
||||
Handler: n,
|
||||
TLSConfig: tlsConfig,
|
||||
BaseContext: func(listener net.Listener) context.Context {
|
||||
return n.ctx
|
||||
},
|
||||
}
|
||||
go func() {
|
||||
var sErr error
|
||||
|
||||
@@ -2,6 +2,7 @@ package ntp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
@@ -9,6 +10,7 @@ import (
|
||||
"github.com/sagernet/sing-box/common/settings"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
@@ -20,7 +22,7 @@ var _ adapter.TimeService = (*Service)(nil)
|
||||
|
||||
type Service struct {
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
cancel common.ContextCancelCauseFunc
|
||||
server M.Socksaddr
|
||||
writeToSystem bool
|
||||
dialer N.Dialer
|
||||
@@ -30,7 +32,7 @@ type Service struct {
|
||||
}
|
||||
|
||||
func NewService(ctx context.Context, router adapter.Router, logger logger.Logger, options option.NTPOptions) *Service {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
ctx, cancel := common.ContextWithCancelCause(ctx)
|
||||
server := options.ServerOptions.Build()
|
||||
if server.Port == 0 {
|
||||
server.Port = 123
|
||||
@@ -64,7 +66,7 @@ func (s *Service) Start() error {
|
||||
|
||||
func (s *Service) Close() error {
|
||||
s.ticker.Stop()
|
||||
s.cancel()
|
||||
s.cancel(os.ErrClosed)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,9 @@ type SocksOutboundOptions struct {
|
||||
type HTTPOutboundOptions struct {
|
||||
DialerOptions
|
||||
ServerOptions
|
||||
Username string `json:"username,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
TLS *OutboundTLSOptions `json:"tls,omitempty"`
|
||||
Username string `json:"username,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
TLS *OutboundTLSOptions `json:"tls,omitempty"`
|
||||
Path string `json:"path,omitempty"`
|
||||
Headers map[string]Listable[string] `json:"headers,omitempty"`
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, t
|
||||
case C.TypeSelector:
|
||||
return NewSelector(router, logger, tag, options.SelectorOptions)
|
||||
case C.TypeURLTest:
|
||||
return NewURLTest(router, logger, tag, options.URLTestOptions)
|
||||
return NewURLTest(ctx, router, logger, tag, options.URLTestOptions)
|
||||
default:
|
||||
return nil, E.New("unknown outbound type: ", options.Type)
|
||||
}
|
||||
|
||||
@@ -102,11 +102,10 @@ func (d *DNS) handleConnection(ctx context.Context, conn net.Conn, metadata adap
|
||||
|
||||
func (d *DNS) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
|
||||
ctx = adapter.WithContext(ctx, &metadata)
|
||||
fastClose, cancel := context.WithCancel(ctx)
|
||||
fastClose, cancel := common.ContextWithCancelCause(ctx)
|
||||
timeout := canceler.New(fastClose, cancel, C.DNSTimeout)
|
||||
var group task.Group
|
||||
group.Append0(func(ctx context.Context) error {
|
||||
defer cancel()
|
||||
_buffer := buf.StackNewSize(dns.FixedPacketSize)
|
||||
defer common.KeepAlive(_buffer)
|
||||
buffer := common.Dup(_buffer)
|
||||
@@ -115,11 +114,13 @@ func (d *DNS) NewPacketConnection(ctx context.Context, conn N.PacketConn, metada
|
||||
buffer.FullReset()
|
||||
destination, err := conn.ReadPacket(buffer)
|
||||
if err != nil {
|
||||
cancel(err)
|
||||
return err
|
||||
}
|
||||
var message mDNS.Msg
|
||||
err = message.Unpack(buffer.Bytes())
|
||||
if err != nil {
|
||||
cancel(err)
|
||||
return err
|
||||
}
|
||||
timeout.Update()
|
||||
@@ -127,17 +128,22 @@ func (d *DNS) NewPacketConnection(ctx context.Context, conn N.PacketConn, metada
|
||||
go func() error {
|
||||
response, err := d.router.Exchange(adapter.WithContext(ctx, &metadataInQuery), &message)
|
||||
if err != nil {
|
||||
cancel(err)
|
||||
return err
|
||||
}
|
||||
timeout.Update()
|
||||
responseBuffer := buf.NewPacket()
|
||||
n, err := response.PackBuffer(responseBuffer.FreeBytes())
|
||||
if err != nil {
|
||||
cancel(err)
|
||||
responseBuffer.Release()
|
||||
return err
|
||||
}
|
||||
responseBuffer.Truncate(len(n))
|
||||
err = conn.WritePacket(responseBuffer, destination)
|
||||
if err != nil {
|
||||
cancel(err)
|
||||
}
|
||||
return err
|
||||
}()
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package outbound
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
@@ -14,14 +15,14 @@ import (
|
||||
"github.com/sagernet/sing/common"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/sing/protocol/http"
|
||||
sHTTP "github.com/sagernet/sing/protocol/http"
|
||||
)
|
||||
|
||||
var _ adapter.Outbound = (*HTTP)(nil)
|
||||
|
||||
type HTTP struct {
|
||||
myOutboundAdapter
|
||||
client *http.Client
|
||||
client *sHTTP.Client
|
||||
}
|
||||
|
||||
func NewHTTP(router adapter.Router, logger log.ContextLogger, tag string, options option.HTTPOutboundOptions) (*HTTP, error) {
|
||||
@@ -29,6 +30,13 @@ func NewHTTP(router adapter.Router, logger log.ContextLogger, tag string, option
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var headers http.Header
|
||||
if options.Headers != nil {
|
||||
headers = make(http.Header)
|
||||
for key, values := range options.Headers {
|
||||
headers[key] = values
|
||||
}
|
||||
}
|
||||
return &HTTP{
|
||||
myOutboundAdapter{
|
||||
protocol: C.TypeHTTP,
|
||||
@@ -37,7 +45,14 @@ func NewHTTP(router adapter.Router, logger log.ContextLogger, tag string, option
|
||||
logger: logger,
|
||||
tag: tag,
|
||||
},
|
||||
http.NewClient(detour, options.ServerOptions.Build(), options.Username, options.Password, nil),
|
||||
sHTTP.NewClient(sHTTP.Options{
|
||||
Dialer: detour,
|
||||
Server: options.ServerOptions.Build(),
|
||||
Username: options.Username,
|
||||
Password: options.Password,
|
||||
Path: options.Path,
|
||||
Headers: headers,
|
||||
}),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"net"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
@@ -25,6 +26,7 @@ var (
|
||||
|
||||
type URLTest struct {
|
||||
myOutboundAdapter
|
||||
ctx context.Context
|
||||
tags []string
|
||||
link string
|
||||
interval time.Duration
|
||||
@@ -32,7 +34,7 @@ type URLTest struct {
|
||||
group *URLTestGroup
|
||||
}
|
||||
|
||||
func NewURLTest(router adapter.Router, logger log.ContextLogger, tag string, options option.URLTestOutboundOptions) (*URLTest, error) {
|
||||
func NewURLTest(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.URLTestOutboundOptions) (*URLTest, error) {
|
||||
outbound := &URLTest{
|
||||
myOutboundAdapter: myOutboundAdapter{
|
||||
protocol: C.TypeURLTest,
|
||||
@@ -40,6 +42,7 @@ func NewURLTest(router adapter.Router, logger log.ContextLogger, tag string, opt
|
||||
logger: logger,
|
||||
tag: tag,
|
||||
},
|
||||
ctx: ctx,
|
||||
tags: options.Outbounds,
|
||||
link: options.URL,
|
||||
interval: time.Duration(options.Interval),
|
||||
@@ -67,11 +70,11 @@ func (s *URLTest) Start() error {
|
||||
}
|
||||
outbounds = append(outbounds, detour)
|
||||
}
|
||||
s.group = NewURLTestGroup(s.router, s.logger, outbounds, s.link, s.interval, s.tolerance)
|
||||
s.group = NewURLTestGroup(s.ctx, s.router, s.logger, outbounds, s.link, s.interval, s.tolerance)
|
||||
return s.group.Start()
|
||||
}
|
||||
|
||||
func (s URLTest) Close() error {
|
||||
func (s *URLTest) Close() error {
|
||||
return common.Close(
|
||||
common.PtrOrNil(s.group),
|
||||
)
|
||||
@@ -85,6 +88,10 @@ func (s *URLTest) All() []string {
|
||||
return s.tags
|
||||
}
|
||||
|
||||
func (s *URLTest) URLTest(ctx context.Context, link string) (map[string]uint16, error) {
|
||||
return s.group.URLTest(ctx, link)
|
||||
}
|
||||
|
||||
func (s *URLTest) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||
outbound := s.group.Select(network)
|
||||
conn, err := outbound.DialContext(ctx, network, destination)
|
||||
@@ -92,14 +99,7 @@ func (s *URLTest) DialContext(ctx context.Context, network string, destination M
|
||||
return conn, nil
|
||||
}
|
||||
s.logger.ErrorContext(ctx, err)
|
||||
go s.group.checkOutbounds()
|
||||
outbounds := s.group.Fallback(outbound)
|
||||
for _, fallback := range outbounds {
|
||||
conn, err = fallback.DialContext(ctx, network, destination)
|
||||
if err == nil {
|
||||
return conn, nil
|
||||
}
|
||||
}
|
||||
s.group.history.DeleteURLTestHistory(outbound.Tag())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -110,14 +110,7 @@ func (s *URLTest) ListenPacket(ctx context.Context, destination M.Socksaddr) (ne
|
||||
return conn, nil
|
||||
}
|
||||
s.logger.ErrorContext(ctx, err)
|
||||
go s.group.checkOutbounds()
|
||||
outbounds := s.group.Fallback(outbound)
|
||||
for _, fallback := range outbounds {
|
||||
conn, err = fallback.ListenPacket(ctx, destination)
|
||||
if err == nil {
|
||||
return conn, nil
|
||||
}
|
||||
}
|
||||
s.group.history.DeleteURLTestHistory(outbound.Tag())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -130,6 +123,7 @@ func (s *URLTest) NewPacketConnection(ctx context.Context, conn N.PacketConn, me
|
||||
}
|
||||
|
||||
type URLTestGroup struct {
|
||||
ctx context.Context
|
||||
router adapter.Router
|
||||
logger log.Logger
|
||||
outbounds []adapter.Outbound
|
||||
@@ -142,11 +136,7 @@ type URLTestGroup struct {
|
||||
close chan struct{}
|
||||
}
|
||||
|
||||
func NewURLTestGroup(router adapter.Router, logger log.Logger, outbounds []adapter.Outbound, link string, interval time.Duration, tolerance uint16) *URLTestGroup {
|
||||
if link == "" {
|
||||
//goland:noinspection HttpUrlsUsage
|
||||
link = "http://www.gstatic.com/generate_204"
|
||||
}
|
||||
func NewURLTestGroup(ctx context.Context, router adapter.Router, logger log.Logger, outbounds []adapter.Outbound, link string, interval time.Duration, tolerance uint16) *URLTestGroup {
|
||||
if interval == 0 {
|
||||
interval = C.DefaultURLTestInterval
|
||||
}
|
||||
@@ -160,6 +150,7 @@ func NewURLTestGroup(router adapter.Router, logger log.Logger, outbounds []adapt
|
||||
history = urltest.NewHistoryStorage()
|
||||
}
|
||||
return &URLTestGroup{
|
||||
ctx: ctx,
|
||||
router: router,
|
||||
logger: logger,
|
||||
outbounds: outbounds,
|
||||
@@ -249,8 +240,14 @@ func (g *URLTestGroup) loopCheck() {
|
||||
}
|
||||
|
||||
func (g *URLTestGroup) checkOutbounds() {
|
||||
b, _ := batch.New(context.Background(), batch.WithConcurrencyNum[any](10))
|
||||
_, _ = g.URLTest(g.ctx, g.link)
|
||||
}
|
||||
|
||||
func (g *URLTestGroup) URLTest(ctx context.Context, link string) (map[string]uint16, error) {
|
||||
b, _ := batch.New(ctx, batch.WithConcurrencyNum[any](10))
|
||||
checked := make(map[string]bool)
|
||||
result := make(map[string]uint16)
|
||||
var resultAccess sync.Mutex
|
||||
for _, detour := range g.outbounds {
|
||||
tag := detour.Tag()
|
||||
realTag := RealTag(detour)
|
||||
@@ -269,7 +266,7 @@ func (g *URLTestGroup) checkOutbounds() {
|
||||
b.Go(realTag, func() (any, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), C.TCPTimeout)
|
||||
defer cancel()
|
||||
t, err := urltest.URLTest(ctx, g.link, p)
|
||||
t, err := urltest.URLTest(ctx, link, p)
|
||||
if err != nil {
|
||||
g.logger.Debug("outbound ", tag, " unavailable: ", err)
|
||||
g.history.DeleteURLTestHistory(realTag)
|
||||
@@ -279,9 +276,13 @@ func (g *URLTestGroup) checkOutbounds() {
|
||||
Time: time.Now(),
|
||||
Delay: t,
|
||||
})
|
||||
resultAccess.Lock()
|
||||
result[tag] = t
|
||||
resultAccess.Unlock()
|
||||
}
|
||||
return nil, nil
|
||||
})
|
||||
}
|
||||
b.Wait()
|
||||
return result, nil
|
||||
}
|
||||
|
||||
@@ -660,7 +660,7 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad
|
||||
if metadata.InboundOptions.SniffEnabled {
|
||||
buffer := buf.NewPacket()
|
||||
buffer.FullReset()
|
||||
sniffMetadata, _ := sniff.PeekStream(ctx, conn, buffer, time.Duration(metadata.InboundOptions.SniffTimeout), sniff.StreamDomainNameQuery, sniff.TLSClientHello, sniff.HTTPHost)
|
||||
sniffMetadata, err := sniff.PeekStream(ctx, conn, buffer, time.Duration(metadata.InboundOptions.SniffTimeout), sniff.StreamDomainNameQuery, sniff.TLSClientHello, sniff.HTTPHost)
|
||||
if sniffMetadata != nil {
|
||||
metadata.Protocol = sniffMetadata.Protocol
|
||||
metadata.Domain = sniffMetadata.Domain
|
||||
@@ -675,6 +675,8 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad
|
||||
} else {
|
||||
r.logger.DebugContext(ctx, "sniffed protocol: ", metadata.Protocol)
|
||||
}
|
||||
} else if err != nil {
|
||||
r.logger.TraceContext(ctx, "sniffed no protocol: ", err)
|
||||
}
|
||||
if !buffer.IsEmpty() {
|
||||
conn = bufio.NewCachedConn(conn, buffer)
|
||||
|
||||
@@ -17,22 +17,30 @@ type NATPacketConn struct {
|
||||
func NewNATPacketConn(conn N.PacketConn, origin M.Socksaddr, destination M.Socksaddr) *NATPacketConn {
|
||||
return &NATPacketConn{
|
||||
PacketConn: conn,
|
||||
origin: origin,
|
||||
destination: destination,
|
||||
origin: socksaddrWithoutPort(origin),
|
||||
destination: socksaddrWithoutPort(destination),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *NATPacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) {
|
||||
destination, err = c.PacketConn.ReadPacket(buffer)
|
||||
if destination == c.origin {
|
||||
destination = c.destination
|
||||
if socksaddrWithoutPort(destination) == c.origin {
|
||||
destination = M.Socksaddr{
|
||||
Addr: c.destination.Addr,
|
||||
Fqdn: c.destination.Fqdn,
|
||||
Port: destination.Port,
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *NATPacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
|
||||
if destination == c.destination {
|
||||
destination = c.origin
|
||||
if socksaddrWithoutPort(destination) == c.destination {
|
||||
destination = M.Socksaddr{
|
||||
Addr: c.origin.Addr,
|
||||
Fqdn: c.origin.Fqdn,
|
||||
Port: destination.Port,
|
||||
}
|
||||
}
|
||||
return c.PacketConn.WritePacket(buffer, destination)
|
||||
}
|
||||
@@ -40,3 +48,8 @@ func (c *NATPacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr)
|
||||
func (c *NATPacketConn) Upstream() any {
|
||||
return c.PacketConn
|
||||
}
|
||||
|
||||
func socksaddrWithoutPort(destination M.Socksaddr) M.Socksaddr {
|
||||
destination.Port = 0
|
||||
return destination
|
||||
}
|
||||
|
||||
@@ -498,7 +498,12 @@ func (c *PacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
||||
return
|
||||
}
|
||||
n = copy(p, msg.Data)
|
||||
addr = M.ParseSocksaddrHostPort(msg.Host, msg.Port).UDPAddr()
|
||||
destination := M.ParseSocksaddrHostPort(msg.Host, msg.Port)
|
||||
if destination.IsFqdn() {
|
||||
addr = destination
|
||||
} else {
|
||||
addr = destination.UDPAddr()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -131,7 +131,11 @@ func (c *ClientPacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error)
|
||||
return
|
||||
}
|
||||
n = buffer.Len()
|
||||
addr = destination.UDPAddr()
|
||||
if destination.IsFqdn() {
|
||||
addr = destination
|
||||
} else {
|
||||
addr = destination.UDPAddr()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -46,11 +46,8 @@ func NewClientTransport(ctx context.Context, dialer N.Dialer, serverAddr M.Socks
|
||||
}
|
||||
switch options.Type {
|
||||
case C.V2RayTransportTypeHTTP:
|
||||
return v2rayhttp.NewClient(ctx, dialer, serverAddr, options.HTTPOptions, tlsConfig), nil
|
||||
return v2rayhttp.NewClient(ctx, dialer, serverAddr, options.HTTPOptions, tlsConfig)
|
||||
case C.V2RayTransportTypeGRPC:
|
||||
if tlsConfig == nil {
|
||||
return nil, C.ErrTLSRequired
|
||||
}
|
||||
return NewGRPCClient(ctx, dialer, serverAddr, options.GRPCOptions, tlsConfig)
|
||||
case C.V2RayTransportTypeWebsocket:
|
||||
return v2raywebsocket.NewClient(ctx, dialer, serverAddr, options.WebsocketOptions, tlsConfig), nil
|
||||
|
||||
@@ -37,7 +37,9 @@ type Client struct {
|
||||
func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayGRPCOptions, tlsConfig tls.Config) (adapter.V2RayClientTransport, error) {
|
||||
var dialOptions []grpc.DialOption
|
||||
if tlsConfig != nil {
|
||||
tlsConfig.SetNextProtos([]string{http2.NextProtoTLS})
|
||||
if len(tlsConfig.NextProtos()) == 0 {
|
||||
tlsConfig.SetNextProtos([]string{http2.NextProtoTLS})
|
||||
}
|
||||
dialOptions = append(dialOptions, grpc.WithTransportCredentials(NewTLSTransportCredentials(tlsConfig)))
|
||||
} else {
|
||||
dialOptions = append(dialOptions, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
@@ -102,10 +104,10 @@ func (c *Client) DialContext(ctx context.Context) (net.Conn, error) {
|
||||
return nil, err
|
||||
}
|
||||
client := NewGunServiceClient(clientConn).(GunServiceCustomNameClient)
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
ctx, cancel := common.ContextWithCancelCause(ctx)
|
||||
stream, err := client.TunCustomName(ctx, c.serviceName)
|
||||
if err != nil {
|
||||
cancel()
|
||||
cancel(err)
|
||||
return nil, err
|
||||
}
|
||||
return deadline.NewConn(NewGRPCConn(stream, cancel)), nil
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package v2raygrpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/common/baderror"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/rw"
|
||||
)
|
||||
|
||||
@@ -14,11 +14,11 @@ var _ net.Conn = (*GRPCConn)(nil)
|
||||
|
||||
type GRPCConn struct {
|
||||
GunService
|
||||
cancel context.CancelFunc
|
||||
cancel common.ContextCancelCauseFunc
|
||||
cache []byte
|
||||
}
|
||||
|
||||
func NewGRPCConn(service GunService, cancel context.CancelFunc) *GRPCConn {
|
||||
func NewGRPCConn(service GunService, cancel common.ContextCancelCauseFunc) *GRPCConn {
|
||||
if client, isClient := service.(GunService_TunClient); isClient {
|
||||
service = &clientConnWrapper{client}
|
||||
}
|
||||
@@ -37,6 +37,7 @@ func (c *GRPCConn) Read(b []byte) (n int, err error) {
|
||||
hunk, err := c.Recv()
|
||||
err = baderror.WrapGRPC(err)
|
||||
if err != nil {
|
||||
c.cancel(err)
|
||||
return
|
||||
}
|
||||
n = copy(b, hunk.Data)
|
||||
@@ -49,13 +50,14 @@ func (c *GRPCConn) Read(b []byte) (n int, err error) {
|
||||
func (c *GRPCConn) Write(b []byte) (n int, err error) {
|
||||
err = baderror.WrapGRPC(c.Send(&Hunk{Data: b}))
|
||||
if err != nil {
|
||||
c.cancel(err)
|
||||
return
|
||||
}
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
func (c *GRPCConn) Close() error {
|
||||
c.cancel()
|
||||
c.cancel(net.ErrClosed)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -10,10 +10,12 @@ import (
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/tls"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/bufio/deadline"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
|
||||
"golang.org/x/net/http2"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/keepalive"
|
||||
gM "google.golang.org/grpc/metadata"
|
||||
@@ -31,7 +33,9 @@ type Server struct {
|
||||
func NewServer(ctx context.Context, options option.V2RayGRPCOptions, tlsConfig tls.ServerConfig, handler N.TCPConnectionHandler) (*Server, error) {
|
||||
var serverOptions []grpc.ServerOption
|
||||
if tlsConfig != nil {
|
||||
tlsConfig.SetNextProtos([]string{"h2"})
|
||||
if !common.Contains(tlsConfig.NextProtos(), http2.NextProtoTLS) {
|
||||
tlsConfig.SetNextProtos(append([]string{"h2"}, tlsConfig.NextProtos()...))
|
||||
}
|
||||
serverOptions = append(serverOptions, grpc.Creds(NewTLSTransportCredentials(tlsConfig)))
|
||||
}
|
||||
if options.IdleTimeout > 0 {
|
||||
@@ -46,7 +50,7 @@ func NewServer(ctx context.Context, options option.V2RayGRPCOptions, tlsConfig t
|
||||
}
|
||||
|
||||
func (s *Server) Tun(server GunService_TunServer) error {
|
||||
ctx, cancel := context.WithCancel(s.ctx)
|
||||
ctx, cancel := common.ContextWithCancelCause(s.ctx)
|
||||
conn := NewGRPCConn(server, cancel)
|
||||
var metadata M.Metadata
|
||||
if remotePeer, loaded := peer.FromContext(server.Context()); loaded {
|
||||
|
||||
@@ -2,7 +2,6 @@ package v2raygrpclite
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -32,56 +31,57 @@ type Client struct {
|
||||
ctx context.Context
|
||||
dialer N.Dialer
|
||||
serverAddr M.Socksaddr
|
||||
transport http.RoundTripper
|
||||
transport *http2.Transport
|
||||
options option.V2RayGRPCOptions
|
||||
url *url.URL
|
||||
}
|
||||
|
||||
func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayGRPCOptions, tlsConfig tls.Config) adapter.V2RayClientTransport {
|
||||
var transport http.RoundTripper
|
||||
if tlsConfig == nil {
|
||||
transport = &http.Transport{
|
||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
return dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
||||
},
|
||||
}
|
||||
} else {
|
||||
tlsConfig.SetNextProtos([]string{http2.NextProtoTLS})
|
||||
transport = &http2.Transport{
|
||||
ReadIdleTimeout: time.Duration(options.IdleTimeout),
|
||||
PingTimeout: time.Duration(options.PingTimeout),
|
||||
DialTLSContext: func(ctx context.Context, network, addr string, cfg *tls.STDConfig) (net.Conn, error) {
|
||||
conn, err := dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tls.ClientHandshake(ctx, conn, tlsConfig)
|
||||
},
|
||||
}
|
||||
}
|
||||
return &Client{
|
||||
client := &Client{
|
||||
ctx: ctx,
|
||||
dialer: dialer,
|
||||
serverAddr: serverAddr,
|
||||
options: options,
|
||||
transport: transport,
|
||||
transport: &http2.Transport{
|
||||
ReadIdleTimeout: time.Duration(options.IdleTimeout),
|
||||
PingTimeout: time.Duration(options.PingTimeout),
|
||||
DisableCompression: true,
|
||||
},
|
||||
url: &url.URL{
|
||||
Scheme: "https",
|
||||
Host: serverAddr.String(),
|
||||
Path: fmt.Sprintf("/%s/Tun", url.QueryEscape(options.ServiceName)),
|
||||
Scheme: "https",
|
||||
Host: serverAddr.String(),
|
||||
Path: "/" + options.ServiceName + "/Tun",
|
||||
RawPath: "/" + url.PathEscape(options.ServiceName) + "/Tun",
|
||||
},
|
||||
}
|
||||
|
||||
if tlsConfig == nil {
|
||||
client.transport.DialTLSContext = func(ctx context.Context, network, addr string, cfg *tls.STDConfig) (net.Conn, error) {
|
||||
return dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
||||
}
|
||||
} else {
|
||||
if len(tlsConfig.NextProtos()) == 0 {
|
||||
tlsConfig.SetNextProtos([]string{http2.NextProtoTLS})
|
||||
}
|
||||
client.transport.DialTLSContext = func(ctx context.Context, network, addr string, cfg *tls.STDConfig) (net.Conn, error) {
|
||||
conn, err := dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tls.ClientHandshake(ctx, conn, tlsConfig)
|
||||
}
|
||||
}
|
||||
|
||||
return client
|
||||
}
|
||||
|
||||
func (c *Client) DialContext(ctx context.Context) (net.Conn, error) {
|
||||
pipeInReader, pipeInWriter := io.Pipe()
|
||||
request := &http.Request{
|
||||
Method: http.MethodPost,
|
||||
Body: pipeInReader,
|
||||
URL: c.url,
|
||||
Proto: "HTTP/2",
|
||||
ProtoMajor: 2,
|
||||
Header: defaultClientHeader,
|
||||
Method: http.MethodPost,
|
||||
Body: pipeInReader,
|
||||
URL: c.url,
|
||||
Header: defaultClientHeader,
|
||||
}
|
||||
request = request.WithContext(ctx)
|
||||
conn := newLateGunConn(pipeInWriter)
|
||||
@@ -97,6 +97,8 @@ func (c *Client) DialContext(ctx context.Context) (net.Conn, error) {
|
||||
}
|
||||
|
||||
func (c *Client) Close() error {
|
||||
v2rayhttp.CloseIdleConnections(c.transport)
|
||||
if c.transport != nil {
|
||||
v2rayhttp.CloseIdleConnections(c.transport)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -117,6 +117,7 @@ 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
|
||||
|
||||
@@ -2,10 +2,8 @@ package v2raygrpclite
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -46,13 +44,16 @@ func NewServer(ctx context.Context, options option.V2RayGRPCOptions, tlsConfig t
|
||||
server := &Server{
|
||||
tlsConfig: tlsConfig,
|
||||
handler: handler,
|
||||
path: fmt.Sprintf("/%s/Tun", url.QueryEscape(options.ServiceName)),
|
||||
path: "/" + options.ServiceName + "/Tun",
|
||||
h2Server: &http2.Server{
|
||||
IdleTimeout: time.Duration(options.IdleTimeout),
|
||||
},
|
||||
}
|
||||
server.httpServer = &http.Server{
|
||||
Handler: server,
|
||||
BaseContext: func(net.Listener) context.Context {
|
||||
return ctx
|
||||
},
|
||||
}
|
||||
server.h2cHandler = h2c.NewHandler(server, server.h2Server)
|
||||
return server, nil
|
||||
@@ -101,8 +102,8 @@ func (s *Server) fallbackRequest(ctx context.Context, writer http.ResponseWriter
|
||||
|
||||
func (s *Server) Serve(listener net.Listener) error {
|
||||
if s.tlsConfig != nil {
|
||||
if len(s.tlsConfig.NextProtos()) == 0 {
|
||||
s.tlsConfig.SetNextProtos([]string{http2.NextProtoTLS})
|
||||
if !common.Contains(s.tlsConfig.NextProtos(), http2.NextProtoTLS) {
|
||||
s.tlsConfig.SetNextProtos(append([]string{"h2"}, s.tlsConfig.NextProtos()...))
|
||||
}
|
||||
listener = aTLS.NewListener(listener, s.tlsConfig)
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
@@ -16,6 +15,7 @@ import (
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
sHTTP "github.com/sagernet/sing/protocol/http"
|
||||
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
@@ -34,7 +34,7 @@ type Client struct {
|
||||
headers http.Header
|
||||
}
|
||||
|
||||
func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayHTTPOptions, tlsConfig tls.Config) adapter.V2RayClientTransport {
|
||||
func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayHTTPOptions, tlsConfig tls.Config) (adapter.V2RayClientTransport, error) {
|
||||
var transport http.RoundTripper
|
||||
if tlsConfig == nil {
|
||||
transport = &http.Transport{
|
||||
@@ -43,7 +43,9 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt
|
||||
},
|
||||
}
|
||||
} else {
|
||||
tlsConfig.SetNextProtos([]string{http2.NextProtoTLS})
|
||||
if len(tlsConfig.NextProtos()) == 0 {
|
||||
tlsConfig.SetNextProtos([]string{http2.NextProtoTLS})
|
||||
}
|
||||
transport = &http2.Transport{
|
||||
ReadIdleTimeout: time.Duration(options.IdleTimeout),
|
||||
PingTimeout: time.Duration(options.PingTimeout),
|
||||
@@ -77,14 +79,15 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt
|
||||
}
|
||||
uri.Host = serverAddr.String()
|
||||
uri.Path = options.Path
|
||||
if !strings.HasPrefix(uri.Path, "/") {
|
||||
uri.Path = "/" + uri.Path
|
||||
err := sHTTP.URLSetPath(&uri, options.Path)
|
||||
if err != nil {
|
||||
return nil, E.New("failed to set path: " + err.Error())
|
||||
}
|
||||
for key, valueList := range options.Headers {
|
||||
client.headers[key] = valueList
|
||||
}
|
||||
client.url = &uri
|
||||
return client
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func (c *Client) DialContext(ctx context.Context) (net.Conn, error) {
|
||||
|
||||
@@ -70,6 +70,9 @@ func NewServer(ctx context.Context, options option.V2RayHTTPOptions, tlsConfig t
|
||||
Handler: server,
|
||||
ReadHeaderTimeout: C.TCPTimeout,
|
||||
MaxHeaderBytes: http.DefaultMaxHeaderBytes,
|
||||
BaseContext: func(net.Listener) context.Context {
|
||||
return ctx
|
||||
},
|
||||
}
|
||||
server.h2cHandler = h2c.NewHandler(server, server.h2Server)
|
||||
return server, nil
|
||||
@@ -102,12 +105,20 @@ func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
writer.(http.Flusher).Flush()
|
||||
|
||||
var metadata M.Metadata
|
||||
metadata.Source = sHttp.SourceAddress(request)
|
||||
if h, ok := writer.(http.Hijacker); ok {
|
||||
var requestBody *buf.Buffer
|
||||
if contentLength := int(request.ContentLength); contentLength > 0 {
|
||||
requestBody = buf.NewSize(contentLength)
|
||||
_, err := requestBody.ReadFullFrom(request.Body, contentLength)
|
||||
if err != nil {
|
||||
s.fallbackRequest(request.Context(), writer, request, 0, E.Cause(err, "read request"))
|
||||
return
|
||||
}
|
||||
}
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
writer.(http.Flusher).Flush()
|
||||
conn, reader, err := h.Hijack()
|
||||
if err != nil {
|
||||
s.fallbackRequest(request.Context(), writer, request, 0, E.Cause(err, "hijack conn"))
|
||||
@@ -122,8 +133,12 @@ func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
|
||||
}
|
||||
conn = bufio.NewCachedConn(conn, cache)
|
||||
}
|
||||
if requestBody != nil {
|
||||
conn = bufio.NewCachedConn(conn, requestBody)
|
||||
}
|
||||
s.handler.NewConnection(request.Context(), conn, metadata)
|
||||
} else {
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
conn := NewHTTP2Wrapper(&ServerHTTPConn{
|
||||
NewHTTPConn(request.Body, writer),
|
||||
writer.(http.Flusher),
|
||||
@@ -149,6 +164,11 @@ func (s *Server) fallbackRequest(ctx context.Context, writer http.ResponseWriter
|
||||
|
||||
func (s *Server) Serve(listener net.Listener) error {
|
||||
if s.tlsConfig != nil {
|
||||
if len(s.tlsConfig.NextProtos()) == 0 {
|
||||
s.tlsConfig.SetNextProtos([]string{http2.NextProtoTLS, "http/1.1"})
|
||||
} else if !common.Contains(s.tlsConfig.NextProtos(), http2.NextProtoTLS) {
|
||||
s.tlsConfig.SetNextProtos(append([]string{"h2"}, s.tlsConfig.NextProtos()...))
|
||||
}
|
||||
listener = aTLS.NewListener(listener, s.tlsConfig)
|
||||
}
|
||||
return s.httpServer.Serve(listener)
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
@@ -15,6 +14,7 @@ import (
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
sHTTP "github.com/sagernet/sing/protocol/http"
|
||||
"github.com/sagernet/websocket"
|
||||
)
|
||||
|
||||
@@ -58,8 +58,9 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt
|
||||
}
|
||||
uri.Host = serverAddr.String()
|
||||
uri.Path = options.Path
|
||||
if !strings.HasPrefix(uri.Path, "/") {
|
||||
uri.Path = "/" + uri.Path
|
||||
err := sHTTP.URLSetPath(&uri, options.Path)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
headers := make(http.Header)
|
||||
for key, value := range options.Headers {
|
||||
|
||||
@@ -69,6 +69,14 @@ func (c *WebsocketConn) SetDeadline(t time.Time) error {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
|
||||
func (c *WebsocketConn) SetReadDeadline(t time.Time) error {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
|
||||
func (c *WebsocketConn) SetWriteDeadline(t time.Time) error {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
|
||||
func (c *WebsocketConn) Upstream() any {
|
||||
return c.Conn.NetConn()
|
||||
}
|
||||
@@ -195,24 +203,15 @@ func (c *EarlyWebsocketConn) RemoteAddr() net.Addr {
|
||||
}
|
||||
|
||||
func (c *EarlyWebsocketConn) SetDeadline(t time.Time) error {
|
||||
if c.conn == nil {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
return c.conn.SetDeadline(t)
|
||||
return os.ErrInvalid
|
||||
}
|
||||
|
||||
func (c *EarlyWebsocketConn) SetReadDeadline(t time.Time) error {
|
||||
if c.conn == nil {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
return c.conn.SetReadDeadline(t)
|
||||
return os.ErrInvalid
|
||||
}
|
||||
|
||||
func (c *EarlyWebsocketConn) SetWriteDeadline(t time.Time) error {
|
||||
if c.conn == nil {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
return c.conn.SetWriteDeadline(t)
|
||||
return os.ErrInvalid
|
||||
}
|
||||
|
||||
func (c *EarlyWebsocketConn) Upstream() any {
|
||||
|
||||
@@ -53,6 +53,9 @@ func NewServer(ctx context.Context, options option.V2RayWebsocketOptions, tlsCon
|
||||
Handler: server,
|
||||
ReadHeaderTimeout: C.TCPTimeout,
|
||||
MaxHeaderBytes: http.DefaultMaxHeaderBytes,
|
||||
BaseContext: func(net.Listener) context.Context {
|
||||
return ctx
|
||||
},
|
||||
}
|
||||
return server, nil
|
||||
}
|
||||
|
||||
@@ -198,7 +198,11 @@ func (c *PacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
addr = c.destination.UDPAddr()
|
||||
if c.destination.IsFqdn() {
|
||||
addr = c.destination
|
||||
} else {
|
||||
addr = c.destination.UDPAddr()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -147,7 +147,11 @@ func (c *serverPacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
addr = c.destination.UDPAddr()
|
||||
if c.destination.IsFqdn() {
|
||||
addr = c.destination
|
||||
} else {
|
||||
addr = c.destination.UDPAddr()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user