singbox

package
v0.0.3 Latest Latest
Warning

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

Go to latest
Published: Jul 25, 2026 License: GPL-3.0 Imports: 34 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func BuildDNSOptions

func BuildDNSOptions(ctx context.Context, raw []byte) (*option.DNSOptions, error)

BuildDNSOptions parses a raw `dns{}` block (doc/09 §6.1). Returns nil when raw is empty, meaning "leave the Box at its default DNS".

ctx must be the Box context (sing2Context) — DNS transports resolve through a registry just like inbounds do.

func BuildDNSOptionsFromPanel added in v0.0.2

func BuildDNSOptionsFromPanel(servers []api.NameServer, strategy string) (option.DNSOptions, error)

BuildDNSOptionsFromPanel translates a neutral NameServer list plus an xray-style domain-resolution strategy into native sing-box option.DNSOptions.

strategy accepts the xray DomainStrategy vocabulary (case-insensitive): "" / "AsIs", "UseIP", "UseIPv4", "UseIPv6" — plus the sing-box-native "PreferIPv4" / "PreferIPv6" aliases. Mapping to sing-box strategy:

AsIs       → as_is
UseIP      → prefer_ipv4   (xray "resolve, no family pin"; prefer v4)
UseIPv4    → ipv4_only
UseIPv6    → ipv6_only
PreferIPv4 → prefer_ipv4
PreferIPv6 → prefer_ipv6

An empty server list returns a zero-value option.DNSOptions with no error (the caller then leaves box's dns{} at its default).

func BuildOutbounds added in v0.0.2

func BuildOutbounds(ctx context.Context, raw []byte) ([]option.Outbound, error)

BuildOutbounds parses a raw outbound array (doc/09 §6.2). Returns nil when raw is empty, letting the caller fall back to a lone direct outbound.

func BuildRoute added in v0.0.2

func BuildRoute(ctx context.Context, raw []byte) (*option.RouteOptions, error)

BuildRoute parses a raw `route{}` block (doc/09 §6.2). Returns nil when raw is empty (no routing — everything takes the default outbound).

func Context added in v0.0.2

func Context(ctx context.Context) context.Context

sing2Context is include.Context with Sing2's protocol overrides applied.

It exists because the base fork's Mieru inbound cannot hot-swap users: it drives mieru through the apis/server facade, whose config is frozen after Start. Sing2's replacement (protocol/mieru) talks to mieru's protocol.Mux directly and implements UpdateUsers, so a user change no longer tears down every live connection on the node.

The override is safe and total: inbound.Registry.register is a plain map assignment (adapter/inbound/registry.go), so registering C.TypeMieru again replaces the base's constructor for every code path that resolves through this context — box.New, the dynamic box.Inbound().Create, and option.Inbound's context-aware unmarshaler used by the native translator. Context is sing2Context's exported form: callers that must parse sing-box options (outbounds / DNS / route) before constructing the Server need the registries in hand first (panel.Start, doc/09 §6.2).

func DirectOutbound

func DirectOutbound(tag string) option.Outbound

DirectOutbound builds a plain direct outbound so smoke traffic has somewhere to go (sing-box uses the first outbound as the default final route).

func MinimalVLESSInbound

func MinimalVLESSInbound(tag, listen string, port uint16, uuid, flow string) (option.Inbound, error)

MinimalVLESSInbound builds a native VLESS inbound with a single user, for the Phase 0 cross-core baseline smoke (Xray-core client → Sing2 server). flow may be "" or "xtls-rprx-vision"; REALITY is layered by the translation layer in Phase 1.

func Translate

func Translate(ctx context.Context, tag string, info *api.NodeInfo, users []api.UserInfo) (string, any, error)

Translate builds the (inboundType, options) pair for info together with its current authoritative user set. users may be empty at AddNode time — users are hot-swapped in later via Inbound.UpdateUsers (see user.go). It is the cert-less public entry (plain-TLS nodes get an SNI-only TLS block); the Server drives the cert-aware path via translateNode.

ctx must be the include.Context-wrapped context the Box was built with: the native dialect (doc/13 T4) resolves inbound options through the registry stored in it. Flat-dialect nodes ignore ctx.

func ValidateRouteOutbounds added in v0.0.2

func ValidateRouteOutbounds(route *option.RouteOptions, outbounds []option.Outbound) error

ValidateRouteOutbounds cross-checks that every outbound tag a route rule references actually exists.

sing-box would surface this at Start as a bare "outbound not found: xxx" with no indication of which rule is at fault; on a node that is a silent egress misroute waiting to happen, so it is worth catching at config-load time.

Types

type BillingServer

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

BillingServer is the per-connection billing tracker (Hinge A). Its RoutedConnection runs the per-connection pipeline limiter(rate/device/online/conn-count) → rule(audit) → stats(inner byte count) → ratelimit(outer token-bucket) and defers the conn-count decrement to the wrapped conn's Close (doc/11 §2.2).

Phase 1 increment A: the pipeline is wired but limiter/rule are not yet populated by the panel (no inbound registered → no throttle / no block); stats and connlimit take effect immediately — the intended "wired-but-unpopulated" state.

func NewBillingServer

func NewBillingServer(
	lim *limiter.Limiter,
	rul *rule.Manager,
	st *stats.Registry,
	rt *ratelimit.Manager,
	conns *limiter.ConnLimiter,
) *BillingServer

NewBillingServer wires the five billing components into the tracker.

func (*BillingServer) RoutedConnection

func (b *BillingServer) RoutedConnection(
	ctx context.Context, conn net.Conn, metadata adapter.InboundContext,
	matchedRule adapter.Rule, matchOutbound adapter.Outbound,
) net.Conn

RoutedConnection is the TCP injection point (doc/11 §2.2). The pipeline runs conn-count → rate/device/online limiter → audit rule → stats → ratelimit, unwinding the conn-count on any early reject and deferring the decrement to the wrapped conn's Close on success. When access logging is enabled it emits one rejected row per early-reject path and one accepted row on release.

func (*BillingServer) RoutedPacketConnection

func (b *BillingServer) RoutedPacketConnection(
	ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext,
	matchedRule adapter.Rule, matchOutbound adapter.Outbound,
) N.PacketConn

RoutedPacketConnection is the UDP injection point. UDP is not counted toward the per-user concurrent connection cap; it still gets byte accounting and token-bucket limiting.

type ConnPolicy added in v0.0.3

type ConnPolicy struct {
	// UDPTimeout is how long an idle UDP session is kept. Base default is 5
	// minutes for most inbounds. Zero leaves the base default alone.
	UDPTimeout time.Duration
	// TCPKeepAlive is the idle period before the first keep-alive probe;
	// TCPKeepAliveInterval is the gap between probes. Zero means base default.
	TCPKeepAlive         time.Duration
	TCPKeepAliveInterval time.Duration
	// DisableTCPKeepAlive turns probing off entirely. Only ever turns it off —
	// see applyTo.
	DisableTCPKeepAlive bool
}

ConnPolicy is the node-local connection-tuning policy — config.yml's ConnectionConfig block. Like XrayR's it is process-level (one policy, every node), and it lands on every inbound's option.ListenOptions.

This is deliberately NOT a field-for-field port of XrayR's ConnectionConfig

XrayR's five fields (handshake / connIdle / uplinkOnly / downlinkOnly / bufferSize) are xray `policy.levels[0]` settings — see XrayR-master panel/panel.go:251-268, which stuffs them straight into conf.Policy. sing-box has no policy layer at all, and four of the five have no runtime equivalent anywhere in the base:

  • handshake — no equivalent. Each protocol handles its own handshake deadline internally; there is no knob.
  • uplinkOnly / downlinkOnly — no equivalent, and no need for one: sing uses CloseWrite half-close semantics rather than a post-close grace timer.
  • bufferSize — no RUNTIME equivalent. sing's buffer sizes are compile-time constants (sing common/buf/buffer_standard.go:5-8, BufferSize = 32 KiB / UDPBufferSize = 16 KiB); the only lever is the `with_low_memory` build tag, which halves them.
  • connIdle — the closest match is per-inbound `udp_timeout`, but mapping it silently would be a REGRESSION: xray's connIdle defaults to 30s and covers TCP too, while sing-box's udp_timeout defaults to 5 minutes. An operator copying `connIdle: 30` across would quietly get UDP sessions dying at 30s.

So Sing2 exposes the knobs the base actually has, and panel/config.go warns — per key — on the XrayR names instead of swallowing them (doc/09 §7).

The TCP side of connIdle's intent (reap dead connections, don't leak fds) is served by TCP keep-alive rather than an idle timer: sing-box never kills an idle-but-live TCP connection, which is the correct behaviour for a proxy.

func (ConnPolicy) IsZero added in v0.0.3

func (p ConnPolicy) IsZero() bool

IsZero reports whether the policy would change nothing.

type Server

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

Server is the sing-box-backed Core implementation. It owns the Box, the five billing components (also handed to the Hinge A tracker), and the authoritative per-node user sets driving the whole-set UpdateUsers hot-swap.

func NewServer

func NewServer(ctx context.Context, opts option.Options) (*Server, error)

NewServer builds a Box from opts, registers the billing ConnectionTracker (Hinge A — Router.AppendTracker, doc/11 §2.1), and returns the wrapper. The caller drives it with Start/Close. include.Context installs the protocol registries the fork's registry-era constructor (and dynamic Create) require.

func NewServerWithContext added in v0.0.2

func NewServerWithContext(ctx context.Context, opts option.Options) (*Server, error)

NewServerWithContext is NewServer for a caller that already built the Box context via Context() — typically because it had to parse outbound / DNS / route options (which resolve through the same registries) before it could assemble option.Options. Passing a context that did NOT come from Context() will fail at option decoding with "missing … registry in context".

func (*Server) AddNode

func (s *Server) AddNode(tag string, info *api.NodeInfo) error

AddNode translates info into a dynamic inbound, creates it on the running Box, and registers the node in the limiter (empty user set — users arrive via AddUsers). It is an error to add a tag that already exists.

func (*Server) AddUsers

func (s *Server) AddUsers(tag string, users []api.UserInfo) (added int, err error)

AddUsers merges users into the node's authoritative set (deduped by join key), re-pushes the complete user list to the live inbound, and syncs the limiter. Returns the count of users newly added (already-present join keys update in place and are not counted).

func (*Server) Close

func (s *Server) Close() error

func (*Server) DelNode

func (s *Server) DelNode(tag string) error

DelNode removes the dynamic inbound and its limiter/bookkeeping. Removing an unknown tag is a no-op error from the inbound manager but the local state is cleared regardless.

func (*Server) DelUsers

func (s *Server) DelUsers(tag string, users []api.UserInfo) error

DelUsers removes users (by join key) from the node's authoritative set, re-pushes the remaining list to the inbound, and evicts the removed users from the limiter's per-user maps so no stale entries linger.

func (*Server) GetDetectResult

func (s *Server) GetDetectResult(tag string) ([]api.DetectResult, error)

GetDetectResult drains the audit hits accumulated for a node since the last call (the rule.Manager clears them on read) and returns them by value. The panel layer filters out local rules (RuleID < 0) before reporting upstream.

func (*Server) GetUserTraffic

func (s *Server) GetUserTraffic(tag string, reset bool) ([]api.UserTraffic, error)

GetUserTraffic returns per-user byte counters for the node, keyed by join key against the stats registry. reset=true atomically zeroes each counter after read (reset-then-report, doc/10 §7.1). Only users with non-zero traffic in the period are returned (XrayR reporting convention).

func (*Server) OnlineIPs

func (s *Server) OnlineIPs(tag string) ([]api.OnlineUser, error)

OnlineIPs returns the users currently connected on the node (per the limiter's online-device map). Note GetOnlineDevice resets the map as a side effect, so each call reports the devices seen since the previous call.

func (*Server) Protocols

func (s *Server) Protocols() []string

func (*Server) ResolveIdentity

func (s *Server) ResolveIdentity(tag, name string) string

ResolveIdentity maps an inbound's authenticated user name to the billing join key. It satisfies identityResolver for the Hinge A tracker. Unknown tags and names pass through unchanged, so any protocol that never needed translation — and any race against a user removal — degrades to the pre-T5 behaviour rather than losing the connection.

func (*Server) SetAccessLog

func (s *Server) SetAccessLog(m *accesslog.Manager)

SetAccessLog wires (or clears with nil) the process-level access-log reporter consulted by the Hinge A tracker. Call before Start; the manager itself is started/stopped by the panel orchestrator (doc/11 §13).

func (*Server) SetConnPolicy added in v0.0.3

func (s *Server) SetConnPolicy(p ConnPolicy)

SetConnPolicy records the process-level connection-tuning policy (config.yml ConnectionConfig). Like the Set*Node* setters it must be called before AddNode: the policy is read when the inbound is created and existing inbounds are not retro-fitted, because changing a listener's socket options would mean rebuilding it — exactly the thing that drops live connections.

func (*Server) SetNodeCert

func (s *Server) SetNodeCert(tag string, cfg cert.CertConfig)

SetNodeCert records the certificate policy for a node tag. It must be called before AddNode(tag, …) so the translation layer can build the inbound TLS block for a plain-TLS node (REALITY nodes ignore it). Overwrites any prior value for the tag (doc/03 T10).

func (*Server) SetNodeConnLimit

func (s *Server) SetNodeConnLimit(tag string, max int)

SetNodeConnLimit sets the per-user concurrent-TCP-connection cap for a node tag on the shared ConnLimiter (doc/09 §5.3 ConnLimitConfig, T5). max <= 0 clears the tag's limit. Safe to call before or after AddNode; the panel calls it before AddNode alongside the other per-tag policies.

func (*Server) SetNodeGlobalLimit

func (s *Server) SetNodeGlobalLimit(tag string, cfg *limiter.GlobalDeviceLimitConfig)

SetNodeGlobalLimit records the cross-node device-limit (Redis) policy for a node tag. It must be called before AddNode(tag, …) so the limiter registers the tag with its global cap. A nil cfg (or one with Enable=false) leaves the tag without a global limit. Overwrites any prior value for the tag (doc/09 §5.3, doc/11 §7).

func (*Server) Start

func (s *Server) Start() error

func (*Server) Type

func (s *Server) Type() string

func (*Server) UpdateInboundLimiter

func (s *Server) UpdateInboundLimiter(tag string, users []api.UserInfo) error

UpdateInboundLimiter forwards a user-list speed-limit update to the shared limiter. The panel's dynamic speed-limit driver (T7 AutoLimiter) uses it to throttle/restore users without reaching into billing internals (doc/11 §8).

func (*Server) UpdateRule

func (s *Server) UpdateRule(tag string, rules []api.DetectRule) error

UpdateRule installs the audit rule set for a node. It forwards to the shared rule.Manager (the same instance the Hinge A tracker consults). This is beyond the core.Core interface — the panel orchestration layer calls it on the concrete *Server (doc/11 §5, doc/10 §3.6). RuleNotModified handling is the caller's concern; this always installs the list it is given.

Jump to

Keyboard shortcuts

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