optimize(udp)/fix(quicSniffer): optimize performance of udp and fix a potential panic of quic (#301)

This commit is contained in:
mzz 2023-11-15 14:32:57 +08:00 committed by GitHub
parent dedc716413
commit 25c047a766
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 968 additions and 466 deletions

View File

@ -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)
}

View File

@ -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,

View File

@ -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()

View File

@ -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)

View File

@ -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) {

View File

@ -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

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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{}

View File

@ -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
}

View File

@ -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)

View 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)
}

View File

@ -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
}

View File

@ -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 {

View File

@ -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.

View File

@ -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)

View File

@ -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
View 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
}
}

View File

@ -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)

View File

@ -47,7 +47,7 @@ const (
)
var (
UnsupportedQuestionTypeError = fmt.Errorf("unsupported question type")
ErrUnsupportedQuestionType = fmt.Errorf("unsupported question type")
)
var (

View 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
}

View 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
}
}

View File

@ -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,

View File

@ -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.

View File

@ -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

View File

@ -194,6 +194,8 @@ routing {
### 以下为自定义规则
# 禁用 h3因为它通常消耗很多 CPU 和内存资源
l4proto(udp) && dport(443) -> block
dip(geoip:private) -> direct
dip(geoip:cn) -> direct
domain(geosite:cn) -> direct

View File

@ -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
View File

@ -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
View File

@ -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=

View File

@ -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()

View File

@ -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-- {