routing

package
v0.39.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 13, 2026 License: CC0-1.0 Imports: 19 Imported by: 0

README

routing

Package github.com/asciimoth/gonnect/routing provides bytecode based routing rules for Gonnect Network, Sniffer, and Tun middleware.

The package can build:

  • gonnect.RouterCfg for gonnect.Router
  • tun.SplitRouter for gonnect/tun.Splitter
  • sniffer.Control and sniffer.SniffControl for gonnect/sniffer.Sniffer

Rules can be created from immutable bytecode tables or parsed from a small text format.

Sniffer bytecode rules

NewSnifferBytecodeRules builds both sniffer.Control and sniffer.SniffControl from one rule text. SNIFF can refer to a named classifier from the classifier list, or it can construct an inline classifier from the built-in constructor collection.

Sniffer-only operations are split by phase:

  • INTERCEPT stays only in the pre-sniff control program.
  • SNIFF ... and SNIFF_NONE stay only in the sniff-control program.
  • Normal address, network, method, and slot rules stay in both programs.
  • ROUTE works in sniffer bytecode only. In a SNIFF segment it stays only in sniff control. In a segment without SNIFF, it is copied to both phases.

Sniff errors always reject the connection by routing to slot 0.

ROUTE selects a slot and can change call fields before that slot is used:

ROUTE <slot> [<field>:<value> ...]

Supported fields:

  • NETWORK, SRC, DST
  • SRC_ADDR, SRC_PORT, DST_ADDR, DST_PORT
  • HOST, SERVICE, PROTO

SRC and DST replace the full endpoint string. Address-part fields preserve the current port when the endpoint is a valid host:port; otherwise they replace the full endpoint. Port-part fields preserve the current host when the endpoint is a valid host:port; otherwise they leave the endpoint unchanged. Values are fixed strings from the rule text. Metadata templates such as ${tls.sni} are not supported.

Inline classifier specifications use this form:

SNIFF <constructor> [<option>:<value> ...]

Constructor names are case-sensitive. The built-in constructors are HTTP and TLS. Options do not allow spaces; values are passed as written. Repeated equivalent inline specifications share one generated classifier. Generated classifiers are appended after named classifiers, in first-use order.

Supported HTTP options:

  • METHOD, URL, URL_PATTERN, VERSION
  • HOST or HOSTNAME
  • HOST_PATTERN or HOSTNAME_PATTERN
  • MAX_REQUEST_LINE_BYTES, MAX_HEADER_BYTES

Supported TLS options:

  • VERSION
  • SNI, HOST, or HOSTNAME
  • SNI_PATTERN, HOST_PATTERN, or HOSTNAME_PATTERN
  • ALPN, ALPN_PATTERN
  • SNI_AVAILABLE, SNI_ENCRYPTED
  • MAX_CLIENT_HELLO_BYTES

VERSION accepts 1.0, 1.1, 1.2, 1.3, or a numeric TLS version. SNI_AVAILABLE and SNI_ENCRYPTED accept ANY, REQUIRED, or FORBIDDEN.

Block a specific HTTP URL

This example uses inline HTTP classifiers. No pre-built classifier list is needed:

rules, err := routing.NewSnifferBytecodeRules(nil, routeText)

Use one rule text:

DIAL
TCP
AND
INTERCEPT

SNIFF HTTP URL:/blocked
SNIFF HTTP URL:/blocked2
OR
DROP

SNIFF HTTP
SLOT 1

TRUE
DROP

Action:

  • TCP dials are intercepted and sniffed.
  • HTTP requests for /blocked or /blocked2 reject.
  • Other HTTP requests route to slot 1.
  • Non-HTTP traffic rejects.
Route TLS by ALPN

This example uses an inline TLS classifier:

rules, err := routing.NewSnifferBytecodeRules(nil, routeText)

Use one rule text:

DIAL
TCP
AND
PORT 443
AND
INTERCEPT

SNIFF TLS ALPN:h2
SLOT 2

SNIFF_NONE
SLOT 1

Action:

  • TCP dials to port 443 are intercepted and sniffed.
  • TLS ClientHello with ALPN h2 routes to slot 2.
  • Traffic that does not match a classifier routes to slot 1.

Named classifiers are still supported when Go code must build custom sniffer.Factory values:

rules, err := routing.NewSnifferBytecodeRules(
	[]routing.NamedSniffClassifier{
		{Name: "custom", Factory: customFactory},
	},
	"SNIFF custom\nSLOT 1\n",
)

Use ROUTE when a sniffer rule must choose a slot and rewrite call fields in one step. A gonnect.Remapper can still sit behind a slot when the same remap must apply to all traffic routed to that slot.

Remap HTTP by URL

This example routes one HTTP URL to a fixed backend address:

DIAL
TCP
AND
INTERCEPT

SNIFF HTTP URL:/blocked
ROUTE 1 DST:127.0.0.1:8080

SNIFF HTTP URL:/allowed
SLOT 2

TRUE
DROP

Action:

  • /blocked routes to slot 1 with destination 127.0.0.1:8080.
  • /allowed routes to slot 2 with the original destination.
  • Other traffic rejects.
Remap TLS by ALPN

This example routes a TLS ALPN match to a fixed backend address:

DIAL
TCP
AND
INTERCEPT

SNIFF TLS ALPN:myproto
ROUTE 1 DST:127.0.0.1:9443

TRUE
DROP

Action:

  • TLS ClientHello with ALPN myproto routes to slot 1 with destination 127.0.0.1:9443.
  • Other traffic rejects.

License

Files in this repository are distributed under the CC0 license.

CC0
To the extent possible under law, ASCIIMoth has waived all copyright and related or neighboring rights to gonnect.

Documentation

Overview

Package routing provides bytecode based routing rules for Gonnect middleware.

The package can build gonnect.RouterCfg, tun.SplitRouter, and sniffer.Sniffer control callbacks from immutable bytecode tables or from a small text rule format.

Index

Constants

View Source
const (
	// OP_DROP pops a boolean value and routes to slot 0 when it is true.
	OP_DROP byte = iota
	// OP_SLOT pops a boolean value and routes to the following uint8 slot when it is true.
	OP_SLOT
	// OP_TRUE pushes true onto the boolean stack.
	OP_TRUE
	// OP_FALSE pushes false onto the boolean stack.
	OP_FALSE
	// OP_NOT inverts the boolean value on top of the stack.
	OP_NOT
	// OP_AND replaces the two top stack values with their logical conjunction.
	OP_AND
	// OP_OR replaces the two top stack values with their logical disjunction.
	OP_OR
	// OP_NET4 pushes whether the operation is for IPv4 or has an IPv4 address.
	OP_NET4
	// OP_NET6 pushes whether the operation is for IPv6 or has an IPv6 address.
	OP_NET6
	// OP_UDP pushes whether the operation uses a UDP network.
	OP_UDP
	// OP_TCP pushes whether the operation uses a TCP network.
	OP_TCP
	// OP_FQDN pushes whether the remote address is a hostname rather than an IP.
	OP_FQDN
	// OP_LFQDN pushes whether the local address is a hostname rather than an IP.
	OP_LFQDN
	// OP_ADDR_S pushes whether the remote address host equals a string table value.
	OP_ADDR_S
	// OP_LADDR_S pushes whether the local address host equals a string table value.
	OP_LADDR_S
	// OP_ADDR_RE pushes whether the remote address host matches a regexp table value.
	OP_ADDR_RE
	// OP_LADDR_RE pushes whether the local address host matches a regexp table value.
	OP_LADDR_RE
	// OP_ADDR4 pushes whether the remote address equals an IPv4 table value.
	OP_ADDR4
	// OP_LADDR4 pushes whether the local address equals an IPv4 table value.
	OP_LADDR4
	// OP_ADDR6 pushes whether the remote address equals an IPv6 table value.
	OP_ADDR6
	// OP_LADDR6 pushes whether the local address equals an IPv6 table value.
	OP_LADDR6
	// OP_SNET4 pushes whether the remote IPv4 address is in an IPv4 subnet table value.
	OP_SNET4
	// OP_LSNET4 pushes whether the local IPv4 address is in an IPv4 subnet table value.
	OP_LSNET4
	// OP_SNET6 pushes whether the remote IPv6 address is in an IPv6 subnet table value.
	OP_SNET6
	// OP_LSNET6 pushes whether the local IPv6 address is in an IPv6 subnet table value.
	OP_LSNET6
	// OP_PORT pushes whether the remote address port equals the following uint16 port.
	OP_PORT
	// OP_LPORT pushes whether the local address port equals the following uint16 port.
	OP_LPORT
	// OP_RULE pushes whether the packet flow matches a sysnet rule.
	OP_RULE
	// OP_DIAL pushes whether the RouterCfg method is DialTCP, DialUDP, or RouteUDP.
	OP_DIAL
	// OP_LISTEN pushes whether the RouterCfg method is ListenTCP.
	OP_LISTEN
	// OP_LOOKUP pushes whether the RouterCfg method is Lookup.
	OP_LOOKUP
	// OP_INTERCEPT pops a boolean value and asks sniffer.Sniffer to sniff the
	// TCP stream when it is true. It is valid only in Sniffer control bytecode.
	OP_INTERCEPT
	// OP_SNIFF pushes whether the sniff result matched the named classifier
	// referenced by the following uint16 classifier table index.
	OP_SNIFF
	// OP_SNIFF_NONE pushes whether sniffing found no matching classifier.
	OP_SNIFF_NONE
	// OP_ROUTE pops a boolean value, mutates a sniffer call, and routes to
	// a route-action slot when it is true.
	OP_ROUTE
)

Variables

This section is empty.

Functions

func NewBytecodeSniffer added in v0.39.0

func NewBytecodeSniffer(
	config sniffer.SnifferConfig,
	rules SnifferBytecodeRules,
) (*sniffer.Sniffer, error)

NewBytecodeSniffer validates rules and returns callbacks that can be used by sniffer.NewSniffer. Existing Control, SniffControl, and Classifiers fields in config are replaced with the bytecode-backed values.

Types

type BytecodeRules

type BytecodeRules struct {
	Strings     []string
	Regexps     []*regexp.Regexp
	IPv4Addrs   []uint32
	IPv4Subnets []IPv4Subnet
	IPv6Addrs   []netip.Addr
	IPv6Subnets []netip.Prefix

	DNSCacheStorage gdns.CacheStorage

	DialTCP   []byte
	ListenTCP []byte
	DialUDP   []byte
	RouteUDP  []byte
	Lookup    []byte
}

BytecodeRules contains the immutable tables and bytecode programs used to build a gonnect.RouterCfg.

Each program is encoded as one-byte opcodes followed by the opcode parameter when it has one. OP_SLOT uses one uint8 parameter. String, regexp, address, subnet, and port operations use one little-endian uint16 parameter. A program routes to slot 0 when it finishes without a matching OP_DROP or OP_SLOT. OP_DIAL, OP_LISTEN, and OP_LOOKUP push the current RouterCfg method class: DialTCP, DialUDP, and RouteUDP are dial operations; ListenTCP is a listen operation; Lookup is a lookup operation.

Every bytecode slice is validated by NewBytecodeRouterCfg. The constructor copies all slices, so later changes to BytecodeRules do not affect routing. When DNSCacheStorage is set, cached reverse DNS names are matched exactly as stored; names produced by github.com/asciimoth/gonnect/dns.Cache are usually absolute names with a trailing dot, such as "example.test.".

func NewBytecodeRules

func NewBytecodeRules(
	dialTCP, listenTCP, dialUDP, routeUDP, lookup string,
) (BytecodeRules, error)

NewBytecodeRules parses the simple routing rules language into BytecodeRules.

func NewBytecodeRulesProgram added in v0.39.0

func NewBytecodeRulesProgram(program string) (BytecodeRules, error)

NewBytecodeRulesProgram parses one routing rules program and derives the five RouterCfg method programs from it.

The input is split into independent segments. A segment ends at a DROP or SLOT operation, matched case-insensitively, or at the end of the string. Each derived method program receives only the segments that can still affect that method:

  • segments without DIAL, LISTEN, or LOOKUP are copied to every method;
  • segments with method operations are omitted only when their terminal DROP or SLOT condition is provably false for that method.

The proof is intentionally conservative. Method operations are evaluated as constants for the target method, TRUE/FALSE/NOT/AND/OR are evaluated exactly, and every runtime-dependent predicate is treated as unknown. Unknown, invalid, or unterminated segments are kept and then validated normally by NewBytecodeRouterCfg.

type IPv4Subnet

type IPv4Subnet struct {
	Addr uint32
	Bits uint8
}

IPv4Subnet is an IPv4 CIDR subnet used by bytecode routing rules.

Addr is the canonical big-endian 32-bit IPv4 address. Bits is the CIDR prefix length and must be in the range 0..32.

type NamedSniffClassifier added in v0.39.0

type NamedSniffClassifier struct {
	Name    string
	Factory sniffer.Factory
}

NamedSniffClassifier binds a rule-language name to a Sniffer classifier factory.

Name must be non-empty and must not contain white space. Factory can be any implementation of sniffer.Factory. Rules can refer to the name with SNIFF <name>.

type NamedSniffClassifierConstructor added in v0.39.0

type NamedSniffClassifierConstructor struct {
	Name        string
	Constructor SniffClassifierConstructor
}

NamedSniffClassifierConstructor binds a constructor name to an inline SNIFF classifier constructor.

Name is case-sensitive and must be non-empty. It must not contain white space or ":". Constructor must not be nil.

func DefaultSniffClassifierConstructors added in v0.39.0

func DefaultSniffClassifierConstructors() []NamedSniffClassifierConstructor

DefaultSniffClassifierConstructors returns the built-in inline SNIFF classifier constructors.

The current collection contains HTTP and TLS. The returned slice is a copy and can be changed by the caller before it is passed to NewSnifferBytecodeRulesWithConstructors.

type RouterCfg added in v0.39.0

type RouterCfg interface {
	gonnect.RouterCfg
	SlotReporter
}

func NewBytecodeRouterCfg

func NewBytecodeRouterCfg(rules BytecodeRules) (RouterCfg, error)

NewBytecodeRouterCfg validates rules and returns a gonnect.RouterCfg that evaluates stack-based bytecode for each Router operation.

type SlotReporter added in v0.39.0

type SlotReporter interface {
	MentionedSlots() []int
}

SlotReporter is implemented by bytecode-backed routers that can report the non-drop slots mentioned by their rules.

type SniffClassifierConstructor added in v0.39.0

type SniffClassifierConstructor func(
	options []SniffClassifierOption,
) (canonical string, factory sniffer.Factory, err error)

SniffClassifierConstructor builds one Sniffer classifier factory from inline SNIFF options.

The returned canonical string must identify the effective options. It is used only for de-duplication, so equivalent option sets should return the same canonical string. Return an empty canonical string when no option is set.

type SniffClassifierOption added in v0.39.0

type SniffClassifierOption struct {
	Key   string
	Value string
}

SniffClassifierOption is one KEY:VALUE option from an inline SNIFF classifier specification.

Constructor implementations receive keys after parser normalization. Values are not decoded or changed by the parser.

type SnifferBytecodeRules added in v0.39.0

type SnifferBytecodeRules struct {
	Classifiers []NamedSniffClassifier

	Strings     []string
	Regexps     []*regexp.Regexp
	IPv4Addrs   []uint32
	IPv4Subnets []IPv4Subnet
	IPv6Addrs   []netip.Addr
	IPv6Subnets []netip.Prefix

	DNSCacheStorage gdns.CacheStorage

	// RouteActions contains the fixed call changes used by OP_ROUTE.
	RouteActions []SnifferRouteAction

	Control      []byte
	SniffControl []byte
}

SnifferBytecodeRules contains the immutable tables and bytecode programs used to build gonnect/sniffer control callbacks.

Control runs before sniffing. It can route directly with OP_SLOT, OP_DROP, or OP_ROUTE, or request TCP interception with OP_INTERCEPT. SniffControl runs after Sniffer restores inspected bytes. It can route with OP_SLOT, OP_DROP, or OP_ROUTE, and it can test the matched classifier with OP_SNIFF or OP_SNIFF_NONE.

NewSnifferBytecodeRules derives Control and SniffControl from one rule text. Sniffer-only segments are copied only to the phase where they make sense; normal address, network, method, and slot segments are copied to both.

Classifiers contains named classifiers supplied by the caller and generated inline classifiers built from SNIFF specs such as "SNIFF HTTP URL:/blocked".

func NewSnifferBytecodeRules added in v0.39.0

func NewSnifferBytecodeRules(
	classifiers []NamedSniffClassifier,
	program string,
) (SnifferBytecodeRules, error)

NewSnifferBytecodeRules parses one rules program and derives the Sniffer control and sniff-control programs from it.

INTERCEPT segments are copied only to Control. SNIFF and SNIFF_NONE segments are copied only to SniffControl. Segments that use neither Sniffer-only operation are copied to both programs. A segment that uses SNIFF and ends in INTERCEPT is rejected because it has no meaningful execution phase.

SNIFF can refer to a named classifier from classifiers, or to an inline classifier specification built by DefaultSniffClassifierConstructors.

func NewSnifferBytecodeRulesWithConstructors added in v0.39.0

func NewSnifferBytecodeRulesWithConstructors(
	classifiers []NamedSniffClassifier,
	constructors []NamedSniffClassifierConstructor,
	program string,
) (SnifferBytecodeRules, error)

NewSnifferBytecodeRulesWithConstructors parses one rules program like NewSnifferBytecodeRules, but uses constructors as the inline SNIFF classifier collection.

A SNIFF argument that matches a named classifier still refers to that classifier. Other arguments can use this form:

SNIFF <constructor> [<option>:<value> ...]

Constructor names are case-sensitive. Option names are normalized by each constructor. Repeated equivalent inline specifications share one generated classifier entry. Generated classifiers are appended after the named classifiers, in first-use order.

type SnifferCallMutation added in v0.39.0

type SnifferCallMutation struct {
	SetNetwork bool
	Network    string

	SetSrc bool
	Src    string

	SetDst bool
	Dst    string

	SetSrcAddr bool
	SrcAddr    string

	SetSrcPort bool
	SrcPort    string

	SetDstAddr bool
	DstAddr    string

	SetDstPort bool
	DstPort    string

	SetHost bool
	Host    string

	SetService bool
	Service    string

	SetProto bool
	Proto    string
}

SnifferCallMutation contains fixed values that OP_ROUTE can write to a sniffer.Call.

A Set field controls whether the paired value is written. Full endpoint replacements, Src and Dst, are applied before endpoint-part replacements.

type SnifferControls added in v0.39.0

type SnifferControls interface {
	SlotReporter

	Control(call *sniffer.Call) sniffer.Action
	SniffControl(call *sniffer.SniffedCall) sniffer.Action
	Classifiers() []sniffer.Factory
}

SnifferControls is the bytecode-backed control pair for sniffer.Sniffer.

func NewBytecodeSnifferControls added in v0.39.0

func NewBytecodeSnifferControls(
	rules SnifferBytecodeRules,
) (SnifferControls, error)

NewBytecodeSnifferControls validates rules and returns a reusable Sniffer control pair. Sniff errors always reject, independent of SniffControl bytecode.

type SnifferRouteAction added in v0.39.0

type SnifferRouteAction struct {
	Slot uint8

	Mutation SnifferCallMutation
}

SnifferRouteAction is a route target with call field changes.

Slot is the output slot returned by OP_ROUTE. Mutation is applied to the active sniffer.Call before the action is returned.

type SplitBytecodeRules

type SplitBytecodeRules struct {
	System sysnet.System
	Rules  []sysnet.Rule

	// RuleCacheTTL controls how long OP_RULE matcher results are cached by
	// flow. A zero value uses the default TTL; a negative value disables the
	// cross-packet rule cache.
	RuleCacheTTL time.Duration
	// RuleCacheMaxEntries bounds the cross-packet OP_RULE cache. A zero value
	// uses the default size; a negative value disables the cache.
	RuleCacheMaxEntries int
	// RouteCacheTTL controls how long whole bytecode route results are cached
	// by packet flow. When RouteCacheTTL and RouteCacheMaxEntries are both
	// zero, the route cache inherits the OP_RULE cache settings; this means
	// disabling the rule cache also disables the default route cache. A negative
	// value disables the route cache.
	RouteCacheTTL time.Duration
	// RouteCacheMaxEntries bounds the whole bytecode route-result cache. A zero
	// value uses the inherited or default size; a negative value disables the
	// route cache.
	RouteCacheMaxEntries int

	Strings     []string
	Regexps     []*regexp.Regexp
	IPv4Addrs   []uint32
	IPv4Subnets []IPv4Subnet
	IPv6Addrs   []netip.Addr
	IPv6Subnets []netip.Prefix

	DNSCacheStorage gdns.CacheStorage

	Route []byte
}

SplitBytecodeRules contains the immutable tables and bytecode program used to build a tun.SplitRouter.

The packet router supports the common bytecode opcodes plus OP_RULE. OP_RULE indexes Rules and is evaluated by a sysnet.Matcher built from System. The RouterCfg method opcodes OP_DIAL, OP_LISTEN, and OP_LOOKUP are not valid for packet routing. The constructor validates and copies all slices before returning the router. When DNSCacheStorage is set, cached reverse DNS names are matched exactly as stored; names produced by github.com/asciimoth/gonnect/dns.Cache are usually absolute names with a trailing dot, such as "example.test.".

func NewSplitBytecodeRules

func NewSplitBytecodeRules(
	system sysnet.System,
	route string,
) (SplitBytecodeRules, error)

NewSplitBytecodeRules parses the simple routing rules language into SplitBytecodeRules. RULE takes a sysnet rule type followed by the rule text:

RULE app org.example.App

The first field after RULE becomes sysnet.Rule.Type. Everything after the separating space or tab becomes sysnet.Rule.Rule verbatim, so rule text may contain spaces and tabs.

type SplitRouter added in v0.39.0

type SplitRouter interface {
	tun.SplitRouter
	SlotReporter
	Close() error
}

func NewBytecodeSplitRouter

func NewBytecodeSplitRouter(rules SplitBytecodeRules) (SplitRouter, error)

NewBytecodeSplitRouter validates rules and returns a SplitRouter that evaluates stack-based bytecode against IP packets. The returned router owns matchers built from rules.System; call Close when the router is no longer used.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL