Compare commits

..
Author SHA1 Message Date
RPRX bb977280e7 Update server.go 2026-06-17 14:16:04 +00:00
RPRX 8916151f8b Update protocol.go 2026-06-17 14:13:48 +00:00
RPRX c07f7f94cb Update protocol.go 2026-06-17 14:08:35 +00:00
Fangliding 325b57dc24 copy inbound 2026-06-17 20:53:59 +08:00
Fangliding b4cfe4f122 chore 2026-06-17 20:53:58 +08:00
Fangliding df25e33ab3 Fix panic in illegal request 2026-06-17 20:53:58 +08:00
35 changed files with 220 additions and 385 deletions
+1 -1
View File
@@ -65,7 +65,7 @@ jobs:
echo "LATEST=$LATEST" >>${GITHUB_ENV}
- name: Checkout code
uses: actions/checkout@v7
uses: actions/checkout@v6
- name: Set up QEMU
uses: docker/setup-qemu-action@v4
+1 -1
View File
@@ -83,7 +83,7 @@ jobs:
CGO_ENABLED: 0
steps:
- name: Checkout codebase
uses: actions/checkout@v7
uses: actions/checkout@v6
- name: Show workflow information
run: |
+1 -1
View File
@@ -170,7 +170,7 @@ jobs:
CGO_ENABLED: 0
steps:
- name: Checkout codebase
uses: actions/checkout@v7
uses: actions/checkout@v6
- name: Set up NDK
if: matrix.goos == 'android'
+3 -3
View File
@@ -40,7 +40,7 @@ jobs:
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
steps:
- name: Checkout codebase
uses: actions/checkout@v7
uses: actions/checkout@v6
- name: Check Proto Version Header
run: |
head -n 4 core/config.pb.go > ref.txt
@@ -59,7 +59,7 @@ jobs:
contents: read
steps:
- name: Checkout codebase
uses: actions/checkout@v7
uses: actions/checkout@v6
- name: Set up Go
uses: actions/setup-go@v6
with:
@@ -83,7 +83,7 @@ jobs:
os: [windows-latest, ubuntu-latest, macos-latest]
steps:
- name: Checkout codebase
uses: actions/checkout@v7
uses: actions/checkout@v6
- name: Set up Go
uses: actions/setup-go@v6
with:
+2 -7
View File
@@ -198,14 +198,9 @@ func parseResponse(payload []byte) (*IPRecord, error) {
ipRecord := &IPRecord{
ReqID: h.ID,
RCode: h.RCode,
Expire: now.Add(time.Second * dns_feature.DefaultTTL),
RawHeader: &h,
}
defer func() {
// set to default TTL if no valid TTL is found
if ipRecord.Expire.IsZero() {
ipRecord.Expire = now.Add(time.Second * dns_feature.DefaultTTL)
}
}()
L:
for {
@@ -222,7 +217,7 @@ L:
ttl = 1
}
expire := now.Add(time.Duration(ttl) * time.Second)
if ipRecord.Expire.IsZero() || ipRecord.Expire.After(expire) {
if ipRecord.Expire.After(expire) {
ipRecord.Expire = expire
}
+1 -1
View File
@@ -220,7 +220,7 @@ func parseDomain(d *Domain) (strmatcher.Matcher, error) {
case Domain_Regex:
return strmatcher.Regex.New(d.Value)
case Domain_Domain:
return strmatcher.Domain.New(strings.ToLower(d.Value))
return strmatcher.Domain.New(d.Value)
case Domain_Full:
return strmatcher.Full.New(strings.ToLower(d.Value))
default:
+7 -15
View File
@@ -6,14 +6,12 @@ import (
"sync/atomic"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/utils"
"github.com/xtls/xray-core/common/uuid"
)
type DomainRegistry struct {
mu sync.Mutex
factory DomainMatcherFactory
matchers *utils.WeakCacheMap[uuid.UUID, DynamicDomainMatcher]
matchers []*DynamicDomainMatcher
}
func (r *DomainRegistry) BuildDomainMatcher(rules []*DomainRule) (DomainMatcher, error) {
@@ -26,7 +24,7 @@ func (r *DomainRegistry) BuildDomainMatcher(rules []*DomainRule) (DomainMatcher,
}
d := NewDynamicDomainMatcher(rules, m)
r.matchers.Store(uuid.New(), d)
r.matchers = append(r.matchers, d)
return d, nil
}
@@ -34,20 +32,15 @@ func (r *DomainRegistry) Reload() error {
r.mu.Lock()
defer r.mu.Unlock()
var matchers []*DynamicDomainMatcher
r.matchers.Range(func(_ uuid.UUID, matcher *DynamicDomainMatcher) bool {
matchers = append(matchers, matcher)
return true
})
errors.LogInfo(context.Background(), "reloading GeoSite data for ", len(matchers), " domain matcher(s)")
errors.LogInfo(context.Background(), "reloading GeoSite data for ", len(r.matchers), " domain matcher(s)")
factory := newDomainMatcherFactory()
type reloadEntry struct {
dynamic *DynamicDomainMatcher
matcher DomainMatcher
}
reloaded := make([]reloadEntry, len(matchers))
for i, d := range matchers {
reloaded := make([]reloadEntry, len(r.matchers))
for i, d := range r.matchers {
m, err := factory.BuildMatcher(d.rules)
if err != nil {
errors.LogErrorInner(context.Background(), err, "failed to reload GeoSite data for domain matcher ", i)
@@ -59,14 +52,13 @@ func (r *DomainRegistry) Reload() error {
entry.dynamic.Reload(entry.matcher)
}
r.factory = factory
errors.LogInfo(context.Background(), "reloaded GeoSite data for ", len(matchers), " domain matcher(s)")
errors.LogInfo(context.Background(), "reloaded GeoSite data for ", len(r.matchers), " domain matcher(s)")
return nil
}
func newDomainRegistry() *DomainRegistry {
return &DomainRegistry{
factory: newDomainMatcherFactory(),
matchers: utils.NewWeakCacheMap[uuid.UUID, DynamicDomainMatcher](),
factory: newDomainMatcherFactory(),
}
}
+11 -19
View File
@@ -7,27 +7,25 @@ import (
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/utils"
"github.com/xtls/xray-core/common/uuid"
)
type IPRegistry struct {
mu sync.Mutex
factory *IPSetFactory
matchers *utils.WeakCacheMap[uuid.UUID, DynamicIPMatcher]
mu sync.Mutex
ipsetFactory *IPSetFactory
matchers []*DynamicIPMatcher
}
func (r *IPRegistry) BuildIPMatcher(rules []*IPRule) (IPMatcher, error) {
r.mu.Lock()
defer r.mu.Unlock()
m, err := buildOptimizedIPMatcher(r.factory, rules)
m, err := buildOptimizedIPMatcher(r.ipsetFactory, rules)
if err != nil {
return nil, err
}
d := NewDynamicIPMatcher(rules, m)
r.matchers.Store(uuid.New(), d)
r.matchers = append(r.matchers, d)
return d, nil
}
@@ -35,20 +33,15 @@ func (r *IPRegistry) Reload() error {
r.mu.Lock()
defer r.mu.Unlock()
var matchers []*DynamicIPMatcher
r.matchers.Range(func(_ uuid.UUID, matcher *DynamicIPMatcher) bool {
matchers = append(matchers, matcher)
return true
})
errors.LogInfo(context.Background(), "reloading GeoIP data for ", len(matchers), " IP matcher(s)")
errors.LogInfo(context.Background(), "reloading GeoIP data for ", len(r.matchers), " IP matcher(s)")
factory := newIPSetFactory()
type reloadEntry struct {
dynamic *DynamicIPMatcher
matcher IPMatcher
}
reloaded := make([]reloadEntry, len(matchers))
for i, d := range matchers {
reloaded := make([]reloadEntry, len(r.matchers))
for i, d := range r.matchers {
m, err := buildOptimizedIPMatcher(factory, d.rules)
if err != nil {
errors.LogErrorInner(context.Background(), err, "failed to reload GeoIP data for IP matcher ", i)
@@ -59,15 +52,14 @@ func (r *IPRegistry) Reload() error {
for _, entry := range reloaded {
entry.dynamic.Reload(entry.matcher)
}
r.factory = factory
errors.LogInfo(context.Background(), "reloaded GeoIP data for ", len(matchers), " IP matcher(s)")
r.ipsetFactory = factory
errors.LogInfo(context.Background(), "reloaded GeoIP data for ", len(r.matchers), " IP matcher(s)")
return nil
}
func newIPRegistry() *IPRegistry {
return &IPRegistry{
factory: newIPSetFactory(),
matchers: utils.NewWeakCacheMap[uuid.UUID, DynamicIPMatcher](),
ipsetFactory: newIPSetFactory(),
}
}
+2 -2
View File
@@ -138,7 +138,7 @@ func ParseDomainRule(r string, defaultType Domain_Type) (*DomainRule, error) {
}
prefix := 0
for _, ext := range [...]string{"ext:", "ext-domain:", "ext-site:"} {
for _, ext := range [...]string{"ext:", "ext-domain:"} {
if strings.HasPrefix(r, ext) {
prefix = len(ext)
break
@@ -167,7 +167,7 @@ func ParseDomainRules(rules []string, defaultType Domain_Type) ([]*DomainRule, e
}
prefix := 0
for _, ext := range [...]string{"ext:", "ext-domain:", "ext-site:"} {
for _, ext := range [...]string{"ext:", "ext-domain:"} {
if strings.HasPrefix(r, ext) {
prefix = len(ext)
break
+10 -26
View File
@@ -1,41 +1,25 @@
package http
import (
"context"
"net/http"
"strconv"
"strings"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net"
)
// ApplyTrustedXForwardedFor returns remoteAddr overridden by X-Forwarded-For only when a configured trusted header is present.
func ApplyTrustedXForwardedFor(header http.Header, trusted []string, remoteAddr net.Addr) net.Addr {
value := header.Get("X-Forwarded-For")
if value == "" {
return remoteAddr
// ParseXForwardedFor parses X-Forwarded-For header in http headers, and return the IP list in it.
func ParseXForwardedFor(header http.Header) []net.Address {
xff := header.Get("X-Forwarded-For")
if xff == "" {
return nil
}
for _, t := range trusted {
if len(header.Values(t)) > 0 {
if idx := strings.IndexByte(value, ','); idx >= 0 {
value = value[:idx]
}
if addr := net.ParseAddress(value); addr.Family().IsIP() {
return &net.TCPAddr{
IP: addr.IP(),
Port: 0,
}
}
return remoteAddr
}
list := strings.Split(xff, ",")
addrs := make([]net.Address, 0, len(list))
for _, proxy := range list {
addrs = append(addrs, net.ParseAddress(proxy))
}
if len(trusted) == 0 {
errors.LogWarning(context.Background(), `received "X-Forwarded-For" from `, remoteAddr, ` but "sockopt.trustedXForwardedFor" is not configured; ignoring it and using the real remote address`)
} else {
errors.LogError(context.Background(), `ignored potentially forged "X-Forwarded-For" from `, remoteAddr, `: `, value)
}
return remoteAddr
return addrs
}
// RemoveHopByHopHeaders removes hop by hop headers in http header list.
+8 -33
View File
@@ -2,48 +2,23 @@ package http_test
import (
"bufio"
gonet "net"
"net/http"
"strings"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/net"
. "github.com/xtls/xray-core/common/protocol/http"
)
func TestApplyTrustedXForwardedFor(t *testing.T) {
remoteAddr := &gonet.TCPAddr{IP: gonet.ParseIP("127.0.0.1"), Port: 12345}
t.Run("ignore X-Forwarded-For without trusted header", func(t *testing.T) {
header := http.Header{}
header.Add("X-Forwarded-For", "129.78.138.66, 129.78.64.103")
if addr := ApplyTrustedXForwardedFor(header, nil, remoteAddr); addr != remoteAddr {
t.Fatalf("unexpected remote address: %v", addr)
}
})
t.Run("trust X-Forwarded-For", func(t *testing.T) {
header := http.Header{}
header.Add("X-Forwarded-For", "129.78.138.66, 129.78.64.103")
header.Add("X-Trusted-CDN", "")
addr := ApplyTrustedXForwardedFor(header, []string{"X-Trusted-CDN"}, remoteAddr)
if addr.String() != "129.78.138.66:0" {
t.Fatalf("unexpected remote address: %v", addr)
}
})
t.Run("ignore non-IP X-Forwarded-For", func(t *testing.T) {
header := http.Header{}
header.Add("X-Forwarded-For", "example.com")
header.Add("X-Trusted-CDN", "")
if addr := ApplyTrustedXForwardedFor(header, []string{"X-Trusted-CDN"}, remoteAddr); addr != remoteAddr {
t.Fatalf("unexpected remote address: %v", addr)
}
})
func TestParseXForwardedFor(t *testing.T) {
header := http.Header{}
header.Add("X-Forwarded-For", "129.78.138.66, 129.78.64.103")
addrs := ParseXForwardedFor(header)
if r := cmp.Diff(addrs, []net.Address{net.ParseAddress("129.78.138.66"), net.ParseAddress("129.78.64.103")}); r != "" {
t.Error(r)
}
}
func TestHopByHopHeadersRemoving(t *testing.T) {
-14
View File
@@ -1,7 +1,6 @@
package utils
import (
"maps"
"runtime"
"sync"
"weak"
@@ -44,16 +43,3 @@ func (c *WeakCacheMap[K, V]) Store(key K, value *V) {
}
}, struct{}{})
}
func (c *WeakCacheMap[K, V]) Range(f func(K, *V) bool) {
c.mu.Lock()
snapshot := maps.Clone(c.m)
c.mu.Unlock()
for k, v := range snapshot {
if value := v.Value(); value != nil {
if !f(k, value) {
break
}
}
}
}
+2 -12
View File
@@ -1,24 +1,14 @@
package conf
import (
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/proxy/loopback"
"google.golang.org/protobuf/proto"
)
type LoopbackConfig struct {
InboundTag string `json:"inboundTag"`
Sniffing *SniffingConfig `json:"sniffing"`
InboundTag string `json:"inboundTag"`
}
func (l LoopbackConfig) Build() (proto.Message, error) {
c := &loopback.Config{InboundTag: l.InboundTag}
if l.Sniffing != nil {
sc, err := l.Sniffing.Build()
if err != nil {
return nil, errors.New("failed to build sniffing config").Base(err)
}
c.Sniffing = sc
}
return c, nil
return &loopback.Config{InboundTag: l.InboundTag}, nil
}
+3 -6
View File
@@ -1788,15 +1788,12 @@ func (c *MkcpLegacy) Build() (proto.Message, error) {
}
type Salamander struct {
Password string `json:"password"`
PacketSize Int32Range `json:"packetSize"`
Password string `json:"password"`
PacketSize *Int32Range `json:"packetSize"`
}
func (c *Salamander) Build() (proto.Message, error) {
if c.PacketSize.To > 0 {
if c.PacketSize.From <= 0 || c.PacketSize.To > 2048 {
return nil, errors.New("gecko: invalid min/max packet size")
}
if c.PacketSize != nil {
return &salamander.GeckoConfig{
Password: c.Password,
MinPacketSize: c.PacketSize.From,
+21
View File
@@ -173,6 +173,27 @@ 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`)
+10 -22
View File
@@ -7,7 +7,6 @@
package loopback
import (
proxyman "github.com/xtls/xray-core/app/proxyman"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
@@ -23,9 +22,8 @@ const (
)
type Config struct {
state protoimpl.MessageState `protogen:"open.v1"`
InboundTag string `protobuf:"bytes,1,opt,name=inbound_tag,json=inboundTag,proto3" json:"inbound_tag,omitempty"`
Sniffing *proxyman.SniffingConfig `protobuf:"bytes,2,opt,name=sniffing,proto3" json:"sniffing,omitempty"`
state protoimpl.MessageState `protogen:"open.v1"`
InboundTag string `protobuf:"bytes,1,opt,name=inbound_tag,json=inboundTag,proto3" json:"inbound_tag,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@@ -67,22 +65,14 @@ func (x *Config) GetInboundTag() string {
return ""
}
func (x *Config) GetSniffing() *proxyman.SniffingConfig {
if x != nil {
return x.Sniffing
}
return nil
}
var File_proxy_loopback_config_proto protoreflect.FileDescriptor
const file_proxy_loopback_config_proto_rawDesc = "" +
"\n" +
"\x1bproxy/loopback/config.proto\x12\x13xray.proxy.loopback\x1a\x19app/proxyman/config.proto\"h\n" +
"\x1bproxy/loopback/config.proto\x12\x13xray.proxy.loopback\")\n" +
"\x06Config\x12\x1f\n" +
"\vinbound_tag\x18\x01 \x01(\tR\n" +
"inboundTag\x12=\n" +
"\bsniffing\x18\x02 \x01(\v2!.xray.app.proxyman.SniffingConfigR\bsniffingB[\n" +
"inboundTagB[\n" +
"\x17com.xray.proxy.loopbackP\x01Z(github.com/xtls/xray-core/proxy/loopback\xaa\x02\x13Xray.Proxy.Loopbackb\x06proto3"
var (
@@ -99,16 +89,14 @@ func file_proxy_loopback_config_proto_rawDescGZIP() []byte {
var file_proxy_loopback_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_proxy_loopback_config_proto_goTypes = []any{
(*Config)(nil), // 0: xray.proxy.loopback.Config
(*proxyman.SniffingConfig)(nil), // 1: xray.app.proxyman.SniffingConfig
(*Config)(nil), // 0: xray.proxy.loopback.Config
}
var file_proxy_loopback_config_proto_depIdxs = []int32{
1, // 0: xray.proxy.loopback.Config.sniffing:type_name -> xray.app.proxyman.SniffingConfig
1, // [1:1] is the sub-list for method output_type
1, // [1:1] is the sub-list for method input_type
1, // [1:1] is the sub-list for extension type_name
1, // [1:1] is the sub-list for extension extendee
0, // [0:1] is the sub-list for field type_name
0, // [0:0] is the sub-list for method output_type
0, // [0:0] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_proxy_loopback_config_proto_init() }
-3
View File
@@ -6,9 +6,6 @@ option go_package = "github.com/xtls/xray-core/proxy/loopback";
option java_package = "com.xray.proxy.loopback";
option java_multiple_files = true;
import "app/proxyman/config.proto";
message Config {
string inbound_tag = 1;
xray.app.proxyman.SniffingConfig sniffing = 2;
}
+5 -14
View File
@@ -3,7 +3,6 @@ package loopback
import (
"context"
proxyman "github.com/xtls/xray-core/app/proxyman"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/session"
@@ -14,8 +13,7 @@ import (
)
type Loopback struct {
inboundTag string
sniffingRequest session.SniffingRequest
config *Config
dispatcherInstance routing.Dispatcher
}
@@ -31,7 +29,6 @@ func (l *Loopback) Process(ctx context.Context, link *transport.Link, _ internet
errors.LogInfo(ctx, "opening connection to ", destination)
content := new(session.Content)
content.SkipDNSResolve = true
content.SniffingRequest = l.sniffingRequest
ctx = session.ContextWithContent(ctx, content)
inbound := &session.Inbound{}
@@ -40,26 +37,20 @@ func (l *Loopback) Process(ctx context.Context, link *transport.Link, _ internet
// get a shallow copy to avoid modifying the inbound tag in upstream context
*inbound = *originInbound
}
inbound.Tag = l.inboundTag
inbound.Tag = l.config.InboundTag
ctx = session.ContextWithInbound(ctx, inbound)
err := l.dispatcherInstance.DispatchLink(ctx, destination, link)
if err != nil {
return errors.New(ctx, "failed to process loopback connection").Base(err)
errors.New(ctx, "failed to process loopback connection").Base(err)
return err
}
return nil
}
func (l *Loopback) init(config *Config, dispatcherInstance routing.Dispatcher) error {
l.dispatcherInstance = dispatcherInstance
l.inboundTag = config.InboundTag
if config.Sniffing.GetEnabled() {
request, err := proxyman.BuildSniffingRequest(config.Sniffing)
if err != nil {
return errors.New("failed to build loopback sniffing request").Base(err).AtError()
}
l.sniffingRequest = request
}
l.config = config
return nil
}
+10 -11
View File
@@ -3,7 +3,6 @@ package finalmask
import (
"context"
"net"
"slices"
"github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/common/errors"
@@ -29,7 +28,7 @@ func NewUdpmaskManager(udpmasks []Udpmask) *UdpmaskManager {
func (m *UdpmaskManager) WrapPacketConnClient(raw net.PacketConn) (net.PacketConn, error) {
var sizes []int
var conns []net.PacketConn
for i, mask := range slices.Backward(m.udpmasks) {
for i, mask := range m.udpmasks {
if _, ok := mask.(headerConn); ok {
conn, err := mask.WrapPacketConnClient(nil, i, len(m.udpmasks)-1)
if err != nil {
@@ -62,7 +61,7 @@ func (m *UdpmaskManager) WrapPacketConnClient(raw net.PacketConn) (net.PacketCon
func (m *UdpmaskManager) WrapPacketConnServer(raw net.PacketConn) (net.PacketConn, error) {
var sizes []int
var conns []net.PacketConn
for i, mask := range slices.Backward(m.udpmasks) {
for i, mask := range m.udpmasks {
if _, ok := mask.(headerConn); ok {
conn, err := mask.WrapPacketConnServer(nil, i, len(m.udpmasks)-1)
if err != nil {
@@ -125,7 +124,7 @@ func (c *headerManagerConn) ReadFrom(p []byte) (n int, addr net.Addr, err error)
if err != nil {
return n, addr, err
}
buf := b[:n]
b = b[:n]
sum := 0
for _, size := range c.sizes {
@@ -133,24 +132,24 @@ func (c *headerManagerConn) ReadFrom(p []byte) (n int, addr net.Addr, err error)
}
if n < sum {
errors.LogError(context.Background(), "[mask] drop packet from ", addr, " with size ", n)
errors.LogError(context.Background(), "[mask] drop packet from ", addr, " with size ", len(b))
continue
}
for i := range c.conns {
n, _, err = c.conns[i].ReadFrom(buf)
n, _, err = c.conns[i].ReadFrom(b)
if err != nil {
errors.LogErrorInner(context.Background(), err, "[mask] drop packet from ", addr, " with size ", n)
errors.LogErrorInner(context.Background(), err, "[mask] drop packet from ", addr, " with size ", len(b))
break
}
buf = buf[c.sizes[i] : n+c.sizes[i]]
b = b[c.sizes[i] : n+c.sizes[i]]
}
if err != nil {
continue
}
return copy(p, buf), addr, nil
return copy(p, b), addr, nil
}
}
@@ -213,7 +212,7 @@ func NewTcpmaskManager(tcpmasks []Tcpmask) *TcpmaskManager {
func (m *TcpmaskManager) WrapConnClient(raw net.Conn) (net.Conn, error) {
var err error
for _, mask := range slices.Backward(m.tcpmasks) {
for _, mask := range m.tcpmasks {
raw, err = mask.WrapConnClient(raw)
if err != nil {
return nil, err
@@ -224,7 +223,7 @@ func (m *TcpmaskManager) WrapConnClient(raw net.Conn) (net.Conn, error) {
func (m *TcpmaskManager) WrapConnServer(raw net.Conn) (net.Conn, error) {
var err error
for _, mask := range slices.Backward(m.tcpmasks) {
for _, mask := range m.tcpmasks {
raw, err = mask.WrapConnServer(raw)
if err != nil {
return nil, err
+2 -2
View File
@@ -62,7 +62,7 @@ func dialgRPC(ctx context.Context, dest net.Destination, streamSettings *interne
if err != nil {
return nil, errors.New("Cannot dial gRPC").Base(err)
}
return encoding.NewMultiHunkConn(grpcService, nil, nil), nil
return encoding.NewMultiHunkConn(grpcService, nil), nil
}
errors.LogDebug(ctx, "using gRPC tun mode service name: `"+grpcSettings.getServiceName()+"` stream name: `"+grpcSettings.getTunStreamName()+"`")
@@ -71,7 +71,7 @@ func dialgRPC(ctx context.Context, dest net.Destination, streamSettings *interne
return nil, errors.New("Cannot dial gRPC").Base(err)
}
return encoding.NewHunkConn(grpcService, nil, nil), nil
return encoding.NewHunkConn(grpcService, nil), nil
}
func getGrpcClient(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (*grpc.ClientConn, error) {
+27 -2
View File
@@ -9,6 +9,8 @@ import (
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/net/cnc"
"github.com/xtls/xray-core/common/signal/done"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/peer"
)
type HunkConn interface {
@@ -36,8 +38,31 @@ func NewHunkReadWriter(hc HunkConn, cancel context.CancelFunc) *HunkReaderWriter
return &HunkReaderWriter{hc, cancel, done.New(), nil, 0}
}
func NewHunkConn(hc HunkConn, cancel context.CancelFunc, trustedXForwardedFor []string) net.Conn {
rAddr := remoteAddrFromContext(hc.Context(), trustedXForwardedFor)
func NewHunkConn(hc HunkConn, cancel context.CancelFunc) net.Conn {
var rAddr net.Addr
pr, ok := peer.FromContext(hc.Context())
if ok {
rAddr = pr.Addr
} else {
rAddr = &net.TCPAddr{
IP: []byte{0, 0, 0, 0},
Port: 0,
}
}
md, ok := metadata.FromIncomingContext(hc.Context())
if ok {
header := md.Get("x-real-ip")
if len(header) > 0 {
realip := net.ParseAddress(header[0])
if realip.Family().IsIP() {
rAddr = &net.TCPAddr{
IP: realip.IP(),
Port: 0,
}
}
}
}
wrc := NewHunkReadWriter(hc, cancel)
return cnc.NewConnection(
cnc.ConnectionInput(wrc),
+29 -3
View File
@@ -3,12 +3,15 @@ package encoding
import (
"context"
"io"
"net"
"github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net"
xnet "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/net/cnc"
"github.com/xtls/xray-core/common/signal/done"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/peer"
)
type MultiHunkConn interface {
@@ -31,8 +34,31 @@ func NewMultiHunkReadWriter(hc MultiHunkConn, cancel context.CancelFunc) *MultiH
return &MultiHunkReaderWriter{hc, cancel, done.New(), nil}
}
func NewMultiHunkConn(hc MultiHunkConn, cancel context.CancelFunc, trustedXForwardedFor []string) net.Conn {
rAddr := remoteAddrFromContext(hc.Context(), trustedXForwardedFor)
func NewMultiHunkConn(hc MultiHunkConn, cancel context.CancelFunc) net.Conn {
var rAddr net.Addr
pr, ok := peer.FromContext(hc.Context())
if ok {
rAddr = pr.Addr
} else {
rAddr = &net.TCPAddr{
IP: []byte{0, 0, 0, 0},
Port: 0,
}
}
md, ok := metadata.FromIncomingContext(hc.Context())
if ok {
header := md.Get("x-real-ip")
if len(header) > 0 {
realip := xnet.ParseAddress(header[0])
if realip.Family().IsIP() {
rAddr = &net.TCPAddr{
IP: realip.IP(),
Port: 0,
}
}
}
}
wrc := NewMultiHunkReadWriter(hc, cancel)
return cnc.NewConnection(
cnc.ConnectionInputMulti(wrc),
@@ -1,58 +0,0 @@
package encoding
import (
"context"
"strings"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/peer"
)
func remoteAddrFromContext(ctx context.Context, trusted []string) net.Addr {
var remoteAddr net.Addr
if pr, ok := peer.FromContext(ctx); ok {
remoteAddr = pr.Addr
} else {
remoteAddr = &net.TCPAddr{
IP: []byte{0, 0, 0, 0},
Port: 0,
}
}
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return remoteAddr
}
if forwardedAddr := parseTrustedXForwardedFor(md, trusted, remoteAddr); forwardedAddr != nil && forwardedAddr.Family().IsIP() {
remoteAddr = &net.TCPAddr{
IP: forwardedAddr.IP(),
Port: 0,
}
}
return remoteAddr
}
func parseTrustedXForwardedFor(md metadata.MD, trusted []string, remoteAddr net.Addr) net.Address {
values := md.Get("X-Forwarded-For")
if len(values) == 0 || values[0] == "" {
return nil
}
value := values[0]
for _, t := range trusted {
if len(md.Get(t)) > 0 {
if idx := strings.IndexByte(value, ','); idx >= 0 {
value = value[:idx]
}
return net.ParseAddress(value)
}
}
if len(trusted) == 0 {
errors.LogWarning(context.Background(), `received "X-Forwarded-For" from `, remoteAddr, ` but "sockopt.trustedXForwardedFor" is not configured; ignoring it and using the real remote address`)
} else {
errors.LogError(context.Background(), `ignored potentially forged "X-Forwarded-For" from `, remoteAddr, `: `, value)
}
return nil
}
@@ -1,53 +0,0 @@
package encoding
import (
"context"
"net"
"testing"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/peer"
)
func TestRemoteAddrFromContext(t *testing.T) {
tests := []struct {
name string
metadata metadata.MD
trustedXForwardedFor []string
expectedRemoteAddress string
}{
{
name: "trust X-Forwarded-For when configured",
metadata: metadata.Pairs("X-Forwarded-For", "2.2.2.2, 3.3.3.3"),
trustedXForwardedFor: []string{"X-Forwarded-For"},
expectedRemoteAddress: "2.2.2.2:0",
},
{
name: "trust X-Forwarded-For with trusted marker",
metadata: metadata.Pairs("X-Forwarded-For", "4.4.4.4", "X-Trusted-CDN", "1"),
trustedXForwardedFor: []string{"X-Trusted-CDN"},
expectedRemoteAddress: "4.4.4.4:0",
},
{
name: "ignore X-Forwarded-For without trusted marker",
metadata: metadata.Pairs("X-Forwarded-For", "5.5.5.5"),
trustedXForwardedFor: []string{"X-Trusted-CDN"},
expectedRemoteAddress: "127.0.0.1:12345",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
ctx := peer.NewContext(metadata.NewIncomingContext(context.Background(), test.metadata), &peer.Peer{
Addr: &net.TCPAddr{
IP: net.ParseIP("127.0.0.1"),
Port: 12345,
},
})
remoteAddr := remoteAddrFromContext(ctx, test.trustedXForwardedFor)
if remoteAddr.String() != test.expectedRemoteAddress {
t.Fatalf("unexpected remote address: %s", remoteAddr.String())
}
})
}
}
+6 -10
View File
@@ -19,25 +19,24 @@ import (
type Listener struct {
encoding.UnimplementedGRPCServiceServer
ctx context.Context
handler internet.ConnHandler
local net.Addr
config *Config
trustedXForwardedFor []string
ctx context.Context
handler internet.ConnHandler
local net.Addr
config *Config
s *grpc.Server
}
func (l Listener) Tun(server encoding.GRPCService_TunServer) error {
tunCtx, cancel := context.WithCancel(l.ctx)
l.handler(encoding.NewHunkConn(server, cancel, l.trustedXForwardedFor))
l.handler(encoding.NewHunkConn(server, cancel))
<-tunCtx.Done()
return nil
}
func (l Listener) TunMulti(server encoding.GRPCService_TunMultiServer) error {
tunCtx, cancel := context.WithCancel(l.ctx)
l.handler(encoding.NewMultiHunkConn(server, cancel, l.trustedXForwardedFor))
l.handler(encoding.NewMultiHunkConn(server, cancel))
<-tunCtx.Done()
return nil
}
@@ -75,9 +74,6 @@ func Listen(ctx context.Context, address net.Address, port net.Port, settings *i
}
listener.ctx = ctx
if settings.SocketSettings != nil {
listener.trustedXForwardedFor = settings.SocketSettings.TrustedXForwardedFor
}
config := tls.ConfigFromStreamSettings(settings)
@@ -138,9 +138,6 @@ func TestDialWithRemoteAddr(t *testing.T) {
ProtocolSettings: &Config{
Path: "httpupgrade",
},
SocketSettings: &internet.SocketConfig{
TrustedXForwardedFor: []string{"X-Forwarded-For"},
},
}, func(conn stat.Connection) {
go func(c stat.Connection) {
defer c.Close()
+16 -4
View File
@@ -80,12 +80,24 @@ func (s *server) upgrade(conn net.Conn) (stat.Connection, error) {
return nil, err
}
var forwardedAddrs []net.Address
if s.socketSettings != nil && len(s.socketSettings.TrustedXForwardedFor) > 0 {
for _, key := range s.socketSettings.TrustedXForwardedFor {
if len(req.Header.Values(key)) > 0 {
forwardedAddrs = http_proto.ParseXForwardedFor(req.Header)
break
}
}
} else {
forwardedAddrs = http_proto.ParseXForwardedFor(req.Header)
}
remoteAddr := conn.RemoteAddr()
var trustedXFF []string
if s.socketSettings != nil {
trustedXFF = s.socketSettings.TrustedXForwardedFor
if len(forwardedAddrs) > 0 && forwardedAddrs[0].Family().IsIP() {
remoteAddr = &net.TCPAddr{
IP: forwardedAddrs[0].IP(),
Port: int(0),
}
}
remoteAddr = http_proto.ApplyTrustedXForwardedFor(req.Header, trustedXFF, remoteAddr)
return stat.Connection(newConnection(conn, remoteAddr)), nil
}
+1 -15
View File
@@ -9,7 +9,6 @@ import (
"net/http/httptrace"
"sync"
"github.com/apernet/quic-go/http3"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/common/errors"
@@ -60,11 +59,7 @@ func (c *DefaultDialerClient) OpenStream(ctx context.Context, url string, sessio
if body != nil {
method = c.transportConfig.GetNormalizedUplinkHTTPMethod() // stream-up/one
}
req, err := http.NewRequestWithContext(context.WithoutCancel(ctx), method, url, body)
if err != nil {
errors.LogInfoInner(ctx, err, "failed to create HTTP request for "+url)
return nil, nil, nil, err
}
req, _ := http.NewRequestWithContext(context.WithoutCancel(ctx), method, url, body)
c.transportConfig.FillStreamRequest(req, sessionId, "")
wrc = &WaitReadCloser{Wait: make(chan struct{})}
@@ -177,15 +172,6 @@ func (c *DefaultDialerClient) PostPacket(ctx context.Context, url string, sessio
return nil
}
// HTTP/1.1 and HTTP/2 will close itself, we only handle HTTP/3 here
func (c *DefaultDialerClient) Close() error {
transport := c.client.Transport
if h3Transport, ok := transport.(*http3.Transport); ok {
h3Transport.Close()
}
return nil
}
type WaitReadCloser struct {
Wait chan struct{}
io.ReadCloser
+4 -5
View File
@@ -259,7 +259,6 @@ func createHTTPClient(dest net.Destination, streamSettings *internet.MemoryStrea
if err != nil {
return nil, err
}
context.AfterFunc(conn.Context(), func() { pktConn.Close() })
switch quicParams.Congestion {
case "reno":
@@ -426,10 +425,10 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me
}
if xmuxClient != nil {
xmuxClient.AddRunning()
xmuxClient.OpenUsage.Add(1)
}
if xmuxClient2 != nil && xmuxClient2 != xmuxClient {
xmuxClient2.AddRunning()
xmuxClient2.OpenUsage.Add(1)
}
var closed atomic.Int32
@@ -441,10 +440,10 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me
return
}
if xmuxClient != nil {
xmuxClient.DoneRunning()
xmuxClient.OpenUsage.Add(-1)
}
if xmuxClient2 != nil && xmuxClient2 != xmuxClient {
xmuxClient2.DoneRunning()
xmuxClient2.OpenUsage.Add(-1)
}
},
}
+16 -4
View File
@@ -155,6 +155,17 @@ func (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Req
return
}
var forwardedAddrs []net.Address
if h.socketSettings != nil && len(h.socketSettings.TrustedXForwardedFor) > 0 {
for _, key := range h.socketSettings.TrustedXForwardedFor {
if len(request.Header.Values(key)) > 0 {
forwardedAddrs = http_proto.ParseXForwardedFor(request.Header)
break
}
}
} else {
forwardedAddrs = http_proto.ParseXForwardedFor(request.Header)
}
var remoteAddr net.Addr
var err error
remoteAddr, err = net.ResolveTCPAddr("tcp", request.RemoteAddr)
@@ -170,11 +181,12 @@ func (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Req
Port: remoteAddr.(*net.TCPAddr).Port,
}
}
var trustedXFF []string
if h.socketSettings != nil {
trustedXFF = h.socketSettings.TrustedXForwardedFor
if len(forwardedAddrs) > 0 && forwardedAddrs[0].Family().IsIP() {
remoteAddr = &net.TCPAddr{
IP: forwardedAddrs[0].IP(),
Port: 0,
}
}
remoteAddr = http_proto.ApplyTrustedXForwardedFor(request.Header, trustedXFF, remoteAddr)
var currentSession *httpSession
if sessionId != "" {
+3 -23
View File
@@ -8,7 +8,6 @@ import (
"sync/atomic"
"time"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/errors"
)
@@ -18,27 +17,10 @@ type XmuxConn interface {
type XmuxClient struct {
XmuxConn XmuxConn
Running atomic.Int32
OpenUsage atomic.Int32
leftUsage int32
LeftRequests atomic.Int32
UnreusableAt time.Time
NotUsed atomic.Bool
}
func (c *XmuxClient) AddRunning() {
c.Running.Add(1)
}
func (c *XmuxClient) DoneRunning() {
c.Running.Add(-1)
c.maybeClose()
}
// close the XmuxConn if it is not used and has no running requests
func (c *XmuxClient) maybeClose() {
if c.NotUsed.Load() && c.Running.Load() <= 0 {
common.Close(c.XmuxConn)
}
}
type XmuxManager struct {
@@ -86,12 +68,10 @@ func (m *XmuxManager) GetXmuxClient(ctx context.Context) *XmuxClient { // when l
xmuxClient.LeftRequests.Load() <= 0 ||
(xmuxClient.UnreusableAt != time.Time{} && time.Now().After(xmuxClient.UnreusableAt)) {
errors.LogDebug(ctx, "XMUX: removing xmuxClient, IsClosed() = ", xmuxClient.XmuxConn.IsClosed(),
", Running = ", xmuxClient.Running.Load(),
", OpenUsage = ", xmuxClient.OpenUsage.Load(),
", leftUsage = ", xmuxClient.leftUsage,
", LeftRequests = ", xmuxClient.LeftRequests.Load(),
", UnreusableAt = ", xmuxClient.UnreusableAt)
xmuxClient.NotUsed.Store(true)
xmuxClient.maybeClose()
m.xmuxClients = append(m.xmuxClients[:i], m.xmuxClients[i+1:]...)
} else {
i++
@@ -111,7 +91,7 @@ func (m *XmuxManager) GetXmuxClient(ctx context.Context) *XmuxClient { // when l
xmuxClients := make([]*XmuxClient, 0)
if m.concurrency > 0 {
for _, xmuxClient := range m.xmuxClients {
if xmuxClient.Running.Load() < m.concurrency {
if xmuxClient.OpenUsage.Load() < m.concurrency {
xmuxClients = append(xmuxClients, xmuxClient)
}
}
+2 -2
View File
@@ -63,7 +63,7 @@ func TestMaxConcurrency(t *testing.T) {
xmuxClients := make(map[interface{}]struct{})
for i := 0; i < 64; i++ {
xmuxClient := xmuxManager.GetXmuxClient(context.Background())
xmuxClient.AddRunning()
xmuxClient.OpenUsage.Add(1)
xmuxClients[xmuxClient] = struct{}{}
}
@@ -82,7 +82,7 @@ func TestDefault(t *testing.T) {
xmuxClients := make(map[interface{}]struct{})
for i := 0; i < 64; i++ {
xmuxClient := xmuxManager.GetXmuxClient(context.Background())
xmuxClient.AddRunning()
xmuxClient.OpenUsage.Add(1)
xmuxClients[xmuxClient] = struct{}{}
}
@@ -88,9 +88,6 @@ func TestDialWithRemoteAddr(t *testing.T) {
ProtocolSettings: &Config{
Path: "sh",
},
SocketSettings: &internet.SocketConfig{
TrustedXForwardedFor: []string{"X-Forwarded-For"},
},
}, func(conn stat.Connection) {
go func(c stat.Connection) {
defer c.Close()
+16 -4
View File
@@ -65,12 +65,24 @@ func (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Req
return
}
var forwardedAddrs []net.Address
if h.socketSettings != nil && len(h.socketSettings.TrustedXForwardedFor) > 0 {
for _, key := range h.socketSettings.TrustedXForwardedFor {
if len(request.Header.Values(key)) > 0 {
forwardedAddrs = http_proto.ParseXForwardedFor(request.Header)
break
}
}
} else {
forwardedAddrs = http_proto.ParseXForwardedFor(request.Header)
}
remoteAddr := conn.RemoteAddr()
var trustedXFF []string
if h.socketSettings != nil {
trustedXFF = h.socketSettings.TrustedXForwardedFor
if len(forwardedAddrs) > 0 && forwardedAddrs[0].Family().IsIP() {
remoteAddr = &net.TCPAddr{
IP: forwardedAddrs[0].IP(),
Port: int(0),
}
}
remoteAddr = http_proto.ApplyTrustedXForwardedFor(request.Header, trustedXFF, remoteAddr)
h.ln.addConn(NewConnection(conn, remoteAddr, extraReader, h.ln.config.HeartbeatPeriod))
}
-3
View File
@@ -79,9 +79,6 @@ func TestDialWithRemoteAddr(t *testing.T) {
ProtocolSettings: &Config{
Path: "ws",
},
SocketSettings: &internet.SocketConfig{
TrustedXForwardedFor: []string{"X-Forwarded-For"},
},
}, func(conn stat.Connection) {
go func(c stat.Connection) {
defer c.Close()