mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-11-14 14:09:42 +00:00
Freedom: Add custom firewall replies
This commit is contained in:
+41
-9
@@ -46,13 +46,37 @@ type Noise struct {
|
||||
}
|
||||
|
||||
type FreedomFirewallConfig struct {
|
||||
Action string `json:"action"`
|
||||
Network *NetworkList `json:"network"`
|
||||
Port *PortList `json:"port"`
|
||||
IP *StringList `json:"ip"`
|
||||
Delay *Int32Range `json:"delay"`
|
||||
LogLevel string `json:"logLevel"`
|
||||
BlockDelay *Int32Range `json:"blockDelay"`
|
||||
Action string `json:"action"`
|
||||
Network *NetworkList `json:"network"`
|
||||
Port *PortList `json:"port"`
|
||||
IP *StringList `json:"ip"`
|
||||
Delay *Int32Range `json:"delay"`
|
||||
LogLevel string `json:"logLevel"`
|
||||
Reply *FreedomReplyConfig `json:"reply"`
|
||||
BlockDelay *Int32Range `json:"blockDelay"`
|
||||
}
|
||||
|
||||
type FreedomReplyConfig struct {
|
||||
Type string `json:"type"`
|
||||
CustomResponseData string `json:"customResponseData"`
|
||||
}
|
||||
|
||||
func (c *FreedomReplyConfig) Build() (*freedom.Reply, error) {
|
||||
replyType := strings.ToLower(c.Type)
|
||||
switch replyType {
|
||||
case "none", "":
|
||||
return &freedom.Reply{Type: "none"}, nil
|
||||
case "http":
|
||||
return &freedom.Reply{Type: "http"}, nil
|
||||
case "custom":
|
||||
data, err := base64.StdEncoding.DecodeString(c.CustomResponseData)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to decode custom reply data: " + err.Error())
|
||||
}
|
||||
return &freedom.Reply{Type: "custom", CustomResponseData: data}, nil
|
||||
default:
|
||||
return nil, errors.New("unknown firewall reply: " + replyType)
|
||||
}
|
||||
}
|
||||
|
||||
// Build implements Buildable
|
||||
@@ -296,8 +320,8 @@ func (c *FreedomFirewallConfig) Build() (*freedom.FirewallConfig, error) {
|
||||
}
|
||||
|
||||
if rule.Action != freedom.RuleAction_Block {
|
||||
if c.Delay != nil || c.LogLevel != "" {
|
||||
return nil, errors.New("delay and logLevel are only supported for firewall action block")
|
||||
if c.Delay != nil || c.LogLevel != "" || c.Reply != nil {
|
||||
return nil, errors.New("delay, logLevel and reply are only supported for firewall action block")
|
||||
}
|
||||
return rule, nil
|
||||
}
|
||||
@@ -309,6 +333,14 @@ func (c *FreedomFirewallConfig) Build() (*freedom.FirewallConfig, error) {
|
||||
}
|
||||
}
|
||||
|
||||
if c.Reply != nil {
|
||||
reply, err := c.Reply.Build()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rule.Reply = reply
|
||||
}
|
||||
|
||||
switch strings.ToLower(c.LogLevel) {
|
||||
case "", "info":
|
||||
rule.LogLevel = log.Severity_Info.Enum()
|
||||
|
||||
@@ -48,7 +48,11 @@ func TestFreedomConfig(t *testing.T) {
|
||||
"port": "53,443",
|
||||
"ip": ["10.0.0.0/8", "2001:db8::/32"],
|
||||
"delay": "30-60",
|
||||
"logLevel": "debug"
|
||||
"logLevel": "debug",
|
||||
"reply": {
|
||||
"type": "custom",
|
||||
"customResponseData": "Y3VzdG9tIHJlc3BvbnNl"
|
||||
}
|
||||
}, {
|
||||
"action": "allow",
|
||||
"network": ["udp"]
|
||||
@@ -90,6 +94,10 @@ func TestFreedomConfig(t *testing.T) {
|
||||
},
|
||||
Delay: &freedom.Range{Min: 30, Max: 60},
|
||||
LogLevel: log.Severity_Debug.Enum(),
|
||||
Reply: &freedom.Reply{
|
||||
Type: "custom",
|
||||
CustomResponseData: []byte("custom response"),
|
||||
},
|
||||
},
|
||||
{
|
||||
Action: freedom.RuleAction_Allow,
|
||||
|
||||
+100
-34
@@ -352,6 +352,58 @@ func (x *Range) GetMax() uint64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
type Reply struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"`
|
||||
CustomResponseData []byte `protobuf:"bytes,2,opt,name=custom_response_data,json=customResponseData,proto3" json:"custom_response_data,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *Reply) Reset() {
|
||||
*x = Reply{}
|
||||
mi := &file_proxy_freedom_config_proto_msgTypes[4]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *Reply) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Reply) ProtoMessage() {}
|
||||
|
||||
func (x *Reply) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proxy_freedom_config_proto_msgTypes[4]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Reply.ProtoReflect.Descriptor instead.
|
||||
func (*Reply) Descriptor() ([]byte, []int) {
|
||||
return file_proxy_freedom_config_proto_rawDescGZIP(), []int{4}
|
||||
}
|
||||
|
||||
func (x *Reply) GetType() string {
|
||||
if x != nil {
|
||||
return x.Type
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Reply) GetCustomResponseData() []byte {
|
||||
if x != nil {
|
||||
return x.CustomResponseData
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type FirewallConfig struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Action RuleAction `protobuf:"varint,1,opt,name=action,proto3,enum=xray.proxy.freedom.RuleAction" json:"action,omitempty"`
|
||||
@@ -360,13 +412,14 @@ type FirewallConfig struct {
|
||||
Ip []*geodata.IPRule `protobuf:"bytes,4,rep,name=ip,proto3" json:"ip,omitempty"`
|
||||
Delay *Range `protobuf:"bytes,5,opt,name=delay,proto3" json:"delay,omitempty"`
|
||||
LogLevel *log.Severity `protobuf:"varint,6,opt,name=log_level,json=logLevel,proto3,enum=xray.common.log.Severity,oneof" json:"log_level,omitempty"`
|
||||
Reply *Reply `protobuf:"bytes,7,opt,name=reply,proto3" json:"reply,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *FirewallConfig) Reset() {
|
||||
*x = FirewallConfig{}
|
||||
mi := &file_proxy_freedom_config_proto_msgTypes[4]
|
||||
mi := &file_proxy_freedom_config_proto_msgTypes[5]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -378,7 +431,7 @@ func (x *FirewallConfig) String() string {
|
||||
func (*FirewallConfig) ProtoMessage() {}
|
||||
|
||||
func (x *FirewallConfig) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proxy_freedom_config_proto_msgTypes[4]
|
||||
mi := &file_proxy_freedom_config_proto_msgTypes[5]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -391,7 +444,7 @@ func (x *FirewallConfig) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use FirewallConfig.ProtoReflect.Descriptor instead.
|
||||
func (*FirewallConfig) Descriptor() ([]byte, []int) {
|
||||
return file_proxy_freedom_config_proto_rawDescGZIP(), []int{4}
|
||||
return file_proxy_freedom_config_proto_rawDescGZIP(), []int{5}
|
||||
}
|
||||
|
||||
func (x *FirewallConfig) GetAction() RuleAction {
|
||||
@@ -436,6 +489,13 @@ func (x *FirewallConfig) GetLogLevel() log.Severity {
|
||||
return log.Severity(0)
|
||||
}
|
||||
|
||||
func (x *FirewallConfig) GetReply() *Reply {
|
||||
if x != nil {
|
||||
return x.Reply
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
DomainStrategy internet.DomainStrategy `protobuf:"varint,1,opt,name=domain_strategy,json=domainStrategy,proto3,enum=xray.transport.internet.DomainStrategy" json:"domain_strategy,omitempty"`
|
||||
@@ -450,7 +510,7 @@ type Config struct {
|
||||
|
||||
func (x *Config) Reset() {
|
||||
*x = Config{}
|
||||
mi := &file_proxy_freedom_config_proto_msgTypes[5]
|
||||
mi := &file_proxy_freedom_config_proto_msgTypes[6]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -462,7 +522,7 @@ func (x *Config) String() string {
|
||||
func (*Config) ProtoMessage() {}
|
||||
|
||||
func (x *Config) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proxy_freedom_config_proto_msgTypes[5]
|
||||
mi := &file_proxy_freedom_config_proto_msgTypes[6]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -475,7 +535,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{5}
|
||||
return file_proxy_freedom_config_proto_rawDescGZIP(), []int{6}
|
||||
}
|
||||
|
||||
func (x *Config) GetDomainStrategy() internet.DomainStrategy {
|
||||
@@ -550,14 +610,18 @@ const file_proxy_freedom_config_proto_rawDesc = "" +
|
||||
"\bapply_to\x18\x06 \x01(\tR\aapplyTo\"+\n" +
|
||||
"\x05Range\x12\x10\n" +
|
||||
"\x03min\x18\x01 \x01(\x04R\x03min\x12\x10\n" +
|
||||
"\x03max\x18\x02 \x01(\x04R\x03max\"\xdf\x02\n" +
|
||||
"\x03max\x18\x02 \x01(\x04R\x03max\"M\n" +
|
||||
"\x05Reply\x12\x12\n" +
|
||||
"\x04type\x18\x01 \x01(\tR\x04type\x120\n" +
|
||||
"\x14custom_response_data\x18\x02 \x01(\fR\x12customResponseData\"\x90\x03\n" +
|
||||
"\x0eFirewallConfig\x126\n" +
|
||||
"\x06action\x18\x01 \x01(\x0e2\x1e.xray.proxy.freedom.RuleActionR\x06action\x124\n" +
|
||||
"\bnetworks\x18\x02 \x03(\x0e2\x18.xray.common.net.NetworkR\bnetworks\x126\n" +
|
||||
"\tport_list\x18\x03 \x01(\v2\x19.xray.common.net.PortListR\bportList\x12+\n" +
|
||||
"\x02ip\x18\x04 \x03(\v2\x1b.xray.common.geodata.IPRuleR\x02ip\x12/\n" +
|
||||
"\x05delay\x18\x05 \x01(\v2\x19.xray.proxy.freedom.RangeR\x05delay\x12;\n" +
|
||||
"\tlog_level\x18\x06 \x01(\x0e2\x19.xray.common.log.SeverityH\x00R\blogLevel\x88\x01\x01B\f\n" +
|
||||
"\tlog_level\x18\x06 \x01(\x0e2\x19.xray.common.log.SeverityH\x00R\blogLevel\x88\x01\x01\x12/\n" +
|
||||
"\x05reply\x18\a \x01(\v2\x19.xray.proxy.freedom.ReplyR\x05replyB\f\n" +
|
||||
"\n" +
|
||||
"_log_level\"\x88\x03\n" +
|
||||
"\x06Config\x12P\n" +
|
||||
@@ -587,40 +651,42 @@ 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, 6)
|
||||
var file_proxy_freedom_config_proto_msgTypes = make([]protoimpl.MessageInfo, 7)
|
||||
var file_proxy_freedom_config_proto_goTypes = []any{
|
||||
(RuleAction)(0), // 0: xray.proxy.freedom.RuleAction
|
||||
(*DestinationOverride)(nil), // 1: xray.proxy.freedom.DestinationOverride
|
||||
(*Fragment)(nil), // 2: xray.proxy.freedom.Fragment
|
||||
(*Noise)(nil), // 3: xray.proxy.freedom.Noise
|
||||
(*Range)(nil), // 4: xray.proxy.freedom.Range
|
||||
(*FirewallConfig)(nil), // 5: xray.proxy.freedom.FirewallConfig
|
||||
(*Config)(nil), // 6: xray.proxy.freedom.Config
|
||||
(*protocol.ServerEndpoint)(nil), // 7: xray.common.protocol.ServerEndpoint
|
||||
(net.Network)(0), // 8: xray.common.net.Network
|
||||
(*net.PortList)(nil), // 9: xray.common.net.PortList
|
||||
(*geodata.IPRule)(nil), // 10: xray.common.geodata.IPRule
|
||||
(log.Severity)(0), // 11: xray.common.log.Severity
|
||||
(internet.DomainStrategy)(0), // 12: xray.transport.internet.DomainStrategy
|
||||
(*Reply)(nil), // 5: xray.proxy.freedom.Reply
|
||||
(*FirewallConfig)(nil), // 6: xray.proxy.freedom.FirewallConfig
|
||||
(*Config)(nil), // 7: xray.proxy.freedom.Config
|
||||
(*protocol.ServerEndpoint)(nil), // 8: xray.common.protocol.ServerEndpoint
|
||||
(net.Network)(0), // 9: xray.common.net.Network
|
||||
(*net.PortList)(nil), // 10: xray.common.net.PortList
|
||||
(*geodata.IPRule)(nil), // 11: xray.common.geodata.IPRule
|
||||
(log.Severity)(0), // 12: xray.common.log.Severity
|
||||
(internet.DomainStrategy)(0), // 13: xray.transport.internet.DomainStrategy
|
||||
}
|
||||
var file_proxy_freedom_config_proto_depIdxs = []int32{
|
||||
7, // 0: xray.proxy.freedom.DestinationOverride.server:type_name -> xray.common.protocol.ServerEndpoint
|
||||
8, // 0: xray.proxy.freedom.DestinationOverride.server:type_name -> xray.common.protocol.ServerEndpoint
|
||||
0, // 1: xray.proxy.freedom.FirewallConfig.action:type_name -> xray.proxy.freedom.RuleAction
|
||||
8, // 2: xray.proxy.freedom.FirewallConfig.networks:type_name -> xray.common.net.Network
|
||||
9, // 3: xray.proxy.freedom.FirewallConfig.port_list:type_name -> xray.common.net.PortList
|
||||
10, // 4: xray.proxy.freedom.FirewallConfig.ip:type_name -> xray.common.geodata.IPRule
|
||||
9, // 2: xray.proxy.freedom.FirewallConfig.networks:type_name -> xray.common.net.Network
|
||||
10, // 3: xray.proxy.freedom.FirewallConfig.port_list:type_name -> xray.common.net.PortList
|
||||
11, // 4: xray.proxy.freedom.FirewallConfig.ip:type_name -> xray.common.geodata.IPRule
|
||||
4, // 5: xray.proxy.freedom.FirewallConfig.delay:type_name -> xray.proxy.freedom.Range
|
||||
11, // 6: xray.proxy.freedom.FirewallConfig.log_level:type_name -> xray.common.log.Severity
|
||||
12, // 7: xray.proxy.freedom.Config.domain_strategy:type_name -> xray.transport.internet.DomainStrategy
|
||||
1, // 8: xray.proxy.freedom.Config.destination_override:type_name -> xray.proxy.freedom.DestinationOverride
|
||||
2, // 9: xray.proxy.freedom.Config.fragment:type_name -> xray.proxy.freedom.Fragment
|
||||
3, // 10: xray.proxy.freedom.Config.noises:type_name -> xray.proxy.freedom.Noise
|
||||
5, // 11: xray.proxy.freedom.Config.firewall:type_name -> xray.proxy.freedom.FirewallConfig
|
||||
12, // [12:12] is the sub-list for method output_type
|
||||
12, // [12:12] is the sub-list for method input_type
|
||||
12, // [12:12] is the sub-list for extension type_name
|
||||
12, // [12:12] is the sub-list for extension extendee
|
||||
0, // [0:12] is the sub-list for field type_name
|
||||
12, // 6: xray.proxy.freedom.FirewallConfig.log_level:type_name -> xray.common.log.Severity
|
||||
5, // 7: xray.proxy.freedom.FirewallConfig.reply:type_name -> xray.proxy.freedom.Reply
|
||||
13, // 8: xray.proxy.freedom.Config.domain_strategy:type_name -> xray.transport.internet.DomainStrategy
|
||||
1, // 9: xray.proxy.freedom.Config.destination_override:type_name -> xray.proxy.freedom.DestinationOverride
|
||||
2, // 10: xray.proxy.freedom.Config.fragment:type_name -> xray.proxy.freedom.Fragment
|
||||
3, // 11: xray.proxy.freedom.Config.noises:type_name -> xray.proxy.freedom.Noise
|
||||
6, // 12: xray.proxy.freedom.Config.firewall:type_name -> xray.proxy.freedom.FirewallConfig
|
||||
13, // [13:13] is the sub-list for method output_type
|
||||
13, // [13:13] is the sub-list for method input_type
|
||||
13, // [13:13] is the sub-list for extension type_name
|
||||
13, // [13:13] is the sub-list for extension extendee
|
||||
0, // [0:13] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_proxy_freedom_config_proto_init() }
|
||||
@@ -628,14 +694,14 @@ func file_proxy_freedom_config_proto_init() {
|
||||
if File_proxy_freedom_config_proto != nil {
|
||||
return
|
||||
}
|
||||
file_proxy_freedom_config_proto_msgTypes[4].OneofWrappers = []any{}
|
||||
file_proxy_freedom_config_proto_msgTypes[5].OneofWrappers = []any{}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_proxy_freedom_config_proto_rawDesc), len(file_proxy_freedom_config_proto_rawDesc)),
|
||||
NumEnums: 1,
|
||||
NumMessages: 6,
|
||||
NumMessages: 7,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
|
||||
@@ -47,6 +47,11 @@ enum RuleAction {
|
||||
Block = 1;
|
||||
}
|
||||
|
||||
message Reply {
|
||||
string type = 1;
|
||||
bytes custom_response_data = 2;
|
||||
}
|
||||
|
||||
message FirewallConfig {
|
||||
RuleAction action = 1;
|
||||
repeated xray.common.net.Network networks = 2;
|
||||
@@ -54,6 +59,7 @@ message FirewallConfig {
|
||||
repeated xray.common.geodata.IPRule ip = 4;
|
||||
Range delay = 5;
|
||||
optional xray.common.log.Severity log_level = 6;
|
||||
Reply reply = 7;
|
||||
}
|
||||
|
||||
message Config {
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package freedom
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
@@ -38,6 +40,16 @@ var (
|
||||
defaultBlockAllRule *Firewall
|
||||
)
|
||||
|
||||
var http403response = http.Response{
|
||||
StatusCode: 403,
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 1,
|
||||
Header: http.Header{
|
||||
"Connection": {"close"},
|
||||
"Cache-Control": {"max-age=3600, public"},
|
||||
},
|
||||
}
|
||||
|
||||
func reloadEnvSettings() error {
|
||||
const defaultFlagValue = "NOT_DEFINED_AT_ALL"
|
||||
value := platform.NewEnvFlag(platform.UseFreedomSplice).GetValue(func() string { return defaultFlagValue })
|
||||
@@ -92,6 +104,7 @@ type Firewall struct {
|
||||
ip geodata.IPMatcher
|
||||
delay *Range
|
||||
logLevel *log.Severity
|
||||
reply []byte
|
||||
}
|
||||
|
||||
// Handler handles Freedom connections.
|
||||
@@ -110,6 +123,20 @@ func buildRule(config *FirewallConfig) (*Firewall, error) {
|
||||
logLevel: config.LogLevel,
|
||||
}
|
||||
|
||||
if config.Reply != nil {
|
||||
switch config.Reply.Type {
|
||||
case "", "none":
|
||||
case "http":
|
||||
var data bytes.Buffer
|
||||
common.Must(http403response.Write(&data))
|
||||
rule.reply = data.Bytes()
|
||||
case "custom":
|
||||
rule.reply = config.Reply.CustomResponseData
|
||||
default:
|
||||
return nil, errors.New("unknown firewall reply: " + config.Reply.Type)
|
||||
}
|
||||
}
|
||||
|
||||
if len(config.Networks) == 0 {
|
||||
rule.network = allNetworks
|
||||
} else {
|
||||
@@ -251,6 +278,13 @@ func (h *Handler) blackhole(ctx context.Context, input buf.Reader, output buf.Wr
|
||||
delay := rule.blockDuration()
|
||||
rule.log(ctx, "target blocked by a firewall rule: ", *dest, ", blackholing connection for ", delay)
|
||||
defer common.Close(output)
|
||||
if len(rule.reply) > 0 {
|
||||
mbc := buf.MultiBufferContainer{}
|
||||
common.Must2(mbc.Write(rule.reply))
|
||||
output.WriteMultiBuffer(mbc.MultiBuffer)
|
||||
// Sleep a little here to make sure the reply is sent to client.
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
done := make(chan struct{})
|
||||
go func() { // a DispatchLink inbound's reader ignores Interrupt, so don't wait for this copy
|
||||
_ = buf.Copy(input, buf.Discard)
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package freedom
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/xtls/xray-core/common/buf"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
)
|
||||
|
||||
func TestFirewallReply(t *testing.T) {
|
||||
custom := bytes.Repeat([]byte("custom response"), buf.Size)
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
reply *Reply
|
||||
want []byte
|
||||
}{
|
||||
{name: "default"},
|
||||
{name: "none", reply: &Reply{Type: "none"}},
|
||||
{name: "http", reply: &Reply{Type: "http"}},
|
||||
{name: "custom", reply: &Reply{Type: "custom", CustomResponseData: custom}, want: custom},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
rule, err := buildRule(&FirewallConfig{
|
||||
Action: RuleAction_Block,
|
||||
Delay: &Range{},
|
||||
Reply: test.reply,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var output bytes.Buffer
|
||||
dest := net.TCPDestination(net.LocalHostIP, 80)
|
||||
if err := new(Handler).blackhole(context.Background(), buf.NewReader(bytes.NewReader(nil)), &buf.BufferToBytesWriter{Writer: &output}, rule, &dest); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if test.name == "http" {
|
||||
response, err := http.ReadResponse(bufio.NewReader(&output), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer response.Body.Close()
|
||||
if response.StatusCode != http.StatusForbidden {
|
||||
t.Fatalf("HTTP status = %d, want 403", response.StatusCode)
|
||||
}
|
||||
} else if !bytes.Equal(output.Bytes(), test.want) {
|
||||
t.Fatalf("reply has %d bytes, want %d", output.Len(), len(test.want))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user