xray

package
v0.0.0-...-e103f64 Latest Latest
Warning

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

Go to latest
Published: May 29, 2026 License: GPL-3.0 Imports: 17 Imported by: 0

Documentation

Overview

Package xray provides the rendr-as-xray-transport binding.

rendr is a transport-of-transports: it does not define a new wire protocol; it wraps existing xray transports (tcp / quic / mkcp / ws / grpc / h2) and adds the migration layer on top.

The package exposes Config + Dialer + Listener (stream) and PacketListener (datagram) so embedders that import xray-core can wire rendr into the transport.internet.Dialer contract, and standalone embedders can use the API directly without an xray-core dependency.

Index

Examples

Constants

View Source
const DefaultTransportName = "rendr"

DefaultTransportName is the xray streamSettings.network value used by the default registration helpers.

Variables

This section is empty.

Functions

func RegisterRendrAsXrayInbound

func RegisterRendrAsXrayInbound(cfg *ListenConfig) error

RegisterRendrAsXrayInbound registers the default "rendr" xray transport listener. If cfg is nil or has no paths, xray's requested listen address/port becomes a single TCP rendr listener. If cfg has paths, those paths are used as-is so embedders can expose a multi-transport rendr listener.

func RegisterRendrAsXrayTransport

func RegisterRendrAsXrayTransport(cfg *Config) error

RegisterRendrAsXrayTransport registers the default "rendr" xray transport dialer. After this succeeds, xray-core stream settings with ProtocolName/network "rendr" dial through a rendr Conn.

func RegisterRendrTransportDialer

func RegisterRendrTransportDialer(name string, cfg *Config) error

RegisterRendrTransportDialer registers a named xray transport dialer. Tests and embedders that need more than one rendr registration in the same process can pass a custom name and set streamSettings.ProtocolName to the same value.

func RegisterRendrTransportListener

func RegisterRendrTransportListener(name string, cfg *ListenConfig) error

RegisterRendrTransportListener registers a named xray transport listener. A nil cfg means "bind a TCP rendr listener to the address xray passes into internet.ListenTCP".

func XrayInstanceAsPacketFactory

func XrayInstanceAsPacketFactory(inst *core.Instance) rendr.PacketPathFactory

XrayInstanceAsPacketFactory returns the UDP-side equivalent of XrayInstanceAsStreamFactory. xray-core's UDP dispatcher derives the per-packet target from the xray Instance configuration; rendr's packet path wrapper still receives PathSpec.Address and uses it as its own WriteTo peer.

func XrayInstanceAsStreamFactory

func XrayInstanceAsStreamFactory(inst *core.Instance) rendr.StreamPathFactory

XrayInstanceAsStreamFactory returns a rendr StreamPathFactory that dials through a started xray-core Instance. The Instance's routing and outbound configuration decides which xray protocol chain carries the bytes; rendr sees only the resulting net.Conn.

func XrayOutboundAsPacketFactory

func XrayOutboundAsPacketFactory(inst *core.Instance, chain *core.OutboundHandlerConfig) rendr.PacketPathFactory

XrayOutboundAsPacketFactory is the packet-mode companion to XrayOutboundAsStreamFactory.

func XrayOutboundAsStreamFactory

func XrayOutboundAsStreamFactory(inst *core.Instance, chain *core.OutboundHandlerConfig) rendr.StreamPathFactory

XrayOutboundAsStreamFactory is the roadmap-named glue-B helper. The stable xray-core public API dispatches through a started Instance, so the outbound chain should already be installed in inst; chain is retained in the signature for embedders that keep the chosen config object next to the factory.

func XrayTaggedOutboundAsPacketFactory

func XrayTaggedOutboundAsPacketFactory(inst *core.Instance, outboundTag string) rendr.PacketPathFactory

XrayTaggedOutboundAsPacketFactory returns a PacketPathFactory forced through a specific xray outbound tag.

func XrayTaggedOutboundAsStreamFactory

func XrayTaggedOutboundAsStreamFactory(inst *core.Instance, outboundTag string) rendr.StreamPathFactory

XrayTaggedOutboundAsStreamFactory returns a StreamPathFactory that dispatches through a specific xray outbound tag. This is the small building block behind BalancerObject adapter mode: xray owns the protocol chain, rendr owns path migration.

Types

type BalancerPacketFactory

type BalancerPacketFactory struct {
	Name        string
	OutboundTag string
	Factory     rendr.PacketPathFactory
}

BalancerPacketFactory is the packet-mode companion to BalancerStreamFactory.

func XrayBalancerAsPacketFactories

func XrayBalancerAsPacketFactories(inst *core.Instance, rule *xrouter.BalancingRule) ([]BalancerPacketFactory, error)

XrayBalancerAsPacketFactories adapts an xray BalancingRule into packet-mode rendr factories.

type BalancerStreamFactory

type BalancerStreamFactory struct {
	// Name is stable for this process and suitable for
	// rendr.Dialer.AddStreamPathFactory plus PathSpec.Transport.
	Name string

	// OutboundTag is the xray outbound tag forced for this path.
	OutboundTag string

	Factory rendr.StreamPathFactory
}

BalancerStreamFactory is one outbound selected from an xray BalancingRule, exposed as a rendr StreamPathFactory registration.

func XrayBalancerAsStreamFactories

func XrayBalancerAsStreamFactories(inst *core.Instance, rule *xrouter.BalancingRule) ([]BalancerStreamFactory, error)

XrayBalancerAsStreamFactories adapts an xray BalancingRule into per-outbound rendr stream factories. The returned factories use the selected outbound tags as forced xray detours; embedders put the matching Name into rendr PathSpec.Transport and the rendr server address into PathSpec.Address.

type Config

type Config struct {
	Mode  Mode
	Paths []PathSpec

	// Prime knobs. Zero values clamp to project defaults
	// (Hysteresis=0.25, Dwell=5s, Cooldown=30s).
	Hysteresis float64
	Dwell      time.Duration
	Cooldown   time.Duration

	// MigrationBudget. 0 = use the project default 90s. Values
	// above 90s are clamped down by the engine.
	MigrationBudget time.Duration

	// ProbeInterval: per-path RTT probe cadence. 0 = engine default 1s.
	ProbeInterval time.Duration

	// ZombieMaxMigrations: consecutive completed migrations with no
	// payload between them before the engine declares the peer dead.
	// 0 = engine default 2.
	ZombieMaxMigrations int

	// ZombieCooldown: zombie counter reset window. 0 = engine default 30s.
	ZombieCooldown time.Duration

	// BondStuckRTTMultiplier (bond only): a path whose RTT exceeds
	// best_rtt * Multiplier is bypassed in bond round-robin.
	// 0 = engine default 3.0.
	BondStuckRTTMultiplier float64

	// OnConn, when non-nil, is called after the xray transport dialer
	// creates the underlying rendr net.Conn and before it is returned
	// to xray-core. Tests and embedders can type-assert the value to
	// rendr.AdminConn to observe or trigger migrations without changing
	// xray's protocol-level connection.
	OnConn func(net.Conn)
}

Config is the full xray-side description of one rendr Conn. xray callers populate this from their proto stream settings; non-xray embedders construct it directly.

A Config is *transport-of-transports*: it doesn't carry its own wire format. Wire framing comes from each PathSpec's Transport. rendr layers SEQ / migration / dedup on top.

func (*Config) Validate

func (c *Config) Validate() error

Validate runs cheap structural checks. Embedders should call this before passing the Config into NewDialer / NewListener.

type Dialer

type Dialer struct {

	// QUICTLS lets the embedder inject a real TLS config for QUIC
	// paths. When nil, the QUIC adapter falls back to a self-signed
	// dev cert (development only).
	QUICTLS *tls.Config
	// contains filtered or unexported fields
}

Dialer wraps a Config and dials a rendr.Conn (which is a net.Conn) per call. It is the integration anchor for xray's transport.internet.Dialer contract: an embedder that imports xray-core wires this into the appropriate adapter. Standalone embedders can use it directly without any xray-core dependency.

Example

ExampleDialer demonstrates the shape an xray-style embedder uses: build a rendr.xray.Config, hand it to NewDialer, and treat the returned *Dialer as the project's own internet.Dialer implementation. The net.Conn returned by DialContext already implements rendr.AdminConn, so migration metrics / explicit Migrate() are reachable without unwrapping the xray adapter.

package main

import (
	"context"
	"fmt"
	"io"
	"log"
	"time"

	"github.com/FrankoonG/rendr/xray"
)

func main() {
	ln, err := xray.ListenTCP("127.0.0.1:0")
	if err != nil {
		log.Fatal(err)
	}
	defer ln.Close()

	srvDone := make(chan struct{})
	go func() {
		defer close(srvDone)
		ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
		defer cancel()
		c, err := ln.AcceptContext(ctx)
		if err != nil {
			return
		}
		defer c.Close()
		buf := make([]byte, 32)
		n, _ := c.Read(buf)
		fmt.Println("server got:", string(buf[:n]))
	}()

	d, err := xray.NewDialer(&xray.Config{
		Mode: xray.ModePrime,
		Paths: []xray.PathSpec{
			{Transport: "tcp", Address: ln.Addr().String()},
		},
	})
	if err != nil {
		log.Fatal(err)
	}
	c, err := d.DialContext(context.Background(), nil)
	if err != nil {
		log.Fatal(err)
	}
	defer c.Close()
	_, _ = io.WriteString(c, "hello xray-shaped")
	<-srvDone
}
Output:
server got: hello xray-shaped
Example (Admin)

ExampleDialer_admin shows the migration-control surface an xray embedder reaches via rendr.AdminConn on the net.Conn returned by DialContext. The same value is both a net.Conn (for xray's wire protocols) and an AdminConn (for migration metrics / path-set updates) - no parallel handle is required.

package main

import (
	"context"
	"fmt"
	"io"
	"log"
	"time"

	"github.com/FrankoonG/rendr"
	"github.com/FrankoonG/rendr/xray"
)

func main() {
	ln, err := xray.ListenTCP("127.0.0.1:0")
	if err != nil {
		log.Fatal(err)
	}
	defer ln.Close()

	srvDone := make(chan struct{})
	go func() {
		defer close(srvDone)
		ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
		defer cancel()
		c, _ := ln.AcceptContext(ctx)
		if c != nil {
			defer c.Close()
			io.ReadFull(c, make([]byte, 4))
		}
	}()

	d, _ := xray.NewDialer(&xray.Config{
		Mode:  xray.ModePrime,
		Paths: []xray.PathSpec{{Transport: "tcp", Address: ln.Addr().String()}},
	})
	c, err := d.DialContext(context.Background(), nil)
	if err != nil {
		log.Fatal(err)
	}
	defer c.Close()
	_, _ = c.Write([]byte("ping"))

	if adm, ok := c.(rendr.AdminConn); ok {
		s := adm.Stats()
		fmt.Println("mode:", s.Mode)
		fmt.Println("paths:", len(s.Paths))
		fmt.Println("state:", s.State)
	}
	<-srvDone
}
Output:
mode: prime
paths: 1
state: active

func NewDialer

func NewDialer(cfg *Config) (*Dialer, error)

NewDialer constructs a Dialer from cfg. The Config is validated; invalid configs return an error early so the embedder doesn't fail mid-dial.

func (*Dialer) DialContext

func (d *Dialer) DialContext(ctx context.Context, dest net.Addr) (net.Conn, error)

DialContext establishes a rendr.Conn following the Config's paths and mode. The returned value implements net.Conn; callers that also want migration / FlowID introspection can type-assert to rendr.Conn or rendr.AdminConn.

dest is reserved for xray adapter use (xray supplies a net.Destination at this point). For now it is ignored - the destination is implicit in each PathSpec's Address.

func (*Dialer) DialPacketContext

func (d *Dialer) DialPacketContext(ctx context.Context, dest net.Addr) (net.PacketConn, error)

DialPacketContext mirrors DialContext but produces a net.PacketConn in packet-boundary mode. Each application WriteTo becomes exactly one rendr frame; each ReadFrom returns one frame's payload. Use this for xray protocols whose wire format is datagram-oriented (e.g. Hysteria UDP, WireGuard relay, Shadowsocks UDP-over-TCP).

The peer must accept via a rendr packet-mode listener (see xray.ListenUDPFlow / rendr.ListenUDPFlowPacket). Stream-mode listeners reject this dial with a HELLO-caps mismatch.

type ListenConfig

type ListenConfig struct {
	Paths []PathSpec

	// QUICTLS is used for any QUIC listen path. Nil is accepted for
	// local development and uses rendr's ephemeral dev certificate;
	// production embedders should supply a real server config.
	QUICTLS *tls.Config
}

ListenConfig describes an xray-side multi-transport stream listener.

type Listener

type Listener struct {
	// contains filtered or unexported fields
}

Listener wraps a rendr.Listener so it satisfies the integration surface xray expects for inbound transports.

func Listen

func Listen(cfg *ListenConfig) (*Listener, error)

Listen starts a stream listener whose paths share one rendr bridge table. This is the xray-facing wrapper around rendr.Listen for streamSettings.network="rendr" deployments that expose more than one underlying path transport.

func ListenQUIC

func ListenQUIC(addr string, tlsCfg *tls.Config) (*Listener, error)

ListenQUIC starts a QUIC-only rendr listener.

func ListenTCP

func ListenTCP(addr string) (*Listener, error)

ListenTCP starts a TCP-only rendr listener at addr suitable for the xray transport-bridge. The returned Listener exposes Accept / Close / Addr (net.Addr).

func (*Listener) Accept

func (l *Listener) Accept() (net.Conn, error)

Accept blocks until an inbound rendr Conn is available. The listener accepts against context.Background; xray-side adapters supply per-Accept cancellation via AcceptContext below.

func (*Listener) AcceptContext

func (l *Listener) AcceptContext(ctx context.Context) (net.Conn, error)

AcceptContext is the cancellable variant.

func (*Listener) Addr

func (l *Listener) Addr() net.Addr

Addr reports the local network address.

func (*Listener) Addrs

func (l *Listener) Addrs() []net.Addr

Addrs reports every bound transport address. Single-transport listeners return a one-element slice.

func (*Listener) Close

func (l *Listener) Close() error

Close shuts the listener.

func (*Listener) FlowIDs

func (l *Listener) FlowIDs() [][16]byte

FlowIDs returns the set of live flow_ids the listener is serving. Useful for monitoring panels that want to enumerate active rendr connections without inspecting each accepted net.Conn.

Example

ExampleListener_FlowIDs shows how a monitoring goroutine can enumerate the live flow_ids being served by an xray-side listener without unwrapping to the underlying rendr.Listener. The use case is a metrics endpoint that reports active rendr connections per xray inbound.

package main

import (
	"context"
	"fmt"
	"log"
	"net"
	"time"

	"github.com/FrankoonG/rendr/xray"
)

func main() {
	ln, err := xray.ListenTCP("127.0.0.1:0")
	if err != nil {
		log.Fatal(err)
	}
	defer ln.Close()

	acceptedCh := make(chan net.Conn, 1)
	go func() {
		ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
		defer cancel()
		c, _ := ln.AcceptContext(ctx)
		acceptedCh <- c
	}()

	d, _ := xray.NewDialer(&xray.Config{
		Mode:  xray.ModePrime,
		Paths: []xray.PathSpec{{Transport: "tcp", Address: ln.Addr().String()}},
	})
	c, err := d.DialContext(context.Background(), nil)
	if err != nil {
		log.Fatal(err)
	}
	defer c.Close()
	s := <-acceptedCh
	defer s.Close()

	fmt.Println("flows:", len(ln.FlowIDs()))
}
Output:
flows: 1

type Mode

type Mode uint8

Mode mirrors rendr.Mode but is duplicated here so xray-side configuration can be marshalled without importing the parent package directly. Keep the integer values in sync.

const (
	ModeUnset Mode = 0
	ModePrime Mode = 1
	ModeBond  Mode = 2
	ModeRace  Mode = 3
)

type PacketListener

type PacketListener struct {
	// contains filtered or unexported fields
}

PacketListener wraps a rendr.PacketListener so xray-side adapters for datagram-oriented protocols can plug rendr in as the underlying migration-capable transport. It is the packet-mode analogue of xray.Listener.

func ListenQUICDatagram

func ListenQUICDatagram(addr string, tlsCfg *tls.Config) (*PacketListener, error)

ListenQUICDatagram starts a QUIC listener that accepts incoming connections in DATAGRAM mode (RFC 9221). One DATAGRAM == one rendr frame; the engine runs in packet mode automatically. Pair with a client dialer that uses Dialer.DialPacketContext + PathSpec Opts["mode"]="datagram" on a QUIC path.

tlsCfg may be nil during local development; production embedders MUST supply a real *tls.Config.

func ListenUDPFlow

func ListenUDPFlow(addr string) (*PacketListener, error)

ListenUDPFlow starts an opaque-UDP rendr listener at addr in packet-boundary mode. Returned PacketConns preserve message boundaries 1-to-1 with application WriteTo/ReadFrom calls.

Stream-mode peers connecting to this listener are routed to the underlying stream Accept channel and not seen here; AcceptPacket only yields packet-mode HELLOs.

func (*PacketListener) AcceptPacket

func (l *PacketListener) AcceptPacket() (net.PacketConn, error)

AcceptPacket blocks until a packet-mode rendr Conn is available. The returned value implements net.PacketConn; callers that also want migration / FlowID introspection can type-assert to rendr.PacketConn or rendr.AdminPacketConn.

func (*PacketListener) AcceptPacketContext

func (l *PacketListener) AcceptPacketContext(ctx context.Context) (net.PacketConn, error)

AcceptPacketContext is the cancellable variant.

func (*PacketListener) Addr

func (l *PacketListener) Addr() net.Addr

Addr reports the local network address.

func (*PacketListener) Close

func (l *PacketListener) Close() error

Close shuts the listener.

func (*PacketListener) FlowIDs

func (l *PacketListener) FlowIDs() [][16]byte

FlowIDs returns the set of live flow_ids the packet listener is serving. Symmetric with xray.Listener.FlowIDs.

type PathSpec

type PathSpec struct {
	// Transport names the underlying xray transport ("tcp", "quic",
	// "ws", "grpc", "mkcp", "h2"). Default rendr registry only
	// supports "tcp" and "quic"; "ws"/"grpc"/"h2"/"mkcp" require the
	// embedder to register an adapter.
	Transport string

	// Address is the remote endpoint in transport-specific form
	// (host:port for tcp/quic; a URL for ws/grpc).
	Address string

	// Local optionally pins the source side endpoint.
	Local string

	// Opts is the transport-specific configuration blob. For TLS-
	// bearing transports (quic, ws over TLS), this carries server
	// name / ALPN / cert hints. rendr treats it as opaque.
	Opts map[string]string

	// Weight is an advisory hint to bond / race schedulers about
	// share of frames or duplication priority. prime ignores it.
	Weight uint16
}

PathSpec describes one candidate path. For xray integration each PathSpec corresponds to a sub-transport already known to xray (tcp, quic, mkcp, ws, grpc, h2 ...). At build time the embedder supplies a TransportFactory that maps PathSpec.Transport -> transport.Transport implementation; rendr falls back to its own registry (tcp + quic) when no factory is registered.

type TransportSettings

type TransportSettings struct{}

TransportSettings is the placeholder settings object xray-core stores in MemoryStreamConfig.ProtocolSettings for rendr transports. The actual rendr path configuration is supplied at registration time via Config / ListenConfig.

Jump to

Keyboard shortcuts

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