mirror of
https://github.com/daeuniverse/dae.git
synced 2024-12-22 20:34:40 +07:00
optimize(udp)/fix(quicSniffer): optimize performance of udp and fix a potential panic of quic (#301)
This commit is contained in:
parent
dedc716413
commit
25c047a766
@ -27,7 +27,7 @@ var (
|
||||
systemDns netip.AddrPort
|
||||
systemDnsNextUpdateAfter time.Time
|
||||
|
||||
BadDnsAnsError = fmt.Errorf("bad dns answer")
|
||||
ErrBadDnsAns = fmt.Errorf("bad dns answer")
|
||||
|
||||
BootstrapDns = netip.MustParseAddrPort("208.67.222.222:5353")
|
||||
)
|
||||
@ -107,13 +107,13 @@ func ResolveNetip(ctx context.Context, d netproxy.Dialer, dns netip.AddrPort, ho
|
||||
case dnsmessage.TypeA:
|
||||
a, ok := ans.(*dnsmessage.A)
|
||||
if !ok {
|
||||
return nil, BadDnsAnsError
|
||||
return nil, ErrBadDnsAns
|
||||
}
|
||||
ip, okk = netip.AddrFromSlice(a.A)
|
||||
case dnsmessage.TypeAAAA:
|
||||
a, ok := ans.(*dnsmessage.AAAA)
|
||||
if !ok {
|
||||
return nil, BadDnsAnsError
|
||||
return nil, ErrBadDnsAns
|
||||
}
|
||||
ip, okk = netip.AddrFromSlice(a.AAAA)
|
||||
}
|
||||
@ -137,7 +137,7 @@ func ResolveNS(ctx context.Context, d netproxy.Dialer, dns netip.AddrPort, host
|
||||
}
|
||||
ns, ok := ans.(*dnsmessage.NS)
|
||||
if !ok {
|
||||
return nil, BadDnsAnsError
|
||||
return nil, ErrBadDnsAns
|
||||
}
|
||||
records = append(records, ns.Ns)
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ import (
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var BadUpstreamFormatError = fmt.Errorf("bad upstream format")
|
||||
var ErrBadUpstreamFormat = fmt.Errorf("bad upstream format")
|
||||
|
||||
type Dns struct {
|
||||
log *logrus.Logger
|
||||
@ -55,12 +55,12 @@ func New(dns *config.Dns, opt *NewOption) (s *Dns, err error) {
|
||||
|
||||
tag, link := common.GetTagFromLinkLikePlaintext(string(upstreamRaw))
|
||||
if tag == "" {
|
||||
return nil, fmt.Errorf("%w: '%v' has no tag", BadUpstreamFormatError, upstreamRaw)
|
||||
return nil, fmt.Errorf("%w: '%v' has no tag", ErrBadUpstreamFormat, upstreamRaw)
|
||||
}
|
||||
var u *url.URL
|
||||
u, err = url.Parse(link)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %v", BadUpstreamFormatError, err)
|
||||
return nil, fmt.Errorf("%w: %v", ErrBadUpstreamFormat, err)
|
||||
}
|
||||
r := &UpstreamResolver{
|
||||
Raw: u,
|
||||
|
@ -20,7 +20,7 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
FormatError = fmt.Errorf("format error")
|
||||
ErrFormat = fmt.Errorf("format error")
|
||||
)
|
||||
|
||||
type UpstreamScheme string
|
||||
@ -75,7 +75,7 @@ type Upstream struct {
|
||||
func NewUpstream(ctx context.Context, upstream *url.URL, resolverNetwork string) (up *Upstream, err error) {
|
||||
scheme, hostname, port, err := ParseRawUpstream(upstream)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %v", FormatError, err)
|
||||
return nil, fmt.Errorf("%w: %v", ErrFormat, err)
|
||||
}
|
||||
|
||||
systemDns, err := netutils.SystemDns()
|
||||
|
@ -6,7 +6,6 @@
|
||||
package dialer
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
@ -27,6 +26,7 @@ import (
|
||||
"github.com/daeuniverse/dae/common/netutils"
|
||||
"github.com/daeuniverse/softwind/netproxy"
|
||||
"github.com/daeuniverse/softwind/pkg/fastrand"
|
||||
"github.com/daeuniverse/softwind/pool"
|
||||
"github.com/daeuniverse/softwind/protocol/direct"
|
||||
dnsmessage "github.com/miekg/dns"
|
||||
"github.com/sirupsen/logrus"
|
||||
@ -604,7 +604,8 @@ func (d *Dialer) HttpCheck(ctx context.Context, u *netutils.URL, ip netip.Addr,
|
||||
if page := path.Base(req.URL.Path); strings.HasPrefix(page, "generate_") {
|
||||
if strconv.Itoa(resp.StatusCode) != strings.TrimPrefix(page, "generate_") {
|
||||
b, _ := io.ReadAll(resp.Body)
|
||||
buf := bytes.NewBuffer(nil)
|
||||
buf := pool.GetBuffer()
|
||||
defer pool.PutBuffer(buf)
|
||||
_ = resp.Request.Write(buf)
|
||||
d.Log.Debugln(buf.String(), "Resp: ", string(b))
|
||||
return false, fmt.Errorf("unexpected status code: %v", resp.StatusCode)
|
||||
|
@ -72,6 +72,19 @@ func TproxyControl(c syscall.RawConn) error {
|
||||
return sockOptErr
|
||||
}
|
||||
|
||||
func TransparentControl(c syscall.RawConn) error {
|
||||
var sockOptErr error
|
||||
controlErr := c.Control(func(fd uintptr) {
|
||||
if err := syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_TRANSPARENT, 1); err != nil {
|
||||
sockOptErr = fmt.Errorf("error setting IP_TRANSPARENT socket option: %w", err)
|
||||
}
|
||||
})
|
||||
if controlErr != nil {
|
||||
return fmt.Errorf("error invoking socket control function: %w", controlErr)
|
||||
}
|
||||
return sockOptErr
|
||||
}
|
||||
|
||||
func BindControl(c syscall.RawConn, lAddrPort netip.AddrPort) error {
|
||||
var sockOptErr error
|
||||
controlErr := c.Control(func(fd uintptr) {
|
||||
|
@ -16,7 +16,7 @@ import (
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var NoAliveDialerError = fmt.Errorf("no alive dialer")
|
||||
var ErrNoAliveDialer = fmt.Errorf("no alive dialer")
|
||||
|
||||
type DialerGroup struct {
|
||||
netproxy.Dialer
|
||||
@ -226,14 +226,14 @@ func (d *DialerGroup) MustGetAliveDialerSet(typ *dialer.NetworkType) *dialer.Ali
|
||||
func (g *DialerGroup) Select(networkType *dialer.NetworkType, strictIpVersion bool) (d *dialer.Dialer, latency time.Duration, err error) {
|
||||
policy := g.selectionPolicy
|
||||
d, latency, err = g._select(networkType, policy)
|
||||
if !strictIpVersion && errors.Is(err, NoAliveDialerError) {
|
||||
if !strictIpVersion && errors.Is(err, ErrNoAliveDialer) {
|
||||
networkType.IpVersion = (consts.IpVersion_X - networkType.IpVersion.ToIpVersionType()).ToIpVersionStr()
|
||||
return g._select(networkType, policy)
|
||||
}
|
||||
if err == nil {
|
||||
return d, latency, nil
|
||||
}
|
||||
if errors.Is(err, NoAliveDialerError) && len(g.Dialers) == 1 {
|
||||
if errors.Is(err, ErrNoAliveDialer) && len(g.Dialers) == 1 {
|
||||
// There is only one dialer in this group. Just choose it instead of return error.
|
||||
if d, _, err = g._select(networkType, &DialerSelectionPolicy{
|
||||
Policy: consts.DialerSelectionPolicy_Fixed,
|
||||
@ -256,7 +256,7 @@ func (g *DialerGroup) _select(networkType *dialer.NetworkType, policy *DialerSel
|
||||
d := a.GetRand()
|
||||
if d == nil {
|
||||
// No alive dialer.
|
||||
return nil, time.Hour, NoAliveDialerError
|
||||
return nil, time.Hour, ErrNoAliveDialer
|
||||
}
|
||||
return d, 0, nil
|
||||
|
||||
@ -272,7 +272,7 @@ func (g *DialerGroup) _select(networkType *dialer.NetworkType, policy *DialerSel
|
||||
d, latency := a.GetMinLatency()
|
||||
if d == nil {
|
||||
// No alive dialer.
|
||||
return nil, time.Hour, NoAliveDialerError
|
||||
return nil, time.Hour, ErrNoAliveDialer
|
||||
}
|
||||
return d, latency, nil
|
||||
|
||||
|
@ -17,10 +17,10 @@ type ConnSniffer struct {
|
||||
*Sniffer
|
||||
}
|
||||
|
||||
func NewConnSniffer(conn net.Conn, snifferBufSize int, dataWaitingTimeout time.Duration) *ConnSniffer {
|
||||
func NewConnSniffer(conn net.Conn, snifferBufSize int, timeout time.Duration) *ConnSniffer {
|
||||
s := &ConnSniffer{
|
||||
Conn: conn,
|
||||
Sniffer: NewStreamSniffer(conn, snifferBufSize, dataWaitingTimeout),
|
||||
Sniffer: NewStreamSniffer(conn, snifferBufSize, timeout),
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
@ -8,34 +8,35 @@ package sniffing
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"github.com/daeuniverse/dae/common"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/daeuniverse/dae/common"
|
||||
)
|
||||
|
||||
func (s *Sniffer) SniffHttp() (d string, err error) {
|
||||
// First byte should be printable.
|
||||
if len(s.buf) == 0 || !unicode.IsPrint(rune(s.buf[0])) {
|
||||
return "", NotApplicableError
|
||||
if s.buf.Len() == 0 || !unicode.IsPrint(rune(s.buf.Bytes()[0])) {
|
||||
return "", ErrNotApplicable
|
||||
}
|
||||
|
||||
// Search method.
|
||||
search := s.buf
|
||||
search := s.buf.Bytes()
|
||||
if len(search) > 12 {
|
||||
search = search[:12]
|
||||
}
|
||||
method, _, found := bytes.Cut(search, []byte(" "))
|
||||
if !found {
|
||||
return "", NotApplicableError
|
||||
return "", ErrNotApplicable
|
||||
}
|
||||
if !common.IsValidHttpMethod(string(method)) {
|
||||
return "", NotApplicableError
|
||||
return "", ErrNotApplicable
|
||||
}
|
||||
|
||||
// Now we assume it is an HTTP packet. We should not return NotApplicableError after here.
|
||||
|
||||
// Search Host.
|
||||
scanner := bufio.NewScanner(bytes.NewReader(s.buf))
|
||||
scanner := bufio.NewScanner(bytes.NewReader(s.buf.Bytes()))
|
||||
// \r\n
|
||||
scanner.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) {
|
||||
if atEOF && len(data) == 0 {
|
||||
@ -62,5 +63,5 @@ func (s *Sniffer) SniffHttp() (d string, err error) {
|
||||
return strings.TrimSpace(string(value)), nil
|
||||
}
|
||||
}
|
||||
return "", NotFoundError
|
||||
return "", ErrNotFound
|
||||
}
|
||||
|
@ -10,10 +10,11 @@ import (
|
||||
"crypto/cipher"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
|
||||
"github.com/daeuniverse/dae/common"
|
||||
"github.com/daeuniverse/softwind/pool"
|
||||
"golang.org/x/crypto/hkdf"
|
||||
"io"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -113,7 +114,7 @@ func (k *Keys) HeaderProtection_(sample []byte, longHeader bool, firstByte *byte
|
||||
return packetNumber, nil
|
||||
}
|
||||
|
||||
func (k *Keys) PayloadDecryptFromPool(ciphertext []byte, packetNumber []byte, header []byte) (plaintext []byte, err error) {
|
||||
func (k *Keys) PayloadDecrypt(ciphertext []byte, packetNumber []byte, header []byte) (plaintext []byte, err error) {
|
||||
// https://datatracker.ietf.org/doc/html/rfc9001#name-initial-secrets
|
||||
|
||||
aead, err := k.newAead(k.key)
|
||||
@ -125,15 +126,15 @@ func (k *Keys) PayloadDecryptFromPool(ciphertext []byte, packetNumber []byte, he
|
||||
for i := range packetNumber {
|
||||
k.iv[len(k.iv)-len(packetNumber)+i] ^= packetNumber[i]
|
||||
}
|
||||
plaintext = pool.Get(len(ciphertext) - aead.Overhead())
|
||||
plaintext = make([]byte, len(ciphertext)-aead.Overhead())
|
||||
plaintext, err = aead.Open(plaintext[:0], k.iv, ciphertext, header)
|
||||
if err != nil {
|
||||
pool.Put(plaintext)
|
||||
// Do nothing.
|
||||
}
|
||||
return plaintext, nil
|
||||
}
|
||||
|
||||
func DecryptQuicFromPool_(header []byte, blockEnd int, destConnId []byte) (plaintext []byte, err error) {
|
||||
func DecryptQuic_(header []byte, blockEnd int, destConnId []byte) (plaintext []byte, err error) {
|
||||
_version := binary.BigEndian.Uint32(header[1:])
|
||||
version, err := ParseVersion(_version)
|
||||
if err != nil {
|
||||
@ -158,7 +159,7 @@ func DecryptQuicFromPool_(header []byte, blockEnd int, destConnId []byte) (plain
|
||||
header = header[:len(header)-MaxPacketNumberLength+len(packetNumber)] // Correct header
|
||||
payload := header[len(header):blockEnd] // Correct payload
|
||||
|
||||
plaintext, err = keys.PayloadDecryptFromPool(payload, packetNumber, header)
|
||||
plaintext, err = keys.PayloadDecrypt(payload, packetNumber, header)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -89,7 +89,7 @@ func TestKeys_PayloadDecrypt_(t *testing.T) {
|
||||
}
|
||||
header = data[:len(header)-4+len(packetNumber)]
|
||||
payload := data[len(header):]
|
||||
plaintext, err := keys.PayloadDecryptFromPool(payload, packetNumber, header)
|
||||
plaintext, err := keys.PayloadDecrypt(payload, packetNumber, header)
|
||||
if err != nil {
|
||||
t.Fatal("PayloadDecryptFromPool:", err)
|
||||
}
|
||||
|
@ -7,14 +7,13 @@ package quicutils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/daeuniverse/softwind/pool"
|
||||
"io/fs"
|
||||
"sort"
|
||||
)
|
||||
|
||||
var (
|
||||
UnknownFrameTypeError = fmt.Errorf("unknown frame type")
|
||||
OutOfRangeError = fmt.Errorf("index out of range")
|
||||
ErrUnknownFrameType = fmt.Errorf("unknown frame type")
|
||||
ErrOutOfRange = fmt.Errorf("index out of range")
|
||||
)
|
||||
|
||||
const (
|
||||
@ -31,73 +30,57 @@ type CryptoFrameOffset struct {
|
||||
Data []byte
|
||||
}
|
||||
|
||||
type CryptoFrameRelocation struct {
|
||||
payload []byte
|
||||
o []*CryptoFrameOffset
|
||||
length int
|
||||
}
|
||||
|
||||
func NewCryptoFrameRelocation(plaintextPayload []byte) (cryptoRelocation *CryptoFrameRelocation, err error) {
|
||||
var frameSize int
|
||||
var offset *CryptoFrameOffset
|
||||
cryptoRelocation = &CryptoFrameRelocation{
|
||||
payload: plaintextPayload,
|
||||
o: nil,
|
||||
}
|
||||
|
||||
// Extract crypto frames.
|
||||
for iNextFrame := 0; iNextFrame < len(plaintextPayload); iNextFrame += frameSize {
|
||||
offset, frameSize, err = ExtractCryptoFrameOffset(plaintextPayload[iNextFrame:], iNextFrame)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if offset == nil {
|
||||
continue
|
||||
}
|
||||
cryptoRelocation.o = append(cryptoRelocation.o, offset)
|
||||
}
|
||||
|
||||
// Sort offsets by UpperAppOffset.
|
||||
sort.Slice(cryptoRelocation.o, func(i, j int) bool {
|
||||
return cryptoRelocation.o[i].UpperAppOffset < cryptoRelocation.o[j].UpperAppOffset
|
||||
})
|
||||
|
||||
// Store length.
|
||||
left := cryptoRelocation.o[0]
|
||||
right := cryptoRelocation.o[len(cryptoRelocation.o)-1]
|
||||
cryptoRelocation.length = right.UpperAppOffset + len(right.Data) - left.UpperAppOffset
|
||||
|
||||
return cryptoRelocation, nil
|
||||
}
|
||||
|
||||
func ReassembleCryptoToBytesFromPool(plaintextPayload []byte) (b []byte, err error) {
|
||||
func ReassembleCryptos(offsets []*CryptoFrameOffset, newPayload []byte) (newOffsets []*CryptoFrameOffset, err error) {
|
||||
oldLen := len(offsets)
|
||||
var frameSize int
|
||||
var offset *CryptoFrameOffset
|
||||
var boundary int
|
||||
b = pool.Get(len(plaintextPayload))
|
||||
// Extract crypto frames.
|
||||
for iNextFrame := 0; iNextFrame < len(plaintextPayload); iNextFrame += frameSize {
|
||||
offset, frameSize, err = ExtractCryptoFrameOffset(plaintextPayload[iNextFrame:], iNextFrame)
|
||||
for iNextFrame := 0; iNextFrame < len(newPayload); iNextFrame += frameSize {
|
||||
offset, frameSize, err = ExtractCryptoFrameOffset(newPayload[iNextFrame:], iNextFrame)
|
||||
if err != nil {
|
||||
pool.Put(b)
|
||||
return nil, err
|
||||
}
|
||||
if offset == nil {
|
||||
continue
|
||||
}
|
||||
copy(b[offset.UpperAppOffset:], offset.Data)
|
||||
offsets = append(offsets, offset)
|
||||
if offset.UpperAppOffset+len(offset.Data) > boundary {
|
||||
boundary = offset.UpperAppOffset + len(offset.Data)
|
||||
}
|
||||
}
|
||||
return b[:boundary], nil
|
||||
// Sort the new part.
|
||||
newPart := offsets[oldLen:]
|
||||
sort.Slice(newPart, func(i, j int) bool {
|
||||
return newPart[i].UpperAppOffset < newPart[j].UpperAppOffset
|
||||
})
|
||||
|
||||
// Insertion sort.
|
||||
for i := oldLen; i < len(offsets); i++ {
|
||||
item := offsets[i]
|
||||
j := i - 1
|
||||
for ; j >= 0; j-- {
|
||||
if item.UpperAppOffset < offsets[j].UpperAppOffset {
|
||||
offsets[j+1] = offsets[j]
|
||||
} else {
|
||||
if offsets[j+1] != item {
|
||||
offsets[j+1] = item
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if j < 0 {
|
||||
offsets[0] = item
|
||||
}
|
||||
}
|
||||
return offsets, nil
|
||||
}
|
||||
|
||||
func ExtractCryptoFrameOffset(remainder []byte, transportOffset int) (offset *CryptoFrameOffset, frameSize int, err error) {
|
||||
if len(remainder) == 0 {
|
||||
return nil, 0, fmt.Errorf("frame has no length: %w", OutOfRangeError)
|
||||
return nil, 0, fmt.Errorf("frame has no length: %w", ErrOutOfRange)
|
||||
}
|
||||
frameType, nextField, err := BigEndianUvarint(remainder[:])
|
||||
frameType, nextField, err := BigEndianUvarint(remainder)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
@ -128,112 +111,20 @@ func ExtractCryptoFrameOffset(remainder []byte, transportOffset int) (offset *Cr
|
||||
case Quic_FrameType_ConnectionClose, Quic_FrameType_ConnectionClose2:
|
||||
return nil, 0, fmt.Errorf("connection closed: %w", fs.ErrClosed)
|
||||
default:
|
||||
return nil, 0, fmt.Errorf("%w: %v", UnknownFrameTypeError, frameType)
|
||||
return nil, 0, fmt.Errorf("%w: %v", ErrUnknownFrameType, frameType)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *CryptoFrameRelocation) BinarySearch(iUpper int, leftOuter, rightOuter int) (iOuter int, iInner int, err error) {
|
||||
rightOuterInstance := r.o[rightOuter]
|
||||
if iUpper < r.o[leftOuter].UpperAppOffset || iUpper >= rightOuterInstance.UpperAppOffset+len(rightOuterInstance.Data) {
|
||||
return 0, 0, fmt.Errorf("%w: %v is not in [%v, %v)", OutOfRangeError, iUpper, r.o[leftOuter].UpperAppOffset, rightOuterInstance.UpperAppOffset+len(rightOuterInstance.Data))
|
||||
}
|
||||
for leftOuter < rightOuter {
|
||||
mid := leftOuter + ((rightOuter - leftOuter) >> 1)
|
||||
if iUpper < r.o[mid].UpperAppOffset {
|
||||
rightOuter = mid - 1
|
||||
} else if iUpper >= r.o[mid].UpperAppOffset {
|
||||
if iUpper < r.o[mid].UpperAppOffset+len(r.o[mid].Data) {
|
||||
return mid, iUpper - r.o[mid].UpperAppOffset, nil
|
||||
} else {
|
||||
leftOuter = mid + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
return leftOuter, iUpper - r.o[leftOuter].UpperAppOffset, nil
|
||||
}
|
||||
|
||||
func (r *CryptoFrameRelocation) BytesFromPool() []byte {
|
||||
if len(r.o) == 0 {
|
||||
return pool.Get(0)
|
||||
}
|
||||
right := r.o[len(r.o)-1]
|
||||
return r.copyBytesToPool(0, 0, len(r.o)-1, len(right.Data)-1, r.length)
|
||||
}
|
||||
|
||||
// RangeFromPool copy bytes from iUpperAppOffset to jUpperAppOffset.
|
||||
// It is not suggested to use it for large range and frequent copy.
|
||||
func (r *CryptoFrameRelocation) RangeFromPool(i, j int) []byte {
|
||||
if i > j {
|
||||
panic(fmt.Sprintf("i > j: %v > %v", i, j))
|
||||
}
|
||||
// We find bytes including i and j, so we should sub j with 1.
|
||||
j--
|
||||
|
||||
// Find i.
|
||||
iOuter, iInner, err := r.BinarySearch(i, 0, len(r.o)-1)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// Check if j and i is in the same outer or adjacent outers.
|
||||
// It is very common because we usually have small access range.
|
||||
var jOuter, jInner int
|
||||
if iInner+j-i < len(r.o[iOuter].Data) {
|
||||
jOuter = iOuter
|
||||
jInner = iInner + j - i
|
||||
} else if iOuter+1 < len(r.o) && j < r.o[iOuter+1].UpperAppOffset+len(r.o[iOuter+1].Data) {
|
||||
jOuter = iOuter + 1
|
||||
jInner = (j - i) + (len(r.o[iOuter].Data) - iInner)
|
||||
} else {
|
||||
// We have searched iOuter and iOuter+1
|
||||
jOuter, jInner, err = r.BinarySearch(j, iOuter+2, len(r.o)-1)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
return r.copyBytesToPool(iOuter, iInner, jOuter, jInner, j-i+1)
|
||||
}
|
||||
|
||||
// copyBytesToPool copy bytes including i and j.
|
||||
func (r *CryptoFrameRelocation) copyBytesToPool(iOuter, iInner, jOuter, jInner, size int) []byte {
|
||||
b := pool.Get(size)
|
||||
//io := r.o[iOuter]
|
||||
k := 0
|
||||
for {
|
||||
// Most accesses are small range accesses.
|
||||
base := r.o[iOuter].Data
|
||||
if iOuter == jOuter {
|
||||
k += copy(b[k:], base[iInner:jInner+1])
|
||||
if k != size {
|
||||
panic("unmatched size")
|
||||
}
|
||||
return b
|
||||
} else {
|
||||
k += copy(b[k:], base[iInner:])
|
||||
if iInner != 0 {
|
||||
iInner = 0
|
||||
}
|
||||
iOuter++
|
||||
}
|
||||
}
|
||||
}
|
||||
func (r *CryptoFrameRelocation) At(i int) byte {
|
||||
iOuter, iInner, err := r.BinarySearch(i, 0, len(r.o)-1)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return r.o[iOuter].Data[iInner]
|
||||
}
|
||||
|
||||
func (r *CryptoFrameRelocation) Len() int {
|
||||
return r.length
|
||||
}
|
||||
var (
|
||||
ErrMissingCrypto = fmt.Errorf("missing crypto frame")
|
||||
)
|
||||
|
||||
type Locator interface {
|
||||
Range(i, j int) []byte
|
||||
Slice(i, j int) Locator
|
||||
At(i int) byte
|
||||
Range(i, j int) ([]byte, error)
|
||||
Slice(i, j int) (Locator, error)
|
||||
At(i int) (byte, error)
|
||||
Len() int
|
||||
Bytes() ([]byte, error)
|
||||
}
|
||||
|
||||
// LinearLocator only searches forward and have no boundary check.
|
||||
@ -244,95 +135,130 @@ type LinearLocator struct {
|
||||
baseEnd int
|
||||
baseStart int
|
||||
baseData []byte
|
||||
cfr *CryptoFrameRelocation
|
||||
o []*CryptoFrameOffset
|
||||
}
|
||||
|
||||
func NewLinearLocator(cfr *CryptoFrameRelocation) (linearLocator *LinearLocator) {
|
||||
func NewLinearLocator(o []*CryptoFrameOffset) *LinearLocator {
|
||||
if len(o) == 0 {
|
||||
return &LinearLocator{}
|
||||
}
|
||||
return &LinearLocator{
|
||||
left: 0,
|
||||
length: cfr.length,
|
||||
length: o[len(o)-1].UpperAppOffset + len(o[len(o)-1].Data),
|
||||
iOuter: 0,
|
||||
baseData: cfr.o[0].Data,
|
||||
baseStart: cfr.o[0].UpperAppOffset,
|
||||
baseEnd: cfr.o[0].UpperAppOffset + len(cfr.o[0].Data),
|
||||
cfr: cfr,
|
||||
baseData: o[0].Data,
|
||||
baseStart: o[0].UpperAppOffset,
|
||||
baseEnd: o[0].UpperAppOffset + len(o[0].Data),
|
||||
o: o,
|
||||
}
|
||||
}
|
||||
|
||||
func (ll *LinearLocator) relocate(i int) {
|
||||
func (l *LinearLocator) relocate(i int) error {
|
||||
// Relocate ll.iOuter.
|
||||
for i >= ll.baseEnd {
|
||||
ll.iOuter++
|
||||
ll.baseData = ll.cfr.o[ll.iOuter].Data
|
||||
ll.baseStart = ll.cfr.o[ll.iOuter].UpperAppOffset
|
||||
ll.baseEnd = ll.baseStart + len(ll.baseData)
|
||||
for i >= l.baseEnd {
|
||||
if l.iOuter+1 >= len(l.o) {
|
||||
return ErrMissingCrypto
|
||||
}
|
||||
l.iOuter++
|
||||
l.baseData = l.o[l.iOuter].Data
|
||||
l.baseStart = l.o[l.iOuter].UpperAppOffset
|
||||
l.baseEnd = l.baseStart + len(l.baseData)
|
||||
}
|
||||
if i < l.baseStart {
|
||||
return ErrMissingCrypto
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ll *LinearLocator) Range(i, j int) []byte {
|
||||
func (l *LinearLocator) Range(i, j int) ([]byte, error) {
|
||||
if i == j {
|
||||
return []byte{}
|
||||
return []byte{}, nil
|
||||
}
|
||||
if len(l.o) == 0 {
|
||||
return nil, ErrMissingCrypto
|
||||
}
|
||||
size := j - i
|
||||
|
||||
// We find bytes including i and j, so we should sub j with 1.
|
||||
i += ll.left
|
||||
j += ll.left - 1
|
||||
ll.relocate(i)
|
||||
i += l.left
|
||||
j += l.left - 1
|
||||
if err := l.relocate(i); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Linearly copy.
|
||||
|
||||
if j < ll.baseEnd {
|
||||
if j < l.baseEnd {
|
||||
// In the same block, no copy needed.
|
||||
return ll.baseData[i-ll.baseStart : j-ll.baseStart+1]
|
||||
return l.baseData[i-l.baseStart : j-l.baseStart+1], nil
|
||||
}
|
||||
|
||||
b := make([]byte, size)
|
||||
k := 0
|
||||
for j >= ll.baseEnd {
|
||||
n := copy(b[k:], ll.baseData[i-ll.baseStart:])
|
||||
for j >= l.baseEnd {
|
||||
n := copy(b[k:], l.baseData[i-l.baseStart:])
|
||||
k += n
|
||||
i += n
|
||||
ll.iOuter++
|
||||
ll.baseData = ll.cfr.o[ll.iOuter].Data
|
||||
ll.baseStart = ll.cfr.o[ll.iOuter].UpperAppOffset
|
||||
ll.baseEnd = ll.baseStart + len(ll.baseData)
|
||||
if l.iOuter+1 >= len(l.o) || l.o[l.iOuter].UpperAppOffset+len(l.o[l.iOuter+1].Data) != l.o[l.iOuter].UpperAppOffset {
|
||||
// Some crypto is missing.
|
||||
return nil, ErrMissingCrypto
|
||||
}
|
||||
l.iOuter++
|
||||
l.baseData = l.o[l.iOuter].Data
|
||||
l.baseStart = l.o[l.iOuter].UpperAppOffset
|
||||
l.baseEnd = l.baseStart + len(l.baseData)
|
||||
}
|
||||
copy(b[k:], ll.baseData[i-ll.baseStart:j-ll.baseStart+1])
|
||||
return b
|
||||
copy(b[k:], l.baseData[i-l.baseStart:j-l.baseStart+1])
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (ll *LinearLocator) At(i int) byte {
|
||||
i += ll.left
|
||||
func (l *LinearLocator) At(i int) (byte, error) {
|
||||
if len(l.o) == 0 {
|
||||
return 0, ErrMissingCrypto
|
||||
}
|
||||
i += l.left
|
||||
|
||||
ll.relocate(i)
|
||||
b := ll.baseData[i-ll.baseStart]
|
||||
return b
|
||||
if err := l.relocate(i); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
b := l.baseData[i-l.baseStart]
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (ll *LinearLocator) Slice(i, j int) Locator {
|
||||
func (l *LinearLocator) Slice(i, j int) (Locator, error) {
|
||||
// We do not care about right.
|
||||
newLL := *ll
|
||||
newLL := *l
|
||||
newLL.left += i
|
||||
newLL.length = j - i + 1
|
||||
return &newLL
|
||||
return &newLL, nil
|
||||
}
|
||||
|
||||
func (ll *LinearLocator) Len() int {
|
||||
return ll.length
|
||||
func (l *LinearLocator) Bytes() ([]byte, error) {
|
||||
return l.Range(0, l.length)
|
||||
}
|
||||
|
||||
var _ Locator = &LinearLocator{}
|
||||
|
||||
func (l *LinearLocator) Len() int {
|
||||
return l.length
|
||||
}
|
||||
|
||||
type BuiltinBytesLocator []byte
|
||||
|
||||
func (l BuiltinBytesLocator) Range(i, j int) []byte {
|
||||
return l[i:j]
|
||||
func (l BuiltinBytesLocator) Range(i, j int) ([]byte, error) {
|
||||
return l[i:j], nil
|
||||
}
|
||||
func (l BuiltinBytesLocator) At(i int) byte {
|
||||
return l[i]
|
||||
func (l BuiltinBytesLocator) At(i int) (byte, error) {
|
||||
return l[i], nil
|
||||
}
|
||||
func (l BuiltinBytesLocator) Slice(i, j int) Locator {
|
||||
return l[i:j]
|
||||
func (l BuiltinBytesLocator) Slice(i, j int) (Locator, error) {
|
||||
return l[i:j], nil
|
||||
}
|
||||
func (l BuiltinBytesLocator) Len() int {
|
||||
return len(l)
|
||||
}
|
||||
func (l BuiltinBytesLocator) Bytes() ([]byte, error) {
|
||||
return l, nil
|
||||
}
|
||||
|
||||
var _ Locator = BuiltinBytesLocator{}
|
||||
|
@ -28,10 +28,6 @@ const (
|
||||
QuicFlag_LongPacketType_Initial = 0
|
||||
)
|
||||
|
||||
var (
|
||||
QuicReassemble = QuicReassemblePolicy_ReassembleCryptoToBytesFromPool
|
||||
)
|
||||
|
||||
type QuicReassemblePolicy int
|
||||
|
||||
const (
|
||||
@ -41,50 +37,60 @@ const (
|
||||
)
|
||||
|
||||
func (s *Sniffer) SniffQuic() (d string, err error) {
|
||||
nextBlock := s.buf
|
||||
nextBlock := s.buf.Bytes()[s.quicNextRead:]
|
||||
isQuic := false
|
||||
for {
|
||||
d, nextBlock, err = sniffQuicBlock(nextBlock)
|
||||
if err == nil {
|
||||
return d, nil
|
||||
}
|
||||
// If block is not a quic block, return it.
|
||||
if errors.Is(err, NotApplicableError) {
|
||||
// But if we have found quic block before, correct it.
|
||||
if isQuic {
|
||||
return "", NotFoundError
|
||||
s.quicCryptos, nextBlock, err = sniffQuicBlock(s.quicCryptos, nextBlock)
|
||||
if err != nil {
|
||||
// If block is not a quic block, return it.
|
||||
if errors.Is(err, ErrNotApplicable) {
|
||||
// But if we have found quic block before, correct it.
|
||||
if isQuic {
|
||||
// Unexpected non-block
|
||||
break
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
if errors.Is(err, fs.ErrClosed) {
|
||||
// ConnectionClose sniffed.
|
||||
return "", ErrNotFound
|
||||
}
|
||||
// The code should NOT run here.
|
||||
return "", err
|
||||
}
|
||||
if errors.Is(err, fs.ErrClosed) {
|
||||
// ConnectionClose sniffed.
|
||||
return "", NotFoundError
|
||||
}
|
||||
// Error is not NotApplicableError, should be quic block.
|
||||
// Should be quic block.
|
||||
isQuic = true
|
||||
if len(nextBlock) == 0 {
|
||||
return "", NotFoundError
|
||||
break
|
||||
}
|
||||
}
|
||||
// Is quic.
|
||||
s.quicNextRead = s.buf.Len()
|
||||
sni, err := extractSniFromTls(quicutils.NewLinearLocator(s.quicCryptos))
|
||||
if err != nil {
|
||||
s.needMore = true
|
||||
return "", ErrNotFound
|
||||
}
|
||||
return sni, nil
|
||||
}
|
||||
|
||||
func sniffQuicBlock(buf []byte) (d string, next []byte, err error) {
|
||||
func sniffQuicBlock(cryptos []*quicutils.CryptoFrameOffset, buf []byte) (new []*quicutils.CryptoFrameOffset, next []byte, err error) {
|
||||
// QUIC: A UDP-Based Multiplexed and Secure Transport
|
||||
// https://datatracker.ietf.org/doc/html/rfc9000#name-initial-packet
|
||||
const dstConnIdPos = 6
|
||||
boundary := dstConnIdPos
|
||||
if len(buf) < boundary {
|
||||
return "", nil, NotApplicableError
|
||||
return cryptos, nil, ErrNotApplicable
|
||||
}
|
||||
// Check flag.
|
||||
// Long header: 4 bits masked
|
||||
// High 4 bits are not protected, so we can access QuicFlag_HeaderForm and QuicFlag_LongPacketType without decryption.
|
||||
protectedFlag := buf[0]
|
||||
if ((protectedFlag >> QuicFlag_HeaderForm) & 0b11) != QuicFlag_HeaderForm_LongHeader {
|
||||
return "", nil, NotApplicableError
|
||||
return cryptos, nil, ErrNotApplicable
|
||||
}
|
||||
if ((protectedFlag >> QuicFlag_LongPacketType) & 0b11) != QuicFlag_LongPacketType_Initial {
|
||||
return "", nil, NotApplicableError
|
||||
return cryptos, nil, ErrNotApplicable
|
||||
}
|
||||
|
||||
// Skip version.
|
||||
@ -92,37 +98,37 @@ func sniffQuicBlock(buf []byte) (d string, next []byte, err error) {
|
||||
destConnIdLength := int(buf[boundary-1])
|
||||
boundary += destConnIdLength + 1 // +1 because next field has 1B length
|
||||
if len(buf) < boundary {
|
||||
return "", nil, NotApplicableError
|
||||
return cryptos, nil, ErrNotApplicable
|
||||
}
|
||||
destConnId := buf[dstConnIdPos : dstConnIdPos+destConnIdLength]
|
||||
|
||||
srcConnIdLength := int(buf[boundary-1])
|
||||
boundary += srcConnIdLength + quicutils.MaxVarintLen64 // The next fields may have quic.MaxVarintLen64 bytes length
|
||||
if len(buf) < boundary {
|
||||
return "", nil, NotApplicableError
|
||||
return cryptos, nil, ErrNotApplicable
|
||||
}
|
||||
tokenLength, n, err := quicutils.BigEndianUvarint(buf[boundary-quicutils.MaxVarintLen64:])
|
||||
if err != nil {
|
||||
return "", nil, NotApplicableError
|
||||
return cryptos, nil, ErrNotApplicable
|
||||
}
|
||||
boundary = boundary - quicutils.MaxVarintLen64 + n // Correct boundary.
|
||||
boundary += int(tokenLength) + quicutils.MaxVarintLen64 // Next fields may have quic.MaxVarintLen64 bytes length
|
||||
if len(buf) < boundary {
|
||||
return "", nil, NotApplicableError
|
||||
return cryptos, nil, ErrNotApplicable
|
||||
}
|
||||
// https://datatracker.ietf.org/doc/html/rfc9000#name-variable-length-integer-enc
|
||||
length, n, err := quicutils.BigEndianUvarint(buf[boundary-quicutils.MaxVarintLen64:])
|
||||
if err != nil {
|
||||
return "", nil, NotApplicableError
|
||||
return cryptos, nil, ErrNotApplicable
|
||||
}
|
||||
boundary = boundary - quicutils.MaxVarintLen64 + n // Correct boundary.
|
||||
blockEnd := boundary + int(length)
|
||||
if len(buf) < blockEnd {
|
||||
return "", nil, NotApplicableError
|
||||
return cryptos, nil, ErrNotApplicable
|
||||
}
|
||||
boundary += quicutils.MaxPacketNumberLength
|
||||
if len(buf) < boundary {
|
||||
return "", nil, NotApplicableError
|
||||
return cryptos, nil, ErrNotApplicable
|
||||
}
|
||||
header := buf[:boundary]
|
||||
// Decrypt protected Packets.
|
||||
@ -138,55 +144,18 @@ func sniffQuicBlock(buf []byte) (d string, next []byte, err error) {
|
||||
copy(header[boundary-quicutils.MaxPacketNumberLength:], rawPacketNumber)
|
||||
pool.Put(rawPacketNumber)
|
||||
}()
|
||||
plaintext, err := quicutils.DecryptQuicFromPool_(header, blockEnd, destConnId)
|
||||
plaintext, err := quicutils.DecryptQuic_(header, blockEnd, destConnId)
|
||||
if err != nil {
|
||||
return "", nil, NotApplicableError
|
||||
return cryptos, nil, ErrNotApplicable
|
||||
}
|
||||
defer pool.Put(plaintext)
|
||||
// Now, we confirm it is exact a quic frame.
|
||||
// After here, we should not return NotApplicableError.
|
||||
// And we should return nextFrame.
|
||||
if d, err = extractSniFromQuicPayload(plaintext); err != nil {
|
||||
if new, err = quicutils.ReassembleCryptos(cryptos, plaintext); err != nil {
|
||||
if errors.Is(err, fs.ErrClosed) {
|
||||
return "", nil, err
|
||||
return cryptos, nil, err
|
||||
}
|
||||
return "", buf[blockEnd:], NotFoundError
|
||||
return cryptos, buf[blockEnd:], ErrNotApplicable
|
||||
}
|
||||
return d, buf[blockEnd:], nil
|
||||
}
|
||||
|
||||
func extractSniFromQuicPayload(payload []byte) (sni string, err error) {
|
||||
// One payload may have multiple frames.
|
||||
// Reassemble Crypto frames.
|
||||
|
||||
// Choose locator.
|
||||
var locator quicutils.Locator
|
||||
switch QuicReassemble {
|
||||
case QuicReassemblePolicy_LinearLocator:
|
||||
relocation, err := quicutils.NewCryptoFrameRelocation(payload)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
locator = quicutils.NewLinearLocator(relocation)
|
||||
case QuicReassemblePolicy_Slow:
|
||||
relocation, err := quicutils.NewCryptoFrameRelocation(payload)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
b := relocation.BytesFromPool()
|
||||
defer pool.Put(b)
|
||||
locator = quicutils.BuiltinBytesLocator(b)
|
||||
case QuicReassemblePolicy_ReassembleCryptoToBytesFromPool:
|
||||
b, err := quicutils.ReassembleCryptoToBytesFromPool(payload)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer pool.Put(b)
|
||||
locator = quicutils.BuiltinBytesLocator(b)
|
||||
}
|
||||
sni, err = extractSniFromTls(locator)
|
||||
if err == nil {
|
||||
return sni, nil
|
||||
}
|
||||
return "", NotFoundError
|
||||
return new, buf[blockEnd:], nil
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ package sniffing
|
||||
import (
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
@ -16,41 +17,10 @@ var QuicStream, _ = hex.DecodeString("c00000000108d60451e5cb0f7050000044bc9acdca
|
||||
|
||||
//var QuicStream, _ = hex.DecodeString("c6ff00001d100d5a802c52bfee4d71f3770529a5c6871415ea0d6ef29709e829432a18eb50f3af09c81c75004127f234d23fca9370573fd78cd781f4057ce9940111f0ad20e03e894b232013d76e268299644b036ac4557f03fead23ece9b788b3bcff3492b376861a188d5905e5e07cb156b57d7419e66235bedd44e5e774e1476d344eff64bdb1604aa9755a1fd08d4597a03a205e490f4223ddb32af2fc4023bc6784bcf6622ded2a49bbb976dec36e3712e0016272207f462b93b5a70dc66463131d2375bbfc38ece9215119b0b53676d05d470dcce52460f76d284d8f23846cbb38fcaa7e07fa1d6dec390e2876aea21bbd188dca3fe96dfc8c9f99237564e3db587b240279f46613ccc46c84e1b246cf1536be8275075fa4e63f0750df54f0cfbae986811cf3493c1d6ea63a836f387d1a3a02ac158b433ead3fc2035987f1f9c65c71c2d31803320f7a1a978a1aee3e1a50")
|
||||
|
||||
func BenchmarkLinearLocator(b *testing.B) {
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
QuicReassemble = QuicReassemblePolicy_LinearLocator
|
||||
for i := 0; i < b.N; i++ {
|
||||
sniffer := NewPacketSniffer(QuicStream)
|
||||
d, err := sniffer.SniffQuic()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
if d == "" {
|
||||
b.Fatal(d)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkBuiltinSlow(b *testing.B) {
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
QuicReassemble = QuicReassemblePolicy_Slow
|
||||
for i := 0; i < b.N; i++ {
|
||||
sniffer := NewPacketSniffer(QuicStream)
|
||||
d, err := sniffer.SniffQuic()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
if d == "" {
|
||||
b.Fatal(d)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkReassembleCryptoToBytesFromPool(b *testing.B) {
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
QuicReassemble = QuicReassemblePolicy_ReassembleCryptoToBytesFromPool
|
||||
for i := 0; i < b.N; i++ {
|
||||
sniffer := NewPacketSniffer(QuicStream)
|
||||
sniffer := NewPacketSniffer(QuicStream, 300*time.Millisecond)
|
||||
d, err := sniffer.SniffQuic()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
|
68
component/sniffing/quic_test.go
Normal file
68
component/sniffing/quic_test.go
Normal file
@ -0,0 +1,68 @@
|
||||
package sniffing
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/daeuniverse/dae/component/sniffing/internal/quicutils"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var QuicStream2_2, _ = hex.DecodeString("cc0000000108e8da6ed9f385c987000044d026f109c2764c22f0ea2656550ea03e832d0ed5113eff115f2a057f77655cf5bbbb69fc98f7f70a3f407e0d94f37960c5ba5bd95a2df75f6f25020c2f2f21ddf9db5266bb4293991d58efec945468a820c61b743ca4b73663c3adcda58dee75607c5465e255b58477069a928687789c18c2ccb53911a47d64b83d5b58398ee4fd58f4f88f78788d5594218730cab9db3bac2fbfb947f2cb4eafb5e2964fce361042c622dfa7130afaf0e9d391ffc3aba2f5ee2f5c4d0dfaae0d71db2b3d7fab6dbccbb63d7961ddab55711d5a1beacf00ce5a82030a2c79c4ea65a2762f3b8e5f8fec8f6963b1a42c0f8a8d863225b2d6e7a15e9758e43095459e3d7ff88dc276605452b10de95a8795fe9952eb0b1eb200465ca9b00f98e2c4ad6a2a2e2bff2e2430438241525e1d16d5423c2262134a97056b7e86d5eb7eb2ac546086a3b8d7a97bc2263fa9a8b46f4b7d31cad63762c17a653b89593434aecf7a5e8fc169cfb5aa4a47e78ee817e115feceb9b68b29da6e15c647b7528980fb7cdc7c9ca660871228d0367f030f658d19ddddefe55908a2ec4ef5f5d89ec5aebee33f88a116c2857f7d1a2fd98321f28468a93938da406a68e4e660f0668fe49118812d5264073f28a8aa800c5970ef3f6fb4f0e9e4e48510700a5465c92886c50f2c6af570075f29f6a80636171f73d91864583d2d199e39b18623ee0cb489b449838bd9f7cd67ccc3e38f1b5a3ce08814f979f94db45cdcfa39a475e3efc4847def8e8e4c707a88d2f486fc85e10910ab0f1bbeb40468af777ff2bb0e655f1a006cde0d2e2ae036dafe60f110e859543699e0c9aa47eefa53d792b3cbcfa11ea1d3b55d3629de0345517d47f4e4c801104b81710ad28cd8611e150a1fc32160cb784cfcfdd908052cd43969b27929013edd2b0f3cd914590a32b2f99d4fc88873838b6fa0ec1450adb95f395988998801e85319fa448925ba767e3191df2b5b0983990beb4127216c93291a94463b453a4972c9a974742b0b22c935f4235c350120b6cf8296fc6d3c2812f74a17acf334e3c34ff9988f980e0cfff737a8b1a03508f47d8bf3748fbb5bd5ad7f1f47120c3a33822612f3a614aae7fe536b73db814aa4aac4b685aa1e7357309cf921b931113624881ce764feeff3292d2d794c6fa76529f3da8e6327e8f28aafe8b675a80ae3f478c65f1bf8fd7f2b140fea130dfa55982f0b0fcd61b42c8b2ea27a2b8bb44511eb44c1416ac16698f0ddb739e3d773f2afdd35bcfed0ffd7966aa3e727f8f08d02cab8d034a7ae363e42c9089901ddee147c98a856df4e5dcfeeb2f72e9edb12da513f32d99e1c653f4503e9a7f7fee1f4724ce9d6d530485362d993cb3bc4faff683327a02aee6f004bd9f98a8a4841091d48f5cd27af46431c66e68007750be57361e293650a0ae9fc9fa82ddf4483663c9805dc6e4a9b43529c0b2267cc3c0fb9084378acbda4962150a73e0c1b5aef6e40538d2630d8dbc2b084f9a53079cc73484906b7ad4a5021f280baf276a01b0fcea57d5c4284364f4d795645fc7bd8bb7d00021af924b75829e8a936e153676a182803537a23c76fee7c881e8063751ca0f5a585481b9077e9593734f9997e78b79ba38f6e13a1b631106a2ceddafdf51110b8bf07ec9337024355088d0bb3de2d46a03d3e3e7362b8b815613e36d746e5a9992f8e62ad5257e5798bd49b1a62717f02151b75a18e051df1292191d4")
|
||||
var QuicStream2_1, _ = hex.DecodeString("ce0000000108e8da6ed9f385c987000044d0f34f94dcc26b99261ea264742abe4e552a146e16e89e4b7ef0ab3d6f3a34227b59742e4ba83a1e18cea494d2f67e469be4a7ff01334b151e9b7ca63b53735008eecc1f5c618419982292eca5731bb163ba81c1300e0bb99f2536d89ab0faf2dbd37ebfdb3d71f7343296a2190914bda556b8f9ccf5219964eb3cd373966fcfaca8a4735fb59fbaf69bbbdfc3a81b11570bb81fd3f5ef780fb7036e0666b997b0f4ed3305b68eafa1a99b3c8a6a2142ad9fe1e6b0a0eade6ace92b57416d4bf68fa2e9295bfc22757b0542ce91c8af3f547ef0ad385788db230a50158a0009fd95a7e8ee6e0dd11d6f9a906cbe8117e85bd507cdbd8f1a5a6cabf2617de7227d1ae8a8c6086b8ec325df90c0e16b37b4ed0ce617a00c7598a21924a19aec1b08c31b69430b23eefbe555ca2433431d28a4ffec548e463e8e6363b6b4fe9b8477c686c393571273c30b2e1785261faa0fd6f560c12418b27cd0491e013db5a8b3294e01a46a6e4c6b52e32756ab4be6f4ebc886c0c472d63f117ce30115182a97f1308c7f28989ce301cabced825154b0f4fa3bf4a55ce2f384ff11d9cbc0460d69db363664f92dc014bdb771b9b1e1ab6672c6da71c90aa514dcdc3a4ce45298bf9e5a395ebac3dff2a738c4b4690ee06fdab572a277addac7035d94afe794df05da75a56c79c37f42de1d727dc65e3060d9331e2fc82de2d7cef6cb9ae46f648b9930593975c35960b24deb770d5ee4332f8f57a05503399ca7bfdf7207f66a0f73d6b53269a944d5a3043b225adddfdd29d20ea8f500bb09ea3bb724083dd29ea8839e8192c4360ba3c5a6db0d695af5d357d6c4ed94aa28305033629201689764189774bbd4f0ae41b878b8f29a0fe0e124075ea08c5054871506a05be2f90e9ec0c2db48c0780580312e9ff4071054386e4206841f575f7ca06c228f7ee11e2333d08652b9b4f0b97f473a46a3d79c4f9a3416fb20fdbd88cacfa36f06fe1d73618195c6f0bf759a77c6a16b7e271c6cdb672ea53f6edfac860fcaf03313564abde1f66bca441d844d289a9e1025711c284f2c7c805353f2a89e9aeb52e3f452e879f0fafcdc0b48a0676afcf617a85037d991762664f6db64847eff2308447c4e8ea6688838bb7237a5fdfe0f1695afaa0bbb821b0004585adf151b029bd3458e28ba49dfc17eef1d2dd14ccda88d0848d4cd36d33cc5bab173c2448785ec1bdabc8873c904b95d7847d1b89857f2c7e078c6e2eb96029aa91c077e0efcf7b2ed2f30c7abc12189627793c7870dc0e70342cc27402ee1d6dec5ceea0ca06159002ea14a20c63b85689ed1840f404e46cb83d91c5e02f3ed938462364d3349f689310234083f7044e4b338ac54bed94530640d684c9688651b915d8c8895ef0f05f376292871b589751ac5b233e3d85572bb0c11bbbe91cc49a4ef0422f2676a2f3cc62bc88dbb7acf03cb5e847e976bfca6a90b9cee743ea77be5472ef162ff101c6873043df94c53c252840fd6a2662018f0897a06cd215997d6050917876500796fef718957212c773c39d1c7b839931af1e7dfae6e2c1d2251e78896521bb35b20057bad77df85aaed90288c17edb081398815e47239aeb77293a02a61a5125109fc3953593233fa83c17770a815fad7831c1b8647c6089ec621ee774a12a714def498d4335d0bb8a4a6a3dddead8ddb1176f58218477d55317df88cd2ca5a06b72679cf2ff7253ebd76a5ed3")
|
||||
var QuicStream3, _ = hex.DecodeString("c00000000110787cb250e5ebaa3070534ac6f568006c14376bb3d77569ef83965513f7ab60499d3d6fe8cd00411e61c97af492e1c220194c2460a093505250315e811506fda1a54b7b6bfc85e18d997db284c578a4c4576258c92176200b5f85d40b28734880c8c01a9e9d5944b17568a24e112e966bf0ee955981635f0dde48e0d176f8492708a4436a53a4794a29dd8b020521824823db71bb6a4266baaf9364a2268cf87ee1dd9a543c9268c3d7ef6726e9bdea6f38d615b9ba08b3a290a22ebc1fcd9093bde5098c3c0d6151ab1e30243d21906a88e8d248a55a2c4d282e309fced134e4d13d9d2ef49325a2741824b14f1a018cfed76d0de5b6cd2881c0c708bbcca59cff5cb60ad7b9a2909b1afb4efe0b358ba098b6b2a598da1f9d23accdab814f524c1e1e0d86d3c1e4199b358a5dad8eacfe6d5d1cf431a44129538177824ed150650d97631d4d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
|
||||
|
||||
func dumpCryptos(t *testing.T, cryptos []*quicutils.CryptoFrameOffset) {
|
||||
var b strings.Builder
|
||||
for _, c := range cryptos {
|
||||
b.WriteString(fmt.Sprintf("Offset %v; length: %v:\n", c.UpperAppOffset, len(c.Data)))
|
||||
b.WriteString(fmt.Sprintf("Dump:\n%v\n", hex.Dump(c.Data)))
|
||||
}
|
||||
t.Log(b.String())
|
||||
}
|
||||
|
||||
func TestQuicReassemble(t *testing.T) {
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
sniffer := NewPacketSniffer(QuicStream2_1, 300*time.Millisecond)
|
||||
d, err := sniffer.SniffQuic()
|
||||
if err != nil {
|
||||
if sniffer.NeedMore() {
|
||||
sniffer.AppendData(QuicStream2_2)
|
||||
d, err = sniffer.SniffQuic()
|
||||
} else {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
dumpCryptos(t, sniffer.quicCryptos)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if d == "" {
|
||||
t.Fatal("domain is empty")
|
||||
}
|
||||
t.Log(d)
|
||||
}
|
||||
func TestQuic(t *testing.T) {
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
sniffer := NewPacketSniffer(QuicStream3, 300*time.Millisecond)
|
||||
d, err := sniffer.SniffQuic()
|
||||
if err != nil {
|
||||
dumpCryptos(t, sniffer.quicCryptos)
|
||||
if sniffer.NeedMore() {
|
||||
t.Fatal("need more")
|
||||
} else {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
dumpCryptos(t, sniffer.quicCryptos)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if d == "" {
|
||||
t.Fatal("domain is empty")
|
||||
}
|
||||
t.Log(d)
|
||||
}
|
@ -6,68 +6,99 @@
|
||||
package sniffing
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/daeuniverse/dae/component/sniffing/internal/quicutils"
|
||||
"github.com/daeuniverse/softwind/pool"
|
||||
"github.com/daeuniverse/softwind/pool/bytes"
|
||||
)
|
||||
|
||||
type Sniffer struct {
|
||||
// Stream
|
||||
stream bool
|
||||
r io.Reader
|
||||
dataReady chan struct{}
|
||||
dataError error
|
||||
dataWaitingTimeout time.Duration
|
||||
stream bool
|
||||
r io.Reader
|
||||
dataReady chan struct{}
|
||||
dataError error
|
||||
|
||||
// Common
|
||||
buf []byte
|
||||
bufAt int
|
||||
readMu sync.Mutex
|
||||
sniffed string
|
||||
buf *bytes.Buffer
|
||||
readMu sync.Mutex
|
||||
ctx context.Context
|
||||
cancel func()
|
||||
|
||||
// Packet
|
||||
data [][]byte
|
||||
needMore bool
|
||||
quicNextRead int
|
||||
quicCryptos []*quicutils.CryptoFrameOffset
|
||||
}
|
||||
|
||||
func NewStreamSniffer(r io.Reader, bufSize int, dataWaitingTimeout time.Duration) *Sniffer {
|
||||
func NewStreamSniffer(r io.Reader, bufSize int, timeout time.Duration) *Sniffer {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
buffer := pool.GetBuffer()
|
||||
buffer.Grow(AssumedTlsClientHelloMaxLength)
|
||||
buffer.Reset()
|
||||
s := &Sniffer{
|
||||
stream: true,
|
||||
r: r,
|
||||
buf: make([]byte, bufSize),
|
||||
dataReady: make(chan struct{}),
|
||||
dataWaitingTimeout: dataWaitingTimeout,
|
||||
stream: true,
|
||||
r: r,
|
||||
buf: buffer,
|
||||
dataReady: make(chan struct{}),
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func NewPacketSniffer(data []byte) *Sniffer {
|
||||
func NewPacketSniffer(data []byte, timeout time.Duration) *Sniffer {
|
||||
buffer := pool.GetBuffer()
|
||||
buffer.Write(data)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
s := &Sniffer{
|
||||
stream: false,
|
||||
r: nil,
|
||||
buf: data,
|
||||
buf: buffer,
|
||||
data: [][]byte{buffer.Bytes()},
|
||||
dataReady: make(chan struct{}),
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
type sniff func() (d string, err error)
|
||||
|
||||
func sniffGroup(sniffs []sniff) (d string, err error) {
|
||||
func sniffGroup(sniffs ...sniff) (d string, err error) {
|
||||
for _, sniffer := range sniffs {
|
||||
d, err = sniffer()
|
||||
if err == nil {
|
||||
return d, nil
|
||||
}
|
||||
if err != NotApplicableError {
|
||||
if err != ErrNotApplicable {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return "", NotApplicableError
|
||||
return "", ErrNotApplicable
|
||||
}
|
||||
|
||||
func (s *Sniffer) SniffTcp() (d string, err error) {
|
||||
if s.sniffed != "" {
|
||||
return s.sniffed, nil
|
||||
}
|
||||
defer func() {
|
||||
if err == nil {
|
||||
s.sniffed = d
|
||||
}
|
||||
}()
|
||||
s.readMu.Lock()
|
||||
defer s.readMu.Unlock()
|
||||
if s.stream {
|
||||
go func() {
|
||||
n, err := s.r.Read(s.buf)
|
||||
s.buf = s.buf[:n]
|
||||
// Read once.
|
||||
_, err := s.buf.ReadFromOnce(s.r)
|
||||
if err != nil {
|
||||
s.dataError = err
|
||||
}
|
||||
@ -80,38 +111,70 @@ func (s *Sniffer) SniffTcp() (d string, err error) {
|
||||
if s.dataError != nil {
|
||||
return "", s.dataError
|
||||
}
|
||||
case <-time.After(s.dataWaitingTimeout):
|
||||
return "", NotApplicableError
|
||||
case <-s.ctx.Done():
|
||||
return "", ErrNotApplicable
|
||||
}
|
||||
} else {
|
||||
close(s.dataReady)
|
||||
}
|
||||
|
||||
if len(s.buf) == 0 {
|
||||
return "", NotApplicableError
|
||||
if s.buf.Len() == 0 {
|
||||
return "", ErrNotApplicable
|
||||
}
|
||||
|
||||
return sniffGroup([]sniff{
|
||||
return sniffGroup(
|
||||
// Most sniffable traffic is TLS, thus we sniff it first.
|
||||
s.SniffTls,
|
||||
s.SniffHttp,
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
func (s *Sniffer) SniffUdp() (d string, err error) {
|
||||
if s.sniffed != "" {
|
||||
return s.sniffed, nil
|
||||
}
|
||||
defer func() {
|
||||
if err == nil {
|
||||
s.sniffed = d
|
||||
}
|
||||
}()
|
||||
defer func() {
|
||||
if err == nil {
|
||||
s.sniffed = d
|
||||
}
|
||||
}()
|
||||
s.readMu.Lock()
|
||||
defer s.readMu.Unlock()
|
||||
|
||||
// Always ready.
|
||||
close(s.dataReady)
|
||||
|
||||
if len(s.buf) == 0 {
|
||||
return "", NotApplicableError
|
||||
select {
|
||||
case <-s.dataReady:
|
||||
default:
|
||||
close(s.dataReady)
|
||||
}
|
||||
|
||||
return sniffGroup([]sniff{
|
||||
if s.buf.Len() == 0 {
|
||||
return "", ErrNotApplicable
|
||||
}
|
||||
|
||||
return sniffGroup(
|
||||
s.SniffQuic,
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
func (s *Sniffer) AppendData(data []byte) {
|
||||
s.needMore = false
|
||||
ori := s.buf.Len()
|
||||
s.buf.Write(data)
|
||||
s.data = append(s.data, s.buf.Bytes()[ori:])
|
||||
}
|
||||
|
||||
func (s *Sniffer) Data() [][]byte {
|
||||
return s.data
|
||||
}
|
||||
|
||||
func (s *Sniffer) NeedMore() bool {
|
||||
return s.needMore
|
||||
}
|
||||
|
||||
func (s *Sniffer) Read(p []byte) (n int, err error) {
|
||||
@ -121,21 +184,13 @@ func (s *Sniffer) Read(p []byte) (n int, err error) {
|
||||
defer s.readMu.Unlock()
|
||||
|
||||
if s.dataError != nil {
|
||||
if s.bufAt < len(s.buf) {
|
||||
n = copy(p, s.buf[s.bufAt:])
|
||||
s.bufAt += n
|
||||
}
|
||||
n, _ = s.buf.Read(p)
|
||||
return n, s.dataError
|
||||
}
|
||||
|
||||
if s.bufAt < len(s.buf) {
|
||||
if s.buf.Len() > 0 {
|
||||
// Read buf first.
|
||||
n = copy(p, s.buf[s.bufAt:])
|
||||
s.bufAt += n
|
||||
if s.bufAt >= len(s.buf) {
|
||||
s.buf = nil
|
||||
}
|
||||
return n, nil
|
||||
return s.buf.Read(p)
|
||||
}
|
||||
if !s.stream {
|
||||
return 0, io.EOF
|
||||
@ -144,5 +199,13 @@ func (s *Sniffer) Read(p []byte) (n int, err error) {
|
||||
}
|
||||
|
||||
func (s *Sniffer) Close() (err error) {
|
||||
select {
|
||||
case <-s.ctx.Done():
|
||||
default:
|
||||
s.cancel()
|
||||
if s.buf.Len() == 0 {
|
||||
pool.PutBuffer(s.buf)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -11,9 +11,9 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
Error = fmt.Errorf("sniffing error")
|
||||
NotApplicableError = fmt.Errorf("%w: not applicable", Error)
|
||||
NotFoundError = fmt.Errorf("%w: not found", Error)
|
||||
Error = fmt.Errorf("sniffing error")
|
||||
ErrNotApplicable = fmt.Errorf("%w: not applicable", Error)
|
||||
ErrNotFound = fmt.Errorf("%w: not found", Error)
|
||||
)
|
||||
|
||||
func IsSniffingError(err error) bool {
|
||||
|
@ -18,6 +18,8 @@ const (
|
||||
HandShakeType_Hello byte = 1
|
||||
TlsExtension_ServerName uint16 = 0
|
||||
TlsExtension_ServerNameType_HostName byte = 0
|
||||
|
||||
AssumedTlsClientHelloMaxLength = 4096
|
||||
)
|
||||
|
||||
var (
|
||||
@ -30,18 +32,18 @@ func (s *Sniffer) SniffTls() (d string, err error) {
|
||||
// The Transport Layer Security (TLS) Protocol Version 1.3
|
||||
// https://www.rfc-editor.org/rfc/rfc8446#page-27
|
||||
boundary := 5
|
||||
if len(s.buf) < boundary {
|
||||
return "", NotApplicableError
|
||||
if s.buf.Len() < boundary {
|
||||
return "", ErrNotApplicable
|
||||
}
|
||||
|
||||
if s.buf[0] != ContentType_HandShake || (!bytes.Equal(s.buf[1:3], Version_Tls1_0) && !bytes.Equal(s.buf[1:3], Version_Tls1_2)) {
|
||||
return "", NotApplicableError
|
||||
if s.buf.Bytes()[0] != ContentType_HandShake || (!bytes.Equal(s.buf.Bytes()[1:3], Version_Tls1_0) && !bytes.Equal(s.buf.Bytes()[1:3], Version_Tls1_2)) {
|
||||
return "", ErrNotApplicable
|
||||
}
|
||||
|
||||
length := int(binary.BigEndian.Uint16(s.buf[3:5]))
|
||||
search := s.buf[5:]
|
||||
length := int(binary.BigEndian.Uint16(s.buf.Bytes()[3:5]))
|
||||
search := s.buf.Bytes()[5:]
|
||||
if len(search) < length {
|
||||
return "", NotApplicableError
|
||||
return "", ErrNotApplicable
|
||||
}
|
||||
return extractSniFromTls(quicutils.BuiltinBytesLocator(search[:length]))
|
||||
}
|
||||
@ -49,88 +51,119 @@ func (s *Sniffer) SniffTls() (d string, err error) {
|
||||
func extractSniFromTls(search quicutils.Locator) (sni string, err error) {
|
||||
boundary := 39
|
||||
if search.Len() < boundary {
|
||||
return "", NotApplicableError
|
||||
return "", ErrNotApplicable
|
||||
}
|
||||
// Transport Layer Security (TLS) Extensions: Extension Definitions
|
||||
// https://www.rfc-editor.org/rfc/rfc6066#page-5
|
||||
b := search.Range(0, 6)
|
||||
b, err := search.Range(0, 6)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if b[0] != HandShakeType_Hello {
|
||||
return "", NotApplicableError
|
||||
return "", ErrNotApplicable
|
||||
}
|
||||
|
||||
// Three bytes length.
|
||||
length2 := (int(b[1]) << 16) + (int(b[2]) << 8) + int(b[3])
|
||||
if search.Len() > length2+4 {
|
||||
return "", NotApplicableError
|
||||
return "", ErrNotApplicable
|
||||
}
|
||||
|
||||
if !bytes.Equal(b[4:], Version_Tls1_2) {
|
||||
return "", NotApplicableError
|
||||
return "", ErrNotApplicable
|
||||
}
|
||||
|
||||
// Skip 32 bytes random.
|
||||
|
||||
sessionIdLength := search.At(boundary - 1)
|
||||
sessionIdLength, err := search.At(boundary - 1)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
boundary += int(sessionIdLength) + 2 // +2 because the next field has 2B length
|
||||
if search.Len() < boundary || search.Len() < boundary {
|
||||
return "", NotApplicableError
|
||||
return "", ErrNotApplicable
|
||||
}
|
||||
|
||||
b = search.Range(boundary-2, boundary)
|
||||
b, err = search.Range(boundary-2, boundary)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
cipherSuiteLength := int(binary.BigEndian.Uint16(b))
|
||||
boundary += int(cipherSuiteLength) + 1 // +1 because the next field has 1B length
|
||||
if search.Len() < boundary || search.Len() < boundary {
|
||||
return "", NotApplicableError
|
||||
return "", ErrNotApplicable
|
||||
}
|
||||
|
||||
compressMethodsLength := search.At(boundary - 1)
|
||||
compressMethodsLength, err := search.At(boundary - 1)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
boundary += int(compressMethodsLength) + 2 // +2 because the next field has 2B length
|
||||
if search.Len() < boundary || search.Len() < boundary {
|
||||
return "", NotApplicableError
|
||||
return "", ErrNotApplicable
|
||||
}
|
||||
|
||||
b = search.Range(boundary-2, boundary)
|
||||
b, err = search.Range(boundary-2, boundary)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
extensionsLength := int(binary.BigEndian.Uint16(b))
|
||||
boundary += extensionsLength + 0 // +0 because our search ends
|
||||
if search.Len() < boundary || search.Len() < boundary {
|
||||
return "", NotApplicableError
|
||||
return "", ErrNotApplicable
|
||||
}
|
||||
// Search SNI
|
||||
return findSniExtension(search.Slice(boundary-extensionsLength, boundary))
|
||||
extensions, err := search.Slice(boundary-extensionsLength, boundary)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return findSniExtension(extensions)
|
||||
}
|
||||
|
||||
func findSniExtension(search quicutils.Locator) (string, error) {
|
||||
func findSniExtension(search quicutils.Locator) (d string, err error) {
|
||||
i := 0
|
||||
var b []byte
|
||||
for {
|
||||
if i+4 >= search.Len() {
|
||||
return "", NotFoundError
|
||||
return "", ErrNotFound
|
||||
}
|
||||
b, err = search.Range(i, i+4)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
b = search.Range(i, i+4)
|
||||
typ := binary.BigEndian.Uint16(b)
|
||||
extLength := int(binary.BigEndian.Uint16(b[2:]))
|
||||
|
||||
iNextField := i + 4 + extLength
|
||||
if iNextField > search.Len() {
|
||||
return "", NotApplicableError
|
||||
return "", ErrNotApplicable
|
||||
}
|
||||
if typ == TlsExtension_ServerName {
|
||||
b = search.Range(i+4, i+6)
|
||||
b, err = search.Range(i+4, i+6)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
sniLen := int(binary.BigEndian.Uint16(b))
|
||||
if extLength < sniLen+2 {
|
||||
return "", NotApplicableError
|
||||
return "", ErrNotApplicable
|
||||
}
|
||||
// Search HostName type SNI.
|
||||
for j, indicatorLen := i+6, 0; j+3 <= iNextField; j += indicatorLen {
|
||||
b = search.Range(j, j+3)
|
||||
b, err = search.Range(j, j+3)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
indicatorLen = int(binary.BigEndian.Uint16(b[1:]))
|
||||
if b[0] != TlsExtension_ServerNameType_HostName {
|
||||
continue
|
||||
}
|
||||
if j+3+indicatorLen > iNextField {
|
||||
return "", NotApplicableError
|
||||
return "", ErrNotApplicable
|
||||
}
|
||||
b, err = search.Range(j+3, j+3+indicatorLen)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
b = search.Range(j+3, j+3+indicatorLen)
|
||||
// An SNI value may not include a trailing dot.
|
||||
// https://tools.ietf.org/html/rfc6066#section-3
|
||||
// But we accept it here.
|
||||
|
@ -8,12 +8,14 @@ package sniffing
|
||||
import (
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var tlsStreamGoogle, _ = hex.DecodeString("1603010200010001fc0303d90fdf25b0c7a11c3eb968604a065157a149407c139c22ed32f5c6f486ed2c04206c51c32da7f83c3c19766be60d45d264e898c77504e34915c44caa69513c2221003e130213031301c02cc030009fcca9cca8ccaac02bc02f009ec024c028006bc023c0270067c00ac0140039c009c0130033009d009c003d003c0035002f00ff0100017500000013001100000e7777772e676f6f676c652e636f6d000b000403000102000a00160014001d0017001e00190018010001010102010301040010000e000c02683208687474702f312e31001600000017000000310000000d002a0028040305030603080708080809080a080b080408050806040105010601030303010302040205020602002b0009080304030303020301002d00020101003300260024001d00207fe08226bdc4fb1715e477506b6afe8f3abe2d20daa1f8c78c5483f1a90a9b19001500af00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
|
||||
var tlsStreamWindowsOdinGame, _ = hex.DecodeString("16030300b8010000b403036484b04b0f87a95364166094aa611bb989a6886b4ca4f23480cfd31a1c683e8400002ac02cc02bc030c02f009f009ec024c023c028c027c00ac009c014c013009d009c003d003c0035002f000a010000610000001700150000126f64696e2e67616d652e6461756d2e6e6574000500050100000000000a00080006001d00170018000b00020100000d001a00180804080508060401050102010403050302030202060106030023000000170000ff01000100")
|
||||
var tlsCurlIpsb, _ = hex.DecodeString("1603010200010001fc030331503d966014db2c7034d289c3ee31bcbfcfcffa4219a7b6971bbdec86144b5120ecc056cb75ae5d49ad9a89d82d2b43fe7b8c66d1c4e631e66a80fa273ebb25ae003e130213031301c02cc030009fcca9cca8ccaac02bc02f009ec024c028006bc023c0270067c00ac0140039c009c0130033009d009c003d003c0035002f00ff010001750000000a000800000569702e7362000b000403000102000a00160014001d0017001e00190018010001010102010301040010000e000c02683208687474702f312e31001600000017000000310000000d002a0028040305030603080708080809080a080b080408050806040105010601030303010302040205020602002b0009080304030303020301002d00020101003300260024001d0020bd75abd51a882eeff6a462d1fb12aa7f01ee830c4e6589d6d14e3bcf507e5802001500b800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
|
||||
|
||||
func TestSniffer_SniffTls(t *testing.T) {
|
||||
tests := []struct {
|
||||
@ -25,10 +27,13 @@ func TestSniffer_SniffTls(t *testing.T) {
|
||||
}, {
|
||||
Domain: "odin.game.daum.net",
|
||||
Stream: tlsStreamWindowsOdinGame,
|
||||
}, {
|
||||
Domain: "ip.sb",
|
||||
Stream: tlsCurlIpsb,
|
||||
}}
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
for _, test := range tests {
|
||||
sniffer := NewPacketSniffer(test.Stream)
|
||||
sniffer := NewPacketSniffer(test.Stream, 300*time.Millisecond)
|
||||
d, err := sniffer.SniffTls()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -18,7 +18,7 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
CircularIncludeError = fmt.Errorf("circular include is not allowed")
|
||||
ErrCircularInclude = fmt.Errorf("circular include is not allowed")
|
||||
)
|
||||
|
||||
type Merger struct {
|
||||
@ -51,7 +51,7 @@ func (m *Merger) readEntry(entry string) (err error) {
|
||||
// Check circular include.
|
||||
_, exist := m.entryToSectionMap[entry]
|
||||
if exist {
|
||||
return CircularIncludeError
|
||||
return ErrCircularInclude
|
||||
}
|
||||
|
||||
// Check filename
|
||||
@ -121,7 +121,7 @@ func unsqueezeEntries(patternEntries []string) (unsqueezed []string, err error)
|
||||
func (m *Merger) dfsMerge(entry string, fatherEntry string) (err error) {
|
||||
// Read entry and check circular include.
|
||||
if err = m.readEntry(entry); err != nil {
|
||||
if errors.Is(err, CircularIncludeError) {
|
||||
if errors.Is(err, ErrCircularInclude) {
|
||||
return fmt.Errorf("%w: %v -> %v -> ... -> %v", err, fatherEntry, entry, fatherEntry)
|
||||
}
|
||||
return err
|
||||
|
215
control/anyfrom_pool.go
Normal file
215
control/anyfrom_pool.go
Normal file
@ -0,0 +1,215 @@
|
||||
package control
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"math"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"strconv"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/daeuniverse/dae/component/outbound/dialer"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
type Anyfrom struct {
|
||||
*net.UDPConn
|
||||
deadlineTimer *time.Timer
|
||||
ttl time.Duration
|
||||
// GSO support is modified from quic-go with many thanks.
|
||||
gso bool
|
||||
gotGSOError bool
|
||||
}
|
||||
|
||||
func (a *Anyfrom) afterWrite(err error) {
|
||||
if !a.gotGSOError && isGSOError(err) {
|
||||
a.gotGSOError = true
|
||||
}
|
||||
a.RefreshTtl()
|
||||
}
|
||||
func (a *Anyfrom) RefreshTtl() {
|
||||
a.deadlineTimer.Reset(a.ttl)
|
||||
}
|
||||
func (a *Anyfrom) SupportGso(size int) bool {
|
||||
if size > math.MaxUint16 {
|
||||
return false
|
||||
}
|
||||
return a.gso && !a.gotGSOError
|
||||
}
|
||||
func (a *Anyfrom) ReadFrom(b []byte) (int, net.Addr, error) {
|
||||
defer a.RefreshTtl()
|
||||
return a.UDPConn.ReadFrom(b)
|
||||
}
|
||||
func (a *Anyfrom) ReadFromUDP(b []byte) (n int, addr *net.UDPAddr, err error) {
|
||||
defer a.RefreshTtl()
|
||||
return a.UDPConn.ReadFromUDP(b)
|
||||
}
|
||||
func (a *Anyfrom) ReadFromUDPAddrPort(b []byte) (n int, addr netip.AddrPort, err error) {
|
||||
defer a.RefreshTtl()
|
||||
return a.UDPConn.ReadFromUDPAddrPort(b)
|
||||
}
|
||||
func (a *Anyfrom) ReadMsgUDP(b []byte, oob []byte) (n int, oobn int, flags int, addr *net.UDPAddr, err error) {
|
||||
defer a.RefreshTtl()
|
||||
return a.UDPConn.ReadMsgUDP(b, oob)
|
||||
}
|
||||
func (a *Anyfrom) ReadMsgUDPAddrPort(b []byte, oob []byte) (n int, oobn int, flags int, addr netip.AddrPort, err error) {
|
||||
defer a.RefreshTtl()
|
||||
return a.UDPConn.ReadMsgUDPAddrPort(b, oob)
|
||||
}
|
||||
func (a *Anyfrom) SyscallConn() (syscall.RawConn, error) {
|
||||
defer a.RefreshTtl()
|
||||
return a.UDPConn.SyscallConn()
|
||||
}
|
||||
func (a *Anyfrom) WriteMsgUDP(b []byte, oob []byte, addr *net.UDPAddr) (n int, oobn int, err error) {
|
||||
defer a.afterWrite(err)
|
||||
if a.SupportGso(len(b)) {
|
||||
return a.UDPConn.WriteMsgUDP(b, appendUDPSegmentSizeMsg(oob, uint16(len(b))), addr)
|
||||
}
|
||||
return a.UDPConn.WriteMsgUDP(b, oob, addr)
|
||||
}
|
||||
func (a *Anyfrom) WriteMsgUDPAddrPort(b []byte, oob []byte, addr netip.AddrPort) (n int, oobn int, err error) {
|
||||
defer a.afterWrite(err)
|
||||
if a.SupportGso(len(b)) {
|
||||
return a.UDPConn.WriteMsgUDPAddrPort(b, appendUDPSegmentSizeMsg(oob, uint16(len(b))), addr)
|
||||
}
|
||||
return a.UDPConn.WriteMsgUDPAddrPort(b, oob, addr)
|
||||
}
|
||||
func (a *Anyfrom) WriteTo(b []byte, addr net.Addr) (n int, err error) {
|
||||
defer a.afterWrite(err)
|
||||
if a.SupportGso(len(b)) {
|
||||
n, _, err = a.UDPConn.WriteMsgUDP(b, appendUDPSegmentSizeMsg(nil, uint16(len(b))), addr.(*net.UDPAddr))
|
||||
return n, err
|
||||
}
|
||||
return a.UDPConn.WriteTo(b, addr)
|
||||
}
|
||||
func (a *Anyfrom) WriteToUDP(b []byte, addr *net.UDPAddr) (n int, err error) {
|
||||
defer a.afterWrite(err)
|
||||
if a.SupportGso(len(b)) {
|
||||
n, _, err = a.UDPConn.WriteMsgUDP(b, appendUDPSegmentSizeMsg(nil, uint16(len(b))), addr)
|
||||
return n, err
|
||||
}
|
||||
return a.UDPConn.WriteToUDP(b, addr)
|
||||
}
|
||||
func (a *Anyfrom) WriteToUDPAddrPort(b []byte, addr netip.AddrPort) (n int, err error) {
|
||||
defer a.afterWrite(err)
|
||||
if a.SupportGso(len(b)) {
|
||||
n, _, err = a.UDPConn.WriteMsgUDPAddrPort(b, appendUDPSegmentSizeMsg(nil, uint16(len(b))), addr)
|
||||
return n, err
|
||||
}
|
||||
return a.UDPConn.WriteToUDPAddrPort(b, addr)
|
||||
}
|
||||
|
||||
// isGSOSupported tests if the kernel supports GSO.
|
||||
// Sending with GSO might still fail later on, if the interface doesn't support it (see isGSOError).
|
||||
func isGSOSupported(uc *net.UDPConn) bool {
|
||||
// We disable GSO because we haven't thought through how to design to use larger packets.
|
||||
return false
|
||||
conn, err := uc.SyscallConn()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
disabled, err := strconv.ParseBool(os.Getenv("DAE_DISABLE_GSO"))
|
||||
if err == nil && disabled {
|
||||
return false
|
||||
}
|
||||
var serr error
|
||||
if err := conn.Control(func(fd uintptr) {
|
||||
_, serr = unix.GetsockoptInt(int(fd), unix.IPPROTO_UDP, unix.UDP_SEGMENT)
|
||||
}); err != nil {
|
||||
return false
|
||||
}
|
||||
return serr == nil
|
||||
}
|
||||
func isGSOError(err error) bool {
|
||||
var serr *os.SyscallError
|
||||
if errors.As(err, &serr) {
|
||||
// EIO is returned by udp_send_skb() if the device driver does not have tx checksums enabled,
|
||||
// which is a hard requirement of UDP_SEGMENT. See:
|
||||
// https://git.kernel.org/pub/scm/docs/man-pages/man-pages.git/tree/man7/udp.7?id=806eabd74910447f21005160e90957bde4db0183#n228
|
||||
// https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/ipv4/udp.c?h=v6.2&id=c9c3395d5e3dcc6daee66c6908354d47bf98cb0c#n942
|
||||
return serr.Err == unix.EIO
|
||||
}
|
||||
return false
|
||||
}
|
||||
func appendUDPSegmentSizeMsg(b []byte, size uint16) []byte {
|
||||
startLen := len(b)
|
||||
const dataLen = 2 // payload is a uint16
|
||||
b = append(b, make([]byte, unix.CmsgSpace(dataLen))...)
|
||||
h := (*unix.Cmsghdr)(unsafe.Pointer(&b[startLen]))
|
||||
h.Level = syscall.IPPROTO_UDP
|
||||
h.Type = unix.UDP_SEGMENT
|
||||
h.SetLen(unix.CmsgLen(dataLen))
|
||||
|
||||
// UnixRights uses the private `data` method, but I *think* this achieves the same goal.
|
||||
offset := startLen + unix.CmsgSpace(0)
|
||||
*(*uint16)(unsafe.Pointer(&b[offset])) = size
|
||||
return b
|
||||
}
|
||||
|
||||
// AnyfromPool is a full-cone udp listener pool
|
||||
type AnyfromPool struct {
|
||||
pool map[string]*Anyfrom
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
var DefaultAnyfromPool = NewAnyfromPool()
|
||||
|
||||
func NewAnyfromPool() *AnyfromPool {
|
||||
return &AnyfromPool{
|
||||
pool: make(map[string]*Anyfrom, 64),
|
||||
mu: sync.RWMutex{},
|
||||
}
|
||||
}
|
||||
|
||||
func (p *AnyfromPool) GetOrCreate(lAddr string, ttl time.Duration) (conn *Anyfrom, isNew bool, err error) {
|
||||
p.mu.RLock()
|
||||
af, ok := p.pool[lAddr]
|
||||
if !ok {
|
||||
p.mu.RUnlock()
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
if af, ok = p.pool[lAddr]; ok {
|
||||
return af, false, nil
|
||||
}
|
||||
// Create an Anyfrom.
|
||||
isNew = true
|
||||
d := net.ListenConfig{
|
||||
Control: func(network string, address string, c syscall.RawConn) error {
|
||||
return dialer.TransparentControl(c)
|
||||
},
|
||||
KeepAlive: 0,
|
||||
}
|
||||
pc, err := d.ListenPacket(context.Background(), "udp", lAddr)
|
||||
if err != nil {
|
||||
return nil, true, err
|
||||
}
|
||||
uConn := pc.(*net.UDPConn)
|
||||
af = &Anyfrom{
|
||||
UDPConn: uConn,
|
||||
deadlineTimer: nil,
|
||||
ttl: ttl,
|
||||
gotGSOError: false,
|
||||
gso: isGSOSupported(uConn),
|
||||
}
|
||||
af.deadlineTimer = time.AfterFunc(ttl, func() {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
_af := p.pool[lAddr]
|
||||
if _af == af {
|
||||
delete(p.pool, lAddr)
|
||||
af.Close()
|
||||
}
|
||||
})
|
||||
p.pool[lAddr] = af
|
||||
return af, true, nil
|
||||
} else {
|
||||
af.RefreshTtl()
|
||||
p.mu.RUnlock()
|
||||
return af, false, nil
|
||||
}
|
||||
}
|
@ -767,8 +767,7 @@ func (c *ControlPlane) Serve(readyChan chan<- bool, listener *Listener) (err err
|
||||
goto destRetrieved
|
||||
}
|
||||
}
|
||||
n := copy(data, data[dataOffset:])
|
||||
data = data[:n]
|
||||
data = data[dataOffset:]
|
||||
routingResult = &addrHdr.RoutingResult
|
||||
__ip := common.Ipv6Uint32ArrayToByteSlice(addrHdr.Ip)
|
||||
_ip, _ := netip.AddrFromSlice(__ip)
|
||||
@ -779,7 +778,7 @@ func (c *ControlPlane) Serve(readyChan chan<- bool, listener *Listener) (err err
|
||||
realDst = pktDst
|
||||
}
|
||||
destRetrieved:
|
||||
if e := c.handlePkt(udpConn, data, common.ConvergeAddrPort(src), common.ConvergeAddrPort(pktDst), common.ConvergeAddrPort(realDst), routingResult); e != nil {
|
||||
if e := c.handlePkt(udpConn, data, common.ConvergeAddrPort(src), common.ConvergeAddrPort(pktDst), common.ConvergeAddrPort(realDst), routingResult, false); e != nil {
|
||||
c.log.Warnln("handlePkt:", e)
|
||||
}
|
||||
}(newBuf, newOob, src)
|
||||
|
@ -47,7 +47,7 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
UnsupportedQuestionTypeError = fmt.Errorf("unsupported question type")
|
||||
ErrUnsupportedQuestionType = fmt.Errorf("unsupported question type")
|
||||
)
|
||||
|
||||
var (
|
||||
|
104
control/packet_sniffer_pool.go
Normal file
104
control/packet_sniffer_pool.go
Normal file
@ -0,0 +1,104 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Copyright (c) 2022-2023, daeuniverse Organization <dae@v2raya.org>
|
||||
*/
|
||||
|
||||
package control
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/daeuniverse/dae/component/sniffing"
|
||||
)
|
||||
|
||||
const (
|
||||
PacketSnifferTtl = 3 * time.Second
|
||||
)
|
||||
|
||||
type PacketSniffer struct {
|
||||
*sniffing.Sniffer
|
||||
deadlineTimer *time.Timer
|
||||
Mu sync.Mutex
|
||||
}
|
||||
|
||||
// PacketSnifferPool is a full-cone udp conn pool
|
||||
type PacketSnifferPool struct {
|
||||
pool sync.Map
|
||||
createMuMap sync.Map
|
||||
}
|
||||
type PacketSnifferOptions struct {
|
||||
Ttl time.Duration
|
||||
}
|
||||
type PacketSnifferKey struct {
|
||||
LAddr netip.AddrPort
|
||||
RAddr netip.AddrPort
|
||||
}
|
||||
|
||||
var DefaultPacketSnifferPool = NewPacketSnifferPool()
|
||||
|
||||
func NewPacketSnifferPool() *PacketSnifferPool {
|
||||
return &PacketSnifferPool{}
|
||||
}
|
||||
|
||||
func (p *PacketSnifferPool) Remove(key PacketSnifferKey, sniffer *PacketSniffer) (err error) {
|
||||
if ue, ok := p.pool.LoadAndDelete(key); ok {
|
||||
sniffer.Close()
|
||||
if ue != sniffer {
|
||||
return fmt.Errorf("target udp endpoint is not in the pool")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *PacketSnifferPool) Get(key PacketSnifferKey) *PacketSniffer {
|
||||
_qs, ok := p.pool.Load(key)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return _qs.(*PacketSniffer)
|
||||
}
|
||||
|
||||
func (p *PacketSnifferPool) GetOrCreate(key PacketSnifferKey, createOption *PacketSnifferOptions) (qs *PacketSniffer, isNew bool) {
|
||||
_qs, ok := p.pool.Load(key)
|
||||
begin:
|
||||
if !ok {
|
||||
createMu, _ := p.createMuMap.LoadOrStore(key, &sync.Mutex{})
|
||||
createMu.(*sync.Mutex).Lock()
|
||||
defer createMu.(*sync.Mutex).Unlock()
|
||||
defer p.createMuMap.Delete(key)
|
||||
_qs, ok = p.pool.Load(key)
|
||||
if ok {
|
||||
goto begin
|
||||
}
|
||||
// Create an PacketSniffer.
|
||||
if createOption == nil {
|
||||
createOption = &PacketSnifferOptions{}
|
||||
}
|
||||
if createOption.Ttl == 0 {
|
||||
createOption.Ttl = PacketSnifferTtl
|
||||
}
|
||||
|
||||
qs = &PacketSniffer{
|
||||
Sniffer: sniffing.NewPacketSniffer(nil, createOption.Ttl),
|
||||
Mu: sync.Mutex{},
|
||||
deadlineTimer: nil,
|
||||
}
|
||||
qs.deadlineTimer = time.AfterFunc(createOption.Ttl, func() {
|
||||
if _qs, ok := p.pool.LoadAndDelete(key); ok {
|
||||
if _qs.(*PacketSniffer) == qs {
|
||||
qs.Close()
|
||||
} else {
|
||||
// FIXME: ?
|
||||
}
|
||||
}
|
||||
})
|
||||
_qs = qs
|
||||
p.pool.Store(key, qs)
|
||||
// Receive UDP messages.
|
||||
isNew = true
|
||||
}
|
||||
return _qs.(*PacketSniffer), isNew
|
||||
}
|
59
control/packet_sniffer_pool_test.go
Normal file
59
control/packet_sniffer_pool_test.go
Normal file
@ -0,0 +1,59 @@
|
||||
package control
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"net/netip"
|
||||
"testing"
|
||||
|
||||
"github.com/daeuniverse/dae/component/sniffing"
|
||||
)
|
||||
|
||||
var testPacketSnifferData = []string{
|
||||
"cc0000000108e8da6ed9f385c987000044d026f109c2764c22f0ea2656550ea03e832d0ed5113eff115f2a057f77655cf5bbbb69fc98f7f70a3f407e0d94f37960c5ba5bd95a2df75f6f25020c2f2f21ddf9db5266bb4293991d58efec945468a820c61b743ca4b73663c3adcda58dee75607c5465e255b58477069a928687789c18c2ccb53911a47d64b83d5b58398ee4fd58f4f88f78788d5594218730cab9db3bac2fbfb947f2cb4eafb5e2964fce361042c622dfa7130afaf0e9d391ffc3aba2f5ee2f5c4d0dfaae0d71db2b3d7fab6dbccbb63d7961ddab55711d5a1beacf00ce5a82030a2c79c4ea65a2762f3b8e5f8fec8f6963b1a42c0f8a8d863225b2d6e7a15e9758e43095459e3d7ff88dc276605452b10de95a8795fe9952eb0b1eb200465ca9b00f98e2c4ad6a2a2e2bff2e2430438241525e1d16d5423c2262134a97056b7e86d5eb7eb2ac546086a3b8d7a97bc2263fa9a8b46f4b7d31cad63762c17a653b89593434aecf7a5e8fc169cfb5aa4a47e78ee817e115feceb9b68b29da6e15c647b7528980fb7cdc7c9ca660871228d0367f030f658d19ddddefe55908a2ec4ef5f5d89ec5aebee33f88a116c2857f7d1a2fd98321f28468a93938da406a68e4e660f0668fe49118812d5264073f28a8aa800c5970ef3f6fb4f0e9e4e48510700a5465c92886c50f2c6af570075f29f6a80636171f73d91864583d2d199e39b18623ee0cb489b449838bd9f7cd67ccc3e38f1b5a3ce08814f979f94db45cdcfa39a475e3efc4847def8e8e4c707a88d2f486fc85e10910ab0f1bbeb40468af777ff2bb0e655f1a006cde0d2e2ae036dafe60f110e859543699e0c9aa47eefa53d792b3cbcfa11ea1d3b55d3629de0345517d47f4e4c801104b81710ad28cd8611e150a1fc32160cb784cfcfdd908052cd43969b27929013edd2b0f3cd914590a32b2f99d4fc88873838b6fa0ec1450adb95f395988998801e85319fa448925ba767e3191df2b5b0983990beb4127216c93291a94463b453a4972c9a974742b0b22c935f4235c350120b6cf8296fc6d3c2812f74a17acf334e3c34ff9988f980e0cfff737a8b1a03508f47d8bf3748fbb5bd5ad7f1f47120c3a33822612f3a614aae7fe536b73db814aa4aac4b685aa1e7357309cf921b931113624881ce764feeff3292d2d794c6fa76529f3da8e6327e8f28aafe8b675a80ae3f478c65f1bf8fd7f2b140fea130dfa55982f0b0fcd61b42c8b2ea27a2b8bb44511eb44c1416ac16698f0ddb739e3d773f2afdd35bcfed0ffd7966aa3e727f8f08d02cab8d034a7ae363e42c9089901ddee147c98a856df4e5dcfeeb2f72e9edb12da513f32d99e1c653f4503e9a7f7fee1f4724ce9d6d530485362d993cb3bc4faff683327a02aee6f004bd9f98a8a4841091d48f5cd27af46431c66e68007750be57361e293650a0ae9fc9fa82ddf4483663c9805dc6e4a9b43529c0b2267cc3c0fb9084378acbda4962150a73e0c1b5aef6e40538d2630d8dbc2b084f9a53079cc73484906b7ad4a5021f280baf276a01b0fcea57d5c4284364f4d795645fc7bd8bb7d00021af924b75829e8a936e153676a182803537a23c76fee7c881e8063751ca0f5a585481b9077e9593734f9997e78b79ba38f6e13a1b631106a2ceddafdf51110b8bf07ec9337024355088d0bb3de2d46a03d3e3e7362b8b815613e36d746e5a9992f8e62ad5257e5798bd49b1a62717f02151b75a18e051df1292191d4",
|
||||
"ce0000000108e8da6ed9f385c987000044d0f34f94dcc26b99261ea264742abe4e552a146e16e89e4b7ef0ab3d6f3a34227b59742e4ba83a1e18cea494d2f67e469be4a7ff01334b151e9b7ca63b53735008eecc1f5c618419982292eca5731bb163ba81c1300e0bb99f2536d89ab0faf2dbd37ebfdb3d71f7343296a2190914bda556b8f9ccf5219964eb3cd373966fcfaca8a4735fb59fbaf69bbbdfc3a81b11570bb81fd3f5ef780fb7036e0666b997b0f4ed3305b68eafa1a99b3c8a6a2142ad9fe1e6b0a0eade6ace92b57416d4bf68fa2e9295bfc22757b0542ce91c8af3f547ef0ad385788db230a50158a0009fd95a7e8ee6e0dd11d6f9a906cbe8117e85bd507cdbd8f1a5a6cabf2617de7227d1ae8a8c6086b8ec325df90c0e16b37b4ed0ce617a00c7598a21924a19aec1b08c31b69430b23eefbe555ca2433431d28a4ffec548e463e8e6363b6b4fe9b8477c686c393571273c30b2e1785261faa0fd6f560c12418b27cd0491e013db5a8b3294e01a46a6e4c6b52e32756ab4be6f4ebc886c0c472d63f117ce30115182a97f1308c7f28989ce301cabced825154b0f4fa3bf4a55ce2f384ff11d9cbc0460d69db363664f92dc014bdb771b9b1e1ab6672c6da71c90aa514dcdc3a4ce45298bf9e5a395ebac3dff2a738c4b4690ee06fdab572a277addac7035d94afe794df05da75a56c79c37f42de1d727dc65e3060d9331e2fc82de2d7cef6cb9ae46f648b9930593975c35960b24deb770d5ee4332f8f57a05503399ca7bfdf7207f66a0f73d6b53269a944d5a3043b225adddfdd29d20ea8f500bb09ea3bb724083dd29ea8839e8192c4360ba3c5a6db0d695af5d357d6c4ed94aa28305033629201689764189774bbd4f0ae41b878b8f29a0fe0e124075ea08c5054871506a05be2f90e9ec0c2db48c0780580312e9ff4071054386e4206841f575f7ca06c228f7ee11e2333d08652b9b4f0b97f473a46a3d79c4f9a3416fb20fdbd88cacfa36f06fe1d73618195c6f0bf759a77c6a16b7e271c6cdb672ea53f6edfac860fcaf03313564abde1f66bca441d844d289a9e1025711c284f2c7c805353f2a89e9aeb52e3f452e879f0fafcdc0b48a0676afcf617a85037d991762664f6db64847eff2308447c4e8ea6688838bb7237a5fdfe0f1695afaa0bbb821b0004585adf151b029bd3458e28ba49dfc17eef1d2dd14ccda88d0848d4cd36d33cc5bab173c2448785ec1bdabc8873c904b95d7847d1b89857f2c7e078c6e2eb96029aa91c077e0efcf7b2ed2f30c7abc12189627793c7870dc0e70342cc27402ee1d6dec5ceea0ca06159002ea14a20c63b85689ed1840f404e46cb83d91c5e02f3ed938462364d3349f689310234083f7044e4b338ac54bed94530640d684c9688651b915d8c8895ef0f05f376292871b589751ac5b233e3d85572bb0c11bbbe91cc49a4ef0422f2676a2f3cc62bc88dbb7acf03cb5e847e976bfca6a90b9cee743ea77be5472ef162ff101c6873043df94c53c252840fd6a2662018f0897a06cd215997d6050917876500796fef718957212c773c39d1c7b839931af1e7dfae6e2c1d2251e78896521bb35b20057bad77df85aaed90288c17edb081398815e47239aeb77293a02a61a5125109fc3953593233fa83c17770a815fad7831c1b8647c6089ec621ee774a12a714def498d4335d0bb8a4a6a3dddead8ddb1176f58218477d55317df88cd2ca5a06b72679cf2ff7253ebd76a5ed3",
|
||||
}
|
||||
|
||||
func TestPacketSniffer_Normal(t *testing.T) {
|
||||
for _, _data := range testPacketSnifferData {
|
||||
data, _ := hex.DecodeString(_data)
|
||||
sniffer, _ := DefaultPacketSnifferPool.GetOrCreate(PacketSnifferKey{
|
||||
LAddr: netip.MustParseAddrPort("1.1.1.1:1111"),
|
||||
RAddr: netip.MustParseAddrPort("2.2.2.2:2222"),
|
||||
}, nil)
|
||||
sniffer.AppendData(data)
|
||||
domain, err := sniffer.SniffUdp()
|
||||
if err != nil && !sniffing.IsSniffingError(err) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if sniffer.NeedMore() {
|
||||
continue
|
||||
}
|
||||
sniffer.Close()
|
||||
t.Log(domain)
|
||||
return
|
||||
}
|
||||
t.Fatal("not found")
|
||||
}
|
||||
|
||||
func TestPacketSniffer_Mismatched(t *testing.T) {
|
||||
dst := netip.MustParseAddrPort("2.2.2.2:2222")
|
||||
for _, _data := range testPacketSnifferData {
|
||||
data, _ := hex.DecodeString(_data)
|
||||
sniffer, _ := DefaultPacketSnifferPool.GetOrCreate(PacketSnifferKey{
|
||||
LAddr: netip.MustParseAddrPort("1.1.1.1:1111"),
|
||||
RAddr: dst,
|
||||
}, nil)
|
||||
sniffer.AppendData(data)
|
||||
domain, err := sniffer.SniffUdp()
|
||||
if err != nil && !sniffing.IsSniffingError(err) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if sniffer.NeedMore() {
|
||||
dst = netip.AddrPortFrom(dst.Addr(), dst.Port()+1)
|
||||
continue
|
||||
}
|
||||
sniffer.Close()
|
||||
t.Fatal("unexpected found", domain)
|
||||
return
|
||||
}
|
||||
}
|
@ -21,7 +21,7 @@ import (
|
||||
"github.com/daeuniverse/dae/component/outbound/dialer"
|
||||
"github.com/daeuniverse/dae/component/sniffing"
|
||||
internal "github.com/daeuniverse/dae/pkg/ebpf_internal"
|
||||
"github.com/daeuniverse/softwind/pkg/zeroalloc/buffer"
|
||||
"github.com/daeuniverse/softwind/pool"
|
||||
dnsmessage "github.com/miekg/dns"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
@ -29,6 +29,7 @@ import (
|
||||
const (
|
||||
DefaultNatTimeout = 3 * time.Minute
|
||||
DnsNatTimeout = 17 * time.Second // RFC 5452
|
||||
AnyfromTimeout = 5 * time.Second // Do not cache too long.
|
||||
MaxRetry = 2
|
||||
)
|
||||
|
||||
@ -73,8 +74,8 @@ func sendPktWithHdrWithFlag(data []byte, realFrom netip.AddrPort, lConn *net.UDP
|
||||
},
|
||||
}
|
||||
// Do not put this 'buf' because it has been taken by buffer.
|
||||
b := buffer.NewBuffer(int(unsafe.Sizeof(hdr)) + len(data))
|
||||
defer b.Put()
|
||||
b := pool.GetBuffer()
|
||||
defer pool.PutBuffer(b)
|
||||
// Use internal.NativeEndian due to already big endian.
|
||||
if err := binary.Write(b, internal.NativeEndian, hdr); err != nil {
|
||||
return err
|
||||
@ -102,11 +103,7 @@ func sendPkt(data []byte, from netip.AddrPort, realTo, to netip.AddrPort, lConn
|
||||
return sendPktWithHdrWithFlag(data, from, lConn, to, lanWanFlag)
|
||||
}
|
||||
|
||||
d := net.Dialer{Control: func(network, address string, c syscall.RawConn) error {
|
||||
return dialer.BindControl(c, from)
|
||||
}}
|
||||
var conn net.Conn
|
||||
conn, err = d.Dial("udp", realTo.String())
|
||||
uConn, _, err := DefaultAnyfromPool.GetOrCreate(from.String(), AnyfromTimeout)
|
||||
if err != nil {
|
||||
if errors.Is(err, syscall.EADDRINUSE) {
|
||||
// Port collision, use traditional method.
|
||||
@ -114,13 +111,11 @@ func sendPkt(data []byte, from netip.AddrPort, realTo, to netip.AddrPort, lConn
|
||||
}
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
uConn := conn.(*net.UDPConn)
|
||||
_, err = uConn.Write(data)
|
||||
_, err = uConn.WriteToUDPAddrPort(data, realTo)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *ControlPlane) handlePkt(lConn *net.UDPConn, data []byte, src, pktDst, realDst netip.AddrPort, routingResult *bpfRoutingResult) (err error) {
|
||||
func (c *ControlPlane) handlePkt(lConn *net.UDPConn, data []byte, src, pktDst, realDst netip.AddrPort, routingResult *bpfRoutingResult, skipSniffing bool) (err error) {
|
||||
var lanWanFlag consts.LanWanFlag
|
||||
var realSrc netip.AddrPort
|
||||
var domain string
|
||||
@ -138,13 +133,51 @@ func (c *ControlPlane) handlePkt(lConn *net.UDPConn, data []byte, src, pktDst, r
|
||||
dnsMessage, natTimeout := ChooseNatTimeout(data, realDst.Port() == 53)
|
||||
// We should cache DNS records and set record TTL to 0, in order to monitor the dns req and resp in real time.
|
||||
isDns := dnsMessage != nil
|
||||
if !isDns {
|
||||
if !isDns && !skipSniffing && !DefaultUdpEndpointPool.Exists(realSrc) {
|
||||
// Sniff Quic, ...
|
||||
sniffer := sniffing.NewPacketSniffer(data)
|
||||
defer sniffer.Close()
|
||||
domain, err = sniffer.SniffUdp()
|
||||
if err != nil && !sniffing.IsSniffingError(err) {
|
||||
return err
|
||||
key := PacketSnifferKey{
|
||||
LAddr: realSrc,
|
||||
RAddr: realDst,
|
||||
}
|
||||
_sniffer, _ := DefaultPacketSnifferPool.GetOrCreate(key, nil)
|
||||
_sniffer.Mu.Lock()
|
||||
// Re-get sniffer from pool to confirm the transaction is not done.
|
||||
sniffer := DefaultPacketSnifferPool.Get(key)
|
||||
if _sniffer == sniffer {
|
||||
sniffer.AppendData(data)
|
||||
domain, err = sniffer.SniffUdp()
|
||||
if err != nil && !sniffing.IsSniffingError(err) {
|
||||
sniffer.Mu.Unlock()
|
||||
return err
|
||||
}
|
||||
if sniffer.NeedMore() {
|
||||
sniffer.Mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
logrus.WithError(err).
|
||||
WithField("from", realSrc).
|
||||
WithField("to", realDst).
|
||||
Trace("sniffUdp")
|
||||
}
|
||||
defer DefaultPacketSnifferPool.Remove(key, sniffer)
|
||||
// Re-handlePkt after self func.
|
||||
toRehandle := sniffer.Data()[1 : len(sniffer.Data())-1] // Skip the first empty and the last (self).
|
||||
sniffer.Mu.Unlock()
|
||||
if len(toRehandle) > 0 {
|
||||
defer func() {
|
||||
if err == nil {
|
||||
for _, d := range toRehandle {
|
||||
dCopy := pool.Get(len(d))
|
||||
copy(dCopy, d)
|
||||
go c.handlePkt(lConn, dCopy, src, pktDst, realDst, routingResult, true)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
} else {
|
||||
_sniffer.Mu.Unlock()
|
||||
// sniffer may be nil.
|
||||
}
|
||||
}
|
||||
if routingResult.Must > 0 {
|
||||
@ -179,7 +212,18 @@ func (c *ControlPlane) handlePkt(lConn *net.UDPConn, data []byte, src, pktDst, r
|
||||
}
|
||||
// Get outbound.
|
||||
outboundIndex := consts.OutboundIndex(routingResult.Outbound)
|
||||
dialTarget, shouldReroute, dialIp := c.ChooseDialTarget(outboundIndex, realDst, domain)
|
||||
var (
|
||||
dialTarget string
|
||||
shouldReroute bool
|
||||
dialIp bool
|
||||
)
|
||||
_, shouldReroute, _ = c.ChooseDialTarget(outboundIndex, realDst, domain)
|
||||
// Do not overwrite target.
|
||||
// This fixes a problem that quic connection to google servers.
|
||||
// Reproduce:
|
||||
// docker run --rm --name curl-http3 ymuski/curl-http3 curl --http3 -o /dev/null -v -L https://i.ytimg.com
|
||||
dialTarget = realDst.String()
|
||||
dialIp = true
|
||||
getNew:
|
||||
if retry > MaxRetry {
|
||||
c.log.WithFields(logrus.Fields{
|
||||
@ -220,8 +264,10 @@ getNew:
|
||||
outboundIndex.String(),
|
||||
)
|
||||
}
|
||||
// Reset dialTarget.
|
||||
dialTarget, _, dialIp = c.ChooseDialTarget(outboundIndex, realDst, domain)
|
||||
// Do not overwrite target.
|
||||
// This fixes quic problem from google.
|
||||
// Reproduce:
|
||||
// docker run --rm --name curl-http3 ymuski/curl-http3 curl --http3 -o /dev/null -v -L https://i.ytimg.com
|
||||
default:
|
||||
}
|
||||
|
||||
@ -290,13 +336,13 @@ getNew:
|
||||
|
||||
// Print log.
|
||||
// Only print routing for new connection to avoid the log exploded (Quic and BT).
|
||||
if isNew && c.log.IsLevelEnabled(logrus.InfoLevel) || c.log.IsLevelEnabled(logrus.DebugLevel) {
|
||||
if (isNew && c.log.IsLevelEnabled(logrus.InfoLevel)) || c.log.IsLevelEnabled(logrus.DebugLevel) {
|
||||
fields := logrus.Fields{
|
||||
"network": networkType.StringWithoutDns(),
|
||||
"outbound": ue.Outbound.Name,
|
||||
"policy": ue.Outbound.GetSelectionPolicy(),
|
||||
"dialer": ue.Dialer.Property().Name,
|
||||
"domain": domain,
|
||||
"sniffed": domain,
|
||||
"ip": RefineAddrPortToShow(realDst),
|
||||
"pid": routingResult.Pid,
|
||||
"dscp": routingResult.Dscp,
|
||||
|
@ -87,6 +87,7 @@ func NewUdpEndpointPool() *UdpEndpointPool {
|
||||
func (p *UdpEndpointPool) Remove(lAddr netip.AddrPort, udpEndpoint *UdpEndpoint) (err error) {
|
||||
if ue, ok := p.pool.LoadAndDelete(lAddr); ok {
|
||||
if ue != udpEndpoint {
|
||||
udpEndpoint.Close()
|
||||
return fmt.Errorf("target udp endpoint is not in the pool")
|
||||
}
|
||||
ue.(*UdpEndpoint).Close()
|
||||
@ -94,6 +95,11 @@ func (p *UdpEndpointPool) Remove(lAddr netip.AddrPort, udpEndpoint *UdpEndpoint)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *UdpEndpointPool) Exists(lAddr netip.AddrPort) (ok bool) {
|
||||
_, ok = p.pool.Load(lAddr)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (p *UdpEndpointPool) GetOrCreate(lAddr netip.AddrPort, createOption *UdpEndpointOptions) (udpEndpoint *UdpEndpoint, isNew bool, err error) {
|
||||
_ue, ok := p.pool.Load(lAddr)
|
||||
begin:
|
||||
@ -134,17 +140,22 @@ begin:
|
||||
return nil, true, fmt.Errorf("protocol does not support udp")
|
||||
}
|
||||
ue := &UdpEndpoint{
|
||||
conn: udpConn.(netproxy.PacketConn),
|
||||
deadlineTimer: time.AfterFunc(createOption.NatTimeout, func() {
|
||||
if ue, ok := p.pool.LoadAndDelete(lAddr); ok {
|
||||
ue.(*UdpEndpoint).Close()
|
||||
}
|
||||
}),
|
||||
handler: createOption.Handler,
|
||||
NatTimeout: createOption.NatTimeout,
|
||||
Dialer: dialOption.Dialer,
|
||||
Outbound: dialOption.Outbound,
|
||||
conn: udpConn.(netproxy.PacketConn),
|
||||
deadlineTimer: nil,
|
||||
handler: createOption.Handler,
|
||||
NatTimeout: createOption.NatTimeout,
|
||||
Dialer: dialOption.Dialer,
|
||||
Outbound: dialOption.Outbound,
|
||||
}
|
||||
ue.deadlineTimer = time.AfterFunc(createOption.NatTimeout, func() {
|
||||
if _ue, ok := p.pool.LoadAndDelete(lAddr); ok {
|
||||
if _ue == ue {
|
||||
ue.Close()
|
||||
} else {
|
||||
// FIXME: ?
|
||||
}
|
||||
}
|
||||
})
|
||||
_ue = ue
|
||||
p.pool.Store(lAddr, ue)
|
||||
// Receive UDP messages.
|
@ -200,6 +200,8 @@ routing {
|
||||
|
||||
### Write your rules below.
|
||||
|
||||
# Disable h3 because it usually consumes too much cpu/mem resources.
|
||||
l4proto(udp) && dport(443) -> block
|
||||
dip(geoip:private) -> direct
|
||||
dip(geoip:cn) -> direct
|
||||
domain(geosite:cn) -> direct
|
||||
|
@ -194,6 +194,8 @@ routing {
|
||||
|
||||
### 以下为自定义规则
|
||||
|
||||
# 禁用 h3,因为它通常消耗很多 CPU 和内存资源
|
||||
l4proto(udp) && dport(443) -> block
|
||||
dip(geoip:private) -> direct
|
||||
dip(geoip:cn) -> direct
|
||||
domain(geosite:cn) -> direct
|
||||
|
@ -213,6 +213,8 @@ routing {
|
||||
|
||||
### Write your rules below.
|
||||
|
||||
# Disable h3 because it usually consumes too much cpu/mem resources.
|
||||
l4proto(udp) && dport(443) -> block
|
||||
dip(geoip:cn) -> direct
|
||||
domain(geosite:cn) -> direct
|
||||
|
||||
|
10
go.mod
10
go.mod
@ -1,6 +1,6 @@
|
||||
module github.com/daeuniverse/dae
|
||||
|
||||
go 1.20
|
||||
go 1.21
|
||||
|
||||
require (
|
||||
github.com/adrg/xdg v0.4.0
|
||||
@ -8,7 +8,7 @@ require (
|
||||
github.com/bits-and-blooms/bloom/v3 v3.5.0
|
||||
github.com/cilium/ebpf v0.11.0
|
||||
github.com/daeuniverse/dae-config-dist/go/dae_config v0.0.0-20230604120805-1c27619b592d
|
||||
github.com/daeuniverse/softwind v0.0.0-20230821142121-f4d871b5a8c9
|
||||
github.com/daeuniverse/softwind v0.0.0-20230902065137-dcc321666f9a
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
github.com/json-iterator/go v1.1.12
|
||||
github.com/miekg/dns v1.1.55
|
||||
@ -34,9 +34,9 @@ require (
|
||||
github.com/golang/mock v1.6.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20230705174524-200ffdc848b8 // indirect
|
||||
github.com/klauspost/compress v1.16.7 // indirect
|
||||
github.com/mzz2017/quic-go v0.0.0-20230821141654-3dd2575ee6bc // indirect
|
||||
github.com/mzz2017/quic-go v0.0.0-20230902042923-a727c1c479d4 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.11.0 // indirect
|
||||
github.com/quic-go/qtls-go1-20 v0.3.2 // indirect
|
||||
github.com/quic-go/qtls-go1-20 v0.3.3 // indirect
|
||||
golang.org/x/mod v0.12.0 // indirect
|
||||
golang.org/x/net v0.12.0 // indirect
|
||||
golang.org/x/tools v0.11.0 // indirect
|
||||
@ -72,7 +72,7 @@ require (
|
||||
|
||||
// replace github.com/daeuniverse/softwind => ../softwind
|
||||
|
||||
// replace github.com/metacubex/quic-go => ../quic-go
|
||||
// replace github.com/mzz2017/quic-go => ../quic-go
|
||||
|
||||
//replace github.com/cilium/ebpf => /home/mzz/goProjects/ebpf
|
||||
//replace github.com/daeuniverse/dae-config-dist/go/dae_config => /home/mzz/antlrProjects/dae-config/build/go/dae_config
|
||||
|
22
go.sum
22
go.sum
@ -13,8 +13,8 @@ github.com/cilium/ebpf v0.11.0/go.mod h1:WE7CZAnqOL2RouJ4f1uyNhqr2P4CCvXFIqdRDUg
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/daeuniverse/dae-config-dist/go/dae_config v0.0.0-20230604120805-1c27619b592d h1:hnC39MjR7xt5kZjrKlef7DXKFDkiX8MIcDXYC/6Jf9Q=
|
||||
github.com/daeuniverse/dae-config-dist/go/dae_config v0.0.0-20230604120805-1c27619b592d/go.mod h1:VGWGgv7pCP5WGyHGUyb9+nq/gW0yBm+i/GfCNATOJ1M=
|
||||
github.com/daeuniverse/softwind v0.0.0-20230821142121-f4d871b5a8c9 h1:88k/mjYFuA5294C50M3rXMqRYaqSNAnuk9hnjY/xGMM=
|
||||
github.com/daeuniverse/softwind v0.0.0-20230821142121-f4d871b5a8c9/go.mod h1:K9Au9LY2ttqfAhZXxPPnyqt4Bue1dd/Xi8WPsZEgxOk=
|
||||
github.com/daeuniverse/softwind v0.0.0-20230902065137-dcc321666f9a h1:Eb16FC5oAAM4jdPobNPgJqEiZGBkyjpzGVLWRtAVuis=
|
||||
github.com/daeuniverse/softwind v0.0.0-20230902065137-dcc321666f9a/go.mod h1:oUzDGY0PDAiXCEIm5DNMewP4KpCAOEXOzZJ7JEP1080=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@ -28,15 +28,18 @@ github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140/go.mod h1:c9O8+fp
|
||||
github.com/dgryski/go-rc2 v0.0.0-20150621095337-8a9021637152 h1:ED31mPIxDJnrLt9W9dH5xgd/6KjzEACKHBVGQ33czc0=
|
||||
github.com/dgryski/go-rc2 v0.0.0-20150621095337-8a9021637152/go.mod h1:I9fhc/EvSg88cDxmfQ47v35Ssz9rlFunL/KY0A1JAYI=
|
||||
github.com/ebfe/rc2 v0.0.0-20131011165748-24b9757f5521 h1:fBHFH+Y/GPGFGo7LIrErQc3p2MeAhoIQNgaxPWYsSxk=
|
||||
github.com/ebfe/rc2 v0.0.0-20131011165748-24b9757f5521/go.mod h1:ucvhdsUCE3TH0LoLRb6ShHiJl8e39dGlx6A4g/ujlow=
|
||||
github.com/eknkc/basex v1.0.1 h1:TcyAkqh4oJXgV3WYyL4KEfCMk9W8oJCpmx1bo+jVgKY=
|
||||
github.com/eknkc/basex v1.0.1/go.mod h1:k/F/exNEHFdbs3ZHuasoP2E7zeWwZblG84Y7Z59vQRo=
|
||||
github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA=
|
||||
github.com/frankban/quicktest v1.14.5/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk=
|
||||
github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI=
|
||||
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
||||
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||
@ -57,6 +60,7 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20230705174524-200ffdc848b8 h1:n6vlPhxsA+BW/XsS5+uqi7GyzaLa5MH7qlSLBZtRdiA=
|
||||
github.com/google/pprof v0.0.0-20230705174524-200ffdc848b8/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA=
|
||||
@ -72,7 +76,9 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm
|
||||
github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
|
||||
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
@ -91,8 +97,8 @@ github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
|
||||
github.com/mzz2017/disk-bloom v1.0.1 h1:rEF9MiXd9qMW3ibRpqcerLXULoTgRlM21yqqJl1B90M=
|
||||
github.com/mzz2017/disk-bloom v1.0.1/go.mod h1:JLHETtUu44Z6iBmsqzkOtFlRvXSlKnxjwiBRDapizDI=
|
||||
github.com/mzz2017/quic-go v0.0.0-20230821141654-3dd2575ee6bc h1:2gjLlS2yBxXUGICgHSWGLS5LyRa0Lr6+w5GFiqOco/o=
|
||||
github.com/mzz2017/quic-go v0.0.0-20230821141654-3dd2575ee6bc/go.mod h1:j4yzgjc6nLseaxzQT/As8D7VRQMKASjVWXBunJGTF8Y=
|
||||
github.com/mzz2017/quic-go v0.0.0-20230902042923-a727c1c479d4 h1:7T4E6LsmEhclfkgOt5pCiBgZNRC7h45sIvLj3FunUO8=
|
||||
github.com/mzz2017/quic-go v0.0.0-20230902042923-a727c1c479d4/go.mod h1:tWtXPktBZvMi0SzXP4QFO8SKDNsAkGEijAeiNe8QmyM=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
@ -107,13 +113,15 @@ github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc=
|
||||
github.com/onsi/gomega v1.27.8/go.mod h1:2J8vzI/s+2shY9XHRApDkdgPo1TKT7P2u6fXeJKFnNQ=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/quic-go/qtls-go1-20 v0.3.2 h1:rRgN3WfnKbyik4dBV8A6girlJVxGand/d+jVKbQq5GI=
|
||||
github.com/quic-go/qtls-go1-20 v0.3.2/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
|
||||
github.com/quic-go/qtls-go1-20 v0.3.3 h1:17/glZSLI9P9fDAeyCHBFSWSqJcwx1byhLwP5eUIDCM=
|
||||
github.com/quic-go/qtls-go1-20 v0.3.3/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
|
||||
github.com/refraction-networking/utls v1.3.2 h1:o+AkWB57mkcoW36ET7uJ002CpBWHu0KPxi6vzxvPnv8=
|
||||
github.com/refraction-networking/utls v1.3.2/go.mod h1:fmoaOww2bxzzEpIKOebIsnBvjQpqP7L2vcm/9KUfm/E=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/safchain/ethtool v0.3.0 h1:gimQJpsI6sc1yIqP/y8GYgiXn/NjgvpM0RNoWLVVmP0=
|
||||
github.com/safchain/ethtool v0.3.0/go.mod h1:SA9BwrgyAqNo7M+uaL6IYbxpm5wk3L7Mm6ocLW+CJUs=
|
||||
@ -132,6 +140,7 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg=
|
||||
github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
|
||||
github.com/v2rayA/ahocorasick-domain v0.0.0-20230218160829-122a074c48c8 h1:2Liq3JvM/acVQZ7Gq9U5PpznMzlFRPYMPQxC2yXSi74=
|
||||
@ -171,6 +180,7 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
2
main.go
2
main.go
@ -16,9 +16,11 @@ import (
|
||||
"github.com/daeuniverse/dae/common/json"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/json-iterator/go/extra"
|
||||
// _ "net/http/pprof"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// go http.ListenAndServe("0.0.0.0:8000", nil)
|
||||
jsoniter.RegisterTypeDecoder("bool", &json.FuzzyBoolDecoder{})
|
||||
extra.RegisterFuzzyDecoders()
|
||||
|
||||
|
@ -5,13 +5,13 @@ package trie
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/daeuniverse/softwind/pkg/zeroalloc/buffer"
|
||||
"math/bits"
|
||||
"net/netip"
|
||||
"sort"
|
||||
|
||||
"github.com/daeuniverse/dae/common"
|
||||
"github.com/daeuniverse/dae/common/bitlist"
|
||||
"github.com/daeuniverse/softwind/pool"
|
||||
)
|
||||
|
||||
var ValidCidrChars = NewValidChars([]byte{'0', '1'})
|
||||
@ -100,8 +100,8 @@ func Prefix2bin128(prefix netip.Prefix) (bin128 string) {
|
||||
n += 96
|
||||
}
|
||||
ip := prefix.Addr().As16()
|
||||
buf := buffer.NewBuffer(128)
|
||||
defer buf.Put()
|
||||
buf := pool.GetBuffer()
|
||||
defer pool.PutBuffer(buf)
|
||||
loop:
|
||||
for i := 0; i < len(ip); i++ {
|
||||
for j := 7; j >= 0; j-- {
|
||||
|
Loading…
Reference in New Issue
Block a user