mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-10-08 08:40:01 +00:00
Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f8cdf7d238 | ||
|
|
5e245b082e | ||
|
|
d9c54026c5 |
@@ -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
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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())
|
||||||
|
|||||||
@@ -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
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user