Help us shape the future of Go! Take the 2026 Go Developer Survey

appserver

package
v1.3.102 Latest Latest
Warning

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

Go to latest
Published: Oct 23, 2026 License: AGPL-3.0 Imports: 36 Imported by: 7

Documentation

Overview

Package appserver pkg/app/appserver/app_settings.go

Per-app live tuning settings — the one visor→app value channel the mux bench needs, built the only way the existing app RPC allows.

The app is the RPC client and the visor the server (rpc_ingress_gateway.go), so there is no way to PUSH a value into a running app. Instead the visor holds a map per app name and the app PULLS it on a tick it already runs (skysocks-client's keepalive loop). A version counter makes the steady state an empty round-trip: the app reports the version it has applied, and the gateway answers with values only when the visor's is newer.

Two payloads travel together: the int64 map (pkg/skysocks/skysettings encodes every numeric, duration, ratio, bool and enum knob into it) and a string map for the LIST knobs — a set of public keys, a set of transport types — whose payload does not fit an int64. One version covers both.

The store is process-scoped but NOT cleared when an app stops: an operator who set a knob meant it for the app, not for one run of its process, and the bench spent a release cycle re-applying its sweep after every restart. The visor persists the same map to its config (visorconfig.V1.AppSettings) and restores it at boot, so `proxy settings --reset` is the one thing that forgets a knob.

Ops are the edge-triggered half of the same channel. A value is a level — it says what the app should BE — and some things an operator asks for are not levels at all: "cut the tunnel on port 49170" happens once. Those ride the same pull as a small sequenced queue, applied in order and acked by sequence, so a missed tick delays an op but never loses or repeats it.

Package appserver pkg/app/appserver/app_state.go c2-vis-appsvc

Package appserver pkg/app/appserver/app_stats.go c2-vis-appsvc

Package appserver pkg/app/appserver/dial_app_knobs.go

The dial path's read of the live per-app knobs.

Two of them are the visor's business rather than the app's. `proxy mux cap|width` used to drive a pair of PROCESS-GLOBAL atomics in the routing policy preset, so pinning the legs of the proxy under test also pinned them on the paired reference app dialing beside it, and a bench could only record that contamination rather than avoid it. Here they are read from the knob set of the app that OWNS the dial (mux.width, mux.cap) and stamped on that dial alone; an app with neither set dials exactly as it does today, under the visor-wide adaptive values.

The other two shape the standby pool's candidate search: the app passes pool.exclude_pks and pool.require_tp_types on the dial it issues (it is the app's pool, and the app knows which of its dials is a pool dial), and this file turns them into the router's existing peer-exclusion options and into the first-hop type check that runs once the dial has landed.

Package appserver pkg/app/appserver/doc.go c2-vis-appsvc for implementing the skywire app server.

Package appserver pkg/app/appserver/errors.go c2-vis-appsvc

Package appserver pkg/app/appserver/notify.go c2-vis-appsvc

The app→visor half of the visor-global notification system.

An app decides *whether* an event deserves the user's attention — only skychat knows which thread is focused and which are muted, only vpn-client knows a reconnect matters. It then publishes a NotifyReq and is done. The visor decides *where* that lands (an attached UI, a subscribed host app such as the Android service, the host-OS notification center, or nowhere at all on a headless box). Apps therefore carry no OS-specific notification code.

This file deliberately holds only the wire type and a one-method interface: pkg/app/appserver must keep compiling for GOOS=js/wasm and TinyGo (the browser wasm-visor imports it), so every transport concern — net/http, SSE, os/exec — lives on the pkg/visor side, which never builds for those targets.

Package appserver pkg/app/appserver/proc.go c2-vis-appsvc

Package appserver pkg/app/appserver/proc_credential_unix.go c2-vis-appsvc

Sets the cmd's credentials (UID/GID) before exec on POSIX systems. Looked up from the system's user database via os/user; the spawned process drops to that identity via the kernel's setuid/setgid during exec. Requires the visor itself to be allowed to switch users — i.e. running as root, or with CAP_SETUID / CAP_SETGID granted on the binary.

Package appserver pkg/app/appserver/proc_external_native.go c2-vis-appsvc

External (OS-subprocess) launch path for a Proc. It uses os/exec and the james-barrow/golang-ipc Windows IPC server, neither of which compiles on the TinyGo js/wasm target. proc.go therefore types Proc.cmd / Proc.ipcServer as `any` and routes every os/exec- and ipc-touching operation through the helpers below; proc_external_tinygo.go provides fail-closed stubs so an edge-only browser visor (which runs apps in-process, RunModeInternal) still compiles. See appcommon.RunModeExternal vs RunModeInternal.

Package appserver pkg/app/appserver/proc_ingress_listen_native.go c2-vis-appsvc

Package appserver pkg/app/appserver/proc_manager.go c2-vis-appsvc

Package appserver pkg/app/appserver/rpc_ingress_client.go c2-vis-appsvc

Package appserver pkg/app/appserver/rpc_ingress_gateway.go c2-vis-appsvc

Package appserver pkg/app/appserver/stderr.go c2-vis-appsvc

Package appserver pkg/app/appserver/stderr_unix.go c2-vis-appsvc

Index

Constants

View Source
const (
	// AppDetailedStatusStarting is set during app initialization process.
	AppDetailedStatusStarting = "Starting"

	// AppDetailedStatusRunning is set when the app is running.
	AppDetailedStatusRunning = "Running"

	// AppDetailedStatusVPNConnecting is set during VPN-client session establishment (including handshake).
	AppDetailedStatusVPNConnecting = "Connecting"

	// AppDetailedStatusReconnecting is set after connection failure, during reconnection.
	AppDetailedStatusReconnecting = "Connection failed, reconnecting"

	// AppDetailedStatusShuttingDown is set during shutdown.
	AppDetailedStatusShuttingDown = "Shutting down"

	// AppDetailedStatusStopped is set after shutdown.
	AppDetailedStatusStopped = "Stopped"
)
View Source
const AppOpConsumedTunnel = "consumed-tunnel"

AppOpConsumedTunnel tells the app that one of its tunnels was SPENT, not lost: the router re-homed that tunnel's whole route chain into an active group as a mux leg (docs/design/leg-rehome.md), and the tunnel's own group is closing as a result. Arg is the route group's port, exactly as for AppOpCutTunnel. The app drops the tunnel from its pool and records tunnel_consumed, WITHOUT the redial backoff reset and pool-fill arming a death triggers — nobody lost this tunnel, so nothing needs replacing in a hurry.

View Source
const AppOpCutTunnel = "cut-tunnel"

AppOpCutTunnel closes exactly one of the app's tunnels — the one dialed from the local port in Arg, which is the port `proxy mux info` prints as the route group's dst_port. The app's own pool replaces it, which is the point: it is the per-tunnel teardown a bench previously had to fake by cutting the host's transport.

View Source
const (
	// ProcStartTimeout represents the duration in which a proc should have started and connected with the app server.
	ProcStartTimeout = time.Second * 5
)

Variables

View Source
var (
	// ErrAppAlreadyStarted is returned when trying to run the already running app.
	ErrAppAlreadyStarted = errors.New("app already started")
	// ErrNoSuchApp is returned by ProcManager getters (ProcByName,
	// Wait, Stop, etc.) when no app is registered under the given
	// name. Exported so callers like the launcher's auto-restart
	// watcher can distinguish "proc exited" from "proc was
	// de-registered" without string-matching.
	ErrNoSuchApp = errors.New("no such app")

	// ErrClosed occurs when an action is called after proc manager is closed.
	ErrClosed = errors.New("proc manager is already closed")
)
View Source
var ErrFirstHopTypeRefused = fmt.Errorf("first hop transport type not admitted by pool.require_tp_types")

ErrFirstHopTypeRefused is returned when a dial landed on a first hop whose transport type the caller's pool.require_tp_types does not admit. It is an ordinary dial failure: the standby pool's bounded retry answers it by asking again, which re-ranks and usually picks a different hop.

Functions

This section is empty.

Types

type AcceptResp

type AcceptResp struct {
	Remote appnet.Addr
	ConnID uint16
}

AcceptResp contains response parameters for `Accept`.

type AppConfig

type AppConfig = spec.AppConfig

AppConfig defines app startup parameters.

User/Group/WorkDir/Env are POSIX-only knobs that only take effect when the app runs as an external process (Binary set, no internal RunFunc registered). In-process apps share the visor's UID/GID by definition — credentials can't be dropped per-goroutine without breaking the runtime's thread-pinning assumptions, and the visor logs a warning if any of these fields are set on an internal app. AppConfig is the wire-format type for an app's launcher entry. Aliased from pkg/app/appserver/spec so existing callers writing `appserver.AppConfig{...}` keep compiling, while the canonical (WASM-clean) definition lives in the spec leaf package.

type AppDetailedStatus

type AppDetailedStatus string

AppDetailedStatus is a app's detailed status.

type AppOp

type AppOp struct {
	Seq  uint64 `json:"seq"`
	Kind string `json:"kind"`
	Arg  int64  `json:"arg,omitempty"`
}

AppOp is one edge-triggered instruction for a running app: do this once, now. Kind names it, Arg carries its single parameter (the route group's port for AppOpCutTunnel), Seq orders it and is what the app acks.

type AppSettingsReq

type AppSettingsReq struct {
	Applied    uint64 `json:"applied"`
	OpsApplied uint64 `json:"ops_applied,omitempty"`
}

AppSettingsReq is an app polling for its settings, reporting the version it currently has installed and the highest op sequence it has applied.

type AppSettingsResp

type AppSettingsResp struct {
	// Version is the visor's current version for this app. The app stores it
	// and reports it as Applied on the next poll.
	Version uint64 `json:"version"`
	// Values is the full intended set, or nil when Applied already matched
	// Version (nothing to do) — NOT "reset everything", which is an empty
	// non-nil map carried at a NEW version.
	Values map[string]int64 `json:"values,omitempty"`
	// Text is the same for the list knobs.
	Text map[string]string `json:"text,omitempty"`
	// Ops are the edge-triggered instructions still pending for this app,
	// oldest first. Unlike Values they are carried on EVERY poll until the app
	// acks their sequence, so one lost answer costs a tick, not the op.
	Ops []AppOp `json:"ops,omitempty"`
	// Changed distinguishes the two nil cases above.
	Changed bool `json:"changed"`
}

AppSettingsResp is what an app gets back from the AppSettings ingress call.

type AppState

type AppState struct {
	AppConfig
	Status         AppStatus `json:"status"`
	DetailedStatus string    `json:"detailed_status"`
	// OTP is an app-published one-time code an operator reads out-of-band to
	// authenticate to that app's own UI (skydex-market's operator panel is
	// the first user). It is runtime-only — deliberately on AppState rather
	// than AppConfig so it is never persisted to the visor config — and
	// reaches the hypervisor app list, which is behind hypervisor auth.
	OTP string `json:"otp,omitempty"`
	// PinnedExit is the exit key the CONFIG carried at boot for a proxy
	// client that autostarted on a pinned --srv. Runtime-only, like OTP, and
	// set only for skysocks-client: it lets a reader tell the operator's
	// configured exit from whatever key the app is running on now.
	PinnedExit string `json:"pinned_exit,omitempty"`
}

AppState defines state parameters for a registered App.

type AppStats

type AppStats struct {
	Connections []ConnectionSummary `json:"connections"`
	StartTime   *time.Time          `json:"start_time,omitempty"`
}

AppStats contains app runtime statistics.

type AppStatus

type AppStatus int

AppStatus defines running status of an App.

const (
	// AppStatusStopped represents status of a stopped App.
	AppStatusStopped AppStatus = iota

	// AppStatusRunning represents status of a running App.
	AppStatusRunning

	// AppStatusErrored represents status of an errored App.
	AppStatusErrored

	// AppStatusStarting represents status of an app starting.
	AppStatusStarting
)

func (AppStatus) String

func (s AppStatus) String() string

String returns a string representation of the AppStatus.

type ConnectionSummary

type ConnectionSummary struct {
	IsAlive            bool          `json:"is_alive"`
	Latency            time.Duration `json:"latency"`
	UploadSpeed        uint32        `json:"upload_speed"`
	DownloadSpeed      uint32        `json:"download_speed"`
	BandwidthSent      uint64        `json:"bandwidth_sent"`
	BandwidthReceived  uint64        `json:"bandwidth_received"`
	Error              string        `json:"error"`
	ConnectionDuration int64         `json:"connection_duration,omitempty"`
}

ConnectionSummary sums up the connection stats.

type DeadlineReq

type DeadlineReq struct {
	ConnID   uint16
	Deadline time.Time
}

DeadlineReq contains arguments for deadline methods.

type DialOptionsReq

type DialOptionsReq struct {
	Addr             appnet.Addr
	MuxRoutes        int
	MinHops          int
	ForwardMinHops   int
	ReverseMinHops   int
	ForwardMuxRoutes int
	ReverseMuxRoutes int
	// Direct, when true, forces a direct-transport-only dial: the router
	// creates a direct transport to the destination if none exists, then dials
	// 1-hop over it, bypassing the route-finder. The `--direct` skynet flag
	// sets this. In spirit mutually exclusive with MinHops >= 2.
	Direct bool
	// DiversifyTransports opts this dial into visor-side auto-diversification
	// for multi-tunnel bandwidth aggregation (docs/mux_aggregation_rfc.md
	// step 3). Set by the skysocks-client for its extra tunnels (2..N): the
	// router excludes the first-hop transports/intermediates already claimed by
	// this visor's live route groups to the same dst, so each tunnel leaves
	// over a different first-hop transport and their throughputs sum. Bounded
	// visor-side to the case where a sibling route group already exists, so a
	// lone dial is byte-identical to today. See router.DialOptions.
	DiversifyTransports bool
	// ExcludeFirstHopPKs / RequireFirstHopTypes are the standby pool's live
	// candidate filter (pool.exclude_pks, pool.require_tp_types). The app owns
	// them because the app owns the pool: it knows which of its dials is a
	// pool dial. The PKs become the router's peer-level first-hop and
	// intermediate exclusions; the types are checked once the dial has landed
	// (see checkFirstHopType — a per-dial type filter is not something the
	// router's candidate ranking can express today). Empty on every other
	// dial, which is therefore byte-identical to before.
	ExcludeFirstHopPKs   []string
	RequireFirstHopTypes []string
	// RequireDisjointFirstHop turns DiversifyTransports from a preference into
	// a requirement: instead of conceding a shared first hop, the dial fails
	// with router.ErrNoDisjointFirstHop. The skysocks standby pool sets it so
	// it can tell "there are more disjoint routes to dial" from "the topology
	// has none left" and stop filling at the real bound.
	RequireDisjointFirstHop bool
	// TunnelRole labels the route group this dial creates — "active" or
	// "standby" — for `visor state --select mux_route_groups` and the proxy
	// status page. The dialing app's own label; empty everywhere else.
	TunnelRole string
	// AdoptReservePort asks for the new tunnel to be made from the leg reserve
	// whose far-end port this is, instead of from a fresh route (see
	// router.DialOptions.AdoptReservePort). 0 = an ordinary dial.
	AdoptReservePort uint16
}

DialOptionsReq carries a dial request with extra per-call options. Used by DialWithOptions for app paths that need to request specific router behavior. Zero / unset fields preserve the single-route, router-picks-freely default — equivalent to plain Dial.

Added for #1525-adjacent skywire-cli mux-route testing per the operator's 2026-05-19 ask. Generalizes the per-call route count already plumbed through VisorCat (pkg/visor/api_visor_cat.go) into the app-net surface so apps like skynet-client can request N routes at dial time without needing a parallel "cli skynet cat"-style RPC.

MinHops mirrors the same field on router.DialOptions and is needed to actually exercise the mux>direct hypothesis end-to-end via real data-plane apps: without it, MuxRoutes > 1 still picks the existing direct transport for every route (N mux streams on one TCP socket), not N routes through N intermediates.

ForwardMinHops / ReverseMinHops are per-direction overrides for bandwidth-asymmetric workloads (e.g. HTTP GET = small upstream + bulk downstream). When > 0 they take precedence over the symmetric MinHops for that direction only. Zero = inherit MinHops. Setting just ReverseMinHops=2 with MinHops=0 lets forward stay direct while reverse is forced multi-hop — the canonical asymmetric test shape.

ForwardMuxRoutes / ReverseMuxRoutes are per-direction overrides for MuxRoutes. Setting ForwardMuxRoutes=1 + ReverseMuxRoutes=4 yields 1 forward leg + 4 reverse legs — the canonical asymmetric-count shape (download-heavy workload aggregating only the reverse direction).

func (DialOptionsReq) Explicit

func (r DialOptionsReq) Explicit() bool

Explicit reports whether the request carries any per-call option at all. When it does not, the dial is the plain router-picks-freely default and both the app-side client and the gateway take the ordinary Dial path. Any explicit value — INCLUDING 1 — is an override that must reach the networker (MuxRoutes=1 / MinHops=1 lets an app force a single / direct route out from under a visor-global min_hops or mux_routes > 1).

type DialResp

type DialResp struct {
	ConnID    uint16
	LocalPort routing.Port
}

DialResp contains response parameters for `Dial`.

type GrowMuxReq

type GrowMuxReq struct {
	LocalPort routing.Port
	Legs      int
	MinHops   int
}

GrowMuxReq is an app asking the visor to widen ONE of its tunnels by Legs packet-level mux legs, built on the routes of the app's own STANDBY tunnels to the same exit (pkg/router/pool_legs.go) rather than discovered from scratch. The app knows which tunnel it wants widened and how many of its tunnels are parked; only the visor knows the routes behind them.

LocalPort names the tunnel exactly as in NoteMuxEventReq: the port the app was handed when it dialed. MinHops floors the hop count of a leg the pool could not cover and the route-finder has to plan (0 = the visor's own floor).

type MockProcManager

type MockProcManager struct {
	mock.Mock
}

MockProcManager is an autogenerated mock type for the ProcManager type

func NewMockProcManager

func NewMockProcManager(t interface {
	mock.TestingT
	Cleanup(func())
}) *MockProcManager

NewMockProcManager creates a new instance of MockProcManager. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. The first argument is typically a *testing.T value.

func (*MockProcManager) Addr

func (_m *MockProcManager) Addr() net.Addr

Addr provides a mock function with no fields

func (*MockProcManager) AllAppSettings

func (_m *MockProcManager) AllAppSettings() (map[string]map[string]int64, map[string]map[string]string)

AllAppSettings provides a working stand-in for the real store.

func (*MockProcManager) AppByPort

func (_m *MockProcManager) AppByPort(p routing.Port) (string, bool)

AppByPort provides a mock function with given fields: p

func (*MockProcManager) AppSettings

func (_m *MockProcManager) AppSettings(appName string, applied, opsApplied uint64) (map[string]int64, map[string]string, []AppOp, uint64)

AppSettings provides a working stand-in for the real store.

func (*MockProcManager) AppSettingsState

func (_m *MockProcManager) AppSettingsState(appName string) (map[string]int64, map[string]string, uint64, uint64)

AppSettingsState provides a working stand-in for the real store.

func (*MockProcManager) Close

func (_m *MockProcManager) Close() error

Close provides a mock function with no fields

func (*MockProcManager) ConnectionsSummary

func (_m *MockProcManager) ConnectionsSummary(appName string) ([]ConnectionSummary, error)

ConnectionsSummary provides a mock function with given fields: appName

func (*MockProcManager) Deregister

func (_m *MockProcManager) Deregister(key appcommon.ProcKey) error

Deregister provides a mock function with given fields: key

func (*MockProcManager) DetailedStatus

func (_m *MockProcManager) DetailedStatus(appName string) (string, error)

DetailedStatus provides a mock function with given fields: appName

func (*MockProcManager) ErrorByName

func (_m *MockProcManager) ErrorByName(appName string) (string, bool)

ErrorByName provides a mock function with given fields: appName

func (*MockProcManager) GetAppPort

func (_m *MockProcManager) GetAppPort(appName string) (routing.Port, error)

GetAppPort provides a mock function with given fields: appName

func (*MockProcManager) ProcByName

func (_m *MockProcManager) ProcByName(appName string) (*Proc, bool)

ProcByName provides a mock function with given fields: appName

func (*MockProcManager) ProxyStatus

func (_m *MockProcManager) ProxyStatus(appName string) (proxystatus.Snapshot, bool)

ProxyStatus provides a mock function with given fields: appName

func (*MockProcManager) QueueAppOp

func (_m *MockProcManager) QueueAppOp(appName, kind string, arg int64) uint64

QueueAppOp provides a working stand-in for the real store.

func (*MockProcManager) Range

func (_m *MockProcManager) Range(next func(string, *Proc) bool)

Range provides a mock function with given fields: next

func (*MockProcManager) Register

Register provides a mock function with given fields: conf

func (*MockProcManager) SetAppSettings

func (_m *MockProcManager) SetAppSettings(appName string, vals map[string]int64, text map[string]string) uint64

SetAppSettings provides a working stand-in for the real store.

func (*MockProcManager) SetDetailedStatus

func (_m *MockProcManager) SetDetailedStatus(appName string, status string) error

SetDetailedStatus provides a mock function with given fields: appName, status

func (*MockProcManager) SetError

func (_m *MockProcManager) SetError(appName string, status string) error

SetError provides a mock function with given fields: appName, status

func (*MockProcManager) SetProxyStatusFn

func (_m *MockProcManager) SetProxyStatusFn(fn func(appName string) (proxystatus.Snapshot, bool))

SetProxyStatusFn provides a mock function with given fields: fn

func (*MockProcManager) Start

Start provides a mock function with given fields: conf

func (*MockProcManager) Stats

func (_m *MockProcManager) Stats(appName string) (AppStats, error)

Stats provides a mock function with given fields: appName

func (*MockProcManager) Stop

func (_m *MockProcManager) Stop(appName string) error

Stop provides a mock function with given fields: appName

func (*MockProcManager) Wait

func (_m *MockProcManager) Wait(appName string) error

Wait provides a mock function with given fields: appName

type MockRPCIngressClient

type MockRPCIngressClient struct {
	mock.Mock
}

MockRPCIngressClient is an autogenerated mock type for the RPCIngressClient type

func NewMockRPCIngressClient

func NewMockRPCIngressClient(t interface {
	mock.TestingT
	Cleanup(func())
}) *MockRPCIngressClient

NewMockRPCIngressClient creates a new instance of MockRPCIngressClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. The first argument is typically a *testing.T value.

func (*MockRPCIngressClient) Accept

func (_m *MockRPCIngressClient) Accept(lisID uint16) (uint16, appnet.Addr, error)

Accept provides a mock function with given fields: lisID

func (*MockRPCIngressClient) AppSettings

func (_m *MockRPCIngressClient) AppSettings(req AppSettingsReq) (AppSettingsResp, error)

AppSettings provides a working stand-in for the real store, keyed by the mock itself: nothing about the poll is worth asserting, and a mock.Called here would panic in every existing test that reaches the settings tick.

func (*MockRPCIngressClient) CloseConn

func (_m *MockRPCIngressClient) CloseConn(id uint16) error

CloseConn provides a mock function with given fields: id

func (*MockRPCIngressClient) CloseListener

func (_m *MockRPCIngressClient) CloseListener(id uint16) error

CloseListener provides a mock function with given fields: id

func (*MockRPCIngressClient) Dial

func (_m *MockRPCIngressClient) Dial(remote appnet.Addr) (uint16, routing.Port, error)

Dial provides a mock function with given fields: remote

func (*MockRPCIngressClient) DialWithOptions

func (_m *MockRPCIngressClient) DialWithOptions(req DialOptionsReq) (uint16, routing.Port, error)

DialWithOptions provides a mock function with given fields: req

func (*MockRPCIngressClient) GrowMux

func (_m *MockRPCIngressClient) GrowMux(req GrowMuxReq) (int, error)

GrowMux provides a mock function with given fields: req

func (*MockRPCIngressClient) Listen

func (_m *MockRPCIngressClient) Listen(local appnet.Addr) (uint16, error)

Listen provides a mock function with given fields: local

func (*MockRPCIngressClient) LocalServices added in v1.3.99

func (_m *MockRPCIngressClient) LocalServices() ([]appnet.LocalService, error)

LocalServices provides a mock function with no fields

func (*MockRPCIngressClient) NoteMuxEvent

func (_m *MockRPCIngressClient) NoteMuxEvent(req NoteMuxEventReq) error

NoteMuxEvent provides a mock function with given fields: req

func (*MockRPCIngressClient) Notify

func (_m *MockRPCIngressClient) Notify(n NotifyReq) error

Notify provides a mock function with given fields: n

func (*MockRPCIngressClient) ProxyStatus

func (_m *MockRPCIngressClient) ProxyStatus() (proxystatus.Snapshot, error)

ProxyStatus provides a mock function with no fields

func (*MockRPCIngressClient) Read

func (_m *MockRPCIngressClient) Read(connID uint16, b []byte) (int, error)

Read provides a mock function with given fields: connID, b

func (*MockRPCIngressClient) SetAppPort

func (_m *MockRPCIngressClient) SetAppPort(appPort routing.Port) error

SetAppPort provides a mock function with given fields: appPort

func (*MockRPCIngressClient) SetConnectionDuration

func (_m *MockRPCIngressClient) SetConnectionDuration(dur int64) error

SetConnectionDuration provides a mock function with given fields: dur

func (*MockRPCIngressClient) SetDeadline

func (_m *MockRPCIngressClient) SetDeadline(connID uint16, d time.Time) error

SetDeadline provides a mock function with given fields: connID, d

func (*MockRPCIngressClient) SetDetailedStatus

func (_m *MockRPCIngressClient) SetDetailedStatus(status string) error

SetDetailedStatus provides a mock function with given fields: status

func (*MockRPCIngressClient) SetError

func (_m *MockRPCIngressClient) SetError(appErr string) error

SetError provides a mock function with given fields: appErr

func (*MockRPCIngressClient) SetOTP

func (_m *MockRPCIngressClient) SetOTP(otp string) error

SetOTP provides a mock function with given fields: otp

func (*MockRPCIngressClient) SetReadDeadline

func (_m *MockRPCIngressClient) SetReadDeadline(connID uint16, d time.Time) error

SetReadDeadline provides a mock function with given fields: connID, d

func (*MockRPCIngressClient) SetWriteDeadline

func (_m *MockRPCIngressClient) SetWriteDeadline(connID uint16, d time.Time) error

SetWriteDeadline provides a mock function with given fields: connID, d

func (*MockRPCIngressClient) Write

func (_m *MockRPCIngressClient) Write(connID uint16, b []byte) (int, error)

Write provides a mock function with given fields: connID, b

type NoteMuxEventReq

type NoteMuxEventReq struct {
	LocalPort routing.Port
	Event     string
	Reason    string
	Role      string
}

NoteMuxEventReq is an app reporting a TUNNEL event — one of its tunnels promoted out of standby, parked back into it, snubbed for making no progress (or unsnubbed when its hold is served), or retired dead — onto the router's mux-event ring, and re-labeling that tunnel's role.

LocalPort names the tunnel: it is the port the app was handed when it dialed (DialResp.LocalPort), which is the route group's source port, and the only identifier the app has for a route group it knows nothing else about. Event is one of router.MuxEventTunnel*; Reason is what decided it, in the words of the code that did ("failover: active tunnel died"). Role, when set, is the tunnel's role AFTER the event ("active" / "standby" / "snubbed").

type NotificationHub

type NotificationHub interface {
	Publish(n NotifyReq)
}

NotificationHub receives notifications published by apps and routes them to whatever sink can actually reach the user. It is implemented on the visor side (*visor.NotifyHub); appserver only needs the seam.

Publish MUST NOT block: it is called on the publishing app's single RPC serve goroutine, so a slow sink would stall that app's Dial/Write/Read too.

type NotifyReq

type NotifyReq struct {
	// App is the publishing app's name (e.g. "skychat"). Apps may leave it
	// empty: the visor stamps it from the proc's own identity and overwrites
	// whatever arrived on the wire, so an app cannot publish under another
	// app's name (see RPCIngressGateway.Notify).
	App string
	// Title is the bold heading.
	Title string
	// Body is the message text. Frequently UNTRUSTED — a peer's chat message —
	// so it is never logged and never interpolated into anything executable.
	Body string
	// Tag optionally groups related notifications so a sink that supports
	// coalescing (an Android notification tag, say) can replace rather than
	// stack them. Empty means "no coalescing". Sinks without the concept —
	// pkg/osnotify — ignore it.
	Tag string
}

NotifyReq is a single user-facing notification published by an app.

All fields are plain strings so the value gob-encodes on both the reflection and the TinyGo codec paths without a gob.Register.

type Proc

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

Proc is an instance of a skywire app. It encapsulates the running process itself and the RPC server for app/visor communication.

func NewProc

func NewProc(mLog *logging.MasterLogger, conf appcommon.ProcConfig, disc appdisc.Updater, m ProcManager,
	appName, logStorePath string) *Proc

NewProc constructs `Proc`.

func (*Proc) AwaitConn

func (p *Proc) AwaitConn() bool

AwaitConn waits for the connection.

func (*Proc) Cmd

func (p *Proc) Cmd() *exec.Cmd

Cmd returns the underlying os/exec command (nil for in-process apps).

func (*Proc) ConnectionDuration

func (p *Proc) ConnectionDuration() int64

ConnectionDuration gets proc's connection duration

func (*Proc) ConnectionsSummary

func (p *Proc) ConnectionsSummary() []ConnectionSummary

ConnectionsSummary returns all of the proc's connections stats.

func (*Proc) DetailedStatus

func (p *Proc) DetailedStatus() string

DetailedStatus gets proc's detailed status.

func (*Proc) Error

func (p *Proc) Error() string

Error gets proc's error.

func (*Proc) GetAppPort

func (p *Proc) GetAppPort() routing.Port

GetAppPort gets the proc's connection port

func (*Proc) InjectConn

func (p *Proc) InjectConn(conn net.Conn) bool

InjectConn introduces the connection to the Proc after it is started. Only the first call will return true. It also prepares the RPC gateway.

func (*Proc) IsRunning

func (p *Proc) IsRunning() bool

IsRunning checks whether application cmd is running.

func (*Proc) Logs

func (p *Proc) Logs() appcommon.LogStore

Logs obtains the log store.

func (*Proc) Notify

func (p *Proc) Notify(n NotifyReq)

Notify forwards a notification the app published to the visor's notification hub, which decides which sink (if any) can actually reach the user.

Best-effort and total-no-op safe: the hub is nil on a bare &Proc{} (unit tests) and on the browser wasm-visor, and a nil hub simply drops. The App field is re-stamped from this proc's own config so the hub always attributes the event correctly even if the caller left it empty or lied.

func (*Proc) OTP

func (p *Proc) OTP() string

OTP gets the proc's current one-time code.

func (*Proc) SetAppPort

func (p *Proc) SetAppPort(port routing.Port)

SetAppPort sets the proc's connection port

func (*Proc) SetConnectionDuration

func (p *Proc) SetConnectionDuration(dur int64)

SetConnectionDuration sets the proc's connection duration

func (*Proc) SetDetailedStatus

func (p *Proc) SetDetailedStatus(status string)

SetDetailedStatus sets proc's detailed status.

func (*Proc) SetError

func (p *Proc) SetError(appErr string)

SetError sets proc's detailed status error.

func (*Proc) SetOTP

func (p *Proc) SetOTP(otp string)

SetOTP sets the proc's current one-time code. Apps that gate their own UI publish it here so an operator can read it from the (authenticated) hypervisor app list. Deliberately not logged — app logs are retrievable over the API and may be shipped off-box.

func (*Proc) Start

func (p *Proc) Start() error

Start starts the application.

func (*Proc) StartTime

func (p *Proc) StartTime() (time.Time, bool)

StartTime returns app start time.

func (*Proc) Stop

func (p *Proc) Stop() error

Stop stops the application.

func (*Proc) Wait

func (p *Proc) Wait() error

Wait waits for the application cmd to exit.

type ProcManager

type ProcManager interface {
	io.Closer
	Start(conf appcommon.ProcConfig) (appcommon.ProcID, error)
	Register(conf appcommon.ProcConfig) (appcommon.ProcKey, error)
	Deregister(key appcommon.ProcKey) error
	ProcByName(appName string) (*Proc, bool)
	SetError(appName, status string) error
	ErrorByName(appName string) (string, bool)
	Stop(appName string) error
	Wait(appName string) error
	Range(next func(appName string, proc *Proc) bool)
	Stats(appName string) (AppStats, error)
	SetDetailedStatus(appName, status string) error
	DetailedStatus(appName string) (string, error)
	GetAppPort(appName string) (routing.Port, error)
	// AppByPort is the inverse of GetAppPort: given a routing port,
	// returns the app name registered for that port. Used by the
	// router/setup-node layers to tag rg-scoped log entries with the
	// originating app, so 'cli proxy start --verbose' can scope to a
	// session.
	AppByPort(p routing.Port) (string, bool)
	ConnectionsSummary(appName string) ([]ConnectionSummary, error)
	Addr() net.Addr
	// SetProxyStatusFn wires the builder that turns an app name into the
	// visor's rich read-only status snapshot (per-leg mux telemetry, recent
	// logs, route/transport events). An app that serves its own reserved
	// status host (skysocks-client's status.skysocks) reaches it via the
	// ProxyStatus app RPC. Nil-safe: unset means ProxyStatus reports "no
	// snapshot" and the app renders its own local view.
	SetProxyStatusFn(fn func(appName string) (proxystatus.Snapshot, bool))
	// ProxyStatus returns the visor-built status snapshot for appName. ok is
	// false when no builder is wired (wasm/tests) or it has no data for the app.
	ProxyStatus(appName string) (proxystatus.Snapshot, bool)
	// SetAppSettings replaces the live tuning knobs held for an app — the
	// numeric map and the list map together — and returns the new version. The
	// set OUTLIVES the app process (the visor persists it); see app_settings.go.
	SetAppSettings(appName string, vals map[string]int64, text map[string]string) uint64
	// AppSettings answers the app POLLING for its knobs, recording the version
	// and the op sequence it reports having applied. Values are nil when applied
	// is already current; pending ops are returned on every poll until acked.
	AppSettings(appName string, applied, opsApplied uint64) (map[string]int64, map[string]string, []AppOp, uint64)
	// AppSettingsState reports the intended values, their version, and the
	// version the app last acknowledged — the CLI's pending/applied column.
	AppSettingsState(appName string) (map[string]int64, map[string]string, uint64, uint64)
	// QueueAppOp queues one edge-triggered instruction (AppOpCutTunnel) for an
	// app, returning its sequence. The app applies it on its next pull.
	QueueAppOp(appName, kind string, arg int64) uint64
	// AllAppSettings returns every app's intended set, for the visor to persist.
	AllAppSettings() (map[string]map[string]int64, map[string]map[string]string)
}

ProcManager allows to manage skywire applications.

func NewProcManager

func NewProcManager(mLog *logging.MasterLogger, discF *appdisc.Factory, eb *appevent.Broadcaster,
	hub NotificationHub, addr, logStorePath string) (ProcManager, error)

NewProcManager constructs `ProcManager`.

type RPCErr

type RPCErr struct {
	Err string
}

RPCErr is used to preserve the type of the errors we return via RPC

func (RPCErr) Error

func (e RPCErr) Error() string

type RPCIOErr

type RPCIOErr struct {
	Text           string
	IsNetErr       bool
	IsTimeoutErr   bool
	IsTemporaryErr bool
}

RPCIOErr is used to return an error coming from network stack.

Since client is implemented as an RPC client, we need to correctly pass all kinds of network errors from gateway back to the client. `net.Error` is an interface, so we can't pass it directly, we have to disassemble error on the server side and reassemble it back on the client side.

func (*RPCIOErr) ToError

func (e *RPCIOErr) ToError() error

ToError converts `*RPCIOErr` to `error`.

type RPCIngressClient

type RPCIngressClient interface {
	SetDetailedStatus(status string) error
	SetOTP(otp string) error
	// Notify publishes a user-facing notification to the visor's
	// notification hub. The visor overwrites the App field from the calling
	// proc's identity, so it may be left empty.
	Notify(n NotifyReq) error
	SetConnectionDuration(dur int64) error
	SetError(appErr string) error
	SetAppPort(appPort routing.Port) error
	// ProxyStatus fetches the visor-built rich read-only status snapshot for
	// this app (per-leg mux telemetry, recent logs, route/transport events).
	// An empty snapshot (no error) means the visor has no data; the caller
	// then renders its own local view.
	ProxyStatus() (proxystatus.Snapshot, error)
	// LocalServices lists the in-process services on this app's own visor that
	// it can dial over this same data plane — the resolving proxies, each with
	// the hostname suffixes it answers for.
	LocalServices() ([]appnet.LocalService, error)
	Dial(remote appnet.Addr) (connID uint16, localPort routing.Port, err error)
	// DialWithOptions asks the server to dial req.Addr with the per-call
	// options req carries — mux route counts, min-hops (symmetric and
	// per-direction), the direct-transport shortcut, multi-tunnel
	// diversification and its disjoint requirement, and the tunnel's role
	// label. See DialOptionsReq for each field. A req with nothing explicit
	// set (DialOptionsReq.Explicit) is equivalent to plain Dial.
	//
	// It takes the request struct rather than a positional list because the
	// option set grows: every new dial option used to mean a new parameter on
	// this method, its mock and every call site.
	DialWithOptions(req DialOptionsReq) (connID uint16, localPort routing.Port, err error)
	// NoteMuxEvent records a TUNNEL event the app decided — a standby tunnel
	// promoted, an active one parked, a dead one retired — on the route group
	// the app dialed from req.LocalPort, and re-labels that group's tunnel
	// role. See NoteMuxEventReq.
	NoteMuxEvent(req NoteMuxEventReq) error
	// GrowMux asks the visor to widen the tunnel the app dialed from
	// req.LocalPort by req.Legs mux legs, built on the routes of the app's own
	// STANDBY tunnels to the same exit. Returns how many legs were added.
	GrowMux(req GrowMuxReq) (int, error)
	// AppSettings polls the visor for this app's live tuning knobs, reporting
	// the version the app currently has installed. See AppSettingsResp.
	AppSettings(req AppSettingsReq) (AppSettingsResp, error)
	Listen(local appnet.Addr) (uint16, error)
	Accept(lisID uint16) (connID uint16, remote appnet.Addr, err error)
	Write(connID uint16, b []byte) (int, error)
	Read(connID uint16, b []byte) (int, error)
	CloseConn(id uint16) error
	CloseListener(id uint16) error
	SetDeadline(connID uint16, d time.Time) error
	SetReadDeadline(connID uint16, d time.Time) error
	SetWriteDeadline(connID uint16, d time.Time) error
}

RPCIngressClient describes RPC interface to communicate with the server.

func NewRPCIngressClient

func NewRPCIngressClient(rpc *rpc.Client, procKey appcommon.ProcKey) RPCIngressClient

NewRPCIngressClient constructs new `rpcIngressClient`.

type RPCIngressGateway

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

RPCIngressGateway is a RPC interface for the app server.

func NewRPCGateway

func NewRPCGateway(log *logging.Logger, proc *Proc) *RPCIngressGateway

NewRPCGateway constructs new server RPC interface.

func (*RPCIngressGateway) Accept

func (r *RPCIngressGateway) Accept(lisID *uint16, resp *AcceptResp) (err error)

Accept accepts connection from the listener specified by `lisID`.

func (*RPCIngressGateway) AppSettings

func (r *RPCIngressGateway) AppSettings(req *AppSettingsReq, resp *AppSettingsResp) (err error)

AppSettings answers the calling app POLLING for its live tuning knobs — the one visor->app value channel there is. The gateway is INGRESS only (the app is the RPC client), so nothing can be pushed into a running app; the app asks on a tick it already runs and installs what comes back.

Ownership is r.proc.appName, the same proof ProxyStatus and NoteMuxEvent use, so an app can only ever read its own knobs. A visor with no store wired (unit tests with a bare gateway) answers "nothing changed" WITHOUT error, so the app degrades to its compiled defaults rather than logging a failure every tick.

func (*RPCIngressGateway) CloseConn

func (r *RPCIngressGateway) CloseConn(connID *uint16, _ *struct{}) (err error)

CloseConn closes connection specified by `connID`.

func (*RPCIngressGateway) CloseListener

func (r *RPCIngressGateway) CloseListener(lisID *uint16, _ *struct{}) (err error)

CloseListener closes listener specified by `lisID`.

func (*RPCIngressGateway) Dial

func (r *RPCIngressGateway) Dial(remote *appnet.Addr, resp *DialResp) (err error)

Dial dials to the remote.

func (*RPCIngressGateway) DialWithOptions

func (r *RPCIngressGateway) DialWithOptions(req *DialOptionsReq, resp *DialResp) (err error)

DialWithOptions dials to the remote honoring per-call dial options. Honored options: MuxRoutes (N parallel mux routes), MinHops (require N intermediates), ForwardMinHops / ReverseMinHops (per-direction MinHops overrides for bandwidth-asymmetric workloads). Together these exercise the operator's mux>direct hypothesis and the asymmetric-routing extension: MuxRoutes > 1 with MinHops >= 2 forces N disjoint non-direct paths; per-direction MinHops lets forward and reverse have different constraints (direct upstream + multi-hop downstream). Falls back to plain Dial semantics when no opts demand the router path OR the registered networker for the address family isn't *SkywireNetworker (e.g. dmsg, where mux is inherent).

func (*RPCIngressGateway) GrowMux

func (r *RPCIngressGateway) GrowMux(req *GrowMuxReq, out *int) (err error)

GrowMux widens one of this app's tunnels by req.Legs mux legs taken from the app's standby pool, and reports how many were actually added.

Same seam and same ownership proof as NoteMuxEvent: the router's lookup is by PORT ALONE, so without the check any app process could grow another app's tunnel. Legs is clamped rather than rejected — an app asking for more than the bound gets the bound, which is the useful behavior for a widen request. A networker with no route groups degrades quietly, as dialWithMuxRoutes does.

func (*RPCIngressGateway) Listen

func (r *RPCIngressGateway) Listen(local *appnet.Addr, lisID *uint16) (err error)

Listen starts listening.

func (*RPCIngressGateway) LocalServices added in v1.3.99

func (r *RPCIngressGateway) LocalServices(_ *struct{}, resp *[]appnet.LocalService) (err error)

LocalServices lists the in-process services the calling app can reach on its own visor — the resolving proxies, each with the hostname suffixes it answers for. An app matches a CONNECT target against those suffixes and dials the port, instead of being told a port number twice (once in the visor's config, once in its own args). Read-only; an empty list means there is nothing to reach, which is not an error.

func (*RPCIngressGateway) NoteMuxEvent

func (r *RPCIngressGateway) NoteMuxEvent(req *NoteMuxEventReq, _ *struct{}) (err error)

NoteMuxEvent records an app-decided tunnel event on the route group the app dialed from req.LocalPort, and re-stamps that group's tunnel role.

Only the app knows which of its tunnels carries streams; only the router knows the route behind it and owns the event ring `visor state --select diag` reads. This is the one seam between them. Unknown ports are not an error — a tunnel whose route group has already been reaped is exactly the case a retire event reports, and failing the call would turn a race into a log line in the app.

The request is validated first, because the router's lookup is by PORT ALONE: NoteTunnelEvent matches any route group whose source or destination port equals it, across every app on this visor. Without the ownership check any app process could re-stamp another app's tunnel role and write events attributed to the victim's app name. Reason is truncated because it is stored verbatim in a ring that never shrinks; Event and Role need no truncation once they are whitelisted against a fixed vocabulary.

func (*RPCIngressGateway) Notify

func (r *RPCIngressGateway) Notify(req *NotifyReq, _ *struct{}) (err error)

Notify publishes a user-facing notification to the visor's notification hub, which routes it to whatever sink can actually reach the user.

Like SetOTP the payload is withheld from rpcutil.LogCall (nil input): a body is routinely UNTRUSTED peer text (a chat message), and app logs are retrievable over the hypervisor API.

The App field is overwritten from this proc's own identity rather than trusted: the RPC service is registered per-proc and served on that proc's private conn, so the visor already knows the caller for certain. Without the override any app could publish under skychat's name — which on Android maps to a per-app notification channel the user may have deliberately muted.

func (*RPCIngressGateway) ProxyStatus

func (r *RPCIngressGateway) ProxyStatus(_ *struct{}, resp *proxystatus.Snapshot) (err error)

ProxyStatus returns the visor-built rich read-only status snapshot for the calling app (per-leg mux telemetry, recent logs, route/transport events). It lets an app that serves its own reserved status host — skysocks-client's status.skysocks — render the same rich view the visor-side resolving proxies show, instead of only what the app process itself can see. Read-only; an empty snapshot (the visor has no data, or the manager has no builder wired as on the browser wasm-visor) is returned WITHOUT error so the app degrades gracefully to its own local view.

func (*RPCIngressGateway) Read

func (r *RPCIngressGateway) Read(req *ReadReq, resp *ReadResp) error

Read reads data from connection specified by `connID`.

func (*RPCIngressGateway) SetAppPort

func (r *RPCIngressGateway) SetAppPort(port routing.Port, _ *struct{}) (err error)

SetAppPort sets the connection port of an app (vpn-client in this instance)

func (*RPCIngressGateway) SetConnectionDuration

func (r *RPCIngressGateway) SetConnectionDuration(dur int64, _ *struct{}) (err error)

SetConnectionDuration sets the connection duration of an app (vpn-client in this instance)

func (*RPCIngressGateway) SetDeadline

func (r *RPCIngressGateway) SetDeadline(req *DeadlineReq, _ *struct{}) error

SetDeadline sets deadline for connection specified by `connID`.

func (*RPCIngressGateway) SetDetailedStatus

func (r *RPCIngressGateway) SetDetailedStatus(status *string, _ *struct{}) (err error)

SetDetailedStatus sets detailed status of an app.

func (*RPCIngressGateway) SetError

func (r *RPCIngressGateway) SetError(appErr *string, _ *struct{}) (err error)

SetError sets error of an app.

func (*RPCIngressGateway) SetOTP

func (r *RPCIngressGateway) SetOTP(otp *string, _ *struct{}) (err error)

SetOTP sets the current one-time code of an app that gates its own UI.

Unlike its sibling calls, the value is deliberately withheld from rpcutil.LogCall (nil input) — it is a live credential, and app logs are retrievable over the hypervisor API.

func (*RPCIngressGateway) SetReadDeadline

func (r *RPCIngressGateway) SetReadDeadline(req *DeadlineReq, _ *struct{}) error

SetReadDeadline sets read deadline for connection specified by `connID`.

func (*RPCIngressGateway) SetWriteDeadline

func (r *RPCIngressGateway) SetWriteDeadline(req *DeadlineReq, _ *struct{}) error

SetWriteDeadline sets read deadline for connection specified by `connID`.

func (*RPCIngressGateway) Write

func (r *RPCIngressGateway) Write(req *WriteReq, resp *WriteResp) error

Write writes to the connection.

type ReadReq

type ReadReq struct {
	ConnID uint16
	BufLen int
}

ReadReq contains arguments for `Read`.

type ReadResp

type ReadResp struct {
	B   []byte
	N   int
	Err *RPCIOErr
}

ReadResp contains response parameters for `Read`.

type WriteReq

type WriteReq struct {
	ConnID uint16
	B      []byte
}

WriteReq contains arguments for `Write`.

type WriteResp

type WriteResp struct {
	N   int
	Err *RPCIOErr
}

WriteResp contains response parameters for `Write`.

Directories

Path Synopsis
Package spec pkg/app/appserver/spec/spec.go c2-vis-appsvc Holds the AppConfig wire-format type that pkg/visor/visorconfig.V1's Launcher.Apps field embeds.
Package spec pkg/app/appserver/spec/spec.go c2-vis-appsvc Holds the AppConfig wire-format type that pkg/visor/visorconfig.V1's Launcher.Apps field embeds.

Jump to

Keyboard shortcuts

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