Compare commits

..

25 Commits

Author SHA1 Message Date
世界
1b680d4748 documentation: Update changelog 2023-04-14 19:24:16 +08:00
世界
d407b35b49 Update dependencies 2023-04-14 19:19:27 +08:00
世界
7f49c45673 urltest: Recheck after network change 2023-04-13 19:07:20 +08:00
世界
221cce6e57 Fix wireguard reconnect 2023-04-13 19:07:19 +08:00
世界
95809dce11 Fix deadline race 2023-04-13 19:07:19 +08:00
世界
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
62 changed files with 604 additions and 263 deletions

View File

@@ -31,12 +31,6 @@ jobs:
uses: actions/setup-go@v4 uses: actions/setup-go@v4
with: with:
go-version: ${{ steps.version.outputs.go_version }} 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 - name: Add cache to Go proxy
run: | run: |
version=`git rev-parse HEAD` version=`git rev-parse HEAD`
@@ -196,12 +190,6 @@ jobs:
uses: actions/setup-go@v4 uses: actions/setup-go@v4
with: with:
go-version: ${{ steps.version.outputs.go_version }} 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 - name: Build
id: build id: build
run: make run: make

View File

@@ -31,12 +31,6 @@ jobs:
uses: actions/setup-go@v4 uses: actions/setup-go@v4
with: with:
go-version: ${{ steps.version.outputs.go_version }} 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 - name: golangci-lint
uses: golangci/golangci-lint-action@v3 uses: golangci/golangci-lint-action@v3
with: with:

View File

@@ -77,13 +77,20 @@ test_stdio:
go mod tidy && \ go mod tidy && \
go test -v -tags "$(TAGS_TEST),force_stdio" . 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: 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: lib_install:
go get -v -d 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/gomobile@v0.0.0-20230413023804-244d7ff07035
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.0.0-20221130124640-349ebaa752ca go install -v github.com/sagernet/gomobile/cmd/gobind@v0.0.0-20230413023804-244d7ff07035
clean: clean:
rm -rf bin dist sing-box rm -rf bin dist sing-box

View File

@@ -35,6 +35,11 @@ type OutboundGroup interface {
All() []string All() []string
} }
type URLTestGroup interface {
OutboundGroup
URLTest(ctx context.Context, url string) (map[string]uint16, error)
}
func OutboundTag(detour Outbound) string { func OutboundTag(detour Outbound) string {
if group, isGroup := detour.(OutboundGroup); isGroup { if group, isGroup := detour.(OutboundGroup); isGroup {
return group.Now() return group.Now()

View File

@@ -12,7 +12,7 @@ type Conn struct {
element *list.Element[io.Closer] element *list.Element[io.Closer]
} }
func NewConn(conn net.Conn) (*Conn, error) { func NewConn(conn net.Conn) (net.Conn, error) {
connAccess.Lock() connAccess.Lock()
element := openConnection.PushBack(conn) element := openConnection.PushBack(conn)
connAccess.Unlock() connAccess.Unlock()

View File

@@ -12,7 +12,7 @@ type PacketConn struct {
element *list.Element[io.Closer] element *list.Element[io.Closer]
} }
func NewPacketConn(conn net.PacketConn) (*PacketConn, error) { func NewPacketConn(conn net.PacketConn) (net.PacketConn, error) {
connAccess.Lock() connAccess.Lock()
element := openConnection.PushBack(conn) element := openConnection.PushBack(conn)
connAccess.Unlock() connAccess.Unlock()

View File

@@ -73,7 +73,7 @@ func (d *ResolveDialer) ListenPacket(ctx context.Context, destination M.Socksadd
if err != nil { if err != nil {
return nil, err 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 { func (d *ResolveDialer) Upstream() any {

View File

@@ -414,7 +414,11 @@ func (c *ClientPacketAddrConn) ReadFrom(p []byte) (n int, addr net.Addr, err err
if err != nil { if err != nil {
return return
} }
addr = destination.UDPAddr() if destination.IsFqdn() {
addr = destination
} else {
addr = destination.UDPAddr()
}
var length uint16 var length uint16
err = binary.Read(c.ExtendedConn, binary.BigEndian, &length) err = binary.Read(c.ExtendedConn, binary.BigEndian, &length)
if err != nil { if err != nil {

View File

@@ -3,10 +3,12 @@ package process
import ( import (
"context" "context"
"net/netip" "net/netip"
"os/user"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-tun" "github.com/sagernet/sing-tun"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
) )
type Searcher interface { 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) { 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)) err := conn.SetReadDeadline(time.Now().Add(timeout))
if err != nil { if err != nil {
return nil, err return nil, E.Cause(err, "set read deadline")
} }
_, err = buffer.ReadOnceFrom(conn) _, err = buffer.ReadOnceFrom(conn)
err = E.Errors(err, conn.SetReadDeadline(time.Time{})) err = E.Errors(err, conn.SetReadDeadline(time.Time{}))
if err != nil { if err != nil {
return nil, err return nil, E.Cause(err, "read payload")
} }
var metadata *adapter.InboundContext var metadata *adapter.InboundContext
var errors []error var errors []error

View File

@@ -180,7 +180,7 @@ func NewSTDServer(ctx context.Context, router adapter.Router, logger log.Logger,
tlsConfig.ServerName = options.ServerName tlsConfig.ServerName = options.ServerName
} }
if len(options.ALPN) > 0 { if len(options.ALPN) > 0 {
tlsConfig.NextProtos = append(tlsConfig.NextProtos, options.ALPN...) tlsConfig.NextProtos = append(options.ALPN, tlsConfig.NextProtos...)
} }
if options.MinVersion != "" { if options.MinVersion != "" {
minVersion, err := ParseTLSVersion(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) { 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) linkURL, err := url.Parse(link)
if err != nil { if err != nil {
return return

View File

@@ -1,3 +1,16 @@
#### 1.3-beta6
* Fix WireGuard reconnect
* Perform URLTest recheck after network changes
* Fix bugs and update dependencies
#### 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 #### 1.3-beta4
* Fix bugs * Fix bugs

View File

@@ -59,7 +59,7 @@ serve it at `http://{{external-controller}}/ui`.
ZIP download URL for the external UI, will be used if the specified `external_ui` directory is empty. ZIP download URL for the external UI, will be used if the specified `external_ui` directory is empty.
`https://github.com/Dreamacro/clash-dashboard/archive/refs/heads/gh-pages.zip` will be used if empty. `https://github.com/MetaCubeX/Yacd-meta/archive/gh-pages.zip` will be used if empty.
#### external_ui_download_detour #### external_ui_download_detour

View File

@@ -57,7 +57,7 @@ RESTful web API 监听地址。如果为空,则禁用 Clash API。
静态网页资源的 ZIP 下载 URL如果指定的 `external_ui` 目录为空,将使用。 静态网页资源的 ZIP 下载 URL如果指定的 `external_ui` 目录为空,将使用。
默认使用 `https://github.com/Dreamacro/clash-dashboard/archive/refs/heads/gh-pages.zip` 默认使用 `https://github.com/MetaCubeX/Yacd-meta/archive/gh-pages.zip`
#### external_ui_download_detour #### external_ui_download_detour

View File

@@ -11,6 +11,8 @@
"server_port": 1080, "server_port": 1080,
"username": "sekai", "username": "sekai",
"password": "admin", "password": "admin",
"path": "",
"headers": {},
"tls": {}, "tls": {},
... // Dial Fields ... // Dial Fields
@@ -39,6 +41,14 @@ Basic authorization username.
Basic authorization password. Basic authorization password.
#### path
Path of HTTP request.
#### headers
Extra headers of HTTP request.
#### tls #### tls
TLS configuration, see [TLS](/configuration/shared/tls/#outbound). TLS configuration, see [TLS](/configuration/shared/tls/#outbound).

View File

@@ -11,6 +11,8 @@
"server_port": 1080, "server_port": 1080,
"username": "sekai", "username": "sekai",
"password": "admin", "password": "admin",
"path": "",
"headers": {},
"tls": {}, "tls": {},
... // 拨号字段 ... // 拨号字段
@@ -39,6 +41,14 @@ Basic 认证用户名。
Basic 认证密码。 Basic 认证密码。
#### path
HTTP 请求路径。
#### headers
HTTP 请求的额外标头。
#### tls #### tls
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#outbound)。 TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#outbound)。

View File

@@ -10,7 +10,7 @@
"proxy-b", "proxy-b",
"proxy-c" "proxy-c"
], ],
"url": "http://www.gstatic.com/generate_204", "url": "https://www.gstatic.com/generate_204",
"interval": "1m", "interval": "1m",
"tolerance": 50 "tolerance": 50
} }
@@ -26,7 +26,7 @@ List of outbound tags to test.
#### url #### 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 #### interval

View File

@@ -10,7 +10,7 @@
"proxy-b", "proxy-b",
"proxy-c" "proxy-c"
], ],
"url": "http://www.gstatic.com/generate_204", "url": "https://www.gstatic.com/generate_204",
"interval": "1m", "interval": "1m",
"tolerance": 50 "tolerance": 50
} }
@@ -26,7 +26,7 @@
#### url #### url
用于测试的链接。默认使用 `http://www.gstatic.com/generate_204` 用于测试的链接。默认使用 `https://www.gstatic.com/generate_204`
#### interval #### 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" "net/http"
"sort" "sort"
"strconv" "strconv"
"strings"
"time" "time"
"github.com/sagernet/sing-box/adapter" "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) { return func(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query() query := r.URL.Query()
url := query.Get("url") url := query.Get("url")
if strings.HasPrefix(url, "http://") {
url = ""
}
timeout, err := strconv.ParseInt(query.Get("timeout"), 10, 16) timeout, err := strconv.ParseInt(query.Get("timeout"), 10, 16)
if err != nil { if err != nil {
render.Status(r, http.StatusBadRequest) render.Status(r, http.StatusBadRequest)

View File

@@ -109,6 +109,8 @@ func NewServer(router adapter.Router, logFactory log.ObservableFactory, options
r.Mount("/profile", profileRouter()) r.Mount("/profile", profileRouter())
r.Mount("/cache", cacheRouter(router)) r.Mount("/cache", cacheRouter(router))
r.Mount("/dns", dnsRouter(router)) r.Mount("/dns", dnsRouter(router))
server.setupMetaAPI(r)
}) })
if options.ExternalUI != "" { if options.ExternalUI != "" {
server.externalUI = C.BasePath(os.ExpandEnv(options.ExternalUI)) server.externalUI = C.BasePath(os.ExpandEnv(options.ExternalUI))
@@ -406,5 +408,5 @@ func getLogs(logFactory log.ObservableFactory) func(w http.ResponseWriter, r *ht
} }
func version(w http.ResponseWriter, r *http.Request) { 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

@@ -40,7 +40,7 @@ func (s *Server) downloadExternalUI() error {
if s.externalUIDownloadURL != "" { if s.externalUIDownloadURL != "" {
downloadURL = s.externalUIDownloadURL downloadURL = s.externalUIDownloadURL
} else { } else {
downloadURL = "https://github.com/Dreamacro/clash-dashboard/archive/refs/heads/gh-pages.zip" downloadURL = "https://github.com/MetaCubeX/Yacd-meta/archive/gh-pages.zip"
} }
s.logger.Info("downloading external ui") s.logger.Info("downloading external ui")
var detour adapter.Outbound var detour adapter.Outbound

View File

@@ -1,6 +1,7 @@
package trafficontrol package trafficontrol
import ( import (
"runtime"
"time" "time"
"github.com/sagernet/sing-box/experimental/clashapi/compatible" "github.com/sagernet/sing-box/experimental/clashapi/compatible"
@@ -18,12 +19,15 @@ type Manager struct {
connections compatible.Map[string, tracker] connections compatible.Map[string, tracker]
ticker *time.Ticker ticker *time.Ticker
done chan struct{} done chan struct{}
// process *process.Process
memory uint64
} }
func NewManager() *Manager { func NewManager() *Manager {
manager := &Manager{ manager := &Manager{
ticker: time.NewTicker(time.Second), ticker: time.NewTicker(time.Second),
done: make(chan struct{}), done: make(chan struct{}),
// process: &process.Process{Pid: int32(os.Getpid())},
} }
go manager.handle() go manager.handle()
return manager return manager
@@ -58,10 +62,18 @@ func (m *Manager) Snapshot() *Snapshot {
return true 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{ return &Snapshot{
UploadTotal: m.uploadTotal.Load(), UploadTotal: m.uploadTotal.Load(),
DownloadTotal: m.downloadTotal.Load(), DownloadTotal: m.downloadTotal.Load(),
Connections: connections, Connections: connections,
Memory: m.memory,
} }
} }
@@ -100,4 +112,5 @@ type Snapshot struct {
DownloadTotal int64 `json:"downloadTotal"` DownloadTotal int64 `json:"downloadTotal"`
UploadTotal int64 `json:"uploadTotal"` UploadTotal int64 `json:"uploadTotal"`
Connections []tracker `json:"connections"` Connections []tracker `json:"connections"`
Memory uint64 `json:"memory"`
} }

View File

@@ -63,7 +63,7 @@ func (s *CommandServer) handleLogConn(conn net.Conn) error {
for { for {
select { select {
case <-ctx.Done(): case <-ctx.Done():
return nil return ctx.Err()
case message := <-subscription: case message := <-subscription:
err = writeLog(conn, []byte(message)) err = writeLog(conn, []byte(message))
if err != nil { if err != nil {

View File

@@ -9,6 +9,7 @@ import (
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/debug"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/observable" "github.com/sagernet/sing/common/observable"
"github.com/sagernet/sing/common/x/list" "github.com/sagernet/sing/common/x/list"
@@ -71,7 +72,9 @@ func (s *CommandServer) loopConnection(listener net.Listener) {
go func() { go func() {
hErr := s.handleConnection(conn) hErr := s.handleConnection(conn)
if hErr != nil && !E.IsClosed(err) { 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 { select {
case <-ctx.Done(): case <-ctx.Done():
return nil return ctx.Err()
case <-ticker.C: case <-ticker.C:
} }
} }

10
go.mod
View File

@@ -4,7 +4,7 @@ go 1.18
require ( require (
berty.tech/go-libtor v1.0.385 berty.tech/go-libtor v1.0.385
github.com/Dreamacro/clash v1.14.0 github.com/Dreamacro/clash v1.15.0
github.com/caddyserver/certmagic v0.17.2 github.com/caddyserver/certmagic v0.17.2
github.com/cretz/bine v0.2.0 github.com/cretz/bine v0.2.0
github.com/dustin/go-humanize v1.0.1 github.com/dustin/go-humanize v1.0.1
@@ -22,15 +22,15 @@ require (
github.com/oschwald/maxminddb-golang v1.10.0 github.com/oschwald/maxminddb-golang v1.10.0
github.com/pires/go-proxyproto v0.7.0 github.com/pires/go-proxyproto v0.7.0
github.com/sagernet/cloudflare-tls v0.0.0-20221031050923-d70792f4c3a0 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/quic-go v0.0.0-20230202071646-a8c8afb18b32
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691
github.com/sagernet/sing v0.2.3-0.20230409145127-a82d82e55914 github.com/sagernet/sing v0.2.3-0.20230413105021-520dd58fb01d
github.com/sagernet/sing-dns v0.1.5-0.20230408004833-5adaf486d440 github.com/sagernet/sing-dns v0.1.5-0.20230408004833-5adaf486d440
github.com/sagernet/sing-shadowsocks v0.2.1-0.20230409094647-5c830455eb9b github.com/sagernet/sing-shadowsocks v0.2.1-0.20230412123110-1a7c32b4e2e7
github.com/sagernet/sing-shadowtls v0.1.1-0.20230409094821-9abef019436f github.com/sagernet/sing-shadowtls v0.1.1-0.20230409094821-9abef019436f
github.com/sagernet/sing-tun v0.1.4-0.20230326080954-8848c0e4cbab github.com/sagernet/sing-tun v0.1.4-0.20230326080954-8848c0e4cbab
github.com/sagernet/sing-vmess v0.1.4-0.20230409094745-dc784c704250 github.com/sagernet/sing-vmess v0.1.4-0.20230412122845-9470e68f5e45
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37
github.com/sagernet/tfo-go v0.0.0-20230303015439-ffcfd8c41cf9 github.com/sagernet/tfo-go v0.0.0-20230303015439-ffcfd8c41cf9
github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2 github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2

20
go.sum
View File

@@ -1,7 +1,7 @@
berty.tech/go-libtor v1.0.385 h1:RWK94C3hZj6Z2GdvePpHJLnWYobFr3bY/OdUJ5aoEXw= berty.tech/go-libtor v1.0.385 h1:RWK94C3hZj6Z2GdvePpHJLnWYobFr3bY/OdUJ5aoEXw=
berty.tech/go-libtor v1.0.385/go.mod h1:9swOOQVb+kmvuAlsgWUK/4c52pm69AdbJsxLzk+fJEw= berty.tech/go-libtor v1.0.385/go.mod h1:9swOOQVb+kmvuAlsgWUK/4c52pm69AdbJsxLzk+fJEw=
github.com/Dreamacro/clash v1.14.0 h1:ehJ/C/1m9LEjmME72WSE/Y2YqbR3Q54AbjqiRCvtyW4= github.com/Dreamacro/clash v1.15.0 h1:mlpD950VEggXZBNahV66hyKDRxcczkj3vymoAt78KyE=
github.com/Dreamacro/clash v1.14.0/go.mod h1:ia2CU7V713H1QdCqMwOLK9U9V5Ay8X0voj3yQr2tk+I= github.com/Dreamacro/clash v1.15.0/go.mod h1:WNH69bN11LiAdgdSr4hpkEuXVMfBbWyhEKMCTx9BtNE=
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
@@ -101,8 +101,8 @@ 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/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 h1:5+m7c6AkmAylhauulqN/c5dnh8/KssrE9c93TQrXldA=
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61/go.mod h1:QUQ4RRHD6hGGHdFMEtR8T2P6GS6R3D/CXKdaYHKKXms= 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-20230413023804-244d7ff07035 h1:KttYh6bBhIw8Y6/Ljn7CGwC3CKZn788rzMJmeAKjY+8=
github.com/sagernet/gomobile v0.0.0-20221130124640-349ebaa752ca/go.mod h1:5YE39YkJkCcMsfq1jMKkjsrM2GfBoF9JVWnvU89hmvU= 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 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE=
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= 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= github.com/sagernet/quic-go v0.0.0-20230202071646-a8c8afb18b32 h1:tztuJB+giOWNRKQEBVY2oI3PsheTooMdh+/yxemYQYY=
@@ -111,18 +111,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/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.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.1.8/go.mod h1:jt1w2u7lJQFFSGLiRrRIs5YWmx4kAPfWuOejuDW9qMk=
github.com/sagernet/sing v0.2.3-0.20230409145127-a82d82e55914 h1:ReHDfZgEMKNVh0xvgvOc5j7M2kX9J+3nNQ8DrSwx0Hs= github.com/sagernet/sing v0.2.3-0.20230413105021-520dd58fb01d h1:+hp4necjzOvjg9dQwqmTfH/jAgCCnguVNQzIRUOpO00=
github.com/sagernet/sing v0.2.3-0.20230409145127-a82d82e55914/go.mod h1:Ta8nHnDLAwqySzKhGoKk4ZIB+vJ3GTKj7UPrWYvM+4w= github.com/sagernet/sing v0.2.3-0.20230413105021-520dd58fb01d/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 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-dns v0.1.5-0.20230408004833-5adaf486d440/go.mod h1:69PNSHyEmXdjf6C+bXBOdr2GQnPeEyWjIzo/MV8gmz8=
github.com/sagernet/sing-shadowsocks v0.2.1-0.20230409094647-5c830455eb9b h1:nmP+V4nlc8lqEMpwjjbny8ISkrFIjvKWIETsjs7nSic= github.com/sagernet/sing-shadowsocks v0.2.1-0.20230412123110-1a7c32b4e2e7 h1:3WDMIF1aE/twc5gJ+9PF2ZJqUxwZ80MPtNBKE3yBevU=
github.com/sagernet/sing-shadowsocks v0.2.1-0.20230409094647-5c830455eb9b/go.mod h1:WoVjGUvRqsx5yhYeDAB5CijCHpNDi0LUPHl3cf7u8Lc= github.com/sagernet/sing-shadowsocks v0.2.1-0.20230412123110-1a7c32b4e2e7/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 h1:qzQvpcDm60zPW8UlZa8UEaBoFORFeGAnhDncPc3VWT4=
github.com/sagernet/sing-shadowtls v0.1.1-0.20230409094821-9abef019436f/go.mod h1:MxB+Q9H0pAHcrlvNmwSs1crljRwHFFVhtXyOMBy44Nw= 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 h1:a9oeWuPBuIZ70qMhIIH6RrYhp886xN9jJIwsuu4ZFUo=
github.com/sagernet/sing-tun v0.1.4-0.20230326080954-8848c0e4cbab/go.mod h1:4YxIDEkkCjGXDOTMPw1SXpLmCQUFAWuaQN250oo+928= github.com/sagernet/sing-tun v0.1.4-0.20230326080954-8848c0e4cbab/go.mod h1:4YxIDEkkCjGXDOTMPw1SXpLmCQUFAWuaQN250oo+928=
github.com/sagernet/sing-vmess v0.1.4-0.20230409094745-dc784c704250 h1:Q5X89Z7wV201pLhPm+viAzzVgPcrYD1PniAVGt9Le/0= github.com/sagernet/sing-vmess v0.1.4-0.20230412122845-9470e68f5e45 h1:QqYhWah3u+o2tvLRuTfEu3BwsGpf/wNnVK/VNQV2YBM=
github.com/sagernet/sing-vmess v0.1.4-0.20230409094745-dc784c704250/go.mod h1:eULig3LgaeNiWSquSlzXF42Joypsj3fO1W+Qy93o6hk= github.com/sagernet/sing-vmess v0.1.4-0.20230412122845-9470e68f5e45/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 h1:HuE6xSwco/Xed8ajZ+coeYLmioq0Qp1/Z2zczFaV8as=
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37/go.mod h1:3skNSftZDJWTGVtVaM2jfbce8qHnmH/AGDRe62iNOg0= 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= github.com/sagernet/tfo-go v0.0.0-20230303015439-ffcfd8c41cf9 h1:2ItpW1nMNkPzmBTxV0/eClCklHrFSQMnUGcpUmJxVeE=

View File

@@ -90,6 +90,9 @@ func (n *Naive) Start() error {
n.httpServer = &http.Server{ n.httpServer = &http.Server{
Handler: n, Handler: n,
TLSConfig: tlsConfig, TLSConfig: tlsConfig,
BaseContext: func(listener net.Listener) context.Context {
return n.ctx
},
} }
go func() { go func() {
var sErr error var sErr error

View File

@@ -2,6 +2,7 @@ package ntp
import ( import (
"context" "context"
"os"
"time" "time"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
@@ -9,6 +10,7 @@ import (
"github.com/sagernet/sing-box/common/settings" "github.com/sagernet/sing-box/common/settings"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger" "github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
@@ -20,7 +22,7 @@ var _ adapter.TimeService = (*Service)(nil)
type Service struct { type Service struct {
ctx context.Context ctx context.Context
cancel context.CancelFunc cancel common.ContextCancelCauseFunc
server M.Socksaddr server M.Socksaddr
writeToSystem bool writeToSystem bool
dialer N.Dialer 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 { 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() server := options.ServerOptions.Build()
if server.Port == 0 { if server.Port == 0 {
server.Port = 123 server.Port = 123
@@ -64,7 +66,7 @@ func (s *Service) Start() error {
func (s *Service) Close() error { func (s *Service) Close() error {
s.ticker.Stop() s.ticker.Stop()
s.cancel() s.cancel(os.ErrClosed)
return nil return nil
} }

View File

@@ -27,7 +27,9 @@ type SocksOutboundOptions struct {
type HTTPOutboundOptions struct { type HTTPOutboundOptions struct {
DialerOptions DialerOptions
ServerOptions ServerOptions
Username string `json:"username,omitempty"` Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"` Password string `json:"password,omitempty"`
TLS *OutboundTLSOptions `json:"tls,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: case C.TypeSelector:
return NewSelector(router, logger, tag, options.SelectorOptions) return NewSelector(router, logger, tag, options.SelectorOptions)
case C.TypeURLTest: case C.TypeURLTest:
return NewURLTest(router, logger, tag, options.URLTestOptions) return NewURLTest(ctx, router, logger, tag, options.URLTestOptions)
default: default:
return nil, E.New("unknown outbound type: ", options.Type) return nil, E.New("unknown outbound type: ", options.Type)
} }

View File

@@ -39,6 +39,10 @@ func (a *myOutboundAdapter) Network() []string {
return a.network return a.network
} }
func (a *myOutboundAdapter) NewError(ctx context.Context, err error) {
NewError(a.logger, ctx, err)
}
func NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata adapter.InboundContext) error { func NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata adapter.InboundContext) error {
ctx = adapter.WithContext(ctx, &metadata) ctx = adapter.WithContext(ctx, &metadata)
var outConn net.Conn var outConn net.Conn
@@ -121,3 +125,12 @@ func CopyEarlyConn(ctx context.Context, conn net.Conn, serverConn net.Conn) erro
} }
return bufio.CopyConn(ctx, conn, serverConn) return bufio.CopyConn(ctx, conn, serverConn)
} }
func NewError(logger log.ContextLogger, ctx context.Context, err error) {
common.Close(err)
if E.IsClosedOrCanceled(err) {
logger.DebugContext(ctx, "connection closed: ", err)
return
}
logger.ErrorContext(ctx, err)
}

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 { func (d *DNS) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
ctx = adapter.WithContext(ctx, &metadata) ctx = adapter.WithContext(ctx, &metadata)
fastClose, cancel := context.WithCancel(ctx) fastClose, cancel := common.ContextWithCancelCause(ctx)
timeout := canceler.New(fastClose, cancel, C.DNSTimeout) timeout := canceler.New(fastClose, cancel, C.DNSTimeout)
var group task.Group var group task.Group
group.Append0(func(ctx context.Context) error { group.Append0(func(ctx context.Context) error {
defer cancel()
_buffer := buf.StackNewSize(dns.FixedPacketSize) _buffer := buf.StackNewSize(dns.FixedPacketSize)
defer common.KeepAlive(_buffer) defer common.KeepAlive(_buffer)
buffer := common.Dup(_buffer) buffer := common.Dup(_buffer)
@@ -115,11 +114,13 @@ func (d *DNS) NewPacketConnection(ctx context.Context, conn N.PacketConn, metada
buffer.FullReset() buffer.FullReset()
destination, err := conn.ReadPacket(buffer) destination, err := conn.ReadPacket(buffer)
if err != nil { if err != nil {
cancel(err)
return err return err
} }
var message mDNS.Msg var message mDNS.Msg
err = message.Unpack(buffer.Bytes()) err = message.Unpack(buffer.Bytes())
if err != nil { if err != nil {
cancel(err)
return err return err
} }
timeout.Update() timeout.Update()
@@ -127,17 +128,22 @@ func (d *DNS) NewPacketConnection(ctx context.Context, conn N.PacketConn, metada
go func() error { go func() error {
response, err := d.router.Exchange(adapter.WithContext(ctx, &metadataInQuery), &message) response, err := d.router.Exchange(adapter.WithContext(ctx, &metadataInQuery), &message)
if err != nil { if err != nil {
cancel(err)
return err return err
} }
timeout.Update() timeout.Update()
responseBuffer := buf.NewPacket() responseBuffer := buf.NewPacket()
n, err := response.PackBuffer(responseBuffer.FreeBytes()) n, err := response.PackBuffer(responseBuffer.FreeBytes())
if err != nil { if err != nil {
cancel(err)
responseBuffer.Release() responseBuffer.Release()
return err return err
} }
responseBuffer.Truncate(len(n)) responseBuffer.Truncate(len(n))
err = conn.WritePacket(responseBuffer, destination) err = conn.WritePacket(responseBuffer, destination)
if err != nil {
cancel(err)
}
return err return err
}() }()
} }

View File

@@ -3,6 +3,7 @@ package outbound
import ( import (
"context" "context"
"net" "net"
"net/http"
"os" "os"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
@@ -14,14 +15,14 @@ import (
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network" 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) var _ adapter.Outbound = (*HTTP)(nil)
type HTTP struct { type HTTP struct {
myOutboundAdapter myOutboundAdapter
client *http.Client client *sHTTP.Client
} }
func NewHTTP(router adapter.Router, logger log.ContextLogger, tag string, options option.HTTPOutboundOptions) (*HTTP, error) { 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 { if err != nil {
return nil, err 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{ return &HTTP{
myOutboundAdapter{ myOutboundAdapter{
protocol: C.TypeHTTP, protocol: C.TypeHTTP,
@@ -37,7 +45,14 @@ func NewHTTP(router adapter.Router, logger log.ContextLogger, tag string, option
logger: logger, logger: logger,
tag: tag, 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 }, nil
} }

View File

@@ -4,6 +4,7 @@ import (
"context" "context"
"net" "net"
"sort" "sort"
"sync"
"time" "time"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
@@ -19,12 +20,14 @@ import (
) )
var ( var (
_ adapter.Outbound = (*URLTest)(nil) _ adapter.Outbound = (*URLTest)(nil)
_ adapter.OutboundGroup = (*URLTest)(nil) _ adapter.OutboundGroup = (*URLTest)(nil)
_ adapter.InterfaceUpdateListener = (*URLTest)(nil)
) )
type URLTest struct { type URLTest struct {
myOutboundAdapter myOutboundAdapter
ctx context.Context
tags []string tags []string
link string link string
interval time.Duration interval time.Duration
@@ -32,7 +35,7 @@ type URLTest struct {
group *URLTestGroup 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{ outbound := &URLTest{
myOutboundAdapter: myOutboundAdapter{ myOutboundAdapter: myOutboundAdapter{
protocol: C.TypeURLTest, protocol: C.TypeURLTest,
@@ -40,6 +43,7 @@ func NewURLTest(router adapter.Router, logger log.ContextLogger, tag string, opt
logger: logger, logger: logger,
tag: tag, tag: tag,
}, },
ctx: ctx,
tags: options.Outbounds, tags: options.Outbounds,
link: options.URL, link: options.URL,
interval: time.Duration(options.Interval), interval: time.Duration(options.Interval),
@@ -67,11 +71,11 @@ func (s *URLTest) Start() error {
} }
outbounds = append(outbounds, detour) 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() return s.group.Start()
} }
func (s URLTest) Close() error { func (s *URLTest) Close() error {
return common.Close( return common.Close(
common.PtrOrNil(s.group), common.PtrOrNil(s.group),
) )
@@ -85,6 +89,10 @@ func (s *URLTest) All() []string {
return s.tags 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) { func (s *URLTest) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
outbound := s.group.Select(network) outbound := s.group.Select(network)
conn, err := outbound.DialContext(ctx, network, destination) conn, err := outbound.DialContext(ctx, network, destination)
@@ -92,14 +100,7 @@ func (s *URLTest) DialContext(ctx context.Context, network string, destination M
return conn, nil return conn, nil
} }
s.logger.ErrorContext(ctx, err) s.logger.ErrorContext(ctx, err)
go s.group.checkOutbounds() s.group.history.DeleteURLTestHistory(outbound.Tag())
outbounds := s.group.Fallback(outbound)
for _, fallback := range outbounds {
conn, err = fallback.DialContext(ctx, network, destination)
if err == nil {
return conn, nil
}
}
return nil, err return nil, err
} }
@@ -110,14 +111,7 @@ func (s *URLTest) ListenPacket(ctx context.Context, destination M.Socksaddr) (ne
return conn, nil return conn, nil
} }
s.logger.ErrorContext(ctx, err) s.logger.ErrorContext(ctx, err)
go s.group.checkOutbounds() s.group.history.DeleteURLTestHistory(outbound.Tag())
outbounds := s.group.Fallback(outbound)
for _, fallback := range outbounds {
conn, err = fallback.ListenPacket(ctx, destination)
if err == nil {
return conn, nil
}
}
return nil, err return nil, err
} }
@@ -129,7 +123,13 @@ func (s *URLTest) NewPacketConnection(ctx context.Context, conn N.PacketConn, me
return NewPacketConnection(ctx, s, conn, metadata) return NewPacketConnection(ctx, s, conn, metadata)
} }
func (s *URLTest) InterfaceUpdated() error {
go s.group.checkOutbounds()
return nil
}
type URLTestGroup struct { type URLTestGroup struct {
ctx context.Context
router adapter.Router router adapter.Router
logger log.Logger logger log.Logger
outbounds []adapter.Outbound outbounds []adapter.Outbound
@@ -142,11 +142,7 @@ type URLTestGroup struct {
close chan struct{} close chan struct{}
} }
func NewURLTestGroup(router adapter.Router, logger log.Logger, outbounds []adapter.Outbound, link string, interval time.Duration, tolerance uint16) *URLTestGroup { func NewURLTestGroup(ctx context.Context, 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"
}
if interval == 0 { if interval == 0 {
interval = C.DefaultURLTestInterval interval = C.DefaultURLTestInterval
} }
@@ -160,6 +156,7 @@ func NewURLTestGroup(router adapter.Router, logger log.Logger, outbounds []adapt
history = urltest.NewHistoryStorage() history = urltest.NewHistoryStorage()
} }
return &URLTestGroup{ return &URLTestGroup{
ctx: ctx,
router: router, router: router,
logger: logger, logger: logger,
outbounds: outbounds, outbounds: outbounds,
@@ -249,8 +246,14 @@ func (g *URLTestGroup) loopCheck() {
} }
func (g *URLTestGroup) checkOutbounds() { 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) checked := make(map[string]bool)
result := make(map[string]uint16)
var resultAccess sync.Mutex
for _, detour := range g.outbounds { for _, detour := range g.outbounds {
tag := detour.Tag() tag := detour.Tag()
realTag := RealTag(detour) realTag := RealTag(detour)
@@ -269,7 +272,7 @@ func (g *URLTestGroup) checkOutbounds() {
b.Go(realTag, func() (any, error) { b.Go(realTag, func() (any, error) {
ctx, cancel := context.WithTimeout(context.Background(), C.TCPTimeout) ctx, cancel := context.WithTimeout(context.Background(), C.TCPTimeout)
defer cancel() defer cancel()
t, err := urltest.URLTest(ctx, g.link, p) t, err := urltest.URLTest(ctx, link, p)
if err != nil { if err != nil {
g.logger.Debug("outbound ", tag, " unavailable: ", err) g.logger.Debug("outbound ", tag, " unavailable: ", err)
g.history.DeleteURLTestHistory(realTag) g.history.DeleteURLTestHistory(realTag)
@@ -279,9 +282,13 @@ func (g *URLTestGroup) checkOutbounds() {
Time: time.Now(), Time: time.Now(),
Delay: t, Delay: t,
}) })
resultAccess.Lock()
result[tag] = t
resultAccess.Unlock()
} }
return nil, nil return nil, nil
}) })
} }
b.Wait() b.Wait()
return result, nil
} }

View File

@@ -67,7 +67,7 @@ func NewWireGuard(ctx context.Context, router adapter.Router, logger log.Context
connectAddr = options.ServerOptions.Build() connectAddr = options.ServerOptions.Build()
} }
} }
outbound.bind = wireguard.NewClientBind(ctx, dialer.New(router, options.DialerOptions), isConnect, connectAddr, reserved) outbound.bind = wireguard.NewClientBind(ctx, outbound, dialer.New(router, options.DialerOptions), isConnect, connectAddr, reserved)
localPrefixes := common.Map(options.LocalAddress, option.ListenPrefix.Build) localPrefixes := common.Map(options.LocalAddress, option.ListenPrefix.Build)
if len(localPrefixes) == 0 { if len(localPrefixes) == 0 {
return nil, E.New("missing local address") return nil, E.New("missing local address")

View File

@@ -660,7 +660,7 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad
if metadata.InboundOptions.SniffEnabled { if metadata.InboundOptions.SniffEnabled {
buffer := buf.NewPacket() buffer := buf.NewPacket()
buffer.FullReset() 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 { if sniffMetadata != nil {
metadata.Protocol = sniffMetadata.Protocol metadata.Protocol = sniffMetadata.Protocol
metadata.Domain = sniffMetadata.Domain metadata.Domain = sniffMetadata.Domain
@@ -675,6 +675,8 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad
} else { } else {
r.logger.DebugContext(ctx, "sniffed protocol: ", metadata.Protocol) r.logger.DebugContext(ctx, "sniffed protocol: ", metadata.Protocol)
} }
} else if err != nil {
r.logger.TraceContext(ctx, "sniffed no protocol: ", err)
} }
if !buffer.IsEmpty() { if !buffer.IsEmpty() {
conn = bufio.NewCachedConn(conn, buffer) conn = bufio.NewCachedConn(conn, buffer)

View File

@@ -17,22 +17,30 @@ type NATPacketConn struct {
func NewNATPacketConn(conn N.PacketConn, origin M.Socksaddr, destination M.Socksaddr) *NATPacketConn { func NewNATPacketConn(conn N.PacketConn, origin M.Socksaddr, destination M.Socksaddr) *NATPacketConn {
return &NATPacketConn{ return &NATPacketConn{
PacketConn: conn, PacketConn: conn,
origin: origin, origin: socksaddrWithoutPort(origin),
destination: destination, destination: socksaddrWithoutPort(destination),
} }
} }
func (c *NATPacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) { func (c *NATPacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) {
destination, err = c.PacketConn.ReadPacket(buffer) destination, err = c.PacketConn.ReadPacket(buffer)
if destination == c.origin { if socksaddrWithoutPort(destination) == c.origin {
destination = c.destination destination = M.Socksaddr{
Addr: c.destination.Addr,
Fqdn: c.destination.Fqdn,
Port: destination.Port,
}
} }
return return
} }
func (c *NATPacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error { func (c *NATPacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
if destination == c.destination { if socksaddrWithoutPort(destination) == c.destination {
destination = c.origin destination = M.Socksaddr{
Addr: c.origin.Addr,
Fqdn: c.origin.Fqdn,
Port: destination.Port,
}
} }
return c.PacketConn.WritePacket(buffer, destination) 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 { func (c *NATPacketConn) Upstream() any {
return c.PacketConn 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 return
} }
n = copy(p, msg.Data) 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 return
} }

View File

@@ -32,10 +32,10 @@ type TLSObfs struct {
func (to *TLSObfs) read(b []byte, discardN int) (int, error) { func (to *TLSObfs) read(b []byte, discardN int) (int, error) {
buf := B.Get(discardN) buf := B.Get(discardN)
_, err := io.ReadFull(to.Conn, buf) _, err := io.ReadFull(to.Conn, buf)
B.Put(buf)
if err != nil { if err != nil {
return 0, err return 0, err
} }
B.Put(buf)
sizeBuf := make([]byte, 2) sizeBuf := make([]byte, 2)
_, err = io.ReadFull(to.Conn, sizeBuf) _, err = io.ReadFull(to.Conn, sizeBuf)

View File

@@ -131,7 +131,11 @@ func (c *ClientPacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error)
return return
} }
n = buffer.Len() n = buffer.Len()
addr = destination.UDPAddr() if destination.IsFqdn() {
addr = destination
} else {
addr = destination.UDPAddr()
}
return return
} }

View File

@@ -46,11 +46,8 @@ func NewClientTransport(ctx context.Context, dialer N.Dialer, serverAddr M.Socks
} }
switch options.Type { switch options.Type {
case C.V2RayTransportTypeHTTP: 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: case C.V2RayTransportTypeGRPC:
if tlsConfig == nil {
return nil, C.ErrTLSRequired
}
return NewGRPCClient(ctx, dialer, serverAddr, options.GRPCOptions, tlsConfig) return NewGRPCClient(ctx, dialer, serverAddr, options.GRPCOptions, tlsConfig)
case C.V2RayTransportTypeWebsocket: case C.V2RayTransportTypeWebsocket:
return v2raywebsocket.NewClient(ctx, dialer, serverAddr, options.WebsocketOptions, tlsConfig), nil return v2raywebsocket.NewClient(ctx, dialer, serverAddr, options.WebsocketOptions, tlsConfig), nil

View File

@@ -37,7 +37,9 @@ type Client struct {
func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayGRPCOptions, tlsConfig tls.Config) (adapter.V2RayClientTransport, error) { func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayGRPCOptions, tlsConfig tls.Config) (adapter.V2RayClientTransport, error) {
var dialOptions []grpc.DialOption var dialOptions []grpc.DialOption
if tlsConfig != nil { 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))) dialOptions = append(dialOptions, grpc.WithTransportCredentials(NewTLSTransportCredentials(tlsConfig)))
} else { } else {
dialOptions = append(dialOptions, grpc.WithTransportCredentials(insecure.NewCredentials())) dialOptions = append(dialOptions, grpc.WithTransportCredentials(insecure.NewCredentials()))
@@ -102,10 +104,10 @@ func (c *Client) DialContext(ctx context.Context) (net.Conn, error) {
return nil, err return nil, err
} }
client := NewGunServiceClient(clientConn).(GunServiceCustomNameClient) client := NewGunServiceClient(clientConn).(GunServiceCustomNameClient)
ctx, cancel := context.WithCancel(ctx) ctx, cancel := common.ContextWithCancelCause(ctx)
stream, err := client.TunCustomName(ctx, c.serviceName) stream, err := client.TunCustomName(ctx, c.serviceName)
if err != nil { if err != nil {
cancel() cancel(err)
return nil, err return nil, err
} }
return deadline.NewConn(NewGRPCConn(stream, cancel)), nil return deadline.NewConn(NewGRPCConn(stream, cancel)), nil

View File

@@ -1,12 +1,12 @@
package v2raygrpc package v2raygrpc
import ( import (
"context"
"net" "net"
"os" "os"
"time" "time"
"github.com/sagernet/sing-box/common/baderror" "github.com/sagernet/sing-box/common/baderror"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/rw" "github.com/sagernet/sing/common/rw"
) )
@@ -14,11 +14,11 @@ var _ net.Conn = (*GRPCConn)(nil)
type GRPCConn struct { type GRPCConn struct {
GunService GunService
cancel context.CancelFunc cancel common.ContextCancelCauseFunc
cache []byte 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 { if client, isClient := service.(GunService_TunClient); isClient {
service = &clientConnWrapper{client} service = &clientConnWrapper{client}
} }
@@ -37,6 +37,7 @@ func (c *GRPCConn) Read(b []byte) (n int, err error) {
hunk, err := c.Recv() hunk, err := c.Recv()
err = baderror.WrapGRPC(err) err = baderror.WrapGRPC(err)
if err != nil { if err != nil {
c.cancel(err)
return return
} }
n = copy(b, hunk.Data) 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) { func (c *GRPCConn) Write(b []byte) (n int, err error) {
err = baderror.WrapGRPC(c.Send(&Hunk{Data: b})) err = baderror.WrapGRPC(c.Send(&Hunk{Data: b}))
if err != nil { if err != nil {
c.cancel(err)
return return
} }
return len(b), nil return len(b), nil
} }
func (c *GRPCConn) Close() error { func (c *GRPCConn) Close() error {
c.cancel() c.cancel(net.ErrClosed)
return nil return nil
} }

View File

@@ -10,10 +10,12 @@ import (
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/tls" "github.com/sagernet/sing-box/common/tls"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/bufio/deadline" "github.com/sagernet/sing/common/bufio/deadline"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
"golang.org/x/net/http2"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/keepalive" "google.golang.org/grpc/keepalive"
gM "google.golang.org/grpc/metadata" gM "google.golang.org/grpc/metadata"
@@ -31,7 +33,9 @@ type Server struct {
func NewServer(ctx context.Context, options option.V2RayGRPCOptions, tlsConfig tls.ServerConfig, handler N.TCPConnectionHandler) (*Server, error) { func NewServer(ctx context.Context, options option.V2RayGRPCOptions, tlsConfig tls.ServerConfig, handler N.TCPConnectionHandler) (*Server, error) {
var serverOptions []grpc.ServerOption var serverOptions []grpc.ServerOption
if tlsConfig != nil { 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))) serverOptions = append(serverOptions, grpc.Creds(NewTLSTransportCredentials(tlsConfig)))
} }
if options.IdleTimeout > 0 { if options.IdleTimeout > 0 {
@@ -46,7 +50,7 @@ func NewServer(ctx context.Context, options option.V2RayGRPCOptions, tlsConfig t
} }
func (s *Server) Tun(server GunService_TunServer) error { func (s *Server) Tun(server GunService_TunServer) error {
ctx, cancel := context.WithCancel(s.ctx) ctx, cancel := common.ContextWithCancelCause(s.ctx)
conn := NewGRPCConn(server, cancel) conn := NewGRPCConn(server, cancel)
var metadata M.Metadata var metadata M.Metadata
if remotePeer, loaded := peer.FromContext(server.Context()); loaded { if remotePeer, loaded := peer.FromContext(server.Context()); loaded {

View File

@@ -2,7 +2,6 @@ package v2raygrpclite
import ( import (
"context" "context"
"fmt"
"io" "io"
"net" "net"
"net/http" "net/http"
@@ -32,56 +31,57 @@ type Client struct {
ctx context.Context ctx context.Context
dialer N.Dialer dialer N.Dialer
serverAddr M.Socksaddr serverAddr M.Socksaddr
transport http.RoundTripper transport *http2.Transport
options option.V2RayGRPCOptions options option.V2RayGRPCOptions
url *url.URL url *url.URL
} }
func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayGRPCOptions, tlsConfig tls.Config) adapter.V2RayClientTransport { func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayGRPCOptions, tlsConfig tls.Config) adapter.V2RayClientTransport {
var transport http.RoundTripper client := &Client{
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{
ctx: ctx, ctx: ctx,
dialer: dialer, dialer: dialer,
serverAddr: serverAddr, serverAddr: serverAddr,
options: options, options: options,
transport: transport, transport: &http2.Transport{
ReadIdleTimeout: time.Duration(options.IdleTimeout),
PingTimeout: time.Duration(options.PingTimeout),
DisableCompression: true,
},
url: &url.URL{ url: &url.URL{
Scheme: "https", Scheme: "https",
Host: serverAddr.String(), Host: serverAddr.String(),
Path: fmt.Sprintf("/%s/Tun", url.QueryEscape(options.ServiceName)), 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) { func (c *Client) DialContext(ctx context.Context) (net.Conn, error) {
pipeInReader, pipeInWriter := io.Pipe() pipeInReader, pipeInWriter := io.Pipe()
request := &http.Request{ request := &http.Request{
Method: http.MethodPost, Method: http.MethodPost,
Body: pipeInReader, Body: pipeInReader,
URL: c.url, URL: c.url,
Proto: "HTTP/2", Header: defaultClientHeader,
ProtoMajor: 2,
Header: defaultClientHeader,
} }
request = request.WithContext(ctx) request = request.WithContext(ctx)
conn := newLateGunConn(pipeInWriter) conn := newLateGunConn(pipeInWriter)
@@ -97,6 +97,8 @@ func (c *Client) DialContext(ctx context.Context) (net.Conn, error) {
} }
func (c *Client) Close() error { func (c *Client) Close() error {
v2rayhttp.CloseIdleConnections(c.transport) if c.transport != nil {
v2rayhttp.CloseIdleConnections(c.transport)
}
return nil return nil
} }

View File

@@ -117,6 +117,7 @@ func (c *GunConn) WriteBuffer(buffer *buf.Buffer) error {
dataLen := buffer.Len() dataLen := buffer.Len()
varLen := rw.UVariantLen(uint64(dataLen)) varLen := rw.UVariantLen(uint64(dataLen))
header := buffer.ExtendHeader(6 + varLen) header := buffer.ExtendHeader(6 + varLen)
_ = header[6]
header[0] = 0x00 header[0] = 0x00
binary.BigEndian.PutUint32(header[1:5], uint32(1+varLen+dataLen)) binary.BigEndian.PutUint32(header[1:5], uint32(1+varLen+dataLen))
header[5] = 0x0A header[5] = 0x0A

View File

@@ -2,10 +2,8 @@ package v2raygrpclite
import ( import (
"context" "context"
"fmt"
"net" "net"
"net/http" "net/http"
"net/url"
"os" "os"
"strings" "strings"
"time" "time"
@@ -46,13 +44,16 @@ func NewServer(ctx context.Context, options option.V2RayGRPCOptions, tlsConfig t
server := &Server{ server := &Server{
tlsConfig: tlsConfig, tlsConfig: tlsConfig,
handler: handler, handler: handler,
path: fmt.Sprintf("/%s/Tun", url.QueryEscape(options.ServiceName)), path: "/" + options.ServiceName + "/Tun",
h2Server: &http2.Server{ h2Server: &http2.Server{
IdleTimeout: time.Duration(options.IdleTimeout), IdleTimeout: time.Duration(options.IdleTimeout),
}, },
} }
server.httpServer = &http.Server{ server.httpServer = &http.Server{
Handler: server, Handler: server,
BaseContext: func(net.Listener) context.Context {
return ctx
},
} }
server.h2cHandler = h2c.NewHandler(server, server.h2Server) server.h2cHandler = h2c.NewHandler(server, server.h2Server)
return server, nil return server, nil
@@ -101,8 +102,8 @@ func (s *Server) fallbackRequest(ctx context.Context, writer http.ResponseWriter
func (s *Server) Serve(listener net.Listener) error { func (s *Server) Serve(listener net.Listener) error {
if s.tlsConfig != nil { if s.tlsConfig != nil {
if len(s.tlsConfig.NextProtos()) == 0 { if !common.Contains(s.tlsConfig.NextProtos(), http2.NextProtoTLS) {
s.tlsConfig.SetNextProtos([]string{http2.NextProtoTLS}) s.tlsConfig.SetNextProtos(append([]string{"h2"}, s.tlsConfig.NextProtos()...))
} }
listener = aTLS.NewListener(listener, s.tlsConfig) listener = aTLS.NewListener(listener, s.tlsConfig)
} }

View File

@@ -7,7 +7,6 @@ import (
"net" "net"
"net/http" "net/http"
"net/url" "net/url"
"strings"
"time" "time"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
@@ -16,6 +15,7 @@ import (
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
sHTTP "github.com/sagernet/sing/protocol/http"
"golang.org/x/net/http2" "golang.org/x/net/http2"
) )
@@ -34,7 +34,7 @@ type Client struct {
headers http.Header 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 var transport http.RoundTripper
if tlsConfig == nil { if tlsConfig == nil {
transport = &http.Transport{ transport = &http.Transport{
@@ -43,7 +43,9 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt
}, },
} }
} else { } else {
tlsConfig.SetNextProtos([]string{http2.NextProtoTLS}) if len(tlsConfig.NextProtos()) == 0 {
tlsConfig.SetNextProtos([]string{http2.NextProtoTLS})
}
transport = &http2.Transport{ transport = &http2.Transport{
ReadIdleTimeout: time.Duration(options.IdleTimeout), ReadIdleTimeout: time.Duration(options.IdleTimeout),
PingTimeout: time.Duration(options.PingTimeout), 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.Host = serverAddr.String()
uri.Path = options.Path uri.Path = options.Path
if !strings.HasPrefix(uri.Path, "/") { err := sHTTP.URLSetPath(&uri, options.Path)
uri.Path = "/" + uri.Path if err != nil {
return nil, E.New("failed to set path: " + err.Error())
} }
for key, valueList := range options.Headers { for key, valueList := range options.Headers {
client.headers[key] = valueList client.headers[key] = valueList
} }
client.url = &uri client.url = &uri
return client return client, nil
} }
func (c *Client) DialContext(ctx context.Context) (net.Conn, error) { 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, Handler: server,
ReadHeaderTimeout: C.TCPTimeout, ReadHeaderTimeout: C.TCPTimeout,
MaxHeaderBytes: http.DefaultMaxHeaderBytes, MaxHeaderBytes: http.DefaultMaxHeaderBytes,
BaseContext: func(net.Listener) context.Context {
return ctx
},
} }
server.h2cHandler = h2c.NewHandler(server, server.h2Server) server.h2cHandler = h2c.NewHandler(server, server.h2Server)
return server, nil return server, nil
@@ -102,12 +105,20 @@ func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
} }
} }
writer.WriteHeader(http.StatusOK)
writer.(http.Flusher).Flush()
var metadata M.Metadata var metadata M.Metadata
metadata.Source = sHttp.SourceAddress(request) metadata.Source = sHttp.SourceAddress(request)
if h, ok := writer.(http.Hijacker); ok { 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() conn, reader, err := h.Hijack()
if err != nil { if err != nil {
s.fallbackRequest(request.Context(), writer, request, 0, E.Cause(err, "hijack conn")) s.fallbackRequest(request.Context(), writer, request, 0, E.Cause(err, "hijack conn"))
@@ -122,8 +133,12 @@ func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
} }
conn = bufio.NewCachedConn(conn, cache) conn = bufio.NewCachedConn(conn, cache)
} }
if requestBody != nil {
conn = bufio.NewCachedConn(conn, requestBody)
}
s.handler.NewConnection(request.Context(), conn, metadata) s.handler.NewConnection(request.Context(), conn, metadata)
} else { } else {
writer.WriteHeader(http.StatusOK)
conn := NewHTTP2Wrapper(&ServerHTTPConn{ conn := NewHTTP2Wrapper(&ServerHTTPConn{
NewHTTPConn(request.Body, writer), NewHTTPConn(request.Body, writer),
writer.(http.Flusher), writer.(http.Flusher),
@@ -149,6 +164,11 @@ func (s *Server) fallbackRequest(ctx context.Context, writer http.ResponseWriter
func (s *Server) Serve(listener net.Listener) error { func (s *Server) Serve(listener net.Listener) error {
if s.tlsConfig != nil { 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) listener = aTLS.NewListener(listener, s.tlsConfig)
} }
return s.httpServer.Serve(listener) return s.httpServer.Serve(listener)

View File

@@ -5,7 +5,6 @@ import (
"net" "net"
"net/http" "net/http"
"net/url" "net/url"
"strings"
"time" "time"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
@@ -15,6 +14,7 @@ import (
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
sHTTP "github.com/sagernet/sing/protocol/http"
"github.com/sagernet/websocket" "github.com/sagernet/websocket"
) )
@@ -58,8 +58,9 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt
} }
uri.Host = serverAddr.String() uri.Host = serverAddr.String()
uri.Path = options.Path uri.Path = options.Path
if !strings.HasPrefix(uri.Path, "/") { err := sHTTP.URLSetPath(&uri, options.Path)
uri.Path = "/" + uri.Path if err != nil {
return nil
} }
headers := make(http.Header) headers := make(http.Header)
for key, value := range options.Headers { for key, value := range options.Headers {

View File

@@ -69,6 +69,14 @@ func (c *WebsocketConn) SetDeadline(t time.Time) error {
return os.ErrInvalid 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 { func (c *WebsocketConn) Upstream() any {
return c.Conn.NetConn() return c.Conn.NetConn()
} }
@@ -195,24 +203,15 @@ func (c *EarlyWebsocketConn) RemoteAddr() net.Addr {
} }
func (c *EarlyWebsocketConn) SetDeadline(t time.Time) error { func (c *EarlyWebsocketConn) SetDeadline(t time.Time) error {
if c.conn == nil { return os.ErrInvalid
return os.ErrInvalid
}
return c.conn.SetDeadline(t)
} }
func (c *EarlyWebsocketConn) SetReadDeadline(t time.Time) error { func (c *EarlyWebsocketConn) SetReadDeadline(t time.Time) error {
if c.conn == nil { return os.ErrInvalid
return os.ErrInvalid
}
return c.conn.SetReadDeadline(t)
} }
func (c *EarlyWebsocketConn) SetWriteDeadline(t time.Time) error { func (c *EarlyWebsocketConn) SetWriteDeadline(t time.Time) error {
if c.conn == nil { return os.ErrInvalid
return os.ErrInvalid
}
return c.conn.SetWriteDeadline(t)
} }
func (c *EarlyWebsocketConn) Upstream() any { func (c *EarlyWebsocketConn) Upstream() any {

View File

@@ -53,6 +53,9 @@ func NewServer(ctx context.Context, options option.V2RayWebsocketOptions, tlsCon
Handler: server, Handler: server,
ReadHeaderTimeout: C.TCPTimeout, ReadHeaderTimeout: C.TCPTimeout,
MaxHeaderBytes: http.DefaultMaxHeaderBytes, MaxHeaderBytes: http.DefaultMaxHeaderBytes,
BaseContext: func(net.Listener) context.Context {
return ctx
},
} }
return server, nil return server, nil
} }

View File

@@ -198,7 +198,11 @@ func (c *PacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
if err != nil { if err != nil {
return return
} }
addr = c.destination.UDPAddr() if c.destination.IsFqdn() {
addr = c.destination
} else {
addr = c.destination.UDPAddr()
}
return return
} }

View File

@@ -147,7 +147,11 @@ func (c *serverPacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error)
if err != nil { if err != nil {
return return
} }
addr = c.destination.UDPAddr() if c.destination.IsFqdn() {
addr = c.destination
} else {
addr = c.destination.UDPAddr()
}
return return
} }

View File

@@ -7,8 +7,8 @@ import (
"sync" "sync"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio" "github.com/sagernet/sing/common/bufio"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
"github.com/sagernet/wireguard-go/conn" "github.com/sagernet/wireguard-go/conn"
@@ -18,6 +18,7 @@ var _ conn.Bind = (*ClientBind)(nil)
type ClientBind struct { type ClientBind struct {
ctx context.Context ctx context.Context
errorHandler E.Handler
dialer N.Dialer dialer N.Dialer
reservedForEndpoint map[M.Socksaddr][3]uint8 reservedForEndpoint map[M.Socksaddr][3]uint8
connAccess sync.Mutex connAccess sync.Mutex
@@ -28,9 +29,10 @@ type ClientBind struct {
reserved [3]uint8 reserved [3]uint8
} }
func NewClientBind(ctx context.Context, dialer N.Dialer, isConnect bool, connectAddr M.Socksaddr, reserved [3]uint8) *ClientBind { func NewClientBind(ctx context.Context, errorHandler E.Handler, dialer N.Dialer, isConnect bool, connectAddr M.Socksaddr, reserved [3]uint8) *ClientBind {
return &ClientBind{ return &ClientBind{
ctx: ctx, ctx: ctx,
errorHandler: errorHandler,
dialer: dialer, dialer: dialer,
reservedForEndpoint: make(map[M.Socksaddr][3]uint8), reservedForEndpoint: make(map[M.Socksaddr][3]uint8),
isConnect: isConnect, isConnect: isConnect,
@@ -67,10 +69,10 @@ func (c *ClientBind) connect() (*wireConn, error) {
if c.isConnect { if c.isConnect {
udpConn, err := c.dialer.DialContext(c.ctx, N.NetworkUDP, c.connectAddr) udpConn, err := c.dialer.DialContext(c.ctx, N.NetworkUDP, c.connectAddr)
if err != nil { if err != nil {
return nil, &wireError{err} return nil, err
} }
c.conn = &wireConn{ c.conn = &wireConn{
NetPacketConn: &bufio.UnbindPacketConn{ PacketConn: &bufio.UnbindPacketConn{
ExtendedConn: bufio.NewExtendedConn(udpConn), ExtendedConn: bufio.NewExtendedConn(udpConn),
Addr: c.connectAddr, Addr: c.connectAddr,
}, },
@@ -79,11 +81,11 @@ func (c *ClientBind) connect() (*wireConn, error) {
} else { } else {
udpConn, err := c.dialer.ListenPacket(c.ctx, M.Socksaddr{Addr: netip.IPv4Unspecified()}) udpConn, err := c.dialer.ListenPacket(c.ctx, M.Socksaddr{Addr: netip.IPv4Unspecified()})
if err != nil { if err != nil {
return nil, &wireError{err} return nil, err
} }
c.conn = &wireConn{ c.conn = &wireConn{
NetPacketConn: bufio.NewPacketConn(udpConn), PacketConn: bufio.NewPacketConn(udpConn),
done: make(chan struct{}), done: make(chan struct{}),
} }
} }
return c.conn, nil return c.conn, nil
@@ -102,30 +104,31 @@ func (c *ClientBind) Open(port uint16) (fns []conn.ReceiveFunc, actualPort uint1
func (c *ClientBind) receive(b []byte) (n int, ep conn.Endpoint, err error) { func (c *ClientBind) receive(b []byte) (n int, ep conn.Endpoint, err error) {
udpConn, err := c.connect() udpConn, err := c.connect()
if err != nil { if err != nil {
err = &wireError{err} select {
case <-c.done:
return
default:
}
c.errorHandler.NewError(context.Background(), E.Cause(err, "connect to server"))
err = nil
return return
} }
buffer := buf.With(b) n, addr, err := udpConn.ReadFrom(b)
destination, err := udpConn.ReadPacket(buffer)
if err != nil { if err != nil {
udpConn.Close() udpConn.Close()
select { select {
case <-c.done: case <-c.done:
default: default:
err = &wireError{err} c.errorHandler.NewError(context.Background(), E.Cause(err, "read packet"))
} }
return return
} }
n = buffer.Len()
if buffer.Start() > 0 {
copy(b, buffer.Bytes())
}
if n > 3 { if n > 3 {
b[1] = 0 b[1] = 0
b[2] = 0 b[2] = 0
b[3] = 0 b[3] = 0
} }
ep = Endpoint(destination) ep = Endpoint(M.SocksaddrFromNet(addr))
return return
} }
@@ -167,7 +170,7 @@ func (c *ClientBind) Send(b []byte, ep conn.Endpoint) error {
b[2] = reserved[1] b[2] = reserved[1]
b[3] = reserved[2] b[3] = reserved[2]
} }
err = udpConn.WritePacket(buf.As(b), destination) _, err = udpConn.WriteTo(b, destination)
if err != nil { if err != nil {
udpConn.Close() udpConn.Close()
} }
@@ -179,7 +182,7 @@ func (c *ClientBind) ParseEndpoint(s string) (conn.Endpoint, error) {
} }
type wireConn struct { type wireConn struct {
N.NetPacketConn net.PacketConn
access sync.Mutex access sync.Mutex
done chan struct{} done chan struct{}
} }
@@ -192,7 +195,7 @@ func (w *wireConn) Close() error {
return net.ErrClosed return net.ErrClosed
default: default:
} }
w.NetPacketConn.Close() w.PacketConn.Close()
close(w.done) close(w.done)
return nil return nil
} }

View File

@@ -1,22 +0,0 @@
package wireguard
import "net"
type wireError struct {
cause error
}
func (w *wireError) Error() string {
return w.cause.Error()
}
func (w *wireError) Timeout() bool {
if cause, causeNet := w.cause.(net.Error); causeNet {
return cause.Timeout()
}
return false
}
func (w *wireError) Temporary() bool {
return true
}