Documentation
¶
Overview ¶
Package sniffer incrementally classifies the client-first prefix of a stream while restoring every inspected byte to a putback.Conn before returning.
Classifiers are state machines. Sniff reads into the caller's buffer in normal batches, then feeds each active classifier one byte at a time in stream order. State is checked after every byte, making classification independent of net.Conn Read chunking. Sniff stops when a classifier reports Match, every classifier reports Mismatch, the caller-provided buffer is exhausted, or the connection returns an error. The complete read prefix is put back on every return path, so another sniffer or the selected protocol handler sees the original byte sequence.
SniffWithPool and SniffFactoriesWithPool allocate the temporary inspection buffer from a bufpool.Pool. Pass the same pool to putback.New when restored bytes should also use pooled storage.
Classifiers and factories report MinSniffBufferSize. Use MinSniffBufferSize or MinFactorySniffBufferSize to select a buffer large enough for the classifiers you pass to Sniff. ClassifierFunc and FactoryFunc report 0 by default; use WithMinSniffBufferSize or FactoryWithMinSniffBufferSize when they need a non-zero size.
Classifiers can optionally implement MetadataProvider. Sniff does not use metadata directly, but callers can inspect the matched classifier with Metadata after Sniff returns. Built-in HTTP and TLS classifiers expose HTTPInfo and TLSClientHelloInfo. Custom classifiers can expose any typed value that their routing code understands.
HTTP and HTTPFactory match HTTP request lines. HTTP2 and HTTP2Factory match the cleartext HTTP/2 client preface. HTTPWithConfig and HTTPFactoryWithConfig can also filter by exact or multi-value methods, exact or glob URL request-targets, HTTP-version tokens, normalized hostnames, and request-line or header byte limits.
TLS and TLSFactory match TLS ClientHello records. TLSWithConfig and TLSFactoryWithConfig can also filter by offered TLS versions, visible SNI availability, encrypted_client_hello extension presence, visible SNI hostnames, ALPN protocol names, and ClientHello byte limits. TLS version filters match versions offered by the ClientHello, not the server-selected version.
ProxyProtocolV1, ProxyProtocolV2, and ProxyProtocol match HAProxy PROXY protocol headers. SOCKS4, SOCKS5, and SOCKS match SOCKS client greetings or requests. DNSOverTCP matches length-prefixed DNS query messages. AMQP matches AMQP protocol headers. MQTT matches MQTT CONNECT headers. PostgreSQL matches startup-family packets. MongoDB matches wire protocol request headers. Redis matches RESP array requests. RTSP and SIP match request lines. STUN matches STUN and TURN headers. RDP matches TPKT/X.224 connection requests. SMB matches SMB-over-TCP negotiate requests. LDAP matches LDAP client request prefixes. Cassandra matches native protocol STARTUP and OPTIONS requests. Memcached matches binary and text protocol requests.
Sniff owns neither timeouts nor policy. Callers should set and clear read deadlines on the connection as appropriate. A deadline error is returned as the connection's read error. Use gonnect.IsTimeout to identify timeout errors when timeout means fallback for the caller. Buffer exhaustion is a normal NoMatch result.
Important limitations:
- Only client-first information can be classified. A server-first protocol, or a client that waits for the server, produces no bytes to inspect. A caller must use metadata, a deadline, or a fallback route for that case.
- Some prefixes are fundamentally ambiguous. If one protocol's complete signature is a prefix of another's, an immediate Match may select the shorter classifier. Sniff returns as soon as any classifier matches; it does not wait to learn whether a NeedMore classifier might match later. If several classifiers match after the same byte, the lowest index wins.
- TCP read boundaries have no protocol meaning. A classifier must handle every fragmentation and coalescing when used outside Sniff and must not assume that one Feed call corresponds to one packet or protocol field.
- Classifiers cannot return errors. Malformed, unsupported, or over-limit input must become Mismatch. The only errors returned by Sniff come from Conn.Read. EOF is returned to the caller; it is not fed to classifiers.
- Sniff restores bytes, not external side effects. Classifiers must not read from or write to the connection themselves, and Feed input is read-only.
- The caller must give Sniff exclusive ownership of the connection's read side until it returns. Concurrent readers can consume bytes that cannot be attributed or restored safely.
- A putback.Conn preserves byte order, but buffered bytes do not call the wrapped connection. Read deadlines and raw socket state are observed only after the put-back buffer drains.
Sniffer is a gonnect.Network middleware in this package. It routes all Network calls to immutable output slots through a Control callback. For outgoing TCP Dial and DialTCP calls, Control can request interception: Sniffer returns a local TCP connection, sniffs client-first bytes from it, restores those bytes, runs SniffControl with the classifier index and metadata, then opens the selected output connection and pipes the stream. Close and Down close only objects returned by Sniffer and never close output Networks.
Index ¶
- Constants
- func Metadata(classifier Classifier) any
- func MinFactorySniffBufferSize(factories ...Factory) int
- func MinSniffBufferSize(classifiers ...Classifier) int
- func Sniff(buffer []byte, conn putback.Conn, classifiers ...Classifier) (index int, err error)
- func SniffFactories(buffer []byte, conn putback.Conn, factories ...Factory) (int, error)
- func SniffFactoriesWithPool(bufferSize int, pool bufpool.Pool, conn putback.Conn, factories ...Factory) (int, error)
- func SniffWithPool(bufferSize int, pool bufpool.Pool, conn putback.Conn, ...) (int, error)
- type Action
- type Call
- type Classifier
- func AMQP() Classifier
- func And(children ...Classifier) Classifier
- func Cassandra() Classifier
- func DNSOverTCP() Classifier
- func DNSOverTCPWithConfig(config DNSOverTCPConfig) Classifier
- func HTTP() Classifier
- func HTTP2() Classifier
- func HTTPWithConfig(config HTTPConfig) Classifier
- func LDAP() Classifier
- func Limit(limit int, child Classifier) Classifier
- func MQTT() Classifier
- func Memcached() Classifier
- func MemcachedASCII() Classifier
- func MemcachedBinary() Classifier
- func MongoDB() Classifier
- func Not(child Classifier) Classifier
- func Or(children ...Classifier) Classifier
- func PostgreSQL() Classifier
- func Prefix(prefix []byte) Classifier
- func ProxyProtocol() Classifier
- func ProxyProtocolV1() Classifier
- func ProxyProtocolV2() Classifier
- func RDP() Classifier
- func RTSP() Classifier
- func Redis() Classifier
- func SIP() Classifier
- func SMB() Classifier
- func SOCKS() Classifier
- func SOCKS4() Classifier
- func SOCKS5() Classifier
- func SSH() Classifier
- func STUN() Classifier
- func TLS() Classifier
- func TLSWithConfig(config TLSConfig) Classifier
- func WithMinSniffBufferSize(minSize int, classifier Classifier) Classifier
- type ClassifierFunc
- type CompositeMetadata
- type Control
- type DNSOverTCPConfig
- type Factory
- func AMQPFactory() Factory
- func AndFactory(children ...Factory) Factory
- func CassandraFactory() Factory
- func DNSOverTCPFactory() Factory
- func DNSOverTCPFactoryWithConfig(config DNSOverTCPConfig) Factory
- func FactoryWithMinSniffBufferSize(minSize int, factory Factory) Factory
- func HTTP2Factory() Factory
- func HTTPFactory() Factory
- func HTTPFactoryWithConfig(config HTTPConfig) Factory
- func LDAPFactory() Factory
- func LimitFactory(limit int, child Factory) Factory
- func MQTTFactory() Factory
- func MemcachedASCIIFactory() Factory
- func MemcachedBinaryFactory() Factory
- func MemcachedFactory() Factory
- func MongoDBFactory() Factory
- func NotFactory(child Factory) Factory
- func OrFactory(children ...Factory) Factory
- func PostgreSQLFactory() Factory
- func PrefixFactory(prefix []byte) Factory
- func ProxyProtocolFactory() Factory
- func ProxyProtocolV1Factory() Factory
- func ProxyProtocolV2Factory() Factory
- func RDPFactory() Factory
- func RTSPFactory() Factory
- func RedisFactory() Factory
- func SIPFactory() Factory
- func SMBFactory() Factory
- func SOCKS4Factory() Factory
- func SOCKS5Factory() Factory
- func SOCKSFactory() Factory
- func SSHFactory() Factory
- func STUNFactory() Factory
- func TLSFactory() Factory
- func TLSFactoryWithConfig(config TLSConfig) Factory
- type FactoryFunc
- type HTTPConfig
- type HTTPInfo
- type MetadataProvider
- type MinSniffBufferSizer
- type Operation
- type SniffControl
- type SniffResult
- type SniffedCall
- type Sniffer
- func (s *Sniffer) Classifiers() []Factory
- func (s *Sniffer) Close() error
- func (s *Sniffer) Dial(ctx context.Context, network, address string) (net.Conn, error)
- func (s *Sniffer) DialTCP(ctx context.Context, network, laddr, raddr string) (gonnect.TCPConn, error)
- func (s *Sniffer) DialUDP(ctx context.Context, network, laddr, raddr string) (gonnect.UDPConn, error)
- func (s *Sniffer) Down() error
- func (s *Sniffer) InterfaceAddrs() ([]net.Addr, error)
- func (s *Sniffer) InterfaceMulticastAddrs() ([]net.Addr, error)
- func (s *Sniffer) Interfaces() ([]gonnect.NetworkInterface, error)
- func (s *Sniffer) InterfacesByIndex(index int) ([]gonnect.NetworkInterface, error)
- func (s *Sniffer) InterfacesByName(name string) ([]gonnect.NetworkInterface, error)
- func (s *Sniffer) IsNative() bool
- func (s *Sniffer) IsUp() (bool, error)
- func (s *Sniffer) Listen(ctx context.Context, network, address string) (net.Listener, error)
- func (s *Sniffer) ListenMulticastUDP(ctx context.Context, network, address string, opts gonnect.MulticastOptions) (gonnect.MulticastPacketConn, error)
- func (s *Sniffer) ListenPacket(ctx context.Context, network, address string) (gonnect.PacketConn, error)
- func (s *Sniffer) ListenPacketConfig(ctx context.Context, lc *gonnect.ListenConfig, network, address string) (gonnect.PacketConn, error)
- func (s *Sniffer) ListenTCP(ctx context.Context, network, laddr string) (gonnect.TCPListener, error)
- func (s *Sniffer) ListenUDP(ctx context.Context, network, laddr string) (gonnect.UDPConn, error)
- func (s *Sniffer) ListenUDPConfig(ctx context.Context, lc *gonnect.ListenConfig, network, laddr string) (gonnect.UDPConn, error)
- func (s *Sniffer) LookupAddr(ctx context.Context, addr string) ([]string, error)
- func (s *Sniffer) LookupCNAME(ctx context.Context, host string) (string, error)
- func (s *Sniffer) LookupHost(ctx context.Context, host string) ([]string, error)
- func (s *Sniffer) LookupIP(ctx context.Context, network, address string) ([]net.IP, error)
- func (s *Sniffer) LookupIPAddr(ctx context.Context, host string) ([]net.IPAddr, error)
- func (s *Sniffer) LookupMX(ctx context.Context, name string) ([]*net.MX, error)
- func (s *Sniffer) LookupNS(ctx context.Context, name string) ([]*net.NS, error)
- func (s *Sniffer) LookupNetIP(ctx context.Context, network, host string) ([]netip.Addr, error)
- func (s *Sniffer) LookupPort(ctx context.Context, network, service string) (int, error)
- func (s *Sniffer) LookupSRV(ctx context.Context, service, proto, name string) (string, []*net.SRV, error)
- func (s *Sniffer) LookupTXT(ctx context.Context, name string) ([]string, error)
- func (s *Sniffer) Outputs() []gonnect.Network
- func (s *Sniffer) PacketDial(ctx context.Context, network, address string) (gonnect.PacketConn, error)
- func (s *Sniffer) SubscribeCloser(c io.Closer) (func(), error)
- func (s *Sniffer) SubscribeUpDown(u gonnect.UpDown) (func(), error)
- func (s *Sniffer) Up() error
- type SnifferConfig
- type State
- type TLSClientHelloInfo
- type TLSConfig
- type TLSFlag
Examples ¶
Constants ¶
const ( // RejectSlot rejects an operation instead of routing it to an output. RejectSlot = 0 // DefaultSlot is the default output slot used when no control callback is // configured. Slots are 1-based. DefaultSlot = 1 )
const DefaultDNSOverTCPMessageMaxBytes = 4096
DefaultDNSOverTCPMessageMaxBytes is the default DNS-over-TCP message inspection limit used by DNSOverTCP and DNSOverTCPFactory.
The limit applies to the DNS message bytes after the two-byte TCP length prefix. A message that declares a larger size mismatches.
const DefaultHTTPHeaderMaxBytes = 8192
DefaultHTTPHeaderMaxBytes is the default HTTP header inspection limit.
The limit applies only when the classifier must inspect headers, such as when Hostname, Hostnames, or HostnamePatterns is set.
const DefaultHTTPRequestLineMaxBytes = 4096
DefaultHTTPRequestLineMaxBytes is the default HTTP request-line inspection limit used by HTTP and HTTPFactory.
The limit includes the line-ending byte. A request line that does not end with LF within this many bytes mismatches.
const DefaultTLSClientHelloMaxBytes = 64 * 1024
DefaultTLSClientHelloMaxBytes is the default TLS ClientHello inspection limit used by TLS and TLSFactory.
The limit applies to bytes read from the stream while parsing the first ClientHello. It includes TLS record headers, handshake headers, and ClientHello data. A ClientHello that is not complete within this many bytes mismatches.
const NoMatch = -1
NoMatch is returned by Sniff when no classifier produced Match.
Variables ¶
This section is empty.
Functions ¶
func Metadata ¶ added in v0.38.0
func Metadata(classifier Classifier) any
Metadata returns metadata from classifier when it implements MetadataProvider. Otherwise it returns nil.
func MinFactorySniffBufferSize ¶
MinFactorySniffBufferSize returns the largest reported minimum Sniff buffer size from factories.
func MinSniffBufferSize ¶
func MinSniffBufferSize(classifiers ...Classifier) int
MinSniffBufferSize returns the largest reported minimum Sniff buffer size from classifiers.
func Sniff ¶
Sniff incrementally classifies bytes read from conn.
buffer is both scratch storage and the total byte budget for this call. Its length, not its capacity, is the maximum number of bytes Sniff may inspect. The contents on entry are ignored and may be overwritten. A zero-length buffer causes an immediate NoMatch unless a classifier matches on its initial empty Feed.
classifiers are stateful instances intended for this one sniffing operation. Sniff first calls Feed(nil) on each classifier. It reads from conn in normal batches, but feeds classifiers one byte at a time and checks their states after every byte. This makes selection independent of net.Conn Read chunking. As soon as one or more classifiers match on a byte, Sniff returns the lowest matching index. It does not delay a match while another classifier still needs more bytes.
Every byte read by Sniff is put back before Sniff returns, including on no match and read-error paths. Thus callers may invoke another Sniff on the same putback.Conn or hand it to a protocol implementation without losing bytes.
Return values are:
- index >= 0, err == nil: classifiers[index] matched.
- index == NoMatch, err == nil: all classifiers mismatched, buffer was exhausted, buffer was empty, no classifiers were supplied, or a Read made no progress by returning (0, nil).
- index == NoMatch, err != nil: conn.Read returned that error before a definitive classifier result.
Sniff creates no errors of its own. A nil conn or nil/invalid classifier is a programming error and causes a panic.
The caller must provide exclusive read-side access to conn until Sniff returns. Deadline policy is deliberately outside this function; set any read deadline before calling Sniff and clear or replace it afterward.
Example ¶
ExampleSniff shows routing by a client-first prefix. A real server would wrap the net.Conn returned by Accept and set its own classification deadline.
package main
import (
"fmt"
"net"
"time"
"github.com/asciimoth/bufpool"
"github.com/asciimoth/gonnect/putback"
"github.com/asciimoth/gonnect/sniffer"
)
func closeExampleConn(conn net.Conn) {
_ = conn.Close()
}
func main() {
server, client := net.Pipe()
defer closeExampleConn(server)
defer closeExampleConn(client)
go func() {
_, _ = client.Write([]byte("SSH-2.0-example\r\n"))
}()
var pool bufpool.Pool
conn := putback.New(server, pool)
_ = conn.SetReadDeadline(time.Now().Add(time.Second))
factories := []sniffer.Factory{
sniffer.PrefixFactory([]byte("GET ")),
sniffer.SSHFactory(),
}
index, err := sniffer.SniffFactoriesWithPool(
sniffer.MinFactorySniffBufferSize(factories...),
pool,
conn,
factories...,
)
_ = conn.SetReadDeadline(time.Time{})
fmt.Println(index, err)
}
Output: 1 <nil>
Example (Chain) ¶
ExampleSniff_chain shows that a no-match result does not consume the stream.
package main
import (
"fmt"
"net"
"github.com/asciimoth/bufpool"
"github.com/asciimoth/gonnect/putback"
"github.com/asciimoth/gonnect/sniffer"
)
func closeExampleConn(conn net.Conn) {
_ = conn.Close()
}
func main() {
server, client := net.Pipe()
defer closeExampleConn(server)
defer closeExampleConn(client)
go func() {
_, _ = client.Write([]byte("SSH-2.0-example\r\n"))
}()
var pool bufpool.Pool
conn := putback.New(server, pool)
first, _ := sniffer.SniffWithPool(
8,
pool,
conn,
sniffer.Prefix([]byte{0x16, 0x03}), // simple TLS record prefix
)
second, _ := sniffer.SniffWithPool(4, pool, conn, sniffer.SSH())
fmt.Println(first == sniffer.NoMatch, second)
}
Output: true 0
Example (TimeoutFallback) ¶
ExampleSniff_timeoutFallback shows how callers can turn a classification timeout into a fallback route while returning other read errors.
package main
import (
"fmt"
"net"
"time"
"github.com/asciimoth/bufpool"
"github.com/asciimoth/gonnect"
"github.com/asciimoth/gonnect/putback"
"github.com/asciimoth/gonnect/sniffer"
)
func closeExampleConn(conn net.Conn) {
_ = conn.Close()
}
func main() {
server, client := net.Pipe()
defer closeExampleConn(server)
defer closeExampleConn(client)
var pool bufpool.Pool
conn := putback.New(server, pool)
_ = conn.SetReadDeadline(time.Now().Add(-time.Nanosecond))
index, err := sniffer.SniffWithPool(64, pool, conn, sniffer.SSH())
_ = conn.SetReadDeadline(time.Time{})
fmt.Println(index == sniffer.NoMatch, gonnect.IsTimeout(err))
}
Output: true true
func SniffFactories ¶
SniffFactories is a convenience wrapper that creates one fresh classifier from each factory and calls Sniff. The returned index refers to factories.
func SniffFactoriesWithPool ¶
func SniffFactoriesWithPool( bufferSize int, pool bufpool.Pool, conn putback.Conn, factories ...Factory, ) (int, error)
SniffFactoriesWithPool is a convenience wrapper that creates one fresh classifier from each factory and calls SniffWithPool. The returned index refers to factories.
func SniffWithPool ¶
func SniffWithPool( bufferSize int, pool bufpool.Pool, conn putback.Conn, classifiers ...Classifier, ) (int, error)
SniffWithPool gets the inspection buffer from pool, calls Sniff, and returns the buffer to pool before it returns.
bufferSize is the total byte budget for this call. A zero bufferSize has the same behavior as passing a zero-length buffer to Sniff. bufferSize must not be negative.
Types ¶
type Action ¶ added in v0.38.0
Action is the routing decision returned by a control callback.
Slot is a 1-based output slot. Slot 0 rejects the call. Invalid slots and nil output slots also reject the call.
Intercept is honored only for outgoing TCP Dial and DialTCP calls. When it is true for those calls, Sniffer returns a local mock TCP connection, sniffs client-first bytes from that connection, and then calls SniffControl for the final route. When it is true for any other call, Sniffer rejects the call as if Slot were RejectSlot.
type Call ¶ added in v0.38.0
type Call struct {
Operation Operation
Network string
Src string
Dst string
Host string
Service string
Proto string
IfIndex int
IfName string
ListenConfig *gonnect.ListenConfig
MulticastOptions gonnect.MulticastOptions
}
Call contains the arguments for one Network method call.
The control callback receives a pointer to a Call copy. It may modify fields before returning its Action. Sniffer uses the modified fields for routing. The callback must not retain the pointer.
Dial-style operations use Dst for the remote address. DialTCP and DialUDP also use Src for the local address. Listen-style operations use Src for the listen address. Lookup operations use Host for the lookup name or address, except LookupPort, which uses Service, and LookupSRV, which uses Service, Proto, and Host. Interface lookups use IfIndex or IfName.
type Classifier ¶
type Classifier interface {
MinSniffBufferSizer
Feed(p []byte) State
}
Classifier incrementally recognizes a byte stream prefix.
Feed receives only bytes not supplied in earlier calls, in stream order, and returns the state after consuming p. Feed may be called with an empty slice to query the initial/current state without advancing input. Implementations must therefore handle empty input.
Match and Mismatch are terminal: after returning either state, a classifier must return the same state from all later Feed calls. Sniff and the supplied combinators normally stop feeding terminal classifiers.
p is read-only. A classifier must not modify it and must not retain it unless it copies the bytes it needs. The caller's sniff buffer may be reused after Sniff returns.
Classifiers do not return errors. Invalid, malformed, unsupported, or classifier-specific over-limit data should result in Mismatch.
func AMQP ¶
func AMQP() Classifier
AMQP returns a classifier for AMQP protocol headers.
It matches the eight-byte protocol header for AMQP 0-9-1, AMQP 1.0, or AMQP 1.0 SASL negotiation. It does not inspect later connection tuning, SASL frames, or AMQP frames.
func And ¶
func And(children ...Classifier) Classifier
And returns a classifier that matches when every child matches, mismatches when any child mismatches, and otherwise needs more bytes.
And with no children is the boolean identity true and therefore matches on its initial empty Feed.
func Cassandra ¶
func Cassandra() Classifier
Cassandra returns a classifier for Cassandra native protocol startup requests.
It validates a v3, v4, or v5 request header and accepts STARTUP or OPTIONS, which are the normal first client messages on a Cassandra connection. It does not inspect the body string map or compression.
func DNSOverTCP ¶
func DNSOverTCP() Classifier
DNSOverTCP returns a classifier that matches a DNS-over-TCP query.
The classifier reads the two-byte TCP length prefix, then validates a DNS message header and question section. It requires a client query, not a response, and at least one question.
func DNSOverTCPWithConfig ¶
func DNSOverTCPWithConfig(config DNSOverTCPConfig) Classifier
DNSOverTCPWithConfig returns a DNS-over-TCP classifier that uses config.
func HTTP ¶
func HTTP() Classifier
HTTP returns a classifier that matches an HTTP request line.
The classifier accepts any syntactically valid method token, non-empty request-target, and HTTP-version token that starts with "HTTP/". Use HTTPWithConfig when the route must match specific methods, URL request-targets, HTTP versions, hostnames, or byte limits.
func HTTP2 ¶
func HTTP2() Classifier
HTTP2 returns a classifier for cleartext HTTP/2 prior knowledge.
It matches the HTTP/2 client connection preface at stream offset zero. TLS connections that negotiate HTTP/2 by ALPN are still TLS streams; use TLS with an ALPN filter for those connections.
func HTTPWithConfig ¶
func HTTPWithConfig(config HTTPConfig) Classifier
HTTPWithConfig returns a classifier that matches an HTTP request and the fields requested by config.
func LDAP ¶
func LDAP() Classifier
LDAP returns a classifier for LDAP client messages.
It validates enough BER to find a non-zero message ID and a client request protocolOp tag. It does not parse request fields, controls, filters, or authentication data.
func Limit ¶
func Limit(limit int, child Classifier) Classifier
Limit wraps child and changes NeedMore to Mismatch after limit bytes have been fed to it. It is useful for making a classifier's own inspection bound explicit independently of Sniff's total buffer bound.
At most limit bytes are passed to child. If a Feed chunk crosses the limit, only the portion within the limit is passed. A child that matches or mismatches within that portion keeps its result. limit must be non-negative.
func MQTT ¶
func MQTT() Classifier
MQTT returns a classifier for MQTT CONNECT packets.
It validates the fixed header packet type, decodes the Remaining Length field, and checks the CONNECT variable header through protocol name, version level, connect flags, and keep-alive. MQTT 3.1, 3.1.1, and 5 CONNECT headers are accepted. The classifier matches before reading the client identifier or other payload fields.
func Memcached ¶
func Memcached() Classifier
Memcached returns a classifier for memcached binary or text protocol requests.
func MemcachedASCII ¶
func MemcachedASCII() Classifier
MemcachedASCII returns a classifier for memcached text protocol requests.
It validates the first CRLF-terminated command line for common storage, retrieval, counter, delete, touch, flush, stats, version, quit, and verbosity commands. It matches before reading any value bytes after a storage command.
func MemcachedBinary ¶
func MemcachedBinary() Classifier
MemcachedBinary returns a classifier for memcached binary protocol requests.
It validates the fixed 24-byte request header, request magic, known opcode, data type, extras length, key presence rules, and body length. It does not inspect the key, value, CAS, or opaque fields.
func MongoDB ¶
func MongoDB() Classifier
MongoDB returns a classifier for MongoDB wire protocol request messages.
It validates the 16-byte wire message header, requires a client request opcode, and requires responseTo to be zero. It does not inspect BSON command documents or compressed message bodies.
func Not ¶
func Not(child Classifier) Classifier
Not returns the boolean negation of child. NeedMore remains NeedMore, Match becomes Mismatch, and Mismatch becomes Match.
func Or ¶
func Or(children ...Classifier) Classifier
Or returns a classifier that matches when any child matches, mismatches when every child mismatches, and otherwise needs more bytes.
Or with no children is the boolean identity false and therefore mismatches on its initial empty Feed.
func PostgreSQL ¶
func PostgreSQL() Classifier
PostgreSQL returns a classifier for PostgreSQL client startup messages.
It validates the first eight bytes of the client startup packet. Normal protocol 3.0 startup messages, SSLRequest, GSSENCRequest, and CancelRequest packets are accepted. It does not inspect startup parameters, user names, databases, or cancellation keys.
func Prefix ¶
func Prefix(prefix []byte) Classifier
Prefix returns a classifier that matches when the stream begins with prefix. It mismatches at the first differing byte and needs more bytes while the bytes seen so far are a proper prefix of prefix.
Prefix copies prefix. An empty prefix matches immediately on an empty Feed.
func ProxyProtocol ¶
func ProxyProtocol() Classifier
ProxyProtocol returns a classifier for HAProxy PROXY protocol v1 or v2.
func ProxyProtocolV1 ¶
func ProxyProtocolV1() Classifier
ProxyProtocolV1 returns a classifier for HAProxy PROXY protocol v1.
It matches the ASCII prefix "PROXY " at stream offset zero. This is a routing heuristic, not a full PROXY v1 header parser. It does not validate the source address, destination address, ports, or line ending.
func ProxyProtocolV2 ¶
func ProxyProtocolV2() Classifier
ProxyProtocolV2 returns a classifier for HAProxy PROXY protocol v2.
It validates the binary signature and fixed 16-byte header. For PROXY commands with a known address family, it also checks that the declared payload length can contain the required source and destination addresses. It does not inspect address values or TLVs.
func RDP ¶
func RDP() Classifier
RDP returns a classifier for RDP connection requests over TPKT.
It validates the TPKT header and the X.224 Connection Request fixed fields. It matches before optional cookies, routing tokens, or negotiation data are parsed.
func RTSP ¶
func RTSP() Classifier
RTSP returns a classifier for RTSP request lines.
It matches a known RTSP method, a non-empty request URI, and the RTSP/1.0 version token on the first CRLF-terminated line. It does not inspect RTSP headers, Transport parameters, CSeq, or message bodies.
func Redis ¶
func Redis() Classifier
Redis returns a classifier for Redis RESP array requests.
It validates the first request array and its first bulk-string command name. Inline Redis commands are intentionally not matched because their text forms are ambiguous with other line-oriented protocols. The command name must be non-empty, use printable non-space bytes, and be no larger than 128 bytes.
func SIP ¶
func SIP() Classifier
SIP returns a classifier for SIP request lines.
It matches a known SIP method, a non-empty Request-URI, and the SIP/2.0 version token on the first CRLF-terminated line. It does not inspect Via, To, From, Call-ID, CSeq, or message bodies.
func SMB ¶
func SMB() Classifier
SMB returns a classifier for SMB over TCP client negotiate requests.
It validates the NetBIOS Session Service header and the start of an SMB1 or SMB2/3 negotiate request. It does not inspect dialects, security modes, capabilities, or later SMB messages.
func SOCKS ¶
func SOCKS() Classifier
SOCKS returns a classifier for SOCKS4, SOCKS4a, or SOCKS5 client streams.
func SOCKS4 ¶
func SOCKS4() Classifier
SOCKS4 returns a classifier for SOCKS4 and SOCKS4a requests.
It validates the fixed request header: version 4, CONNECT or BIND command, non-zero destination port, and non-zero destination address marker. It matches before reading the user ID or the optional SOCKS4a hostname.
func SOCKS5 ¶
func SOCKS5() Classifier
SOCKS5 returns a classifier for SOCKS5 client greetings.
It validates the version byte, waits for the declared method list, and rejects empty method lists.
func SSH ¶
func SSH() Classifier
SSH returns a simple SSH transport classifier.
It matches the four-byte ASCII prefix "SSH-" at stream offset zero. This is intentionally a routing heuristic, not a complete SSH identification-line validator. It does not validate protocol/software versions and does not accept non-SSH preamble lines before the identification string.
func STUN ¶
func STUN() Classifier
STUN returns a classifier for STUN and TURN messages.
It validates the fixed 20-byte STUN header: message type top bits, message length alignment, magic cookie, and known method. It accepts STUN methods used by TURN as well. It does not inspect attributes or integrity.
func TLS ¶
func TLS() Classifier
TLS returns a classifier that matches a syntactically valid TLS ClientHello.
Use TLSWithConfig when the route must match offered TLS versions, SNI availability, ECH presence, SNI hostnames, ALPN protocols, or byte limits.
func TLSWithConfig ¶
func TLSWithConfig(config TLSConfig) Classifier
TLSWithConfig returns a classifier that matches a TLS ClientHello and the fields requested by config.
func WithMinSniffBufferSize ¶
func WithMinSniffBufferSize( minSize int, classifier Classifier, ) Classifier
WithMinSniffBufferSize wraps classifier and reports minSize as its minimum Sniff buffer size. It is useful for custom classifiers.
type ClassifierFunc ¶
ClassifierFunc adapts a function to Classifier. It reports a minimum Sniff buffer size of 0. Use WithMinSniffBufferSize when f needs a larger buffer.
func (ClassifierFunc) MinSniffBufferSize ¶
func (f ClassifierFunc) MinSniffBufferSize() int
MinSniffBufferSize returns 0.
type CompositeMetadata ¶ added in v0.38.0
type CompositeMetadata struct {
Children []any
}
CompositeMetadata stores child metadata from a composite classifier.
Children has the same order as the child classifiers. A nil child value means that child had no metadata.
type DNSOverTCPConfig ¶
type DNSOverTCPConfig struct {
MaxMessageBytes int
}
DNSOverTCPConfig configures a DNS-over-TCP classifier.
MaxMessageBytes bounds the DNS message bytes after the two-byte TCP length prefix. Zero uses DefaultDNSOverTCPMessageMaxBytes. A negative value, or a value above the 65535-byte DNS TCP length field, is a programming error and causes a panic.
type Factory ¶
type Factory interface {
MinSniffBufferSizer
NewClassifier() Classifier
}
Factory constructs a fresh classifier for one stream.
NewClassifier must return an independent instance on every call. A Factory may be shared by goroutines and should therefore be safe for concurrent use.
func AndFactory ¶
AndFactory returns a factory that constructs a fresh And classifier around fresh classifiers from children.
func CassandraFactory ¶
func CassandraFactory() Factory
CassandraFactory returns a factory for Cassandra classifiers.
func DNSOverTCPFactory ¶
func DNSOverTCPFactory() Factory
DNSOverTCPFactory returns a factory for DNSOverTCP classifiers.
func DNSOverTCPFactoryWithConfig ¶
func DNSOverTCPFactoryWithConfig(config DNSOverTCPConfig) Factory
DNSOverTCPFactoryWithConfig returns a factory for DNS-over-TCP classifiers that use config.
func FactoryWithMinSniffBufferSize ¶
FactoryWithMinSniffBufferSize wraps factory and reports minSize as the minimum Sniff buffer size needed by classifiers it creates.
func HTTP2Factory ¶
func HTTP2Factory() Factory
HTTP2Factory returns a factory for HTTP2 classifiers.
func HTTPFactory ¶
func HTTPFactory() Factory
HTTPFactory returns a factory for HTTP classifiers with the default config.
func HTTPFactoryWithConfig ¶
func HTTPFactoryWithConfig(config HTTPConfig) Factory
HTTPFactoryWithConfig returns a factory for HTTP classifiers that use config.
func LimitFactory ¶
LimitFactory returns a factory that applies Limit to fresh child instances.
func MemcachedASCIIFactory ¶
func MemcachedASCIIFactory() Factory
MemcachedASCIIFactory returns a factory for memcached text classifiers.
func MemcachedBinaryFactory ¶
func MemcachedBinaryFactory() Factory
MemcachedBinaryFactory returns a factory for memcached binary classifiers.
func MemcachedFactory ¶
func MemcachedFactory() Factory
MemcachedFactory returns a factory for memcached binary or text classifiers.
func MongoDBFactory ¶
func MongoDBFactory() Factory
MongoDBFactory returns a factory for MongoDB classifiers.
func NotFactory ¶
NotFactory returns a factory that constructs a fresh Not classifier around a fresh classifier from child.
func OrFactory ¶
OrFactory returns a factory that constructs a fresh Or classifier around fresh classifiers from children.
func PostgreSQLFactory ¶
func PostgreSQLFactory() Factory
PostgreSQLFactory returns a factory for PostgreSQL classifiers.
func PrefixFactory ¶
PrefixFactory returns a factory for Prefix classifiers. It copies prefix when the factory is created, so later caller mutation does not affect instances.
func ProxyProtocolFactory ¶
func ProxyProtocolFactory() Factory
ProxyProtocolFactory returns a factory for PROXY protocol classifiers.
func ProxyProtocolV1Factory ¶
func ProxyProtocolV1Factory() Factory
ProxyProtocolV1Factory returns a factory for PROXY protocol v1 classifiers.
func ProxyProtocolV2Factory ¶
func ProxyProtocolV2Factory() Factory
ProxyProtocolV2Factory returns a factory for PROXY protocol v2 classifiers.
func RedisFactory ¶
func RedisFactory() Factory
RedisFactory returns a factory for Redis classifiers.
func SOCKS4Factory ¶
func SOCKS4Factory() Factory
SOCKS4Factory returns a factory for SOCKS4 classifiers.
func SOCKS5Factory ¶
func SOCKS5Factory() Factory
SOCKS5Factory returns a factory for SOCKS5 classifiers.
func SOCKSFactory ¶
func SOCKSFactory() Factory
SOCKSFactory returns a factory for SOCKS classifiers.
func TLSFactory ¶
func TLSFactory() Factory
TLSFactory returns a factory for TLS classifiers with the default config.
func TLSFactoryWithConfig ¶
TLSFactoryWithConfig returns a factory for TLS classifiers that use config.
type FactoryFunc ¶
type FactoryFunc func() Classifier
FactoryFunc adapts a function to Factory. It reports a minimum Sniff buffer size of 0. Use FactoryWithMinSniffBufferSize when f needs a larger buffer.
func (FactoryFunc) MinSniffBufferSize ¶
func (f FactoryFunc) MinSniffBufferSize() int
MinSniffBufferSize returns 0.
func (FactoryFunc) NewClassifier ¶
func (f FactoryFunc) NewClassifier() Classifier
NewClassifier calls f.
type HTTPConfig ¶
type HTTPConfig struct {
MaxRequestLineBytes int
MaxHeaderBytes int
Method string
Methods []string
URL string
URLs []string
URLPatterns []string
Version string
Versions []string
Hostname string
Hostnames []string
HostnamePatterns []string
}
HTTPConfig configures an HTTP request classifier.
Empty field groups are wildcards. Non-empty values in a field group are ORed within that group, and all configured groups must match. For example, Methods {"GET", "POST"} and URLPatterns {"/api/*"} matches GET or POST requests whose request-target matches /api/*.
The singular Method, URL, Version, and Hostname fields are exact-match shortcuts. They are ORed with their plural exact-match fields. Empty strings in plural fields are ignored.
URL and URLPatterns match the request-target exactly as sent on the wire, such as /path?q=1 or http://example.com/path. Version and Versions match the HTTP-version token, such as HTTP/1.1. Leave Version and Versions empty to accept any HTTP-version token that starts with HTTP/.
URLPatterns and HostnamePatterns are byte-oriented glob patterns. A pattern must match the whole value. In a pattern, * matches any byte sequence, ? matches one byte, and \ escapes the next byte.
Hostname, Hostnames, and HostnamePatterns match a normalized hostname from the absolute-form request-target or the Host header. Matching is case-insensitive, and an optional port is removed before matching.
MaxRequestLineBytes bounds how many request-line bytes the classifier may inspect, including LF. MaxHeaderBytes bounds bytes after the request line when hostname matching must inspect headers. Zero values use the defaults. A negative value is a programming error and causes a panic.
type HTTPInfo ¶ added in v0.38.0
HTTPInfo is metadata parsed from an HTTP request prefix.
Method, URL, and Version come from the request line. Hostname is normalized and lower-case when it was visible from an absolute-form request-target or from a Host header inspected by the classifier. Hostname can be empty when the classifier did not need to inspect a Host header.
type MetadataProvider ¶ added in v0.38.0
type MetadataProvider interface {
Metadata() any
}
MetadataProvider is implemented by classifiers that expose parsed metadata.
Metadata returns data that is meaningful to the classifier implementation. It should return nil when no metadata is available. A classifier that returns Match should return a stable value after the matching Feed call completes.
Sniff does not depend on metadata. Callers can type-assert the matched classifier to MetadataProvider after Sniff returns.
type MinSniffBufferSizer ¶
type MinSniffBufferSizer interface {
MinSniffBufferSize() int
}
MinSniffBufferSizer reports a minimum Sniff buffer size.
The value is the number of bytes needed by a classifier, or by classifiers made by a factory, to make its bounded decision. A caller that sniffs with several classifiers should use the largest reported size.
type Operation ¶ added in v0.38.0
type Operation string
Operation names the gonnect.Network method currently being controlled.
const ( OpDial Operation = "Dial" OpListen Operation = "Listen" OpPacketDial Operation = "PacketDial" OpListenPacket Operation = "ListenPacket" OpDialTCP Operation = "DialTCP" OpListenTCP Operation = "ListenTCP" OpDialUDP Operation = "DialUDP" OpListenUDP Operation = "ListenUDP" OpListenPacketConfig Operation = "ListenPacketConfig" OpListenUDPConfig Operation = "ListenUDPConfig" OpListenMulticastUDP Operation = "ListenMulticastUDP" OpLookupIP Operation = "LookupIP" OpLookupIPAddr Operation = "LookupIPAddr" OpLookupNetIP Operation = "LookupNetIP" OpLookupHost Operation = "LookupHost" OpLookupAddr Operation = "LookupAddr" OpLookupCNAME Operation = "LookupCNAME" OpLookupPort Operation = "LookupPort" OpLookupNS Operation = "LookupNS" OpLookupMX Operation = "LookupMX" OpLookupSRV Operation = "LookupSRV" OpLookupTXT Operation = "LookupTXT" OpInterfaces Operation = "Interfaces" OpInterfaceAddrs Operation = "InterfaceAddrs" OpInterfaceMcast Operation = "InterfaceMulticastAddrs" OpInterfacesByIndex Operation = "InterfacesByIndex" OpInterfacesByName Operation = "InterfacesByName" )
type SniffControl ¶ added in v0.38.0
type SniffControl func(*SniffedCall) Action
SniffControl decides the final route for an intercepted and sniffed connection.
type SniffResult ¶ added in v0.38.0
type SniffResult struct {
// Index is the matched classifier index, or NoMatch.
Index int
// Metadata is Metadata(classifier) for the matched classifier.
Metadata any
// Err is the read error returned by Sniff, if any.
Err error
}
SniffResult is the output of an intercepted connection sniff.
type SniffedCall ¶ added in v0.38.0
type SniffedCall struct {
Call
Result SniffResult
}
SniffedCall is passed to SniffControl after an intercepted connection has been sniffed.
type Sniffer ¶ added in v0.38.0
type Sniffer struct {
// contains filtered or unexported fields
}
Sniffer is a gonnect.Network middleware that routes calls to immutable output slots and can sniff outgoing TCP dials before the final route.
Sniffer does not close or move output Networks up or down. It owns only the connections and listeners returned by its own methods. Close permanently closes Sniffer, closes those owned objects, and makes future calls return net.ErrClosed or normal rejection errors. Down closes owned objects and rejects calls until Up is called.
Output slot closure affects only calls routed to that output. Sniffer does not subscribe to output lifecycle events.
func NewSniffer ¶ added in v0.38.0
func NewSniffer(config SnifferConfig) (*Sniffer, error)
NewSniffer creates a Sniffer Network.
func (*Sniffer) Classifiers ¶ added in v0.38.0
Classifiers returns a copy of the configured classifier factories.
func (*Sniffer) Close ¶ added in v0.38.0
Close permanently closes Sniffer. It does not close output Networks.
func (*Sniffer) DialTCP ¶ added in v0.38.0
func (s *Sniffer) DialTCP( ctx context.Context, network, laddr, raddr string, ) (gonnect.TCPConn, error)
DialTCP routes or intercepts an outgoing TCP dial.
func (*Sniffer) DialUDP ¶ added in v0.38.0
func (s *Sniffer) DialUDP( ctx context.Context, network, laddr, raddr string, ) (gonnect.UDPConn, error)
DialUDP routes a UDP dial.
func (*Sniffer) Down ¶ added in v0.38.0
Down disables Sniffer and closes all objects returned by it.
func (*Sniffer) InterfaceAddrs ¶ added in v0.38.0
InterfaceAddrs routes an interface address list call.
func (*Sniffer) InterfaceMulticastAddrs ¶ added in v0.38.0
InterfaceMulticastAddrs routes an interface multicast address list call.
func (*Sniffer) Interfaces ¶ added in v0.38.0
func (s *Sniffer) Interfaces() ([]gonnect.NetworkInterface, error)
Interfaces routes an interface list call.
func (*Sniffer) InterfacesByIndex ¶ added in v0.38.0
func (s *Sniffer) InterfacesByIndex( index int, ) ([]gonnect.NetworkInterface, error)
InterfacesByIndex routes an interface lookup by index.
func (*Sniffer) InterfacesByName ¶ added in v0.38.0
func (s *Sniffer) InterfacesByName( name string, ) ([]gonnect.NetworkInterface, error)
InterfacesByName routes an interface lookup by name.
func (*Sniffer) IsNative ¶ added in v0.38.0
IsNative always reports false because Sniffer can route and defer dials.
func (*Sniffer) IsUp ¶ added in v0.38.0
IsUp reports whether Sniffer is currently up and not closed.
func (*Sniffer) ListenMulticastUDP ¶ added in v0.38.0
func (s *Sniffer) ListenMulticastUDP( ctx context.Context, network, address string, opts gonnect.MulticastOptions, ) (gonnect.MulticastPacketConn, error)
ListenMulticastUDP routes a multicast UDP listener.
func (*Sniffer) ListenPacket ¶ added in v0.38.0
func (s *Sniffer) ListenPacket( ctx context.Context, network, address string, ) (gonnect.PacketConn, error)
ListenPacket routes a packet listener.
func (*Sniffer) ListenPacketConfig ¶ added in v0.38.0
func (s *Sniffer) ListenPacketConfig( ctx context.Context, lc *gonnect.ListenConfig, network, address string, ) (gonnect.PacketConn, error)
ListenPacketConfig routes a packet listener with a ListenConfig.
func (*Sniffer) ListenTCP ¶ added in v0.38.0
func (s *Sniffer) ListenTCP( ctx context.Context, network, laddr string, ) (gonnect.TCPListener, error)
ListenTCP routes a TCP listener.
func (*Sniffer) ListenUDPConfig ¶ added in v0.38.0
func (s *Sniffer) ListenUDPConfig( ctx context.Context, lc *gonnect.ListenConfig, network, laddr string, ) (gonnect.UDPConn, error)
ListenUDPConfig routes a UDP listener with a ListenConfig.
func (*Sniffer) LookupAddr ¶ added in v0.38.0
LookupAddr routes a reverse lookup.
func (*Sniffer) LookupCNAME ¶ added in v0.38.0
LookupCNAME routes a CNAME lookup.
func (*Sniffer) LookupHost ¶ added in v0.38.0
LookupHost routes a host lookup.
func (*Sniffer) LookupIPAddr ¶ added in v0.38.0
LookupIPAddr routes an IP-address lookup.
func (*Sniffer) LookupNetIP ¶ added in v0.38.0
LookupNetIP routes a netip lookup.
func (*Sniffer) LookupPort ¶ added in v0.38.0
LookupPort routes a service lookup.
func (*Sniffer) LookupSRV ¶ added in v0.38.0
func (s *Sniffer) LookupSRV( ctx context.Context, service, proto, name string, ) (string, []*net.SRV, error)
LookupSRV routes an SRV lookup.
func (*Sniffer) PacketDial ¶ added in v0.38.0
func (s *Sniffer) PacketDial( ctx context.Context, network, address string, ) (gonnect.PacketConn, error)
PacketDial routes a packet dial.
func (*Sniffer) SubscribeCloser ¶ added in v0.38.0
SubscribeCloser registers c to be closed when Sniffer is closed.
func (*Sniffer) SubscribeUpDown ¶ added in v0.38.0
SubscribeUpDown registers u to follow Sniffer's Up and Down state.
type SnifferConfig ¶ added in v0.38.0
type SnifferConfig struct {
// Outputs are the immutable output slots. Slots are 1-based.
Outputs []gonnect.Network
// Control runs for every Network method call.
Control Control
// SniffControl runs after an intercepted outgoing TCP connection is
// sniffed. If nil, the first Action is used as the final action.
SniffControl SniffControl
// Classifiers are used for each intercepted outgoing TCP connection.
Classifiers []Factory
// SniffBufferSize is the maximum byte count inspected from an intercepted
// connection. Zero uses MinFactorySniffBufferSize(Classifiers...).
SniffBufferSize int
// Pool optionally provides sniff and put-back buffers.
Pool bufpool.Pool
// Spawner optionally starts background workers.
Spawner gonnect.Spawner
}
SnifferConfig configures a Sniffer Network.
type State ¶
type State uint8
State is the current terminal or non-terminal result of a Classifier.
const ( // NeedMore means the bytes seen so far are compatible with the classifier, // but are insufficient for a definitive decision. NeedMore State = iota // Match means the classifier has definitively recognized its input. Match // Mismatch means no future suffix can make the classifier match. Mismatch )
type TLSClientHelloInfo ¶
type TLSClientHelloInfo struct {
Versions []uint16
SNIHostname string
SNIEncrypted bool
ALPNProtocols []string
}
TLSClientHelloInfo is the visible metadata from a TLS ClientHello.
Versions contains the protocol versions offered by the client. If the supported_versions extension is present, it is used. Otherwise Versions contains the legacy_version field.
SNIHostname is the normalized visible SNI host_name, if one is present. It is lower-case and never has a trailing dot. When ECH is present, this is the outer ClientHello hostname.
SNIEncrypted reports whether the encrypted_client_hello extension is present. This is only the visible ECH signal. It does not prove that the server will accept ECH and it cannot reveal an encrypted inner hostname.
ALPNProtocols contains the ALPN protocols offered by the client, in wire order.
func SniffTLSClientHello ¶
func SniffTLSClientHello( buffer []byte, conn putback.Conn, ) (info TLSClientHelloInfo, ok bool, err error)
SniffTLSClientHello sniffs a syntactically valid TLS ClientHello and returns its visible metadata.
buffer is both scratch storage and the total byte budget. Its length limits how many bytes this function may inspect. All bytes read from conn are put back before this function returns, including on no-match and read-error paths.
The ok result is true only when a full valid TLS ClientHello was parsed within buffer. Non-TLS data, malformed TLS data, and TLS data that is over the byte budget return ok false with a nil error. Read errors are returned unchanged.
type TLSConfig ¶
type TLSConfig struct {
MaxClientHelloBytes int
Version uint16
Versions []uint16
SNIAvailable TLSFlag
SNIEncrypted TLSFlag
Hostname string
Hostnames []string
HostnamePatterns []string
ALPN string
ALPNs []string
ALPNPatterns []string
}
TLSConfig configures a TLS ClientHello classifier.
Empty field groups are wildcards. Non-empty values in a field group are ORed within that group, and all configured groups must match. For example, Versions {tls.VersionTLS12, tls.VersionTLS13}, HostnamePatterns {"*.example.test"}, and ALPNs {"h2", "http/1.1"} match a TLS ClientHello that offers TLS 1.2 or TLS 1.3, has a visible SNI hostname below example.test, and offers h2 or http/1.1 by ALPN.
Version and Versions match protocol versions offered by the ClientHello. If the supported_versions extension is present, it is used. Otherwise the ClientHello legacy_version field is used. The server-selected TLS version is not visible before routing.
SNIAvailable matches whether a visible SNI host_name is present. SNIEncrypted matches whether the encrypted_client_hello extension is present. This is an observable ECH signal only; the classifier cannot verify that the server accepts ECH or reveal an encrypted inner hostname.
Hostname, Hostnames, and HostnamePatterns match the visible SNI hostname. Matching is case-insensitive. If ECH is present, the visible hostname is the outer ClientHello hostname, when the client sends one.
ALPN, ALPNs, and ALPNPatterns match any protocol name in the ALPN extension. Matching is case-sensitive.
HostnamePatterns and ALPNPatterns are byte-oriented glob patterns. A pattern must match the whole value. In a pattern, * matches any byte sequence, ? matches one byte, and \ escapes the next byte.
MaxClientHelloBytes bounds bytes inspected while parsing the first ClientHello. Zero uses DefaultTLSClientHelloMaxBytes. A negative value is a programming error and causes a panic.