Compare commits

..
Author SHA1 Message Date
Fangliding 9105f80b98 Fix unexpected TTL clamp 2026-06-23 02:53:23 +08:00
11 changed files with 84 additions and 209 deletions
+7 -2
View File
@@ -198,9 +198,14 @@ 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 {
@@ -217,7 +222,7 @@ L:
ttl = 1
}
expire := now.Add(time.Duration(ttl) * time.Second)
if ipRecord.Expire.After(expire) {
if ipRecord.Expire.IsZero() || ipRecord.Expire.After(expire) {
ipRecord.Expire = expire
}
+1 -1
View File
@@ -20,7 +20,7 @@ import (
var (
Version_x byte = 26
Version_y byte = 6
Version_z byte = 22
Version_z byte = 1
)
var (
+2 -2
View File
@@ -4,7 +4,7 @@ go 1.26
require (
github.com/apernet/quic-go v0.59.1-0.20260425001925-6c6cc9bcb716
github.com/cloudflare/circl v1.6.4
github.com/cloudflare/circl v1.6.3
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344
github.com/golang/mock v1.7.0-rc.1
github.com/google/go-cmp v0.7.0
@@ -12,7 +12,7 @@ require (
github.com/klauspost/cpuid/v2 v2.3.0
github.com/miekg/dns v1.1.72
github.com/pelletier/go-toml v1.9.5
github.com/pion/stun/v3 v3.1.6
github.com/pion/stun/v3 v3.1.5
github.com/pires/go-proxyproto v0.12.0
github.com/refraction-networking/utls v1.8.3-0.20260301010127-aa6edf4b11af
github.com/robfig/cron/v3 v3.0.1
+4 -4
View File
@@ -4,8 +4,8 @@ github.com/apernet/quic-go v0.59.1-0.20260425001925-6c6cc9bcb716 h1:J1O+xpLuJWkd
github.com/apernet/quic-go v0.59.1-0.20260425001925-6c6cc9bcb716/go.mod h1:Npbg8qBtAZlsAB3FWmqwlVh5jtVG6a4DlYsOylUpvzA=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cloudflare/circl v1.6.4 h1:pOXuDTCEYyzydgUpQ0CQz3LsinKjiSk6nNP5Lt5K64U=
github.com/cloudflare/circl v1.6.4/go.mod h1:YxarevkLlbaHuWsxG6vmYNWBEsSp4pnp7j+4VljMavY=
github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -49,8 +49,8 @@ github.com/pion/dtls/v3 v3.1.4 h1:QhvtMflMfu9Kf0RcDC5BJBle4caPskByrKQR6uuYqpY=
github.com/pion/dtls/v3 v3.1.4/go.mod h1:cr/qotLISUw/9C1m83ZPNZtj9WnXkYLpfCptPqbkInc=
github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8=
github.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so=
github.com/pion/stun/v3 v3.1.6 h1:WnhsD0eHCiwCfKNkVx0VJJwr2Y3eV4Ueih3KJ+dfZy8=
github.com/pion/stun/v3 v3.1.6/go.mod h1:zRUghXSQU32Lx5orJsz3uYMkIihweXb3mu5gIns02fs=
github.com/pion/stun/v3 v3.1.5 h1:Y1FHlhaI6+4UoC5i/zQf4F7JvdZtB24/05oyy/GF1x8=
github.com/pion/stun/v3 v3.1.5/go.mod h1:zRUghXSQU32Lx5orJsz3uYMkIihweXb3mu5gIns02fs=
github.com/pion/transport/v4 v4.0.2 h1:ifYlPqNwsy6aKQ9y8yzxXlHae5431ZrH2avkD/Rn6Tk=
github.com/pion/transport/v4 v4.0.2/go.mod h1:06hFI+jCFcok2X2MekVufNZ/uzNZXivGBPfviSVcjgM=
github.com/pires/go-proxyproto v0.12.0 h1:TTCxD66dU898tahivkqc3hoceZp7P44FnorWyo9d5vM=
+10 -27
View File
@@ -1409,12 +1409,10 @@ func (c *HeaderCustomTCP) Build() (proto.Message, error) {
}
type FragmentMask struct {
Packets string `json:"packets"`
Length Int32Range `json:"length"`
Delay Int32Range `json:"delay"`
Lengths []Int32Range `json:"lengths"`
Delays []Int32Range `json:"delays"`
MaxSplit Int32Range `json:"maxSplit"`
Packets string `json:"packets"`
Length Int32Range `json:"length"`
Delay Int32Range `json:"delay"`
MaxSplit Int32Range `json:"maxSplit"`
}
func (c *FragmentMask) Build() (proto.Message, error) {
@@ -1439,29 +1437,14 @@ func (c *FragmentMask) Build() (proto.Message, error) {
}
}
if len(c.Lengths) > 0 {
for _, r := range c.Lengths {
config.LengthsMin = append(config.LengthsMin, int64(r.From))
config.LengthsMax = append(config.LengthsMax, int64(r.To))
}
} else {
config.LengthsMin = append(config.LengthsMin, int64(c.Length.From))
config.LengthsMax = append(config.LengthsMax, int64(c.Length.To))
config.LengthMin = int64(c.Length.From)
config.LengthMax = int64(c.Length.To)
if config.LengthMin == 0 {
return nil, errors.New("LengthMin can't be 0")
}
if config.LengthsMin[len(config.LengthsMin)-1] == 0 {
return nil, errors.New("last lengths entry min can't be 0")
}
if len(c.Delays) > 0 {
for _, r := range c.Delays {
config.DelaysMin = append(config.DelaysMin, int64(r.From))
config.DelaysMax = append(config.DelaysMax, int64(r.To))
}
} else {
config.DelaysMin = append(config.DelaysMin, int64(c.Delay.From))
config.DelaysMax = append(config.DelaysMax, int64(c.Delay.To))
}
config.DelayMin = int64(c.Delay.From)
config.DelayMax = int64(c.Delay.To)
config.MaxSplitMin = int64(c.MaxSplit.From)
config.MaxSplitMax = int64(c.MaxSplit.To)
+1 -8
View File
@@ -143,14 +143,7 @@ func (t *Handler) HandleConnection(conn net.Conn, destination net.Destination) {
defer cancel()
ctx = c.ContextWithID(ctx, session.NewID())
// if the connection is already closed, conn.RemoteAddr() will be nil
// due to gvisor weird behavior
remote := conn.RemoteAddr()
if remote == nil {
errors.LogInfo(t.ctx, "dropped quickly closed connection")
return
}
source := net.DestinationFromAddr(remote)
source := net.DestinationFromAddr(conn.RemoteAddr())
inbound := session.Inbound{
Name: "tun",
Tag: t.tag,
+3 -73
View File
@@ -4,11 +4,8 @@ package tun
import (
"net"
"strconv"
"github.com/vishvananda/netlink"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/platform"
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/tcpip/link/fdbased"
"gvisor.dev/gvisor/pkg/tcpip/stack"
@@ -21,7 +18,6 @@ type LinuxTun struct {
tunFd int
tunLink netlink.Link
options *Config
ownsTun bool
}
// LinuxTun implements Tun
@@ -29,24 +25,12 @@ var _ Tun = (*LinuxTun)(nil)
// NewTun builds new tun interface handler (linux specific)
func NewTun(options *Config) (Tun, error) {
tunFd, tunLink, fdProvided, err := openFromEnv(options.Name)
if err != nil {
return nil, err
}
if fdProvided {
return &LinuxTun{
tunFd: tunFd,
tunLink: tunLink,
options: options,
}, nil
}
tunFd, err = open(options.Name)
tunFd, err := open(options.Name)
if err != nil {
return nil, err
}
tunLink, err = setup(options.Name, int(options.MTU))
tunLink, err := setup(options.Name, int(options.MTU))
if err != nil {
_ = unix.Close(tunFd)
return nil, err
@@ -56,59 +40,11 @@ func NewTun(options *Config) (Tun, error) {
tunFd: tunFd,
tunLink: tunLink,
options: options,
ownsTun: true,
}
return linuxTun, nil
}
func openFromEnv(expectedName string) (int, netlink.Link, bool, error) {
fdStr := platform.NewEnvFlag(platform.TunFdKey).GetValue(func() string { return "" })
if fdStr == "" {
return -1, nil, false, nil
}
fd, err := strconv.Atoi(fdStr)
if err != nil {
return -1, nil, true, errors.New("invalid ", platform.TunFdKey).Base(err)
}
if fd < 3 {
return -1, nil, true, errors.New("invalid ", platform.TunFdKey, ": file descriptor must be >= 3")
}
ifr, err := unix.NewIfreq("")
if err != nil {
return -1, nil, true, err
}
if err = unix.IoctlIfreq(fd, unix.TUNGETIFF, ifr); err != nil {
return -1, nil, true, err
}
flags := ifr.Uint16()
if flags&unix.IFF_TUN == 0 {
return -1, nil, true, errors.New("invalid ", platform.TunFdKey, ": file descriptor is not a TUN device")
}
if flags&unix.IFF_NO_PI == 0 {
return -1, nil, true, errors.New("invalid ", platform.TunFdKey, ": TUN device must use IFF_NO_PI")
}
actualName := ifr.Name()
if expectedName != "" && actualName != expectedName {
return -1, nil, true, errors.New("invalid ", platform.TunFdKey, ": TUN device name ", actualName, " does not match configured name ", expectedName)
}
tunLink, err := netlink.LinkByName(actualName)
if err != nil {
return -1, nil, true, err
}
if err = unix.SetNonblock(fd, true); err != nil {
return -1, nil, true, err
}
return fd, tunLink, true, nil
}
// open the file that implements tun interface in the OS
func open(name string) (int, error) {
fd, err := unix.Open("/dev/net/tun", unix.O_RDWR, 0)
@@ -157,10 +93,6 @@ func setup(name string, MTU int) (netlink.Link, error) {
// Start is called by handler to bring tun interface to life
func (t *LinuxTun) Start() error {
if !t.ownsTun {
return nil
}
err := netlink.LinkSetUp(t.tunLink)
if err != nil {
return err
@@ -171,9 +103,7 @@ func (t *LinuxTun) Start() error {
// Close is called to shut down the tun interface
func (t *LinuxTun) Close() error {
if t.ownsTun {
_ = netlink.LinkSetDown(t.tunLink)
}
_ = netlink.LinkSetDown(t.tunLink)
_ = unix.Close(t.tunFd)
return nil
@@ -25,12 +25,12 @@ type Config struct {
state protoimpl.MessageState `protogen:"open.v1"`
PacketsFrom int64 `protobuf:"varint,1,opt,name=packets_from,json=packetsFrom,proto3" json:"packets_from,omitempty"`
PacketsTo int64 `protobuf:"varint,2,opt,name=packets_to,json=packetsTo,proto3" json:"packets_to,omitempty"`
LengthMin int64 `protobuf:"varint,3,opt,name=length_min,json=lengthMin,proto3" json:"length_min,omitempty"`
LengthMax int64 `protobuf:"varint,4,opt,name=length_max,json=lengthMax,proto3" json:"length_max,omitempty"`
DelayMin int64 `protobuf:"varint,5,opt,name=delay_min,json=delayMin,proto3" json:"delay_min,omitempty"`
DelayMax int64 `protobuf:"varint,6,opt,name=delay_max,json=delayMax,proto3" json:"delay_max,omitempty"`
MaxSplitMin int64 `protobuf:"varint,7,opt,name=max_split_min,json=maxSplitMin,proto3" json:"max_split_min,omitempty"`
MaxSplitMax int64 `protobuf:"varint,8,opt,name=max_split_max,json=maxSplitMax,proto3" json:"max_split_max,omitempty"`
LengthsMin []int64 `protobuf:"varint,9,rep,packed,name=lengths_min,json=lengthsMin,proto3" json:"lengths_min,omitempty"`
LengthsMax []int64 `protobuf:"varint,10,rep,packed,name=lengths_max,json=lengthsMax,proto3" json:"lengths_max,omitempty"`
DelaysMin []int64 `protobuf:"varint,11,rep,packed,name=delays_min,json=delaysMin,proto3" json:"delays_min,omitempty"`
DelaysMax []int64 `protobuf:"varint,12,rep,packed,name=delays_max,json=delaysMax,proto3" json:"delays_max,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@@ -79,6 +79,34 @@ func (x *Config) GetPacketsTo() int64 {
return 0
}
func (x *Config) GetLengthMin() int64 {
if x != nil {
return x.LengthMin
}
return 0
}
func (x *Config) GetLengthMax() int64 {
if x != nil {
return x.LengthMax
}
return 0
}
func (x *Config) GetDelayMin() int64 {
if x != nil {
return x.DelayMin
}
return 0
}
func (x *Config) GetDelayMax() int64 {
if x != nil {
return x.DelayMax
}
return 0
}
func (x *Config) GetMaxSplitMin() int64 {
if x != nil {
return x.MaxSplitMin
@@ -93,54 +121,23 @@ func (x *Config) GetMaxSplitMax() int64 {
return 0
}
func (x *Config) GetLengthsMin() []int64 {
if x != nil {
return x.LengthsMin
}
return nil
}
func (x *Config) GetLengthsMax() []int64 {
if x != nil {
return x.LengthsMax
}
return nil
}
func (x *Config) GetDelaysMin() []int64 {
if x != nil {
return x.DelaysMin
}
return nil
}
func (x *Config) GetDelaysMax() []int64 {
if x != nil {
return x.DelaysMax
}
return nil
}
var File_transport_internet_finalmask_fragment_config_proto protoreflect.FileDescriptor
const file_transport_internet_finalmask_fragment_config_proto_rawDesc = "" +
"\n" +
"2transport/internet/finalmask/fragment/config.proto\x12*xray.transport.internet.finalmask.fragment\"\x92\x02\n" +
"2transport/internet/finalmask/fragment/config.proto\x12*xray.transport.internet.finalmask.fragment\"\x8a\x02\n" +
"\x06Config\x12!\n" +
"\fpackets_from\x18\x01 \x01(\x03R\vpacketsFrom\x12\x1d\n" +
"\n" +
"packets_to\x18\x02 \x01(\x03R\tpacketsTo\x12\"\n" +
"packets_to\x18\x02 \x01(\x03R\tpacketsTo\x12\x1d\n" +
"\n" +
"length_min\x18\x03 \x01(\x03R\tlengthMin\x12\x1d\n" +
"\n" +
"length_max\x18\x04 \x01(\x03R\tlengthMax\x12\x1b\n" +
"\tdelay_min\x18\x05 \x01(\x03R\bdelayMin\x12\x1b\n" +
"\tdelay_max\x18\x06 \x01(\x03R\bdelayMax\x12\"\n" +
"\rmax_split_min\x18\a \x01(\x03R\vmaxSplitMin\x12\"\n" +
"\rmax_split_max\x18\b \x01(\x03R\vmaxSplitMax\x12\x1f\n" +
"\vlengths_min\x18\t \x03(\x03R\n" +
"lengthsMin\x12\x1f\n" +
"\vlengths_max\x18\n" +
" \x03(\x03R\n" +
"lengthsMax\x12\x1d\n" +
"\n" +
"delays_min\x18\v \x03(\x03R\tdelaysMin\x12\x1d\n" +
"\n" +
"delays_max\x18\f \x03(\x03R\tdelaysMaxB\xa0\x01\n" +
"\rmax_split_max\x18\b \x01(\x03R\vmaxSplitMaxB\xa0\x01\n" +
".com.xray.transport.internet.finalmask.fragmentP\x01Z?github.com/xtls/xray-core/transport/internet/finalmask/fragment\xaa\x02*Xray.Transport.Internet.Finalmask.Fragmentb\x06proto3"
var (
@@ -9,10 +9,10 @@ option java_multiple_files = true;
message Config {
int64 packets_from = 1;
int64 packets_to = 2;
int64 length_min = 3;
int64 length_max = 4;
int64 delay_min = 5;
int64 delay_max = 6;
int64 max_split_min = 7;
int64 max_split_max = 8;
repeated int64 lengths_min = 9;
repeated int64 lengths_max = 10;
repeated int64 delays_min = 11;
repeated int64 delays_max = 12;
}
+9 -41
View File
@@ -43,29 +43,6 @@ func (c *fragmentConn) Splice() bool {
return true
}
// lengthForSegment returns the length range (min, max) for the given segment index (0-based).
// Clamps to the last entry when the index exceeds the list length.
func (c *fragmentConn) lengthForSegment(segIdx int) (int64, int64) {
if segIdx >= len(c.config.LengthsMin) {
segIdx = len(c.config.LengthsMin) - 1
}
return c.config.LengthsMin[segIdx], c.config.LengthsMax[segIdx]
}
// delayForSegment returns the delay range (min, max) for the given segment index (0-based).
// Clamps to the last entry when the index exceeds the list length.
func (c *fragmentConn) delayForSegment(segIdx int) (int64, int64) {
if segIdx >= len(c.config.DelaysMin) {
segIdx = len(c.config.DelaysMin) - 1
}
return c.config.DelaysMin[segIdx], c.config.DelaysMax[segIdx]
}
// mergeTlsHelloSegments returns true only when delays has exactly one zero entry.
func (c *fragmentConn) mergeTlsHelloSegments() bool {
return len(c.config.DelaysMax) == 1 && c.config.DelaysMax[0] == 0
}
func (c *fragmentConn) Write(p []byte) (n int, err error) {
c.count++
@@ -80,13 +57,12 @@ func (c *fragmentConn) Write(p []byte) (n int, err error) {
data := p[5:recordLen]
buff := make([]byte, 2048)
var hello []byte
mergeHello := c.mergeTlsHelloSegments()
maxSplit := crypto.RandBetween(c.config.MaxSplitMin, c.config.MaxSplitMax)
var splitNum int64
for from := 0; ; {
lengthMin, lengthMax := c.lengthForSegment(int(splitNum))
to := from + int(crypto.RandBetween(lengthMin, lengthMax))
if to > len(data) || (maxSplit > 0 && splitNum+1 >= maxSplit) {
to := from + int(crypto.RandBetween(c.config.LengthMin, c.config.LengthMax))
splitNum++
if to > len(data) || (maxSplit > 0 && splitNum >= maxSplit) {
to = len(data)
}
l := to - from
@@ -98,19 +74,15 @@ func (c *fragmentConn) Write(p []byte) (n int, err error) {
from = to
buff[3] = byte(l >> 8)
buff[4] = byte(l)
if mergeHello {
if c.config.DelayMax == 0 {
hello = append(hello, buff[:5+l]...)
} else {
delayMin, delayMax := c.delayForSegment(int(splitNum))
_, err := c.Conn.Write(buff[:5+l])
if delayMax > 0 {
time.Sleep(time.Duration(crypto.RandBetween(delayMin, delayMax)) * time.Millisecond)
}
time.Sleep(time.Duration(crypto.RandBetween(c.config.DelayMin, c.config.DelayMax)) * time.Millisecond)
if err != nil {
return 0, err
}
}
splitNum++
if from == len(data) {
if len(hello) > 0 {
_, err := c.Conn.Write(hello)
@@ -135,9 +107,9 @@ func (c *fragmentConn) Write(p []byte) (n int, err error) {
maxSplit := crypto.RandBetween(c.config.MaxSplitMin, c.config.MaxSplitMax)
var splitNum int64
for from := 0; ; {
lengthMin, lengthMax := c.lengthForSegment(int(splitNum))
to := from + int(crypto.RandBetween(lengthMin, lengthMax))
if to > len(p) || (maxSplit > 0 && splitNum+1 >= maxSplit) {
to := from + int(crypto.RandBetween(c.config.LengthMin, c.config.LengthMax))
splitNum++
if to > len(p) || (maxSplit > 0 && splitNum >= maxSplit) {
to = len(p)
}
n, err := c.Conn.Write(p[from:to])
@@ -145,11 +117,7 @@ func (c *fragmentConn) Write(p []byte) (n int, err error) {
if err != nil {
return from, err
}
delayMin, delayMax := c.delayForSegment(int(splitNum))
if delayMax > 0 {
time.Sleep(time.Duration(crypto.RandBetween(delayMin, delayMax)) * time.Millisecond)
}
splitNum++
time.Sleep(time.Duration(crypto.RandBetween(c.config.DelayMin, c.config.DelayMax)) * time.Millisecond)
if from >= len(p) {
return from, nil
}
+2 -3
View File
@@ -146,7 +146,6 @@ func (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Req
writer.WriteHeader(http.StatusBadRequest)
return
}
obfsPaddingAccepted := h.config.XPaddingObfsMode && paddingValue != ""
sessionId, seqStr := h.config.ExtractMetaFromRequest(request, h.path)
@@ -216,8 +215,8 @@ func (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Req
writer.Header().Set("Cache-Control", "no-store")
writer.WriteHeader(http.StatusOK)
scStreamUpServerSecs := h.config.GetNormalizedScStreamUpServerSecs()
hasLegacyRefererCompatMarker := request.Header.Get("Referer") != ""
if (hasLegacyRefererCompatMarker || obfsPaddingAccepted) && scStreamUpServerSecs.To > 0 {
referrer := request.Header.Get("Referer")
if referrer != "" && scStreamUpServerSecs.To > 0 {
go func() {
for {
_, err := httpSC.Write(bytes.Repeat([]byte{'X'}, int(h.config.GetNormalizedXPaddingBytes().rand())))