mirror of
https://github.com/XTLS/Xray-core.git
synced 2025-04-30 17:38:41 +00:00
v1.0.0
This commit is contained in:
parent
47d23e9972
commit
c7f7c08ead
711 changed files with 82154 additions and 2 deletions
110
transport/internet/kcp/config.go
Normal file
110
transport/internet/kcp/config.go
Normal file
|
@ -0,0 +1,110 @@
|
|||
// +build !confonly
|
||||
|
||||
package kcp
|
||||
|
||||
import (
|
||||
"crypto/cipher"
|
||||
|
||||
"github.com/xtls/xray-core/v1/common"
|
||||
"github.com/xtls/xray-core/v1/transport/internet"
|
||||
)
|
||||
|
||||
const protocolName = "mkcp"
|
||||
|
||||
// GetMTUValue returns the value of MTU settings.
|
||||
func (c *Config) GetMTUValue() uint32 {
|
||||
if c == nil || c.Mtu == nil {
|
||||
return 1350
|
||||
}
|
||||
return c.Mtu.Value
|
||||
}
|
||||
|
||||
// GetTTIValue returns the value of TTI settings.
|
||||
func (c *Config) GetTTIValue() uint32 {
|
||||
if c == nil || c.Tti == nil {
|
||||
return 50
|
||||
}
|
||||
return c.Tti.Value
|
||||
}
|
||||
|
||||
// GetUplinkCapacityValue returns the value of UplinkCapacity settings.
|
||||
func (c *Config) GetUplinkCapacityValue() uint32 {
|
||||
if c == nil || c.UplinkCapacity == nil {
|
||||
return 5
|
||||
}
|
||||
return c.UplinkCapacity.Value
|
||||
}
|
||||
|
||||
// GetDownlinkCapacityValue returns the value of DownlinkCapacity settings.
|
||||
func (c *Config) GetDownlinkCapacityValue() uint32 {
|
||||
if c == nil || c.DownlinkCapacity == nil {
|
||||
return 20
|
||||
}
|
||||
return c.DownlinkCapacity.Value
|
||||
}
|
||||
|
||||
// GetWriteBufferSize returns the size of WriterBuffer in bytes.
|
||||
func (c *Config) GetWriteBufferSize() uint32 {
|
||||
if c == nil || c.WriteBuffer == nil {
|
||||
return 2 * 1024 * 1024
|
||||
}
|
||||
return c.WriteBuffer.Size
|
||||
}
|
||||
|
||||
// GetReadBufferSize returns the size of ReadBuffer in bytes.
|
||||
func (c *Config) GetReadBufferSize() uint32 {
|
||||
if c == nil || c.ReadBuffer == nil {
|
||||
return 2 * 1024 * 1024
|
||||
}
|
||||
return c.ReadBuffer.Size
|
||||
}
|
||||
|
||||
// GetSecurity returns the security settings.
|
||||
func (c *Config) GetSecurity() (cipher.AEAD, error) {
|
||||
if c.Seed != nil {
|
||||
return NewAEADAESGCMBasedOnSeed(c.Seed.Seed), nil
|
||||
}
|
||||
return NewSimpleAuthenticator(), nil
|
||||
}
|
||||
|
||||
func (c *Config) GetPackerHeader() (internet.PacketHeader, error) {
|
||||
if c.HeaderConfig != nil {
|
||||
rawConfig, err := c.HeaderConfig.GetInstance()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return internet.CreatePacketHeader(rawConfig)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (c *Config) GetSendingInFlightSize() uint32 {
|
||||
size := c.GetUplinkCapacityValue() * 1024 * 1024 / c.GetMTUValue() / (1000 / c.GetTTIValue())
|
||||
if size < 8 {
|
||||
size = 8
|
||||
}
|
||||
return size
|
||||
}
|
||||
|
||||
func (c *Config) GetSendingBufferSize() uint32 {
|
||||
return c.GetWriteBufferSize() / c.GetMTUValue()
|
||||
}
|
||||
|
||||
func (c *Config) GetReceivingInFlightSize() uint32 {
|
||||
size := c.GetDownlinkCapacityValue() * 1024 * 1024 / c.GetMTUValue() / (1000 / c.GetTTIValue())
|
||||
if size < 8 {
|
||||
size = 8
|
||||
}
|
||||
return size
|
||||
}
|
||||
|
||||
func (c *Config) GetReceivingBufferSize() uint32 {
|
||||
return c.GetReadBufferSize() / c.GetMTUValue()
|
||||
}
|
||||
|
||||
func init() {
|
||||
common.Must(internet.RegisterProtocolConfigCreator(protocolName, func() interface{} {
|
||||
return new(Config)
|
||||
}))
|
||||
}
|
774
transport/internet/kcp/config.pb.go
Normal file
774
transport/internet/kcp/config.pb.go
Normal file
|
@ -0,0 +1,774 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.25.0
|
||||
// protoc v3.14.0
|
||||
// source: transport/internet/kcp/config.proto
|
||||
|
||||
package kcp
|
||||
|
||||
import (
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
serial "github.com/xtls/xray-core/v1/common/serial"
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
// This is a compile-time assertion that a sufficiently up-to-date version
|
||||
// of the legacy proto package is being used.
|
||||
const _ = proto.ProtoPackageIsVersion4
|
||||
|
||||
// Maximum Transmission Unit, in bytes.
|
||||
type MTU struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Value uint32 `protobuf:"varint,1,opt,name=value,proto3" json:"value,omitempty"`
|
||||
}
|
||||
|
||||
func (x *MTU) Reset() {
|
||||
*x = MTU{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_transport_internet_kcp_config_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *MTU) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*MTU) ProtoMessage() {}
|
||||
|
||||
func (x *MTU) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_transport_internet_kcp_config_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use MTU.ProtoReflect.Descriptor instead.
|
||||
func (*MTU) Descriptor() ([]byte, []int) {
|
||||
return file_transport_internet_kcp_config_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *MTU) GetValue() uint32 {
|
||||
if x != nil {
|
||||
return x.Value
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Transmission Time Interview, in milli-sec.
|
||||
type TTI struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Value uint32 `protobuf:"varint,1,opt,name=value,proto3" json:"value,omitempty"`
|
||||
}
|
||||
|
||||
func (x *TTI) Reset() {
|
||||
*x = TTI{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_transport_internet_kcp_config_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *TTI) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*TTI) ProtoMessage() {}
|
||||
|
||||
func (x *TTI) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_transport_internet_kcp_config_proto_msgTypes[1]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use TTI.ProtoReflect.Descriptor instead.
|
||||
func (*TTI) Descriptor() ([]byte, []int) {
|
||||
return file_transport_internet_kcp_config_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *TTI) GetValue() uint32 {
|
||||
if x != nil {
|
||||
return x.Value
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Uplink capacity, in MB.
|
||||
type UplinkCapacity struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Value uint32 `protobuf:"varint,1,opt,name=value,proto3" json:"value,omitempty"`
|
||||
}
|
||||
|
||||
func (x *UplinkCapacity) Reset() {
|
||||
*x = UplinkCapacity{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_transport_internet_kcp_config_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *UplinkCapacity) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*UplinkCapacity) ProtoMessage() {}
|
||||
|
||||
func (x *UplinkCapacity) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_transport_internet_kcp_config_proto_msgTypes[2]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use UplinkCapacity.ProtoReflect.Descriptor instead.
|
||||
func (*UplinkCapacity) Descriptor() ([]byte, []int) {
|
||||
return file_transport_internet_kcp_config_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
func (x *UplinkCapacity) GetValue() uint32 {
|
||||
if x != nil {
|
||||
return x.Value
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Downlink capacity, in MB.
|
||||
type DownlinkCapacity struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Value uint32 `protobuf:"varint,1,opt,name=value,proto3" json:"value,omitempty"`
|
||||
}
|
||||
|
||||
func (x *DownlinkCapacity) Reset() {
|
||||
*x = DownlinkCapacity{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_transport_internet_kcp_config_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *DownlinkCapacity) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*DownlinkCapacity) ProtoMessage() {}
|
||||
|
||||
func (x *DownlinkCapacity) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_transport_internet_kcp_config_proto_msgTypes[3]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use DownlinkCapacity.ProtoReflect.Descriptor instead.
|
||||
func (*DownlinkCapacity) Descriptor() ([]byte, []int) {
|
||||
return file_transport_internet_kcp_config_proto_rawDescGZIP(), []int{3}
|
||||
}
|
||||
|
||||
func (x *DownlinkCapacity) GetValue() uint32 {
|
||||
if x != nil {
|
||||
return x.Value
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type WriteBuffer struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// Buffer size in bytes.
|
||||
Size uint32 `protobuf:"varint,1,opt,name=size,proto3" json:"size,omitempty"`
|
||||
}
|
||||
|
||||
func (x *WriteBuffer) Reset() {
|
||||
*x = WriteBuffer{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_transport_internet_kcp_config_proto_msgTypes[4]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *WriteBuffer) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*WriteBuffer) ProtoMessage() {}
|
||||
|
||||
func (x *WriteBuffer) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_transport_internet_kcp_config_proto_msgTypes[4]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use WriteBuffer.ProtoReflect.Descriptor instead.
|
||||
func (*WriteBuffer) Descriptor() ([]byte, []int) {
|
||||
return file_transport_internet_kcp_config_proto_rawDescGZIP(), []int{4}
|
||||
}
|
||||
|
||||
func (x *WriteBuffer) GetSize() uint32 {
|
||||
if x != nil {
|
||||
return x.Size
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type ReadBuffer struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// Buffer size in bytes.
|
||||
Size uint32 `protobuf:"varint,1,opt,name=size,proto3" json:"size,omitempty"`
|
||||
}
|
||||
|
||||
func (x *ReadBuffer) Reset() {
|
||||
*x = ReadBuffer{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_transport_internet_kcp_config_proto_msgTypes[5]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *ReadBuffer) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ReadBuffer) ProtoMessage() {}
|
||||
|
||||
func (x *ReadBuffer) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_transport_internet_kcp_config_proto_msgTypes[5]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ReadBuffer.ProtoReflect.Descriptor instead.
|
||||
func (*ReadBuffer) Descriptor() ([]byte, []int) {
|
||||
return file_transport_internet_kcp_config_proto_rawDescGZIP(), []int{5}
|
||||
}
|
||||
|
||||
func (x *ReadBuffer) GetSize() uint32 {
|
||||
if x != nil {
|
||||
return x.Size
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type ConnectionReuse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Enable bool `protobuf:"varint,1,opt,name=enable,proto3" json:"enable,omitempty"`
|
||||
}
|
||||
|
||||
func (x *ConnectionReuse) Reset() {
|
||||
*x = ConnectionReuse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_transport_internet_kcp_config_proto_msgTypes[6]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *ConnectionReuse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ConnectionReuse) ProtoMessage() {}
|
||||
|
||||
func (x *ConnectionReuse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_transport_internet_kcp_config_proto_msgTypes[6]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ConnectionReuse.ProtoReflect.Descriptor instead.
|
||||
func (*ConnectionReuse) Descriptor() ([]byte, []int) {
|
||||
return file_transport_internet_kcp_config_proto_rawDescGZIP(), []int{6}
|
||||
}
|
||||
|
||||
func (x *ConnectionReuse) GetEnable() bool {
|
||||
if x != nil {
|
||||
return x.Enable
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Maximum Transmission Unit, in bytes.
|
||||
type EncryptionSeed struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Seed string `protobuf:"bytes,1,opt,name=seed,proto3" json:"seed,omitempty"`
|
||||
}
|
||||
|
||||
func (x *EncryptionSeed) Reset() {
|
||||
*x = EncryptionSeed{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_transport_internet_kcp_config_proto_msgTypes[7]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *EncryptionSeed) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*EncryptionSeed) ProtoMessage() {}
|
||||
|
||||
func (x *EncryptionSeed) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_transport_internet_kcp_config_proto_msgTypes[7]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use EncryptionSeed.ProtoReflect.Descriptor instead.
|
||||
func (*EncryptionSeed) Descriptor() ([]byte, []int) {
|
||||
return file_transport_internet_kcp_config_proto_rawDescGZIP(), []int{7}
|
||||
}
|
||||
|
||||
func (x *EncryptionSeed) GetSeed() string {
|
||||
if x != nil {
|
||||
return x.Seed
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Mtu *MTU `protobuf:"bytes,1,opt,name=mtu,proto3" json:"mtu,omitempty"`
|
||||
Tti *TTI `protobuf:"bytes,2,opt,name=tti,proto3" json:"tti,omitempty"`
|
||||
UplinkCapacity *UplinkCapacity `protobuf:"bytes,3,opt,name=uplink_capacity,json=uplinkCapacity,proto3" json:"uplink_capacity,omitempty"`
|
||||
DownlinkCapacity *DownlinkCapacity `protobuf:"bytes,4,opt,name=downlink_capacity,json=downlinkCapacity,proto3" json:"downlink_capacity,omitempty"`
|
||||
Congestion bool `protobuf:"varint,5,opt,name=congestion,proto3" json:"congestion,omitempty"`
|
||||
WriteBuffer *WriteBuffer `protobuf:"bytes,6,opt,name=write_buffer,json=writeBuffer,proto3" json:"write_buffer,omitempty"`
|
||||
ReadBuffer *ReadBuffer `protobuf:"bytes,7,opt,name=read_buffer,json=readBuffer,proto3" json:"read_buffer,omitempty"`
|
||||
HeaderConfig *serial.TypedMessage `protobuf:"bytes,8,opt,name=header_config,json=headerConfig,proto3" json:"header_config,omitempty"`
|
||||
Seed *EncryptionSeed `protobuf:"bytes,10,opt,name=seed,proto3" json:"seed,omitempty"`
|
||||
}
|
||||
|
||||
func (x *Config) Reset() {
|
||||
*x = Config{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_transport_internet_kcp_config_proto_msgTypes[8]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *Config) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Config) ProtoMessage() {}
|
||||
|
||||
func (x *Config) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_transport_internet_kcp_config_proto_msgTypes[8]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Config.ProtoReflect.Descriptor instead.
|
||||
func (*Config) Descriptor() ([]byte, []int) {
|
||||
return file_transport_internet_kcp_config_proto_rawDescGZIP(), []int{8}
|
||||
}
|
||||
|
||||
func (x *Config) GetMtu() *MTU {
|
||||
if x != nil {
|
||||
return x.Mtu
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Config) GetTti() *TTI {
|
||||
if x != nil {
|
||||
return x.Tti
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Config) GetUplinkCapacity() *UplinkCapacity {
|
||||
if x != nil {
|
||||
return x.UplinkCapacity
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Config) GetDownlinkCapacity() *DownlinkCapacity {
|
||||
if x != nil {
|
||||
return x.DownlinkCapacity
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Config) GetCongestion() bool {
|
||||
if x != nil {
|
||||
return x.Congestion
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *Config) GetWriteBuffer() *WriteBuffer {
|
||||
if x != nil {
|
||||
return x.WriteBuffer
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Config) GetReadBuffer() *ReadBuffer {
|
||||
if x != nil {
|
||||
return x.ReadBuffer
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Config) GetHeaderConfig() *serial.TypedMessage {
|
||||
if x != nil {
|
||||
return x.HeaderConfig
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Config) GetSeed() *EncryptionSeed {
|
||||
if x != nil {
|
||||
return x.Seed
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var File_transport_internet_kcp_config_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_transport_internet_kcp_config_proto_rawDesc = []byte{
|
||||
0x0a, 0x23, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65,
|
||||
0x72, 0x6e, 0x65, 0x74, 0x2f, 0x6b, 0x63, 0x70, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1b, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e,
|
||||
0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x6b,
|
||||
0x63, 0x70, 0x1a, 0x21, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x73, 0x65, 0x72, 0x69, 0x61,
|
||||
0x6c, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x64, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x1b, 0x0a, 0x03, 0x4d, 0x54, 0x55, 0x12, 0x14, 0x0a, 0x05,
|
||||
0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x76, 0x61, 0x6c,
|
||||
0x75, 0x65, 0x22, 0x1b, 0x0a, 0x03, 0x54, 0x54, 0x49, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c,
|
||||
0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22,
|
||||
0x26, 0x0a, 0x0e, 0x55, 0x70, 0x6c, 0x69, 0x6e, 0x6b, 0x43, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74,
|
||||
0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d,
|
||||
0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x28, 0x0a, 0x10, 0x44, 0x6f, 0x77, 0x6e, 0x6c,
|
||||
0x69, 0x6e, 0x6b, 0x43, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76,
|
||||
0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75,
|
||||
0x65, 0x22, 0x21, 0x0a, 0x0b, 0x57, 0x72, 0x69, 0x74, 0x65, 0x42, 0x75, 0x66, 0x66, 0x65, 0x72,
|
||||
0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04,
|
||||
0x73, 0x69, 0x7a, 0x65, 0x22, 0x20, 0x0a, 0x0a, 0x52, 0x65, 0x61, 0x64, 0x42, 0x75, 0x66, 0x66,
|
||||
0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d,
|
||||
0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x22, 0x29, 0x0a, 0x0f, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63,
|
||||
0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x75, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x65, 0x6e, 0x61,
|
||||
0x62, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x65, 0x6e, 0x61, 0x62, 0x6c,
|
||||
0x65, 0x22, 0x24, 0x0a, 0x0e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x53,
|
||||
0x65, 0x65, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x65, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x04, 0x73, 0x65, 0x65, 0x64, 0x22, 0xe7, 0x04, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66,
|
||||
0x69, 0x67, 0x12, 0x32, 0x0a, 0x03, 0x6d, 0x74, 0x75, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32,
|
||||
0x20, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74,
|
||||
0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x6b, 0x63, 0x70, 0x2e, 0x4d, 0x54,
|
||||
0x55, 0x52, 0x03, 0x6d, 0x74, 0x75, 0x12, 0x32, 0x0a, 0x03, 0x74, 0x74, 0x69, 0x18, 0x02, 0x20,
|
||||
0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73,
|
||||
0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x6b, 0x63,
|
||||
0x70, 0x2e, 0x54, 0x54, 0x49, 0x52, 0x03, 0x74, 0x74, 0x69, 0x12, 0x54, 0x0a, 0x0f, 0x75, 0x70,
|
||||
0x6c, 0x69, 0x6e, 0x6b, 0x5f, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x18, 0x03, 0x20,
|
||||
0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73,
|
||||
0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x6b, 0x63,
|
||||
0x70, 0x2e, 0x55, 0x70, 0x6c, 0x69, 0x6e, 0x6b, 0x43, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79,
|
||||
0x52, 0x0e, 0x75, 0x70, 0x6c, 0x69, 0x6e, 0x6b, 0x43, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79,
|
||||
0x12, 0x5a, 0x0a, 0x11, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x69, 0x6e, 0x6b, 0x5f, 0x63, 0x61, 0x70,
|
||||
0x61, 0x63, 0x69, 0x74, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x78, 0x72,
|
||||
0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74,
|
||||
0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x6b, 0x63, 0x70, 0x2e, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x69,
|
||||
0x6e, 0x6b, 0x43, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x52, 0x10, 0x64, 0x6f, 0x77, 0x6e,
|
||||
0x6c, 0x69, 0x6e, 0x6b, 0x43, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x12, 0x1e, 0x0a, 0x0a,
|
||||
0x63, 0x6f, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08,
|
||||
0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x4b, 0x0a, 0x0c,
|
||||
0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x62, 0x75, 0x66, 0x66, 0x65, 0x72, 0x18, 0x06, 0x20, 0x01,
|
||||
0x28, 0x0b, 0x32, 0x28, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70,
|
||||
0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x6b, 0x63, 0x70,
|
||||
0x2e, 0x57, 0x72, 0x69, 0x74, 0x65, 0x42, 0x75, 0x66, 0x66, 0x65, 0x72, 0x52, 0x0b, 0x77, 0x72,
|
||||
0x69, 0x74, 0x65, 0x42, 0x75, 0x66, 0x66, 0x65, 0x72, 0x12, 0x48, 0x0a, 0x0b, 0x72, 0x65, 0x61,
|
||||
0x64, 0x5f, 0x62, 0x75, 0x66, 0x66, 0x65, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27,
|
||||
0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e,
|
||||
0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x6b, 0x63, 0x70, 0x2e, 0x52, 0x65, 0x61,
|
||||
0x64, 0x42, 0x75, 0x66, 0x66, 0x65, 0x72, 0x52, 0x0a, 0x72, 0x65, 0x61, 0x64, 0x42, 0x75, 0x66,
|
||||
0x66, 0x65, 0x72, 0x12, 0x45, 0x0a, 0x0d, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x5f, 0x63, 0x6f,
|
||||
0x6e, 0x66, 0x69, 0x67, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x78, 0x72, 0x61,
|
||||
0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x2e,
|
||||
0x54, 0x79, 0x70, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x0c, 0x68, 0x65,
|
||||
0x61, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x3f, 0x0a, 0x04, 0x73, 0x65,
|
||||
0x65, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e,
|
||||
0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e,
|
||||
0x65, 0x74, 0x2e, 0x6b, 0x63, 0x70, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f,
|
||||
0x6e, 0x53, 0x65, 0x65, 0x64, 0x52, 0x04, 0x73, 0x65, 0x65, 0x64, 0x4a, 0x04, 0x08, 0x09, 0x10,
|
||||
0x0a, 0x42, 0x76, 0x0a, 0x1f, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72,
|
||||
0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74,
|
||||
0x2e, 0x6b, 0x63, 0x70, 0x50, 0x01, 0x5a, 0x33, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
|
||||
0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72,
|
||||
0x65, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69,
|
||||
0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x6b, 0x63, 0x70, 0xaa, 0x02, 0x1b, 0x58, 0x72,
|
||||
0x61, 0x79, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e, 0x74,
|
||||
0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x4b, 0x63, 0x70, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
file_transport_internet_kcp_config_proto_rawDescOnce sync.Once
|
||||
file_transport_internet_kcp_config_proto_rawDescData = file_transport_internet_kcp_config_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_transport_internet_kcp_config_proto_rawDescGZIP() []byte {
|
||||
file_transport_internet_kcp_config_proto_rawDescOnce.Do(func() {
|
||||
file_transport_internet_kcp_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_transport_internet_kcp_config_proto_rawDescData)
|
||||
})
|
||||
return file_transport_internet_kcp_config_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_transport_internet_kcp_config_proto_msgTypes = make([]protoimpl.MessageInfo, 9)
|
||||
var file_transport_internet_kcp_config_proto_goTypes = []interface{}{
|
||||
(*MTU)(nil), // 0: xray.transport.internet.kcp.MTU
|
||||
(*TTI)(nil), // 1: xray.transport.internet.kcp.TTI
|
||||
(*UplinkCapacity)(nil), // 2: xray.transport.internet.kcp.UplinkCapacity
|
||||
(*DownlinkCapacity)(nil), // 3: xray.transport.internet.kcp.DownlinkCapacity
|
||||
(*WriteBuffer)(nil), // 4: xray.transport.internet.kcp.WriteBuffer
|
||||
(*ReadBuffer)(nil), // 5: xray.transport.internet.kcp.ReadBuffer
|
||||
(*ConnectionReuse)(nil), // 6: xray.transport.internet.kcp.ConnectionReuse
|
||||
(*EncryptionSeed)(nil), // 7: xray.transport.internet.kcp.EncryptionSeed
|
||||
(*Config)(nil), // 8: xray.transport.internet.kcp.Config
|
||||
(*serial.TypedMessage)(nil), // 9: xray.common.serial.TypedMessage
|
||||
}
|
||||
var file_transport_internet_kcp_config_proto_depIdxs = []int32{
|
||||
0, // 0: xray.transport.internet.kcp.Config.mtu:type_name -> xray.transport.internet.kcp.MTU
|
||||
1, // 1: xray.transport.internet.kcp.Config.tti:type_name -> xray.transport.internet.kcp.TTI
|
||||
2, // 2: xray.transport.internet.kcp.Config.uplink_capacity:type_name -> xray.transport.internet.kcp.UplinkCapacity
|
||||
3, // 3: xray.transport.internet.kcp.Config.downlink_capacity:type_name -> xray.transport.internet.kcp.DownlinkCapacity
|
||||
4, // 4: xray.transport.internet.kcp.Config.write_buffer:type_name -> xray.transport.internet.kcp.WriteBuffer
|
||||
5, // 5: xray.transport.internet.kcp.Config.read_buffer:type_name -> xray.transport.internet.kcp.ReadBuffer
|
||||
9, // 6: xray.transport.internet.kcp.Config.header_config:type_name -> xray.common.serial.TypedMessage
|
||||
7, // 7: xray.transport.internet.kcp.Config.seed:type_name -> xray.transport.internet.kcp.EncryptionSeed
|
||||
8, // [8:8] is the sub-list for method output_type
|
||||
8, // [8:8] is the sub-list for method input_type
|
||||
8, // [8:8] is the sub-list for extension type_name
|
||||
8, // [8:8] is the sub-list for extension extendee
|
||||
0, // [0:8] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_transport_internet_kcp_config_proto_init() }
|
||||
func file_transport_internet_kcp_config_proto_init() {
|
||||
if File_transport_internet_kcp_config_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_transport_internet_kcp_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*MTU); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_transport_internet_kcp_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*TTI); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_transport_internet_kcp_config_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*UplinkCapacity); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_transport_internet_kcp_config_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*DownlinkCapacity); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_transport_internet_kcp_config_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*WriteBuffer); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_transport_internet_kcp_config_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*ReadBuffer); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_transport_internet_kcp_config_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*ConnectionReuse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_transport_internet_kcp_config_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*EncryptionSeed); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_transport_internet_kcp_config_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Config); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_transport_internet_kcp_config_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 9,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_transport_internet_kcp_config_proto_goTypes,
|
||||
DependencyIndexes: file_transport_internet_kcp_config_proto_depIdxs,
|
||||
MessageInfos: file_transport_internet_kcp_config_proto_msgTypes,
|
||||
}.Build()
|
||||
File_transport_internet_kcp_config_proto = out.File
|
||||
file_transport_internet_kcp_config_proto_rawDesc = nil
|
||||
file_transport_internet_kcp_config_proto_goTypes = nil
|
||||
file_transport_internet_kcp_config_proto_depIdxs = nil
|
||||
}
|
61
transport/internet/kcp/config.proto
Normal file
61
transport/internet/kcp/config.proto
Normal file
|
@ -0,0 +1,61 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package xray.transport.internet.kcp;
|
||||
option csharp_namespace = "Xray.Transport.Internet.Kcp";
|
||||
option go_package = "github.com/xtls/xray-core/v1/transport/internet/kcp";
|
||||
option java_package = "com.xray.transport.internet.kcp";
|
||||
option java_multiple_files = true;
|
||||
|
||||
import "common/serial/typed_message.proto";
|
||||
|
||||
// Maximum Transmission Unit, in bytes.
|
||||
message MTU {
|
||||
uint32 value = 1;
|
||||
}
|
||||
|
||||
// Transmission Time Interview, in milli-sec.
|
||||
message TTI {
|
||||
uint32 value = 1;
|
||||
}
|
||||
|
||||
// Uplink capacity, in MB.
|
||||
message UplinkCapacity {
|
||||
uint32 value = 1;
|
||||
}
|
||||
|
||||
// Downlink capacity, in MB.
|
||||
message DownlinkCapacity {
|
||||
uint32 value = 1;
|
||||
}
|
||||
|
||||
message WriteBuffer {
|
||||
// Buffer size in bytes.
|
||||
uint32 size = 1;
|
||||
}
|
||||
|
||||
message ReadBuffer {
|
||||
// Buffer size in bytes.
|
||||
uint32 size = 1;
|
||||
}
|
||||
|
||||
message ConnectionReuse {
|
||||
bool enable = 1;
|
||||
}
|
||||
|
||||
// Maximum Transmission Unit, in bytes.
|
||||
message EncryptionSeed {
|
||||
string seed = 1;
|
||||
}
|
||||
|
||||
message Config {
|
||||
MTU mtu = 1;
|
||||
TTI tti = 2;
|
||||
UplinkCapacity uplink_capacity = 3;
|
||||
DownlinkCapacity downlink_capacity = 4;
|
||||
bool congestion = 5;
|
||||
WriteBuffer write_buffer = 6;
|
||||
ReadBuffer read_buffer = 7;
|
||||
xray.common.serial.TypedMessage header_config = 8;
|
||||
reserved 9;
|
||||
EncryptionSeed seed = 10;
|
||||
}
|
663
transport/internet/kcp/connection.go
Normal file
663
transport/internet/kcp/connection.go
Normal file
|
@ -0,0 +1,663 @@
|
|||
// +build !confonly
|
||||
|
||||
package kcp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"net"
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/v1/common/buf"
|
||||
"github.com/xtls/xray-core/v1/common/signal"
|
||||
"github.com/xtls/xray-core/v1/common/signal/semaphore"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrIOTimeout = newError("Read/Write timeout")
|
||||
ErrClosedListener = newError("Listener closed.")
|
||||
ErrClosedConnection = newError("Connection closed.")
|
||||
)
|
||||
|
||||
// State of the connection
|
||||
type State int32
|
||||
|
||||
// Is returns true if current State is one of the candidates.
|
||||
func (s State) Is(states ...State) bool {
|
||||
for _, state := range states {
|
||||
if s == state {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const (
|
||||
StateActive State = 0 // Connection is active
|
||||
StateReadyToClose State = 1 // Connection is closed locally
|
||||
StatePeerClosed State = 2 // Connection is closed on remote
|
||||
StateTerminating State = 3 // Connection is ready to be destroyed locally
|
||||
StatePeerTerminating State = 4 // Connection is ready to be destroyed on remote
|
||||
StateTerminated State = 5 // Connection is destroyed.
|
||||
)
|
||||
|
||||
func nowMillisec() int64 {
|
||||
now := time.Now()
|
||||
return now.Unix()*1000 + int64(now.Nanosecond()/1000000)
|
||||
}
|
||||
|
||||
type RoundTripInfo struct {
|
||||
sync.RWMutex
|
||||
variation uint32
|
||||
srtt uint32
|
||||
rto uint32
|
||||
minRtt uint32
|
||||
updatedTimestamp uint32
|
||||
}
|
||||
|
||||
func (info *RoundTripInfo) UpdatePeerRTO(rto uint32, current uint32) {
|
||||
info.Lock()
|
||||
defer info.Unlock()
|
||||
|
||||
if current-info.updatedTimestamp < 3000 {
|
||||
return
|
||||
}
|
||||
|
||||
info.updatedTimestamp = current
|
||||
info.rto = rto
|
||||
}
|
||||
|
||||
func (info *RoundTripInfo) Update(rtt uint32, current uint32) {
|
||||
if rtt > 0x7FFFFFFF {
|
||||
return
|
||||
}
|
||||
info.Lock()
|
||||
defer info.Unlock()
|
||||
|
||||
// https://tools.ietf.org/html/rfc6298
|
||||
if info.srtt == 0 {
|
||||
info.srtt = rtt
|
||||
info.variation = rtt / 2
|
||||
} else {
|
||||
delta := rtt - info.srtt
|
||||
if info.srtt > rtt {
|
||||
delta = info.srtt - rtt
|
||||
}
|
||||
info.variation = (3*info.variation + delta) / 4
|
||||
info.srtt = (7*info.srtt + rtt) / 8
|
||||
if info.srtt < info.minRtt {
|
||||
info.srtt = info.minRtt
|
||||
}
|
||||
}
|
||||
var rto uint32
|
||||
if info.minRtt < 4*info.variation {
|
||||
rto = info.srtt + 4*info.variation
|
||||
} else {
|
||||
rto = info.srtt + info.variation
|
||||
}
|
||||
|
||||
if rto > 10000 {
|
||||
rto = 10000
|
||||
}
|
||||
info.rto = rto * 5 / 4
|
||||
info.updatedTimestamp = current
|
||||
}
|
||||
|
||||
func (info *RoundTripInfo) Timeout() uint32 {
|
||||
info.RLock()
|
||||
defer info.RUnlock()
|
||||
|
||||
return info.rto
|
||||
}
|
||||
|
||||
func (info *RoundTripInfo) SmoothedTime() uint32 {
|
||||
info.RLock()
|
||||
defer info.RUnlock()
|
||||
|
||||
return info.srtt
|
||||
}
|
||||
|
||||
type Updater struct {
|
||||
interval int64
|
||||
shouldContinue func() bool
|
||||
shouldTerminate func() bool
|
||||
updateFunc func()
|
||||
notifier *semaphore.Instance
|
||||
}
|
||||
|
||||
func NewUpdater(interval uint32, shouldContinue func() bool, shouldTerminate func() bool, updateFunc func()) *Updater {
|
||||
u := &Updater{
|
||||
interval: int64(time.Duration(interval) * time.Millisecond),
|
||||
shouldContinue: shouldContinue,
|
||||
shouldTerminate: shouldTerminate,
|
||||
updateFunc: updateFunc,
|
||||
notifier: semaphore.New(1),
|
||||
}
|
||||
return u
|
||||
}
|
||||
|
||||
func (u *Updater) WakeUp() {
|
||||
select {
|
||||
case <-u.notifier.Wait():
|
||||
go u.run()
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func (u *Updater) run() {
|
||||
defer u.notifier.Signal()
|
||||
|
||||
if u.shouldTerminate() {
|
||||
return
|
||||
}
|
||||
ticker := time.NewTicker(u.Interval())
|
||||
for u.shouldContinue() {
|
||||
u.updateFunc()
|
||||
<-ticker.C
|
||||
}
|
||||
ticker.Stop()
|
||||
}
|
||||
|
||||
func (u *Updater) Interval() time.Duration {
|
||||
return time.Duration(atomic.LoadInt64(&u.interval))
|
||||
}
|
||||
|
||||
func (u *Updater) SetInterval(d time.Duration) {
|
||||
atomic.StoreInt64(&u.interval, int64(d))
|
||||
}
|
||||
|
||||
type ConnMetadata struct {
|
||||
LocalAddr net.Addr
|
||||
RemoteAddr net.Addr
|
||||
Conversation uint16
|
||||
}
|
||||
|
||||
// Connection is a KCP connection over UDP.
|
||||
type Connection struct {
|
||||
meta ConnMetadata
|
||||
closer io.Closer
|
||||
rd time.Time
|
||||
wd time.Time // write deadline
|
||||
since int64
|
||||
dataInput *signal.Notifier
|
||||
dataOutput *signal.Notifier
|
||||
Config *Config
|
||||
|
||||
state State
|
||||
stateBeginTime uint32
|
||||
lastIncomingTime uint32
|
||||
lastPingTime uint32
|
||||
|
||||
mss uint32
|
||||
roundTrip *RoundTripInfo
|
||||
|
||||
receivingWorker *ReceivingWorker
|
||||
sendingWorker *SendingWorker
|
||||
|
||||
output SegmentWriter
|
||||
|
||||
dataUpdater *Updater
|
||||
pingUpdater *Updater
|
||||
}
|
||||
|
||||
// NewConnection create a new KCP connection between local and remote.
|
||||
func NewConnection(meta ConnMetadata, writer PacketWriter, closer io.Closer, config *Config) *Connection {
|
||||
newError("#", meta.Conversation, " creating connection to ", meta.RemoteAddr).WriteToLog()
|
||||
|
||||
conn := &Connection{
|
||||
meta: meta,
|
||||
closer: closer,
|
||||
since: nowMillisec(),
|
||||
dataInput: signal.NewNotifier(),
|
||||
dataOutput: signal.NewNotifier(),
|
||||
Config: config,
|
||||
output: NewRetryableWriter(NewSegmentWriter(writer)),
|
||||
mss: config.GetMTUValue() - uint32(writer.Overhead()) - DataSegmentOverhead,
|
||||
roundTrip: &RoundTripInfo{
|
||||
rto: 100,
|
||||
minRtt: config.GetTTIValue(),
|
||||
},
|
||||
}
|
||||
|
||||
conn.receivingWorker = NewReceivingWorker(conn)
|
||||
conn.sendingWorker = NewSendingWorker(conn)
|
||||
|
||||
isTerminating := func() bool {
|
||||
return conn.State().Is(StateTerminating, StateTerminated)
|
||||
}
|
||||
isTerminated := func() bool {
|
||||
return conn.State() == StateTerminated
|
||||
}
|
||||
conn.dataUpdater = NewUpdater(
|
||||
config.GetTTIValue(),
|
||||
func() bool {
|
||||
return !isTerminating() && (conn.sendingWorker.UpdateNecessary() || conn.receivingWorker.UpdateNecessary())
|
||||
},
|
||||
isTerminating,
|
||||
conn.updateTask)
|
||||
conn.pingUpdater = NewUpdater(
|
||||
5000, // 5 seconds
|
||||
func() bool { return !isTerminated() },
|
||||
isTerminated,
|
||||
conn.updateTask)
|
||||
conn.pingUpdater.WakeUp()
|
||||
|
||||
return conn
|
||||
}
|
||||
|
||||
func (c *Connection) Elapsed() uint32 {
|
||||
return uint32(nowMillisec() - c.since)
|
||||
}
|
||||
|
||||
// ReadMultiBuffer implements buf.Reader.
|
||||
func (c *Connection) ReadMultiBuffer() (buf.MultiBuffer, error) {
|
||||
if c == nil {
|
||||
return nil, io.EOF
|
||||
}
|
||||
|
||||
for {
|
||||
if c.State().Is(StateReadyToClose, StateTerminating, StateTerminated) {
|
||||
return nil, io.EOF
|
||||
}
|
||||
mb := c.receivingWorker.ReadMultiBuffer()
|
||||
if !mb.IsEmpty() {
|
||||
c.dataUpdater.WakeUp()
|
||||
return mb, nil
|
||||
}
|
||||
|
||||
if c.State() == StatePeerTerminating {
|
||||
return nil, io.EOF
|
||||
}
|
||||
|
||||
if err := c.waitForDataInput(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Connection) waitForDataInput() error {
|
||||
for i := 0; i < 16; i++ {
|
||||
select {
|
||||
case <-c.dataInput.Wait():
|
||||
return nil
|
||||
default:
|
||||
runtime.Gosched()
|
||||
}
|
||||
}
|
||||
|
||||
duration := time.Second * 16
|
||||
if !c.rd.IsZero() {
|
||||
duration = time.Until(c.rd)
|
||||
if duration < 0 {
|
||||
return ErrIOTimeout
|
||||
}
|
||||
}
|
||||
|
||||
timeout := time.NewTimer(duration)
|
||||
defer timeout.Stop()
|
||||
|
||||
select {
|
||||
case <-c.dataInput.Wait():
|
||||
case <-timeout.C:
|
||||
if !c.rd.IsZero() && c.rd.Before(time.Now()) {
|
||||
return ErrIOTimeout
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read implements the Conn Read method.
|
||||
func (c *Connection) Read(b []byte) (int, error) {
|
||||
if c == nil {
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
for {
|
||||
if c.State().Is(StateReadyToClose, StateTerminating, StateTerminated) {
|
||||
return 0, io.EOF
|
||||
}
|
||||
nBytes := c.receivingWorker.Read(b)
|
||||
if nBytes > 0 {
|
||||
c.dataUpdater.WakeUp()
|
||||
return nBytes, nil
|
||||
}
|
||||
|
||||
if err := c.waitForDataInput(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Connection) waitForDataOutput() error {
|
||||
for i := 0; i < 16; i++ {
|
||||
select {
|
||||
case <-c.dataOutput.Wait():
|
||||
return nil
|
||||
default:
|
||||
runtime.Gosched()
|
||||
}
|
||||
}
|
||||
|
||||
duration := time.Second * 16
|
||||
if !c.wd.IsZero() {
|
||||
duration = time.Until(c.wd)
|
||||
if duration < 0 {
|
||||
return ErrIOTimeout
|
||||
}
|
||||
}
|
||||
|
||||
timeout := time.NewTimer(duration)
|
||||
defer timeout.Stop()
|
||||
|
||||
select {
|
||||
case <-c.dataOutput.Wait():
|
||||
case <-timeout.C:
|
||||
if !c.wd.IsZero() && c.wd.Before(time.Now()) {
|
||||
return ErrIOTimeout
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Write implements io.Writer.
|
||||
func (c *Connection) Write(b []byte) (int, error) {
|
||||
reader := bytes.NewReader(b)
|
||||
if err := c.writeMultiBufferInternal(reader); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
// WriteMultiBuffer implements buf.Writer.
|
||||
func (c *Connection) WriteMultiBuffer(mb buf.MultiBuffer) error {
|
||||
reader := &buf.MultiBufferContainer{
|
||||
MultiBuffer: mb,
|
||||
}
|
||||
defer reader.Close()
|
||||
|
||||
return c.writeMultiBufferInternal(reader)
|
||||
}
|
||||
|
||||
func (c *Connection) writeMultiBufferInternal(reader io.Reader) error {
|
||||
updatePending := false
|
||||
defer func() {
|
||||
if updatePending {
|
||||
c.dataUpdater.WakeUp()
|
||||
}
|
||||
}()
|
||||
|
||||
var b *buf.Buffer
|
||||
defer b.Release()
|
||||
|
||||
for {
|
||||
for {
|
||||
if c == nil || c.State() != StateActive {
|
||||
return io.ErrClosedPipe
|
||||
}
|
||||
|
||||
if b == nil {
|
||||
b = buf.New()
|
||||
_, err := b.ReadFrom(io.LimitReader(reader, int64(c.mss)))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if !c.sendingWorker.Push(b) {
|
||||
break
|
||||
}
|
||||
updatePending = true
|
||||
b = nil
|
||||
}
|
||||
|
||||
if updatePending {
|
||||
c.dataUpdater.WakeUp()
|
||||
updatePending = false
|
||||
}
|
||||
|
||||
if err := c.waitForDataOutput(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Connection) SetState(state State) {
|
||||
current := c.Elapsed()
|
||||
atomic.StoreInt32((*int32)(&c.state), int32(state))
|
||||
atomic.StoreUint32(&c.stateBeginTime, current)
|
||||
newError("#", c.meta.Conversation, " entering state ", state, " at ", current).AtDebug().WriteToLog()
|
||||
|
||||
switch state {
|
||||
case StateReadyToClose:
|
||||
c.receivingWorker.CloseRead()
|
||||
case StatePeerClosed:
|
||||
c.sendingWorker.CloseWrite()
|
||||
case StateTerminating:
|
||||
c.receivingWorker.CloseRead()
|
||||
c.sendingWorker.CloseWrite()
|
||||
c.pingUpdater.SetInterval(time.Second)
|
||||
case StatePeerTerminating:
|
||||
c.sendingWorker.CloseWrite()
|
||||
c.pingUpdater.SetInterval(time.Second)
|
||||
case StateTerminated:
|
||||
c.receivingWorker.CloseRead()
|
||||
c.sendingWorker.CloseWrite()
|
||||
c.pingUpdater.SetInterval(time.Second)
|
||||
c.dataUpdater.WakeUp()
|
||||
c.pingUpdater.WakeUp()
|
||||
go c.Terminate()
|
||||
}
|
||||
}
|
||||
|
||||
// Close closes the connection.
|
||||
func (c *Connection) Close() error {
|
||||
if c == nil {
|
||||
return ErrClosedConnection
|
||||
}
|
||||
|
||||
c.dataInput.Signal()
|
||||
c.dataOutput.Signal()
|
||||
|
||||
switch c.State() {
|
||||
case StateReadyToClose, StateTerminating, StateTerminated:
|
||||
return ErrClosedConnection
|
||||
case StateActive:
|
||||
c.SetState(StateReadyToClose)
|
||||
case StatePeerClosed:
|
||||
c.SetState(StateTerminating)
|
||||
case StatePeerTerminating:
|
||||
c.SetState(StateTerminated)
|
||||
}
|
||||
|
||||
newError("#", c.meta.Conversation, " closing connection to ", c.meta.RemoteAddr).WriteToLog()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// LocalAddr returns the local network address. The Addr returned is shared by all invocations of LocalAddr, so do not modify it.
|
||||
func (c *Connection) LocalAddr() net.Addr {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
return c.meta.LocalAddr
|
||||
}
|
||||
|
||||
// RemoteAddr returns the remote network address. The Addr returned is shared by all invocations of RemoteAddr, so do not modify it.
|
||||
func (c *Connection) RemoteAddr() net.Addr {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
return c.meta.RemoteAddr
|
||||
}
|
||||
|
||||
// SetDeadline sets the deadline associated with the listener. A zero time value disables the deadline.
|
||||
func (c *Connection) SetDeadline(t time.Time) error {
|
||||
if err := c.SetReadDeadline(t); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.SetWriteDeadline(t)
|
||||
}
|
||||
|
||||
// SetReadDeadline implements the Conn SetReadDeadline method.
|
||||
func (c *Connection) SetReadDeadline(t time.Time) error {
|
||||
if c == nil || c.State() != StateActive {
|
||||
return ErrClosedConnection
|
||||
}
|
||||
c.rd = t
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetWriteDeadline implements the Conn SetWriteDeadline method.
|
||||
func (c *Connection) SetWriteDeadline(t time.Time) error {
|
||||
if c == nil || c.State() != StateActive {
|
||||
return ErrClosedConnection
|
||||
}
|
||||
c.wd = t
|
||||
return nil
|
||||
}
|
||||
|
||||
// kcp update, input loop
|
||||
func (c *Connection) updateTask() {
|
||||
c.flush()
|
||||
}
|
||||
|
||||
func (c *Connection) Terminate() {
|
||||
if c == nil {
|
||||
return
|
||||
}
|
||||
newError("#", c.meta.Conversation, " terminating connection to ", c.RemoteAddr()).WriteToLog()
|
||||
|
||||
// v.SetState(StateTerminated)
|
||||
c.dataInput.Signal()
|
||||
c.dataOutput.Signal()
|
||||
|
||||
c.closer.Close()
|
||||
c.sendingWorker.Release()
|
||||
c.receivingWorker.Release()
|
||||
}
|
||||
|
||||
func (c *Connection) HandleOption(opt SegmentOption) {
|
||||
if (opt & SegmentOptionClose) == SegmentOptionClose {
|
||||
c.OnPeerClosed()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Connection) OnPeerClosed() {
|
||||
switch c.State() {
|
||||
case StateReadyToClose:
|
||||
c.SetState(StateTerminating)
|
||||
case StateActive:
|
||||
c.SetState(StatePeerClosed)
|
||||
}
|
||||
}
|
||||
|
||||
// Input when you received a low level packet (eg. UDP packet), call it
|
||||
func (c *Connection) Input(segments []Segment) {
|
||||
current := c.Elapsed()
|
||||
atomic.StoreUint32(&c.lastIncomingTime, current)
|
||||
|
||||
for _, seg := range segments {
|
||||
if seg.Conversation() != c.meta.Conversation {
|
||||
break
|
||||
}
|
||||
|
||||
switch seg := seg.(type) {
|
||||
case *DataSegment:
|
||||
c.HandleOption(seg.Option)
|
||||
c.receivingWorker.ProcessSegment(seg)
|
||||
if c.receivingWorker.IsDataAvailable() {
|
||||
c.dataInput.Signal()
|
||||
}
|
||||
c.dataUpdater.WakeUp()
|
||||
case *AckSegment:
|
||||
c.HandleOption(seg.Option)
|
||||
c.sendingWorker.ProcessSegment(current, seg, c.roundTrip.Timeout())
|
||||
c.dataOutput.Signal()
|
||||
c.dataUpdater.WakeUp()
|
||||
case *CmdOnlySegment:
|
||||
c.HandleOption(seg.Option)
|
||||
if seg.Command() == CommandTerminate {
|
||||
switch c.State() {
|
||||
case StateActive, StatePeerClosed:
|
||||
c.SetState(StatePeerTerminating)
|
||||
case StateReadyToClose:
|
||||
c.SetState(StateTerminating)
|
||||
case StateTerminating:
|
||||
c.SetState(StateTerminated)
|
||||
}
|
||||
}
|
||||
if seg.Option == SegmentOptionClose || seg.Command() == CommandTerminate {
|
||||
c.dataInput.Signal()
|
||||
c.dataOutput.Signal()
|
||||
}
|
||||
c.sendingWorker.ProcessReceivingNext(seg.ReceivingNext)
|
||||
c.receivingWorker.ProcessSendingNext(seg.SendingNext)
|
||||
c.roundTrip.UpdatePeerRTO(seg.PeerRTO, current)
|
||||
seg.Release()
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Connection) flush() {
|
||||
current := c.Elapsed()
|
||||
|
||||
if c.State() == StateTerminated {
|
||||
return
|
||||
}
|
||||
if c.State() == StateActive && current-atomic.LoadUint32(&c.lastIncomingTime) >= 30000 {
|
||||
c.Close()
|
||||
}
|
||||
if c.State() == StateReadyToClose && c.sendingWorker.IsEmpty() {
|
||||
c.SetState(StateTerminating)
|
||||
}
|
||||
|
||||
if c.State() == StateTerminating {
|
||||
newError("#", c.meta.Conversation, " sending terminating cmd.").AtDebug().WriteToLog()
|
||||
c.Ping(current, CommandTerminate)
|
||||
|
||||
if current-atomic.LoadUint32(&c.stateBeginTime) > 8000 {
|
||||
c.SetState(StateTerminated)
|
||||
}
|
||||
return
|
||||
}
|
||||
if c.State() == StatePeerTerminating && current-atomic.LoadUint32(&c.stateBeginTime) > 4000 {
|
||||
c.SetState(StateTerminating)
|
||||
}
|
||||
|
||||
if c.State() == StateReadyToClose && current-atomic.LoadUint32(&c.stateBeginTime) > 15000 {
|
||||
c.SetState(StateTerminating)
|
||||
}
|
||||
|
||||
// flush acknowledges
|
||||
c.receivingWorker.Flush(current)
|
||||
c.sendingWorker.Flush(current)
|
||||
|
||||
if current-atomic.LoadUint32(&c.lastPingTime) >= 3000 {
|
||||
c.Ping(current, CommandPing)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Connection) State() State {
|
||||
return State(atomic.LoadInt32((*int32)(&c.state)))
|
||||
}
|
||||
|
||||
func (c *Connection) Ping(current uint32, cmd Command) {
|
||||
seg := NewCmdOnlySegment()
|
||||
seg.Conv = c.meta.Conversation
|
||||
seg.Cmd = cmd
|
||||
seg.ReceivingNext = c.receivingWorker.NextNumber()
|
||||
seg.SendingNext = c.sendingWorker.FirstUnacknowledged()
|
||||
seg.PeerRTO = c.roundTrip.Timeout()
|
||||
if c.State() == StateReadyToClose {
|
||||
seg.Option = SegmentOptionClose
|
||||
}
|
||||
c.output.Write(seg)
|
||||
atomic.StoreUint32(&c.lastPingTime, current)
|
||||
seg.Release()
|
||||
}
|
38
transport/internet/kcp/connection_test.go
Normal file
38
transport/internet/kcp/connection_test.go
Normal file
|
@ -0,0 +1,38 @@
|
|||
package kcp_test
|
||||
|
||||
import (
|
||||
"io"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/v1/common/buf"
|
||||
. "github.com/xtls/xray-core/v1/transport/internet/kcp"
|
||||
)
|
||||
|
||||
type NoOpCloser int
|
||||
|
||||
func (NoOpCloser) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestConnectionReadTimeout(t *testing.T) {
|
||||
conn := NewConnection(ConnMetadata{Conversation: 1}, &KCPPacketWriter{
|
||||
Writer: buf.DiscardBytes,
|
||||
}, NoOpCloser(0), &Config{})
|
||||
conn.SetReadDeadline(time.Now().Add(time.Second))
|
||||
|
||||
b := make([]byte, 1024)
|
||||
nBytes, err := conn.Read(b)
|
||||
if nBytes != 0 || err == nil {
|
||||
t.Error("unexpected read: ", nBytes, err)
|
||||
}
|
||||
|
||||
conn.Terminate()
|
||||
}
|
||||
|
||||
func TestConnectionInterface(t *testing.T) {
|
||||
_ = (io.Writer)(new(Connection))
|
||||
_ = (io.Reader)(new(Connection))
|
||||
_ = (buf.Reader)(new(Connection))
|
||||
_ = (buf.Writer)(new(Connection))
|
||||
}
|
78
transport/internet/kcp/crypt.go
Normal file
78
transport/internet/kcp/crypt.go
Normal file
|
@ -0,0 +1,78 @@
|
|||
// +build !confonly
|
||||
|
||||
package kcp
|
||||
|
||||
import (
|
||||
"crypto/cipher"
|
||||
"encoding/binary"
|
||||
"hash/fnv"
|
||||
|
||||
"github.com/xtls/xray-core/v1/common"
|
||||
)
|
||||
|
||||
// SimpleAuthenticator is a legacy AEAD used for KCP encryption.
|
||||
type SimpleAuthenticator struct{}
|
||||
|
||||
// NewSimpleAuthenticator creates a new SimpleAuthenticator
|
||||
func NewSimpleAuthenticator() cipher.AEAD {
|
||||
return &SimpleAuthenticator{}
|
||||
}
|
||||
|
||||
// NonceSize implements cipher.AEAD.NonceSize().
|
||||
func (*SimpleAuthenticator) NonceSize() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Overhead implements cipher.AEAD.NonceSize().
|
||||
func (*SimpleAuthenticator) Overhead() int {
|
||||
return 6
|
||||
}
|
||||
|
||||
// Seal implements cipher.AEAD.Seal().
|
||||
func (a *SimpleAuthenticator) Seal(dst, nonce, plain, extra []byte) []byte {
|
||||
dst = append(dst, 0, 0, 0, 0, 0, 0) // 4 bytes for hash, and then 2 bytes for length
|
||||
binary.BigEndian.PutUint16(dst[4:], uint16(len(plain)))
|
||||
dst = append(dst, plain...)
|
||||
|
||||
fnvHash := fnv.New32a()
|
||||
common.Must2(fnvHash.Write(dst[4:]))
|
||||
fnvHash.Sum(dst[:0])
|
||||
|
||||
dstLen := len(dst)
|
||||
xtra := 4 - dstLen%4
|
||||
if xtra != 4 {
|
||||
dst = append(dst, make([]byte, xtra)...)
|
||||
}
|
||||
xorfwd(dst)
|
||||
if xtra != 4 {
|
||||
dst = dst[:dstLen]
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// Open implements cipher.AEAD.Open().
|
||||
func (a *SimpleAuthenticator) Open(dst, nonce, cipherText, extra []byte) ([]byte, error) {
|
||||
dst = append(dst, cipherText...)
|
||||
dstLen := len(dst)
|
||||
xtra := 4 - dstLen%4
|
||||
if xtra != 4 {
|
||||
dst = append(dst, make([]byte, xtra)...)
|
||||
}
|
||||
xorbkd(dst)
|
||||
if xtra != 4 {
|
||||
dst = dst[:dstLen]
|
||||
}
|
||||
|
||||
fnvHash := fnv.New32a()
|
||||
common.Must2(fnvHash.Write(dst[4:]))
|
||||
if binary.BigEndian.Uint32(dst[:4]) != fnvHash.Sum32() {
|
||||
return nil, newError("invalid auth")
|
||||
}
|
||||
|
||||
length := binary.BigEndian.Uint16(dst[4:6])
|
||||
if len(dst)-6 != int(length) {
|
||||
return nil, newError("invalid auth")
|
||||
}
|
||||
|
||||
return dst[6:], nil
|
||||
}
|
38
transport/internet/kcp/crypt_test.go
Normal file
38
transport/internet/kcp/crypt_test.go
Normal file
|
@ -0,0 +1,38 @@
|
|||
package kcp_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
|
||||
"github.com/xtls/xray-core/v1/common"
|
||||
. "github.com/xtls/xray-core/v1/transport/internet/kcp"
|
||||
)
|
||||
|
||||
func TestSimpleAuthenticator(t *testing.T) {
|
||||
cache := make([]byte, 512)
|
||||
|
||||
payload := []byte{'a', 'b', 'c', 'd', 'e', 'f', 'g'}
|
||||
|
||||
auth := NewSimpleAuthenticator()
|
||||
b := auth.Seal(cache[:0], nil, payload, nil)
|
||||
c, err := auth.Open(cache[:0], nil, b, nil)
|
||||
common.Must(err)
|
||||
if r := cmp.Diff(c, payload); r != "" {
|
||||
t.Error(r)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSimpleAuthenticator2(t *testing.T) {
|
||||
cache := make([]byte, 512)
|
||||
|
||||
payload := []byte{'a', 'b'}
|
||||
|
||||
auth := NewSimpleAuthenticator()
|
||||
b := auth.Seal(cache[:0], nil, payload, nil)
|
||||
c, err := auth.Open(cache[:0], nil, b, nil)
|
||||
common.Must(err)
|
||||
if r := cmp.Diff(c, payload); r != "" {
|
||||
t.Error(r)
|
||||
}
|
||||
}
|
15
transport/internet/kcp/cryptreal.go
Normal file
15
transport/internet/kcp/cryptreal.go
Normal file
|
@ -0,0 +1,15 @@
|
|||
package kcp
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/sha256"
|
||||
|
||||
"github.com/xtls/xray-core/v1/common"
|
||||
)
|
||||
|
||||
func NewAEADAESGCMBasedOnSeed(seed string) cipher.AEAD {
|
||||
hashedSeed := sha256.Sum256([]byte(seed))
|
||||
aesBlock := common.Must2(aes.NewCipher(hashedSeed[:16])).(cipher.Block)
|
||||
return common.Must2(cipher.NewGCM(aesBlock)).(cipher.AEAD)
|
||||
}
|
102
transport/internet/kcp/dialer.go
Normal file
102
transport/internet/kcp/dialer.go
Normal file
|
@ -0,0 +1,102 @@
|
|||
// +build !confonly
|
||||
|
||||
package kcp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/xtls/xray-core/v1/common"
|
||||
"github.com/xtls/xray-core/v1/common/buf"
|
||||
"github.com/xtls/xray-core/v1/common/dice"
|
||||
"github.com/xtls/xray-core/v1/common/net"
|
||||
"github.com/xtls/xray-core/v1/transport/internet"
|
||||
"github.com/xtls/xray-core/v1/transport/internet/tls"
|
||||
"github.com/xtls/xray-core/v1/transport/internet/xtls"
|
||||
)
|
||||
|
||||
var (
|
||||
globalConv = uint32(dice.RollUint16())
|
||||
)
|
||||
|
||||
func fetchInput(_ context.Context, input io.Reader, reader PacketReader, conn *Connection) {
|
||||
cache := make(chan *buf.Buffer, 1024)
|
||||
go func() {
|
||||
for {
|
||||
payload := buf.New()
|
||||
if _, err := payload.ReadFrom(input); err != nil {
|
||||
payload.Release()
|
||||
close(cache)
|
||||
return
|
||||
}
|
||||
select {
|
||||
case cache <- payload:
|
||||
default:
|
||||
payload.Release()
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
for payload := range cache {
|
||||
segments := reader.Read(payload.Bytes())
|
||||
payload.Release()
|
||||
if len(segments) > 0 {
|
||||
conn.Input(segments)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DialKCP dials a new KCP connections to the specific destination.
|
||||
func DialKCP(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (internet.Connection, error) {
|
||||
dest.Network = net.Network_UDP
|
||||
newError("dialing mKCP to ", dest).WriteToLog()
|
||||
|
||||
rawConn, err := internet.DialSystem(ctx, dest, streamSettings.SocketSettings)
|
||||
if err != nil {
|
||||
return nil, newError("failed to dial to dest: ", err).AtWarning().Base(err)
|
||||
}
|
||||
|
||||
kcpSettings := streamSettings.ProtocolSettings.(*Config)
|
||||
|
||||
header, err := kcpSettings.GetPackerHeader()
|
||||
if err != nil {
|
||||
return nil, newError("failed to create packet header").Base(err)
|
||||
}
|
||||
security, err := kcpSettings.GetSecurity()
|
||||
if err != nil {
|
||||
return nil, newError("failed to create security").Base(err)
|
||||
}
|
||||
reader := &KCPPacketReader{
|
||||
Header: header,
|
||||
Security: security,
|
||||
}
|
||||
writer := &KCPPacketWriter{
|
||||
Header: header,
|
||||
Security: security,
|
||||
Writer: rawConn,
|
||||
}
|
||||
|
||||
conv := uint16(atomic.AddUint32(&globalConv, 1))
|
||||
session := NewConnection(ConnMetadata{
|
||||
LocalAddr: rawConn.LocalAddr(),
|
||||
RemoteAddr: rawConn.RemoteAddr(),
|
||||
Conversation: conv,
|
||||
}, writer, rawConn, kcpSettings)
|
||||
|
||||
go fetchInput(ctx, rawConn, reader, session)
|
||||
|
||||
var iConn internet.Connection = session
|
||||
|
||||
if config := tls.ConfigFromStreamSettings(streamSettings); config != nil {
|
||||
iConn = tls.Client(iConn, config.GetTLSConfig(tls.WithDestination(dest)))
|
||||
} else if config := xtls.ConfigFromStreamSettings(streamSettings); config != nil {
|
||||
iConn = xtls.Client(iConn, config.GetXTLSConfig(xtls.WithDestination(dest)))
|
||||
}
|
||||
|
||||
return iConn, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
common.Must(internet.RegisterTransportDialer(protocolName, DialKCP))
|
||||
}
|
9
transport/internet/kcp/errors.generated.go
Normal file
9
transport/internet/kcp/errors.generated.go
Normal file
|
@ -0,0 +1,9 @@
|
|||
package kcp
|
||||
|
||||
import "github.com/xtls/xray-core/v1/common/errors"
|
||||
|
||||
type errPathObjHolder struct{}
|
||||
|
||||
func newError(values ...interface{}) *errors.Error {
|
||||
return errors.New(values...).WithPathObj(errPathObjHolder{})
|
||||
}
|
97
transport/internet/kcp/io.go
Normal file
97
transport/internet/kcp/io.go
Normal file
|
@ -0,0 +1,97 @@
|
|||
// +build !confonly
|
||||
|
||||
package kcp
|
||||
|
||||
import (
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"io"
|
||||
|
||||
"github.com/xtls/xray-core/v1/common"
|
||||
"github.com/xtls/xray-core/v1/common/buf"
|
||||
"github.com/xtls/xray-core/v1/transport/internet"
|
||||
)
|
||||
|
||||
type PacketReader interface {
|
||||
Read([]byte) []Segment
|
||||
}
|
||||
|
||||
type PacketWriter interface {
|
||||
Overhead() int
|
||||
io.Writer
|
||||
}
|
||||
|
||||
type KCPPacketReader struct {
|
||||
Security cipher.AEAD
|
||||
Header internet.PacketHeader
|
||||
}
|
||||
|
||||
func (r *KCPPacketReader) Read(b []byte) []Segment {
|
||||
if r.Header != nil {
|
||||
if int32(len(b)) <= r.Header.Size() {
|
||||
return nil
|
||||
}
|
||||
b = b[r.Header.Size():]
|
||||
}
|
||||
if r.Security != nil {
|
||||
nonceSize := r.Security.NonceSize()
|
||||
overhead := r.Security.Overhead()
|
||||
if len(b) <= nonceSize+overhead {
|
||||
return nil
|
||||
}
|
||||
out, err := r.Security.Open(b[nonceSize:nonceSize], b[:nonceSize], b[nonceSize:], nil)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
b = out
|
||||
}
|
||||
var result []Segment
|
||||
for len(b) > 0 {
|
||||
seg, x := ReadSegment(b)
|
||||
if seg == nil {
|
||||
break
|
||||
}
|
||||
result = append(result, seg)
|
||||
b = x
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
type KCPPacketWriter struct {
|
||||
Header internet.PacketHeader
|
||||
Security cipher.AEAD
|
||||
Writer io.Writer
|
||||
}
|
||||
|
||||
func (w *KCPPacketWriter) Overhead() int {
|
||||
overhead := 0
|
||||
if w.Header != nil {
|
||||
overhead += int(w.Header.Size())
|
||||
}
|
||||
if w.Security != nil {
|
||||
overhead += w.Security.Overhead()
|
||||
}
|
||||
return overhead
|
||||
}
|
||||
|
||||
func (w *KCPPacketWriter) Write(b []byte) (int, error) {
|
||||
bb := buf.StackNew()
|
||||
defer bb.Release()
|
||||
|
||||
if w.Header != nil {
|
||||
w.Header.Serialize(bb.Extend(w.Header.Size()))
|
||||
}
|
||||
if w.Security != nil {
|
||||
nonceSize := w.Security.NonceSize()
|
||||
common.Must2(bb.ReadFullFrom(rand.Reader, int32(nonceSize)))
|
||||
nonce := bb.BytesFrom(int32(-nonceSize))
|
||||
|
||||
encrypted := bb.Extend(int32(w.Security.Overhead() + len(b)))
|
||||
w.Security.Seal(encrypted[:0], nonce, b, nil)
|
||||
} else {
|
||||
bb.Write(b)
|
||||
}
|
||||
|
||||
_, err := w.Writer.Write(bb.Bytes())
|
||||
return len(b), err
|
||||
}
|
36
transport/internet/kcp/io_test.go
Normal file
36
transport/internet/kcp/io_test.go
Normal file
|
@ -0,0 +1,36 @@
|
|||
package kcp_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/xtls/xray-core/v1/transport/internet/kcp"
|
||||
)
|
||||
|
||||
func TestKCPPacketReader(t *testing.T) {
|
||||
reader := KCPPacketReader{
|
||||
Security: &SimpleAuthenticator{},
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
Input []byte
|
||||
Output []Segment
|
||||
}{
|
||||
{
|
||||
Input: []byte{},
|
||||
Output: nil,
|
||||
},
|
||||
{
|
||||
Input: []byte{1},
|
||||
Output: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
seg := reader.Read(testCase.Input)
|
||||
if testCase.Output == nil && seg != nil {
|
||||
t.Errorf("Expect nothing returned, but actually %v", seg)
|
||||
} else if testCase.Output != nil && seg == nil {
|
||||
t.Errorf("Expect some output, but got nil")
|
||||
}
|
||||
}
|
||||
}
|
8
transport/internet/kcp/kcp.go
Normal file
8
transport/internet/kcp/kcp.go
Normal file
|
@ -0,0 +1,8 @@
|
|||
// Package kcp - A Fast and Reliable ARQ Protocol
|
||||
//
|
||||
// Acknowledgement:
|
||||
// skywind3000@github for inventing the KCP protocol
|
||||
// xtaci@github for translating to Golang
|
||||
package kcp
|
||||
|
||||
//go:generate go run github.com/xtls/xray-core/v1/common/errors/errorgen
|
85
transport/internet/kcp/kcp_test.go
Normal file
85
transport/internet/kcp/kcp_test.go
Normal file
|
@ -0,0 +1,85 @@
|
|||
package kcp_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"io"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/xtls/xray-core/v1/common"
|
||||
"github.com/xtls/xray-core/v1/common/errors"
|
||||
"github.com/xtls/xray-core/v1/common/net"
|
||||
"github.com/xtls/xray-core/v1/transport/internet"
|
||||
. "github.com/xtls/xray-core/v1/transport/internet/kcp"
|
||||
)
|
||||
|
||||
func TestDialAndListen(t *testing.T) {
|
||||
listerner, err := NewListener(context.Background(), net.LocalHostIP, net.Port(0), &internet.MemoryStreamConfig{
|
||||
ProtocolName: "mkcp",
|
||||
ProtocolSettings: &Config{},
|
||||
}, func(conn internet.Connection) {
|
||||
go func(c internet.Connection) {
|
||||
payload := make([]byte, 4096)
|
||||
for {
|
||||
nBytes, err := c.Read(payload)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
for idx, b := range payload[:nBytes] {
|
||||
payload[idx] = b ^ 'c'
|
||||
}
|
||||
c.Write(payload[:nBytes])
|
||||
}
|
||||
c.Close()
|
||||
}(conn)
|
||||
})
|
||||
common.Must(err)
|
||||
defer listerner.Close()
|
||||
|
||||
port := net.Port(listerner.Addr().(*net.UDPAddr).Port)
|
||||
|
||||
var errg errgroup.Group
|
||||
for i := 0; i < 10; i++ {
|
||||
errg.Go(func() error {
|
||||
clientConn, err := DialKCP(context.Background(), net.UDPDestination(net.LocalHostIP, port), &internet.MemoryStreamConfig{
|
||||
ProtocolName: "mkcp",
|
||||
ProtocolSettings: &Config{},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer clientConn.Close()
|
||||
|
||||
clientSend := make([]byte, 1024*1024)
|
||||
rand.Read(clientSend)
|
||||
go clientConn.Write(clientSend)
|
||||
|
||||
clientReceived := make([]byte, 1024*1024)
|
||||
common.Must2(io.ReadFull(clientConn, clientReceived))
|
||||
|
||||
clientExpected := make([]byte, 1024*1024)
|
||||
for idx, b := range clientSend {
|
||||
clientExpected[idx] = b ^ 'c'
|
||||
}
|
||||
if r := cmp.Diff(clientReceived, clientExpected); r != "" {
|
||||
return errors.New(r)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
if err := errg.Wait(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for i := 0; i < 60 && listerner.ActiveConnections() > 0; i++ {
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
}
|
||||
if v := listerner.ActiveConnections(); v != 0 {
|
||||
t.Error("active connections: ", v)
|
||||
}
|
||||
}
|
206
transport/internet/kcp/listener.go
Normal file
206
transport/internet/kcp/listener.go
Normal file
|
@ -0,0 +1,206 @@
|
|||
// +build !confonly
|
||||
|
||||
package kcp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/cipher"
|
||||
gotls "crypto/tls"
|
||||
"sync"
|
||||
|
||||
goxtls "github.com/xtls/go"
|
||||
|
||||
"github.com/xtls/xray-core/v1/common"
|
||||
"github.com/xtls/xray-core/v1/common/buf"
|
||||
"github.com/xtls/xray-core/v1/common/net"
|
||||
"github.com/xtls/xray-core/v1/transport/internet"
|
||||
"github.com/xtls/xray-core/v1/transport/internet/tls"
|
||||
"github.com/xtls/xray-core/v1/transport/internet/udp"
|
||||
"github.com/xtls/xray-core/v1/transport/internet/xtls"
|
||||
)
|
||||
|
||||
type ConnectionID struct {
|
||||
Remote net.Address
|
||||
Port net.Port
|
||||
Conv uint16
|
||||
}
|
||||
|
||||
// Listener defines a server listening for connections
|
||||
type Listener struct {
|
||||
sync.Mutex
|
||||
sessions map[ConnectionID]*Connection
|
||||
hub *udp.Hub
|
||||
tlsConfig *gotls.Config
|
||||
xtlsConfig *goxtls.Config
|
||||
config *Config
|
||||
reader PacketReader
|
||||
header internet.PacketHeader
|
||||
security cipher.AEAD
|
||||
addConn internet.ConnHandler
|
||||
}
|
||||
|
||||
func NewListener(ctx context.Context, address net.Address, port net.Port, streamSettings *internet.MemoryStreamConfig, addConn internet.ConnHandler) (*Listener, error) {
|
||||
kcpSettings := streamSettings.ProtocolSettings.(*Config)
|
||||
header, err := kcpSettings.GetPackerHeader()
|
||||
if err != nil {
|
||||
return nil, newError("failed to create packet header").Base(err).AtError()
|
||||
}
|
||||
security, err := kcpSettings.GetSecurity()
|
||||
if err != nil {
|
||||
return nil, newError("failed to create security").Base(err).AtError()
|
||||
}
|
||||
l := &Listener{
|
||||
header: header,
|
||||
security: security,
|
||||
reader: &KCPPacketReader{
|
||||
Header: header,
|
||||
Security: security,
|
||||
},
|
||||
sessions: make(map[ConnectionID]*Connection),
|
||||
config: kcpSettings,
|
||||
addConn: addConn,
|
||||
}
|
||||
|
||||
if config := tls.ConfigFromStreamSettings(streamSettings); config != nil {
|
||||
l.tlsConfig = config.GetTLSConfig()
|
||||
}
|
||||
if config := xtls.ConfigFromStreamSettings(streamSettings); config != nil {
|
||||
l.xtlsConfig = config.GetXTLSConfig()
|
||||
}
|
||||
|
||||
hub, err := udp.ListenUDP(ctx, address, port, streamSettings, udp.HubCapacity(1024))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
l.Lock()
|
||||
l.hub = hub
|
||||
l.Unlock()
|
||||
newError("listening on ", address, ":", port).WriteToLog()
|
||||
|
||||
go l.handlePackets()
|
||||
|
||||
return l, nil
|
||||
}
|
||||
|
||||
func (l *Listener) handlePackets() {
|
||||
receive := l.hub.Receive()
|
||||
for payload := range receive {
|
||||
l.OnReceive(payload.Payload, payload.Source)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Listener) OnReceive(payload *buf.Buffer, src net.Destination) {
|
||||
segments := l.reader.Read(payload.Bytes())
|
||||
payload.Release()
|
||||
|
||||
if len(segments) == 0 {
|
||||
newError("discarding invalid payload from ", src).WriteToLog()
|
||||
return
|
||||
}
|
||||
|
||||
conv := segments[0].Conversation()
|
||||
cmd := segments[0].Command()
|
||||
|
||||
id := ConnectionID{
|
||||
Remote: src.Address,
|
||||
Port: src.Port,
|
||||
Conv: conv,
|
||||
}
|
||||
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
conn, found := l.sessions[id]
|
||||
|
||||
if !found {
|
||||
if cmd == CommandTerminate {
|
||||
return
|
||||
}
|
||||
writer := &Writer{
|
||||
id: id,
|
||||
hub: l.hub,
|
||||
dest: src,
|
||||
listener: l,
|
||||
}
|
||||
remoteAddr := &net.UDPAddr{
|
||||
IP: src.Address.IP(),
|
||||
Port: int(src.Port),
|
||||
}
|
||||
localAddr := l.hub.Addr()
|
||||
conn = NewConnection(ConnMetadata{
|
||||
LocalAddr: localAddr,
|
||||
RemoteAddr: remoteAddr,
|
||||
Conversation: conv,
|
||||
}, &KCPPacketWriter{
|
||||
Header: l.header,
|
||||
Security: l.security,
|
||||
Writer: writer,
|
||||
}, writer, l.config)
|
||||
var netConn internet.Connection = conn
|
||||
if l.tlsConfig != nil {
|
||||
netConn = tls.Server(conn, l.tlsConfig)
|
||||
} else if l.xtlsConfig != nil {
|
||||
netConn = xtls.Server(conn, l.xtlsConfig)
|
||||
}
|
||||
|
||||
l.addConn(netConn)
|
||||
l.sessions[id] = conn
|
||||
}
|
||||
conn.Input(segments)
|
||||
}
|
||||
|
||||
func (l *Listener) Remove(id ConnectionID) {
|
||||
l.Lock()
|
||||
delete(l.sessions, id)
|
||||
l.Unlock()
|
||||
}
|
||||
|
||||
// Close stops listening on the UDP address. Already Accepted connections are not closed.
|
||||
func (l *Listener) Close() error {
|
||||
l.hub.Close()
|
||||
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
for _, conn := range l.sessions {
|
||||
go conn.Terminate()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Listener) ActiveConnections() int {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
return len(l.sessions)
|
||||
}
|
||||
|
||||
// Addr returns the listener's network address, The Addr returned is shared by all invocations of Addr, so do not modify it.
|
||||
func (l *Listener) Addr() net.Addr {
|
||||
return l.hub.Addr()
|
||||
}
|
||||
|
||||
type Writer struct {
|
||||
id ConnectionID
|
||||
dest net.Destination
|
||||
hub *udp.Hub
|
||||
listener *Listener
|
||||
}
|
||||
|
||||
func (w *Writer) Write(payload []byte) (int, error) {
|
||||
return w.hub.WriteTo(payload, w.dest)
|
||||
}
|
||||
|
||||
func (w *Writer) Close() error {
|
||||
w.listener.Remove(w.id)
|
||||
return nil
|
||||
}
|
||||
|
||||
func ListenKCP(ctx context.Context, address net.Address, port net.Port, streamSettings *internet.MemoryStreamConfig, addConn internet.ConnHandler) (internet.Listener, error) {
|
||||
return NewListener(ctx, address, port, streamSettings, addConn)
|
||||
}
|
||||
|
||||
func init() {
|
||||
common.Must(internet.RegisterTransportListener(protocolName, ListenKCP))
|
||||
}
|
56
transport/internet/kcp/output.go
Normal file
56
transport/internet/kcp/output.go
Normal file
|
@ -0,0 +1,56 @@
|
|||
// +build !confonly
|
||||
|
||||
package kcp
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"github.com/xtls/xray-core/v1/common/retry"
|
||||
|
||||
"github.com/xtls/xray-core/v1/common/buf"
|
||||
)
|
||||
|
||||
type SegmentWriter interface {
|
||||
Write(seg Segment) error
|
||||
}
|
||||
|
||||
type SimpleSegmentWriter struct {
|
||||
sync.Mutex
|
||||
buffer *buf.Buffer
|
||||
writer io.Writer
|
||||
}
|
||||
|
||||
func NewSegmentWriter(writer io.Writer) SegmentWriter {
|
||||
return &SimpleSegmentWriter{
|
||||
writer: writer,
|
||||
buffer: buf.New(),
|
||||
}
|
||||
}
|
||||
|
||||
func (w *SimpleSegmentWriter) Write(seg Segment) error {
|
||||
w.Lock()
|
||||
defer w.Unlock()
|
||||
|
||||
w.buffer.Clear()
|
||||
rawBytes := w.buffer.Extend(seg.ByteSize())
|
||||
seg.Serialize(rawBytes)
|
||||
_, err := w.writer.Write(w.buffer.Bytes())
|
||||
return err
|
||||
}
|
||||
|
||||
type RetryableWriter struct {
|
||||
writer SegmentWriter
|
||||
}
|
||||
|
||||
func NewRetryableWriter(writer SegmentWriter) SegmentWriter {
|
||||
return &RetryableWriter{
|
||||
writer: writer,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *RetryableWriter) Write(seg Segment) error {
|
||||
return retry.Timed(5, 100).On(func() error {
|
||||
return w.writer.Write(seg)
|
||||
})
|
||||
}
|
260
transport/internet/kcp/receiving.go
Normal file
260
transport/internet/kcp/receiving.go
Normal file
|
@ -0,0 +1,260 @@
|
|||
// +build !confonly
|
||||
|
||||
package kcp
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/xtls/xray-core/v1/common/buf"
|
||||
)
|
||||
|
||||
type ReceivingWindow struct {
|
||||
cache map[uint32]*DataSegment
|
||||
}
|
||||
|
||||
func NewReceivingWindow() *ReceivingWindow {
|
||||
return &ReceivingWindow{
|
||||
cache: make(map[uint32]*DataSegment),
|
||||
}
|
||||
}
|
||||
|
||||
func (w *ReceivingWindow) Set(id uint32, value *DataSegment) bool {
|
||||
_, f := w.cache[id]
|
||||
if f {
|
||||
return false
|
||||
}
|
||||
w.cache[id] = value
|
||||
return true
|
||||
}
|
||||
|
||||
func (w *ReceivingWindow) Has(id uint32) bool {
|
||||
_, f := w.cache[id]
|
||||
return f
|
||||
}
|
||||
|
||||
func (w *ReceivingWindow) Remove(id uint32) *DataSegment {
|
||||
v, f := w.cache[id]
|
||||
if !f {
|
||||
return nil
|
||||
}
|
||||
delete(w.cache, id)
|
||||
return v
|
||||
}
|
||||
|
||||
type AckList struct {
|
||||
writer SegmentWriter
|
||||
timestamps []uint32
|
||||
numbers []uint32
|
||||
nextFlush []uint32
|
||||
|
||||
flushCandidates []uint32
|
||||
dirty bool
|
||||
}
|
||||
|
||||
func NewAckList(writer SegmentWriter) *AckList {
|
||||
return &AckList{
|
||||
writer: writer,
|
||||
timestamps: make([]uint32, 0, 128),
|
||||
numbers: make([]uint32, 0, 128),
|
||||
nextFlush: make([]uint32, 0, 128),
|
||||
flushCandidates: make([]uint32, 0, 128),
|
||||
}
|
||||
}
|
||||
|
||||
func (l *AckList) Add(number uint32, timestamp uint32) {
|
||||
l.timestamps = append(l.timestamps, timestamp)
|
||||
l.numbers = append(l.numbers, number)
|
||||
l.nextFlush = append(l.nextFlush, 0)
|
||||
l.dirty = true
|
||||
}
|
||||
|
||||
func (l *AckList) Clear(una uint32) {
|
||||
count := 0
|
||||
for i := 0; i < len(l.numbers); i++ {
|
||||
if l.numbers[i] < una {
|
||||
continue
|
||||
}
|
||||
if i != count {
|
||||
l.numbers[count] = l.numbers[i]
|
||||
l.timestamps[count] = l.timestamps[i]
|
||||
l.nextFlush[count] = l.nextFlush[i]
|
||||
}
|
||||
count++
|
||||
}
|
||||
if count < len(l.numbers) {
|
||||
l.numbers = l.numbers[:count]
|
||||
l.timestamps = l.timestamps[:count]
|
||||
l.nextFlush = l.nextFlush[:count]
|
||||
l.dirty = true
|
||||
}
|
||||
}
|
||||
|
||||
func (l *AckList) Flush(current uint32, rto uint32) {
|
||||
l.flushCandidates = l.flushCandidates[:0]
|
||||
|
||||
seg := NewAckSegment()
|
||||
for i := 0; i < len(l.numbers); i++ {
|
||||
if l.nextFlush[i] > current {
|
||||
if len(l.flushCandidates) < cap(l.flushCandidates) {
|
||||
l.flushCandidates = append(l.flushCandidates, l.numbers[i])
|
||||
}
|
||||
continue
|
||||
}
|
||||
seg.PutNumber(l.numbers[i])
|
||||
seg.PutTimestamp(l.timestamps[i])
|
||||
timeout := rto / 2
|
||||
if timeout < 20 {
|
||||
timeout = 20
|
||||
}
|
||||
l.nextFlush[i] = current + timeout
|
||||
|
||||
if seg.IsFull() {
|
||||
l.writer.Write(seg)
|
||||
seg.Release()
|
||||
seg = NewAckSegment()
|
||||
l.dirty = false
|
||||
}
|
||||
}
|
||||
|
||||
if l.dirty || !seg.IsEmpty() {
|
||||
for _, number := range l.flushCandidates {
|
||||
if seg.IsFull() {
|
||||
break
|
||||
}
|
||||
seg.PutNumber(number)
|
||||
}
|
||||
l.writer.Write(seg)
|
||||
l.dirty = false
|
||||
}
|
||||
|
||||
seg.Release()
|
||||
}
|
||||
|
||||
type ReceivingWorker struct {
|
||||
sync.RWMutex
|
||||
conn *Connection
|
||||
leftOver buf.MultiBuffer
|
||||
window *ReceivingWindow
|
||||
acklist *AckList
|
||||
nextNumber uint32
|
||||
windowSize uint32
|
||||
}
|
||||
|
||||
func NewReceivingWorker(kcp *Connection) *ReceivingWorker {
|
||||
worker := &ReceivingWorker{
|
||||
conn: kcp,
|
||||
window: NewReceivingWindow(),
|
||||
windowSize: kcp.Config.GetReceivingInFlightSize(),
|
||||
}
|
||||
worker.acklist = NewAckList(worker)
|
||||
return worker
|
||||
}
|
||||
|
||||
func (w *ReceivingWorker) Release() {
|
||||
w.Lock()
|
||||
buf.ReleaseMulti(w.leftOver)
|
||||
w.leftOver = nil
|
||||
w.Unlock()
|
||||
}
|
||||
|
||||
func (w *ReceivingWorker) ProcessSendingNext(number uint32) {
|
||||
w.Lock()
|
||||
defer w.Unlock()
|
||||
|
||||
w.acklist.Clear(number)
|
||||
}
|
||||
|
||||
func (w *ReceivingWorker) ProcessSegment(seg *DataSegment) {
|
||||
w.Lock()
|
||||
defer w.Unlock()
|
||||
|
||||
number := seg.Number
|
||||
idx := number - w.nextNumber
|
||||
if idx >= w.windowSize {
|
||||
return
|
||||
}
|
||||
w.acklist.Clear(seg.SendingNext)
|
||||
w.acklist.Add(number, seg.Timestamp)
|
||||
|
||||
if !w.window.Set(seg.Number, seg) {
|
||||
seg.Release()
|
||||
}
|
||||
}
|
||||
|
||||
func (w *ReceivingWorker) ReadMultiBuffer() buf.MultiBuffer {
|
||||
if w.leftOver != nil {
|
||||
mb := w.leftOver
|
||||
w.leftOver = nil
|
||||
return mb
|
||||
}
|
||||
|
||||
mb := make(buf.MultiBuffer, 0, 32)
|
||||
|
||||
w.Lock()
|
||||
defer w.Unlock()
|
||||
for {
|
||||
seg := w.window.Remove(w.nextNumber)
|
||||
if seg == nil {
|
||||
break
|
||||
}
|
||||
w.nextNumber++
|
||||
mb = append(mb, seg.Detach())
|
||||
seg.Release()
|
||||
}
|
||||
|
||||
return mb
|
||||
}
|
||||
|
||||
func (w *ReceivingWorker) Read(b []byte) int {
|
||||
mb := w.ReadMultiBuffer()
|
||||
if mb.IsEmpty() {
|
||||
return 0
|
||||
}
|
||||
mb, nBytes := buf.SplitBytes(mb, b)
|
||||
if !mb.IsEmpty() {
|
||||
w.leftOver = mb
|
||||
}
|
||||
return nBytes
|
||||
}
|
||||
|
||||
func (w *ReceivingWorker) IsDataAvailable() bool {
|
||||
w.RLock()
|
||||
defer w.RUnlock()
|
||||
return w.window.Has(w.nextNumber)
|
||||
}
|
||||
|
||||
func (w *ReceivingWorker) NextNumber() uint32 {
|
||||
w.RLock()
|
||||
defer w.RUnlock()
|
||||
|
||||
return w.nextNumber
|
||||
}
|
||||
|
||||
func (w *ReceivingWorker) Flush(current uint32) {
|
||||
w.Lock()
|
||||
defer w.Unlock()
|
||||
|
||||
w.acklist.Flush(current, w.conn.roundTrip.Timeout())
|
||||
}
|
||||
|
||||
func (w *ReceivingWorker) Write(seg Segment) error {
|
||||
ackSeg := seg.(*AckSegment)
|
||||
ackSeg.Conv = w.conn.meta.Conversation
|
||||
ackSeg.ReceivingNext = w.nextNumber
|
||||
ackSeg.ReceivingWindow = w.nextNumber + w.windowSize
|
||||
ackSeg.Option = 0
|
||||
if w.conn.State() == StateReadyToClose {
|
||||
ackSeg.Option = SegmentOptionClose
|
||||
}
|
||||
return w.conn.output.Write(ackSeg)
|
||||
}
|
||||
|
||||
func (*ReceivingWorker) CloseRead() {
|
||||
}
|
||||
|
||||
func (w *ReceivingWorker) UpdateNecessary() bool {
|
||||
w.RLock()
|
||||
defer w.RUnlock()
|
||||
|
||||
return len(w.acklist.numbers) > 0
|
||||
}
|
305
transport/internet/kcp/segment.go
Normal file
305
transport/internet/kcp/segment.go
Normal file
|
@ -0,0 +1,305 @@
|
|||
// +build !confonly
|
||||
|
||||
package kcp
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/xtls/xray-core/v1/common/buf"
|
||||
)
|
||||
|
||||
// Command is a KCP command that indicate the purpose of a Segment.
|
||||
type Command byte
|
||||
|
||||
const (
|
||||
// CommandACK indicates an AckSegment.
|
||||
CommandACK Command = 0
|
||||
// CommandData indicates a DataSegment.
|
||||
CommandData Command = 1
|
||||
// CommandTerminate indicates that peer terminates the connection.
|
||||
CommandTerminate Command = 2
|
||||
// CommandPing indicates a ping.
|
||||
CommandPing Command = 3
|
||||
)
|
||||
|
||||
type SegmentOption byte
|
||||
|
||||
const (
|
||||
SegmentOptionClose SegmentOption = 1
|
||||
)
|
||||
|
||||
type Segment interface {
|
||||
Release()
|
||||
Conversation() uint16
|
||||
Command() Command
|
||||
ByteSize() int32
|
||||
Serialize([]byte)
|
||||
parse(conv uint16, cmd Command, opt SegmentOption, buf []byte) (bool, []byte)
|
||||
}
|
||||
|
||||
const (
|
||||
DataSegmentOverhead = 18
|
||||
)
|
||||
|
||||
type DataSegment struct {
|
||||
Conv uint16
|
||||
Option SegmentOption
|
||||
Timestamp uint32
|
||||
Number uint32
|
||||
SendingNext uint32
|
||||
|
||||
payload *buf.Buffer
|
||||
timeout uint32
|
||||
transmit uint32
|
||||
}
|
||||
|
||||
func NewDataSegment() *DataSegment {
|
||||
return new(DataSegment)
|
||||
}
|
||||
|
||||
func (s *DataSegment) parse(conv uint16, cmd Command, opt SegmentOption, buf []byte) (bool, []byte) {
|
||||
s.Conv = conv
|
||||
s.Option = opt
|
||||
if len(buf) < 15 {
|
||||
return false, nil
|
||||
}
|
||||
s.Timestamp = binary.BigEndian.Uint32(buf)
|
||||
buf = buf[4:]
|
||||
|
||||
s.Number = binary.BigEndian.Uint32(buf)
|
||||
buf = buf[4:]
|
||||
|
||||
s.SendingNext = binary.BigEndian.Uint32(buf)
|
||||
buf = buf[4:]
|
||||
|
||||
dataLen := int(binary.BigEndian.Uint16(buf))
|
||||
buf = buf[2:]
|
||||
|
||||
if len(buf) < dataLen {
|
||||
return false, nil
|
||||
}
|
||||
s.Data().Clear()
|
||||
s.Data().Write(buf[:dataLen])
|
||||
buf = buf[dataLen:]
|
||||
|
||||
return true, buf
|
||||
}
|
||||
|
||||
func (s *DataSegment) Conversation() uint16 {
|
||||
return s.Conv
|
||||
}
|
||||
|
||||
func (*DataSegment) Command() Command {
|
||||
return CommandData
|
||||
}
|
||||
|
||||
func (s *DataSegment) Detach() *buf.Buffer {
|
||||
r := s.payload
|
||||
s.payload = nil
|
||||
return r
|
||||
}
|
||||
|
||||
func (s *DataSegment) Data() *buf.Buffer {
|
||||
if s.payload == nil {
|
||||
s.payload = buf.New()
|
||||
}
|
||||
return s.payload
|
||||
}
|
||||
|
||||
func (s *DataSegment) Serialize(b []byte) {
|
||||
binary.BigEndian.PutUint16(b, s.Conv)
|
||||
b[2] = byte(CommandData)
|
||||
b[3] = byte(s.Option)
|
||||
binary.BigEndian.PutUint32(b[4:], s.Timestamp)
|
||||
binary.BigEndian.PutUint32(b[8:], s.Number)
|
||||
binary.BigEndian.PutUint32(b[12:], s.SendingNext)
|
||||
binary.BigEndian.PutUint16(b[16:], uint16(s.payload.Len()))
|
||||
copy(b[18:], s.payload.Bytes())
|
||||
}
|
||||
|
||||
func (s *DataSegment) ByteSize() int32 {
|
||||
return 2 + 1 + 1 + 4 + 4 + 4 + 2 + s.payload.Len()
|
||||
}
|
||||
|
||||
func (s *DataSegment) Release() {
|
||||
s.payload.Release()
|
||||
s.payload = nil
|
||||
}
|
||||
|
||||
type AckSegment struct {
|
||||
Conv uint16
|
||||
Option SegmentOption
|
||||
ReceivingWindow uint32
|
||||
ReceivingNext uint32
|
||||
Timestamp uint32
|
||||
NumberList []uint32
|
||||
}
|
||||
|
||||
const ackNumberLimit = 128
|
||||
|
||||
func NewAckSegment() *AckSegment {
|
||||
return new(AckSegment)
|
||||
}
|
||||
|
||||
func (s *AckSegment) parse(conv uint16, cmd Command, opt SegmentOption, buf []byte) (bool, []byte) {
|
||||
s.Conv = conv
|
||||
s.Option = opt
|
||||
if len(buf) < 13 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
s.ReceivingWindow = binary.BigEndian.Uint32(buf)
|
||||
buf = buf[4:]
|
||||
|
||||
s.ReceivingNext = binary.BigEndian.Uint32(buf)
|
||||
buf = buf[4:]
|
||||
|
||||
s.Timestamp = binary.BigEndian.Uint32(buf)
|
||||
buf = buf[4:]
|
||||
|
||||
count := int(buf[0])
|
||||
buf = buf[1:]
|
||||
|
||||
if len(buf) < count*4 {
|
||||
return false, nil
|
||||
}
|
||||
for i := 0; i < count; i++ {
|
||||
s.PutNumber(binary.BigEndian.Uint32(buf))
|
||||
buf = buf[4:]
|
||||
}
|
||||
|
||||
return true, buf
|
||||
}
|
||||
|
||||
func (s *AckSegment) Conversation() uint16 {
|
||||
return s.Conv
|
||||
}
|
||||
|
||||
func (*AckSegment) Command() Command {
|
||||
return CommandACK
|
||||
}
|
||||
|
||||
func (s *AckSegment) PutTimestamp(timestamp uint32) {
|
||||
if timestamp-s.Timestamp < 0x7FFFFFFF {
|
||||
s.Timestamp = timestamp
|
||||
}
|
||||
}
|
||||
|
||||
func (s *AckSegment) PutNumber(number uint32) {
|
||||
s.NumberList = append(s.NumberList, number)
|
||||
}
|
||||
|
||||
func (s *AckSegment) IsFull() bool {
|
||||
return len(s.NumberList) == ackNumberLimit
|
||||
}
|
||||
|
||||
func (s *AckSegment) IsEmpty() bool {
|
||||
return len(s.NumberList) == 0
|
||||
}
|
||||
|
||||
func (s *AckSegment) ByteSize() int32 {
|
||||
return 2 + 1 + 1 + 4 + 4 + 4 + 1 + int32(len(s.NumberList)*4)
|
||||
}
|
||||
|
||||
func (s *AckSegment) Serialize(b []byte) {
|
||||
binary.BigEndian.PutUint16(b, s.Conv)
|
||||
b[2] = byte(CommandACK)
|
||||
b[3] = byte(s.Option)
|
||||
binary.BigEndian.PutUint32(b[4:], s.ReceivingWindow)
|
||||
binary.BigEndian.PutUint32(b[8:], s.ReceivingNext)
|
||||
binary.BigEndian.PutUint32(b[12:], s.Timestamp)
|
||||
b[16] = byte(len(s.NumberList))
|
||||
n := 17
|
||||
for _, number := range s.NumberList {
|
||||
binary.BigEndian.PutUint32(b[n:], number)
|
||||
n += 4
|
||||
}
|
||||
}
|
||||
|
||||
func (s *AckSegment) Release() {}
|
||||
|
||||
type CmdOnlySegment struct {
|
||||
Conv uint16
|
||||
Cmd Command
|
||||
Option SegmentOption
|
||||
SendingNext uint32
|
||||
ReceivingNext uint32
|
||||
PeerRTO uint32
|
||||
}
|
||||
|
||||
func NewCmdOnlySegment() *CmdOnlySegment {
|
||||
return new(CmdOnlySegment)
|
||||
}
|
||||
|
||||
func (s *CmdOnlySegment) parse(conv uint16, cmd Command, opt SegmentOption, buf []byte) (bool, []byte) {
|
||||
s.Conv = conv
|
||||
s.Cmd = cmd
|
||||
s.Option = opt
|
||||
|
||||
if len(buf) < 12 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
s.SendingNext = binary.BigEndian.Uint32(buf)
|
||||
buf = buf[4:]
|
||||
|
||||
s.ReceivingNext = binary.BigEndian.Uint32(buf)
|
||||
buf = buf[4:]
|
||||
|
||||
s.PeerRTO = binary.BigEndian.Uint32(buf)
|
||||
buf = buf[4:]
|
||||
|
||||
return true, buf
|
||||
}
|
||||
|
||||
func (s *CmdOnlySegment) Conversation() uint16 {
|
||||
return s.Conv
|
||||
}
|
||||
|
||||
func (s *CmdOnlySegment) Command() Command {
|
||||
return s.Cmd
|
||||
}
|
||||
|
||||
func (*CmdOnlySegment) ByteSize() int32 {
|
||||
return 2 + 1 + 1 + 4 + 4 + 4
|
||||
}
|
||||
|
||||
func (s *CmdOnlySegment) Serialize(b []byte) {
|
||||
binary.BigEndian.PutUint16(b, s.Conv)
|
||||
b[2] = byte(s.Cmd)
|
||||
b[3] = byte(s.Option)
|
||||
binary.BigEndian.PutUint32(b[4:], s.SendingNext)
|
||||
binary.BigEndian.PutUint32(b[8:], s.ReceivingNext)
|
||||
binary.BigEndian.PutUint32(b[12:], s.PeerRTO)
|
||||
}
|
||||
|
||||
func (*CmdOnlySegment) Release() {}
|
||||
|
||||
func ReadSegment(buf []byte) (Segment, []byte) {
|
||||
if len(buf) < 4 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
conv := binary.BigEndian.Uint16(buf)
|
||||
buf = buf[2:]
|
||||
|
||||
cmd := Command(buf[0])
|
||||
opt := SegmentOption(buf[1])
|
||||
buf = buf[2:]
|
||||
|
||||
var seg Segment
|
||||
switch cmd {
|
||||
case CommandData:
|
||||
seg = NewDataSegment()
|
||||
case CommandACK:
|
||||
seg = NewAckSegment()
|
||||
default:
|
||||
seg = NewCmdOnlySegment()
|
||||
}
|
||||
|
||||
valid, extra := seg.parse(conv, cmd, opt, buf)
|
||||
if !valid {
|
||||
return nil, nil
|
||||
}
|
||||
return seg, extra
|
||||
}
|
107
transport/internet/kcp/segment_test.go
Normal file
107
transport/internet/kcp/segment_test.go
Normal file
|
@ -0,0 +1,107 @@
|
|||
package kcp_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
|
||||
. "github.com/xtls/xray-core/v1/transport/internet/kcp"
|
||||
)
|
||||
|
||||
func TestBadSegment(t *testing.T) {
|
||||
seg, buf := ReadSegment(nil)
|
||||
if seg != nil {
|
||||
t.Error("non-nil seg")
|
||||
}
|
||||
if len(buf) != 0 {
|
||||
t.Error("buf len: ", len(buf))
|
||||
}
|
||||
}
|
||||
|
||||
func TestDataSegment(t *testing.T) {
|
||||
seg := &DataSegment{
|
||||
Conv: 1,
|
||||
Timestamp: 3,
|
||||
Number: 4,
|
||||
SendingNext: 5,
|
||||
}
|
||||
seg.Data().Write([]byte{'a', 'b', 'c', 'd'})
|
||||
|
||||
nBytes := seg.ByteSize()
|
||||
bytes := make([]byte, nBytes)
|
||||
seg.Serialize(bytes)
|
||||
|
||||
iseg, _ := ReadSegment(bytes)
|
||||
seg2 := iseg.(*DataSegment)
|
||||
if r := cmp.Diff(seg2, seg, cmpopts.IgnoreUnexported(DataSegment{})); r != "" {
|
||||
t.Error(r)
|
||||
}
|
||||
if r := cmp.Diff(seg2.Data().Bytes(), seg.Data().Bytes()); r != "" {
|
||||
t.Error(r)
|
||||
}
|
||||
}
|
||||
|
||||
func Test1ByteDataSegment(t *testing.T) {
|
||||
seg := &DataSegment{
|
||||
Conv: 1,
|
||||
Timestamp: 3,
|
||||
Number: 4,
|
||||
SendingNext: 5,
|
||||
}
|
||||
seg.Data().WriteByte('a')
|
||||
|
||||
nBytes := seg.ByteSize()
|
||||
bytes := make([]byte, nBytes)
|
||||
seg.Serialize(bytes)
|
||||
|
||||
iseg, _ := ReadSegment(bytes)
|
||||
seg2 := iseg.(*DataSegment)
|
||||
if r := cmp.Diff(seg2, seg, cmpopts.IgnoreUnexported(DataSegment{})); r != "" {
|
||||
t.Error(r)
|
||||
}
|
||||
if r := cmp.Diff(seg2.Data().Bytes(), seg.Data().Bytes()); r != "" {
|
||||
t.Error(r)
|
||||
}
|
||||
}
|
||||
|
||||
func TestACKSegment(t *testing.T) {
|
||||
seg := &AckSegment{
|
||||
Conv: 1,
|
||||
ReceivingWindow: 2,
|
||||
ReceivingNext: 3,
|
||||
Timestamp: 10,
|
||||
NumberList: []uint32{1, 3, 5, 7, 9},
|
||||
}
|
||||
|
||||
nBytes := seg.ByteSize()
|
||||
bytes := make([]byte, nBytes)
|
||||
seg.Serialize(bytes)
|
||||
|
||||
iseg, _ := ReadSegment(bytes)
|
||||
seg2 := iseg.(*AckSegment)
|
||||
if r := cmp.Diff(seg2, seg); r != "" {
|
||||
t.Error(r)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCmdSegment(t *testing.T) {
|
||||
seg := &CmdOnlySegment{
|
||||
Conv: 1,
|
||||
Cmd: CommandPing,
|
||||
Option: SegmentOptionClose,
|
||||
SendingNext: 11,
|
||||
ReceivingNext: 13,
|
||||
PeerRTO: 15,
|
||||
}
|
||||
|
||||
nBytes := seg.ByteSize()
|
||||
bytes := make([]byte, nBytes)
|
||||
seg.Serialize(bytes)
|
||||
|
||||
iseg, _ := ReadSegment(bytes)
|
||||
seg2 := iseg.(*CmdOnlySegment)
|
||||
if r := cmp.Diff(seg2, seg); r != "" {
|
||||
t.Error(r)
|
||||
}
|
||||
}
|
366
transport/internet/kcp/sending.go
Normal file
366
transport/internet/kcp/sending.go
Normal file
|
@ -0,0 +1,366 @@
|
|||
// +build !confonly
|
||||
|
||||
package kcp
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"sync"
|
||||
|
||||
"github.com/xtls/xray-core/v1/common/buf"
|
||||
)
|
||||
|
||||
type SendingWindow struct {
|
||||
cache *list.List
|
||||
totalInFlightSize uint32
|
||||
writer SegmentWriter
|
||||
onPacketLoss func(uint32)
|
||||
}
|
||||
|
||||
func NewSendingWindow(writer SegmentWriter, onPacketLoss func(uint32)) *SendingWindow {
|
||||
window := &SendingWindow{
|
||||
cache: list.New(),
|
||||
writer: writer,
|
||||
onPacketLoss: onPacketLoss,
|
||||
}
|
||||
return window
|
||||
}
|
||||
|
||||
func (sw *SendingWindow) Release() {
|
||||
if sw == nil {
|
||||
return
|
||||
}
|
||||
for sw.cache.Len() > 0 {
|
||||
seg := sw.cache.Front().Value.(*DataSegment)
|
||||
seg.Release()
|
||||
sw.cache.Remove(sw.cache.Front())
|
||||
}
|
||||
}
|
||||
|
||||
func (sw *SendingWindow) Len() uint32 {
|
||||
return uint32(sw.cache.Len())
|
||||
}
|
||||
|
||||
func (sw *SendingWindow) IsEmpty() bool {
|
||||
return sw.cache.Len() == 0
|
||||
}
|
||||
|
||||
func (sw *SendingWindow) Push(number uint32, b *buf.Buffer) {
|
||||
seg := NewDataSegment()
|
||||
seg.Number = number
|
||||
seg.payload = b
|
||||
|
||||
sw.cache.PushBack(seg)
|
||||
}
|
||||
|
||||
func (sw *SendingWindow) FirstNumber() uint32 {
|
||||
return sw.cache.Front().Value.(*DataSegment).Number
|
||||
}
|
||||
|
||||
func (sw *SendingWindow) Clear(una uint32) {
|
||||
for !sw.IsEmpty() {
|
||||
seg := sw.cache.Front().Value.(*DataSegment)
|
||||
if seg.Number >= una {
|
||||
break
|
||||
}
|
||||
seg.Release()
|
||||
sw.cache.Remove(sw.cache.Front())
|
||||
}
|
||||
}
|
||||
|
||||
func (sw *SendingWindow) HandleFastAck(number uint32, rto uint32) {
|
||||
if sw.IsEmpty() {
|
||||
return
|
||||
}
|
||||
|
||||
sw.Visit(func(seg *DataSegment) bool {
|
||||
if number == seg.Number || number-seg.Number > 0x7FFFFFFF {
|
||||
return false
|
||||
}
|
||||
|
||||
if seg.transmit > 0 && seg.timeout > rto/3 {
|
||||
seg.timeout -= rto / 3
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
func (sw *SendingWindow) Visit(visitor func(seg *DataSegment) bool) {
|
||||
if sw.IsEmpty() {
|
||||
return
|
||||
}
|
||||
|
||||
for e := sw.cache.Front(); e != nil; e = e.Next() {
|
||||
seg := e.Value.(*DataSegment)
|
||||
if !visitor(seg) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (sw *SendingWindow) Flush(current uint32, rto uint32, maxInFlightSize uint32) {
|
||||
if sw.IsEmpty() {
|
||||
return
|
||||
}
|
||||
|
||||
var lost uint32
|
||||
var inFlightSize uint32
|
||||
|
||||
sw.Visit(func(segment *DataSegment) bool {
|
||||
if current-segment.timeout >= 0x7FFFFFFF {
|
||||
return true
|
||||
}
|
||||
if segment.transmit == 0 {
|
||||
// First time
|
||||
sw.totalInFlightSize++
|
||||
} else {
|
||||
lost++
|
||||
}
|
||||
segment.timeout = current + rto
|
||||
|
||||
segment.Timestamp = current
|
||||
segment.transmit++
|
||||
sw.writer.Write(segment)
|
||||
inFlightSize++
|
||||
return inFlightSize < maxInFlightSize
|
||||
})
|
||||
|
||||
if sw.onPacketLoss != nil && inFlightSize > 0 && sw.totalInFlightSize != 0 {
|
||||
rate := lost * 100 / sw.totalInFlightSize
|
||||
sw.onPacketLoss(rate)
|
||||
}
|
||||
}
|
||||
|
||||
func (sw *SendingWindow) Remove(number uint32) bool {
|
||||
if sw.IsEmpty() {
|
||||
return false
|
||||
}
|
||||
|
||||
for e := sw.cache.Front(); e != nil; e = e.Next() {
|
||||
seg := e.Value.(*DataSegment)
|
||||
if seg.Number > number {
|
||||
return false
|
||||
} else if seg.Number == number {
|
||||
if sw.totalInFlightSize > 0 {
|
||||
sw.totalInFlightSize--
|
||||
}
|
||||
seg.Release()
|
||||
sw.cache.Remove(e)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
type SendingWorker struct {
|
||||
sync.RWMutex
|
||||
conn *Connection
|
||||
window *SendingWindow
|
||||
firstUnacknowledged uint32
|
||||
nextNumber uint32
|
||||
remoteNextNumber uint32
|
||||
controlWindow uint32
|
||||
fastResend uint32
|
||||
windowSize uint32
|
||||
firstUnacknowledgedUpdated bool
|
||||
closed bool
|
||||
}
|
||||
|
||||
func NewSendingWorker(kcp *Connection) *SendingWorker {
|
||||
worker := &SendingWorker{
|
||||
conn: kcp,
|
||||
fastResend: 2,
|
||||
remoteNextNumber: 32,
|
||||
controlWindow: kcp.Config.GetSendingInFlightSize(),
|
||||
windowSize: kcp.Config.GetSendingBufferSize(),
|
||||
}
|
||||
worker.window = NewSendingWindow(worker, worker.OnPacketLoss)
|
||||
return worker
|
||||
}
|
||||
|
||||
func (w *SendingWorker) Release() {
|
||||
w.Lock()
|
||||
w.window.Release()
|
||||
w.closed = true
|
||||
w.Unlock()
|
||||
}
|
||||
|
||||
func (w *SendingWorker) ProcessReceivingNext(nextNumber uint32) {
|
||||
w.Lock()
|
||||
defer w.Unlock()
|
||||
|
||||
w.ProcessReceivingNextWithoutLock(nextNumber)
|
||||
}
|
||||
|
||||
func (w *SendingWorker) ProcessReceivingNextWithoutLock(nextNumber uint32) {
|
||||
w.window.Clear(nextNumber)
|
||||
w.FindFirstUnacknowledged()
|
||||
}
|
||||
|
||||
func (w *SendingWorker) FindFirstUnacknowledged() {
|
||||
first := w.firstUnacknowledged
|
||||
if !w.window.IsEmpty() {
|
||||
w.firstUnacknowledged = w.window.FirstNumber()
|
||||
} else {
|
||||
w.firstUnacknowledged = w.nextNumber
|
||||
}
|
||||
if first != w.firstUnacknowledged {
|
||||
w.firstUnacknowledgedUpdated = true
|
||||
}
|
||||
}
|
||||
|
||||
func (w *SendingWorker) processAck(number uint32) bool {
|
||||
// number < v.firstUnacknowledged || number >= v.nextNumber
|
||||
if number-w.firstUnacknowledged > 0x7FFFFFFF || number-w.nextNumber < 0x7FFFFFFF {
|
||||
return false
|
||||
}
|
||||
|
||||
removed := w.window.Remove(number)
|
||||
if removed {
|
||||
w.FindFirstUnacknowledged()
|
||||
}
|
||||
return removed
|
||||
}
|
||||
|
||||
func (w *SendingWorker) ProcessSegment(current uint32, seg *AckSegment, rto uint32) {
|
||||
defer seg.Release()
|
||||
|
||||
w.Lock()
|
||||
defer w.Unlock()
|
||||
|
||||
if w.closed {
|
||||
return
|
||||
}
|
||||
|
||||
if w.remoteNextNumber < seg.ReceivingWindow {
|
||||
w.remoteNextNumber = seg.ReceivingWindow
|
||||
}
|
||||
w.ProcessReceivingNextWithoutLock(seg.ReceivingNext)
|
||||
|
||||
if seg.IsEmpty() {
|
||||
return
|
||||
}
|
||||
|
||||
var maxack uint32
|
||||
var maxackRemoved bool
|
||||
for _, number := range seg.NumberList {
|
||||
removed := w.processAck(number)
|
||||
if maxack < number {
|
||||
maxack = number
|
||||
maxackRemoved = removed
|
||||
}
|
||||
}
|
||||
|
||||
if maxackRemoved {
|
||||
w.window.HandleFastAck(maxack, rto)
|
||||
if current-seg.Timestamp < 10000 {
|
||||
w.conn.roundTrip.Update(current-seg.Timestamp, current)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *SendingWorker) Push(b *buf.Buffer) bool {
|
||||
w.Lock()
|
||||
defer w.Unlock()
|
||||
|
||||
if w.closed {
|
||||
return false
|
||||
}
|
||||
|
||||
if w.window.Len() > w.windowSize {
|
||||
return false
|
||||
}
|
||||
|
||||
w.window.Push(w.nextNumber, b)
|
||||
w.nextNumber++
|
||||
return true
|
||||
}
|
||||
|
||||
func (w *SendingWorker) Write(seg Segment) error {
|
||||
dataSeg := seg.(*DataSegment)
|
||||
|
||||
dataSeg.Conv = w.conn.meta.Conversation
|
||||
dataSeg.SendingNext = w.firstUnacknowledged
|
||||
dataSeg.Option = 0
|
||||
if w.conn.State() == StateReadyToClose {
|
||||
dataSeg.Option = SegmentOptionClose
|
||||
}
|
||||
|
||||
return w.conn.output.Write(dataSeg)
|
||||
}
|
||||
|
||||
func (w *SendingWorker) OnPacketLoss(lossRate uint32) {
|
||||
if !w.conn.Config.Congestion || w.conn.roundTrip.Timeout() == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if lossRate >= 15 {
|
||||
w.controlWindow = 3 * w.controlWindow / 4
|
||||
} else if lossRate <= 5 {
|
||||
w.controlWindow += w.controlWindow / 4
|
||||
}
|
||||
if w.controlWindow < 16 {
|
||||
w.controlWindow = 16
|
||||
}
|
||||
if w.controlWindow > 2*w.conn.Config.GetSendingInFlightSize() {
|
||||
w.controlWindow = 2 * w.conn.Config.GetSendingInFlightSize()
|
||||
}
|
||||
}
|
||||
|
||||
func (w *SendingWorker) Flush(current uint32) {
|
||||
w.Lock()
|
||||
|
||||
if w.closed {
|
||||
w.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
cwnd := w.conn.Config.GetSendingInFlightSize()
|
||||
if cwnd > w.remoteNextNumber-w.firstUnacknowledged {
|
||||
cwnd = w.remoteNextNumber - w.firstUnacknowledged
|
||||
}
|
||||
if w.conn.Config.Congestion && cwnd > w.controlWindow {
|
||||
cwnd = w.controlWindow
|
||||
}
|
||||
|
||||
cwnd *= 20 // magic
|
||||
|
||||
if !w.window.IsEmpty() {
|
||||
w.window.Flush(current, w.conn.roundTrip.Timeout(), cwnd)
|
||||
w.firstUnacknowledgedUpdated = false
|
||||
}
|
||||
|
||||
updated := w.firstUnacknowledgedUpdated
|
||||
w.firstUnacknowledgedUpdated = false
|
||||
|
||||
w.Unlock()
|
||||
|
||||
if updated {
|
||||
w.conn.Ping(current, CommandPing)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *SendingWorker) CloseWrite() {
|
||||
w.Lock()
|
||||
defer w.Unlock()
|
||||
|
||||
w.window.Clear(0xFFFFFFFF)
|
||||
}
|
||||
|
||||
func (w *SendingWorker) IsEmpty() bool {
|
||||
w.RLock()
|
||||
defer w.RUnlock()
|
||||
|
||||
return w.window.IsEmpty()
|
||||
}
|
||||
|
||||
func (w *SendingWorker) UpdateNecessary() bool {
|
||||
return !w.IsEmpty()
|
||||
}
|
||||
|
||||
func (w *SendingWorker) FirstUnacknowledged() uint32 {
|
||||
w.RLock()
|
||||
defer w.RUnlock()
|
||||
|
||||
return w.firstUnacknowledged
|
||||
}
|
17
transport/internet/kcp/xor.go
Normal file
17
transport/internet/kcp/xor.go
Normal file
|
@ -0,0 +1,17 @@
|
|||
// +build !amd64
|
||||
|
||||
package kcp
|
||||
|
||||
// xorfwd performs XOR forwards in words, x[i] ^= x[i-4], i from 0 to len
|
||||
func xorfwd(x []byte) {
|
||||
for i := 4; i < len(x); i++ {
|
||||
x[i] ^= x[i-4]
|
||||
}
|
||||
}
|
||||
|
||||
// xorbkd performs XOR backwords in words, x[i] ^= x[i-4], i from len to 0
|
||||
func xorbkd(x []byte) {
|
||||
for i := len(x) - 1; i >= 4; i-- {
|
||||
x[i] ^= x[i-4]
|
||||
}
|
||||
}
|
7
transport/internet/kcp/xor_amd64.go
Normal file
7
transport/internet/kcp/xor_amd64.go
Normal file
|
@ -0,0 +1,7 @@
|
|||
package kcp
|
||||
|
||||
//go:noescape
|
||||
func xorfwd(x []byte)
|
||||
|
||||
//go:noescape
|
||||
func xorbkd(x []byte)
|
47
transport/internet/kcp/xor_amd64.s
Normal file
47
transport/internet/kcp/xor_amd64.s
Normal file
|
@ -0,0 +1,47 @@
|
|||
#include "textflag.h"
|
||||
|
||||
// func xorfwd(x []byte)
|
||||
TEXT ·xorfwd(SB),NOSPLIT,$0
|
||||
MOVQ x+0(FP), SI // x[i]
|
||||
MOVQ x_len+8(FP), CX // x.len
|
||||
MOVQ x+0(FP), DI
|
||||
ADDQ $4, DI // x[i+4]
|
||||
SUBQ $4, CX
|
||||
xorfwdloop:
|
||||
MOVL (SI), AX
|
||||
XORL AX, (DI)
|
||||
ADDQ $4, SI
|
||||
ADDQ $4, DI
|
||||
SUBQ $4, CX
|
||||
|
||||
CMPL CX, $0
|
||||
JE xorfwddone
|
||||
|
||||
JMP xorfwdloop
|
||||
xorfwddone:
|
||||
RET
|
||||
|
||||
// func xorbkd(x []byte)
|
||||
TEXT ·xorbkd(SB),NOSPLIT,$0
|
||||
MOVQ x+0(FP), SI
|
||||
MOVQ x_len+8(FP), CX // x.len
|
||||
MOVQ x+0(FP), DI
|
||||
ADDQ CX, SI // x[-8]
|
||||
SUBQ $8, SI
|
||||
ADDQ CX, DI // x[-4]
|
||||
SUBQ $4, DI
|
||||
SUBQ $4, CX
|
||||
xorbkdloop:
|
||||
MOVL (SI), AX
|
||||
XORL AX, (DI)
|
||||
SUBQ $4, SI
|
||||
SUBQ $4, DI
|
||||
SUBQ $4, CX
|
||||
|
||||
CMPL CX, $0
|
||||
JE xorbkddone
|
||||
|
||||
JMP xorbkdloop
|
||||
|
||||
xorbkddone:
|
||||
RET
|
Loading…
Add table
Add a link
Reference in a new issue