Forráskód Böngészése

Added tcp fragmentation for freedom outbound (#2021)

* Added tcp fragmentation for freedom outbound

* Added TCP_NODELAY to outbound sockopt

* Changed fragment parameters to accept ranges and changed strategy to use length

* Changed packetNumber to packets, supporting range.

* Refactored the freedom fragment logic

* Refine Write()

---------

Co-authored-by: RPRX <[email protected]>
sambali9 2 éve
szülő
commit
5f5ae37571

+ 94 - 4
infra/conf/freedom.go

@@ -2,6 +2,7 @@ package conf
 
 import (
 	"net"
+	"strconv"
 	"strings"
 
 	"github.com/golang/protobuf/proto"
@@ -11,10 +12,17 @@ import (
 )
 
 type FreedomConfig struct {
-	DomainStrategy string  `json:"domainStrategy"`
-	Timeout        *uint32 `json:"timeout"`
-	Redirect       string  `json:"redirect"`
-	UserLevel      uint32  `json:"userLevel"`
+	DomainStrategy string    `json:"domainStrategy"`
+	Timeout        *uint32   `json:"timeout"`
+	Redirect       string    `json:"redirect"`
+	UserLevel      uint32    `json:"userLevel"`
+	Fragment       *Fragment `json:"fragment"`
+}
+
+type Fragment struct {
+	Packets  string `json:"packets"`
+	Length   string `json:"length"`
+	Interval string `json:"interval"`
 }
 
 // Build implements Buildable
@@ -30,6 +38,88 @@ func (c *FreedomConfig) Build() (proto.Message, error) {
 		config.DomainStrategy = freedom.Config_USE_IP6
 	}
 
+	if c.Fragment != nil {
+		if len(c.Fragment.Interval) == 0 || len(c.Fragment.Length) == 0 {
+			return nil, newError("Invalid interval or length")
+		}
+		intervalMinMax := strings.Split(c.Fragment.Interval, "-")
+		var minInterval, maxInterval int64
+		var err, err2 error
+		if len(intervalMinMax) == 2 {
+			minInterval, err = strconv.ParseInt(intervalMinMax[0], 10, 64)
+			maxInterval, err2 = strconv.ParseInt(intervalMinMax[1], 10, 64)
+		} else {
+			minInterval, err = strconv.ParseInt(intervalMinMax[0], 10, 64)
+			maxInterval = minInterval
+		}
+		if err != nil {
+			return nil, newError("Invalid minimum interval: ", err).Base(err)
+		}
+		if err2 != nil {
+			return nil, newError("Invalid maximum interval: ", err2).Base(err2)
+		}
+
+		lengthMinMax := strings.Split(c.Fragment.Length, "-")
+		var minLength, maxLength int64
+		if len(lengthMinMax) == 2 {
+			minLength, err = strconv.ParseInt(lengthMinMax[0], 10, 64)
+			maxLength, err2 = strconv.ParseInt(lengthMinMax[1], 10, 64)
+
+		} else {
+			minLength, err = strconv.ParseInt(lengthMinMax[0], 10, 64)
+			maxLength = minLength
+		}
+		if err != nil {
+			return nil, newError("Invalid minimum length: ", err).Base(err)
+		}
+		if err2 != nil {
+			return nil, newError("Invalid maximum length: ", err2).Base(err2)
+		}
+
+		if minInterval > maxInterval {
+			minInterval, maxInterval = maxInterval, minInterval
+		}
+		if minLength > maxLength {
+			minLength, maxLength = maxLength, minLength
+		}
+
+		config.Fragment = &freedom.Fragment{
+			MinInterval: int32(minInterval),
+			MaxInterval: int32(maxInterval),
+			MinLength:   int32(minLength),
+			MaxLength:   int32(maxLength),
+		}
+
+		if len(c.Fragment.Packets) > 0 {
+			packetRange := strings.Split(c.Fragment.Packets, "-")
+			var startPacket, endPacket int64
+			if len(packetRange) == 2 {
+				startPacket, err = strconv.ParseInt(packetRange[0], 10, 64)
+				endPacket, err2 = strconv.ParseInt(packetRange[1], 10, 64)
+			} else {
+				startPacket, err = strconv.ParseInt(packetRange[0], 10, 64)
+				endPacket = startPacket
+			}
+			if err != nil {
+				return nil, newError("Invalid start packet: ", err).Base(err)
+			}
+			if err2 != nil {
+				return nil, newError("Invalid end packet: ", err2).Base(err2)
+			}
+			if startPacket > endPacket {
+				return nil, newError("Invalid packet range: ", c.Fragment.Packets)
+			}
+			if startPacket < 1 {
+				return nil, newError("Cannot start from packet 0")
+			}
+			config.Fragment.StartPacket = int32(startPacket)
+			config.Fragment.EndPacket = int32(endPacket)
+		} else {
+			config.Fragment.StartPacket = 0
+			config.Fragment.EndPacket = 0
+		}
+	}
+
 	if c.Timeout != nil {
 		config.Timeout = *c.Timeout
 	}

+ 4 - 2
infra/conf/transport_internet.go

@@ -617,7 +617,8 @@ type SocketConfig struct {
 	TCPKeepAliveIdle     int32       `json:"tcpKeepAliveIdle"`
 	TCPCongestion        string      `json:"tcpCongestion"`
 	TCPWindowClamp       int32       `json:"tcpWindowClamp"`
-	TCPMaxSeg	     int32	 `json:"tcpMaxSeg"`
+	TCPMaxSeg            int32       `json:"tcpMaxSeg"`
+	TcpNoDelay           bool        `json:"tcpNoDelay"`
 	TCPUserTimeout       int32       `json:"tcpUserTimeout"`
 	V6only               bool        `json:"v6only"`
 	Interface            string      `json:"interface"`
@@ -671,7 +672,8 @@ func (c *SocketConfig) Build() (*internet.SocketConfig, error) {
 		TcpKeepAliveIdle:     c.TCPKeepAliveIdle,
 		TcpCongestion:        c.TCPCongestion,
 		TcpWindowClamp:       c.TCPWindowClamp,
-		TcpMaxSeg:	      c.TCPMaxSeg,
+		TcpMaxSeg:            c.TCPMaxSeg,
+		TcpNoDelay:           c.TcpNoDelay,
 		TcpUserTimeout:       c.TCPUserTimeout,
 		V6Only:               c.V6only,
 		Interface:            c.Interface,

+ 167 - 41
proxy/freedom/config.pb.go

@@ -70,7 +70,7 @@ func (x Config_DomainStrategy) Number() protoreflect.EnumNumber {
 
 // Deprecated: Use Config_DomainStrategy.Descriptor instead.
 func (Config_DomainStrategy) EnumDescriptor() ([]byte, []int) {
-	return file_proxy_freedom_config_proto_rawDescGZIP(), []int{1, 0}
+	return file_proxy_freedom_config_proto_rawDescGZIP(), []int{2, 0}
 }
 
 type DestinationOverride struct {
@@ -120,6 +120,93 @@ func (x *DestinationOverride) GetServer() *protocol.ServerEndpoint {
 	return nil
 }
 
+type Fragment struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	MinInterval int32 `protobuf:"varint,1,opt,name=min_interval,json=minInterval,proto3" json:"min_interval,omitempty"`
+	MaxInterval int32 `protobuf:"varint,2,opt,name=max_interval,json=maxInterval,proto3" json:"max_interval,omitempty"`
+	MinLength   int32 `protobuf:"varint,3,opt,name=min_length,json=minLength,proto3" json:"min_length,omitempty"`
+	MaxLength   int32 `protobuf:"varint,4,opt,name=max_length,json=maxLength,proto3" json:"max_length,omitempty"`
+	StartPacket int32 `protobuf:"varint,5,opt,name=start_packet,json=startPacket,proto3" json:"start_packet,omitempty"`
+	EndPacket   int32 `protobuf:"varint,6,opt,name=end_packet,json=endPacket,proto3" json:"end_packet,omitempty"`
+}
+
+func (x *Fragment) Reset() {
+	*x = Fragment{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_proxy_freedom_config_proto_msgTypes[1]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *Fragment) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Fragment) ProtoMessage() {}
+
+func (x *Fragment) ProtoReflect() protoreflect.Message {
+	mi := &file_proxy_freedom_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 Fragment.ProtoReflect.Descriptor instead.
+func (*Fragment) Descriptor() ([]byte, []int) {
+	return file_proxy_freedom_config_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *Fragment) GetMinInterval() int32 {
+	if x != nil {
+		return x.MinInterval
+	}
+	return 0
+}
+
+func (x *Fragment) GetMaxInterval() int32 {
+	if x != nil {
+		return x.MaxInterval
+	}
+	return 0
+}
+
+func (x *Fragment) GetMinLength() int32 {
+	if x != nil {
+		return x.MinLength
+	}
+	return 0
+}
+
+func (x *Fragment) GetMaxLength() int32 {
+	if x != nil {
+		return x.MaxLength
+	}
+	return 0
+}
+
+func (x *Fragment) GetStartPacket() int32 {
+	if x != nil {
+		return x.StartPacket
+	}
+	return 0
+}
+
+func (x *Fragment) GetEndPacket() int32 {
+	if x != nil {
+		return x.EndPacket
+	}
+	return 0
+}
+
 type Config struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
@@ -130,12 +217,13 @@ type Config struct {
 	Timeout             uint32               `protobuf:"varint,2,opt,name=timeout,proto3" json:"timeout,omitempty"`
 	DestinationOverride *DestinationOverride `protobuf:"bytes,3,opt,name=destination_override,json=destinationOverride,proto3" json:"destination_override,omitempty"`
 	UserLevel           uint32               `protobuf:"varint,4,opt,name=user_level,json=userLevel,proto3" json:"user_level,omitempty"`
+	Fragment            *Fragment            `protobuf:"bytes,5,opt,name=fragment,proto3" json:"fragment,omitempty"`
 }
 
 func (x *Config) Reset() {
 	*x = Config{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_proxy_freedom_config_proto_msgTypes[1]
+		mi := &file_proxy_freedom_config_proto_msgTypes[2]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -148,7 +236,7 @@ func (x *Config) String() string {
 func (*Config) ProtoMessage() {}
 
 func (x *Config) ProtoReflect() protoreflect.Message {
-	mi := &file_proxy_freedom_config_proto_msgTypes[1]
+	mi := &file_proxy_freedom_config_proto_msgTypes[2]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -161,7 +249,7 @@ func (x *Config) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use Config.ProtoReflect.Descriptor instead.
 func (*Config) Descriptor() ([]byte, []int) {
-	return file_proxy_freedom_config_proto_rawDescGZIP(), []int{1}
+	return file_proxy_freedom_config_proto_rawDescGZIP(), []int{2}
 }
 
 func (x *Config) GetDomainStrategy() Config_DomainStrategy {
@@ -193,6 +281,13 @@ func (x *Config) GetUserLevel() uint32 {
 	return 0
 }
 
+func (x *Config) GetFragment() *Fragment {
+	if x != nil {
+		return x.Fragment
+	}
+	return nil
+}
+
 var File_proxy_freedom_config_proto protoreflect.FileDescriptor
 
 var file_proxy_freedom_config_proto_rawDesc = []byte{
@@ -206,33 +301,50 @@ var file_proxy_freedom_config_proto_rawDesc = []byte{
 	0x72, 0x76, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x78, 0x72, 0x61,
 	0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f,
 	0x6c, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74,
-	0x52, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x22, 0xb8, 0x02, 0x0a, 0x06, 0x43, 0x6f, 0x6e,
-	0x66, 0x69, 0x67, 0x12, 0x52, 0x0a, 0x0f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x73, 0x74,
-	0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x29, 0x2e, 0x78,
-	0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x66, 0x72, 0x65, 0x65, 0x64, 0x6f,
-	0x6d, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53,
-	0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x0e, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53,
-	0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x1c, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f,
-	0x75, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, 0x52, 0x07, 0x74, 0x69,
-	0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, 0x5a, 0x0a, 0x14, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61,
-	0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x18, 0x03, 0x20,
-	0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79,
-	0x2e, 0x66, 0x72, 0x65, 0x65, 0x64, 0x6f, 0x6d, 0x2e, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61,
-	0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x52, 0x13, 0x64, 0x65,
-	0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64,
-	0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18,
-	0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x75, 0x73, 0x65, 0x72, 0x4c, 0x65, 0x76, 0x65, 0x6c,
-	0x22, 0x41, 0x0a, 0x0e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65,
-	0x67, 0x79, 0x12, 0x09, 0x0a, 0x05, 0x41, 0x53, 0x5f, 0x49, 0x53, 0x10, 0x00, 0x12, 0x0a, 0x0a,
-	0x06, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x53, 0x45,
-	0x5f, 0x49, 0x50, 0x34, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50,
-	0x36, 0x10, 0x03, 0x42, 0x58, 0x0a, 0x16, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e,
-	0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x66, 0x72, 0x65, 0x65, 0x64, 0x6f, 0x6d, 0x50, 0x01, 0x5a,
-	0x27, 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, 0x70, 0x72, 0x6f, 0x78, 0x79,
-	0x2f, 0x66, 0x72, 0x65, 0x65, 0x64, 0x6f, 0x6d, 0xaa, 0x02, 0x12, 0x58, 0x72, 0x61, 0x79, 0x2e,
-	0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x46, 0x72, 0x65, 0x65, 0x64, 0x6f, 0x6d, 0x62, 0x06, 0x70,
-	0x72, 0x6f, 0x74, 0x6f, 0x33,
+	0x52, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x22, 0xd0, 0x01, 0x0a, 0x08, 0x46, 0x72, 0x61,
+	0x67, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x69, 0x6e, 0x5f, 0x69, 0x6e, 0x74,
+	0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x6d, 0x69, 0x6e,
+	0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x61, 0x78, 0x5f,
+	0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b,
+	0x6d, 0x61, 0x78, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x1d, 0x0a, 0x0a, 0x6d,
+	0x69, 0x6e, 0x5f, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52,
+	0x09, 0x6d, 0x69, 0x6e, 0x4c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x61,
+	0x78, 0x5f, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09,
+	0x6d, 0x61, 0x78, 0x4c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x74, 0x61,
+	0x72, 0x74, 0x5f, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52,
+	0x0b, 0x73, 0x74, 0x61, 0x72, 0x74, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x1d, 0x0a, 0x0a,
+	0x65, 0x6e, 0x64, 0x5f, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05,
+	0x52, 0x09, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x22, 0xf2, 0x02, 0x0a, 0x06,
+	0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x52, 0x0a, 0x0f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e,
+	0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32,
+	0x29, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x66, 0x72, 0x65,
+	0x65, 0x64, 0x6f, 0x6d, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x44, 0x6f, 0x6d, 0x61,
+	0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x0e, 0x64, 0x6f, 0x6d, 0x61,
+	0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x1c, 0x0a, 0x07, 0x74, 0x69,
+	0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x02, 0x18, 0x01, 0x52,
+	0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, 0x5a, 0x0a, 0x14, 0x64, 0x65, 0x73, 0x74,
+	0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65,
+	0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72,
+	0x6f, 0x78, 0x79, 0x2e, 0x66, 0x72, 0x65, 0x65, 0x64, 0x6f, 0x6d, 0x2e, 0x44, 0x65, 0x73, 0x74,
+	0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x52,
+	0x13, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x76, 0x65, 0x72,
+	0x72, 0x69, 0x64, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x6c, 0x65, 0x76,
+	0x65, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x75, 0x73, 0x65, 0x72, 0x4c, 0x65,
+	0x76, 0x65, 0x6c, 0x12, 0x38, 0x0a, 0x08, 0x66, 0x72, 0x61, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x18,
+	0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f,
+	0x78, 0x79, 0x2e, 0x66, 0x72, 0x65, 0x65, 0x64, 0x6f, 0x6d, 0x2e, 0x46, 0x72, 0x61, 0x67, 0x6d,
+	0x65, 0x6e, 0x74, 0x52, 0x08, 0x66, 0x72, 0x61, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x22, 0x41, 0x0a,
+	0x0e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12,
+	0x09, 0x0a, 0x05, 0x41, 0x53, 0x5f, 0x49, 0x53, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x55, 0x53,
+	0x45, 0x5f, 0x49, 0x50, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50,
+	0x34, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50, 0x36, 0x10, 0x03,
+	0x42, 0x58, 0x0a, 0x16, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f,
+	0x78, 0x79, 0x2e, 0x66, 0x72, 0x65, 0x65, 0x64, 0x6f, 0x6d, 0x50, 0x01, 0x5a, 0x27, 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, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x66, 0x72,
+	0x65, 0x65, 0x64, 0x6f, 0x6d, 0xaa, 0x02, 0x12, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x50, 0x72, 0x6f,
+	0x78, 0x79, 0x2e, 0x46, 0x72, 0x65, 0x65, 0x64, 0x6f, 0x6d, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
+	0x6f, 0x33,
 }
 
 var (
@@ -248,22 +360,24 @@ func file_proxy_freedom_config_proto_rawDescGZIP() []byte {
 }
 
 var file_proxy_freedom_config_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
-var file_proxy_freedom_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
+var file_proxy_freedom_config_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
 var file_proxy_freedom_config_proto_goTypes = []interface{}{
 	(Config_DomainStrategy)(0),      // 0: xray.proxy.freedom.Config.DomainStrategy
 	(*DestinationOverride)(nil),     // 1: xray.proxy.freedom.DestinationOverride
-	(*Config)(nil),                  // 2: xray.proxy.freedom.Config
-	(*protocol.ServerEndpoint)(nil), // 3: xray.common.protocol.ServerEndpoint
+	(*Fragment)(nil),                // 2: xray.proxy.freedom.Fragment
+	(*Config)(nil),                  // 3: xray.proxy.freedom.Config
+	(*protocol.ServerEndpoint)(nil), // 4: xray.common.protocol.ServerEndpoint
 }
 var file_proxy_freedom_config_proto_depIdxs = []int32{
-	3, // 0: xray.proxy.freedom.DestinationOverride.server:type_name -> xray.common.protocol.ServerEndpoint
+	4, // 0: xray.proxy.freedom.DestinationOverride.server:type_name -> xray.common.protocol.ServerEndpoint
 	0, // 1: xray.proxy.freedom.Config.domain_strategy:type_name -> xray.proxy.freedom.Config.DomainStrategy
 	1, // 2: xray.proxy.freedom.Config.destination_override:type_name -> xray.proxy.freedom.DestinationOverride
-	3, // [3:3] is the sub-list for method output_type
-	3, // [3:3] is the sub-list for method input_type
-	3, // [3:3] is the sub-list for extension type_name
-	3, // [3:3] is the sub-list for extension extendee
-	0, // [0:3] is the sub-list for field type_name
+	2, // 3: xray.proxy.freedom.Config.fragment:type_name -> xray.proxy.freedom.Fragment
+	4, // [4:4] is the sub-list for method output_type
+	4, // [4:4] is the sub-list for method input_type
+	4, // [4:4] is the sub-list for extension type_name
+	4, // [4:4] is the sub-list for extension extendee
+	0, // [0:4] is the sub-list for field type_name
 }
 
 func init() { file_proxy_freedom_config_proto_init() }
@@ -285,6 +399,18 @@ func file_proxy_freedom_config_proto_init() {
 			}
 		}
 		file_proxy_freedom_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*Fragment); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_proxy_freedom_config_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
 			switch v := v.(*Config); i {
 			case 0:
 				return &v.state
@@ -303,7 +429,7 @@ func file_proxy_freedom_config_proto_init() {
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
 			RawDescriptor: file_proxy_freedom_config_proto_rawDesc,
 			NumEnums:      1,
-			NumMessages:   2,
+			NumMessages:   3,
 			NumExtensions: 0,
 			NumServices:   0,
 		},

+ 10 - 0
proxy/freedom/config.proto

@@ -12,6 +12,15 @@ message DestinationOverride {
   xray.common.protocol.ServerEndpoint server = 1;
 }
 
+message Fragment {
+  int32 min_interval = 1;
+  int32 max_interval = 2;
+  int32 min_length = 3;
+  int32 max_length = 4;
+  int32 start_packet = 5;
+  int32 end_packet = 6;
+}
+
 message Config {
   enum DomainStrategy {
     AS_IS = 0;
@@ -23,4 +32,5 @@ message Config {
   uint32 timeout = 2 [deprecated = true];
   DestinationOverride destination_override = 3;
   uint32 user_level = 4;
+  Fragment fragment = 5;  
 }

+ 65 - 1
proxy/freedom/freedom.go

@@ -4,6 +4,9 @@ package freedom
 
 import (
 	"context"
+	"crypto/rand"
+	"io"
+	"math/big"
 	"time"
 
 	"github.com/xtls/xray-core/common"
@@ -169,7 +172,21 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
 
 		var writer buf.Writer
 		if destination.Network == net.Network_TCP {
-			writer = buf.NewWriter(conn)
+			if h.config.Fragment != nil {
+				writer = buf.NewWriter(
+					&FragmentWriter{
+						Writer:      conn,
+						minLength:   int(h.config.Fragment.MinLength),
+						maxLength:   int(h.config.Fragment.MaxLength),
+						minInterval: time.Duration(h.config.Fragment.MinInterval) * time.Millisecond,
+						maxInterval: time.Duration(h.config.Fragment.MaxInterval) * time.Millisecond,
+						startPacket: int(h.config.Fragment.StartPacket),
+						endPacket:   int(h.config.Fragment.EndPacket),
+						PacketCount: 0,
+					})
+			} else {
+				writer = buf.NewWriter(conn)
+			}
 		} else {
 			writer = NewPacketWriter(conn, h, ctx, UDPOverride)
 		}
@@ -324,3 +341,50 @@ func (w *PacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
 	}
 	return nil
 }
+
+type FragmentWriter struct {
+	io.Writer
+	minLength   int
+	maxLength   int
+	minInterval time.Duration
+	maxInterval time.Duration
+	startPacket int
+	endPacket   int
+	PacketCount int
+}
+
+func (w *FragmentWriter) Write(buf []byte) (int, error) {
+	w.PacketCount += 1
+	if (w.startPacket != 0 && (w.PacketCount < w.startPacket || w.PacketCount > w.endPacket)) || len(buf) <= w.minLength {
+		return w.Writer.Write(buf)
+	}
+
+	nTotal := 0
+	for {
+		randomBytesTo := int(randBetween(int64(w.minLength), int64(w.maxLength))) + nTotal
+		if randomBytesTo > len(buf) {
+			randomBytesTo = len(buf)
+		}
+		n, err := w.Writer.Write(buf[nTotal:randomBytesTo])
+		if err != nil {
+			return nTotal + n, err
+		}
+		nTotal += n
+
+		if nTotal >= len(buf) {
+			return nTotal, nil
+		}
+
+		randomInterval := randBetween(int64(w.minInterval), int64(w.maxInterval))
+		time.Sleep(time.Duration(randomInterval))
+	}
+}
+
+// stolen from github.com/xtls/xray-core/transport/internet/reality
+func randBetween(left int64, right int64) int64 {
+	if left == right {
+		return left
+	}
+	bigInt, _ := rand.Int(rand.Reader, big.NewInt(right-left))
+	return left + bigInt.Int64()
+}

+ 33 - 23
transport/internet/config.pb.go

@@ -1,7 +1,7 @@
 // Code generated by protoc-gen-go. DO NOT EDIT.
 // versions:
 // 	protoc-gen-go v1.28.1
-// 	protoc        v4.22.0
+// 	protoc        v3.12.4
 // source: transport/internet/config.proto
 
 package internet
@@ -430,6 +430,7 @@ type SocketConfig struct {
 	TcpWindowClamp             int32          `protobuf:"varint,15,opt,name=tcp_window_clamp,json=tcpWindowClamp,proto3" json:"tcp_window_clamp,omitempty"`
 	TcpUserTimeout             int32          `protobuf:"varint,16,opt,name=tcp_user_timeout,json=tcpUserTimeout,proto3" json:"tcp_user_timeout,omitempty"`
 	TcpMaxSeg                  int32          `protobuf:"varint,17,opt,name=tcp_max_seg,json=tcpMaxSeg,proto3" json:"tcp_max_seg,omitempty"`
+	TcpNoDelay                 bool           `protobuf:"varint,18,opt,name=tcp_no_delay,json=tcpNoDelay,proto3" json:"tcp_no_delay,omitempty"`
 }
 
 func (x *SocketConfig) Reset() {
@@ -583,6 +584,13 @@ func (x *SocketConfig) GetTcpMaxSeg() int32 {
 	return 0
 }
 
+func (x *SocketConfig) GetTcpNoDelay() bool {
+	if x != nil {
+		return x.TcpNoDelay
+	}
+	return false
+}
+
 var File_transport_internet_config_proto protoreflect.FileDescriptor
 
 var file_transport_internet_config_proto_rawDesc = []byte{
@@ -635,7 +643,7 @@ var file_transport_internet_config_proto_rawDesc = []byte{
 	0x12, 0x30, 0x0a, 0x13, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x4c, 0x61, 0x79,
 	0x65, 0x72, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x74,
 	0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x50, 0x72, 0x6f,
-	0x78, 0x79, 0x22, 0x92, 0x06, 0x0a, 0x0c, 0x53, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x6e,
+	0x78, 0x79, 0x22, 0xb4, 0x06, 0x0a, 0x0c, 0x53, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x6e,
 	0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x61, 0x72, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28,
 	0x05, 0x52, 0x04, 0x6d, 0x61, 0x72, 0x6b, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x66, 0x6f, 0x18, 0x02,
 	0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x74, 0x66, 0x6f, 0x12, 0x48, 0x0a, 0x06, 0x74, 0x70, 0x72,
@@ -681,27 +689,29 @@ var file_transport_internet_config_proto_rawDesc = []byte{
 	0x18, 0x10, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x74, 0x63, 0x70, 0x55, 0x73, 0x65, 0x72, 0x54,
 	0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, 0x1e, 0x0a, 0x0b, 0x74, 0x63, 0x70, 0x5f, 0x6d, 0x61,
 	0x78, 0x5f, 0x73, 0x65, 0x67, 0x18, 0x11, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x74, 0x63, 0x70,
-	0x4d, 0x61, 0x78, 0x53, 0x65, 0x67, 0x22, 0x2f, 0x0a, 0x0a, 0x54, 0x50, 0x72, 0x6f, 0x78, 0x79,
-	0x4d, 0x6f, 0x64, 0x65, 0x12, 0x07, 0x0a, 0x03, 0x4f, 0x66, 0x66, 0x10, 0x00, 0x12, 0x0a, 0x0a,
-	0x06, 0x54, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x52, 0x65, 0x64,
-	0x69, 0x72, 0x65, 0x63, 0x74, 0x10, 0x02, 0x2a, 0x5a, 0x0a, 0x11, 0x54, 0x72, 0x61, 0x6e, 0x73,
-	0x70, 0x6f, 0x72, 0x74, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x07, 0x0a, 0x03,
-	0x54, 0x43, 0x50, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x55, 0x44, 0x50, 0x10, 0x01, 0x12, 0x08,
-	0x0a, 0x04, 0x4d, 0x4b, 0x43, 0x50, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x57, 0x65, 0x62, 0x53,
-	0x6f, 0x63, 0x6b, 0x65, 0x74, 0x10, 0x03, 0x12, 0x08, 0x0a, 0x04, 0x48, 0x54, 0x54, 0x50, 0x10,
-	0x04, 0x12, 0x10, 0x0a, 0x0c, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x6f, 0x63, 0x6b, 0x65,
-	0x74, 0x10, 0x05, 0x2a, 0x41, 0x0a, 0x0e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72,
-	0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x09, 0x0a, 0x05, 0x41, 0x53, 0x5f, 0x49, 0x53, 0x10, 0x00,
-	0x12, 0x0a, 0x0a, 0x06, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07,
-	0x55, 0x53, 0x45, 0x5f, 0x49, 0x50, 0x34, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x53, 0x45,
-	0x5f, 0x49, 0x50, 0x36, 0x10, 0x03, 0x42, 0x67, 0x0a, 0x1b, 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, 0x50, 0x01, 0x5a, 0x2c, 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, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74,
-	0x65, 0x72, 0x6e, 0x65, 0x74, 0xaa, 0x02, 0x17, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x54, 0x72, 0x61,
-	0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x62,
-	0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+	0x4d, 0x61, 0x78, 0x53, 0x65, 0x67, 0x12, 0x20, 0x0a, 0x0c, 0x74, 0x63, 0x70, 0x5f, 0x6e, 0x6f,
+	0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x18, 0x12, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x74, 0x63,
+	0x70, 0x4e, 0x6f, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x22, 0x2f, 0x0a, 0x0a, 0x54, 0x50, 0x72, 0x6f,
+	0x78, 0x79, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x07, 0x0a, 0x03, 0x4f, 0x66, 0x66, 0x10, 0x00, 0x12,
+	0x0a, 0x0a, 0x06, 0x54, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x52,
+	0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x10, 0x02, 0x2a, 0x5a, 0x0a, 0x11, 0x54, 0x72, 0x61,
+	0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x07,
+	0x0a, 0x03, 0x54, 0x43, 0x50, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x55, 0x44, 0x50, 0x10, 0x01,
+	0x12, 0x08, 0x0a, 0x04, 0x4d, 0x4b, 0x43, 0x50, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x57, 0x65,
+	0x62, 0x53, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x10, 0x03, 0x12, 0x08, 0x0a, 0x04, 0x48, 0x54, 0x54,
+	0x50, 0x10, 0x04, 0x12, 0x10, 0x0a, 0x0c, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x6f, 0x63,
+	0x6b, 0x65, 0x74, 0x10, 0x05, 0x2a, 0x41, 0x0a, 0x0e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53,
+	0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x09, 0x0a, 0x05, 0x41, 0x53, 0x5f, 0x49, 0x53,
+	0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50, 0x10, 0x01, 0x12, 0x0b,
+	0x0a, 0x07, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50, 0x34, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x55,
+	0x53, 0x45, 0x5f, 0x49, 0x50, 0x36, 0x10, 0x03, 0x42, 0x67, 0x0a, 0x1b, 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, 0x50, 0x01, 0x5a, 0x2c, 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, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69,
+	0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0xaa, 0x02, 0x17, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x54,
+	0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65,
+	0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
 }
 
 var (

+ 2 - 0
transport/internet/config.proto

@@ -108,4 +108,6 @@ message SocketConfig {
   int32 tcp_user_timeout = 16;
 
   int32 tcp_max_seg = 17;
+
+  bool tcp_no_delay = 18;
 }

+ 6 - 0
transport/internet/sockopt_darwin.go

@@ -126,6 +126,12 @@ func applyOutboundSocketOptions(network string, address string, fd uintptr, conf
 				return newError("failed to unset SO_KEEPALIVE", err)
 			}
 		}
+
+		if config.TcpNoDelay {
+			if err := unix.SetsockoptInt(int(fd), unix.IPPROTO_TCP, unix.TCP_NODELAY, 1); err != nil {
+				return newError("failed to set TCP_NODELAY", err)
+			}
+		}
 	}
 
 	return nil

+ 6 - 0
transport/internet/sockopt_linux.go

@@ -101,6 +101,12 @@ func applyOutboundSocketOptions(network string, address string, fd uintptr, conf
 			}
 		}
 
+		if config.TcpNoDelay {
+			if err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, unix.TCP_NODELAY, 1); err != nil {
+				return newError("failed to set TCP_NODELAY", err)
+			}
+		}
+
 	}
 
 	if config.Tproxy.IsEnabled() {

+ 5 - 0
transport/internet/sockopt_windows.go

@@ -34,6 +34,11 @@ func applyOutboundSocketOptions(network string, address string, fd uintptr, conf
 				return newError("failed to unset SO_KEEPALIVE", err)
 			}
 		}
+		if config.TcpNoDelay {
+			if err := syscall.SetsockoptInt(syscall.Handle(fd), syscall.IPPROTO_TCP, syscall.TCP_NODELAY, 1); err != nil {
+				return newError("failed to set TCP_NODELAY", err)
+			}
+		}
 	}
 
 	return nil