mirror of
https://github.com/XTLS/Xray-core.git
synced 2025-08-22 09:36:49 +08:00
Compare commits
7 Commits
7f23a1cb65
...
dbd9125686
Author | SHA1 | Date | |
---|---|---|---|
![]() |
dbd9125686 | ||
![]() |
923b5d7229 | ||
![]() |
f90fae22aa | ||
![]() |
b065595f58 | ||
![]() |
308f8a7459 | ||
![]() |
050f596e8f | ||
![]() |
3b47d0846e |
@@ -88,11 +88,12 @@
|
||||
- [SaeedDev94/Xray](https://github.com/SaeedDev94/Xray)
|
||||
- [SimpleXray](https://github.com/lhear/SimpleXray)
|
||||
- [AnyPortal](https://github.com/AnyPortal/AnyPortal)
|
||||
- iOS & macOS arm64
|
||||
- [Happ](https://apps.apple.com/app/happ-proxy-utility/id6504287215)
|
||||
- iOS & macOS arm64 & tvOS
|
||||
- [Happ](https://apps.apple.com/app/happ-proxy-utility/id6504287215) ([tvOS](https://apps.apple.com/us/app/happ-proxy-utility-for-tv/id6748297274))
|
||||
- [Streisand](https://apps.apple.com/app/streisand/id6450534064)
|
||||
- [OneXray](https://github.com/OneXray/OneXray)
|
||||
- macOS arm64 & x64
|
||||
- [Happ](https://apps.apple.com/app/happ-proxy-utility/id6504287215)
|
||||
- [V2rayU](https://github.com/yanue/V2rayU)
|
||||
- [V2RayXS](https://github.com/tzmax/V2RayXS)
|
||||
- [Furious](https://github.com/LorenEteval/Furious)
|
||||
@@ -106,7 +107,7 @@
|
||||
|
||||
## Others that support VLESS, XTLS, REALITY, XUDP, PLUX...
|
||||
|
||||
- iOS & macOS arm64
|
||||
- iOS & macOS arm64 & tvOS
|
||||
- [Shadowrocket](https://apps.apple.com/app/shadowrocket/id932747118)
|
||||
- [Loon](https://apps.apple.com/us/app/loon/id1373567447)
|
||||
- Xray Tools
|
||||
|
@@ -204,7 +204,12 @@ func (s *DNS) LookupIP(domain string, option dns.IPOption) ([]net.IP, uint32, er
|
||||
}
|
||||
|
||||
// Static host lookup
|
||||
switch addrs := s.hosts.Lookup(domain, option); {
|
||||
switch addrs, err := s.hosts.Lookup(domain, option); {
|
||||
case err != nil:
|
||||
if go_errors.Is(err, dns.ErrEmptyResponse) {
|
||||
return nil, 0, dns.ErrEmptyResponse
|
||||
}
|
||||
return nil, 0, errors.New("returning nil for domain ", domain).Base(err)
|
||||
case addrs == nil: // Domain not recorded in static host
|
||||
break
|
||||
case len(addrs) == 0: // Domain recorded, but no valid IP returned (e.g. IPv4 address with only IPv6 enabled)
|
||||
|
@@ -2,6 +2,8 @@ package dns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/strmatcher"
|
||||
@@ -31,7 +33,15 @@ func NewStaticHosts(hosts []*Config_HostMapping) (*StaticHosts, error) {
|
||||
ips := make([]net.Address, 0, len(mapping.Ip)+1)
|
||||
switch {
|
||||
case len(mapping.ProxiedDomain) > 0:
|
||||
ips = append(ips, net.DomainAddress(mapping.ProxiedDomain))
|
||||
if mapping.ProxiedDomain[0] == '#' {
|
||||
rcode, err := strconv.Atoi(mapping.ProxiedDomain[1:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ips = append(ips, dns.RCodeError(rcode))
|
||||
} else {
|
||||
ips = append(ips, net.DomainAddress(mapping.ProxiedDomain))
|
||||
}
|
||||
case len(mapping.Ip) > 0:
|
||||
for _, ip := range mapping.Ip {
|
||||
addr := net.IPAddress(ip)
|
||||
@@ -58,38 +68,51 @@ func filterIP(ips []net.Address, option dns.IPOption) []net.Address {
|
||||
return filtered
|
||||
}
|
||||
|
||||
func (h *StaticHosts) lookupInternal(domain string) []net.Address {
|
||||
func (h *StaticHosts) lookupInternal(domain string) ([]net.Address, error) {
|
||||
ips := make([]net.Address, 0)
|
||||
found := false
|
||||
for _, id := range h.matchers.Match(domain) {
|
||||
for _, v := range h.ips[id] {
|
||||
if err, ok := v.(dns.RCodeError); ok {
|
||||
if uint16(err) == 0 {
|
||||
return nil, dns.ErrEmptyResponse
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
ips = append(ips, h.ips[id]...)
|
||||
found = true
|
||||
}
|
||||
if !found {
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
return ips
|
||||
return ips, nil
|
||||
}
|
||||
|
||||
func (h *StaticHosts) lookup(domain string, option dns.IPOption, maxDepth int) []net.Address {
|
||||
switch addrs := h.lookupInternal(domain); {
|
||||
func (h *StaticHosts) lookup(domain string, option dns.IPOption, maxDepth int) ([]net.Address, error) {
|
||||
switch addrs, err := h.lookupInternal(domain); {
|
||||
case err != nil:
|
||||
return nil, err
|
||||
case len(addrs) == 0: // Not recorded in static hosts, return nil
|
||||
return addrs
|
||||
return addrs, nil
|
||||
case len(addrs) == 1 && addrs[0].Family().IsDomain(): // Try to unwrap domain
|
||||
errors.LogDebug(context.Background(), "found replaced domain: ", domain, " -> ", addrs[0].Domain(), ". Try to unwrap it")
|
||||
if maxDepth > 0 {
|
||||
unwrapped := h.lookup(addrs[0].Domain(), option, maxDepth-1)
|
||||
unwrapped, err := h.lookup(addrs[0].Domain(), option, maxDepth-1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if unwrapped != nil {
|
||||
return unwrapped
|
||||
return unwrapped, nil
|
||||
}
|
||||
}
|
||||
return addrs
|
||||
return addrs, nil
|
||||
default: // IP record found, return a non-nil IP array
|
||||
return filterIP(addrs, option)
|
||||
return filterIP(addrs, option), nil
|
||||
}
|
||||
}
|
||||
|
||||
// Lookup returns IP addresses or proxied domain for the given domain, if exists in this StaticHosts.
|
||||
func (h *StaticHosts) Lookup(domain string, option dns.IPOption) []net.Address {
|
||||
func (h *StaticHosts) Lookup(domain string, option dns.IPOption) ([]net.Address, error) {
|
||||
return h.lookup(domain, option, 5)
|
||||
}
|
||||
|
@@ -12,6 +12,11 @@ import (
|
||||
|
||||
func TestStaticHosts(t *testing.T) {
|
||||
pb := []*Config_HostMapping{
|
||||
{
|
||||
Type: DomainMatchingType_Subdomain,
|
||||
Domain: "lan",
|
||||
ProxiedDomain: "#3",
|
||||
},
|
||||
{
|
||||
Type: DomainMatchingType_Full,
|
||||
Domain: "example.com",
|
||||
@@ -54,7 +59,14 @@ func TestStaticHosts(t *testing.T) {
|
||||
common.Must(err)
|
||||
|
||||
{
|
||||
ips := hosts.Lookup("example.com", dns.IPOption{
|
||||
_, err := hosts.Lookup("example.com.lan", dns.IPOption{})
|
||||
if dns.RCodeFromError(err) != 3 {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
ips, _ := hosts.Lookup("example.com", dns.IPOption{
|
||||
IPv4Enable: true,
|
||||
IPv6Enable: true,
|
||||
})
|
||||
@@ -67,7 +79,7 @@ func TestStaticHosts(t *testing.T) {
|
||||
}
|
||||
|
||||
{
|
||||
domain := hosts.Lookup("proxy.xray.com", dns.IPOption{
|
||||
domain, _ := hosts.Lookup("proxy.xray.com", dns.IPOption{
|
||||
IPv4Enable: true,
|
||||
IPv6Enable: false,
|
||||
})
|
||||
@@ -80,7 +92,7 @@ func TestStaticHosts(t *testing.T) {
|
||||
}
|
||||
|
||||
{
|
||||
domain := hosts.Lookup("proxy2.xray.com", dns.IPOption{
|
||||
domain, _ := hosts.Lookup("proxy2.xray.com", dns.IPOption{
|
||||
IPv4Enable: true,
|
||||
IPv6Enable: false,
|
||||
})
|
||||
@@ -93,7 +105,7 @@ func TestStaticHosts(t *testing.T) {
|
||||
}
|
||||
|
||||
{
|
||||
ips := hosts.Lookup("www.example.cn", dns.IPOption{
|
||||
ips, _ := hosts.Lookup("www.example.cn", dns.IPOption{
|
||||
IPv4Enable: true,
|
||||
IPv6Enable: true,
|
||||
})
|
||||
@@ -106,7 +118,7 @@ func TestStaticHosts(t *testing.T) {
|
||||
}
|
||||
|
||||
{
|
||||
ips := hosts.Lookup("baidu.com", dns.IPOption{
|
||||
ips, _ := hosts.Lookup("baidu.com", dns.IPOption{
|
||||
IPv4Enable: false,
|
||||
IPv6Enable: true,
|
||||
})
|
||||
|
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
func (c *Control) FillInRandom() {
|
||||
randomLength := dice.Roll(64)
|
||||
randomLength++
|
||||
c.Random = make([]byte, randomLength)
|
||||
io.ReadFull(rand.Reader, c.Random)
|
||||
}
|
||||
|
@@ -170,7 +170,7 @@ func (p *StaticMuxPicker) PickAvailable() (*mux.ClientWorker, error) {
|
||||
if w.draining {
|
||||
continue
|
||||
}
|
||||
if w.client.Closed() {
|
||||
if w.IsFull() {
|
||||
continue
|
||||
}
|
||||
if w.client.ActiveConnections() < minConn {
|
||||
@@ -211,6 +211,7 @@ type PortalWorker struct {
|
||||
writer buf.Writer
|
||||
reader buf.Reader
|
||||
draining bool
|
||||
counter uint32
|
||||
}
|
||||
|
||||
func NewPortalWorker(client *mux.ClientWorker) (*PortalWorker, error) {
|
||||
@@ -244,7 +245,7 @@ func NewPortalWorker(client *mux.ClientWorker) (*PortalWorker, error) {
|
||||
}
|
||||
|
||||
func (w *PortalWorker) heartbeat() error {
|
||||
if w.client.Closed() {
|
||||
if w.Closed() {
|
||||
return errors.New("client worker stopped")
|
||||
}
|
||||
|
||||
@@ -260,16 +261,21 @@ func (w *PortalWorker) heartbeat() error {
|
||||
msg.State = Control_DRAIN
|
||||
|
||||
defer func() {
|
||||
w.client.GetTimer().Reset(time.Second * 16)
|
||||
common.Close(w.writer)
|
||||
common.Interrupt(w.reader)
|
||||
w.writer = nil
|
||||
}()
|
||||
}
|
||||
|
||||
b, err := proto.Marshal(msg)
|
||||
common.Must(err)
|
||||
mb := buf.MergeBytes(nil, b)
|
||||
return w.writer.WriteMultiBuffer(mb)
|
||||
w.counter = (w.counter + 1) % 5
|
||||
if w.draining || w.counter == 1 {
|
||||
b, err := proto.Marshal(msg)
|
||||
common.Must(err)
|
||||
mb := buf.MergeBytes(nil, b)
|
||||
return w.writer.WriteMultiBuffer(mb)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *PortalWorker) IsFull() bool {
|
||||
|
@@ -173,6 +173,7 @@ type ClientWorker struct {
|
||||
sessionManager *SessionManager
|
||||
link transport.Link
|
||||
done *done.Instance
|
||||
timer *time.Ticker
|
||||
strategy ClientStrategy
|
||||
}
|
||||
|
||||
@@ -187,6 +188,7 @@ func NewClientWorker(stream transport.Link, s ClientStrategy) (*ClientWorker, er
|
||||
sessionManager: NewSessionManager(),
|
||||
link: stream,
|
||||
done: done.New(),
|
||||
timer: time.NewTicker(time.Second * 16),
|
||||
strategy: s,
|
||||
}
|
||||
|
||||
@@ -209,9 +211,12 @@ func (m *ClientWorker) Closed() bool {
|
||||
return m.done.Done()
|
||||
}
|
||||
|
||||
func (m *ClientWorker) GetTimer() *time.Ticker {
|
||||
return m.timer
|
||||
}
|
||||
|
||||
func (m *ClientWorker) monitor() {
|
||||
timer := time.NewTicker(time.Second * 16)
|
||||
defer timer.Stop()
|
||||
defer m.timer.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
@@ -220,7 +225,7 @@ func (m *ClientWorker) monitor() {
|
||||
common.Close(m.link.Writer)
|
||||
common.Interrupt(m.link.Reader)
|
||||
return
|
||||
case <-timer.C:
|
||||
case <-m.timer.C:
|
||||
size := m.sessionManager.Size()
|
||||
if size == 0 && m.sessionManager.CloseIfNoSession() {
|
||||
common.Must(m.done.Close())
|
||||
@@ -276,6 +281,8 @@ func (m *ClientWorker) IsClosing() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IsFull returns true if this ClientWorker is unable to accept more connections.
|
||||
// it might be because it is closing, or the number of connections has reached the limit.
|
||||
func (m *ClientWorker) IsFull() bool {
|
||||
if m.IsClosing() || m.Closed() {
|
||||
return true
|
||||
@@ -289,12 +296,12 @@ func (m *ClientWorker) IsFull() bool {
|
||||
}
|
||||
|
||||
func (m *ClientWorker) Dispatch(ctx context.Context, link *transport.Link) bool {
|
||||
if m.IsFull() || m.Closed() {
|
||||
if m.IsFull() {
|
||||
return false
|
||||
}
|
||||
|
||||
sm := m.sessionManager
|
||||
s := sm.Allocate()
|
||||
s := sm.Allocate(&m.strategy)
|
||||
if s == nil {
|
||||
return false
|
||||
}
|
||||
|
@@ -201,11 +201,12 @@ func (w *ServerWorker) handleStatusNew(ctx context.Context, meta *FrameMetadata,
|
||||
transferType: protocol.TransferTypePacket,
|
||||
XUDP: x,
|
||||
}
|
||||
go handle(ctx, x.Mux, w.link.Writer)
|
||||
x.Status = Active
|
||||
if !w.sessionManager.Add(x.Mux) {
|
||||
x.Mux.Close(false)
|
||||
return errors.New("failed to add new session")
|
||||
}
|
||||
go handle(ctx, x.Mux, w.link.Writer)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -226,18 +227,23 @@ func (w *ServerWorker) handleStatusNew(ctx context.Context, meta *FrameMetadata,
|
||||
if meta.Target.Network == net.Network_UDP {
|
||||
s.transferType = protocol.TransferTypePacket
|
||||
}
|
||||
w.sessionManager.Add(s)
|
||||
if !w.sessionManager.Add(s) {
|
||||
s.Close(false)
|
||||
return errors.New("failed to add new session")
|
||||
}
|
||||
go handle(ctx, s, w.link.Writer)
|
||||
if !meta.Option.Has(OptionData) {
|
||||
return nil
|
||||
}
|
||||
|
||||
rr := s.NewReader(reader, &meta.Target)
|
||||
if err := buf.Copy(rr, s.output); err != nil {
|
||||
buf.Copy(rr, buf.Discard)
|
||||
return s.Close(false)
|
||||
err = buf.Copy(rr, s.output)
|
||||
|
||||
if err != nil && buf.IsWriteError(err) {
|
||||
s.Close(false)
|
||||
return buf.Copy(rr, buf.Discard)
|
||||
}
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
func (w *ServerWorker) handleStatusKeep(meta *FrameMetadata, reader *buf.BufferedReader) error {
|
||||
@@ -304,10 +310,11 @@ func (w *ServerWorker) handleFrame(ctx context.Context, reader *buf.BufferedRead
|
||||
}
|
||||
|
||||
func (w *ServerWorker) run(ctx context.Context) {
|
||||
input := w.link.Reader
|
||||
reader := &buf.BufferedReader{Reader: input}
|
||||
reader := &buf.BufferedReader{Reader: w.link.Reader}
|
||||
|
||||
defer w.sessionManager.Close()
|
||||
defer common.Close(w.link.Writer)
|
||||
defer common.Interrupt(w.link.Reader)
|
||||
|
||||
for {
|
||||
select {
|
||||
@@ -318,7 +325,6 @@ func (w *ServerWorker) run(ctx context.Context) {
|
||||
if err != nil {
|
||||
if errors.Cause(err) != io.EOF {
|
||||
errors.LogInfoInner(ctx, err, "unexpected EOF")
|
||||
common.Interrupt(input)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@@ -50,11 +50,14 @@ func (m *SessionManager) Count() int {
|
||||
return int(m.count)
|
||||
}
|
||||
|
||||
func (m *SessionManager) Allocate() *Session {
|
||||
func (m *SessionManager) Allocate(Strategy *ClientStrategy) *Session {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
MaxConcurrency := int(Strategy.MaxConcurrency)
|
||||
MaxConnection := uint16(Strategy.MaxConnection)
|
||||
|
||||
if m.closed {
|
||||
if m.closed || (MaxConcurrency > 0 && len(m.sessions) >= MaxConcurrency) || (MaxConnection > 0 && m.count >= MaxConnection) {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@@ -9,7 +9,7 @@ import (
|
||||
func TestSessionManagerAdd(t *testing.T) {
|
||||
m := NewSessionManager()
|
||||
|
||||
s := m.Allocate()
|
||||
s := m.Allocate(&ClientStrategy{})
|
||||
if s.ID != 1 {
|
||||
t.Error("id: ", s.ID)
|
||||
}
|
||||
@@ -17,7 +17,7 @@ func TestSessionManagerAdd(t *testing.T) {
|
||||
t.Error("size: ", m.Size())
|
||||
}
|
||||
|
||||
s = m.Allocate()
|
||||
s = m.Allocate(&ClientStrategy{})
|
||||
if s.ID != 2 {
|
||||
t.Error("id: ", s.ID)
|
||||
}
|
||||
@@ -39,7 +39,7 @@ func TestSessionManagerAdd(t *testing.T) {
|
||||
|
||||
func TestSessionManagerClose(t *testing.T) {
|
||||
m := NewSessionManager()
|
||||
s := m.Allocate()
|
||||
s := m.Allocate(&ClientStrategy{})
|
||||
|
||||
if m.CloseIfNoSession() {
|
||||
t.Error("able to close")
|
||||
|
112
common/utils/typed_sync_map.go
Normal file
112
common/utils/typed_sync_map.go
Normal file
@@ -0,0 +1,112 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// TypedSyncMap is a wrapper of sync.Map that provides type-safe for keys and values.
|
||||
// No need to use type assertions every time, so you can have more time to enjoy other things like GochiUsa
|
||||
// If sync.Map methods returned nil, it will return the zero value of the type V.
|
||||
type TypedSyncMap[K, V any] struct {
|
||||
syncMap *sync.Map
|
||||
}
|
||||
|
||||
// NewTypedSyncMap creates a new TypedSyncMap
|
||||
// K is key type, V is value type
|
||||
// It is recommended to use pointer types for V because sync.Map might return nil
|
||||
// If sync.Map methods really returned nil, it will return the zero value of the type V
|
||||
func NewTypedSyncMap[K any, V any]() *TypedSyncMap[K, V] {
|
||||
return &TypedSyncMap[K, V]{
|
||||
syncMap: &sync.Map{},
|
||||
}
|
||||
}
|
||||
|
||||
// Clear deletes all the entries, resulting in an empty Map.
|
||||
func (m *TypedSyncMap[K, V]) Clear() {
|
||||
m.syncMap.Clear()
|
||||
}
|
||||
|
||||
// CompareAndDelete deletes the entry for key if its value is equal to old.
|
||||
// The old value must be of a comparable type.
|
||||
//
|
||||
// If there is no current value for key in the map, CompareAndDelete
|
||||
// returns false (even if the old value is the nil interface value).
|
||||
func (m *TypedSyncMap[K, V]) CompareAndDelete(key K, old V) (deleted bool) {
|
||||
return m.syncMap.CompareAndDelete(key, old)
|
||||
}
|
||||
|
||||
// CompareAndSwap swaps the old and new values for key
|
||||
// if the value stored in the map is equal to old.
|
||||
// The old value must be of a comparable type.
|
||||
func (m *TypedSyncMap[K, V]) CompareAndSwap(key K, old V, new V) (swapped bool) {
|
||||
return m.syncMap.CompareAndSwap(key, old, new)
|
||||
}
|
||||
|
||||
// Delete deletes the value for a key.
|
||||
func (m *TypedSyncMap[K, V]) Delete(key K) {
|
||||
m.syncMap.Delete(key)
|
||||
}
|
||||
|
||||
// Load returns the value stored in the map for a key, or nil if no
|
||||
// value is present.
|
||||
// The ok result indicates whether value was found in the map.
|
||||
func (m *TypedSyncMap[K, V]) Load(key K) (value V, ok bool) {
|
||||
anyValue, ok := m.syncMap.Load(key)
|
||||
// anyValue might be nil
|
||||
if anyValue != nil {
|
||||
value = anyValue.(V)
|
||||
}
|
||||
return value, ok
|
||||
}
|
||||
|
||||
// LoadAndDelete deletes the value for a key, returning the previous value if any.
|
||||
// The loaded result reports whether the key was present.
|
||||
func (m *TypedSyncMap[K, V]) LoadAndDelete(key K) (value V, loaded bool) {
|
||||
anyValue, loaded := m.syncMap.LoadAndDelete(key)
|
||||
if anyValue != nil {
|
||||
value = anyValue.(V)
|
||||
}
|
||||
return value, loaded
|
||||
}
|
||||
|
||||
// LoadOrStore returns the existing value for the key if present.
|
||||
// Otherwise, it stores and returns the given value.
|
||||
// The loaded result is true if the value was loaded, false if stored.
|
||||
func (m *TypedSyncMap[K, V]) LoadOrStore(key K, value V) (actual V, loaded bool) {
|
||||
anyActual, loaded := m.syncMap.LoadOrStore(key, value)
|
||||
if anyActual != nil {
|
||||
actual = anyActual.(V)
|
||||
}
|
||||
return actual, loaded
|
||||
}
|
||||
|
||||
// Range calls f sequentially for each key and value present in the map.
|
||||
// If f returns false, range stops the iteration.
|
||||
//
|
||||
// Range does not necessarily correspond to any consistent snapshot of the Map's
|
||||
// contents: no key will be visited more than once, but if the value for any key
|
||||
// is stored or deleted concurrently (including by f), Range may reflect any
|
||||
// mapping for that key from any point during the Range call. Range does not
|
||||
// block other methods on the receiver; even f itself may call any method on m.
|
||||
//
|
||||
// Range may be O(N) with the number of elements in the map even if f returns
|
||||
// false after a constant number of calls.
|
||||
func (m *TypedSyncMap[K, V]) Range(f func(key K, value V) bool) {
|
||||
m.syncMap.Range(func(key, value any) bool {
|
||||
return f(key.(K), value.(V))
|
||||
})
|
||||
}
|
||||
|
||||
// Store sets the value for a key.
|
||||
func (m *TypedSyncMap[K, V]) Store(key K, value V) {
|
||||
m.syncMap.Store(key, value)
|
||||
}
|
||||
|
||||
// Swap swaps the value for a key and returns the previous value if any. The loaded result reports whether the key was present.
|
||||
func (m *TypedSyncMap[K, V]) Swap(key K, value V) (previous V, loaded bool) {
|
||||
anyPrevious, loaded := m.syncMap.Swap(key, value)
|
||||
if anyPrevious != nil {
|
||||
previous = anyPrevious.(V)
|
||||
}
|
||||
return previous, loaded
|
||||
}
|
@@ -42,6 +42,24 @@ func (e RCodeError) Error() string {
|
||||
return serial.Concat("rcode: ", uint16(e))
|
||||
}
|
||||
|
||||
func (RCodeError) IP() net.IP {
|
||||
panic("Calling IP() on a RCodeError.")
|
||||
}
|
||||
|
||||
func (RCodeError) Domain() string {
|
||||
panic("Calling Domain() on a RCodeError.")
|
||||
}
|
||||
|
||||
func (RCodeError) Family() net.AddressFamily {
|
||||
panic("Calling Family() on a RCodeError.")
|
||||
}
|
||||
|
||||
func (e RCodeError) String() string {
|
||||
return e.Error()
|
||||
}
|
||||
|
||||
var _ net.Address = (*RCodeError)(nil)
|
||||
|
||||
func RCodeFromError(err error) uint16 {
|
||||
if err == nil {
|
||||
return 0
|
||||
|
@@ -18,6 +18,7 @@ import (
|
||||
"github.com/xtls/xray-core/common/session"
|
||||
"github.com/xtls/xray-core/common/signal"
|
||||
"github.com/xtls/xray-core/common/task"
|
||||
"github.com/xtls/xray-core/common/utils"
|
||||
"github.com/xtls/xray-core/core"
|
||||
"github.com/xtls/xray-core/features/dns"
|
||||
"github.com/xtls/xray-core/features/policy"
|
||||
@@ -202,7 +203,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
|
||||
writer = buf.NewWriter(conn)
|
||||
}
|
||||
} else {
|
||||
writer = NewPacketWriter(conn, h, ctx, UDPOverride)
|
||||
writer = NewPacketWriter(conn, h, ctx, UDPOverride, destination)
|
||||
if h.config.Noises != nil {
|
||||
errors.LogDebug(ctx, "NOISE", h.config.Noises)
|
||||
writer = &NoisePacketWriter{
|
||||
@@ -238,7 +239,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
|
||||
if destination.Network == net.Network_TCP {
|
||||
reader = buf.NewReader(conn)
|
||||
} else {
|
||||
reader = NewPacketReader(conn, UDPOverride)
|
||||
reader = NewPacketReader(conn, UDPOverride, destination)
|
||||
}
|
||||
if err := buf.Copy(reader, output, buf.UpdateActivity(timer)); err != nil {
|
||||
return errors.New("failed to process response").Base(err)
|
||||
@@ -273,7 +274,7 @@ func isTLSConn(conn stat.Connection) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func NewPacketReader(conn net.Conn, UDPOverride net.Destination) buf.Reader {
|
||||
func NewPacketReader(conn net.Conn, UDPOverride net.Destination, DialDest net.Destination) buf.Reader {
|
||||
iConn := conn
|
||||
statConn, ok := iConn.(*stat.CounterConnection)
|
||||
if ok {
|
||||
@@ -283,10 +284,15 @@ func NewPacketReader(conn net.Conn, UDPOverride net.Destination) buf.Reader {
|
||||
if statConn != nil {
|
||||
counter = statConn.ReadCounter
|
||||
}
|
||||
if c, ok := iConn.(*internet.PacketConnWrapper); ok && UDPOverride.Address == nil && UDPOverride.Port == 0 {
|
||||
if c, ok := iConn.(*internet.PacketConnWrapper); ok {
|
||||
isAddrChanged := false
|
||||
if UDPOverride.Address != nil || UDPOverride.Port != 0 || DialDest.Address.Family().IsDomain() {
|
||||
isAddrChanged = true
|
||||
}
|
||||
return &PacketReader{
|
||||
PacketConnWrapper: c,
|
||||
Counter: counter,
|
||||
IsAddrChanged: isAddrChanged,
|
||||
}
|
||||
}
|
||||
return &buf.PacketReader{Reader: conn}
|
||||
@@ -295,6 +301,7 @@ func NewPacketReader(conn net.Conn, UDPOverride net.Destination) buf.Reader {
|
||||
type PacketReader struct {
|
||||
*internet.PacketConnWrapper
|
||||
stats.Counter
|
||||
IsAddrChanged bool
|
||||
}
|
||||
|
||||
func (r *PacketReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
|
||||
@@ -306,10 +313,14 @@ func (r *PacketReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
|
||||
return nil, err
|
||||
}
|
||||
b.Resize(0, int32(n))
|
||||
b.UDP = &net.Destination{
|
||||
Address: net.IPAddress(d.(*net.UDPAddr).IP),
|
||||
Port: net.Port(d.(*net.UDPAddr).Port),
|
||||
Network: net.Network_UDP,
|
||||
// if udp dest addr is changed, we are unable to get the correct src addr
|
||||
// so we don't attach src info to udp packet, break cone behavior, assuming the dial dest is the expected scr addr
|
||||
if !r.IsAddrChanged {
|
||||
b.UDP = &net.Destination{
|
||||
Address: net.IPAddress(d.(*net.UDPAddr).IP),
|
||||
Port: net.Port(d.(*net.UDPAddr).Port),
|
||||
Network: net.Network_UDP,
|
||||
}
|
||||
}
|
||||
if r.Counter != nil {
|
||||
r.Counter.Add(int64(n))
|
||||
@@ -317,7 +328,8 @@ func (r *PacketReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
|
||||
return buf.MultiBuffer{b}, nil
|
||||
}
|
||||
|
||||
func NewPacketWriter(conn net.Conn, h *Handler, ctx context.Context, UDPOverride net.Destination) buf.Writer {
|
||||
// DialDest means the dial target used in the dialer when creating conn
|
||||
func NewPacketWriter(conn net.Conn, h *Handler, ctx context.Context, UDPOverride net.Destination, DialDest net.Destination) buf.Writer {
|
||||
iConn := conn
|
||||
statConn, ok := iConn.(*stat.CounterConnection)
|
||||
if ok {
|
||||
@@ -328,12 +340,20 @@ func NewPacketWriter(conn net.Conn, h *Handler, ctx context.Context, UDPOverride
|
||||
counter = statConn.WriteCounter
|
||||
}
|
||||
if c, ok := iConn.(*internet.PacketConnWrapper); ok {
|
||||
// If DialDest is a domain, it will be resolved in dialer
|
||||
// check this behavior and add it to map
|
||||
resolvedUDPAddr := utils.NewTypedSyncMap[string, net.Address]()
|
||||
if DialDest.Address.Family().IsDomain() {
|
||||
RemoteAddress, _, _ := net.SplitHostPort(conn.RemoteAddr().String())
|
||||
resolvedUDPAddr.Store(DialDest.Address.String(), net.ParseAddress(RemoteAddress))
|
||||
}
|
||||
return &PacketWriter{
|
||||
PacketConnWrapper: c,
|
||||
Counter: counter,
|
||||
Handler: h,
|
||||
Context: ctx,
|
||||
UDPOverride: UDPOverride,
|
||||
resolvedUDPAddr: resolvedUDPAddr,
|
||||
}
|
||||
|
||||
}
|
||||
@@ -346,6 +366,12 @@ type PacketWriter struct {
|
||||
*Handler
|
||||
context.Context
|
||||
UDPOverride net.Destination
|
||||
|
||||
// Dest of udp packets might be a domain, we will resolve them to IP
|
||||
// But resolver will return a random one if the domain has many IPs
|
||||
// Resulting in these packets being sent to many different IPs randomly
|
||||
// So, cache and keep the resolve result
|
||||
resolvedUDPAddr *utils.TypedSyncMap[string, net.Address]
|
||||
}
|
||||
|
||||
func (w *PacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
|
||||
@@ -364,10 +390,34 @@ func (w *PacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
|
||||
if w.UDPOverride.Port != 0 {
|
||||
b.UDP.Port = w.UDPOverride.Port
|
||||
}
|
||||
if w.Handler.config.hasStrategy() && b.UDP.Address.Family().IsDomain() {
|
||||
ip := w.Handler.resolveIP(w.Context, b.UDP.Address.Domain(), nil)
|
||||
if ip != nil {
|
||||
if b.UDP.Address.Family().IsDomain() {
|
||||
if ip, ok := w.resolvedUDPAddr.Load(b.UDP.Address.Domain()); ok {
|
||||
b.UDP.Address = ip
|
||||
} else {
|
||||
ShouldUseSystemResolver := true
|
||||
if w.Handler.config.hasStrategy() {
|
||||
ip = w.Handler.resolveIP(w.Context, b.UDP.Address.Domain(), nil)
|
||||
if ip != nil {
|
||||
ShouldUseSystemResolver = false
|
||||
}
|
||||
// drop packet if resolve failed when forceIP
|
||||
if ip == nil && w.Handler.config.forceIP() {
|
||||
b.Release()
|
||||
continue
|
||||
}
|
||||
}
|
||||
if ShouldUseSystemResolver {
|
||||
udpAddr, err := net.ResolveUDPAddr("udp", b.UDP.NetAddr())
|
||||
if err != nil {
|
||||
b.Release()
|
||||
continue
|
||||
} else {
|
||||
ip = net.IPAddress(udpAddr.IP)
|
||||
}
|
||||
}
|
||||
if ip != nil {
|
||||
b.UDP.Address, _ = w.resolvedUDPAddr.LoadOrStore(b.UDP.Address.Domain(), ip)
|
||||
}
|
||||
}
|
||||
}
|
||||
destAddr, _ := net.ResolveUDPAddr("udp", b.UDP.NetAddr())
|
||||
|
@@ -44,6 +44,10 @@ func NewDispatcher(dispatcher routing.Dispatcher, callback ResponseCallback) *Di
|
||||
func (v *Dispatcher) RemoveRay() {
|
||||
v.Lock()
|
||||
defer v.Unlock()
|
||||
v.removeRay()
|
||||
}
|
||||
|
||||
func (v *Dispatcher) removeRay() {
|
||||
if v.conn != nil {
|
||||
common.Interrupt(v.conn.link.Reader)
|
||||
common.Close(v.conn.link.Writer)
|
||||
@@ -62,9 +66,16 @@ func (v *Dispatcher) getInboundRay(ctx context.Context, dest net.Destination) (*
|
||||
errors.LogInfo(ctx, "establishing new connection for ", dest)
|
||||
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
entry := &connEntry{}
|
||||
removeRay := func() {
|
||||
cancel()
|
||||
v.RemoveRay()
|
||||
v.Lock()
|
||||
defer v.Unlock()
|
||||
if entry == v.conn {
|
||||
cancel()
|
||||
v.removeRay()
|
||||
} else {
|
||||
errors.LogError(ctx, "removeRay trying to remove a conn that not belongs to it, canceling.")
|
||||
}
|
||||
}
|
||||
timer := signal.CancelAfterInactivity(ctx, removeRay, time.Minute)
|
||||
|
||||
@@ -73,7 +84,7 @@ func (v *Dispatcher) getInboundRay(ctx context.Context, dest net.Destination) (*
|
||||
return nil, errors.New("failed to dispatch request to ", dest).Base(err)
|
||||
}
|
||||
|
||||
entry := &connEntry{
|
||||
*entry = connEntry{
|
||||
link: link,
|
||||
timer: timer,
|
||||
cancel: removeRay,
|
||||
|
Reference in New Issue
Block a user