diff --git a/infra/conf/transport_internet.go b/infra/conf/transport_internet.go index b4458096b458..eeed6e8b4f61 100644 --- a/infra/conf/transport_internet.go +++ b/infra/conf/transport_internet.go @@ -1100,12 +1100,13 @@ func (p TransportProtocol) Build() (string, error) { } type CustomSockoptConfig struct { - Syetem string `json:"system"` - Network string `json:"network"` - Level string `json:"level"` - Opt string `json:"opt"` - Value string `json:"value"` - Type string `json:"type"` + Syetem string `json:"system"` + Network string `json:"network"` + TcpAfterConn bool `json:"tcpAfterConn"` + Level string `json:"level"` + Opt string `json:"opt"` + Type string `json:"type"` + Value json.RawMessage `json:"value"` } type HappyEyeballsConfig struct { @@ -1213,13 +1214,28 @@ func (c *SocketConfig) Build() (*internet.SocketConfig, error) { var customSockopts []*internet.CustomSockopt for _, copt := range c.CustomSockopt { + var err error + switch copt.Type { + case "int", "str": + copt.Value, err = PraseByteSlice(copt.Value, "str") + case "", "array", "hex", "base64": + copt.Value, err = PraseByteSlice(copt.Value, copt.Type) + copt.Type = "str" + default: + return nil, errors.New("unknown custom sockopt type") + } + if err != nil { + return nil, errors.New("custom sockopt").Base(err) + } + customSockopt := &internet.CustomSockopt{ - System: copt.Syetem, - Network: copt.Network, - Level: copt.Level, - Opt: copt.Opt, - Value: copt.Value, - Type: copt.Type, + System: copt.Syetem, + Network: copt.Network, + TcpAfterConn: copt.TcpAfterConn, + Level: copt.Level, + Opt: copt.Opt, + Type: copt.Type, + Value: copt.Value, } customSockopts = append(customSockopts, customSockopt) } @@ -1276,6 +1292,40 @@ func (c *SocketConfig) Build() (*internet.SocketConfig, error) { }, nil } +func PraseByteSlice(data json.RawMessage, typ string) ([]byte, error) { + switch strings.ToLower(typ) { + case "", "array": + if len(data) == 0 { + return data, nil + } + var packet []byte + if err := json.Unmarshal(data, &packet); err != nil { + return nil, err + } + return packet, nil + case "str": + var str string + if err := json.Unmarshal(data, &str); err != nil { + return nil, err + } + return []byte(str), nil + case "hex": + var str string + if err := json.Unmarshal(data, &str); err != nil { + return nil, err + } + return hex.DecodeString(str) + case "base64": + var str string + if err := json.Unmarshal(data, &str); err != nil { + return nil, err + } + return base64.StdEncoding.DecodeString(str) + default: + return nil, errors.New("unknown type") + } +} + var ( udpmaskLoader = NewJSONConfigLoader(ConfigCreatorCache{ "header-dns": func() interface{} { return new(Dns) }, diff --git a/transport/internet/config.pb.go b/transport/internet/config.pb.go index 5ea4ffb8053d..c21439cc2c74 100644 --- a/transport/internet/config.pb.go +++ b/transport/internet/config.pb.go @@ -430,10 +430,11 @@ type CustomSockopt struct { state protoimpl.MessageState `protogen:"open.v1"` System string `protobuf:"bytes,1,opt,name=system,proto3" json:"system,omitempty"` Network string `protobuf:"bytes,2,opt,name=network,proto3" json:"network,omitempty"` - Level string `protobuf:"bytes,3,opt,name=level,proto3" json:"level,omitempty"` - Opt string `protobuf:"bytes,4,opt,name=opt,proto3" json:"opt,omitempty"` - Value string `protobuf:"bytes,5,opt,name=value,proto3" json:"value,omitempty"` + TcpAfterConn bool `protobuf:"varint,3,opt,name=tcp_after_conn,json=tcpAfterConn,proto3" json:"tcp_after_conn,omitempty"` + Level string `protobuf:"bytes,4,opt,name=level,proto3" json:"level,omitempty"` + Opt string `protobuf:"bytes,5,opt,name=opt,proto3" json:"opt,omitempty"` Type string `protobuf:"bytes,6,opt,name=type,proto3" json:"type,omitempty"` + Value []byte `protobuf:"bytes,7,opt,name=value,proto3" json:"value,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -482,6 +483,13 @@ func (x *CustomSockopt) GetNetwork() string { return "" } +func (x *CustomSockopt) GetTcpAfterConn() bool { + if x != nil { + return x.TcpAfterConn + } + return false +} + func (x *CustomSockopt) GetLevel() string { if x != nil { return x.Level @@ -496,18 +504,18 @@ func (x *CustomSockopt) GetOpt() string { return "" } -func (x *CustomSockopt) GetValue() string { +func (x *CustomSockopt) GetType() string { if x != nil { - return x.Value + return x.Type } return "" } -func (x *CustomSockopt) GetType() string { +func (x *CustomSockopt) GetValue() []byte { if x != nil { - return x.Type + return x.Value } - return "" + return nil } // SocketConfig is options to be applied on network sockets. @@ -825,14 +833,15 @@ const file_transport_internet_config_proto_rawDesc = "" + "\x0fsocket_settings\x18\x06 \x01(\v2%.xray.transport.internet.SocketConfigR\x0esocketSettings\"Q\n" + "\vProxyConfig\x12\x10\n" + "\x03tag\x18\x01 \x01(\tR\x03tag\x120\n" + - "\x13transportLayerProxy\x18\x02 \x01(\bR\x13transportLayerProxy\"\x93\x01\n" + + "\x13transportLayerProxy\x18\x02 \x01(\bR\x13transportLayerProxy\"\xb9\x01\n" + "\rCustomSockopt\x12\x16\n" + "\x06system\x18\x01 \x01(\tR\x06system\x12\x18\n" + - "\anetwork\x18\x02 \x01(\tR\anetwork\x12\x14\n" + - "\x05level\x18\x03 \x01(\tR\x05level\x12\x10\n" + - "\x03opt\x18\x04 \x01(\tR\x03opt\x12\x14\n" + - "\x05value\x18\x05 \x01(\tR\x05value\x12\x12\n" + - "\x04type\x18\x06 \x01(\tR\x04type\"\x89\t\n" + + "\anetwork\x18\x02 \x01(\tR\anetwork\x12$\n" + + "\x0etcp_after_conn\x18\x03 \x01(\bR\ftcpAfterConn\x12\x14\n" + + "\x05level\x18\x04 \x01(\tR\x05level\x12\x10\n" + + "\x03opt\x18\x05 \x01(\tR\x03opt\x12\x12\n" + + "\x04type\x18\x06 \x01(\tR\x04type\x12\x14\n" + + "\x05value\x18\a \x01(\fR\x05value\"\x89\t\n" + "\fSocketConfig\x12\x12\n" + "\x04mark\x18\x01 \x01(\x05R\x04mark\x12\x10\n" + "\x03tfo\x18\x02 \x01(\x05R\x03tfo\x12H\n" + diff --git a/transport/internet/config.proto b/transport/internet/config.proto index 8b1bb23e9fec..90f7cd13b67e 100644 --- a/transport/internet/config.proto +++ b/transport/internet/config.proto @@ -70,10 +70,11 @@ message ProxyConfig { message CustomSockopt { string system = 1; string network = 2; - string level = 3; - string opt = 4; - string value = 5; + bool tcp_after_conn = 3; + string level = 4; + string opt = 5; string type = 6; + bytes value = 7; } // SocketConfig is options to be applied on network sockets. diff --git a/transport/internet/sockopt_darwin.go b/transport/internet/sockopt_darwin.go index bcae2f3fd231..f0d9a01527f0 100644 --- a/transport/internet/sockopt_darwin.go +++ b/transport/internet/sockopt_darwin.go @@ -174,12 +174,12 @@ func applyOutboundSocketOptions(network string, address string, fd uintptr, conf level, _ = strconv.Atoi(custom.Level) } if custom.Type == "int" { - value, _ := strconv.Atoi(custom.Value) - if err := syscall.SetsockoptInt(int(fd), level, opt, value); err != nil { + value, _ := strconv.Atoi(string(custom.Value)) + if err := setsockoptInt(fd, level, opt, value); err != nil { return errors.New("failed to set CustomSockoptInt", opt, value, err) } } else if custom.Type == "str" { - if err := syscall.SetsockoptString(int(fd), level, opt, custom.Value); err != nil { + if err := setsockoptString(fd, level, opt, string(custom.Value)); err != nil { return errors.New("failed to set CustomSockoptString", opt, custom.Value, err) } } else { @@ -260,6 +260,9 @@ func applyInboundSocketOptions(network string, fd uintptr, config *SocketConfig) if !strings.HasPrefix(network, custom.Network) { continue } + if custom.TcpAfterConn { + continue + } var level = 0x6 // default TCP var opt int if len(custom.Opt) == 0 { @@ -271,12 +274,12 @@ func applyInboundSocketOptions(network string, fd uintptr, config *SocketConfig) level, _ = strconv.Atoi(custom.Level) } if custom.Type == "int" { - value, _ := strconv.Atoi(custom.Value) - if err := syscall.SetsockoptInt(int(fd), level, opt, value); err != nil { + value, _ := strconv.Atoi(string(custom.Value)) + if err := setsockoptInt(fd, level, opt, value); err != nil { return errors.New("failed to set CustomSockoptInt", opt, value, err) } } else if custom.Type == "str" { - if err := syscall.SetsockoptString(int(fd), level, opt, custom.Value); err != nil { + if err := setsockoptString(fd, level, opt, string(custom.Value)); err != nil { return errors.New("failed to set CustomSockoptString", opt, custom.Value, err) } } else { @@ -327,3 +330,11 @@ func setReusePort(fd uintptr) error { } return nil } + +func setsockoptInt(fd uintptr, level, opt, value int) error { + return syscall.SetsockoptInt(int(fd), level, opt, value) +} + +func setsockoptString(fd uintptr, level, opt int, s string) error { + return syscall.SetsockoptString(int(fd), level, opt, s) +} diff --git a/transport/internet/sockopt_freebsd.go b/transport/internet/sockopt_freebsd.go index 4d490a40b5c1..8271b5057772 100644 --- a/transport/internet/sockopt_freebsd.go +++ b/transport/internet/sockopt_freebsd.go @@ -263,3 +263,11 @@ func setReusePort(fd uintptr) error { } return nil } + +func setsockoptInt(fd uintptr, level, opt, value int) error { + return syscall.SetsockoptInt(int(fd), level, opt, value) +} + +func setsockoptString(fd uintptr, level, opt int, s string) error { + return syscall.SetsockoptString(int(fd), level, opt, s) +} diff --git a/transport/internet/sockopt_linux.go b/transport/internet/sockopt_linux.go index 9a305a407176..063b932fc35d 100644 --- a/transport/internet/sockopt_linux.go +++ b/transport/internet/sockopt_linux.go @@ -114,12 +114,12 @@ func applyOutboundSocketOptions(network string, address string, fd uintptr, conf level, _ = strconv.Atoi(custom.Level) } if custom.Type == "int" { - value, _ := strconv.Atoi(custom.Value) - if err := syscall.SetsockoptInt(int(fd), level, opt, value); err != nil { + value, _ := strconv.Atoi(string(custom.Value)) + if err := setsockoptInt(fd, level, opt, value); err != nil { return errors.New("failed to set CustomSockoptInt", opt, value, err) } } else if custom.Type == "str" { - if err := syscall.SetsockoptString(int(fd), level, opt, custom.Value); err != nil { + if err := setsockoptString(fd, level, opt, string(custom.Value)); err != nil { return errors.New("failed to set CustomSockoptString", opt, custom.Value, err) } } else { @@ -209,6 +209,9 @@ func applyInboundSocketOptions(network string, fd uintptr, config *SocketConfig) if !strings.HasPrefix(network, custom.Network) { continue } + if custom.TcpAfterConn { + continue + } var level = 0x6 // default TCP var opt int if len(custom.Opt) == 0 { @@ -220,12 +223,12 @@ func applyInboundSocketOptions(network string, fd uintptr, config *SocketConfig) level, _ = strconv.Atoi(custom.Level) } if custom.Type == "int" { - value, _ := strconv.Atoi(custom.Value) - if err := syscall.SetsockoptInt(int(fd), level, opt, value); err != nil { + value, _ := strconv.Atoi(string(custom.Value)) + if err := setsockoptInt(fd, level, opt, value); err != nil { return errors.New("failed to set CustomSockoptInt", opt, value, err) } } else if custom.Type == "str" { - if err := syscall.SetsockoptString(int(fd), level, opt, custom.Value); err != nil { + if err := setsockoptString(fd, level, opt, string(custom.Value)); err != nil { return errors.New("failed to set CustomSockoptString", opt, custom.Value, err) } } else { @@ -271,3 +274,11 @@ func setReusePort(fd uintptr) error { } return nil } + +func setsockoptInt(fd uintptr, level, opt, value int) error { + return syscall.SetsockoptInt(int(fd), level, opt, value) +} + +func setsockoptString(fd uintptr, level, opt int, s string) error { + return syscall.SetsockoptString(int(fd), level, opt, s) +} diff --git a/transport/internet/sockopt_other.go b/transport/internet/sockopt_other.go index 7e91110e4cc1..e8619478d96a 100644 --- a/transport/internet/sockopt_other.go +++ b/transport/internet/sockopt_other.go @@ -22,3 +22,11 @@ func setReuseAddr(fd uintptr) error { func setReusePort(fd uintptr) error { return nil } + +func setsockoptInt(fd uintptr, level, opt, value int) error { + return nil +} + +func setsockoptString(fd uintptr, level, opt int, s string) error { + return nil +} diff --git a/transport/internet/sockopt_windows.go b/transport/internet/sockopt_windows.go index 1389ca06ed0c..78062f361d08 100644 --- a/transport/internet/sockopt_windows.go +++ b/transport/internet/sockopt_windows.go @@ -105,12 +105,14 @@ func applyOutboundSocketOptions(network string, address string, fd uintptr, conf level, _ = strconv.Atoi(custom.Level) } if custom.Type == "int" { - value, _ := strconv.Atoi(custom.Value) - if err := syscall.SetsockoptInt(syscall.Handle(fd), level, opt, value); err != nil { + value, _ := strconv.Atoi(string(custom.Value)) + if err := setsockoptInt(fd, level, opt, value); err != nil { return errors.New("failed to set CustomSockoptInt", opt, value, err) } } else if custom.Type == "str" { - return errors.New("failed to set CustomSockoptString: Str type does not supported on windows") + if err := setsockoptString(fd, level, opt, string(custom.Value)); err != nil { + return errors.New("failed to set CustomSockoptString", opt, custom.Value, err) + } } else { return errors.New("unknown CustomSockopt type:", custom.Type) } @@ -155,6 +157,9 @@ func applyInboundSocketOptions(network string, fd uintptr, config *SocketConfig) if !strings.HasPrefix(network, custom.Network) { continue } + if custom.TcpAfterConn { + continue + } var level = 0x6 // default TCP var opt int if len(custom.Opt) == 0 { @@ -166,12 +171,14 @@ func applyInboundSocketOptions(network string, fd uintptr, config *SocketConfig) level, _ = strconv.Atoi(custom.Level) } if custom.Type == "int" { - value, _ := strconv.Atoi(custom.Value) - if err := syscall.SetsockoptInt(syscall.Handle(fd), level, opt, value); err != nil { + value, _ := strconv.Atoi(string(custom.Value)) + if err := setsockoptInt(fd, level, opt, value); err != nil { return errors.New("failed to set CustomSockoptInt", opt, value, err) } } else if custom.Type == "str" { - return errors.New("failed to set CustomSockoptString: Str type does not supported on windows") + if err := setsockoptString(fd, level, opt, string(custom.Value)); err != nil { + return errors.New("failed to set CustomSockoptString", opt, custom.Value, err) + } } else { return errors.New("unknown CustomSockopt type:", custom.Type) } @@ -192,3 +199,15 @@ func setReuseAddr(fd uintptr) error { func setReusePort(fd uintptr) error { return nil } + +func setsockoptInt(fd uintptr, level, opt, value int) error { + return syscall.SetsockoptInt(syscall.Handle(fd), level, opt, value) +} + +func setsockoptString(fd uintptr, level, opt int, s string) error { + var p *byte + if len(s) > 0 { + p = &[]byte(s)[0] + } + return syscall.Setsockopt(syscall.Handle(fd), int32(level), int32(opt), p, int32(len(s))) +} diff --git a/transport/internet/system_listener.go b/transport/internet/system_listener.go index 2ac28eda9c56..5f6d7e2f954c 100644 --- a/transport/internet/system_listener.go +++ b/transport/internet/system_listener.go @@ -167,6 +167,19 @@ func (dl *DefaultListener) Listen(ctx context.Context, addr net.Addr, sockopt *S } l, err = callback(lc.Listen(ctx, network, address)) + + if sockopt != nil { + for _, copt := range sockopt.CustomSockopt { + if copt.TcpAfterConn { + l = &tcpAfterConnListener{ + CustomSockopt: sockopt.CustomSockopt, + Listener: l, + } + break + } + } + } + if err == nil && sockopt != nil && sockopt.AcceptProxyProtocol { policyFunc := func(upstream net.Addr) (proxyproto.Policy, error) { return proxyproto.REQUIRE, nil } l = &proxyproto.Listener{Listener: l, Policy: policyFunc} @@ -182,6 +195,76 @@ func (dl *DefaultListener) ListenPacket(ctx context.Context, addr net.Addr, sock return lc.ListenPacket(ctx, addr.Network(), addr.String()) } +type tcpAfterConnListener struct { + CustomSockopt []*CustomSockopt + net.Listener +} + +func (l *tcpAfterConnListener) setCustomSockopt(conn net.Conn) error { + sys, ok := conn.(syscall.Conn) + if !ok { + return errors.New("unable to get syscall.Conn") + } + + sysConn, err := sys.SyscallConn() + if err != nil { + return errors.New("failed to get sys fd").Base(err) + } + + err = sysConn.Control(func(fd uintptr) { + for _, custom := range l.CustomSockopt { + if custom.System != "" && custom.System != runtime.GOOS { + errors.LogDebug(context.Background(), "CustomSockopt system not match: ", "want ", custom.System, " got ", runtime.GOOS) + continue + } + // if custom.Network != "" && !isTCPSocket(custom.Network) { + // continue + // } + if !custom.TcpAfterConn { + continue + } + var level = 0x6 + var opt int + if len(custom.Opt) == 0 { + errors.LogDebug(context.Background(), "No opt!") + continue + } else { + opt, _ = strconv.Atoi(custom.Opt) + } + if custom.Level != "" { + level, _ = strconv.Atoi(custom.Level) + } + switch custom.Type { + case "int": + value, _ := strconv.Atoi(string(custom.Value)) + if err := setsockoptInt(fd, level, opt, value); err != nil { + errors.LogDebugInner(context.Background(), err, "failed to set CustomSockoptInt ", opt, " ", value) + } + case "str": + if err := setsockoptString(fd, level, opt, string(custom.Value)); err != nil { + errors.LogDebugInner(context.Background(), err, "failed to set CustomSockoptString ", opt, " ", custom.Value) + } + default: + errors.LogDebug(context.Background(), "unknown CustomSockopt type: ", custom.Type) + } + } + }) + + return err +} + +func (l *tcpAfterConnListener) Accept() (net.Conn, error) { + conn, err := l.Listener.Accept() + if err != nil { + return conn, err + } + err = l.setCustomSockopt(conn) + if err != nil { + errors.LogInfoInner(context.Background(), err, "setCustomSockopt") + } + return conn, nil +} + // RegisterListenerController adds a controller to the effective system listener. // The controller can be used to operate on file descriptors before they are put into use. //