Documentation
¶
Index ¶
- Constants
- Variables
- func Revoke(store Store, id string, now time.Time) (kind string, err error)
- func SaveCredential(path string, c Credential) error
- type Bundle
- type ByePayload
- type Credential
- type DeviceKind
- type DeviceStream
- type DialFunc
- type EnrollmentRecord
- type ErrorPayload
- type ExportedDevice
- type ForwardedDevice
- type Forwarder
- type ForwarderConfig
- type ForwarderRecord
- type Frame
- type FrameType
- type HelloPayload
- type Hub
- func (h *Hub) Connected() []string
- func (h *Hub) DropSession(forwarderID, reason string)
- func (h *Hub) OpenDevice(ctx context.Context, forwarderID, purpose string) (*DeviceStream, error)
- func (h *Hub) OpenStream(ctx context.Context, forwarderID, purpose string) (io.ReadWriteCloser, error)
- func (h *Hub) Resolved(forwarderID string) *HelloPayload
- func (h *Hub) Revoke(forwarderID string) error
- func (h *Hub) ServeHTTP(w http.ResponseWriter, r *http.Request)
- func (h *Hub) Session(forwarderID string) (*session, bool)
- type HubOptions
- type ImportOptions
- type L2Source
- type L2SourceOptions
- type Ledger
- type LedgerEntry
- type LedgerKind
- type LocalSource
- type MintOptions
- type OpenAckPayload
- type OpenPayload
- type ProbeResult
- type Store
- type SweepReport
- type TAPBridge
- type USBImport
- type USBSource
- type USBSourceOptions
- type VHCI
- type WelcomePayload
Constants ¶
const DefaultEnrollTTL = 15 * time.Minute
DefaultEnrollTTL bounds an unredeemed enrollment token.
const DefaultRevocationRecheck = 5 * time.Second
DefaultRevocationRecheck bounds how long a revoked forwarder's live session can outlast its revocation when nothing told the holding process at once.
const DefaultVHCIRoot = "/sys/devices/platform/vhci_hcd.0"
DefaultVHCIRoot is the platform-device directory of the first controller.
const MaxEnrollTTL = 24 * time.Hour
MaxEnrollTTL caps --ttl, so a single-use token cannot be turned into a standing credential.
const MaxFramePayload = 1 << 20 // 1 MiB
MaxFramePayload bounds one frame's payload. A Data chunk larger than this is a bug or an attack, not a legitimate stream write; USB/IP URBs and Ethernet frames are far smaller, and the relay chunks its reads well under it.
const MinProtocolVersion = 1
MinProtocolVersion is the oldest a connecting forwarder may speak.
const ProtocolVersion = 1
ProtocolVersion is the newest forwarder protocol this build speaks.
const TokenFileMode os.FileMode = 0o600
TokenFileMode is the permission an enrollment file must carry.
Variables ¶
var ( // ErrInvalidSpec reports a device descriptor or configuration that is not // admissible. It is the forwarder's ErrInvalidSpec, kept separate from // executor.ErrInvalidSpec because the two packages do not import each // other's error here and a caller branches on the one it was handed. ErrInvalidSpec = errors.New("deviceforward: invalid specification") // ErrTokenInvalid reports a malformed enrollment token or credential, or // one whose MAC does not verify against this control plane. ErrTokenInvalid = errors.New("deviceforward: token is invalid") // ErrTokenExpired reports an enrollment token past its TTL. ErrTokenExpired = errors.New("deviceforward: enrollment token expired") // ErrTokenAlreadyUsed reports an enrollment token redeemed before. ErrTokenAlreadyUsed = errors.New("deviceforward: enrollment token already redeemed") // ErrRevoked reports a token or credential that was explicitly revoked. ErrRevoked = errors.New("deviceforward: credential revoked") // ErrCredentialInvalid reports a long-lived forwarder credential that does // not authenticate — reported distinctly from a token so the device log // says "re-enroll" rather than "bad token". ErrCredentialInvalid = errors.New("deviceforward: credential is invalid") // ErrForwarderNotFound reports no enrollment token or forwarder with an ID. ErrForwarderNotFound = errors.New("deviceforward: no such forwarder") // ErrForwarderOffline reports that the named forwarder has no live session, // so the device it carries cannot be reached right now. ErrForwarderOffline = errors.New("deviceforward: forwarder is offline") // ErrChannelRefused reports that the forwarder declined to open a channel // to its device — the device is unplugged, or the request named something // other than the one device the forwarder is configured for. ErrChannelRefused = errors.New("deviceforward: forwarder refused to open the device") // ErrProtocol reports a wire-format violation on the forwarder connection. ErrProtocol = errors.New("deviceforward: protocol error") )
Sentinel errors for the forwarder feature. They are errors.Is targets so a caller — the CLI, the hub endpoint, a test — can branch on what went wrong without matching on message text.
Functions ¶
func Revoke ¶
Revoke invalidates an enrollment token or a forwarder credential by ID. An enrollment ID additionally revokes the forwarder it produced, so a token that leaked and was redeemed by an attacker does not leave a live credential.
func SaveCredential ¶
func SaveCredential(path string, c Credential) error
SaveCredential writes the credential atomically at 0600.
Types ¶
type Bundle ¶
type Bundle struct {
Server string `json:"server"`
Token string `json:"token"`
Pin string `json:"pin,omitempty"`
EnrollmentID string `json:"enrollment_id"`
Name string `json:"name"`
Device ForwardedDevice `json:"device"`
ExpiresAt time.Time `json:"expires_at"`
}
Bundle is the enrollment payload handed to a forwarder.
func DecodeBundle ¶
DecodeBundle parses the output of Encode.
func ReadTokenFile ¶
ReadTokenFile loads enrollment material from a file, accepting either a bundle or a bare token, exactly as the agent's loader does.
type ByePayload ¶
type ByePayload struct {
Reason string `json:"reason,omitempty"`
Reconnect bool `json:"reconnect"`
}
ByePayload ends a session.
type Credential ¶
type Credential struct {
Server string `json:"server"`
ForwarderID string `json:"forwarder_id"`
Credential string `json:"credential"`
Name string `json:"name,omitempty"`
Device ForwardedDevice `json:"device"`
Pin string `json:"pin,omitempty"`
}
Credential is what a forwarder saves after enrolling.
func LoadCredential ¶
func LoadCredential(path string) (Credential, bool, error)
LoadCredential reads a saved credential. A missing file is reported as (zero, false, nil): the caller then needs an enrollment token.
type DeviceKind ¶
type DeviceKind string
DeviceKind is what sort of device a forwarder carries.
const ( // KindUSB forwards a USB device over USB/IP: the forwarder exposes the // on-prem kernel's usbip server for the one device, and the consumer // attaches it with vhci as if it were plugged in locally. KindUSB DeviceKind = "usb" // KindL2 forwards a layer-2 network interface: the forwarder streams the // interface's Ethernet frames, and the consumer replays them onto a TAP, // so a sandbox in the cloud shares the on-prem segment at the link layer. KindL2 DeviceKind = "l2" )
type DeviceStream ¶
type DeviceStream struct {
io.ReadWriteCloser
ForwarderID string
Device ForwardedDevice
Exported ExportedDevice
}
DeviceStream is an open stream to a forwarder's device, with what is known about the device: the enrolled descriptor, which is authoritative, and what the forwarder reported opening, which for USB names the bus id a consumer's import must carry.
type DialFunc ¶
DialFunc opens a frame connection to a server, for tests to substitute an in-memory transport. token is the credential or enrollment token to present.
type EnrollmentRecord ¶
type EnrollmentRecord struct {
ID string `json:"id"`
Name string `json:"name"`
SecretHash string `json:"secret_hash"`
Device ForwardedDevice `json:"device"`
CreatedAt time.Time `json:"created_at"`
ExpiresAt time.Time `json:"expires_at"`
CreatedBy string `json:"created_by,omitempty"`
// Owner scopes the forwarder to one identity on a multi-user hub, so a
// forwarder is listed and revocable by its owner. Empty on a single-user
// hub.
Owner string `json:"owner,omitempty"`
RedeemedAt time.Time `json:"redeemed_at,omitempty"`
RedeemedAgentID string `json:"redeemed_agent_id,omitempty"`
RevokedAt time.Time `json:"revoked_at,omitempty"`
}
EnrollmentRecord is the hub's durable view of one minted forwarder token. The secret itself is never stored — only SecretHash.
func Mint ¶
func Mint(store Store, opts MintOptions) (token string, rec EnrollmentRecord, err error)
Mint creates a single-use enrollment token, persists its hash, and returns the token string. The token is the only time the secret exists outside the operator's terminal.
func MintBundle ¶
func MintBundle(store Store, opts MintOptions) (encoded string, rec EnrollmentRecord, err error)
MintBundle mints a token and wraps it, the server URL and the pin into a bundle in one step, for the enroll command and the dashboard.
func (EnrollmentRecord) Expired ¶
func (r EnrollmentRecord) Expired(now time.Time) bool
Expired reports whether the token's TTL has elapsed as of now.
func (EnrollmentRecord) Redeemed ¶
func (r EnrollmentRecord) Redeemed() bool
Redeemed reports whether the token has already been used.
func (EnrollmentRecord) Revoked ¶
func (r EnrollmentRecord) Revoked() bool
Revoked reports whether the token was explicitly revoked.
type ErrorPayload ¶
type ErrorPayload struct {
Message string `json:"message"`
}
ErrorPayload carries a non-fatal error.
type ExportedDevice ¶
type ExportedDevice struct {
Kind DeviceKind `json:"kind"`
BusID string `json:"bus_id,omitempty"`
VendorID string `json:"vendor_id,omitempty"`
ProductID string `json:"product_id,omitempty"`
// Speed is the kernel's usb_device_speed enum value.
Speed uint32 `json:"speed,omitempty"`
Interface string `json:"interface,omitempty"`
}
ExportedDevice is what a forwarder reports, in its open acknowledgement, about the device it just opened — the facts a consumer needs to import it. For USB, BusID is the on-prem kernel's bus id, which OP_REQ_IMPORT must name; the IDs are informational.
type ForwardedDevice ¶
type ForwardedDevice struct {
// Kind selects which of the fields below is meaningful.
Kind DeviceKind `json:"kind"`
// USB identifies the forwarded USB device by vendor, product and
// optionally serial — the identity that survives re-enumeration. Set when
// Kind is KindUSB.
USB *executor.USBMatch `json:"usb,omitempty"`
// Interface is the kernel name of the forwarded network interface on the
// on-prem host ("eth1", "can0"). Set when Kind is KindL2.
Interface string `json:"interface,omitempty"`
// Label is an operator-facing description shown in the dashboard and in
// audit rows. Optional; a default is derived from the identity when empty.
Label string `json:"label,omitempty"`
}
ForwardedDevice is the one device a forwarder is configured to carry.
Exactly one of USB and Interface is set, selected by Kind.
func (ForwardedDevice) DefaultLabel ¶
func (d ForwardedDevice) DefaultLabel() string
DefaultLabel returns Label, or a label derived from the identity when none was set, so a forwarder always has something an operator recognises.
func (ForwardedDevice) Describe ¶
func (d ForwardedDevice) Describe() string
Describe renders the device for a card, a log line or an audit row.
func (ForwardedDevice) Normalize ¶
func (d ForwardedDevice) Normalize() (ForwardedDevice, error)
Normalize canonicalises and validates the descriptor.
func (ForwardedDevice) Validate ¶
func (d ForwardedDevice) Validate() error
Validate reports whether the descriptor is admissible.
type Forwarder ¶
type Forwarder struct {
// contains filtered or unexported fields
}
Forwarder is a running edge forwarder.
func NewForwarder ¶
func NewForwarder(cfg ForwarderConfig) (*Forwarder, error)
NewForwarder validates the configuration and builds the transport. Every failure here is an operator-fixable misconfiguration, so they are reported now rather than discovered inside a reconnect loop that would retry forever.
func (*Forwarder) Run ¶
Run dials the control plane and serves until ctx is cancelled or the credential is refused permanently.
func (*Forwarder) TransportSummary ¶
TransportSummary describes the link for startup output.
type ForwarderConfig ¶
type ForwarderConfig struct {
// Server is the full wss://host/api/forwarders/connect URL.
Server string
// Token is a bare enrollment token, used only until a credential is saved.
Token string
// Bundle, if set, supplies Server/Token/Pin together (explicit fields win).
Bundle string
// CredentialPath is where the long-lived credential is saved and loaded.
CredentialPath string
// Pin is the hub's SPKI fingerprint; CAFile an extra root; Insecure allows
// plaintext to a non-loopback host (refused by default).
Pin string
CAFile string
Insecure bool
// Source is the on-prem device endpoint. Required.
Source LocalSource
// AgentVersion is the build string reported in the hello.
AgentVersion string
// Logf receives operational messages. Nil discards them.
Logf func(format string, args ...any)
// Dial overrides the transport, for tests.
Dial DialFunc
}
ForwarderConfig configures a forwarder.
type ForwarderRecord ¶
type ForwarderRecord struct {
ForwarderID string `json:"forwarder_id"`
Name string `json:"name"`
SecretHash string `json:"secret_hash"`
Device ForwardedDevice `json:"device"`
Owner string `json:"owner,omitempty"`
CreatedAt time.Time `json:"created_at"`
LastSeen time.Time `json:"last_seen,omitempty"`
RevokedAt time.Time `json:"revoked_at,omitempty"`
EnrollmentID string `json:"enrollment_id,omitempty"`
}
ForwarderRecord is the hub's durable view of one enrolled forwarder.
func Authenticate ¶
Authenticate verifies a long-lived forwarder credential on every reconnect.
func Redeem ¶
func Redeem(store Store, token string, now time.Time) (credential string, fwd ForwarderRecord, err error)
Redeem exchanges an enrollment token for a long-lived forwarder credential. The order of checks matches the agent's: format and MAC, existence, revocation, replay, expiry, secret, then the atomic single-use claim.
func (ForwarderRecord) Revoked ¶
func (r ForwarderRecord) Revoked() bool
Revoked reports whether the forwarder's credential was revoked.
type FrameType ¶
type FrameType byte
FrameType is the one-byte discriminator at the head of every frame.
const ( // TypeHello (forwarder→hub, channel 0) opens the session and advertises // the one device the forwarder carries. TypeHello FrameType = 1 // TypeWelcome (hub→forwarder, channel 0) accepts the session and names the // negotiated version. TypeWelcome FrameType = 2 // TypeOpen (hub→forwarder) asks the forwarder to open a new stream to its // device on the named channel. TypeOpen FrameType = 3 // TypeOpenAck (forwarder→hub) reports whether the open succeeded. TypeOpenAck FrameType = 4 // TypeData (both directions) carries one chunk of a stream's bytes on its // channel. The payload is opaque — it is the device's own protocol. TypeData FrameType = 5 // TypeClose (both directions) ends a stream's channel. After it, the // channel number must not be reused. TypeClose FrameType = 6 // TypePing / TypePong (channel 0) are the heartbeat. TypePing FrameType = 7 TypePong FrameType = 8 // TypeBye (hub→forwarder, channel 0) ends the session and says whether to // reconnect. TypeBye FrameType = 9 // TypeError (both, any channel) reports a non-fatal problem. On channel 0 // it is about the session; on a positive channel it is about that stream. TypeError FrameType = 10 )
type HelloPayload ¶
type HelloPayload struct {
Version int `json:"version"`
Device ForwardedDevice `json:"device"`
// AgentVersion is the forwarder build string, for the fleet view.
AgentVersion string `json:"agent_version,omitempty"`
// Available reports whether the forwarder can currently reach its device
// (the USB device is plugged in, the interface exists). A forwarder that
// connects with its device missing is still useful to see in the UI, so
// this is advisory, not a refusal.
Available bool `json:"available"`
// Detail explains a false Available, for the operator.
Detail string `json:"detail,omitempty"`
// Resolved, for a USB device, is what the forwarder found the configured
// identity resolving to on its host right now — the concrete device, with
// its current bus/port, so the dashboard can show the real hardware.
Resolved *executor.USBDevice `json:"resolved,omitempty"`
}
HelloPayload is the forwarder's opening frame.
type Hub ¶
type Hub struct {
// contains filtered or unexported fields
}
Hub accepts forwarder connections and tracks the sessions they back.
func (*Hub) DropSession ¶
DropSession ends a forwarder's live session without touching storage.
It exists for the caller that persists a revocation against the control-plane database directly — the source of truth a cluster shares — and then wants the live connection this member happens to hold cut, rather than routing the whole revocation through this hub's own store. It is a no-op when this member holds no session for the forwarder.
func (*Hub) OpenDevice ¶
OpenDevice opens a fresh stream to a connected forwarder's device (Task 20430). It is the one way a consumer reaches a forwarded device: the hub hands the stream to an executor agent over the agent's own tunnel, or attaches it in-process for a sandbox on its own host. There is no listener.
func (*Hub) OpenStream ¶
func (h *Hub) OpenStream(ctx context.Context, forwarderID, purpose string) (io.ReadWriteCloser, error)
OpenStream opens a fresh stream to a connected forwarder's device. The caller splices it to a consumer and closes it when done.
func (*Hub) Resolved ¶
func (h *Hub) Resolved(forwarderID string) *HelloPayload
Resolved returns what a connected forwarder reported its USB identity resolving to, for the dashboard. Nil when offline or not a USB device.
type HubOptions ¶
type HubOptions struct {
// Store is the enrollment/credential persistence. Required.
Store Store
// OnConnect is called after every successful handshake, with the record
// and what the forwarder advertised, so the caller can refresh a row.
OnConnect func(rec ForwarderRecord, hello HelloPayload)
// OnEnroll is called once, when a token is first redeemed.
OnEnroll func(rec ForwarderRecord)
// OnStatusChange mirrors connectivity into durable storage.
OnStatusChange func(forwarderID, status string, at time.Time)
// CheckOrigin decides whether a browser Origin may open the socket. Nil
// accepts same-origin and no-Origin (an agent sets no Origin). The caller
// passes the hub's own origin policy so the two endpoints judge alike.
CheckOrigin func(*http.Request) (bool, string)
// RevocationRecheck is how often a live session re-reads its forwarder's
// record and ends itself if the credential was revoked meanwhile (Task
// 20430). It is the backstop under the immediate cut a revocation makes on
// the member holding the session: a revocation recorded by a process that
// could not reach this one — the CLI, another hub member — still ends the
// session within this interval. Zero means DefaultRevocationRecheck.
RevocationRecheck time.Duration
// Logf receives operational messages. Now overrides the clock.
Logf func(format string, args ...any)
Now func() time.Time
}
HubOptions configures a Hub.
type ImportOptions ¶
type ImportOptions struct {
// VHCI is the controller to attach to. Zero means the host's first.
VHCI VHCI
// SysfsRoot and DevRoot locate /sys and /dev, for resolving the device
// once it has enumerated. Empty means the host's.
SysfsRoot string
DevRoot string
// Ledger, when set, records the attachment so a successor process can
// sweep it if this one dies holding it.
Ledger *Ledger
// EnumerateTimeout bounds the wait for the kernel to enumerate the device
// after the attach. Zero means 15 seconds.
EnumerateTimeout time.Duration
// Logf receives operational messages.
Logf func(string, ...any)
}
ImportOptions configures an import, with seams for tests.
type L2Source ¶
type L2Source struct {
// contains filtered or unexported fields
}
L2Source forwards one network interface's frames.
func NewL2Source ¶
func NewL2Source(iface string, opts L2SourceOptions) (*L2Source, error)
NewL2Source builds an L2 source for an interface name.
func (*L2Source) Device ¶
func (s *L2Source) Device() ForwardedDevice
Device reports the descriptor this source serves.
type L2SourceOptions ¶
L2SourceOptions configures an L2 source.
type Ledger ¶
type Ledger struct {
// contains filtered or unexported fields
}
Ledger is this process's record of its live attachments.
func OpenLedger ¶
OpenLedger opens this process's ledger in dir, creating dir 0700.
func (*Ledger) Add ¶
func (l *Ledger) Add(e LedgerEntry) string
Add records an attachment and returns its ledger id.
func (*Ledger) Entries ¶
func (l *Ledger) Entries() []LedgerEntry
Entries returns the live entries, for diagnostics and tests.
type LedgerEntry ¶
type LedgerEntry struct {
ID string `json:"id"`
Kind LedgerKind `json:"kind"`
VHCIRoot string `json:"vhci_root,omitempty"`
VHCIPort int `json:"vhci_port,omitempty"`
DevID uint32 `json:"dev_id,omitempty"`
TAP string `json:"tap,omitempty"`
At time.Time `json:"at"`
}
LedgerEntry is one attachment.
type LedgerKind ¶
type LedgerKind string
LedgerKind is what an entry records.
const ( // LedgerVHCI is a vhci_hcd port this process attached a device to. LedgerVHCI LedgerKind = "vhci" // LedgerTAP is a TAP interface this process created. LedgerTAP LedgerKind = "tap" )
type LocalSource ¶
type LocalSource interface {
// Device is the descriptor this source serves, for the hello.
Device() ForwardedDevice
// Probe reports whether the device is reachable right now. resolved, for a
// USB source, is the concrete device the configured identity matched, so
// the dashboard can show the real hardware. A false ok is advisory: the
// forwarder still connects so the operator can see it, and refuses Opens
// until the device is present.
Probe(ctx context.Context) (ok bool, detail string, resolved *executor.USBDevice)
// Open opens a fresh byte stream to the device — a connection to the local
// usbip server for a USB device, a raw interface handle for L2. The caller
// closes it when the stream ends.
Open(ctx context.Context) (io.ReadWriteCloser, error)
// Close releases anything held across the forwarder's whole life, such as
// a spawned usbip daemon. Called once at shutdown.
Close() error
}
LocalSource is the on-prem endpoint of a forwarded device.
type MintOptions ¶
type MintOptions struct {
Name string
Device ForwardedDevice
TTL time.Duration
Owner string
// CreatedBy identifies the minting operator (OIDC subject, or "" local).
CreatedBy string
// Server and Pin go into the bundle the operator carries to the device;
// they are not persisted.
Server string
Pin string
Now func() time.Time
}
MintOptions parameterises token creation.
type OpenAckPayload ¶
type OpenAckPayload struct {
OK bool `json:"ok"`
Error string `json:"error,omitempty"`
// Device describes what the forwarder opened (Task 20430): for USB the
// on-prem bus id a consumer's OP_REQ_IMPORT must name. Additive, so an
// older forwarder that omits it still interoperates; the hub then falls
// back to the bus id the forwarder's hello resolved.
Device *ExportedDevice `json:"device,omitempty"`
}
OpenAckPayload reports the outcome of an Open.
type OpenPayload ¶
type OpenPayload struct {
// Purpose is a short free-form note for the forwarder's log and the audit
// trail: "attach", "devlist". Advisory only.
Purpose string `json:"purpose,omitempty"`
}
OpenPayload asks the forwarder to open a stream to its device.
type ProbeResult ¶
type ProbeResult struct {
BusID string `json:"bus_id"`
VendorID string `json:"vendor_id"`
ProductID string `json:"product_id"`
Speed uint32 `json:"speed"`
Interfaces int `json:"interfaces"`
}
ProbeResult is what a USB probe read through a forwarder's stream.
func ProbeUSB ¶
func ProbeUSB(ctx context.Context, stream io.ReadWriteCloser) (ProbeResult, error)
ProbeUSB asks the forwarder behind stream for its device list and closes the stream (Task 20430). It proves the whole path — hub, tunnel, forwarder, device — answers, without binding the device or handing it to any kernel.
type Store ¶
type Store interface {
PutEnrollment(rec EnrollmentRecord) error
GetEnrollment(id string) (EnrollmentRecord, error)
RedeemEnrollment(id, forwarderID string, at time.Time) error
RevokeEnrollment(id string, at time.Time) error
ListEnrollments() ([]EnrollmentRecord, error)
PutForwarder(rec ForwarderRecord) error
GetForwarder(forwarderID string) (ForwarderRecord, error)
RevokeForwarder(forwarderID string, at time.Time) error
TouchForwarder(forwarderID string, at time.Time) error
ListForwarders() ([]ForwarderRecord, error)
}
Store is the persistence the enrollment flow needs. It is an interface so the security-relevant logic can be tested against an in-memory fake, including the concurrent-redemption race. RedeemEnrollment must perform the "still unredeemed?" check and the write atomically.
type SweepReport ¶
type SweepReport struct {
// Detached are the vhci ports detached, as "port N".
Detached []string
// Removed are the TAP interfaces removed.
Removed []string
// Errors are the entries that could not be undone.
Errors []string
}
SweepReport says what a sweep found and undid.
func SweepLedgers ¶
func SweepLedgers(dir string, logf func(string, ...any)) SweepReport
SweepLedgers undoes the attachments recorded in dir by processes that are no longer running, and removes their ledgers. A live process's ledger — this one's, or another member's on the same host — is left alone.
type TAPBridge ¶
type TAPBridge struct {
// contains filtered or unexported fields
}
TAPBridge is a running L2 bridge: a TAP interface fed by a forwarder stream.
func BridgeTAP ¶
func BridgeTAP(stream io.ReadWriteCloser, tapName string) (*TAPBridge, error)
BridgeTAP creates a TAP interface and bridges it to an open stream to an L2 forwarder's device. The TAP is created down and unaddressed; the caller brings it up — or hands it to a sandbox, whose passthrough does. The bridge ends when the stream does, or when the caller closes it, which removes the TAP.
type USBImport ¶
type USBImport struct {
// Port is the vhci port the device is attached to, and DevID the remote
// device id the kernel recorded for it.
Port int
DevID uint32
// Remote is what the forwarder exported.
Remote usbipDevice
// Device is the device as it enumerated on this host.
Device executor.USBDevice
// contains filtered or unexported fields
}
USBImport is a forwarded USB device attached to this host's vhci_hcd.
func ImportUSB ¶
func ImportUSB(ctx context.Context, stream io.ReadWriteCloser, busid string, opts ImportOptions) (_ *USBImport, err error)
ImportUSB imports the device behind stream onto this host. busid is the forwarder's bus id for it, which OP_REQ_IMPORT must name. On success the device has enumerated on this host and u.Device describes it; the stream is owned by the import from then on and closed with it. On failure the stream is closed too.
func (*USBImport) Close ¶
Close ends the attachment: the relay stops, the port is detached if it still holds this device, and the stream is closed. Idempotent; safe to call from any goroutine.
func (*USBImport) Done ¶
func (u *USBImport) Done() <-chan struct{}
Done is closed once the attachment has ended, for any reason.
func (*USBImport) HostDevices ¶
func (u *USBImport) HostDevices(name string) []executor.HostDevice
HostDevices are the device nodes a sandbox is given for this device: the usbfs node and its interface nodes, named after name.
type USBSource ¶
type USBSource struct {
// contains filtered or unexported fields
}
USBSource forwards one USB device.
func NewUSBSource ¶
func NewUSBSource(match executor.USBMatch, opts USBSourceOptions) (*USBSource, error)
NewUSBSource builds a USB source for a device identity.
func (*USBSource) Device ¶
func (s *USBSource) Device() ForwardedDevice
Device reports the descriptor this source serves.
func (*USBSource) Open ¶
Open resolves the device and returns a stream that answers the consumer's opening USB/IP request. Nothing is bound yet: a device list is answered from sysfs and binds nothing, and only an accepted import binds the device to usbip-host and hands the connection to the kernel (Task 20430). A probe therefore never takes the device away from the host it is plugged into.
type USBSourceOptions ¶
type USBSourceOptions struct {
// SysfsRoot defaults to /sys.
SysfsRoot string
// UsbipBinary is the usbip(8) tool used to bind/unbind the device to the
// usbip-host driver. Defaults to "usbip" resolved on PATH.
UsbipBinary string
// Logf receives operational messages.
Logf func(string, ...any)
}
USBSourceOptions configures a USB source, with seams for tests.
type VHCI ¶
type VHCI struct {
// Root is the platform-device directory holding attach, detach and
// status. Empty means DefaultVHCIRoot.
Root string
}
VHCI is one host's virtual host controller.
type WelcomePayload ¶
type WelcomePayload struct {
Version int `json:"version"`
// ForwarderID is the hub's durable ID for this forwarder, echoed so the
// device can log which identity it is serving as.
ForwarderID string `json:"forwarder_id"`
// Credential is the long-lived forwarder credential, non-empty only on the
// connection that redeemed an enrollment token. The forwarder persists it
// and reconnects with it; it is shown once and never again.
Credential string `json:"credential,omitempty"`
}
WelcomePayload is the hub's acceptance.