mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-10-10 17:45:41 +00:00
Compare commits
4
Commits
c1958dba04
...
blackhole
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bd60b01576 | ||
|
|
f49d8b89c8 | ||
|
|
5e245b082e | ||
|
|
d9c54026c5 |
@@ -1,18 +1,10 @@
|
|||||||
package quic
|
package quic
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto"
|
|
||||||
"crypto/cipher"
|
"crypto/cipher"
|
||||||
_ "crypto/tls"
|
_ "crypto/tls"
|
||||||
_ "unsafe"
|
_ "unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CipherSuiteTLS13 struct {
|
|
||||||
ID uint16
|
|
||||||
KeyLen int
|
|
||||||
AEAD func(key, fixedNonce []byte) cipher.AEAD
|
|
||||||
Hash crypto.Hash
|
|
||||||
}
|
|
||||||
|
|
||||||
//go:linkname AEADAESGCMTLS13 crypto/tls.aeadAESGCMTLS13
|
//go:linkname AEADAESGCMTLS13 crypto/tls.aeadAESGCMTLS13
|
||||||
func AEADAESGCMTLS13(key, nonceMask []byte) cipher.AEAD
|
func AEADAESGCMTLS13(key, nonceMask []byte) cipher.AEAD
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package quic
|
|||||||
import (
|
import (
|
||||||
"crypto"
|
"crypto"
|
||||||
"crypto/aes"
|
"crypto/aes"
|
||||||
"crypto/tls"
|
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
@@ -28,22 +27,43 @@ func (s SniffHeader) Domain() string {
|
|||||||
return s.domain
|
return s.domain
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
var (
|
||||||
versionDraft29 uint32 = 0xff00001d
|
errNotQUIC = errors.New("not quic")
|
||||||
version1 uint32 = 0x1
|
errNotQUICInitial = errors.New("not initial packet")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type quicVersionSpec struct {
|
||||||
|
ver uint32
|
||||||
|
typeInitial byte
|
||||||
|
initialSalt []byte
|
||||||
|
labelPrefix string
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
quicSaltOld = []byte{0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, 0x4c, 0x9e, 0x97, 0x86, 0xf1, 0x9c, 0x61, 0x11, 0xe0, 0x43, 0x90, 0xa8, 0x99}
|
quicDraft29 = quicVersionSpec{
|
||||||
quicSalt = []byte{0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a}
|
ver: 0xff00001d,
|
||||||
initialSuite = &CipherSuiteTLS13{
|
typeInitial: 0b00,
|
||||||
ID: tls.TLS_AES_128_GCM_SHA256,
|
initialSalt: []byte{0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, 0x4c, 0x9e, 0x97, 0x86, 0xf1, 0x9c, 0x61, 0x11, 0xe0, 0x43, 0x90, 0xa8, 0x99},
|
||||||
KeyLen: 16,
|
labelPrefix: "quic",
|
||||||
AEAD: AEADAESGCMTLS13,
|
}
|
||||||
Hash: crypto.SHA256,
|
quicV1 = quicVersionSpec{
|
||||||
|
ver: 0x1,
|
||||||
|
typeInitial: 0b00,
|
||||||
|
initialSalt: []byte{0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a},
|
||||||
|
labelPrefix: "quic",
|
||||||
|
}
|
||||||
|
quicV2 = quicVersionSpec{
|
||||||
|
ver: 0x6b3343cf,
|
||||||
|
typeInitial: 0b01,
|
||||||
|
initialSalt: []byte{0x0d, 0xed, 0xe3, 0xde, 0xf7, 0x00, 0xa6, 0xdb, 0x81, 0x93, 0x81, 0xbe, 0x6e, 0x26, 0x9d, 0xcb, 0xf9, 0xbd, 0x2e, 0xd9},
|
||||||
|
labelPrefix: "quicv2",
|
||||||
|
}
|
||||||
|
|
||||||
|
quicVersionSpecMap = map[uint32]*quicVersionSpec{
|
||||||
|
quicDraft29.ver: &quicDraft29,
|
||||||
|
quicV1.ver: &quicV1,
|
||||||
|
quicV2.ver: &quicV2,
|
||||||
}
|
}
|
||||||
errNotQuic = errors.New("not quic")
|
|
||||||
errNotQuicInitial = errors.New("not initial packet")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func SniffQUIC(b []byte) (*SniffHeader, error) {
|
func SniffQUIC(b []byte) (*SniffHeader, error) {
|
||||||
@@ -63,60 +83,61 @@ func SniffQUIC(b []byte) (*SniffHeader, error) {
|
|||||||
buffer := buf.FromBytes(b)
|
buffer := buf.FromBytes(b)
|
||||||
typeByte, err := buffer.ReadByte()
|
typeByte, err := buffer.ReadByte()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errNotQuic
|
return nil, errNotQUIC
|
||||||
}
|
}
|
||||||
|
|
||||||
isLongHeader := typeByte&0x80 > 0
|
isLongHeader := typeByte&0x80 > 0
|
||||||
if !isLongHeader || typeByte&0x40 == 0 {
|
if !isLongHeader || typeByte&0x40 == 0 {
|
||||||
return nil, errNotQuicInitial
|
return nil, errNotQUICInitial
|
||||||
}
|
}
|
||||||
|
|
||||||
vb, err := buffer.ReadBytes(4)
|
vb, err := buffer.ReadBytes(4)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errNotQuic
|
return nil, errNotQUIC
|
||||||
}
|
}
|
||||||
|
|
||||||
versionNumber := binary.BigEndian.Uint32(vb)
|
versionNumber := binary.BigEndian.Uint32(vb)
|
||||||
if versionNumber != 0 && typeByte&0x40 == 0 {
|
var s *quicVersionSpec
|
||||||
return nil, errNotQuic
|
if v, ok := quicVersionSpecMap[versionNumber]; ok {
|
||||||
} else if versionNumber != versionDraft29 && versionNumber != version1 {
|
s = v
|
||||||
return nil, errNotQuic
|
} else {
|
||||||
|
return nil, errNotQUIC
|
||||||
}
|
}
|
||||||
|
|
||||||
packetType := (typeByte & 0x30) >> 4
|
|
||||||
isQuicInitial := packetType == 0x0
|
|
||||||
|
|
||||||
var destConnID []byte
|
var destConnID []byte
|
||||||
if l, err := buffer.ReadByte(); err != nil {
|
if l, err := buffer.ReadByte(); err != nil {
|
||||||
return nil, errNotQuic
|
return nil, errNotQUIC
|
||||||
} else if destConnID, err = buffer.ReadBytes(int32(l)); err != nil {
|
} else if destConnID, err = buffer.ReadBytes(int32(l)); err != nil {
|
||||||
return nil, errNotQuic
|
return nil, errNotQUIC
|
||||||
}
|
}
|
||||||
|
|
||||||
if l, err := buffer.ReadByte(); err != nil {
|
if l, err := buffer.ReadByte(); err != nil {
|
||||||
return nil, errNotQuic
|
return nil, errNotQUIC
|
||||||
} else if common.Error2(buffer.ReadBytes(int32(l))) != nil {
|
} else if common.Error2(buffer.ReadBytes(int32(l))) != nil {
|
||||||
return nil, errNotQuic
|
return nil, errNotQUIC
|
||||||
}
|
}
|
||||||
|
|
||||||
if isQuicInitial { // Only initial packets have token, see https://datatracker.ietf.org/doc/html/rfc9000#section-17.2.2
|
packetType := (typeByte & 0x30) >> 4
|
||||||
tokenLen, err := readShortQuicVarint(buffer)
|
isQUICInitial := packetType == s.typeInitial
|
||||||
|
|
||||||
|
if isQUICInitial { // Only initial packets have token, see https://datatracker.ietf.org/doc/html/rfc9000#section-17.2.2
|
||||||
|
tokenLen, err := readShortQUICVarint(buffer)
|
||||||
if err != nil || tokenLen > int32(len(b)) {
|
if err != nil || tokenLen > int32(len(b)) {
|
||||||
return nil, errNotQuic
|
return nil, errNotQUIC
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err = buffer.ReadBytes(tokenLen); err != nil {
|
if _, err = buffer.ReadBytes(tokenLen); err != nil {
|
||||||
return nil, errNotQuic
|
return nil, errNotQUIC
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
packetLen, err := readShortQuicVarint(buffer)
|
packetLen, err := readShortQUICVarint(buffer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errNotQuic
|
return nil, errNotQUIC
|
||||||
}
|
}
|
||||||
// packetLen is impossible to be shorter than this
|
// packetLen is impossible to be shorter than this
|
||||||
if packetLen < 4 {
|
if packetLen < 4 {
|
||||||
return nil, errNotQuic
|
return nil, errNotQUIC
|
||||||
}
|
}
|
||||||
|
|
||||||
hdrLen := len(b) - int(buffer.Len())
|
hdrLen := len(b) - int(buffer.Len())
|
||||||
@@ -125,26 +146,22 @@ func SniffQUIC(b []byte) (*SniffHeader, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
restPayload := b[hdrLen+int(packetLen):]
|
restPayload := b[hdrLen+int(packetLen):]
|
||||||
if !isQuicInitial { // Skip this packet if it's not initial packet
|
if !isQUICInitial { // Skip this packet if it's not initial packet
|
||||||
b = restPayload
|
b = restPayload
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
var salt []byte
|
salt := s.initialSalt
|
||||||
if versionNumber == version1 {
|
label := s.labelPrefix
|
||||||
salt = quicSalt
|
|
||||||
} else {
|
|
||||||
salt = quicSaltOld
|
|
||||||
}
|
|
||||||
initialSecret := hkdf.Extract(crypto.SHA256.New, destConnID, salt)
|
initialSecret := hkdf.Extract(crypto.SHA256.New, destConnID, salt)
|
||||||
secret := hkdfExpandLabel(crypto.SHA256, initialSecret, []byte{}, "client in", crypto.SHA256.Size())
|
secret := hkdfExpandLabel(initialSecret, "client in", crypto.SHA256.Size())
|
||||||
hpKey := hkdfExpandLabel(initialSuite.Hash, secret, []byte{}, "quic hp", initialSuite.KeyLen)
|
hpKey := hkdfExpandLabel(secret, label+" hp", 16)
|
||||||
block, err := aes.NewCipher(hpKey)
|
block, err := aes.NewCipher(hpKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if len(b) < hdrLen+4+block.BlockSize() {
|
if len(b) < hdrLen+4+block.BlockSize() {
|
||||||
return nil, errNotQuic
|
return nil, errNotQUIC
|
||||||
}
|
}
|
||||||
cache.Clear()
|
cache.Clear()
|
||||||
mask := cache.Extend(int32(block.BlockSize()))
|
mask := cache.Extend(int32(block.BlockSize()))
|
||||||
@@ -155,8 +172,8 @@ func SniffQUIC(b []byte) (*SniffHeader, error) {
|
|||||||
b[hdrLen+i] ^= mask[i+1]
|
b[hdrLen+i] ^= mask[i+1]
|
||||||
}
|
}
|
||||||
|
|
||||||
key := hkdfExpandLabel(crypto.SHA256, secret, []byte{}, "quic key", 16)
|
key := hkdfExpandLabel(secret, label+" key", 16)
|
||||||
iv := hkdfExpandLabel(crypto.SHA256, secret, []byte{}, "quic iv", 12)
|
iv := hkdfExpandLabel(secret, label+" iv", 12)
|
||||||
cipher := AEADAESGCMTLS13(key, iv)
|
cipher := AEADAESGCMTLS13(key, iv)
|
||||||
|
|
||||||
nonce := cache.Extend(int32(cipher.NonceSize()))
|
nonce := cache.Extend(int32(cipher.NonceSize()))
|
||||||
@@ -181,44 +198,44 @@ func SniffQUIC(b []byte) (*SniffHeader, error) {
|
|||||||
case 0x00: // PADDING frame
|
case 0x00: // PADDING frame
|
||||||
case 0x01: // PING frame
|
case 0x01: // PING frame
|
||||||
case 0x02, 0x03: // ACK frame
|
case 0x02, 0x03: // ACK frame
|
||||||
if _, err = readShortQuicVarint(buffer); err != nil { // Field: Largest Acknowledged
|
if _, err = readShortQUICVarint(buffer); err != nil { // Field: Largest Acknowledged
|
||||||
return nil, io.ErrUnexpectedEOF
|
return nil, io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
if _, err = readShortQuicVarint(buffer); err != nil { // Field: ACK Delay
|
if _, err = readShortQUICVarint(buffer); err != nil { // Field: ACK Delay
|
||||||
return nil, io.ErrUnexpectedEOF
|
return nil, io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
ackRangeCount, err := readShortQuicVarint(buffer) // Field: ACK Range Count
|
ackRangeCount, err := readShortQUICVarint(buffer) // Field: ACK Range Count
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, io.ErrUnexpectedEOF
|
return nil, io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
if _, err = readShortQuicVarint(buffer); err != nil { // Field: First ACK Range
|
if _, err = readShortQUICVarint(buffer); err != nil { // Field: First ACK Range
|
||||||
return nil, io.ErrUnexpectedEOF
|
return nil, io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
for i := 0; i < int(ackRangeCount); i++ { // Field: ACK Range
|
for i := 0; i < int(ackRangeCount); i++ { // Field: ACK Range
|
||||||
if _, err = readShortQuicVarint(buffer); err != nil { // Field: ACK Range -> Gap
|
if _, err = readShortQUICVarint(buffer); err != nil { // Field: ACK Range -> Gap
|
||||||
return nil, io.ErrUnexpectedEOF
|
return nil, io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
if _, err = readShortQuicVarint(buffer); err != nil { // Field: ACK Range -> ACK Range Length
|
if _, err = readShortQUICVarint(buffer); err != nil { // Field: ACK Range -> ACK Range Length
|
||||||
return nil, io.ErrUnexpectedEOF
|
return nil, io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if frameType == 0x03 {
|
if frameType == 0x03 {
|
||||||
if _, err = readShortQuicVarint(buffer); err != nil { // Field: ECN Counts -> ECT0 Count
|
if _, err = readShortQUICVarint(buffer); err != nil { // Field: ECN Counts -> ECT0 Count
|
||||||
return nil, io.ErrUnexpectedEOF
|
return nil, io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
if _, err = readShortQuicVarint(buffer); err != nil { // Field: ECN Counts -> ECT1 Count
|
if _, err = readShortQUICVarint(buffer); err != nil { // Field: ECN Counts -> ECT1 Count
|
||||||
return nil, io.ErrUnexpectedEOF
|
return nil, io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
if _, err = readShortQuicVarint(buffer); err != nil { //nolint:misspell // Field: ECN Counts -> ECT-CE Count
|
if _, err = readShortQUICVarint(buffer); err != nil { //nolint:misspell // Field: ECN Counts -> ECT-CE Count
|
||||||
return nil, io.ErrUnexpectedEOF
|
return nil, io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case 0x06: // CRYPTO frame, we will use this frame
|
case 0x06: // CRYPTO frame, we will use this frame
|
||||||
offset, err := readShortQuicVarint(buffer) // Field: Offset
|
offset, err := readShortQUICVarint(buffer) // Field: Offset
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, io.ErrUnexpectedEOF
|
return nil, io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
length, err := readShortQuicVarint(buffer) // Field: Length
|
length, err := readShortQUICVarint(buffer) // Field: Length
|
||||||
if err != nil || length > buffer.Len() {
|
if err != nil || length > buffer.Len() {
|
||||||
return nil, io.ErrUnexpectedEOF
|
return nil, io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
@@ -234,13 +251,13 @@ func SniffQUIC(b []byte) (*SniffHeader, error) {
|
|||||||
return nil, io.ErrUnexpectedEOF
|
return nil, io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
case 0x1c: // CONNECTION_CLOSE frame, only 0x1c is permitted in initial packet
|
case 0x1c: // CONNECTION_CLOSE frame, only 0x1c is permitted in initial packet
|
||||||
if _, err = readShortQuicVarint(buffer); err != nil { // Field: Error Code
|
if _, err = readShortQUICVarint(buffer); err != nil { // Field: Error Code
|
||||||
return nil, io.ErrUnexpectedEOF
|
return nil, io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
if _, err = readShortQuicVarint(buffer); err != nil { // Field: Frame Type
|
if _, err = readShortQUICVarint(buffer); err != nil { // Field: Frame Type
|
||||||
return nil, io.ErrUnexpectedEOF
|
return nil, io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
length, err := readShortQuicVarint(buffer) // Field: Reason Phrase Length
|
length, err := readShortQUICVarint(buffer) // Field: Reason Phrase Length
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, io.ErrUnexpectedEOF
|
return nil, io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
@@ -250,7 +267,7 @@ func SniffQUIC(b []byte) (*SniffHeader, error) {
|
|||||||
default:
|
default:
|
||||||
// Only above frame types are permitted in initial packet.
|
// Only above frame types are permitted in initial packet.
|
||||||
// See https://www.rfc-editor.org/rfc/rfc9000.html#section-17.2.2-8
|
// See https://www.rfc-editor.org/rfc/rfc9000.html#section-17.2.2-8
|
||||||
return nil, errNotQuicInitial
|
return nil, errNotQUICInitial
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -268,35 +285,33 @@ func SniffQUIC(b []byte) (*SniffHeader, error) {
|
|||||||
return nil, protocol.ErrProtoNeedMoreData
|
return nil, protocol.ErrProtoNeedMoreData
|
||||||
}
|
}
|
||||||
|
|
||||||
func hkdfExpandLabel(hash crypto.Hash, secret, context []byte, label string, length int) []byte {
|
func hkdfExpandLabel(secret []byte, label string, length int) []byte {
|
||||||
b := make([]byte, 3, 3+6+len(label)+1+len(context))
|
b := make([]byte, 0, 2+1+6+len(label)+1)
|
||||||
binary.BigEndian.PutUint16(b, uint16(length))
|
b = binary.BigEndian.AppendUint16(b, uint16(length))
|
||||||
b[2] = uint8(6 + len(label))
|
b = append(b, byte(6+len(label)))
|
||||||
b = append(b, []byte("tls13 ")...)
|
b = append(b, "tls13 "...)
|
||||||
b = append(b, []byte(label)...)
|
b = append(b, label...)
|
||||||
b = b[:3+6+len(label)+1]
|
b = append(b, 0) // context
|
||||||
b[3+6+len(label)] = uint8(len(context))
|
|
||||||
b = append(b, context...)
|
|
||||||
|
|
||||||
out := make([]byte, length)
|
out := make([]byte, length)
|
||||||
n, err := hkdf.Expand(hash.New, secret, b).Read(out)
|
n, err := hkdf.Expand(crypto.SHA256.New, secret, b).Read(out)
|
||||||
if err != nil || n != length {
|
if err != nil || n != length {
|
||||||
panic("quic: HKDF-Expand-Label invocation failed unexpectedly")
|
panic("quic: HKDF-Expand-Label invocation failed unexpectedly")
|
||||||
}
|
}
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
// readShortQuicVarint wraps quicvarint.Read with a max limit for length related fields.
|
// readShortQUICVarint wraps quicvarint.Read with a max limit for length related fields.
|
||||||
// we only handle QUIC Initial so these numbers should not exceed 65535
|
// we only handle QUIC Initial so these numbers should not exceed 65535
|
||||||
// returns int32 to reduce type conversion
|
// returns int32 to reduce type conversion
|
||||||
func readShortQuicVarint(reader io.ByteReader) (int32, error) {
|
func readShortQUICVarint(reader io.ByteReader) (int32, error) {
|
||||||
v, err := quicvarint.Read(reader)
|
v, err := quicvarint.Read(reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
if v > 65535 {
|
if v > 65535 {
|
||||||
// not used(
|
// not used(
|
||||||
return 0, errNotQuicInitial
|
return 0, errNotQUICInitial
|
||||||
}
|
}
|
||||||
return int32(v), nil
|
return int32(v), nil
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
+20
-30
@@ -1,52 +1,42 @@
|
|||||||
package conf
|
package conf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/base64"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
"github.com/xtls/xray-core/common/serial"
|
|
||||||
"github.com/xtls/xray-core/proxy/blackhole"
|
"github.com/xtls/xray-core/proxy/blackhole"
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
type NoneResponse struct{}
|
type ResponseConfig struct {
|
||||||
|
Type string `json:"type"`
|
||||||
func (*NoneResponse) Build() (proto.Message, error) {
|
CustomResponseData string `json:"customResponseData"`
|
||||||
return new(blackhole.NoneResponse), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type HTTPResponse struct{}
|
|
||||||
|
|
||||||
func (*HTTPResponse) Build() (proto.Message, error) {
|
|
||||||
return new(blackhole.HTTPResponse), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type BlackholeConfig struct {
|
type BlackholeConfig struct {
|
||||||
Response json.RawMessage `json:"response"`
|
Response *ResponseConfig `json:"response"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *BlackholeConfig) Build() (proto.Message, error) {
|
func (v *BlackholeConfig) Build() (proto.Message, error) {
|
||||||
config := new(blackhole.Config)
|
config := new(blackhole.Config)
|
||||||
if v.Response != nil {
|
if v.Response != nil {
|
||||||
response, _, err := configLoader.Load(v.Response)
|
responseName := strings.ToLower(v.Response.Type)
|
||||||
if err != nil {
|
switch responseName {
|
||||||
return nil, errors.New("Config: Failed to parse Blackhole response config.").Base(err)
|
case "none", "":
|
||||||
|
config.Response = &blackhole.Response{Type: "none"}
|
||||||
|
case "http":
|
||||||
|
config.Response = &blackhole.Response{Type: "http"}
|
||||||
|
case "custom":
|
||||||
|
data, err := base64.StdEncoding.DecodeString(v.Response.CustomResponseData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("failed to decode custom response data: " + err.Error())
|
||||||
|
}
|
||||||
|
config.Response = &blackhole.Response{Type: "custom", CustomResponseData: data}
|
||||||
|
default:
|
||||||
|
return nil, errors.New("unknown blackhole response: " + responseName)
|
||||||
}
|
}
|
||||||
responseSettings, err := response.(Buildable).Build()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
config.Response = serial.ToTypedMessage(responseSettings)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var configLoader = NewJSONConfigLoader(
|
|
||||||
ConfigCreatorCache{
|
|
||||||
"none": func() interface{} { return new(NoneResponse) },
|
|
||||||
"http": func() interface{} { return new(HTTPResponse) },
|
|
||||||
},
|
|
||||||
"type",
|
|
||||||
"",
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package conf_test
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common/serial"
|
|
||||||
. "github.com/xtls/xray-core/infra/conf"
|
. "github.com/xtls/xray-core/infra/conf"
|
||||||
"github.com/xtls/xray-core/proxy/blackhole"
|
"github.com/xtls/xray-core/proxy/blackhole"
|
||||||
)
|
)
|
||||||
@@ -22,7 +21,7 @@ func TestHTTPResponseJSON(t *testing.T) {
|
|||||||
}`,
|
}`,
|
||||||
Parser: loadJSON(creator),
|
Parser: loadJSON(creator),
|
||||||
Output: &blackhole.Config{
|
Output: &blackhole.Config{
|
||||||
Response: serial.ToTypedMessage(&blackhole.HTTPResponse{}),
|
Response: &blackhole.Response{Type: "http"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -32,3 +31,27 @@ func TestHTTPResponseJSON(t *testing.T) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCustomResponseJSON(t *testing.T) {
|
||||||
|
creator := func() Buildable {
|
||||||
|
return new(BlackholeConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
runMultiTestCase(t, []TestCase{
|
||||||
|
{
|
||||||
|
Input: `{
|
||||||
|
"response": {
|
||||||
|
"type": "custom",
|
||||||
|
"customResponseData": "Y3VzdG9tIHJlc3BvbnNl"
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
Parser: loadJSON(creator),
|
||||||
|
Output: &blackhole.Config{
|
||||||
|
Response: &blackhole.Response{
|
||||||
|
Type: "custom",
|
||||||
|
CustomResponseData: []byte("custom response"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ func (o *ObservatoryConfig) Build() (proto.Message, error) {
|
|||||||
type BurstObservatoryConfig struct {
|
type BurstObservatoryConfig struct {
|
||||||
SubjectSelector []string `json:"subjectSelector"`
|
SubjectSelector []string `json:"subjectSelector"`
|
||||||
// health check settings
|
// health check settings
|
||||||
HealthCheck *healthCheckSettings `json:"pingConfig,omitempty"`
|
HealthCheck *HealthCheckSettings `json:"pingConfig,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b BurstObservatoryConfig) Build() (proto.Message, error) {
|
func (b BurstObservatoryConfig) Build() (proto.Message, error) {
|
||||||
|
|||||||
@@ -43,8 +43,8 @@ type strategyLeastLoadConfig struct {
|
|||||||
Tolerance float64 `json:"tolerance,omitempty"`
|
Tolerance float64 `json:"tolerance,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// healthCheckSettings holds settings for health Checker
|
// HealthCheckSettings holds settings for health Checker
|
||||||
type healthCheckSettings struct {
|
type HealthCheckSettings struct {
|
||||||
Destination string `json:"destination"`
|
Destination string `json:"destination"`
|
||||||
Connectivity string `json:"connectivity"`
|
Connectivity string `json:"connectivity"`
|
||||||
Interval duration.Duration `json:"interval"`
|
Interval duration.Duration `json:"interval"`
|
||||||
@@ -53,7 +53,7 @@ type healthCheckSettings struct {
|
|||||||
HttpMethod string `json:"httpMethod"`
|
HttpMethod string `json:"httpMethod"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h healthCheckSettings) Build() (proto.Message, error) {
|
func (h HealthCheckSettings) Build() (proto.Message, error) {
|
||||||
var httpMethod string
|
var httpMethod string
|
||||||
if h.HttpMethod == "" {
|
if h.HttpMethod == "" {
|
||||||
httpMethod = "HEAD"
|
httpMethod = "HEAD"
|
||||||
|
|||||||
@@ -2,12 +2,15 @@
|
|||||||
package blackhole
|
package blackhole
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
"github.com/xtls/xray-core/common/buf"
|
"github.com/xtls/xray-core/common/buf"
|
||||||
"github.com/xtls/xray-core/common/dice"
|
"github.com/xtls/xray-core/common/dice"
|
||||||
|
"github.com/xtls/xray-core/common/errors"
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
"github.com/xtls/xray-core/common/session"
|
"github.com/xtls/xray-core/common/session"
|
||||||
"github.com/xtls/xray-core/common/signal"
|
"github.com/xtls/xray-core/common/signal"
|
||||||
@@ -17,14 +20,34 @@ import (
|
|||||||
|
|
||||||
// Handler is an outbound connection that silently swallow the entire payload.
|
// Handler is an outbound connection that silently swallow the entire payload.
|
||||||
type Handler struct {
|
type Handler struct {
|
||||||
response ResponseConfig
|
response []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
var http403response = http.Response{
|
||||||
|
StatusCode: 403,
|
||||||
|
ProtoMajor: 1,
|
||||||
|
ProtoMinor: 1,
|
||||||
|
Header: http.Header{
|
||||||
|
"Connection": {"close"},
|
||||||
|
"Cache-Control": {"max-age=3600, public"},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new blackhole handler.
|
// New creates a new blackhole handler.
|
||||||
func New(ctx context.Context, config *Config) (*Handler, error) {
|
func New(ctx context.Context, config *Config) (*Handler, error) {
|
||||||
response, err := config.GetInternalResponse()
|
response := []byte{}
|
||||||
if err != nil {
|
if config.Response != nil {
|
||||||
return nil, err
|
switch config.Response.Type {
|
||||||
|
case "", "none":
|
||||||
|
case "http":
|
||||||
|
var data bytes.Buffer
|
||||||
|
common.Must(http403response.Write(&data))
|
||||||
|
response = data.Bytes()
|
||||||
|
case "custom":
|
||||||
|
response = config.Response.CustomResponseData
|
||||||
|
default:
|
||||||
|
return nil, errors.New("unknown blackhole response: " + config.Response.Type)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return &Handler{
|
return &Handler{
|
||||||
response: response,
|
response: response,
|
||||||
@@ -37,8 +60,10 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
|
|||||||
ob := outbounds[len(outbounds)-1]
|
ob := outbounds[len(outbounds)-1]
|
||||||
ob.Name = "blackhole"
|
ob.Name = "blackhole"
|
||||||
|
|
||||||
nBytes := h.response.WriteTo(link.Writer)
|
if len(h.response) > 0 {
|
||||||
if nBytes > 0 {
|
mbc := buf.MultiBufferContainer{}
|
||||||
|
common.Must2(mbc.Write(h.response))
|
||||||
|
link.Writer.WriteMultiBuffer(mbc.MultiBuffer)
|
||||||
// Sleep a little here to make sure the response is sent to client.
|
// Sleep a little here to make sure the response is sent to client.
|
||||||
time.Sleep(time.Second)
|
time.Sleep(time.Second)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
package blackhole_test
|
package blackhole_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/rand"
|
||||||
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
"github.com/xtls/xray-core/common/buf"
|
"github.com/xtls/xray-core/common/buf"
|
||||||
"github.com/xtls/xray-core/common/serial"
|
|
||||||
"github.com/xtls/xray-core/common/session"
|
"github.com/xtls/xray-core/common/session"
|
||||||
"github.com/xtls/xray-core/proxy/blackhole"
|
"github.com/xtls/xray-core/proxy/blackhole"
|
||||||
"github.com/xtls/xray-core/transport"
|
"github.com/xtls/xray-core/transport"
|
||||||
@@ -16,27 +19,58 @@ import (
|
|||||||
func TestBlackholeHTTPResponse(t *testing.T) {
|
func TestBlackholeHTTPResponse(t *testing.T) {
|
||||||
ctx := session.ContextWithOutbounds(context.Background(), []*session.Outbound{{}})
|
ctx := session.ContextWithOutbounds(context.Background(), []*session.Outbound{{}})
|
||||||
handler, err := blackhole.New(ctx, &blackhole.Config{
|
handler, err := blackhole.New(ctx, &blackhole.Config{
|
||||||
Response: serial.ToTypedMessage(&blackhole.HTTPResponse{}),
|
Response: &blackhole.Response{Type: "http"},
|
||||||
})
|
})
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
|
|
||||||
reader, writer := pipe.New(pipe.WithoutSizeLimit())
|
reader, writer := pipe.New(pipe.WithoutSizeLimit())
|
||||||
|
|
||||||
var mb buf.MultiBuffer
|
dataCh := make(chan buf.MultiBuffer, 1)
|
||||||
var rerr error
|
|
||||||
go func() {
|
go func() {
|
||||||
b, e := reader.ReadMultiBuffer()
|
mb := common.Must2(reader.ReadMultiBuffer())
|
||||||
mb = b
|
dataCh <- mb
|
||||||
rerr = e
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
link := transport.Link{
|
link := transport.Link{
|
||||||
Reader: reader,
|
Reader: reader,
|
||||||
Writer: writer,
|
Writer: writer,
|
||||||
}
|
}
|
||||||
common.Must(handler.Process(ctx, &link, nil))
|
common.Must(handler.Process(ctx, &link, nil))
|
||||||
common.Must(rerr)
|
mb := <-dataCh
|
||||||
if mb.IsEmpty() {
|
data := make([]byte, mb.Len())
|
||||||
t.Error("expect http response, but nothing")
|
mb.Copy(data)
|
||||||
|
resp := common.Must2(http.ReadResponse(bufio.NewReader(bytes.NewBuffer(data)), nil))
|
||||||
|
if resp.StatusCode != 403 {
|
||||||
|
t.Errorf("expected 403 response, got %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBlackholeCustomResponse(t *testing.T) {
|
||||||
|
ctx := session.ContextWithOutbounds(context.Background(), []*session.Outbound{{}})
|
||||||
|
// slightly bigger than a buffer
|
||||||
|
expected := make([]byte, buf.Size+1000)
|
||||||
|
if _, err := rand.Read(expected); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
handler, err := blackhole.New(ctx, &blackhole.Config{
|
||||||
|
Response: &blackhole.Response{
|
||||||
|
Type: "custom",
|
||||||
|
CustomResponseData: expected,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
common.Must(err)
|
||||||
|
|
||||||
|
reader, writer := pipe.New(pipe.WithoutSizeLimit())
|
||||||
|
var actual buf.MultiBuffer
|
||||||
|
var rerr error
|
||||||
|
go func() {
|
||||||
|
actual, rerr = reader.ReadMultiBuffer()
|
||||||
|
}()
|
||||||
|
|
||||||
|
link := transport.Link{Reader: reader, Writer: writer}
|
||||||
|
common.Must(handler.Process(ctx, &link, nil))
|
||||||
|
common.Must(rerr)
|
||||||
|
|
||||||
|
if actual.String() != string(expected) {
|
||||||
|
t.Errorf("custom response mismatch")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,47 +0,0 @@
|
|||||||
package blackhole
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/xtls/xray-core/common"
|
|
||||||
"github.com/xtls/xray-core/common/buf"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
http403response = `HTTP/1.1 403 Forbidden
|
|
||||||
Connection: close
|
|
||||||
Cache-Control: max-age=3600, public
|
|
||||||
Content-Length: 0
|
|
||||||
|
|
||||||
|
|
||||||
`
|
|
||||||
)
|
|
||||||
|
|
||||||
// ResponseConfig is the configuration for blackhole responses.
|
|
||||||
type ResponseConfig interface {
|
|
||||||
// WriteTo writes a predefined response to the specified buffer.
|
|
||||||
WriteTo(buf.Writer) int32
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteTo implements ResponseConfig.WriteTo().
|
|
||||||
func (*NoneResponse) WriteTo(buf.Writer) int32 { return 0 }
|
|
||||||
|
|
||||||
// WriteTo implements ResponseConfig.WriteTo().
|
|
||||||
func (*HTTPResponse) WriteTo(writer buf.Writer) int32 {
|
|
||||||
b := buf.New()
|
|
||||||
common.Must2(b.WriteString(http403response))
|
|
||||||
n := b.Len()
|
|
||||||
writer.WriteMultiBuffer(buf.MultiBuffer{b})
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetInternalResponse converts response settings from proto to internal data structure.
|
|
||||||
func (c *Config) GetInternalResponse() (ResponseConfig, error) {
|
|
||||||
if c.GetResponse() == nil {
|
|
||||||
return new(NoneResponse), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
config, err := c.GetResponse().GetInstance()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return config.(ResponseConfig), nil
|
|
||||||
}
|
|
||||||
@@ -7,7 +7,6 @@
|
|||||||
package blackhole
|
package blackhole
|
||||||
|
|
||||||
import (
|
import (
|
||||||
serial "github.com/xtls/xray-core/common/serial"
|
|
||||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
@@ -22,26 +21,28 @@ const (
|
|||||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||||
)
|
)
|
||||||
|
|
||||||
type NoneResponse struct {
|
type Response struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
unknownFields protoimpl.UnknownFields
|
Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"`
|
||||||
sizeCache protoimpl.SizeCache
|
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 *NoneResponse) Reset() {
|
func (x *Response) Reset() {
|
||||||
*x = NoneResponse{}
|
*x = Response{}
|
||||||
mi := &file_proxy_blackhole_config_proto_msgTypes[0]
|
mi := &file_proxy_blackhole_config_proto_msgTypes[0]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *NoneResponse) String() string {
|
func (x *Response) String() string {
|
||||||
return protoimpl.X.MessageStringOf(x)
|
return protoimpl.X.MessageStringOf(x)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*NoneResponse) ProtoMessage() {}
|
func (*Response) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *NoneResponse) ProtoReflect() protoreflect.Message {
|
func (x *Response) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_proxy_blackhole_config_proto_msgTypes[0]
|
mi := &file_proxy_blackhole_config_proto_msgTypes[0]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
@@ -53,57 +54,35 @@ func (x *NoneResponse) ProtoReflect() protoreflect.Message {
|
|||||||
return mi.MessageOf(x)
|
return mi.MessageOf(x)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deprecated: Use NoneResponse.ProtoReflect.Descriptor instead.
|
// Deprecated: Use Response.ProtoReflect.Descriptor instead.
|
||||||
func (*NoneResponse) Descriptor() ([]byte, []int) {
|
func (*Response) Descriptor() ([]byte, []int) {
|
||||||
return file_proxy_blackhole_config_proto_rawDescGZIP(), []int{0}
|
return file_proxy_blackhole_config_proto_rawDescGZIP(), []int{0}
|
||||||
}
|
}
|
||||||
|
|
||||||
type HTTPResponse struct {
|
func (x *Response) GetType() string {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *HTTPResponse) Reset() {
|
|
||||||
*x = HTTPResponse{}
|
|
||||||
mi := &file_proxy_blackhole_config_proto_msgTypes[1]
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *HTTPResponse) String() string {
|
|
||||||
return protoimpl.X.MessageStringOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*HTTPResponse) ProtoMessage() {}
|
|
||||||
|
|
||||||
func (x *HTTPResponse) ProtoReflect() protoreflect.Message {
|
|
||||||
mi := &file_proxy_blackhole_config_proto_msgTypes[1]
|
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
return x.Type
|
||||||
if ms.LoadMessageInfo() == nil {
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
return ms
|
|
||||||
}
|
}
|
||||||
return mi.MessageOf(x)
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deprecated: Use HTTPResponse.ProtoReflect.Descriptor instead.
|
func (x *Response) GetCustomResponseData() []byte {
|
||||||
func (*HTTPResponse) Descriptor() ([]byte, []int) {
|
if x != nil {
|
||||||
return file_proxy_blackhole_config_proto_rawDescGZIP(), []int{1}
|
return x.CustomResponseData
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
Response *serial.TypedMessage `protobuf:"bytes,1,opt,name=response,proto3" json:"response,omitempty"`
|
Response *Response `protobuf:"bytes,1,opt,name=response,proto3" json:"response,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Config) Reset() {
|
func (x *Config) Reset() {
|
||||||
*x = Config{}
|
*x = Config{}
|
||||||
mi := &file_proxy_blackhole_config_proto_msgTypes[2]
|
mi := &file_proxy_blackhole_config_proto_msgTypes[1]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -115,7 +94,7 @@ func (x *Config) String() string {
|
|||||||
func (*Config) ProtoMessage() {}
|
func (*Config) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *Config) ProtoReflect() protoreflect.Message {
|
func (x *Config) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_proxy_blackhole_config_proto_msgTypes[2]
|
mi := &file_proxy_blackhole_config_proto_msgTypes[1]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -128,10 +107,10 @@ func (x *Config) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use Config.ProtoReflect.Descriptor instead.
|
// Deprecated: Use Config.ProtoReflect.Descriptor instead.
|
||||||
func (*Config) Descriptor() ([]byte, []int) {
|
func (*Config) Descriptor() ([]byte, []int) {
|
||||||
return file_proxy_blackhole_config_proto_rawDescGZIP(), []int{2}
|
return file_proxy_blackhole_config_proto_rawDescGZIP(), []int{1}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Config) GetResponse() *serial.TypedMessage {
|
func (x *Config) GetResponse() *Response {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.Response
|
return x.Response
|
||||||
}
|
}
|
||||||
@@ -142,11 +121,12 @@ var File_proxy_blackhole_config_proto protoreflect.FileDescriptor
|
|||||||
|
|
||||||
const file_proxy_blackhole_config_proto_rawDesc = "" +
|
const file_proxy_blackhole_config_proto_rawDesc = "" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"\x1cproxy/blackhole/config.proto\x12\x14xray.proxy.blackhole\x1a!common/serial/typed_message.proto\"\x0e\n" +
|
"\x1cproxy/blackhole/config.proto\x12\x14xray.proxy.blackhole\"P\n" +
|
||||||
"\fNoneResponse\"\x0e\n" +
|
"\bResponse\x12\x12\n" +
|
||||||
"\fHTTPResponse\"F\n" +
|
"\x04type\x18\x01 \x01(\tR\x04type\x120\n" +
|
||||||
"\x06Config\x12<\n" +
|
"\x14custom_response_data\x18\x02 \x01(\fR\x12customResponseData\"D\n" +
|
||||||
"\bresponse\x18\x01 \x01(\v2 .xray.common.serial.TypedMessageR\bresponseB^\n" +
|
"\x06Config\x12:\n" +
|
||||||
|
"\bresponse\x18\x01 \x01(\v2\x1e.xray.proxy.blackhole.ResponseR\bresponseB^\n" +
|
||||||
"\x18com.xray.proxy.blackholeP\x01Z)github.com/xtls/xray-core/proxy/blackhole\xaa\x02\x14Xray.Proxy.Blackholeb\x06proto3"
|
"\x18com.xray.proxy.blackholeP\x01Z)github.com/xtls/xray-core/proxy/blackhole\xaa\x02\x14Xray.Proxy.Blackholeb\x06proto3"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -161,15 +141,13 @@ func file_proxy_blackhole_config_proto_rawDescGZIP() []byte {
|
|||||||
return file_proxy_blackhole_config_proto_rawDescData
|
return file_proxy_blackhole_config_proto_rawDescData
|
||||||
}
|
}
|
||||||
|
|
||||||
var file_proxy_blackhole_config_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
|
var file_proxy_blackhole_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
|
||||||
var file_proxy_blackhole_config_proto_goTypes = []any{
|
var file_proxy_blackhole_config_proto_goTypes = []any{
|
||||||
(*NoneResponse)(nil), // 0: xray.proxy.blackhole.NoneResponse
|
(*Response)(nil), // 0: xray.proxy.blackhole.Response
|
||||||
(*HTTPResponse)(nil), // 1: xray.proxy.blackhole.HTTPResponse
|
(*Config)(nil), // 1: xray.proxy.blackhole.Config
|
||||||
(*Config)(nil), // 2: xray.proxy.blackhole.Config
|
|
||||||
(*serial.TypedMessage)(nil), // 3: xray.common.serial.TypedMessage
|
|
||||||
}
|
}
|
||||||
var file_proxy_blackhole_config_proto_depIdxs = []int32{
|
var file_proxy_blackhole_config_proto_depIdxs = []int32{
|
||||||
3, // 0: xray.proxy.blackhole.Config.response:type_name -> xray.common.serial.TypedMessage
|
0, // 0: xray.proxy.blackhole.Config.response:type_name -> xray.proxy.blackhole.Response
|
||||||
1, // [1:1] is the sub-list for method output_type
|
1, // [1:1] is the sub-list for method output_type
|
||||||
1, // [1:1] is the sub-list for method input_type
|
1, // [1:1] is the sub-list for method input_type
|
||||||
1, // [1:1] is the sub-list for extension type_name
|
1, // [1:1] is the sub-list for extension type_name
|
||||||
@@ -188,7 +166,7 @@ func file_proxy_blackhole_config_proto_init() {
|
|||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_proxy_blackhole_config_proto_rawDesc), len(file_proxy_blackhole_config_proto_rawDesc)),
|
RawDescriptor: unsafe.Slice(unsafe.StringData(file_proxy_blackhole_config_proto_rawDesc), len(file_proxy_blackhole_config_proto_rawDesc)),
|
||||||
NumEnums: 0,
|
NumEnums: 0,
|
||||||
NumMessages: 3,
|
NumMessages: 2,
|
||||||
NumExtensions: 0,
|
NumExtensions: 0,
|
||||||
NumServices: 0,
|
NumServices: 0,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,12 +6,11 @@ option go_package = "github.com/xtls/xray-core/proxy/blackhole";
|
|||||||
option java_package = "com.xray.proxy.blackhole";
|
option java_package = "com.xray.proxy.blackhole";
|
||||||
option java_multiple_files = true;
|
option java_multiple_files = true;
|
||||||
|
|
||||||
import "common/serial/typed_message.proto";
|
message Response {
|
||||||
|
string type = 1;
|
||||||
message NoneResponse {}
|
bytes custom_response_data = 2;
|
||||||
|
}
|
||||||
message HTTPResponse {}
|
|
||||||
|
|
||||||
message Config {
|
message Config {
|
||||||
xray.common.serial.TypedMessage response = 1;
|
Response response = 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +1,19 @@
|
|||||||
package blackhole_test
|
package blackhole_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"context"
|
||||||
"net/http"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
"github.com/xtls/xray-core/common/buf"
|
"github.com/xtls/xray-core/proxy/blackhole"
|
||||||
. "github.com/xtls/xray-core/proxy/blackhole"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestHTTPResponse(t *testing.T) {
|
func TestHTTPResponse(t *testing.T) {
|
||||||
buffer := buf.New()
|
handler, err := blackhole.New(context.Background(), &blackhole.Config{
|
||||||
|
Response: &blackhole.Response{Type: "http"},
|
||||||
httpResponse := new(HTTPResponse)
|
})
|
||||||
httpResponse.WriteTo(buf.NewWriter(buffer))
|
|
||||||
|
|
||||||
reader := bufio.NewReader(buffer)
|
|
||||||
response, err := http.ReadResponse(reader, nil)
|
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
|
if handler == nil {
|
||||||
if response.StatusCode != 403 {
|
t.Error("expected HTTP response handler")
|
||||||
t.Error("expected status code 403, but got ", response.StatusCode)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user