diff --git a/infra/conf/transport_internet.go b/infra/conf/transport_internet.go index e24f0688ee53..146831930fba 100644 --- a/infra/conf/transport_internet.go +++ b/infra/conf/transport_internet.go @@ -387,7 +387,7 @@ func (b Bandwidth) Bps() (uint64, error) { type UdpHop struct { PortList json.RawMessage `json:"port"` - Interval int64 `json:"interval"` + Interval *Int32Range `json:"interval"` } type HysteriaConfig struct { @@ -431,13 +431,19 @@ func (c *HysteriaConfig) Build() (proto.Message, error) { hop = &PortList{} } + var inertvalMin, inertvalMax int64 + if c.UdpHop.Interval != nil { + inertvalMin = int64(c.UdpHop.Interval.From) + inertvalMax = int64(c.UdpHop.Interval.To) + } + if up > 0 && up < 65536 { return nil, errors.New("Up must be at least 65536 Bps") } if down > 0 && down < 65536 { return nil, errors.New("Down must be at least 65536 Bps") } - if c.UdpHop.Interval != 0 && c.UdpHop.Interval < 5 { + if (inertvalMin != 0 && inertvalMin < 5) || (inertvalMax != 0 && inertvalMax < 5) { return nil, errors.New("Interval must be at least 5") } @@ -467,7 +473,8 @@ func (c *HysteriaConfig) Build() (proto.Message, error) { config.Up = up config.Down = down config.Ports = hop.Build().Ports() - config.Interval = c.UdpHop.Interval + config.IntervalMin = inertvalMin + config.IntervalMax = inertvalMax config.InitStreamReceiveWindow = c.InitStreamReceiveWindow config.MaxStreamReceiveWindow = c.MaxStreamReceiveWindow config.InitConnReceiveWindow = c.InitConnectionReceiveWindow diff --git a/transport/internet/hysteria/config.pb.go b/transport/internet/hysteria/config.pb.go index 8ec3a88e9cb6..ed5cbeaa6a72 100644 --- a/transport/internet/hysteria/config.pb.go +++ b/transport/internet/hysteria/config.pb.go @@ -29,14 +29,15 @@ type Config struct { Up uint64 `protobuf:"varint,4,opt,name=up,proto3" json:"up,omitempty"` Down uint64 `protobuf:"varint,5,opt,name=down,proto3" json:"down,omitempty"` Ports []uint32 `protobuf:"varint,6,rep,packed,name=ports,proto3" json:"ports,omitempty"` - Interval int64 `protobuf:"varint,7,opt,name=interval,proto3" json:"interval,omitempty"` - InitStreamReceiveWindow uint64 `protobuf:"varint,8,opt,name=init_stream_receive_window,json=initStreamReceiveWindow,proto3" json:"init_stream_receive_window,omitempty"` - MaxStreamReceiveWindow uint64 `protobuf:"varint,9,opt,name=max_stream_receive_window,json=maxStreamReceiveWindow,proto3" json:"max_stream_receive_window,omitempty"` - InitConnReceiveWindow uint64 `protobuf:"varint,10,opt,name=init_conn_receive_window,json=initConnReceiveWindow,proto3" json:"init_conn_receive_window,omitempty"` - MaxConnReceiveWindow uint64 `protobuf:"varint,11,opt,name=max_conn_receive_window,json=maxConnReceiveWindow,proto3" json:"max_conn_receive_window,omitempty"` - MaxIdleTimeout int64 `protobuf:"varint,12,opt,name=max_idle_timeout,json=maxIdleTimeout,proto3" json:"max_idle_timeout,omitempty"` - KeepAlivePeriod int64 `protobuf:"varint,13,opt,name=keep_alive_period,json=keepAlivePeriod,proto3" json:"keep_alive_period,omitempty"` - DisablePathMtuDiscovery bool `protobuf:"varint,14,opt,name=disable_path_mtu_discovery,json=disablePathMtuDiscovery,proto3" json:"disable_path_mtu_discovery,omitempty"` + IntervalMin int64 `protobuf:"varint,7,opt,name=interval_min,json=intervalMin,proto3" json:"interval_min,omitempty"` + IntervalMax int64 `protobuf:"varint,8,opt,name=interval_max,json=intervalMax,proto3" json:"interval_max,omitempty"` + InitStreamReceiveWindow uint64 `protobuf:"varint,9,opt,name=init_stream_receive_window,json=initStreamReceiveWindow,proto3" json:"init_stream_receive_window,omitempty"` + MaxStreamReceiveWindow uint64 `protobuf:"varint,10,opt,name=max_stream_receive_window,json=maxStreamReceiveWindow,proto3" json:"max_stream_receive_window,omitempty"` + InitConnReceiveWindow uint64 `protobuf:"varint,11,opt,name=init_conn_receive_window,json=initConnReceiveWindow,proto3" json:"init_conn_receive_window,omitempty"` + MaxConnReceiveWindow uint64 `protobuf:"varint,12,opt,name=max_conn_receive_window,json=maxConnReceiveWindow,proto3" json:"max_conn_receive_window,omitempty"` + MaxIdleTimeout int64 `protobuf:"varint,13,opt,name=max_idle_timeout,json=maxIdleTimeout,proto3" json:"max_idle_timeout,omitempty"` + KeepAlivePeriod int64 `protobuf:"varint,14,opt,name=keep_alive_period,json=keepAlivePeriod,proto3" json:"keep_alive_period,omitempty"` + DisablePathMtuDiscovery bool `protobuf:"varint,15,opt,name=disable_path_mtu_discovery,json=disablePathMtuDiscovery,proto3" json:"disable_path_mtu_discovery,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -113,9 +114,16 @@ func (x *Config) GetPorts() []uint32 { return nil } -func (x *Config) GetInterval() int64 { +func (x *Config) GetIntervalMin() int64 { if x != nil { - return x.Interval + return x.IntervalMin + } + return 0 +} + +func (x *Config) GetIntervalMax() int64 { + if x != nil { + return x.IntervalMax } return 0 } @@ -173,7 +181,7 @@ var File_transport_internet_hysteria_config_proto protoreflect.FileDescriptor const file_transport_internet_hysteria_config_proto_rawDesc = "" + "\n" + - "(transport/internet/hysteria/config.proto\x12 xray.transport.internet.hysteria\"\xa7\x04\n" + + "(transport/internet/hysteria/config.proto\x12 xray.transport.internet.hysteria\"\xd1\x04\n" + "\x06Config\x12\x18\n" + "\aversion\x18\x01 \x01(\x05R\aversion\x12\x12\n" + "\x04auth\x18\x02 \x01(\tR\x04auth\x12\x1e\n" + @@ -182,16 +190,17 @@ const file_transport_internet_hysteria_config_proto_rawDesc = "" + "congestion\x12\x0e\n" + "\x02up\x18\x04 \x01(\x04R\x02up\x12\x12\n" + "\x04down\x18\x05 \x01(\x04R\x04down\x12\x14\n" + - "\x05ports\x18\x06 \x03(\rR\x05ports\x12\x1a\n" + - "\binterval\x18\a \x01(\x03R\binterval\x12;\n" + - "\x1ainit_stream_receive_window\x18\b \x01(\x04R\x17initStreamReceiveWindow\x129\n" + - "\x19max_stream_receive_window\x18\t \x01(\x04R\x16maxStreamReceiveWindow\x127\n" + - "\x18init_conn_receive_window\x18\n" + - " \x01(\x04R\x15initConnReceiveWindow\x125\n" + - "\x17max_conn_receive_window\x18\v \x01(\x04R\x14maxConnReceiveWindow\x12(\n" + - "\x10max_idle_timeout\x18\f \x01(\x03R\x0emaxIdleTimeout\x12*\n" + - "\x11keep_alive_period\x18\r \x01(\x03R\x0fkeepAlivePeriod\x12;\n" + - "\x1adisable_path_mtu_discovery\x18\x0e \x01(\bR\x17disablePathMtuDiscoveryB\x82\x01\n" + + "\x05ports\x18\x06 \x03(\rR\x05ports\x12!\n" + + "\finterval_min\x18\a \x01(\x03R\vintervalMin\x12!\n" + + "\finterval_max\x18\b \x01(\x03R\vintervalMax\x12;\n" + + "\x1ainit_stream_receive_window\x18\t \x01(\x04R\x17initStreamReceiveWindow\x129\n" + + "\x19max_stream_receive_window\x18\n" + + " \x01(\x04R\x16maxStreamReceiveWindow\x127\n" + + "\x18init_conn_receive_window\x18\v \x01(\x04R\x15initConnReceiveWindow\x125\n" + + "\x17max_conn_receive_window\x18\f \x01(\x04R\x14maxConnReceiveWindow\x12(\n" + + "\x10max_idle_timeout\x18\r \x01(\x03R\x0emaxIdleTimeout\x12*\n" + + "\x11keep_alive_period\x18\x0e \x01(\x03R\x0fkeepAlivePeriod\x12;\n" + + "\x1adisable_path_mtu_discovery\x18\x0f \x01(\bR\x17disablePathMtuDiscoveryB\x82\x01\n" + "$com.xray.transport.internet.hysteriaP\x01Z5github.com/xtls/xray-core/transport/internet/hysteria\xaa\x02 Xray.Transport.Internet.Hysteriab\x06proto3" var ( diff --git a/transport/internet/hysteria/config.proto b/transport/internet/hysteria/config.proto index 787a2b403697..09f97298f7c2 100644 --- a/transport/internet/hysteria/config.proto +++ b/transport/internet/hysteria/config.proto @@ -13,14 +13,15 @@ message Config { uint64 up = 4; uint64 down = 5; repeated uint32 ports = 6; - int64 interval = 7; + int64 interval_min = 7; + int64 interval_max = 8; - uint64 init_stream_receive_window = 8; - uint64 max_stream_receive_window = 9; - uint64 init_conn_receive_window = 10; - uint64 max_conn_receive_window = 11; - int64 max_idle_timeout = 12; - int64 keep_alive_period = 13; - bool disable_path_mtu_discovery = 14; + uint64 init_stream_receive_window = 9; + uint64 max_stream_receive_window = 10; + uint64 init_conn_receive_window = 11; + uint64 max_conn_receive_window = 12; + int64 max_idle_timeout = 13; + int64 keep_alive_period = 14; + bool disable_path_mtu_discovery = 15; } diff --git a/transport/internet/hysteria/dialer.go b/transport/internet/hysteria/dialer.go index d4723a1b5d0b..45ea580cbe6d 100644 --- a/transport/internet/hysteria/dialer.go +++ b/transport/internet/hysteria/dialer.go @@ -174,7 +174,7 @@ func (c *client) dial() error { IP: remote.(*net.UDPAddr).IP, Ports: c.config.Ports, } - pktConn, err = udphop.NewUDPHopPacketConn(addr, time.Duration(c.config.Interval)*time.Second, c.udphopDialer, pktConn, index) + pktConn, err = udphop.NewUDPHopPacketConn(addr, c.config.IntervalMin, c.config.IntervalMax, c.udphopDialer, pktConn, index) if err != nil { return errors.New("udphop err").Base(err) } diff --git a/transport/internet/hysteria/udphop/conn.go b/transport/internet/hysteria/udphop/conn.go index 5615ec5b8942..c19de476150f 100644 --- a/transport/internet/hysteria/udphop/conn.go +++ b/transport/internet/hysteria/udphop/conn.go @@ -7,6 +7,8 @@ import ( "sync" "syscall" "time" + + "github.com/xtls/xray-core/common/crypto" ) const ( @@ -17,10 +19,11 @@ const ( ) type udpHopPacketConn struct { - Addr net.Addr - Addrs []net.Addr - HopInterval time.Duration - ListenUDPFunc ListenUDPFunc + Addr net.Addr + Addrs []net.Addr + HopIntervalMin int64 + HopIntervalMax int64 + ListenUDPFunc ListenUDPFunc connMutex sync.RWMutex prevConn net.PacketConn @@ -46,10 +49,12 @@ type udpPacket struct { type ListenUDPFunc = func(*net.UDPAddr) (net.PacketConn, error) -func NewUDPHopPacketConn(addr *UDPHopAddr, hopInterval time.Duration, listenUDPFunc ListenUDPFunc, pktConn net.PacketConn, index int) (net.PacketConn, error) { - if hopInterval == 0 { - hopInterval = defaultHopInterval - } else if hopInterval < 5*time.Second { +func NewUDPHopPacketConn(addr *UDPHopAddr, intervalMin int64, intervalMax int64, listenUDPFunc ListenUDPFunc, pktConn net.PacketConn, index int) (net.PacketConn, error) { + if intervalMin == 0 || intervalMax == 0 { + intervalMin = int64(defaultHopInterval) + intervalMax = int64(defaultHopInterval) + } + if intervalMin < 5 || intervalMax < 5 { return nil, errors.New("hop interval must be at least 5 seconds") } // if listenUDPFunc == nil { @@ -69,15 +74,16 @@ func NewUDPHopPacketConn(addr *UDPHopAddr, hopInterval time.Duration, listenUDPF // return nil, err // } hConn := &udpHopPacketConn{ - Addr: addr, - Addrs: addrs, - HopInterval: hopInterval, - ListenUDPFunc: listenUDPFunc, - prevConn: nil, - currentConn: pktConn, - addrIndex: index, - recvQueue: make(chan *udpPacket, packetQueueSize), - closeChan: make(chan struct{}), + Addr: addr, + Addrs: addrs, + HopIntervalMin: intervalMin, + HopIntervalMax: intervalMax, + ListenUDPFunc: listenUDPFunc, + prevConn: nil, + currentConn: pktConn, + addrIndex: index, + recvQueue: make(chan *udpPacket, packetQueueSize), + closeChan: make(chan struct{}), bufPool: sync.Pool{ New: func() interface{} { return make([]byte, udpBufferSize) @@ -115,12 +121,13 @@ func (u *udpHopPacketConn) recvLoop(conn net.PacketConn) { } func (u *udpHopPacketConn) hopLoop() { - ticker := time.NewTicker(u.HopInterval) + ticker := time.NewTicker(time.Duration(crypto.RandBetween(u.HopIntervalMin, u.HopIntervalMax)) * time.Second) defer ticker.Stop() for { select { case <-ticker.C: u.hop() + ticker.Reset(time.Duration(crypto.RandBetween(u.HopIntervalMin, u.HopIntervalMax)) * time.Second) case <-u.closeChan: return }