Compare commits

...

31 Commits

Author SHA1 Message Date
世界
32875e7cbc Fix conntrack return pointer 2023-04-13 13:23:04 +08:00
世界
3de56620ce android: Remove Seq.Delete warning 2023-04-13 13:23:04 +08:00
世界
3651605d3b Use HTTPS URLTest source 2023-04-13 11:02:00 +08:00
世界
7d1174e545 Print sniff result 2023-04-13 09:03:52 +08:00
世界
bd9e6e5cd9 Fix processing domain address in packet 2023-04-12 20:49:45 +08:00
世界
ed37cb858b Update documentation 2023-04-12 16:17:01 +08:00
世界
62bcf22c26 Fix parsing query in http path 2023-04-12 15:58:03 +08:00
Xiaokang Wang (Shelikhoo)
84bd997742 Fix '?' at end of WebSocket path get escaped
This fix align sing-box's behaviour with V2Ray when it comes to processing ? at the end of WebSocket's path.
2023-04-12 15:58:03 +08:00
H1JK
a548e45ad7 Add BaseContext to http servers 2023-04-11 18:00:43 +08:00
H1JK
5c1de2bb06 Fix gRPC service name escape 2023-04-11 18:00:43 +08:00
Hellojack
e5f0add1ab Remove TLS requirement for gRPC client 2023-04-11 18:00:43 +08:00
PuerNya
70e47df295 Add headers option for HTTP outbound 2023-04-11 17:49:37 +08:00
世界
f20642d6fd Fix workflows 2023-04-11 17:44:24 +08:00
世界
73fa926b48 clash-api: Add Clash.Meta APIs 2023-04-11 17:44:24 +08:00
世界
5d9dce8078 Fix find process user 2023-04-11 14:40:10 +08:00
世界
e20e2d57c9 Fix set HTTP TLS ALPN 2023-04-11 14:40:10 +08:00
世界
25f31890ed deadline: Fix disable pipe 2023-04-11 14:13:22 +08:00
世界
194b36b987 Fix v2ray http transport server read request 2023-04-11 10:30:10 +08:00
世界
1e39196bc9 Update cancel context usage 2023-04-10 17:01:21 +08:00
世界
da82a41697 Fix write log to stderr 2023-04-10 08:48:58 +08:00
世界
aceb82a75e Fix fallback packet conn 2023-04-09 22:53:06 +08:00
世界
f2749bc29d platform: Add stderr redirect 2023-04-09 22:52:45 +08:00
世界
55afaa87da Fix write http status after response sent 2023-04-09 22:52:45 +08:00
世界
d77940ab39 Fix deadline not initialized 2023-04-09 22:52:45 +08:00
世界
1eea446e45 documentation: Update external-ui download fields 2023-04-09 15:44:19 +08:00
世界
19c6241e10 Update dependencies 2023-04-09 15:37:06 +08:00
世界
b290d0ed32 documentation: Update changelog 2023-04-09 15:06:20 +08:00
世界
2afe662646 clash api: download clash-dashboard if external-ui directory is empty 2023-04-09 12:39:33 +08:00
世界
107a9a3b51 Fix read deadline implementation 2023-04-09 12:39:33 +08:00
世界
3d0c64f523 Replace usages of uber/atomic 2023-04-09 12:39:33 +08:00
世界
422ca34ac2 Fix timeout error check 2023-04-08 12:25:51 +08:00
86 changed files with 1116 additions and 368 deletions

View File

@@ -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

View File

@@ -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:

View File

@@ -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

View File

@@ -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()

View File

@@ -9,7 +9,7 @@ import (
"github.com/sagernet/sing-box/log"
"github.com/gofrs/uuid"
"github.com/gofrs/uuid/v5"
"github.com/spf13/cobra"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
)

View File

@@ -0,0 +1,114 @@
package badversion
import (
"strconv"
"strings"
F "github.com/sagernet/sing/common/format"
)
type Version struct {
Major int
Minor int
Patch int
PreReleaseIdentifier string
PreReleaseVersion int
}
func (v Version) After(anotherVersion Version) bool {
if v.Major > anotherVersion.Major {
return true
} else if v.Major < anotherVersion.Major {
return false
}
if v.Minor > anotherVersion.Minor {
return true
} else if v.Minor < anotherVersion.Minor {
return false
}
if v.Patch > anotherVersion.Patch {
return true
} else if v.Patch < anotherVersion.Patch {
return false
}
if v.PreReleaseIdentifier == "" && anotherVersion.PreReleaseIdentifier != "" {
return true
} else if v.PreReleaseIdentifier != "" && anotherVersion.PreReleaseIdentifier == "" {
return false
}
if v.PreReleaseIdentifier != "" && anotherVersion.PreReleaseIdentifier != "" {
if v.PreReleaseIdentifier == "beta" && anotherVersion.PreReleaseIdentifier == "alpha" {
return true
} else if v.PreReleaseIdentifier == "alpha" && anotherVersion.PreReleaseIdentifier == "beta" {
return false
}
if v.PreReleaseVersion > anotherVersion.PreReleaseVersion {
return true
} else if v.PreReleaseVersion < anotherVersion.PreReleaseVersion {
return false
}
}
return false
}
func (v Version) String() string {
version := F.ToString(v.Major, ".", v.Minor, ".", v.Patch)
if v.PreReleaseIdentifier != "" {
version = F.ToString(version, "-", v.PreReleaseIdentifier, ".", v.PreReleaseVersion)
}
return version
}
func (v Version) BadString() string {
version := F.ToString(v.Major, ".", v.Minor)
if v.Patch > 0 {
version = F.ToString(version, ".", v.Patch)
}
if v.PreReleaseIdentifier != "" {
version = F.ToString(version, "-", v.PreReleaseIdentifier)
if v.PreReleaseVersion > 0 {
version = F.ToString(version, v.PreReleaseVersion)
}
}
return version
}
func Parse(versionName string) (version Version) {
if strings.HasPrefix(versionName, "v") {
versionName = versionName[1:]
}
if strings.Contains(versionName, "-") {
parts := strings.Split(versionName, "-")
versionName = parts[0]
identifier := parts[1]
if strings.Contains(identifier, ".") {
identifierParts := strings.Split(identifier, ".")
version.PreReleaseIdentifier = identifierParts[0]
if len(identifierParts) >= 2 {
version.PreReleaseVersion, _ = strconv.Atoi(identifierParts[1])
}
} else {
if strings.HasPrefix(identifier, "alpha") {
version.PreReleaseIdentifier = "alpha"
version.PreReleaseVersion, _ = strconv.Atoi(identifier[5:])
} else if strings.HasPrefix(identifier, "beta") {
version.PreReleaseIdentifier = "beta"
version.PreReleaseVersion, _ = strconv.Atoi(identifier[4:])
} else {
version.PreReleaseIdentifier = identifier
}
}
}
versionElements := strings.Split(versionName, ".")
versionLen := len(versionElements)
if versionLen >= 1 {
version.Major, _ = strconv.Atoi(versionElements[0])
}
if versionLen >= 2 {
version.Minor, _ = strconv.Atoi(versionElements[1])
}
if versionLen >= 3 {
version.Patch, _ = strconv.Atoi(versionElements[2])
}
return
}

View File

@@ -0,0 +1,17 @@
package badversion
import "github.com/sagernet/sing-box/common/json"
func (v Version) MarshalJSON() ([]byte, error) {
return json.Marshal(v.String())
}
func (v *Version) UnmarshalJSON(data []byte) error {
var version string
err := json.Unmarshal(data, &version)
if err != nil {
return err
}
*v = Parse(version)
return nil
}

View File

@@ -0,0 +1,18 @@
package badversion
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestCompareVersion(t *testing.T) {
t.Parallel()
require.Equal(t, "1.3.0-beta.1", Parse("v1.3.0-beta1").String())
require.Equal(t, "1.3-beta1", Parse("v1.3.0-beta.1").BadString())
require.True(t, Parse("1.3.0").After(Parse("1.3-beta1")))
require.True(t, Parse("1.3.0").After(Parse("1.3.0-beta1")))
require.True(t, Parse("1.3.0-beta1").After(Parse("1.3.0-alpha1")))
require.True(t, Parse("1.3.1").After(Parse("1.3.0")))
require.True(t, Parse("1.4").After(Parse("1.3")))
}

View File

@@ -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()

View File

@@ -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()

View File

@@ -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 {

View File

@@ -11,6 +11,7 @@ import (
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio"
"github.com/sagernet/sing/common/bufio/deadline"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
@@ -68,7 +69,7 @@ func (c *Client) DialContext(ctx context.Context, network string, destination M.
if err != nil {
return nil, err
}
return bufio.NewUnbindPacketConn(&ClientPacketConn{ExtendedConn: bufio.NewExtendedConn(stream), destination: destination}), nil
return bufio.NewBindPacketConn(deadline.NewPacketConn(bufio.NewNetPacketConn(&ClientPacketConn{ExtendedConn: bufio.NewExtendedConn(stream), destination: destination})), destination), nil
default:
return nil, E.Extend(N.ErrUnknownNetwork, network)
}
@@ -79,7 +80,7 @@ func (c *Client) ListenPacket(ctx context.Context, destination M.Socksaddr) (net
if err != nil {
return nil, err
}
return &ClientPacketAddrConn{ExtendedConn: bufio.NewExtendedConn(stream), destination: destination}, nil
return deadline.NewPacketConn(&ClientPacketAddrConn{ExtendedConn: bufio.NewExtendedConn(stream), destination: destination}), nil
}
func (c *Client) openStream() (net.Conn, error) {
@@ -413,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 {

View File

@@ -10,6 +10,7 @@ import (
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio"
"github.com/sagernet/sing/common/bufio/deadline"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
@@ -67,7 +68,7 @@ func newConnection(ctx context.Context, router adapter.Router, errorHandler E.Ha
logger.InfoContext(ctx, "inbound multiplex packet connection")
packetConn = &ServerPacketAddrConn{ExtendedConn: bufio.NewExtendedConn(stream)}
}
hErr := router.RoutePacketConnection(ctx, packetConn, metadata)
hErr := router.RoutePacketConnection(ctx, deadline.NewPacketConn(bufio.NewNetPacketConn(packetConn)), metadata)
stream.Close()
if hErr != nil {
errorHandler.NewError(ctx, hErr)

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -12,6 +12,7 @@ const dirName = "sing-box"
var (
basePath string
tempPath string
resourcePaths []string
)
@@ -22,10 +23,21 @@ func BasePath(name string) string {
return filepath.Join(basePath, name)
}
func CreateTemp(pattern string) (*os.File, error) {
if tempPath == "" {
tempPath = os.TempDir()
}
return os.CreateTemp(tempPath, pattern)
}
func SetBasePath(path string) {
basePath = path
}
func SetTempPath(path string) {
tempPath = path
}
func FindPath(name string) (string, bool) {
name = os.ExpandEnv(name)
if rw.FileExists(name) {

View File

@@ -1,3 +1,19 @@
#### 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
#### 1.3-beta2
* Download clash-dashboard if the specified Clash `external_ui` directory is empty
* Fix bugs and update dependencies
#### 1.3-beta1
* Add [DNS reverse mapping](/configuration/dns#reverse_mapping) support

View File

@@ -8,6 +8,8 @@
"clash_api": {
"external_controller": "127.0.0.1:9090",
"external_ui": "folder",
"external_ui_download_url": "",
"external_ui_download_detour": "",
"secret": "",
"default_mode": "rule",
"store_selected": false,
@@ -53,6 +55,18 @@ A relative path to the configuration directory or an absolute path to a
directory in which you put some static web resource. sing-box will then
serve it at `http://{{external-controller}}/ui`.
#### external_ui_download_url
ZIP download URL for the external UI, will be used if the specified `external_ui` directory is empty.
`https://github.com/MetaCubeX/Yacd-meta/archive/gh-pages.zip` will be used if empty.
#### external_ui_download_detour
The tag of the outbound to download the external UI.
Default outbound will be used if empty.
#### secret
Secret for the RESTful API (optional)

View File

@@ -8,6 +8,8 @@
"clash_api": {
"external_controller": "127.0.0.1:9090",
"external_ui": "folder",
"external_ui_download_url": "",
"external_ui_download_detour": "",
"secret": "",
"default_mode": "rule",
"store_selected": false,
@@ -51,6 +53,18 @@ RESTful web API 监听地址。如果为空,则禁用 Clash API。
到静态网页资源目录的相对路径或绝对路径。sing-box 会在 `http://{{external-controller}}/ui` 下提供它。
#### external_ui_download_url
静态网页资源的 ZIP 下载 URL如果指定的 `external_ui` 目录为空,将使用。
默认使用 `https://github.com/MetaCubeX/Yacd-meta/archive/gh-pages.zip`
#### external_ui_download_detour
用于下载静态网页资源的出站的标签。
如果为空,将使用默认出站。
#### secret
RESTful API 的密钥(可选)

View File

@@ -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).

View File

@@ -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)。

View File

@@ -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

View File

@@ -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

View 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
}
}
}
}

View 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)
}
}

View File

@@ -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)

View File

@@ -47,6 +47,10 @@ type Server struct {
storeFakeIP bool
cacheFilePath string
cacheFile adapter.ClashCacheFile
externalUI string
externalUIDownloadURL string
externalUIDownloadDetour string
}
func NewServer(router adapter.Router, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) {
@@ -59,11 +63,13 @@ func NewServer(router adapter.Router, logFactory log.ObservableFactory, options
Addr: options.ExternalController,
Handler: chiRouter,
},
trafficManager: trafficManager,
urlTestHistory: urltest.NewHistoryStorage(),
mode: strings.ToLower(options.DefaultMode),
storeSelected: options.StoreSelected,
storeFakeIP: options.StoreFakeIP,
trafficManager: trafficManager,
urlTestHistory: urltest.NewHistoryStorage(),
mode: strings.ToLower(options.DefaultMode),
storeSelected: options.StoreSelected,
storeFakeIP: options.StoreFakeIP,
externalUIDownloadURL: options.ExternalUIDownloadURL,
externalUIDownloadDetour: options.ExternalUIDownloadDetour,
}
if server.mode == "" {
server.mode = "rule"
@@ -103,10 +109,13 @@ 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))
chiRouter.Group(func(r chi.Router) {
fs := http.StripPrefix("/ui", http.FileServer(http.Dir(C.BasePath(os.ExpandEnv(options.ExternalUI)))))
fs := http.StripPrefix("/ui", http.FileServer(http.Dir(server.externalUI)))
r.Get("/ui", http.RedirectHandler("/ui/", http.StatusTemporaryRedirect).ServeHTTP)
r.Get("/ui/*", func(w http.ResponseWriter, r *http.Request) {
fs.ServeHTTP(w, r)
@@ -128,6 +137,7 @@ func (s *Server) PreStart() error {
}
func (s *Server) Start() error {
s.checkAndDownloadExternalUI()
listener, err := net.Listen("tcp", s.httpServer.Addr)
if err != nil {
return E.Cause(err, "external controller listen error")
@@ -398,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})
}

View File

@@ -0,0 +1,164 @@
package clashapi
import (
"archive/zip"
"context"
"io"
"net"
"net/http"
"os"
"path/filepath"
"strings"
"time"
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
func (s *Server) checkAndDownloadExternalUI() {
if s.externalUI == "" {
return
}
entries, err := os.ReadDir(s.externalUI)
if err != nil {
os.MkdirAll(s.externalUI, 0o755)
}
if len(entries) == 0 {
err = s.downloadExternalUI()
if err != nil {
s.logger.Error("download external ui error: ", err)
}
}
}
func (s *Server) downloadExternalUI() error {
var downloadURL string
if s.externalUIDownloadURL != "" {
downloadURL = s.externalUIDownloadURL
} else {
downloadURL = "https://github.com/MetaCubeX/Yacd-meta/archive/gh-pages.zip"
}
s.logger.Info("downloading external ui")
var detour adapter.Outbound
if s.externalUIDownloadDetour != "" {
outbound, loaded := s.router.Outbound(s.externalUIDownloadDetour)
if !loaded {
return E.New("detour outbound not found: ", s.externalUIDownloadDetour)
}
detour = outbound
} else {
detour = s.router.DefaultOutbound(N.NetworkTCP)
}
httpClient := &http.Client{
Transport: &http.Transport{
ForceAttemptHTTP2: true,
TLSHandshakeTimeout: 5 * time.Second,
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
return detour.DialContext(ctx, network, M.ParseSocksaddr(addr))
},
},
}
defer httpClient.CloseIdleConnections()
response, err := httpClient.Get(downloadURL)
if err != nil {
return err
}
defer response.Body.Close()
if response.StatusCode != http.StatusOK {
return E.New("download external ui failed: ", response.Status)
}
err = s.downloadZIP(filepath.Base(downloadURL), response.Body, s.externalUI)
if err != nil {
removeAllInDirectory(s.externalUI)
}
return err
}
func (s *Server) downloadZIP(name string, body io.Reader, output string) error {
tempFile, err := C.CreateTemp(name)
if err != nil {
return err
}
defer os.Remove(tempFile.Name())
_, err = io.Copy(tempFile, body)
tempFile.Close()
if err != nil {
return err
}
reader, err := zip.OpenReader(tempFile.Name())
if err != nil {
return err
}
defer reader.Close()
trimDir := zipIsInSingleDirectory(reader.File)
for _, file := range reader.File {
if file.FileInfo().IsDir() {
continue
}
pathElements := strings.Split(file.Name, "/")
if trimDir {
pathElements = pathElements[1:]
}
saveDirectory := output
if len(pathElements) > 1 {
saveDirectory = filepath.Join(saveDirectory, filepath.Join(pathElements[:len(pathElements)-1]...))
}
err = os.MkdirAll(saveDirectory, 0o755)
if err != nil {
return err
}
savePath := filepath.Join(saveDirectory, pathElements[len(pathElements)-1])
err = downloadZIPEntry(file, savePath)
if err != nil {
return err
}
}
return nil
}
func downloadZIPEntry(zipFile *zip.File, savePath string) error {
saveFile, err := os.Create(savePath)
if err != nil {
return err
}
defer saveFile.Close()
reader, err := zipFile.Open()
if err != nil {
return err
}
defer reader.Close()
return common.Error(io.Copy(saveFile, reader))
}
func removeAllInDirectory(directory string) {
dirEntries, err := os.ReadDir(directory)
if err != nil {
return
}
for _, dirEntry := range dirEntries {
os.RemoveAll(filepath.Join(directory, dirEntry.Name()))
}
}
func zipIsInSingleDirectory(files []*zip.File) bool {
var singleDirectory string
for _, file := range files {
if file.FileInfo().IsDir() {
continue
}
pathElements := strings.Split(file.Name, "/")
if len(pathElements) == 0 {
return false
}
if singleDirectory == "" {
singleDirectory = pathElements[0]
} else if singleDirectory != pathElements[0] {
return false
}
}
return true
}

View File

@@ -1,35 +1,33 @@
package trafficontrol
import (
"runtime"
"time"
"github.com/sagernet/sing-box/experimental/clashapi/compatible"
"go.uber.org/atomic"
"github.com/sagernet/sing/common/atomic"
)
type Manager struct {
connections compatible.Map[string, tracker]
uploadTemp *atomic.Int64
downloadTemp *atomic.Int64
uploadBlip *atomic.Int64
downloadBlip *atomic.Int64
uploadTotal *atomic.Int64
downloadTotal *atomic.Int64
ticker *time.Ticker
done chan struct{}
uploadTemp atomic.Int64
downloadTemp atomic.Int64
uploadBlip atomic.Int64
downloadBlip atomic.Int64
uploadTotal atomic.Int64
downloadTotal atomic.Int64
connections compatible.Map[string, tracker]
ticker *time.Ticker
done chan struct{}
// process *process.Process
memory uint64
}
func NewManager() *Manager {
manager := &Manager{
uploadTemp: atomic.NewInt64(0),
downloadTemp: atomic.NewInt64(0),
uploadBlip: atomic.NewInt64(0),
downloadBlip: atomic.NewInt64(0),
uploadTotal: atomic.NewInt64(0),
downloadTotal: atomic.NewInt64(0),
ticker: time.NewTicker(time.Second),
done: make(chan struct{}),
ticker: time.NewTicker(time.Second),
done: make(chan struct{}),
// process: &process.Process{Pid: int32(os.Getpid())},
}
go manager.handle()
return manager
@@ -64,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,
}
}
@@ -106,4 +112,5 @@ type Snapshot struct {
DownloadTotal int64 `json:"downloadTotal"`
UploadTotal int64 `json:"uploadTotal"`
Connections []tracker `json:"connections"`
Memory uint64 `json:"memory"`
}

View File

@@ -1,6 +1,7 @@
package trafficontrol
import (
"encoding/json"
"net"
"net/netip"
"time"
@@ -8,10 +9,10 @@ import (
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/experimental/trackerconn"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/atomic"
N "github.com/sagernet/sing/common/network"
"github.com/gofrs/uuid"
"go.uber.org/atomic"
"github.com/gofrs/uuid/v5"
)
type Metadata struct {
@@ -43,6 +44,19 @@ type trackerInfo struct {
RulePayload string `json:"rulePayload"`
}
func (t trackerInfo) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]any{
"id": t.UUID.String(),
"metadata": t.Metadata,
"upload": t.UploadTotal.Load(),
"download": t.DownloadTotal.Load(),
"start": t.Start,
"chains": t.Chain,
"rule": t.Rule,
"rulePayload": t.RulePayload,
})
}
type tcpTracker struct {
N.ExtendedConn `json:"-"`
*trackerInfo
@@ -97,8 +111,8 @@ func NewTCPTracker(conn net.Conn, manager *Manager, metadata Metadata, router ad
next = group.Now()
}
upload := atomic.NewInt64(0)
download := atomic.NewInt64(0)
upload := new(atomic.Int64)
download := new(atomic.Int64)
t := &tcpTracker{
ExtendedConn: trackerconn.NewHook(conn, func(n int64) {
@@ -184,8 +198,8 @@ func NewUDPTracker(conn N.PacketConn, manager *Manager, metadata Metadata, route
next = group.Now()
}
upload := atomic.NewInt64(0)
download := atomic.NewInt64(0)
upload := new(atomic.Int64)
download := new(atomic.Int64)
ut := &udpTracker{
PacketConn: trackerconn.NewHookPacket(conn, func(n int64) {

View File

@@ -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)
}
}
}()
}

View File

@@ -42,7 +42,7 @@ func (s *CommandServer) handleStatusConn(conn net.Conn) error {
}
select {
case <-ctx.Done():
return nil
return ctx.Err()
case <-ticker.C:
}
}

View File

@@ -0,0 +1,29 @@
//go:build darwin || linux
package libbox
import (
"os"
"golang.org/x/sys/unix"
)
var stderrFile *os.File
func RedirectStderr(path string) error {
if stats, err := os.Stat(path); err == nil && stats.Size() > 0 {
_ = os.Rename(path, path+".old")
}
outputFile, err := os.Create(path)
if err != nil {
return err
}
err = unix.Dup2(int(outputFile.Fd()), int(os.Stderr.Fd()))
if err != nil {
outputFile.Close()
os.Remove(outputFile.Name())
return err
}
stderrFile = outputFile
return nil
}

View File

@@ -10,6 +10,10 @@ func SetBasePath(path string) {
C.SetBasePath(path)
}
func SetTempPath(path string) {
C.SetTempPath(path)
}
func Version() string {
return C.Version
}

View File

@@ -3,11 +3,10 @@ package trackerconn
import (
"net"
"github.com/sagernet/sing/common/atomic"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio"
N "github.com/sagernet/sing/common/network"
"go.uber.org/atomic"
)
func New(conn net.Conn, readCounter []*atomic.Int64, writeCounter []*atomic.Int64) *Conn {

View File

@@ -1,11 +1,10 @@
package trackerconn
import (
"github.com/sagernet/sing/common/atomic"
"github.com/sagernet/sing/common/buf"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"go.uber.org/atomic"
)
func NewPacket(conn N.PacketConn, readCounter []*atomic.Int64, writeCounter []*atomic.Int64) *PacketConn {

View File

@@ -12,10 +12,9 @@ import (
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/experimental/trackerconn"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common/atomic"
E "github.com/sagernet/sing/common/exceptions"
N "github.com/sagernet/sing/common/network"
"go.uber.org/atomic"
)
func init() {
@@ -211,7 +210,7 @@ func (s *StatsService) loadOrCreateCounter(name string) *atomic.Int64 {
if loaded {
return counter
}
counter = atomic.NewInt64(0)
counter = &atomic.Int64{}
s.counters[name] = counter
return counter
}

18
go.mod
View File

@@ -12,9 +12,9 @@ require (
github.com/go-chi/chi/v5 v5.0.8
github.com/go-chi/cors v1.2.1
github.com/go-chi/render v1.0.2
github.com/gofrs/uuid v4.4.0+incompatible
github.com/gofrs/uuid/v5 v5.0.0
github.com/hashicorp/yamux v0.1.1
github.com/insomniacslk/dhcp v0.0.0-20230327135226-74ae03f2425e
github.com/insomniacslk/dhcp v0.0.0-20230407062729-974c6f05fe16
github.com/logrusorgru/aurora v2.0.3+incompatible
github.com/mholt/acmez v1.1.0
github.com/miekg/dns v1.1.53
@@ -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.2-0.20230407053809-308e421e33c2
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.0
github.com/sagernet/sing-shadowtls v0.1.0
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.3
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
@@ -39,10 +39,9 @@ require (
github.com/spf13/cobra v1.7.0
github.com/stretchr/testify v1.8.2
go.etcd.io/bbolt v1.3.7
go.uber.org/atomic v1.10.0
go.uber.org/zap v1.24.0
go4.org/netipx v0.0.0-20230303233057-f1b76eb4bb35
golang.org/x/crypto v0.7.0
golang.org/x/crypto v0.8.0
golang.org/x/exp v0.0.0-20230321023759-10a507213a29
golang.org/x/net v0.9.0
golang.org/x/sys v0.7.0
@@ -82,6 +81,7 @@ require (
github.com/spf13/pflag v1.0.5 // indirect
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
go.uber.org/atomic v1.10.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
golang.org/x/mod v0.8.0 // indirect
golang.org/x/text v0.9.0 // indirect

34
go.sum
View File

@@ -33,8 +33,8 @@ github.com/go-chi/render v1.0.2 h1:4ER/udB0+fMWB2Jlf15RV3F4A2FDuYi/9f+lFttR/Lg=
github.com/go-chi/render v1.0.2/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gofrs/uuid/v5 v5.0.0 h1:p544++a97kEL+svbcFbCQVM9KFu0Yo25UoISXGNNH9M=
github.com/gofrs/uuid/v5 v5.0.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
@@ -51,8 +51,8 @@ github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbg
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/insomniacslk/dhcp v0.0.0-20230327135226-74ae03f2425e h1:8ChxkWKTVYg7LKBvYNLNRnlobgbPrzzossZUoST2T7o=
github.com/insomniacslk/dhcp v0.0.0-20230327135226-74ae03f2425e/go.mod h1:IKrnDWs3/Mqq5n0lI+RxA2sB7MvN/vbMBP3ehXg65UI=
github.com/insomniacslk/dhcp v0.0.0-20230407062729-974c6f05fe16 h1:+aAGyK41KRn8jbF2Q7PLL0Sxwg6dShGcQSeCC7nZQ8E=
github.com/insomniacslk/dhcp v0.0.0-20230407062729-974c6f05fe16/go.mod h1:IKrnDWs3/Mqq5n0lI+RxA2sB7MvN/vbMBP3ehXg65UI=
github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
@@ -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.2-0.20230407053809-308e421e33c2 h1:VjeHDxEgpB2fqK5G16yBvtLacibvg3h2MsIjal0UXH0=
github.com/sagernet/sing v0.2.2-0.20230407053809-308e421e33c2/go.mod h1:9uHswk2hITw8leDbiLS/xn0t9nzBcbePxzm9PJhwdlw=
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.0 h1:ILDWL7pwWfkPLEbviE/MyCgfjaBmJY/JVVY+5jhSb58=
github.com/sagernet/sing-shadowsocks v0.2.0/go.mod h1:ysYzszRLpNzJSorvlWRMuzU6Vchsp7sd52q+JNY4axw=
github.com/sagernet/sing-shadowtls v0.1.0 h1:05MYce8aR5xfKIn+y7xRFsdKhKt44QZTSEQW+lG5IWQ=
github.com/sagernet/sing-shadowtls v0.1.0/go.mod h1:Kn1VUIprdkwCgkS6SXYaLmIpKzQbqBIKJBMY+RvBhYc=
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.3 h1:q/+tsF46dvvapL6CpQBgPHJ6nQrDUZqEtLHCbsjO7iM=
github.com/sagernet/sing-vmess v0.1.3/go.mod h1:GVXqAHwe9U21uS+Voh4YBIrADQyE4F9v0ayGSixSQAE=
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=
@@ -168,8 +170,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug=
golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=

View File

@@ -10,11 +10,10 @@ import (
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/atomic"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"go.uber.org/atomic"
)
var _ adapter.Inbound = (*myInboundAdapter)(nil)

View File

@@ -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

View File

@@ -16,6 +16,7 @@ import (
"github.com/sagernet/sing-vmess/packetaddr"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/auth"
"github.com/sagernet/sing/common/bufio/deadline"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
M "github.com/sagernet/sing/common/metadata"
@@ -171,7 +172,7 @@ func (h *VLESS) newPacketConnection(ctx context.Context, conn N.PacketConn, meta
}
if metadata.Destination.Fqdn == packetaddr.SeqPacketMagicAddress {
metadata.Destination = M.Socksaddr{}
conn = packetaddr.NewConn(conn.(vmess.PacketConn), metadata.Destination)
conn = deadline.NewPacketConn(packetaddr.NewConn(conn.(vmess.PacketConn), metadata.Destination))
h.logger.InfoContext(ctx, "[", user, "] inbound packet addr connection")
} else {
h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination)

View File

@@ -15,6 +15,7 @@ import (
"github.com/sagernet/sing-vmess/packetaddr"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/auth"
"github.com/sagernet/sing/common/bufio/deadline"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
M "github.com/sagernet/sing/common/metadata"
@@ -179,7 +180,7 @@ func (h *VMess) newPacketConnection(ctx context.Context, conn N.PacketConn, meta
}
if metadata.Destination.Fqdn == packetaddr.SeqPacketMagicAddress {
metadata.Destination = M.Socksaddr{}
conn = packetaddr.NewConn(conn.(vmess.PacketConn), metadata.Destination)
conn = deadline.NewPacketConn(packetaddr.NewConn(conn.(vmess.PacketConn), metadata.Destination))
h.logger.InfoContext(ctx, "[", user, "] inbound packet addr connection")
} else {
h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination)

View File

@@ -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
}

View File

@@ -1,13 +1,15 @@
package option
type ClashAPIOptions struct {
ExternalController string `json:"external_controller,omitempty"`
ExternalUI string `json:"external_ui,omitempty"`
Secret string `json:"secret,omitempty"`
DefaultMode string `json:"default_mode,omitempty"`
StoreSelected bool `json:"store_selected,omitempty"`
StoreFakeIP bool `json:"store_fakeip,omitempty"`
CacheFile string `json:"cache_file,omitempty"`
ExternalController string `json:"external_controller,omitempty"`
ExternalUI string `json:"external_ui,omitempty"`
ExternalUIDownloadURL string `json:"external_ui_download_url,omitempty"`
ExternalUIDownloadDetour string `json:"external_ui_download_detour,omitempty"`
Secret string `json:"secret,omitempty"`
DefaultMode string `json:"default_mode,omitempty"`
StoreSelected bool `json:"store_selected,omitempty"`
StoreFakeIP bool `json:"store_fakeip,omitempty"`
CacheFile string `json:"cache_file,omitempty"`
}
type SelectorOutboundOptions struct {

View File

@@ -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"`
}

View File

@@ -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)
}

View File

@@ -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
}()
}

View File

@@ -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
}

View File

@@ -157,7 +157,7 @@ func (h *shadowsocksDialer) DialContext(ctx context.Context, network string, des
if err != nil {
return nil, err
}
return &bufio.BindPacketConn{PacketConn: h.method.DialPacketConn(outConn), Addr: destination}, nil
return bufio.NewBindPacketConn(h.method.DialPacketConn(outConn), destination), nil
default:
return nil, E.Extend(N.ErrUnknownNetwork, network)
}

View File

@@ -127,7 +127,7 @@ func (h *ShadowsocksR) DialContext(ctx context.Context, network string, destinat
if err != nil {
return nil, err
}
return &bufio.BindPacketConn{PacketConn: conn, Addr: destination}, nil
return bufio.NewBindPacketConn(conn, destination), nil
default:
return nil, E.Extend(N.ErrUnknownNetwork, network)
}

View File

@@ -131,7 +131,7 @@ func (h *trojanDialer) DialContext(ctx context.Context, network string, destinat
case N.NetworkTCP:
return trojan.NewClientConn(conn, h.key, destination), nil
case N.NetworkUDP:
return &bufio.BindPacketConn{PacketConn: trojan.NewClientPacketConn(conn, h.key), Addr: destination}, nil
return bufio.NewBindPacketConn(trojan.NewClientPacketConn(conn, h.key), destination), nil
default:
return nil, E.Extend(N.ErrUnknownNetwork, network)
}

View File

@@ -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
}

View File

@@ -111,7 +111,7 @@ func (h *VLESS) DialContext(ctx context.Context, network string, destination M.S
if err != nil {
return nil, err
}
return &bufio.BindPacketConn{PacketConn: packetaddr.NewConn(packetConn, destination), Addr: destination}, nil
return bufio.NewBindPacketConn(packetaddr.NewConn(packetConn, destination), destination), nil
} else {
return h.client.DialEarlyPacketConn(conn, destination)
}

View File

@@ -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)

View File

@@ -7,14 +7,14 @@ require github.com/sagernet/sing-box v0.0.0
replace github.com/sagernet/sing-box => ../
require (
github.com/docker/docker v20.10.18+incompatible
github.com/docker/docker v23.0.3+incompatible
github.com/docker/go-connections v0.4.0
github.com/gofrs/uuid v4.4.0+incompatible
github.com/sagernet/sing v0.2.2-0.20230407053809-308e421e33c2
github.com/sagernet/sing-shadowsocks v0.2.0
github.com/spyzhov/ajson v0.7.1
github.com/gofrs/uuid/v5 v5.0.0
github.com/sagernet/sing v0.2.2
github.com/sagernet/sing-shadowsocks v0.2.1-0.20230408141421-e40d6a4e42d4
github.com/spyzhov/ajson v0.8.0
github.com/stretchr/testify v1.8.2
go.uber.org/goleak v1.2.0
go.uber.org/goleak v1.2.1
golang.org/x/net v0.9.0
)
@@ -42,7 +42,7 @@ require (
github.com/google/btree v1.0.1 // indirect
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
github.com/hashicorp/yamux v0.1.1 // indirect
github.com/insomniacslk/dhcp v0.0.0-20230327135226-74ae03f2425e // indirect
github.com/insomniacslk/dhcp v0.0.0-20230407062729-974c6f05fe16 // indirect
github.com/josharian/native v1.1.0 // indirect
github.com/klauspost/compress v1.15.15 // indirect
github.com/klauspost/cpuid/v2 v2.1.1 // indirect
@@ -70,16 +70,15 @@ require (
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 // indirect
github.com/sagernet/quic-go v0.0.0-20230202071646-a8c8afb18b32 // indirect
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 // indirect
github.com/sagernet/sing-dns v0.1.5-0.20230407055526-2a27418e7855 // indirect
github.com/sagernet/sing-shadowtls v0.1.0 // indirect
github.com/sagernet/sing-dns v0.1.5-0.20230408004833-5adaf486d440 // indirect
github.com/sagernet/sing-shadowtls v0.1.1-0.20230408141548-81d74d2a8661 // indirect
github.com/sagernet/sing-tun v0.1.4-0.20230326080954-8848c0e4cbab // indirect
github.com/sagernet/sing-vmess v0.1.3 // indirect
github.com/sagernet/sing-vmess v0.1.4-0.20230409073451-6921c3dd77c7 // indirect
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 // indirect
github.com/sagernet/tfo-go v0.0.0-20230303015439-ffcfd8c41cf9 // indirect
github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2 // indirect
github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e // indirect
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c // indirect
github.com/sirupsen/logrus v1.9.0 // indirect
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
go.etcd.io/bbolt v1.3.7 // indirect
@@ -87,7 +86,7 @@ require (
go.uber.org/multierr v1.6.0 // indirect
go.uber.org/zap v1.24.0 // indirect
go4.org/netipx v0.0.0-20230303233057-f1b76eb4bb35 // indirect
golang.org/x/crypto v0.7.0 // indirect
golang.org/x/crypto v0.8.0 // indirect
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect
golang.org/x/mod v0.8.0 // indirect
golang.org/x/sys v0.7.0 // indirect

View File

@@ -25,8 +25,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/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68=
github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v20.10.18+incompatible h1:SN84VYXTBNGn92T/QwIRPlum9zfemfitN7pbsp26WSc=
github.com/docker/docker v20.10.18+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v23.0.3+incompatible h1:9GhVsShNWz1hO//9BNg/dpMnZW25KydO4wtVxWAIbho=
github.com/docker/docker v23.0.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
@@ -43,8 +43,8 @@ github.com/go-chi/render v1.0.2 h1:4ER/udB0+fMWB2Jlf15RV3F4A2FDuYi/9f+lFttR/Lg=
github.com/go-chi/render v1.0.2/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gofrs/uuid/v5 v5.0.0 h1:p544++a97kEL+svbcFbCQVM9KFu0Yo25UoISXGNNH9M=
github.com/gofrs/uuid/v5 v5.0.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
@@ -61,8 +61,8 @@ github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLe
github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/insomniacslk/dhcp v0.0.0-20230327135226-74ae03f2425e h1:8ChxkWKTVYg7LKBvYNLNRnlobgbPrzzossZUoST2T7o=
github.com/insomniacslk/dhcp v0.0.0-20230327135226-74ae03f2425e/go.mod h1:IKrnDWs3/Mqq5n0lI+RxA2sB7MvN/vbMBP3ehXg65UI=
github.com/insomniacslk/dhcp v0.0.0-20230407062729-974c6f05fe16 h1:+aAGyK41KRn8jbF2Q7PLL0Sxwg6dShGcQSeCC7nZQ8E=
github.com/insomniacslk/dhcp v0.0.0-20230407062729-974c6f05fe16/go.mod h1:IKrnDWs3/Mqq5n0lI+RxA2sB7MvN/vbMBP3ehXg65UI=
github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
@@ -126,18 +126,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.2-0.20230407053809-308e421e33c2 h1:VjeHDxEgpB2fqK5G16yBvtLacibvg3h2MsIjal0UXH0=
github.com/sagernet/sing v0.2.2-0.20230407053809-308e421e33c2/go.mod h1:9uHswk2hITw8leDbiLS/xn0t9nzBcbePxzm9PJhwdlw=
github.com/sagernet/sing-dns v0.1.5-0.20230407055526-2a27418e7855 h1:a3W2X1n5C/oYGp/Dd26eoymME3iXN8TJq7LZtO2MSUY=
github.com/sagernet/sing-dns v0.1.5-0.20230407055526-2a27418e7855/go.mod h1:69PNSHyEmXdjf6C+bXBOdr2GQnPeEyWjIzo/MV8gmz8=
github.com/sagernet/sing-shadowsocks v0.2.0 h1:ILDWL7pwWfkPLEbviE/MyCgfjaBmJY/JVVY+5jhSb58=
github.com/sagernet/sing-shadowsocks v0.2.0/go.mod h1:ysYzszRLpNzJSorvlWRMuzU6Vchsp7sd52q+JNY4axw=
github.com/sagernet/sing-shadowtls v0.1.0 h1:05MYce8aR5xfKIn+y7xRFsdKhKt44QZTSEQW+lG5IWQ=
github.com/sagernet/sing-shadowtls v0.1.0/go.mod h1:Kn1VUIprdkwCgkS6SXYaLmIpKzQbqBIKJBMY+RvBhYc=
github.com/sagernet/sing v0.2.2 h1:qfEdSLuwFgIIkeLOcwVQkVDzKLHtclXb93Ql0zZA+aE=
github.com/sagernet/sing v0.2.2/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.20230408141421-e40d6a4e42d4 h1:mKpXBBnAhTy9/CvDKqt5cN78LKX8cVUqjoWEXI/g0No=
github.com/sagernet/sing-shadowsocks v0.2.1-0.20230408141421-e40d6a4e42d4/go.mod h1:NFEROpOEiLG+lEoSPNpSL2yDyXx6q0OJvwWnEAd40cI=
github.com/sagernet/sing-shadowtls v0.1.1-0.20230408141548-81d74d2a8661 h1:QnV79JbJbJGT0MJJfd8o7QMEfRu3eUVKsmahxFMonrc=
github.com/sagernet/sing-shadowtls v0.1.1-0.20230408141548-81d74d2a8661/go.mod h1:xCeSRP8cV32aPsY+6BbRdJjyD6q8ufdKwhgqxEbU/3U=
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.3 h1:q/+tsF46dvvapL6CpQBgPHJ6nQrDUZqEtLHCbsjO7iM=
github.com/sagernet/sing-vmess v0.1.3/go.mod h1:GVXqAHwe9U21uS+Voh4YBIrADQyE4F9v0ayGSixSQAE=
github.com/sagernet/sing-vmess v0.1.4-0.20230409073451-6921c3dd77c7 h1:ZArINfN+zcHMdZCeRFOm4rO3SWyvYuLg3VhWcA5zonc=
github.com/sagernet/sing-vmess v0.1.4-0.20230409073451-6921c3dd77c7/go.mod h1:A9iGfwYmAEmVg8bavkqQ7Hx7nr9Pc2oWMYDwbLIBDjY=
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=
@@ -149,10 +149,8 @@ github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e/go.mod h1:45TUl
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c h1:vK2wyt9aWYHHvNLWniwijBu/n4pySypiKRhN32u/JGo=
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c/go.mod h1:euOmN6O5kk9dQmgSS8Df4psAl3TCjxOz0NW60EWkSaI=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spyzhov/ajson v0.7.1 h1:1MDIlPc6x0zjNtpa7tDzRAyFAvRX+X8ZsvtYz5lZg6A=
github.com/spyzhov/ajson v0.7.1/go.mod h1:63V+CGM6f1Bu/p4nLIN8885ojBdt88TbLoSFzyqMuVA=
github.com/spyzhov/ajson v0.8.0 h1:sFXyMbi4Y/BKjrsfkUZHSjA2JM1184enheSjjoT/zCc=
github.com/spyzhov/ajson v0.8.0/go.mod h1:63V+CGM6f1Bu/p4nLIN8885ojBdt88TbLoSFzyqMuVA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
@@ -176,8 +174,8 @@ go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo=
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
@@ -189,11 +187,10 @@ golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaE
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug=
golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
@@ -228,7 +225,6 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

View File

@@ -9,7 +9,7 @@ import (
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-shadowsocks/shadowaead_2022"
"github.com/gofrs/uuid"
"github.com/gofrs/uuid/v5"
)
var muxProtocols = []mux.Protocol{

View File

@@ -8,7 +8,7 @@ import (
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
"github.com/gofrs/uuid"
"github.com/gofrs/uuid/v5"
"github.com/spyzhov/ajson"
"github.com/stretchr/testify/require"
)

View File

@@ -7,7 +7,7 @@ import (
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
"github.com/gofrs/uuid"
"github.com/gofrs/uuid/v5"
"github.com/stretchr/testify/require"
)

View File

@@ -8,7 +8,7 @@ import (
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
"github.com/gofrs/uuid"
"github.com/gofrs/uuid/v5"
"github.com/spyzhov/ajson"
"github.com/stretchr/testify/require"
)

View File

@@ -9,7 +9,7 @@ import (
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/transport/vless"
"github.com/gofrs/uuid"
"github.com/gofrs/uuid/v5"
"github.com/spyzhov/ajson"
"github.com/stretchr/testify/require"
)

View File

@@ -8,7 +8,7 @@ import (
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
"github.com/gofrs/uuid"
"github.com/gofrs/uuid/v5"
"github.com/spyzhov/ajson"
"github.com/stretchr/testify/require"
)

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -4,6 +4,8 @@ import (
"context"
"net"
"github.com/sagernet/sing/common/bufio"
"github.com/sagernet/sing/common/bufio/deadline"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
"github.com/sagernet/sing/common/rw"
@@ -53,7 +55,7 @@ func newMuxConnection0(ctx context.Context, stream net.Conn, metadata M.Metadata
case CommandTCP:
return handler.NewConnection(ctx, stream, metadata)
case CommandUDP:
return handler.NewPacketConnection(ctx, &PacketConn{stream}, metadata)
return handler.NewPacketConnection(ctx, deadline.NewPacketConn(bufio.NewNetPacketConn(&PacketConn{stream})), metadata)
default:
return E.New("unknown command ", command)
}

View File

@@ -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
}

View File

@@ -8,6 +8,7 @@ import (
"github.com/sagernet/sing/common/auth"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio"
"github.com/sagernet/sing/common/bufio/deadline"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
@@ -106,7 +107,7 @@ func (s *Service[K]) NewConnection(ctx context.Context, conn net.Conn, metadata
case CommandTCP:
return s.handler.NewConnection(ctx, conn, metadata)
case CommandUDP:
return s.handler.NewPacketConnection(ctx, &PacketConn{conn}, metadata)
return s.handler.NewPacketConnection(ctx, deadline.NewPacketConn(bufio.NewNetPacketConn(&PacketConn{conn})), metadata)
// case CommandMux:
default:
return HandleMuxConnection(ctx, conn, metadata, s.handler)

View File

@@ -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

View File

@@ -10,6 +10,7 @@ import (
"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"
@@ -36,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()))
@@ -101,11 +104,11 @@ 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 NewGRPCConn(stream, cancel), nil
return deadline.NewConn(NewGRPCConn(stream, cancel)), nil
}

View File

@@ -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
}

View File

@@ -10,9 +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"
@@ -30,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 {
@@ -45,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 {
@@ -62,7 +67,7 @@ func (s *Server) Tun(server GunService_TunServer) error {
}
}
}
go s.handler.NewConnection(ctx, conn, metadata)
go s.handler.NewConnection(ctx, deadline.NewConn(conn), metadata)
<-ctx.Done()
return nil
}

View File

@@ -2,7 +2,6 @@ package v2raygrpclite
import (
"context"
"fmt"
"io"
"net"
"net/http"
@@ -13,6 +12,7 @@ import (
"github.com/sagernet/sing-box/common/tls"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/transport/v2rayhttp"
"github.com/sagernet/sing/common/bufio/deadline"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
@@ -31,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)
@@ -92,10 +93,12 @@ func (c *Client) DialContext(ctx context.Context) (net.Conn, error) {
conn.setup(nil, err)
}
}()
return conn, nil
return deadline.NewConn(conn), nil
}
func (c *Client) Close() error {
v2rayhttp.CloseIdleConnections(c.transport)
if c.transport != nil {
v2rayhttp.CloseIdleConnections(c.transport)
}
return nil
}

View File

@@ -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
@@ -145,28 +146,13 @@ func (c *GunConn) RemoteAddr() net.Addr {
}
func (c *GunConn) SetDeadline(t time.Time) error {
if responseWriter, loaded := c.writer.(interface {
SetWriteDeadline(time.Time) error
}); loaded {
return responseWriter.SetWriteDeadline(t)
}
return os.ErrInvalid
}
func (c *GunConn) SetReadDeadline(t time.Time) error {
if responseWriter, loaded := c.writer.(interface {
SetReadDeadline(time.Time) error
}); loaded {
return responseWriter.SetReadDeadline(t)
}
return os.ErrInvalid
}
func (c *GunConn) SetWriteDeadline(t time.Time) error {
if responseWriter, loaded := c.writer.(interface {
SetWriteDeadline(time.Time) error
}); loaded {
return responseWriter.SetWriteDeadline(t)
}
return os.ErrInvalid
}

View File

@@ -2,10 +2,8 @@ package v2raygrpclite
import (
"context"
"fmt"
"net"
"net/http"
"net/url"
"os"
"strings"
"time"
@@ -15,6 +13,7 @@ import (
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/transport/v2rayhttp"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/bufio/deadline"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
@@ -45,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
@@ -80,7 +82,7 @@ func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
var metadata M.Metadata
metadata.Source = sHttp.SourceAddress(request)
conn := v2rayhttp.NewHTTP2Wrapper(newGunConn(request.Body, writer, writer.(http.Flusher)))
s.handler.NewConnection(request.Context(), conn, metadata)
s.handler.NewConnection(request.Context(), deadline.NewConn(conn), metadata)
conn.CloseWrapper()
}
@@ -92,14 +94,16 @@ func (s *Server) fallbackRequest(ctx context.Context, writer http.ResponseWriter
} else if fErr == os.ErrInvalid {
fErr = nil
}
writer.WriteHeader(statusCode)
if statusCode > 0 {
writer.WriteHeader(statusCode)
}
s.handler.NewError(request.Context(), E.Cause(E.Errors(err, E.Cause(fErr, "fallback connection")), "process connection from ", request.RemoteAddr))
}
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)
}

View File

@@ -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) {

View File

@@ -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,28 +105,40 @@ 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, http.StatusInternalServerError, E.Cause(err, "hijack conn"))
s.fallbackRequest(request.Context(), writer, request, 0, E.Cause(err, "hijack conn"))
return
}
if cacheLen := reader.Reader.Buffered(); cacheLen > 0 {
cache := buf.NewSize(cacheLen)
_, err = cache.ReadFullFrom(reader.Reader, cacheLen)
if err != nil {
s.fallbackRequest(request.Context(), writer, request, http.StatusInternalServerError, E.Cause(err, "read cache"))
s.fallbackRequest(request.Context(), writer, request, 0, E.Cause(err, "read cache"))
return
}
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),
@@ -141,12 +156,19 @@ func (s *Server) fallbackRequest(ctx context.Context, writer http.ResponseWriter
} else if fErr == os.ErrInvalid {
fErr = nil
}
writer.WriteHeader(statusCode)
if statusCode > 0 {
writer.WriteHeader(statusCode)
}
s.handler.NewError(request.Context(), E.Cause(E.Errors(err, E.Cause(fErr, "fallback connection")), "process connection from ", request.RemoteAddr))
}
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)

View File

@@ -5,15 +5,16 @@ import (
"net"
"net/http"
"net/url"
"strings"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/tls"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common/bufio/deadline"
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"
)
@@ -57,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 {
@@ -77,11 +79,11 @@ func (c *Client) DialContext(ctx context.Context) (net.Conn, error) {
if c.maxEarlyData <= 0 {
conn, response, err := c.dialer.DialContext(ctx, c.uri, c.headers)
if err == nil {
return &WebsocketConn{Conn: conn, Writer: NewWriter(conn, false)}, nil
return deadline.NewConn(&WebsocketConn{Conn: conn, Writer: NewWriter(conn, false)}), nil
}
return nil, wrapDialError(response, err)
} else {
return &EarlyWebsocketConn{Client: c, ctx: ctx, create: make(chan struct{})}, nil
return deadline.NewConn(&EarlyWebsocketConn{Client: c, ctx: ctx, create: make(chan struct{})}), nil
}
}

View File

@@ -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 {

View File

@@ -16,6 +16,7 @@ import (
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio"
"github.com/sagernet/sing/common/bufio/deadline"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
@@ -52,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
}
@@ -95,7 +99,7 @@ func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
}
wsConn, err := upgrader.Upgrade(writer, request, nil)
if err != nil {
s.fallbackRequest(request.Context(), writer, request, http.StatusBadRequest, E.Cause(err, "upgrade websocket connection"))
s.fallbackRequest(request.Context(), writer, request, 0, E.Cause(err, "upgrade websocket connection"))
return
}
var metadata M.Metadata
@@ -104,7 +108,7 @@ func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
if len(earlyData) > 0 {
conn = bufio.NewCachedConn(conn, buf.As(earlyData))
}
s.handler.NewConnection(request.Context(), conn, metadata)
s.handler.NewConnection(request.Context(), deadline.NewConn(conn), metadata)
}
func (s *Server) fallbackRequest(ctx context.Context, writer http.ResponseWriter, request *http.Request, statusCode int, err error) {
@@ -115,7 +119,9 @@ func (s *Server) fallbackRequest(ctx context.Context, writer http.ResponseWriter
} else if fErr == os.ErrInvalid {
fErr = nil
}
writer.WriteHeader(statusCode)
if statusCode > 0 {
writer.WriteHeader(statusCode)
}
s.handler.NewError(request.Context(), E.Cause(E.Errors(err, E.Cause(fErr, "fallback connection")), "process connection from ", request.RemoteAddr))
}

View File

@@ -8,12 +8,14 @@ import (
"github.com/sagernet/sing-vmess"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio"
"github.com/sagernet/sing/common/bufio/deadline"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/gofrs/uuid"
"github.com/gofrs/uuid/v5"
)
type Client struct {
@@ -46,30 +48,30 @@ func (c *Client) prepareConn(conn net.Conn) (net.Conn, error) {
return conn, nil
}
func (c *Client) DialConn(conn net.Conn, destination M.Socksaddr) (*Conn, error) {
func (c *Client) DialConn(conn net.Conn, destination M.Socksaddr) (net.Conn, error) {
vConn, err := c.prepareConn(conn)
if err != nil {
return nil, err
}
serverConn := &Conn{Conn: conn, protocolConn: vConn, key: c.key, command: vmess.CommandTCP, destination: destination, flow: c.flow}
return serverConn, common.Error(serverConn.Write(nil))
return deadline.NewConn(serverConn), common.Error(serverConn.Write(nil))
}
func (c *Client) DialEarlyConn(conn net.Conn, destination M.Socksaddr) (*Conn, error) {
func (c *Client) DialEarlyConn(conn net.Conn, destination M.Socksaddr) (net.Conn, error) {
vConn, err := c.prepareConn(conn)
if err != nil {
return nil, err
}
return &Conn{Conn: conn, protocolConn: vConn, key: c.key, command: vmess.CommandTCP, destination: destination, flow: c.flow}, nil
return deadline.NewConn(&Conn{Conn: conn, protocolConn: vConn, key: c.key, command: vmess.CommandTCP, destination: destination, flow: c.flow}), nil
}
func (c *Client) DialPacketConn(conn net.Conn, destination M.Socksaddr) (*PacketConn, error) {
func (c *Client) DialPacketConn(conn net.Conn, destination M.Socksaddr) (vmess.PacketConn, error) {
serverConn := &PacketConn{Conn: conn, key: c.key, destination: destination, flow: c.flow}
return serverConn, common.Error(serverConn.Write(nil))
return bufio.NewBindPacketConn(deadline.NewPacketConn(bufio.NewPacketConn(serverConn)), destination), common.Error(serverConn.Write(nil))
}
func (c *Client) DialEarlyPacketConn(conn net.Conn, destination M.Socksaddr) (*PacketConn, error) {
return &PacketConn{Conn: conn, key: c.key, destination: destination, flow: c.flow}, nil
func (c *Client) DialEarlyPacketConn(conn net.Conn, destination M.Socksaddr) (vmess.PacketConn, error) {
return bufio.NewBindPacketConn(deadline.NewPacketConn(bufio.NewPacketConn(&PacketConn{Conn: conn, key: c.key, destination: destination, flow: c.flow})), destination), nil
}
func (c *Client) DialXUDPPacketConn(conn net.Conn, destination M.Socksaddr) (vmess.PacketConn, error) {
@@ -78,11 +80,11 @@ func (c *Client) DialXUDPPacketConn(conn net.Conn, destination M.Socksaddr) (vme
if err != nil {
return nil, err
}
return vmess.NewXUDPConn(serverConn, destination), nil
return bufio.NewBindPacketConn(deadline.NewPacketConn(vmess.NewXUDPConn(serverConn, destination)), destination), nil
}
func (c *Client) DialEarlyXUDPPacketConn(conn net.Conn, destination M.Socksaddr) (vmess.PacketConn, error) {
return vmess.NewXUDPConn(&Conn{Conn: conn, protocolConn: conn, key: c.key, command: vmess.CommandMux, destination: destination, flow: c.flow}, destination), nil
return bufio.NewBindPacketConn(deadline.NewPacketConn(vmess.NewXUDPConn(&Conn{Conn: conn, protocolConn: conn, key: c.key, command: vmess.CommandMux, destination: destination, flow: c.flow}, destination)), destination), nil
}
var _ N.EarlyConn = (*Conn)(nil)
@@ -196,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
}

View File

@@ -10,12 +10,13 @@ import (
"github.com/sagernet/sing/common/auth"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio"
"github.com/sagernet/sing/common/bufio/deadline"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/gofrs/uuid"
"github.com/gofrs/uuid/v5"
)
type Service[T comparable] struct {
@@ -89,7 +90,7 @@ func (s *Service[T]) NewConnection(ctx context.Context, conn net.Conn, metadata
case vmess.CommandTCP:
return s.handler.NewConnection(ctx, &serverConn{Conn: conn, responseWriter: responseWriter}, metadata)
case vmess.CommandUDP:
return s.handler.NewPacketConnection(ctx, &serverPacketConn{ExtendedConn: bufio.NewExtendedConn(conn), destination: request.Destination}, metadata)
return s.handler.NewPacketConnection(ctx, deadline.NewPacketConn(&serverPacketConn{ExtendedConn: bufio.NewExtendedConn(conn), destination: request.Destination}), metadata)
case vmess.CommandMux:
return vmess.HandleMuxConnection(ctx, &serverConn{Conn: conn, responseWriter: responseWriter}, s.handler)
default:
@@ -146,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
}