mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-09-16 06:20:28 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6d3ebc4239 |
@@ -73,6 +73,7 @@
|
||||
- [Xray_bash_onekey](https://github.com/hello-yunshu/Xray_bash_onekey), [XTool](https://github.com/LordPenguin666/XTool), [VPainLess](https://github.com/vpainless/vpainless)
|
||||
- [v2ray-agent](https://github.com/mack-a/v2ray-agent), [Xray_onekey](https://github.com/wulabing/Xray_onekey), [ProxySU](https://github.com/proxysu/ProxySU)
|
||||
- Magisk
|
||||
- [Xray4Magisk](https://github.com/Asterisk4Magisk/Xray4Magisk)
|
||||
- [Xray_For_Magisk](https://github.com/E7KMbb/Xray_For_Magisk)
|
||||
- Homebrew
|
||||
- `brew install xray`
|
||||
@@ -119,7 +120,6 @@
|
||||
- [XrayFA](https://github.com/Q7DF1/XrayFA)
|
||||
- [AnyPortal](https://github.com/AnyPortal/AnyPortal)
|
||||
- [OneXray](https://github.com/OneXray/OneXray)
|
||||
- [AsteriskNG](https://github.com/Asterisk4Magisk/AsteriskNG)
|
||||
- iOS & macOS arm64 & tvOS
|
||||
- [Happ](https://apps.apple.com/app/happ-proxy-utility/id6504287215) | [Happ RU](https://apps.apple.com/ru/app/happ-proxy-utility-plus/id6746188973) | [Happ tvOS](https://apps.apple.com/us/app/happ-proxy-utility-for-tv/id6748297274)
|
||||
- [Streisand](https://apps.apple.com/app/streisand/id6450534064)
|
||||
|
||||
@@ -102,16 +102,6 @@ func (h *HealthPing) StartScheduler(selector func() ([]string, error)) {
|
||||
h.Check(tags)
|
||||
}()
|
||||
|
||||
// init run to get a fast check result
|
||||
go func() {
|
||||
tags, err := selector()
|
||||
if err != nil {
|
||||
errors.LogWarning(h.ctx, "error select outbounds for initial health check: ", err)
|
||||
return
|
||||
}
|
||||
h.Check(tags)
|
||||
}()
|
||||
|
||||
go func() {
|
||||
for {
|
||||
go func() {
|
||||
|
||||
@@ -3,7 +3,6 @@ package router
|
||||
import (
|
||||
"context"
|
||||
"math"
|
||||
"slices"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
@@ -78,7 +77,7 @@ func (s *LeastLoadStrategy) PickOutbound(candidates []string) string {
|
||||
}
|
||||
|
||||
func (s *LeastLoadStrategy) pickOutbounds(candidates []string) []*node {
|
||||
qualified := s.getNodes(candidates)
|
||||
qualified := s.getNodes(candidates, time.Duration(s.settings.MaxRTT))
|
||||
selects := s.selectLeastLoad(qualified)
|
||||
return selects
|
||||
}
|
||||
@@ -139,7 +138,7 @@ func (s *LeastLoadStrategy) selectLeastLoad(nodes []*node) []*node {
|
||||
return nodes[:count]
|
||||
}
|
||||
|
||||
func (s *LeastLoadStrategy) getNodes(candidates []string) []*node {
|
||||
func (s *LeastLoadStrategy) getNodes(candidates []string, maxRTT time.Duration) []*node {
|
||||
if s.observer == nil {
|
||||
errors.LogError(s.ctx, "observer is nil")
|
||||
return make([]*node, 0)
|
||||
@@ -152,10 +151,12 @@ func (s *LeastLoadStrategy) getNodes(candidates []string) []*node {
|
||||
|
||||
results := observeResult.(*observatory.ObservationResult)
|
||||
|
||||
outboundlist := outboundList(candidates)
|
||||
|
||||
var ret []*node
|
||||
|
||||
for _, v := range results.Status {
|
||||
if s.shouldSelectNode(v, candidates) {
|
||||
if v.Alive && (v.Delay < maxRTT.Milliseconds() || maxRTT == 0) && outboundlist.contains(v.OutboundTag) {
|
||||
record := &node{
|
||||
Tag: v.OutboundTag,
|
||||
CountAll: 1,
|
||||
@@ -171,8 +172,8 @@ func (s *LeastLoadStrategy) getNodes(candidates []string) []*node {
|
||||
record.RTTDeviationCost = time.Duration(s.costs.Apply(v.OutboundTag, float64(v.HealthPing.Deviation)))
|
||||
record.CountAll = int(v.HealthPing.All)
|
||||
record.CountFail = int(v.HealthPing.Fail)
|
||||
}
|
||||
|
||||
}
|
||||
ret = append(ret, record)
|
||||
}
|
||||
}
|
||||
@@ -181,23 +182,6 @@ func (s *LeastLoadStrategy) getNodes(candidates []string) []*node {
|
||||
return ret
|
||||
}
|
||||
|
||||
func (s *LeastLoadStrategy) shouldSelectNode(v *observatory.OutboundStatus, candidates []string) bool {
|
||||
maxRTT := time.Duration(s.settings.MaxRTT)
|
||||
if !v.Alive {
|
||||
return false
|
||||
}
|
||||
if maxRTT != 0 && v.Delay >= maxRTT.Milliseconds() {
|
||||
return false
|
||||
}
|
||||
if !slices.Contains(candidates, v.OutboundTag) {
|
||||
return false
|
||||
}
|
||||
if v.HealthPing != nil && v.HealthPing.All > 0 && s.settings.Tolerance > 0 && float64(v.HealthPing.Fail)/float64(v.HealthPing.All) > float64(s.settings.Tolerance) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func leastloadSort(nodes []*node) {
|
||||
sort.Slice(nodes, func(i, j int) bool {
|
||||
left := nodes[i]
|
||||
|
||||
+47
-3
@@ -7,16 +7,60 @@ import (
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/utils"
|
||||
"github.com/xtls/xray-core/features/routing"
|
||||
routing_session "github.com/xtls/xray-core/features/routing/session"
|
||||
)
|
||||
|
||||
// parseURL splits a webhook URL into an HTTP URL and an optional Unix socket
|
||||
// path. For regular http/https URLs the input is returned unchanged with an
|
||||
// empty socketPath. For Unix sockets the format is:
|
||||
//
|
||||
// /path/to/socket.sock:/http/path
|
||||
// @abstract:/http/path
|
||||
// @@padded:/http/path
|
||||
//
|
||||
// The :/ separator after the socket path delimits the HTTP request path.
|
||||
// If omitted, "/" is used.
|
||||
func parseURL(raw string) (httpURL, socketPath string) {
|
||||
if len(raw) == 0 || (!filepath.IsAbs(raw) && raw[0] != '@') {
|
||||
return raw, ""
|
||||
}
|
||||
if idx := strings.Index(raw, ":/"); idx >= 0 {
|
||||
return "http://localhost" + raw[idx+1:], raw[:idx]
|
||||
}
|
||||
return "http://localhost/", raw
|
||||
}
|
||||
|
||||
// resolveSocketPath applies platform-specific transformations to a Unix
|
||||
// socket path, matching the behaviour of the listen side in
|
||||
// transport/internet/system_listener.go.
|
||||
//
|
||||
// For abstract sockets (prefix @) on Linux/Android:
|
||||
// - single @ — used as-is (lock-free abstract socket)
|
||||
// - double @@ — stripped to single @ and padded to
|
||||
// syscall.RawSockaddrUnix{}.Path length (HAProxy compat)
|
||||
func resolveSocketPath(path string) string {
|
||||
if len(path) == 0 || path[0] != '@' {
|
||||
return path
|
||||
}
|
||||
if runtime.GOOS != "linux" && runtime.GOOS != "android" {
|
||||
return path
|
||||
}
|
||||
if len(path) > 1 && path[1] == '@' {
|
||||
fullAddr := make([]byte, len(syscall.RawSockaddrUnix{}.Path))
|
||||
copy(fullAddr, path[1:])
|
||||
return string(fullAddr)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func ptr[T any](v T) *T { return &v }
|
||||
|
||||
@@ -52,7 +96,7 @@ func NewWebhookNotifier(cfg *WebhookConfig) (*WebhookNotifier, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
httpURL, socketPath := utils.SplitHTTPUnixURL(cfg.Url)
|
||||
httpURL, socketPath := parseURL(cfg.Url)
|
||||
h := &WebhookNotifier{
|
||||
url: httpURL,
|
||||
deduplication: cfg.Deduplication,
|
||||
@@ -63,7 +107,7 @@ func NewWebhookNotifier(cfg *WebhookConfig) (*WebhookNotifier, error) {
|
||||
}
|
||||
|
||||
if socketPath != "" {
|
||||
dialAddr := utils.ResolveSocketPath(socketPath)
|
||||
dialAddr := resolveSocketPath(socketPath)
|
||||
h.client.Transport = &http.Transport{
|
||||
DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
|
||||
var d net.Dialer
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/geodata/strmatcher"
|
||||
"github.com/xtls/xray-core/common/utils"
|
||||
)
|
||||
|
||||
type DomainMatcher interface {
|
||||
@@ -26,7 +25,7 @@ type DomainMatcherFactory interface {
|
||||
|
||||
type MphDomainMatcherFactory struct {
|
||||
sync.Mutex
|
||||
shared *utils.WeakCacheMap[string, strmatcher.MphValueMatcher]
|
||||
shared map[string]strmatcher.MatcherGroup // TODO: cleanup
|
||||
}
|
||||
|
||||
func buildDomainRulesKey(rules []*DomainRule) string {
|
||||
@@ -66,7 +65,7 @@ func (f *MphDomainMatcherFactory) BuildMatcher(rules []*DomainRule) (DomainMatch
|
||||
if key != "" {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
if g, ok := f.shared.Load(key); ok {
|
||||
if g := f.shared[key]; g != nil {
|
||||
errors.LogDebug(context.Background(), "geodata mph domain matcher cache HIT for ", len(rules), " rules")
|
||||
return g, nil
|
||||
}
|
||||
@@ -103,14 +102,14 @@ func (f *MphDomainMatcherFactory) BuildMatcher(rules []*DomainRule) (DomainMatch
|
||||
return nil, err
|
||||
}
|
||||
if key != "" {
|
||||
f.shared.Store(key, g)
|
||||
f.shared[key] = g
|
||||
}
|
||||
return g, nil
|
||||
}
|
||||
|
||||
type CompactDomainMatcherFactory struct {
|
||||
sync.Mutex
|
||||
shared *utils.WeakCacheMap[string, strmatcher.LinearAnyMatcher]
|
||||
shared map[string]strmatcher.MatcherSet // TODO: cleanup
|
||||
}
|
||||
|
||||
func (f *CompactDomainMatcherFactory) getOrCreateFrom(rule *GeoSiteRule) (strmatcher.MatcherSet, error) {
|
||||
@@ -119,7 +118,7 @@ func (f *CompactDomainMatcherFactory) getOrCreateFrom(rule *GeoSiteRule) (strmat
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
if s, ok := f.shared.Load(key); ok {
|
||||
if s := f.shared[key]; s != nil {
|
||||
errors.LogDebug(context.Background(), "geodata geosite matcher cache HIT ", key)
|
||||
return s, nil
|
||||
}
|
||||
@@ -139,7 +138,7 @@ func (f *CompactDomainMatcherFactory) getOrCreateFrom(rule *GeoSiteRule) (strmat
|
||||
}
|
||||
s.Add(m)
|
||||
}
|
||||
f.shared.Store(key, s)
|
||||
f.shared[key] = s
|
||||
return s, err
|
||||
}
|
||||
|
||||
@@ -231,8 +230,8 @@ func parseDomain(d *Domain) (strmatcher.Matcher, error) {
|
||||
func newDomainMatcherFactory() DomainMatcherFactory {
|
||||
switch runtime.GOOS {
|
||||
case "ios", "android":
|
||||
return &CompactDomainMatcherFactory{shared: utils.NewWeakCacheMap[string, strmatcher.LinearAnyMatcher]()}
|
||||
return &CompactDomainMatcherFactory{shared: make(map[string]strmatcher.MatcherSet)}
|
||||
default:
|
||||
return &MphDomainMatcherFactory{shared: utils.NewWeakCacheMap[string, strmatcher.MphValueMatcher]()}
|
||||
return &MphDomainMatcherFactory{shared: make(map[string]strmatcher.MatcherGroup)}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,11 +7,10 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/xtls/xray-core/common/geodata/strmatcher"
|
||||
"github.com/xtls/xray-core/common/utils"
|
||||
)
|
||||
|
||||
func TestCompactDomainMatcher_PreservesCustomRuleIndices(t *testing.T) {
|
||||
factory := &CompactDomainMatcherFactory{shared: utils.NewWeakCacheMap[string, strmatcher.LinearAnyMatcher]()}
|
||||
factory := &CompactDomainMatcherFactory{shared: make(map[string]strmatcher.MatcherSet)}
|
||||
matcher, err := factory.BuildMatcher([]*DomainRule{
|
||||
{Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Full, Value: "example.com"}}},
|
||||
{Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Domain, Value: "example.com"}}},
|
||||
@@ -32,7 +31,7 @@ func TestCompactDomainMatcher_PreservesCustomRuleIndices(t *testing.T) {
|
||||
func TestCompactDomainMatcher_PreservesMixedRuleIndices(t *testing.T) {
|
||||
t.Setenv("xray.location.asset", filepath.Join("..", "..", "resources"))
|
||||
|
||||
factory := &CompactDomainMatcherFactory{shared: utils.NewWeakCacheMap[string, strmatcher.LinearAnyMatcher]()}
|
||||
factory := &CompactDomainMatcherFactory{shared: make(map[string]strmatcher.MatcherSet)}
|
||||
matcher, err := factory.BuildMatcher([]*DomainRule{
|
||||
{Value: &DomainRule_Geosite{Geosite: &GeoSiteRule{File: DefaultGeoSiteDat, Code: "CN"}}},
|
||||
{Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Full, Value: "163.com"}}},
|
||||
@@ -51,11 +50,10 @@ func TestCompactDomainMatcher_PreservesMixedRuleIndices(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMphDomainMatcher_MatchReturnsDetachedSlice(t *testing.T) {
|
||||
matcher, err := (&MphDomainMatcherFactory{shared: utils.NewWeakCacheMap[string, strmatcher.MphValueMatcher]()}).
|
||||
BuildMatcher([]*DomainRule{
|
||||
{Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Full, Value: "example.com"}}},
|
||||
{Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Domain, Value: "example.com"}}},
|
||||
})
|
||||
matcher, err := (&MphDomainMatcherFactory{shared: make(map[string]strmatcher.MatcherGroup)}).BuildMatcher([]*DomainRule{
|
||||
{Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Full, Value: "example.com"}}},
|
||||
{Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Domain, Value: "example.com"}}},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("BuildMatcher() failed: %v", err)
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/utils"
|
||||
|
||||
"go4.org/netipx"
|
||||
)
|
||||
@@ -807,7 +806,7 @@ func (mm *HeuristicMultiIPMatcher) SetReverse(reverse bool) {
|
||||
|
||||
type IPSetFactory struct {
|
||||
sync.Mutex
|
||||
shared *utils.WeakCacheMap[string, IPSet]
|
||||
shared map[string]*IPSet // TODO: cleanup
|
||||
}
|
||||
|
||||
func (f *IPSetFactory) GetOrCreateFromGeoIPRules(rules []*GeoIPRule) (*IPSet, error) {
|
||||
@@ -816,7 +815,7 @@ func (f *IPSetFactory) GetOrCreateFromGeoIPRules(rules []*GeoIPRule) (*IPSet, er
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
if ipset, ok := f.shared.Load(key); ok {
|
||||
if ipset := f.shared[key]; ipset != nil {
|
||||
errors.LogDebug(context.Background(), "geodata geoip matcher cache HIT ", key)
|
||||
return ipset, nil
|
||||
}
|
||||
@@ -836,7 +835,7 @@ func (f *IPSetFactory) GetOrCreateFromGeoIPRules(rules []*GeoIPRule) (*IPSet, er
|
||||
return nil
|
||||
})
|
||||
if err == nil {
|
||||
f.shared.Store(key, ipset)
|
||||
f.shared[key] = ipset
|
||||
}
|
||||
return ipset, err
|
||||
}
|
||||
@@ -1019,5 +1018,5 @@ func buildOptimizedIPMatcher(f *IPSetFactory, rules []*IPRule) (IPMatcher, error
|
||||
}
|
||||
|
||||
func newIPSetFactory() *IPSetFactory {
|
||||
return &IPSetFactory{shared: utils.NewWeakCacheMap[string, IPSet]()}
|
||||
return &IPSetFactory{shared: make(map[string]*IPSet)}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package singbridge
|
||||
import (
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
)
|
||||
|
||||
@@ -18,14 +17,14 @@ func ToNetwork(network string) net.Network {
|
||||
}
|
||||
}
|
||||
|
||||
func ToDestination(socksaddr M.Socksaddr, network net.Network) (net.Destination, error) {
|
||||
func ToDestination(socksaddr M.Socksaddr, network net.Network) net.Destination {
|
||||
// IsFqdn() implicitly checks if the domain name is valid
|
||||
if socksaddr.IsFqdn() {
|
||||
return net.Destination{
|
||||
Network: network,
|
||||
Address: net.DomainAddress(socksaddr.Fqdn),
|
||||
Port: net.Port(socksaddr.Port),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// IsIP() implicitly checks if the IP address is valid
|
||||
@@ -34,10 +33,10 @@ func ToDestination(socksaddr M.Socksaddr, network net.Network) (net.Destination,
|
||||
Network: network,
|
||||
Address: net.IPAddress(socksaddr.Addr.AsSlice()),
|
||||
Port: net.Port(socksaddr.Port),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
return net.Destination{}, errors.New("invalid socks address: ", socksaddr)
|
||||
return net.Destination{}
|
||||
}
|
||||
|
||||
func ToSocksaddr(destination net.Destination) M.Socksaddr {
|
||||
|
||||
@@ -26,11 +26,7 @@ func NewDialer(dialer internet.Dialer) *XrayDialer {
|
||||
}
|
||||
|
||||
func (d *XrayDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||
dest, err := ToDestination(destination, ToNetwork(network))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d.Dialer.Dial(ctx, dest)
|
||||
return d.Dialer.Dial(ctx, ToDestination(destination, ToNetwork(network)))
|
||||
}
|
||||
|
||||
func (d *XrayDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||
@@ -47,17 +43,13 @@ func NewOutboundDialer(outbound proxy.Outbound, dialer internet.Dialer) *XrayOut
|
||||
}
|
||||
|
||||
func (d *XrayOutboundDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||
dest, err := ToDestination(destination, ToNetwork(network))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
outbounds := session.OutboundsFromContext(ctx)
|
||||
if len(outbounds) == 0 {
|
||||
outbounds = []*session.Outbound{{}}
|
||||
ctx = session.ContextWithOutbounds(ctx, outbounds)
|
||||
}
|
||||
ob := outbounds[len(outbounds)-1]
|
||||
ob.Target = dest
|
||||
ob.Target = ToDestination(destination, ToNetwork(network))
|
||||
|
||||
opts := []pipe.Option{pipe.WithSizeLimit(64 * 1024)}
|
||||
uplinkReader, uplinkWriter := pipe.New(opts...)
|
||||
|
||||
@@ -31,23 +31,15 @@ func NewDispatcher(dispatcher routing.Dispatcher, newErrorFunc func(values ...an
|
||||
}
|
||||
|
||||
func (d *Dispatcher) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
||||
dest, err := ToDestination(metadata.Destination, net.Network_TCP)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
xConn := NewConn(conn)
|
||||
return d.upstream.DispatchLink(ctx, dest, &transport.Link{
|
||||
return d.upstream.DispatchLink(ctx, ToDestination(metadata.Destination, net.Network_TCP), &transport.Link{
|
||||
Reader: xConn,
|
||||
Writer: xConn,
|
||||
})
|
||||
}
|
||||
|
||||
func (d *Dispatcher) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
|
||||
dest, err := ToDestination(metadata.Destination, net.Network_UDP)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return d.upstream.DispatchLink(ctx, dest, &transport.Link{
|
||||
return d.upstream.DispatchLink(ctx, ToDestination(metadata.Destination, net.Network_UDP), &transport.Link{
|
||||
Reader: buf.NewPacketReader(conn.(io.Reader)),
|
||||
Writer: buf.NewWriter(conn.(io.Writer)),
|
||||
})
|
||||
|
||||
+30
-31
@@ -10,21 +10,15 @@ import (
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/buf"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/signal"
|
||||
"github.com/xtls/xray-core/transport"
|
||||
)
|
||||
|
||||
func CopyPacketConn(ctx context.Context, inboundConn net.Conn, link *transport.Link, destination net.Destination, serverConn net.PacketConn) error {
|
||||
cancel := func() {
|
||||
common.Interrupt(link.Reader)
|
||||
common.Interrupt(serverConn)
|
||||
}
|
||||
conn := &PacketConnWrapper{
|
||||
Reader: link.Reader,
|
||||
Writer: link.Writer,
|
||||
Dest: destination,
|
||||
Conn: inboundConn,
|
||||
T: signal.CancelAfterInactivity(ctx, cancel, 300*time.Second),
|
||||
}
|
||||
return ReturnError(bufio.CopyPacketConn(ctx, conn, bufio.NewPacketConn(serverConn)))
|
||||
}
|
||||
@@ -35,19 +29,11 @@ type PacketConnWrapper struct {
|
||||
net.Conn
|
||||
Dest net.Destination
|
||||
cached buf.MultiBuffer
|
||||
|
||||
// A simple patch to avoid goroutine leak since sing infra cannot awake read block by write err
|
||||
T *signal.ActivityTimer
|
||||
}
|
||||
|
||||
func (w *PacketConnWrapper) ReadPacket(buffer *B.Buffer) (addr M.Socksaddr, err error) {
|
||||
w.T.Update()
|
||||
defer func() {
|
||||
if err != nil {
|
||||
// uplinkonly
|
||||
w.T.SetTimeout(2 * time.Second)
|
||||
}
|
||||
}()
|
||||
// This ReadPacket implemented a timeout to avoid goroutine leak like PipeConnWrapper.Read()
|
||||
// as a temporarily solution
|
||||
func (w *PacketConnWrapper) ReadPacket(buffer *B.Buffer) (M.Socksaddr, error) {
|
||||
if w.cached != nil {
|
||||
mb, bb := buf.SplitFirst(w.cached)
|
||||
if bb == nil {
|
||||
@@ -65,7 +51,30 @@ func (w *PacketConnWrapper) ReadPacket(buffer *B.Buffer) (addr M.Socksaddr, err
|
||||
return ToSocksaddr(destination), nil
|
||||
}
|
||||
}
|
||||
mb, err := w.ReadMultiBuffer()
|
||||
|
||||
// timeout
|
||||
type readResult struct {
|
||||
mb buf.MultiBuffer
|
||||
err error
|
||||
}
|
||||
c := make(chan readResult, 1)
|
||||
go func() {
|
||||
mb, err := w.ReadMultiBuffer()
|
||||
c <- readResult{mb: mb, err: err}
|
||||
}()
|
||||
var mb buf.MultiBuffer
|
||||
select {
|
||||
case <-time.After(60 * time.Second):
|
||||
common.Close(w.Reader)
|
||||
common.Interrupt(w.Reader)
|
||||
return M.Socksaddr{}, buf.ErrReadTimeout
|
||||
case result := <-c:
|
||||
if result.err != nil {
|
||||
return M.Socksaddr{}, result.err
|
||||
}
|
||||
mb = result.mb
|
||||
}
|
||||
|
||||
nb, bb := buf.SplitFirst(mb)
|
||||
if bb == nil {
|
||||
return M.Socksaddr{}, nil
|
||||
@@ -83,22 +92,12 @@ func (w *PacketConnWrapper) ReadPacket(buffer *B.Buffer) (addr M.Socksaddr, err
|
||||
}
|
||||
}
|
||||
|
||||
func (w *PacketConnWrapper) WritePacket(buffer *B.Buffer, destination M.Socksaddr) (err error) {
|
||||
w.T.Update()
|
||||
defer func() {
|
||||
if err != nil {
|
||||
// downlinkonly
|
||||
w.T.SetTimeout(5 * time.Second)
|
||||
}
|
||||
}()
|
||||
endpoint, err := ToDestination(destination, net.Network_UDP)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
func (w *PacketConnWrapper) WritePacket(buffer *B.Buffer, destination M.Socksaddr) error {
|
||||
vBuf := buf.New()
|
||||
vBuf.Write(buffer.Bytes())
|
||||
endpoint := ToDestination(destination, net.Network_UDP)
|
||||
vBuf.UDP = &endpoint
|
||||
return w.WriteMultiBuffer(buf.MultiBuffer{vBuf})
|
||||
return w.Writer.WriteMultiBuffer(buf.MultiBuffer{vBuf})
|
||||
}
|
||||
|
||||
func (w *PacketConnWrapper) Close() error {
|
||||
|
||||
+18
-18
@@ -9,7 +9,6 @@ import (
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/buf"
|
||||
"github.com/xtls/xray-core/common/signal"
|
||||
"github.com/xtls/xray-core/transport"
|
||||
)
|
||||
|
||||
@@ -23,11 +22,6 @@ func CopyConn(ctx context.Context, inboundConn net.Conn, link *transport.Link, s
|
||||
} else {
|
||||
conn.R = &buf.BufferedReader{Reader: link.Reader}
|
||||
}
|
||||
cancel := func() {
|
||||
common.Interrupt(link.Reader)
|
||||
common.Interrupt(serverConn)
|
||||
}
|
||||
conn.T = signal.CancelAfterInactivity(ctx, cancel, 300*time.Second)
|
||||
return ReturnError(bufio.CopyConn(ctx, conn, serverConn))
|
||||
}
|
||||
|
||||
@@ -35,27 +29,35 @@ type PipeConnWrapper struct {
|
||||
R io.Reader
|
||||
W buf.Writer
|
||||
net.Conn
|
||||
|
||||
// A simple patch to avoid goroutine leak since sing infra cannot awake read block by write err
|
||||
T *signal.ActivityTimer
|
||||
}
|
||||
|
||||
func (w *PipeConnWrapper) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// This Read implemented a timeout to avoid goroutine leak.
|
||||
// as a temporarily solution
|
||||
func (w *PipeConnWrapper) Read(b []byte) (n int, err error) {
|
||||
w.T.Update()
|
||||
n, err = w.R.Read(b)
|
||||
if err != nil {
|
||||
// uplinkonly
|
||||
w.T.SetTimeout(2 * time.Second)
|
||||
type readResult struct {
|
||||
n int
|
||||
err error
|
||||
}
|
||||
c := make(chan readResult, 1)
|
||||
go func() {
|
||||
n, err := w.R.Read(b)
|
||||
c <- readResult{n: n, err: err}
|
||||
}()
|
||||
select {
|
||||
case result := <-c:
|
||||
return result.n, result.err
|
||||
case <-time.After(300 * time.Second):
|
||||
common.Close(w.R)
|
||||
common.Interrupt(w.R)
|
||||
return 0, buf.ErrReadTimeout
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (w *PipeConnWrapper) Write(p []byte) (n int, err error) {
|
||||
w.T.Update()
|
||||
n = len(p)
|
||||
var mb buf.MultiBuffer
|
||||
pLen := len(p)
|
||||
@@ -74,8 +76,6 @@ func (w *PipeConnWrapper) Write(p []byte) (n int, err error) {
|
||||
if err != nil {
|
||||
n = 0
|
||||
buf.ReleaseMulti(mb)
|
||||
// downlinkonly
|
||||
w.T.SetTimeout(5 * time.Second)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// ResolveSocketPath applies platform-specific transformations to a Unix
|
||||
// socket path, matching the listen-side behaviour in
|
||||
// transport/internet/system_listener.go.
|
||||
//
|
||||
// For abstract sockets (prefix @) on Linux/Android:
|
||||
// - single @ — used as-is (lock-free abstract socket)
|
||||
// - double @@ — stripped to single @ and padded to
|
||||
// syscall.RawSockaddrUnix{}.Path length (HAProxy compat)
|
||||
//
|
||||
// Filesystem paths and abstract sockets on other platforms are returned
|
||||
// unchanged.
|
||||
func ResolveSocketPath(path string) string {
|
||||
if len(path) == 0 || path[0] != '@' {
|
||||
return path
|
||||
}
|
||||
if runtime.GOOS != "linux" && runtime.GOOS != "android" {
|
||||
return path
|
||||
}
|
||||
if len(path) > 1 && path[1] == '@' {
|
||||
fullAddr := make([]byte, len(syscall.RawSockaddrUnix{}.Path))
|
||||
copy(fullAddr, path[1:])
|
||||
return string(fullAddr)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
// SplitHTTPUnixURL splits a target into an HTTP URL and an optional Unix
|
||||
// socket path. For regular http(s) URLs the input is returned unchanged
|
||||
// with an empty socketPath. For Unix sockets the format is:
|
||||
//
|
||||
// /path/to/socket.sock[:/http/path]
|
||||
// @abstract[:/http/path]
|
||||
// @@padded[:/http/path]
|
||||
//
|
||||
// The :/ separator delimits the socket path from the HTTP request path.
|
||||
// If omitted, "/" is used.
|
||||
func SplitHTTPUnixURL(raw string) (httpURL, socketPath string) {
|
||||
if len(raw) == 0 || (!filepath.IsAbs(raw) && raw[0] != '@') {
|
||||
return raw, ""
|
||||
}
|
||||
if idx := strings.Index(raw, ":/"); idx >= 0 {
|
||||
return "http://localhost" + raw[idx+1:], raw[:idx]
|
||||
}
|
||||
return "http://localhost/", raw
|
||||
}
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"sync"
|
||||
"weak"
|
||||
)
|
||||
|
||||
// WeakCacheMap is a map that holds weak references to values.
|
||||
// Use for shared expensive objects and automatic cleanup when no longer used.
|
||||
// This object can be GC and no goroutine is used for cleanup.
|
||||
type WeakCacheMap[K comparable, V any] struct {
|
||||
mu sync.Mutex
|
||||
m map[K]weak.Pointer[V]
|
||||
}
|
||||
|
||||
func NewWeakCacheMap[K comparable, V any]() *WeakCacheMap[K, V] {
|
||||
return &WeakCacheMap[K, V]{
|
||||
m: make(map[K]weak.Pointer[V]),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *WeakCacheMap[K, V]) Load(key K) (value *V, ok bool) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
weakPtr := c.m[key].Value()
|
||||
if weakPtr != nil {
|
||||
return weakPtr, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (c *WeakCacheMap[K, V]) Store(key K, value *V) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
weakPtr := weak.Make(value)
|
||||
c.m[key] = weakPtr
|
||||
runtime.AddCleanup(value, func(struct{}) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
if c.m[key] == weakPtr {
|
||||
delete(c.m, key)
|
||||
}
|
||||
}, struct{}{})
|
||||
}
|
||||
@@ -21,15 +21,15 @@ require (
|
||||
github.com/vishvananda/netlink v1.3.1
|
||||
github.com/xtls/reality v0.0.0-20260322125925-9234c772ba8f
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
||||
golang.org/x/crypto v0.51.0
|
||||
golang.org/x/crypto v0.50.0
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
|
||||
golang.org/x/net v0.54.0
|
||||
golang.org/x/net v0.53.0
|
||||
golang.org/x/sync v0.20.0
|
||||
golang.org/x/sys v0.44.0
|
||||
golang.org/x/sys v0.43.0
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2
|
||||
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb
|
||||
golang.zx2c4.com/wireguard/windows v1.0.1
|
||||
google.golang.org/grpc v1.81.1
|
||||
google.golang.org/grpc v1.81.0
|
||||
google.golang.org/protobuf v1.36.11
|
||||
gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0
|
||||
h12.io/socks v1.0.3
|
||||
@@ -46,10 +46,10 @@ require (
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/quic-go/qpack v0.6.0 // indirect
|
||||
github.com/vishvananda/netns v0.0.5 // indirect
|
||||
golang.org/x/mod v0.35.0 // indirect
|
||||
golang.org/x/text v0.37.0 // indirect
|
||||
golang.org/x/mod v0.34.0 // indirect
|
||||
golang.org/x/text v0.36.0 // indirect
|
||||
golang.org/x/time v0.12.0 // indirect
|
||||
golang.org/x/tools v0.44.0 // indirect
|
||||
golang.org/x/tools v0.43.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
|
||||
@@ -88,18 +88,18 @@ go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBs
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI=
|
||||
golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8=
|
||||
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
|
||||
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
|
||||
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||
golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
|
||||
golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
|
||||
golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
|
||||
golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w=
|
||||
golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ=
|
||||
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
|
||||
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
@@ -111,21 +111,21 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=
|
||||
golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
|
||||
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
|
||||
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
|
||||
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
|
||||
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
|
||||
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
||||
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
|
||||
golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c=
|
||||
golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI=
|
||||
golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=
|
||||
golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -139,8 +139,8 @@ gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
|
||||
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ=
|
||||
google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I=
|
||||
google.golang.org/grpc v1.81.0 h1:W3G9N3KQf3BU+YuCtGKJk0CmxQNbAISICD/9AORxLIw=
|
||||
google.golang.org/grpc v1.81.0/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
||||
@@ -173,26 +173,6 @@ func (c *InboundDetourConfig) Build() (*core.InboundHandlerConfig, error) {
|
||||
return nil, err
|
||||
}
|
||||
receiverSettings.StreamSettings = ss
|
||||
// TODO: Actually implement this breaking change
|
||||
protocol := ss.GetEffectiveProtocol()
|
||||
if (protocol == "websocket" || protocol == "httpupgrade" || protocol == "splithttp") &&
|
||||
(c.StreamSetting.SocketSettings == nil || len(c.StreamSetting.SocketSettings.TrustedXForwardedFor) == 0) {
|
||||
errors.LogWarning(context.Background(),
|
||||
`====== SECURITY WARNING ======`,
|
||||
"\n",
|
||||
`inbound "`, c.Tag, `" using `, protocol, ` has not configured "sockopt.trustedXForwardedFor".`,
|
||||
"\n",
|
||||
`THIS IS VERY INSECURE!!!`,
|
||||
"\n",
|
||||
`For compatibility, Xray still allows this for now and still trusts X-Forwarded-For implicitly.`,
|
||||
"\n",
|
||||
`Please configure "sockopt.trustedXForwardedFor" immediately.`,
|
||||
"\n",
|
||||
`In future versions, this option must be explicitly set.`,
|
||||
"\n",
|
||||
`====== SECURITY WARNING ======`,
|
||||
)
|
||||
}
|
||||
if strings.Contains(ss.SecurityType, "reality") && (receiverSettings.PortList == nil ||
|
||||
len(receiverSettings.PortList.Ports()) != 1 || receiverSettings.PortList.Ports()[0] != 443) {
|
||||
errors.LogWarning(context.Background(), `REALITY: Listening on non-443 ports may get your IP blocked by the GFW`)
|
||||
|
||||
Vendored
+54
-63
@@ -3,8 +3,8 @@ package external
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
"net"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
|
||||
"github.com/xtls/xray-core/common/buf"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/utils"
|
||||
"github.com/xtls/xray-core/main/confloader"
|
||||
)
|
||||
|
||||
@@ -21,10 +20,9 @@ func ConfigLoader(arg string) (out io.Reader, err error) {
|
||||
var data []byte
|
||||
switch {
|
||||
case strings.HasPrefix(arg, "http+unix://"):
|
||||
errors.PrintDeprecatedFeatureWarning(`"http+unix://" prefix`, `direct Unix socket path (e.g. /path/socket.sock:/api or @abstract:/api)`)
|
||||
data, err = FetchHTTPContent(httpUnixToCanonical(arg))
|
||||
data, err = FetchUnixSocketHTTPContent(arg)
|
||||
|
||||
case isRemoteSource(arg):
|
||||
case strings.HasPrefix(arg, "http://"), strings.HasPrefix(arg, "https://"):
|
||||
data, err = FetchHTTPContent(arg)
|
||||
|
||||
case arg == "stdin:":
|
||||
@@ -41,38 +39,19 @@ func ConfigLoader(arg string) (out io.Reader, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// FetchHTTPContent issues an HTTP GET against either a regular HTTP(S) URL
|
||||
// or a Unix socket HTTP endpoint.
|
||||
//
|
||||
// http(s)://host/api regular HTTP(S)
|
||||
// /path/to/socket.sock[:/api] filesystem socket
|
||||
// @abstract[:/api] abstract socket (Linux/Android)
|
||||
// @@padded[:/api] padded abstract socket (HAProxy compat)
|
||||
//
|
||||
// When the ":/" separator is omitted on a socket target, the request is
|
||||
// made to "/".
|
||||
func FetchHTTPContent(target string) ([]byte, error) {
|
||||
httpURL, socketPath := utils.SplitHTTPUnixURL(target)
|
||||
|
||||
parsedTarget, err := url.Parse(httpURL)
|
||||
parsedTarget, err := url.Parse(target)
|
||||
if err != nil {
|
||||
return nil, errors.New("invalid URL: ", target).Base(err)
|
||||
}
|
||||
|
||||
if s := strings.ToLower(parsedTarget.Scheme); s != "http" && s != "https" {
|
||||
return nil, errors.New("invalid scheme: ", parsedTarget.Scheme)
|
||||
}
|
||||
|
||||
client := &http.Client{
|
||||
Timeout: 30 * time.Second,
|
||||
}
|
||||
|
||||
if socketPath != "" {
|
||||
dialAddr := utils.ResolveSocketPath(socketPath)
|
||||
client.Transport = &http.Transport{
|
||||
DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
|
||||
var d net.Dialer
|
||||
return d.DialContext(ctx, "unix", dialAddr)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := client.Do(&http.Request{
|
||||
Method: "GET",
|
||||
URL: parsedTarget,
|
||||
@@ -95,46 +74,58 @@ func FetchHTTPContent(target string) ([]byte, error) {
|
||||
return content, nil
|
||||
}
|
||||
|
||||
|
||||
// isRemoteSource reports whether arg should be fetched via HTTP (regular
|
||||
// network or Unix socket) rather than read from the local filesystem.
|
||||
// Recognized forms:
|
||||
//
|
||||
// - http(s)://... regular HTTP(S)
|
||||
// - @abstract[:/api] abstract socket (Linux/Android)
|
||||
// - /abs/path:/api filesystem socket, explicit HTTP path
|
||||
// - /abs/path filesystem socket detected via os.ModeSocket
|
||||
func isRemoteSource(arg string) bool {
|
||||
if arg == "" {
|
||||
return false
|
||||
// Format: http+unix:///path/to/socket.sock/api/endpoint
|
||||
func FetchUnixSocketHTTPContent(target string) ([]byte, error) {
|
||||
path := strings.TrimPrefix(target, "http+unix://")
|
||||
|
||||
if !strings.HasPrefix(path, "/") {
|
||||
return nil, errors.New("unix socket path must be absolute")
|
||||
}
|
||||
if strings.HasPrefix(arg, "http://") || strings.HasPrefix(arg, "https://") {
|
||||
return true
|
||||
|
||||
var socketPath, httpPath string
|
||||
|
||||
sockIdx := strings.Index(path, ".sock")
|
||||
if sockIdx != -1 {
|
||||
socketPath = path[:sockIdx+5]
|
||||
httpPath = path[sockIdx+5:]
|
||||
if httpPath == "" {
|
||||
httpPath = "/"
|
||||
}
|
||||
} else {
|
||||
return nil, errors.New("cannot determine socket path, socket file should have .sock extension")
|
||||
}
|
||||
if arg[0] == '@' {
|
||||
return true
|
||||
|
||||
if _, err := os.Stat(socketPath); err != nil {
|
||||
return nil, errors.New("socket file not found: ", socketPath).Base(err)
|
||||
}
|
||||
if arg[0] != '/' {
|
||||
return false
|
||||
|
||||
client := &http.Client{
|
||||
Timeout: 30 * time.Second,
|
||||
Transport: &http.Transport{
|
||||
DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
|
||||
var d net.Dialer
|
||||
return d.DialContext(ctx, "unix", socketPath)
|
||||
},
|
||||
},
|
||||
}
|
||||
if strings.Contains(arg, ":/") {
|
||||
return true
|
||||
defer client.CloseIdleConnections()
|
||||
|
||||
resp, err := client.Get("http://localhost" + httpPath)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to fetch from unix socket: ", socketPath).Base(err)
|
||||
}
|
||||
info, err := os.Stat(arg)
|
||||
return err == nil && info.Mode()&os.ModeSocket != 0
|
||||
}
|
||||
|
||||
|
||||
// httpUnixToCanonical converts the deprecated http+unix:///path/to/socket.sock/api
|
||||
// URL into the canonical /path/to/socket.sock:/api form by inserting ":"
|
||||
// between the ".sock" extension and the HTTP path. Inputs without a path
|
||||
// after ".sock" are returned with just the "http+unix://" prefix stripped.
|
||||
func httpUnixToCanonical(target string) string {
|
||||
raw := strings.TrimPrefix(target, "http+unix://")
|
||||
if i := strings.Index(raw, ".sock/"); i >= 0 {
|
||||
raw = raw[:i+5] + ":" + raw[i+5:]
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return nil, errors.New("unexpected HTTP status code: ", resp.StatusCode)
|
||||
}
|
||||
return raw
|
||||
|
||||
content, err := buf.ReadAllToBytes(resp.Body)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to read response").Base(err)
|
||||
}
|
||||
|
||||
return content, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
+22
-1
@@ -91,7 +91,28 @@ func executeRun(cmd *base.Command, args []string) {
|
||||
fmt.Println("Failed to start:", err)
|
||||
os.Exit(-1)
|
||||
}
|
||||
defer server.Close()
|
||||
defer func() {
|
||||
closeErrCh := make(chan error, 1)
|
||||
go func() {
|
||||
closeErrCh <- server.Close()
|
||||
}()
|
||||
select {
|
||||
case err := <-closeErrCh:
|
||||
if err != nil {
|
||||
fmt.Println("Failed to close server:", err)
|
||||
}
|
||||
case <-time.After(10 * time.Second):
|
||||
fmt.Println("Timeout when closing, printing traces:")
|
||||
buf := make([]byte, 1<<20)
|
||||
n := runtime.Stack(buf, true)
|
||||
blocks := strings.Split(string(buf[:n]), "\n\n")
|
||||
for _, block := range blocks {
|
||||
if strings.Contains(block, "github.com/xtls/xray-core/core.(*Instance).Close") {
|
||||
fmt.Println(block)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Explicitly triggering GC to remove garbage from config loading.
|
||||
runtime.GC()
|
||||
|
||||
@@ -2,7 +2,6 @@ package shadowsocks_2022
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
shadowsocks "github.com/sagernet/sing-shadowsocks"
|
||||
"github.com/sagernet/sing-shadowsocks/shadowaead_2022"
|
||||
@@ -19,7 +18,6 @@ import (
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/protocol"
|
||||
"github.com/xtls/xray-core/common/session"
|
||||
"github.com/xtls/xray-core/common/signal"
|
||||
"github.com/xtls/xray-core/common/singbridge"
|
||||
"github.com/xtls/xray-core/features/routing"
|
||||
"github.com/xtls/xray-core/transport/internet/stat"
|
||||
@@ -117,11 +115,7 @@ func (i *Inbound) NewConnection(ctx context.Context, conn net.Conn, metadata M.M
|
||||
})
|
||||
errors.LogInfo(ctx, "tunnelling request to tcp:", metadata.Destination)
|
||||
dispatcher := session.DispatcherFromContext(ctx)
|
||||
destination, err := singbridge.ToDestination(metadata.Destination, net.Network_TCP)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
link, err := dispatcher.Dispatch(ctx, destination)
|
||||
link, err := dispatcher.Dispatch(ctx, singbridge.ToDestination(metadata.Destination, net.Network_TCP))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -142,10 +136,7 @@ func (i *Inbound) NewPacketConnection(ctx context.Context, conn N.PacketConn, me
|
||||
})
|
||||
errors.LogInfo(ctx, "tunnelling request to udp:", metadata.Destination)
|
||||
dispatcher := session.DispatcherFromContext(ctx)
|
||||
destination, err := singbridge.ToDestination(metadata.Destination, net.Network_UDP)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
destination := singbridge.ToDestination(metadata.Destination, net.Network_UDP)
|
||||
link, err := dispatcher.Dispatch(ctx, destination)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -154,9 +145,6 @@ func (i *Inbound) NewPacketConnection(ctx context.Context, conn N.PacketConn, me
|
||||
Reader: link.Reader,
|
||||
Writer: link.Writer,
|
||||
Dest: destination,
|
||||
T: signal.CancelAfterInactivity(ctx, func() {
|
||||
common.Interrupt(link.Reader)
|
||||
}, 300*time.Second),
|
||||
}
|
||||
return bufio.CopyPacketConn(ctx, conn, outConn)
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-shadowsocks/shadowaead_2022"
|
||||
C "github.com/sagernet/sing/common"
|
||||
@@ -23,7 +22,6 @@ import (
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/protocol"
|
||||
"github.com/xtls/xray-core/common/session"
|
||||
"github.com/xtls/xray-core/common/signal"
|
||||
"github.com/xtls/xray-core/common/singbridge"
|
||||
"github.com/xtls/xray-core/common/uuid"
|
||||
"github.com/xtls/xray-core/features/routing"
|
||||
@@ -239,10 +237,11 @@ func (i *MultiUserInbound) NewConnection(ctx context.Context, conn net.Conn, met
|
||||
})
|
||||
errors.LogInfo(ctx, "tunnelling request to tcp:", metadata.Destination)
|
||||
dispatcher := session.DispatcherFromContext(ctx)
|
||||
destination, err := singbridge.ToDestination(metadata.Destination, net.Network_TCP)
|
||||
if err != nil {
|
||||
return err
|
||||
destination := singbridge.ToDestination(metadata.Destination, net.Network_TCP)
|
||||
if !destination.IsValid() {
|
||||
return errors.New("invalid destination")
|
||||
}
|
||||
|
||||
link, err := dispatcher.Dispatch(ctx, destination)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -263,10 +262,7 @@ func (i *MultiUserInbound) NewPacketConnection(ctx context.Context, conn N.Packe
|
||||
})
|
||||
errors.LogInfo(ctx, "tunnelling request to udp:", metadata.Destination)
|
||||
dispatcher := session.DispatcherFromContext(ctx)
|
||||
destination, err := singbridge.ToDestination(metadata.Destination, net.Network_UDP)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
destination := singbridge.ToDestination(metadata.Destination, net.Network_UDP)
|
||||
link, err := dispatcher.Dispatch(ctx, destination)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -275,9 +271,6 @@ func (i *MultiUserInbound) NewPacketConnection(ctx context.Context, conn N.Packe
|
||||
Reader: link.Reader,
|
||||
Writer: link.Writer,
|
||||
Dest: destination,
|
||||
T: signal.CancelAfterInactivity(ctx, func() {
|
||||
common.Interrupt(link.Reader)
|
||||
}, 300*time.Second),
|
||||
}
|
||||
return bufio.CopyPacketConn(ctx, conn, outConn)
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-shadowsocks/shadowaead_2022"
|
||||
C "github.com/sagernet/sing/common"
|
||||
@@ -21,7 +20,6 @@ import (
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/protocol"
|
||||
"github.com/xtls/xray-core/common/session"
|
||||
"github.com/xtls/xray-core/common/signal"
|
||||
"github.com/xtls/xray-core/common/singbridge"
|
||||
"github.com/xtls/xray-core/common/uuid"
|
||||
"github.com/xtls/xray-core/features/routing"
|
||||
@@ -140,11 +138,7 @@ func (i *RelayInbound) NewConnection(ctx context.Context, conn net.Conn, metadat
|
||||
})
|
||||
errors.LogInfo(ctx, "tunnelling request to tcp:", metadata.Destination)
|
||||
dispatcher := session.DispatcherFromContext(ctx)
|
||||
destination, err := singbridge.ToDestination(metadata.Destination, net.Network_TCP)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
link, err := dispatcher.Dispatch(ctx, destination)
|
||||
link, err := dispatcher.Dispatch(ctx, singbridge.ToDestination(metadata.Destination, net.Network_TCP))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -167,10 +161,7 @@ func (i *RelayInbound) NewPacketConnection(ctx context.Context, conn N.PacketCon
|
||||
})
|
||||
errors.LogInfo(ctx, "tunnelling request to udp:", metadata.Destination)
|
||||
dispatcher := session.DispatcherFromContext(ctx)
|
||||
destination, err := singbridge.ToDestination(metadata.Destination, net.Network_UDP)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
destination := singbridge.ToDestination(metadata.Destination, net.Network_UDP)
|
||||
link, err := dispatcher.Dispatch(ctx, destination)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -179,9 +170,6 @@ func (i *RelayInbound) NewPacketConnection(ctx context.Context, conn N.PacketCon
|
||||
Reader: link.Reader,
|
||||
Writer: link.Writer,
|
||||
Dest: destination,
|
||||
T: signal.CancelAfterInactivity(ctx, func() {
|
||||
common.Interrupt(link.Reader)
|
||||
}, 300*time.Second),
|
||||
}
|
||||
return bufio.CopyPacketConn(ctx, conn, outConn)
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ import (
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/session"
|
||||
"github.com/xtls/xray-core/common/signal"
|
||||
"github.com/xtls/xray-core/common/singbridge"
|
||||
"github.com/xtls/xray-core/transport"
|
||||
"github.com/xtls/xray-core/transport/internet"
|
||||
@@ -143,9 +142,6 @@ func (o *Outbound) Process(ctx context.Context, link *transport.Link, dialer int
|
||||
Writer: link.Writer,
|
||||
Conn: inboundConn,
|
||||
Dest: destination,
|
||||
T: signal.CancelAfterInactivity(ctx, func() {
|
||||
common.Interrupt(link.Reader)
|
||||
}, 300*time.Second),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
@@ -39,7 +40,6 @@ type xdnsConnClient struct {
|
||||
net.PacketConn
|
||||
|
||||
resolverAddrs []*net.UDPAddr
|
||||
resolverTypes []uint16
|
||||
resolverIdx uint32
|
||||
resolverSend map[string]*atomic.Uint32
|
||||
|
||||
@@ -61,15 +61,17 @@ func NewConnClient(c *Config, raw net.PacketConn) (net.PacketConn, error) {
|
||||
|
||||
var domains []Name
|
||||
var servers []string
|
||||
var resolverTypes []uint16
|
||||
for _, rs := range c.Resolvers {
|
||||
domain, server, resolverType, err := parseResolver(rs)
|
||||
parts := strings.Split(rs, "+udp://")
|
||||
if len(parts) != 2 {
|
||||
return nil, errors.New("invalid resolvers")
|
||||
}
|
||||
domain, err := ParseName(parts[0])
|
||||
if err != nil {
|
||||
return nil, errors.New("invalid resolvers").Base(err)
|
||||
return nil, err
|
||||
}
|
||||
domains = append(domains, domain)
|
||||
servers = append(servers, server)
|
||||
resolverTypes = append(resolverTypes, resolverType)
|
||||
servers = append(servers, parts[1])
|
||||
}
|
||||
|
||||
var resolverAddrs []*net.UDPAddr
|
||||
@@ -96,7 +98,6 @@ func NewConnClient(c *Config, raw net.PacketConn) (net.PacketConn, error) {
|
||||
PacketConn: raw,
|
||||
|
||||
resolverAddrs: resolverAddrs,
|
||||
resolverTypes: resolverTypes,
|
||||
resolverIdx: 0,
|
||||
resolverSend: resolverSend,
|
||||
|
||||
@@ -215,7 +216,7 @@ func (c *xdnsConnClient) sendLoop() {
|
||||
default:
|
||||
}
|
||||
} else {
|
||||
encoded, _ := encode(nil, c.clientID, c.domains[c.resolverIdx], c.resolverTypes[c.resolverIdx])
|
||||
encoded, _ := encode(nil, c.clientID, c.domains[c.resolverIdx])
|
||||
p = &packet{
|
||||
p: encoded,
|
||||
}
|
||||
@@ -275,8 +276,7 @@ func (c *xdnsConnClient) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||
return 0, io.ErrClosedPipe
|
||||
}
|
||||
|
||||
idx := c.resolverIdx % uint32(len(c.resolverAddrs))
|
||||
encoded, err := encode(p, c.clientID, c.domains[idx], c.resolverTypes[idx])
|
||||
encoded, err := encode(p, c.clientID, c.domains[c.resolverIdx%uint32(len(c.resolverAddrs))])
|
||||
if err != nil {
|
||||
errors.LogDebug(context.Background(), addr, " xdns wireformat err ", err, " ", len(p))
|
||||
return 0, nil
|
||||
@@ -299,7 +299,7 @@ func (c *xdnsConnClient) Close() error {
|
||||
return c.PacketConn.Close()
|
||||
}
|
||||
|
||||
func encode(p []byte, clientID []byte, domain Name, qtype uint16) ([]byte, error) {
|
||||
func encode(p []byte, clientID []byte, domain Name) ([]byte, error) {
|
||||
var decoded []byte
|
||||
{
|
||||
if len(p) >= 224 {
|
||||
@@ -338,7 +338,7 @@ func encode(p []byte, clientID []byte, domain Name, qtype uint16) ([]byte, error
|
||||
Question: []Question{
|
||||
{
|
||||
Name: name,
|
||||
Type: qtype,
|
||||
Type: RRTypeTXT,
|
||||
Class: ClassIN,
|
||||
},
|
||||
},
|
||||
@@ -396,22 +396,29 @@ func dnsResponsePayload(resp *Message, domains []Name) []byte {
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(resp.Answer) == 0 {
|
||||
if len(resp.Answer) != 1 {
|
||||
return nil
|
||||
}
|
||||
answer := resp.Answer[0]
|
||||
|
||||
var ok bool
|
||||
for _, domain := range domains {
|
||||
_, ok = answer.Name.TrimSuffix(domain)
|
||||
if ok {
|
||||
break
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, answer := range resp.Answer {
|
||||
var ok bool
|
||||
for _, domain := range domains {
|
||||
_, ok = answer.Name.TrimSuffix(domain)
|
||||
if ok {
|
||||
break
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
if answer.Type != RRTypeTXT {
|
||||
return nil
|
||||
}
|
||||
payload, err := DecodeRDataTXT(answer.Data)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return decodeResponsePayload(resp.Answer)
|
||||
return payload
|
||||
}
|
||||
|
||||
@@ -45,14 +45,8 @@ var (
|
||||
)
|
||||
|
||||
const (
|
||||
// https://tools.ietf.org/html/rfc1035#section-3.2.2
|
||||
RRTypeA = 1
|
||||
// https://tools.ietf.org/html/rfc1035#section-3.2.2
|
||||
RRTypeCNAME = 5
|
||||
// https://tools.ietf.org/html/rfc1035#section-3.2.2
|
||||
RRTypeTXT = 16
|
||||
// https://tools.ietf.org/html/rfc3596#section-2.1
|
||||
RRTypeAAAA = 28
|
||||
// https://tools.ietf.org/html/rfc6891#section-6.1.1
|
||||
RRTypeOPT = 41
|
||||
|
||||
|
||||
@@ -593,113 +593,3 @@ func TestRDataTXTRoundTrip(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIPAnswerPayloadRoundTrip(t *testing.T) {
|
||||
for _, rrType := range []uint16{RRTypeA, RRTypeAAAA} {
|
||||
for _, payload := range [][]byte{
|
||||
{},
|
||||
{0x01},
|
||||
[]byte("hello world"),
|
||||
bytes.Repeat([]byte{0xab}, payloadChunkSizeForType(rrType)*3+1),
|
||||
} {
|
||||
question := Question{
|
||||
Name: mustParseName("example.com"),
|
||||
Type: rrType,
|
||||
Class: ClassIN,
|
||||
}
|
||||
answers, err := answersForPayload(question, responseTTL, payload)
|
||||
if err != nil {
|
||||
t.Fatalf("answersForPayload(%d) err = %v", rrType, err)
|
||||
}
|
||||
|
||||
if len(answers) > 1 {
|
||||
answers[0], answers[len(answers)-1] = answers[len(answers)-1], answers[0]
|
||||
}
|
||||
|
||||
decoded := decodeResponsePayload(answers)
|
||||
if !bytes.Equal(decoded, payload) {
|
||||
t.Fatalf("rrType=%d decoded %x want %x", rrType, decoded, payload)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseResolver(t *testing.T) {
|
||||
tests := []struct {
|
||||
resolver string
|
||||
rrType uint16
|
||||
}{
|
||||
{"example.com+udp://1.1.1.1:53", RRTypeTXT},
|
||||
{"example.com:txt+udp://1.1.1.1:53", RRTypeTXT},
|
||||
{"example.com:a+udp://1.1.1.1:53", RRTypeA},
|
||||
{"example.com:aaaa+udp://1.1.1.1:53", RRTypeAAAA},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
domain, server, rrType, err := parseResolver(test.resolver)
|
||||
if err != nil {
|
||||
t.Fatalf("parseResolver(%q) err = %v", test.resolver, err)
|
||||
}
|
||||
if domain.String() != "example.com" || server != "1.1.1.1:53" || rrType != test.rrType {
|
||||
t.Fatalf("parseResolver(%q) = (%q, %q, %d)", test.resolver, domain.String(), server, rrType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseDomainSpec(t *testing.T) {
|
||||
tests := []struct {
|
||||
spec string
|
||||
def string
|
||||
rrType uint16
|
||||
wantErr bool
|
||||
}{
|
||||
{"example.com", "", 0, false},
|
||||
{"example.com", "txt", RRTypeTXT, false},
|
||||
{"example.com:a", "", RRTypeA, false},
|
||||
{"example.com:aaaa", "", RRTypeAAAA, false},
|
||||
{"example.com:doh", "", 0, true},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
got, err := parseDomainSpec(test.spec, test.def)
|
||||
if test.wantErr {
|
||||
if err == nil {
|
||||
t.Fatalf("parseDomainSpec(%q, %q) err = nil", test.spec, test.def)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("parseDomainSpec(%q, %q) err = %v", test.spec, test.def, err)
|
||||
}
|
||||
if got.name.String() != "example.com" || got.rrType != test.rrType {
|
||||
t.Fatalf("parseDomainSpec(%q, %q) = (%q, %d)", test.spec, test.def, got.name.String(), got.rrType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestResponseForMethodRestriction(t *testing.T) {
|
||||
query := &Message{
|
||||
ID: 1,
|
||||
Flags: 0x0100,
|
||||
Question: []Question{{
|
||||
Name: mustParseName("abc.example.com"),
|
||||
Type: RRTypeTXT,
|
||||
Class: ClassIN,
|
||||
}},
|
||||
Additional: []RR{{
|
||||
Name: Name{},
|
||||
Type: RRTypeOPT,
|
||||
Class: 4096,
|
||||
}},
|
||||
}
|
||||
|
||||
resp, _ := responseFor(query, []domainSpec{{name: mustParseName("example.com"), rrType: RRTypeA}})
|
||||
if resp == nil || resp.Rcode() != RcodeNameError {
|
||||
t.Fatalf("responseFor method restriction rcode = %v", resp)
|
||||
}
|
||||
|
||||
resp, _ = responseFor(query, []domainSpec{{name: mustParseName("example.com")}})
|
||||
if resp == nil || resp.Rcode() != RcodeNoError {
|
||||
t.Fatalf("responseFor unrestricted rcode = %v", resp)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,226 +0,0 @@
|
||||
package xdns
|
||||
|
||||
import "bytes"
|
||||
|
||||
const ipRecordHeaderSize = 2
|
||||
|
||||
func maxEncodedPayloadForType(rrType uint16) int {
|
||||
switch rrType {
|
||||
case RRTypeA:
|
||||
return maxEncodedPayloadA
|
||||
case RRTypeAAAA:
|
||||
return maxEncodedPayloadAAAA
|
||||
default:
|
||||
return maxEncodedPayloadTXT
|
||||
}
|
||||
}
|
||||
|
||||
func rrDataSizeForType(rrType uint16) int {
|
||||
switch rrType {
|
||||
case RRTypeA:
|
||||
return 4
|
||||
case RRTypeAAAA:
|
||||
return 16
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func payloadChunkSizeForType(rrType uint16) int {
|
||||
size := rrDataSizeForType(rrType)
|
||||
if size <= ipRecordHeaderSize {
|
||||
return 0
|
||||
}
|
||||
return size - ipRecordHeaderSize
|
||||
}
|
||||
|
||||
func answersForPayload(question Question, ttl uint32, payload []byte) ([]RR, error) {
|
||||
switch question.Type {
|
||||
case RRTypeTXT:
|
||||
return []RR{
|
||||
{
|
||||
Name: question.Name,
|
||||
Type: question.Type,
|
||||
Class: question.Class,
|
||||
TTL: ttl,
|
||||
Data: EncodeRDataTXT(payload),
|
||||
},
|
||||
}, nil
|
||||
case RRTypeA, RRTypeAAAA:
|
||||
return ipAnswersForPayload(question, ttl, payload)
|
||||
default:
|
||||
return nil, ErrIntegerOverflow
|
||||
}
|
||||
}
|
||||
|
||||
func ipAnswersForPayload(question Question, ttl uint32, payload []byte) ([]RR, error) {
|
||||
chunkSize := payloadChunkSizeForType(question.Type)
|
||||
rrDataSize := rrDataSizeForType(question.Type)
|
||||
if chunkSize == 0 || rrDataSize == 0 {
|
||||
return nil, ErrIntegerOverflow
|
||||
}
|
||||
|
||||
numRecords := 1
|
||||
if len(payload) > 0 {
|
||||
numRecords = (len(payload) + chunkSize - 1) / chunkSize
|
||||
}
|
||||
if numRecords > 256 {
|
||||
return nil, ErrIntegerOverflow
|
||||
}
|
||||
|
||||
answers := make([]RR, 0, numRecords)
|
||||
for i := 0; i < numRecords; i++ {
|
||||
offset := i * chunkSize
|
||||
n := len(payload) - offset
|
||||
if n < 0 {
|
||||
n = 0
|
||||
}
|
||||
if n > chunkSize {
|
||||
n = chunkSize
|
||||
}
|
||||
|
||||
data := make([]byte, rrDataSize)
|
||||
data[0] = byte(i)
|
||||
data[1] = byte(n)
|
||||
copy(data[ipRecordHeaderSize:], payload[offset:offset+n])
|
||||
|
||||
answers = append(answers, RR{
|
||||
Name: question.Name,
|
||||
Type: question.Type,
|
||||
Class: question.Class,
|
||||
TTL: ttl,
|
||||
Data: data,
|
||||
})
|
||||
}
|
||||
|
||||
return answers, nil
|
||||
}
|
||||
|
||||
func decodeResponsePayload(answers []RR) []byte {
|
||||
if len(answers) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch answers[0].Type {
|
||||
case RRTypeTXT:
|
||||
if len(answers) != 1 {
|
||||
return nil
|
||||
}
|
||||
payload, err := DecodeRDataTXT(answers[0].Data)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return payload
|
||||
case RRTypeA, RRTypeAAAA:
|
||||
return decodeIPAnswerPayload(answers, answers[0].Type)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func decodeIPAnswerPayload(answers []RR, rrType uint16) []byte {
|
||||
chunkSize := payloadChunkSizeForType(rrType)
|
||||
rrDataSize := rrDataSizeForType(rrType)
|
||||
if chunkSize == 0 || rrDataSize == 0 || len(answers) > 256 {
|
||||
return nil
|
||||
}
|
||||
|
||||
parts := make([][]byte, len(answers))
|
||||
for _, answer := range answers {
|
||||
if answer.Type != rrType || len(answer.Data) != rrDataSize {
|
||||
return nil
|
||||
}
|
||||
idx := int(answer.Data[0])
|
||||
n := int(answer.Data[1])
|
||||
if idx >= len(answers) || n > chunkSize || parts[idx] != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
part := make([]byte, n)
|
||||
copy(part, answer.Data[ipRecordHeaderSize:ipRecordHeaderSize+n])
|
||||
parts[idx] = part
|
||||
}
|
||||
|
||||
var payload bytes.Buffer
|
||||
for _, part := range parts {
|
||||
if part == nil {
|
||||
return nil
|
||||
}
|
||||
payload.Write(part)
|
||||
}
|
||||
return payload.Bytes()
|
||||
}
|
||||
|
||||
func computeMaxEncodedPayload(limit int) int {
|
||||
return computeMaxEncodedPayloadForType(limit, RRTypeTXT)
|
||||
}
|
||||
|
||||
func computeMaxEncodedPayloadForType(limit int, rrType uint16) int {
|
||||
maxLengthName, err := NewName([][]byte{
|
||||
[]byte("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"),
|
||||
[]byte("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"),
|
||||
[]byte("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"),
|
||||
[]byte("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"),
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
{
|
||||
n := 0
|
||||
for _, label := range maxLengthName {
|
||||
n += len(label) + 1
|
||||
}
|
||||
n += 1
|
||||
if n != 255 {
|
||||
panic("computeMaxEncodedPayload n != 255")
|
||||
}
|
||||
}
|
||||
|
||||
queryLimit := uint16(limit)
|
||||
if int(queryLimit) != limit {
|
||||
queryLimit = 0xffff
|
||||
}
|
||||
query := &Message{
|
||||
Question: []Question{
|
||||
{
|
||||
Name: maxLengthName,
|
||||
Type: rrType,
|
||||
Class: ClassIN,
|
||||
},
|
||||
},
|
||||
Additional: []RR{
|
||||
{
|
||||
Name: Name{},
|
||||
Type: RRTypeOPT,
|
||||
Class: queryLimit,
|
||||
TTL: 0,
|
||||
Data: []byte{},
|
||||
},
|
||||
},
|
||||
}
|
||||
resp, _ := responseFor(query, []domainSpec{{name: Name{[]byte{}}}})
|
||||
|
||||
low := 0
|
||||
high := 32768
|
||||
if chunkSize := payloadChunkSizeForType(rrType); chunkSize > 0 {
|
||||
high = 256*chunkSize + 1
|
||||
}
|
||||
for low+1 < high {
|
||||
mid := (low + high) / 2
|
||||
resp.Answer, err = answersForPayload(query.Question[0], responseTTL, make([]byte, mid))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
buf, err := resp.WireFormat()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if len(buf) <= limit {
|
||||
low = mid
|
||||
} else {
|
||||
high = mid
|
||||
}
|
||||
}
|
||||
|
||||
return low
|
||||
}
|
||||
@@ -21,10 +21,8 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
maxUDPPayload = 1280 - 40 - 8
|
||||
maxEncodedPayloadTXT = computeMaxEncodedPayloadForType(maxUDPPayload, RRTypeTXT)
|
||||
maxEncodedPayloadA = computeMaxEncodedPayloadForType(maxUDPPayload, RRTypeA)
|
||||
maxEncodedPayloadAAAA = computeMaxEncodedPayloadForType(maxUDPPayload, RRTypeAAAA)
|
||||
maxUDPPayload = 1280 - 40 - 8
|
||||
maxEncodedPayload = computeMaxEncodedPayload(maxUDPPayload)
|
||||
)
|
||||
|
||||
func clientIDToAddr(clientID [8]byte) *net.UDPAddr {
|
||||
@@ -46,16 +44,15 @@ type record struct {
|
||||
}
|
||||
|
||||
type queue struct {
|
||||
last time.Time
|
||||
rrType uint16
|
||||
queue chan []byte
|
||||
stash chan []byte
|
||||
last time.Time
|
||||
queue chan []byte
|
||||
stash chan []byte
|
||||
}
|
||||
|
||||
type xdnsConnServer struct {
|
||||
net.PacketConn
|
||||
|
||||
domains []domainSpec
|
||||
domains []Name
|
||||
|
||||
ch chan *record
|
||||
readQueue chan *packet
|
||||
@@ -69,9 +66,9 @@ func NewConnServer(c *Config, raw net.PacketConn) (net.PacketConn, error) {
|
||||
if len(c.Domains) == 0 {
|
||||
return nil, errors.New("empty domains")
|
||||
}
|
||||
domains := make([]domainSpec, 0, len(c.Domains))
|
||||
domains := make([]Name, 0, len(c.Domains))
|
||||
for _, domain := range c.Domains {
|
||||
domain, err := parseDomainSpec(domain, "")
|
||||
domain, err := ParseName(domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -237,7 +234,6 @@ func (c *xdnsConnServer) recvLoop() {
|
||||
func (c *xdnsConnServer) sendLoop() {
|
||||
var nextRec *record
|
||||
for {
|
||||
var err error
|
||||
rec := nextRec
|
||||
nextRec = nil
|
||||
|
||||
@@ -250,8 +246,18 @@ func (c *xdnsConnServer) sendLoop() {
|
||||
}
|
||||
|
||||
if rec.Resp.Rcode() == RcodeNoError && len(rec.Resp.Question) == 1 {
|
||||
rec.Resp.Answer = []RR{
|
||||
{
|
||||
Name: rec.Resp.Question[0].Name,
|
||||
Type: rec.Resp.Question[0].Type,
|
||||
Class: rec.Resp.Question[0].Class,
|
||||
TTL: responseTTL,
|
||||
Data: nil,
|
||||
},
|
||||
}
|
||||
|
||||
var payload bytes.Buffer
|
||||
limit := maxEncodedPayloadForType(rec.Resp.Question[0].Type)
|
||||
limit := maxEncodedPayload
|
||||
timer := time.NewTimer(maxResponseDelay)
|
||||
|
||||
for {
|
||||
@@ -261,7 +267,6 @@ func (c *xdnsConnServer) sendLoop() {
|
||||
c.mutex.Unlock()
|
||||
return
|
||||
}
|
||||
q.rrType = rec.Resp.Question[0].Type
|
||||
c.mutex.Unlock()
|
||||
|
||||
var p []byte
|
||||
@@ -289,11 +294,7 @@ func (c *xdnsConnServer) sendLoop() {
|
||||
}
|
||||
|
||||
limit -= 2 + len(p)
|
||||
if limit < 0 {
|
||||
if payload.Len() == 0 {
|
||||
errors.LogDebug(context.Background(), rec.Addr, " ", rec.ClientAddr, " xdns payload too large for rrtype ", rec.Resp.Question[0].Type, " ", len(p))
|
||||
continue
|
||||
}
|
||||
if payload.Len() > 0 && limit < 0 {
|
||||
c.stash(q, p)
|
||||
break
|
||||
}
|
||||
@@ -307,11 +308,7 @@ func (c *xdnsConnServer) sendLoop() {
|
||||
}
|
||||
|
||||
timer.Stop()
|
||||
rec.Resp.Answer, err = answersForPayload(rec.Resp.Question[0], responseTTL, payload.Bytes())
|
||||
if err != nil {
|
||||
errors.LogDebug(context.Background(), rec.Addr, " ", rec.ClientAddr, " xdns encode err ", err)
|
||||
continue
|
||||
}
|
||||
rec.Resp.Answer[0].Data = EncodeRDataTXT(payload.Bytes())
|
||||
}
|
||||
|
||||
buf, err := rec.Resp.WireFormat()
|
||||
@@ -352,6 +349,11 @@ func (c *xdnsConnServer) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
||||
}
|
||||
|
||||
func (c *xdnsConnServer) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||
if len(p)+2 > maxEncodedPayload {
|
||||
errors.LogDebug(context.Background(), addr, " mask write err short write ", len(p), "+2 > ", maxEncodedPayload)
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
|
||||
@@ -359,14 +361,6 @@ func (c *xdnsConnServer) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||
if q == nil {
|
||||
return 0, io.ErrClosedPipe
|
||||
}
|
||||
limit := maxEncodedPayloadForType(q.rrType)
|
||||
if q.rrType == 0 {
|
||||
limit = maxEncodedPayloadTXT
|
||||
}
|
||||
if len(p)+2 > limit {
|
||||
errors.LogDebug(context.Background(), addr, " mask write err short write ", len(p), "+2 > ", limit)
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
buf := make([]byte, len(p))
|
||||
copy(buf, p)
|
||||
@@ -412,7 +406,7 @@ func nextPacketServer(r *bytes.Reader) ([]byte, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func responseFor(query *Message, domains []domainSpec) (*Message, []byte) {
|
||||
func responseFor(query *Message, domains []Name) (*Message, []byte) {
|
||||
resp := &Message{
|
||||
ID: query.ID,
|
||||
Flags: 0x8000,
|
||||
@@ -460,15 +454,11 @@ func responseFor(query *Message, domains []domainSpec) (*Message, []byte) {
|
||||
}
|
||||
question := query.Question[0]
|
||||
|
||||
var (
|
||||
prefix Name
|
||||
ok bool
|
||||
match domainSpec
|
||||
)
|
||||
var prefix Name
|
||||
var ok bool
|
||||
for _, domain := range domains {
|
||||
prefix, ok = question.Name.TrimSuffix(domain.name)
|
||||
prefix, ok = question.Name.TrimSuffix(domain)
|
||||
if ok {
|
||||
match = domain
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -483,13 +473,7 @@ func responseFor(query *Message, domains []domainSpec) (*Message, []byte) {
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
switch question.Type {
|
||||
case RRTypeTXT, RRTypeA, RRTypeAAAA:
|
||||
default:
|
||||
resp.Flags |= RcodeNameError
|
||||
return resp, nil
|
||||
}
|
||||
if match.rrType != 0 && question.Type != match.rrType {
|
||||
if question.Type != RRTypeTXT {
|
||||
resp.Flags |= RcodeNameError
|
||||
return resp, nil
|
||||
}
|
||||
@@ -510,3 +494,78 @@ func responseFor(query *Message, domains []domainSpec) (*Message, []byte) {
|
||||
|
||||
return resp, payload
|
||||
}
|
||||
|
||||
func computeMaxEncodedPayload(limit int) int {
|
||||
maxLengthName, err := NewName([][]byte{
|
||||
[]byte("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"),
|
||||
[]byte("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"),
|
||||
[]byte("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"),
|
||||
[]byte("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"),
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
{
|
||||
n := 0
|
||||
for _, label := range maxLengthName {
|
||||
n += len(label) + 1
|
||||
}
|
||||
n += 1
|
||||
if n != 255 {
|
||||
panic("computeMaxEncodedPayload n != 255")
|
||||
}
|
||||
}
|
||||
|
||||
queryLimit := uint16(limit)
|
||||
if int(queryLimit) != limit {
|
||||
queryLimit = 0xffff
|
||||
}
|
||||
query := &Message{
|
||||
Question: []Question{
|
||||
{
|
||||
Name: maxLengthName,
|
||||
Type: RRTypeTXT,
|
||||
Class: RRTypeTXT,
|
||||
},
|
||||
},
|
||||
|
||||
Additional: []RR{
|
||||
{
|
||||
Name: Name{},
|
||||
Type: RRTypeOPT,
|
||||
Class: queryLimit,
|
||||
TTL: 0,
|
||||
Data: []byte{},
|
||||
},
|
||||
},
|
||||
}
|
||||
resp, _ := responseFor(query, []Name{[][]byte{}})
|
||||
|
||||
resp.Answer = []RR{
|
||||
{
|
||||
Name: query.Question[0].Name,
|
||||
Type: query.Question[0].Type,
|
||||
Class: query.Question[0].Class,
|
||||
TTL: responseTTL,
|
||||
Data: nil,
|
||||
},
|
||||
}
|
||||
|
||||
low := 0
|
||||
high := 32768
|
||||
for low+1 < high {
|
||||
mid := (low + high) / 2
|
||||
resp.Answer[0].Data = EncodeRDataTXT(make([]byte, mid))
|
||||
buf, err := resp.WireFormat()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if len(buf) <= limit {
|
||||
low = mid
|
||||
} else {
|
||||
high = mid
|
||||
}
|
||||
}
|
||||
|
||||
return low
|
||||
}
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
package xdns
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
)
|
||||
|
||||
type domainSpec struct {
|
||||
name Name
|
||||
rrType uint16
|
||||
}
|
||||
|
||||
func rrTypeFromMethod(method string) (uint16, error) {
|
||||
switch strings.ToLower(method) {
|
||||
case "", "txt":
|
||||
return RRTypeTXT, nil
|
||||
case "a":
|
||||
return RRTypeA, nil
|
||||
case "aaaa":
|
||||
return RRTypeAAAA, nil
|
||||
default:
|
||||
return 0, errors.New("unsupported method")
|
||||
}
|
||||
}
|
||||
|
||||
func parseDomainSpec(s string, defaultMethod string) (domainSpec, error) {
|
||||
domainPart := s
|
||||
method := ""
|
||||
hasMethod := false
|
||||
|
||||
if i := strings.LastIndex(s, ":"); i >= 0 {
|
||||
domainPart = s[:i]
|
||||
method = s[i+1:]
|
||||
hasMethod = true
|
||||
} else if defaultMethod != "" {
|
||||
method = defaultMethod
|
||||
hasMethod = true
|
||||
}
|
||||
|
||||
if domainPart == "" {
|
||||
return domainSpec{}, errors.New("empty domain")
|
||||
}
|
||||
|
||||
name, err := ParseName(domainPart)
|
||||
if err != nil {
|
||||
return domainSpec{}, err
|
||||
}
|
||||
|
||||
rrType := uint16(0)
|
||||
if hasMethod {
|
||||
var err error
|
||||
rrType, err = rrTypeFromMethod(method)
|
||||
if err != nil {
|
||||
return domainSpec{}, err
|
||||
}
|
||||
}
|
||||
|
||||
return domainSpec{
|
||||
name: name,
|
||||
rrType: rrType,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseResolver(s string) (Name, string, uint16, error) {
|
||||
head, server, ok := strings.Cut(s, "+udp://")
|
||||
if !ok {
|
||||
return nil, "", 0, errors.New("invalid resolver scheme")
|
||||
}
|
||||
if server == "" {
|
||||
return nil, "", 0, errors.New("empty resolver server")
|
||||
}
|
||||
|
||||
spec, err := parseDomainSpec(head, "txt")
|
||||
if err != nil {
|
||||
return nil, "", 0, err
|
||||
}
|
||||
|
||||
return spec.name, server, spec.rrType, nil
|
||||
}
|
||||
@@ -309,7 +309,7 @@ func Listen(ctx context.Context, address net.Address, port net.Port, streamSetti
|
||||
|
||||
tr := &quic.Transport{Conn: pktConn}
|
||||
|
||||
listener, err := tr.Listen(tlsConfig.GetTLSConfig(tls.WithNextProto("h3")), quicConfig)
|
||||
listener, err := tr.Listen(tlsConfig.GetTLSConfig(), quicConfig)
|
||||
if err != nil {
|
||||
_ = tr.Close()
|
||||
_ = pktConn.Close()
|
||||
|
||||
@@ -183,8 +183,6 @@ func createHTTPClient(dest net.Destination, streamSettings *internet.MemoryStrea
|
||||
if quicParams.KeepAlivePeriod == 0 {
|
||||
if keepAlivePeriod == 0 {
|
||||
quicConfig.KeepAlivePeriod = net.QuicgoH3KeepAlivePeriod
|
||||
} else if keepAlivePeriod > 0 {
|
||||
quicConfig.KeepAlivePeriod = keepAlivePeriod
|
||||
}
|
||||
}
|
||||
if quicParams.MaxIncomingStreams == 0 {
|
||||
@@ -508,8 +506,6 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me
|
||||
var seq int64
|
||||
var lastWrite time.Time
|
||||
|
||||
dynamicHTTPClient := httpClient
|
||||
dynamicXmuxClient := xmuxClient
|
||||
for {
|
||||
// by offloading the uploads into a buffered pipe, multiple conn.Write
|
||||
// calls get automatically batched together into larger POST requests.
|
||||
@@ -544,13 +540,13 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me
|
||||
|
||||
lastWrite = time.Now()
|
||||
|
||||
if dynamicXmuxClient != nil && (dynamicXmuxClient.LeftRequests.Add(-1) <= 0 ||
|
||||
(dynamicXmuxClient.UnreusableAt != time.Time{} && lastWrite.After(dynamicXmuxClient.UnreusableAt))) {
|
||||
dynamicHTTPClient, dynamicXmuxClient = getHTTPClient(ctx, dest, streamSettings)
|
||||
if xmuxClient != nil && (xmuxClient.LeftRequests.Add(-1) <= 0 ||
|
||||
(xmuxClient.UnreusableAt != time.Time{} && lastWrite.After(xmuxClient.UnreusableAt))) {
|
||||
httpClient, xmuxClient = getHTTPClient(ctx, dest, streamSettings)
|
||||
}
|
||||
|
||||
go func(hClient DialerClient) {
|
||||
err := hClient.PostPacket(
|
||||
go func() {
|
||||
err := httpClient.PostPacket(
|
||||
ctx,
|
||||
requestURL.String(),
|
||||
sessionId,
|
||||
@@ -563,9 +559,9 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me
|
||||
uploadPipeReader.Interrupt()
|
||||
doSplit.Store(false)
|
||||
}
|
||||
}(dynamicHTTPClient)
|
||||
}()
|
||||
|
||||
if _, ok := dynamicHTTPClient.(*DefaultDialerClient); ok {
|
||||
if _, ok := httpClient.(*DefaultDialerClient); ok {
|
||||
<-wroteRequest.Wait()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user