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 ¶
- Constants
- func RegisterRendrAsXrayInbound(cfg *ListenConfig) error
- func RegisterRendrAsXrayTransport(cfg *Config) error
- func RegisterRendrTransportDialer(name string, cfg *Config) error
- func RegisterRendrTransportListener(name string, cfg *ListenConfig) error
- func XrayInstanceAsPacketFactory(inst *core.Instance) rendr.PacketPathFactory
- func XrayInstanceAsStreamFactory(inst *core.Instance) rendr.StreamPathFactory
- func XrayOutboundAsPacketFactory(inst *core.Instance, chain *core.OutboundHandlerConfig) rendr.PacketPathFactory
- func XrayOutboundAsStreamFactory(inst *core.Instance, chain *core.OutboundHandlerConfig) rendr.StreamPathFactory
- func XrayTaggedOutboundAsPacketFactory(inst *core.Instance, outboundTag string) rendr.PacketPathFactory
- func XrayTaggedOutboundAsStreamFactory(inst *core.Instance, outboundTag string) rendr.StreamPathFactory
- type BalancerPacketFactory
- type BalancerStreamFactory
- type Config
- type Dialer
- type ListenConfig
- type Listener
- type Mode
- type PacketListener
- type PathSpec
- type TransportSettings
Examples ¶
Constants ¶
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 ¶
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 ¶
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.
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 ¶
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 ¶
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 ¶
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 ¶
ListenQUIC starts a QUIC-only rendr listener.
func ListenTCP ¶
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 ¶
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 ¶
AcceptContext is the cancellable variant.
func (*Listener) Addrs ¶
Addrs reports every bound transport address. Single-transport listeners return a one-element slice.
func (*Listener) FlowIDs ¶
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.
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) 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.