Compare commits

...
3 Commits
Author SHA1 Message Date
Fangliding f8cdf7d238 Use std parser instead of homemade 2026-09-02 19:17:32 +08:00
meanwhile131andGitHub 5e245b082e Routing: Export HealthCheckSettings (#6680) 2026-08-30 12:19:54 +00:00
d9c54026c5 Sniffing: Support QUICv2 (#6695)
Co-authored-by: 风扇滑翔翼 <Fangliding.fshxy@outlook.com>
2026-08-30 12:15:25 +00:00
7 changed files with 212 additions and 203 deletions
+53 -62
View File
@@ -1,13 +1,16 @@
package http package http
import ( import (
"bufio"
"bytes" "bytes"
"context" "context"
"errors" "errors"
"io"
"net/http"
"strings" "strings"
"unsafe"
"github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/session" "github.com/xtls/xray-core/common/session"
) )
@@ -39,79 +42,67 @@ func (h *SniffHeader) Domain() string {
} }
var ( var (
methods = [...]string{"get", "post", "head", "put", "delete", "options", "connect"} validMethods = map[string]bool{}
errNotHTTP = errors.New("not an HTTP request")
errNotHTTPMethod = errors.New("not an HTTP method")
) )
func beginWithHTTPMethod(b []byte) error { func init() {
for _, m := range &methods { // https://www.iana.org/assignments/http-methods
if len(b) >= len(m) && strings.EqualFold(string(b[:len(m)]), m) { methods := []string{
return nil "ACL", "BASELINE-CONTROL", "BIND", "CHECKIN", "CHECKOUT",
} "CONNECT", "COPY", "DELETE", "GET", "HEAD",
"LABEL", "LINK", "LOCK", "MERGE", "MKACTIVITY",
if len(b) < len(m) { "MKCALENDAR", "MKCOL", "MKREDIRECTREF", "MKWORKSPACE", "MOVE",
return common.ErrNoClue "OPTIONS", "ORDERPATCH", "PATCH", "POST", "PRI",
} "PROPFIND", "PROPPATCH", "PUT", "QUERY", "REBIND",
"REPORT", "SEARCH", "TRACE", "UNBIND", "UNCHECKOUT",
"UNLINK", "UNLOCK", "UPDATE", "UPDATEREDIRECTREF", "VERSION-CONTROL",
} }
for _, m := range methods {
validMethods[m] = true
}
}
return errNotHTTPMethod func isValidHTTPMethod(b []byte) bool {
if len(b) == 0 {
return false
}
idx := bytes.IndexByte(b, ' ')
if idx == -1 {
return false
}
method := unsafe.String(unsafe.SliceData(b), idx)
return validMethods[method]
} }
func SniffHTTP(b []byte, c context.Context) (*SniffHeader, error) { func SniffHTTP(b []byte, c context.Context) (*SniffHeader, error) {
if !isValidHTTPMethod(b) {
return nil, errNotHTTP
}
content := session.ContentFromContext(c) content := session.ContentFromContext(c)
ShouldSniffAttr := true r, err := http.ReadRequest(bufio.NewReader(bytes.NewReader(b)))
// If content.Attributes have information, that means it comes from HTTP inbound PlainHTTP mode. if err != nil {
// It will set attributes, so skip it. if err == io.ErrUnexpectedEOF {
if content == nil || len(content.Attributes) != 0 { return nil, common.ErrNoClue
ShouldSniffAttr = false }
return nil, errNotHTTP
} }
if err := beginWithHTTPMethod(b); err != nil { if r.Host == "" {
return nil, err return nil, common.ErrNoClue
} }
sh := &SniffHeader{ sh := &SniffHeader{
version: HTTP1, version: HTTP1,
host: r.Host,
}
// If content.Attributes have information, that means it comes from HTTP inbound PlainHTTP mode.
// It will set attributes, so skip it.
if content != nil && len(content.Attributes) == 0 {
for key, h := range r.Header {
content.Attributes[key] = strings.Join(h, ",")
}
content.Attributes[":method"] = r.Method
content.Attributes[":path"] = r.URL.Path
} }
headers := bytes.Split(b, []byte{'\n'}) return sh, nil
for i := 1; i < len(headers); i++ {
header := headers[i]
if len(header) == 0 {
break
}
parts := bytes.SplitN(header, []byte{':'}, 2)
if len(parts) != 2 {
continue
}
key := strings.ToLower(string(parts[0]))
value := string(bytes.TrimSpace(parts[1]))
if ShouldSniffAttr {
content.SetAttribute(key, value) // Put header in attribute
}
if key == "host" {
rawHost := strings.ToLower(value)
dest, err := ParseHost(rawHost, net.Port(80))
if err != nil {
return nil, err
}
sh.host = dest.Address.String()
}
}
// Parse request line
// Request line is like this
// "GET /homo/114514 HTTP/1.1"
if len(headers) > 0 && ShouldSniffAttr {
RequestLineParts := bytes.Split(headers[0], []byte{' '})
if len(RequestLineParts) == 3 {
content.SetAttribute(":method", string(RequestLineParts[0]))
content.SetAttribute(":path", string(RequestLineParts[1]))
}
}
if len(sh.host) > 0 {
return sh, nil
}
return nil, common.ErrNoClue
} }
+57 -55
View File
@@ -14,75 +14,76 @@ func TestHTTPHeaders(t *testing.T) {
err bool err bool
}{ }{
{ {
input: `GET /tutorials/other/top-20-mysql-best-practices/ HTTP/1.1 input: "GET /tutorials/other/top-20-mysql-best-practices/ HTTP/1.1\r\n" +
Host: net.tutsplus.com "Host: net.tutsplus.com\r\n" +
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5 (.NET CLR 3.5.30729) "User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5 (.NET CLR 3.5.30729)\r\n" +
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" +
Accept-Language: en-us,en;q=0.5 "Accept-Language: en-us,en;q=0.5\r\n" +
Accept-Encoding: gzip,deflate "Accept-Encoding: gzip,deflate\r\n" +
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n" +
Keep-Alive: 300 "Keep-Alive: 300\r\n" +
Connection: keep-alive "Connection: keep-alive\r\n" +
Cookie: PHPSESSID=r2t5uvjq435r4q7ib3vtdjq120 "Cookie: PHPSESSID=r2t5uvjq435r4q7ib3vtdjq120\r\n" +
Pragma: no-cache "Pragma: no-cache\r\n" +
Cache-Control: no-cache`, "Cache-Control: no-cache\r\n" +
"\r\n",
domain: "net.tutsplus.com", domain: "net.tutsplus.com",
}, },
{ {
input: `POST /foo.php HTTP/1.1 input: "POST /foo.php HTTP/1.1\r\n" +
Host: localhost "Host: localhost\r\n" +
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5 (.NET CLR 3.5.30729) "User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5 (.NET CLR 3.5.30729)\r\n" +
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" +
Accept-Language: en-us,en;q=0.5 "Accept-Language: en-us,en;q=0.5\r\n" +
Accept-Encoding: gzip,deflate "Accept-Encoding: gzip,deflate\r\n" +
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n" +
Keep-Alive: 300 "Keep-Alive: 300\r\n" +
Connection: keep-alive "Connection: keep-alive\r\n" +
Referer: http://localhost/test.php "Referer: http://localhost/test.php\r\n" +
Content-Type: application/x-www-form-urlencoded "Content-Type: application/x-www-form-urlencoded\r\n" +
Content-Length: 43 "Content-Length: 43\r\n" +
"\r\n" +
first_name=John&last_name=Doe&action=Submit`, "first_name=John&last_name=Doe&action=Submit",
domain: "localhost", domain: "localhost",
}, },
{ {
input: `X /foo.php HTTP/1.1 input: "X /foo.php HTTP/1.1\r\n" +
Host: localhost "Host: localhost\r\n" +
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5 (.NET CLR 3.5.30729) "User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5 (.NET CLR 3.5.30729)\r\n" +
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" +
Accept-Language: en-us,en;q=0.5 "Accept-Language: en-us,en;q=0.5\r\n" +
Accept-Encoding: gzip,deflate "Accept-Encoding: gzip,deflate\r\n" +
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n" +
Keep-Alive: 300 "Keep-Alive: 300\r\n" +
Connection: keep-alive "Connection: keep-alive\r\n" +
Referer: http://localhost/test.php "Referer: http://localhost/test.php\r\n" +
Content-Type: application/x-www-form-urlencoded "Content-Type: application/x-www-form-urlencoded\r\n" +
Content-Length: 43 "Content-Length: 43\r\n" +
"\r\n" +
first_name=John&last_name=Doe&action=Submit`, "first_name=John&last_name=Doe&action=Submit",
domain: "", domain: "",
err: true, err: true,
}, },
{ {
input: `GET /foo.php HTTP/1.1 input: "GET /foo.php HTTP/1.1\r\n" +
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5 (.NET CLR 3.5.30729) "User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5 (.NET CLR 3.5.30729)\r\n" +
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" +
Accept-Language: en-us,en;q=0.5 "Accept-Language: en-us,en;q=0.5\r\n" +
Accept-Encoding: gzip,deflate "Accept-Encoding: gzip,deflate\r\n" +
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n" +
Keep-Alive: 300 "Keep-Alive: 300\r\n" +
Connection: keep-alive "Connection: keep-alive\r\n" +
Referer: http://localhost/test.php "Referer: http://localhost/test.php\r\n" +
Content-Type: application/x-www-form-urlencoded "Content-Type: application/x-www-form-urlencoded\r\n" +
Content-Length: 43 "Content-Length: 43\r\n" +
"\r\n" +
Host: localhost "Host: localhost\r\n" +
first_name=John&last_name=Doe&action=Submit`, "first_name=John&last_name=Doe&action=Submit",
domain: "", domain: "",
err: true, err: true,
}, },
{ {
input: `GET /tutorials/other/top-20-mysql-best-practices/ HTTP/1.1`, input: "GET /tutorials/other/top-20-mysql-best-practices/ HTTP/1.1\r\n",
domain: "", domain: "",
err: true, err: true,
}, },
@@ -97,6 +98,7 @@ first_name=John&last_name=Doe&action=Submit`,
} else { } else {
if err != nil { if err != nil {
t.Errorf("Expect no error but actually %s in test %v", err.Error(), test) t.Errorf("Expect no error but actually %s in test %v", err.Error(), test)
continue
} }
if header.Domain() != test.domain { if header.Domain() != test.domain {
t.Error("expected domain ", test.domain, " but got ", header.Domain()) t.Error("expected domain ", test.domain, " but got ", header.Domain())
-8
View File
@@ -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
+89 -74
View File
@@ -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
+1 -1
View File
@@ -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) {
+3 -3
View File
@@ -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"