Documentation
¶
Overview ¶
Package room lets two or more copies of an app find each other with no account, and then talk directly over WebRTC data channels.
Signaling (presence, offers and answers) is sealed with a key derived from a shared secret and goes over one or more carriers: the local network (OpenLAN), a hub on one of the app's own machines (NewHub, DialHub, with NewTURN and package portmap beside it), or public MQTT brokers (package mqtt). Every carrier sees only random bytes on a random-looking topic. Once two members have found each other, Mesh opens a pion PeerConnection between them, binds it to each side's ed25519 identity with a signed-fingerprint handshake, and hands the app one reliable ordered message channel plus on-demand byte streams.
It was extracted from Toru's rooms (internal/stream/room.go and ice.go).
Index ¶
- Constants
- Variables
- func CleanICEURLs(in []string) []string
- func Decode[T any](env Envelope) (T, error)
- func LoopbackOnly() func(*webrtc.SettingEngine)
- func NewCode() string
- func NormalizeCode(in string) (string, bool)
- func OnPublicNetwork(ip netip.Addr) bool
- func SplitTCP(ln net.Listener) (hub, turn net.Listener)
- func WebRTCServers(list []ICEServer) []webrtc.ICEServer
- type ByeError
- type Carrier
- type Channel
- type Config
- type Envelope
- type Hub
- func (h *Hub) Close()
- func (h *Hub) CloseGuest(topic string)
- func (h *Hub) Drop(id string)
- func (h *Hub) Local() Carrier
- func (h *Hub) OpenGuest(topic string, ttl time.Duration) error
- func (h *Hub) OpenInvite(topic string, ttl time.Duration) error
- func (h *Hub) Peers() []HubPeer
- func (h *Hub) Recheck()
- func (h *Hub) Serve(ln net.Listener) error
- type HubClient
- func (c *HubClient) Addr() string
- func (c *HubClient) Carrier() Carrier
- func (c *HubClient) Close()
- func (c *HubClient) CloseGuest(topic string)
- func (c *HubClient) OpenGuest(ctx context.Context, topic string, ttl time.Duration) error
- func (c *HubClient) OpenInvite(ctx context.Context, topic string, ttl time.Duration) error
- func (c *HubClient) Server() (id string, pub ed25519.PublicKey)
- func (c *HubClient) TURN(ctx context.Context) (TURNCreds, error)
- func (c *HubClient) Up() bool
- type HubConfig
- type HubDialConfig
- type HubLimits
- type HubPeer
- type ICEServer
- type Identity
- type LAN
- type LANConfig
- type Leg
- type LocalCloseError
- type LocalSubnet
- type MemHub
- type MemLAN
- type Mesh
- type Peer
- func (p *Peer) Close(reason string)
- func (p *Peer) Done() <-chan struct{}
- func (p *Peer) Err() error
- func (p *Peer) Fingerprints() (local, remote string)
- func (p *Peer) ID() string
- func (p *Peer) Messages() *Channel
- func (p *Peer) OnStream(fn func(label string, s *Stream))
- func (p *Peer) OpenStream(ctx context.Context, label string) (*Stream, error)
- func (p *Peer) Path() string
- func (p *Peer) Pub() ed25519.PublicKey
- func (p *Peer) RTT() time.Duration
- func (p *Peer) Session() string
- type Relay
- type RelayConfig
- type RelayStatus
- type Secret
- type SignalOptions
- type Signaler
- type Stream
- type TURNConfig
- type TURNCreds
- type TURNServer
Constants ¶
const ( RelayNone = "none" // no relay set up: STUN only RelayPending = "pending" // fetching the first credentials RelayOK = "ok" // TURN is in the list RelayError = "error" // credentials couldn't be fetched (Detail says why) )
Relay states, for a "Direct only" note and diagnostics.
const ( MaxLANDatagram = 8 << 10 // bytes, header included DefaultLANRate = 200 // datagrams opened per second )
LAN limits and defaults.
const ( MsgWant = "rtc.want" // to the offerer: please offer to me MsgOffer = "rtc.offer" // offerer → answerer: session, sdp MsgAnswer = "rtc.answer" // answerer → offerer: session, sdp MsgBye = "rtc.bye" // either way: session, reason )
Envelope types Mesh sends and owns.
const ( DefaultMaxMessage = 4 << 20 DefaultMaxStreams = 4 DefaultAuthTimeout = 10 * time.Second DefaultConnectTimeout = 30 * time.Second )
Defaults for Config's zero values.
const ( DefaultTURNTTL = 12 * time.Hour DefaultTURNRealm = "relay" DefaultMaxPerUser = 8 DefaultMaxTotal = 32 DefaultMaxGuests = 4 // allocations across every guest DefaultMaxBoardUser = 4 // allocations per other person (a board user) DefaultMaxTCP = 64 // TURN-over-TCP connections DefaultMaxTCPPerIP = 8 )
TURN defaults.
const CodeLen = 16
CodeLen is the number of symbols in a room code: 16 of 31 is about 79 bits.
const DefaultMaxAge = 2 * time.Minute
DefaultMaxAge drops anything older (or further ahead) than this: a carrier replaying a backlog after a reconnect, or a clock far off.
const GuestInvite = "invite"
GuestInvite is hubGuestBody.Kind for an invite guest topic.
const HubALPN = "go-room-hub/1"
HubALPN is the hub's TLS application protocol.
const MaxGuestTTL = 10 * time.Minute
MaxGuestTTL caps how long a pairing guest topic stays open.
const MaxHubFrame = 64 << 10
MaxHubFrame caps one frame, type byte included.
const MaxInviteTTL = time.Hour
MaxInviteTTL caps how long an invite guest topic stays open: long enough for a voice call.
const RTCPrefix = "rtc."
RTCPrefix starts the envelope types Mesh owns ("rtc.want", "rtc.offer", "rtc.answer", "rtc.bye"). They never reach SignalOptions.OnMsg.
const SyncLabel = "sync"
SyncLabel is the label of every peer's message channel (pre-negotiated, stream id 0). OpenStream refuses it.
Variables ¶
var DefaultSTUN = []ICEServer{ {URLs: []string{"stun:stun.l.google.com:19302", "stun:stun1.l.google.com:19302"}}, {URLs: []string{"stun:stun.cloudflare.com:3478"}}, }
DefaultSTUN are public STUN servers (no account) for finding each machine's public address. Data never passes through them.
var ErrBadCode = errors.New("that isn't a room code (16 letters and numbers, like ABCD-EFGH-JKMN-PQRS)")
ErrBadCode is returned for a string that isn't a room code.
var ErrClosed = errors.New("room: closed")
ErrClosed is returned by anything used after Close.
var ErrNotConnected = errors.New("not connected to the room yet")
ErrNotConnected is Send's error while no carrier is up.
var ErrRefused = errors.New("room: refused by peer")
ErrRefused means the other side's Accept turned this side down.
var ErrReplaced = errors.New("room: replaced by a new connection")
ErrReplaced is why an up peer went down (OnPeerDown) when a newer connection to the same member came up in its place: OnPeer for the new one follows. Mesh doesn't reconnect after it; there is nothing to fix.
var ErrTooBig = errors.New("room: message too big")
ErrTooBig is returned for a message over the channel's cap.
var ErrTooBigDatagram = errors.New("room: envelope too big for a LAN datagram")
ErrTooBigDatagram is Publish's error for a payload that doesn't fit one LAN datagram.
Functions ¶
func CleanICEURLs ¶
CleanICEURLs keeps the well-formed stun:, turn: and turns: entries.
func LoopbackOnly ¶
func LoopbackOnly() func(*webrtc.SettingEngine)
LoopbackOnly keeps pion on 127.0.0.1: UDP4 only, loopback candidates included, every other address filtered out (no LAN host candidates, so a fresh test binary raises no Windows Firewall prompt). Pair it with an ICE list that returns nil, so no STUN gathering socket is opened.
func NewCode ¶
func NewCode() string
NewCode returns a fresh random room code, formatted XXXX-XXXX-XXXX-XXXX.
func NormalizeCode ¶
NormalizeCode accepts a code typed or pasted any way (lower case, spaces, missing dashes) and returns its canonical form.
func OnPublicNetwork ¶ added in v0.2.0
OnPublicNetwork reports whether ip is this machine's address on a network Windows has on the Public firewall profile (always false on other systems), where the LAN carrier stays silent and Windows Firewall blocks what a server listens for.
func SplitTCP ¶ added in v0.2.0
SplitTCP shares one TCP listener between the hub and TURN over TCP, by each connection's first byte: 0x16 (a TLS handshake record) goes to the hub, 0x00 or 0x01 (a STUN message) to TURN, anything else is closed. TURN ChannelData only ever follows on a connection TURN already has.
Each side is closed on its own; ln closes once both are.
func WebRTCServers ¶
WebRTCServers converts a list to the shape pion takes.
Types ¶
type ByeError ¶
type ByeError struct{ Reason string }
ByeError is why a peer went down when the other side closed it.
type Carrier ¶ added in v0.2.0
type Carrier interface {
// Join starts carrying topic. deliver gets every payload that arrives
// for it, with the address it came from when the carrier knows one (the
// zero AddrPort otherwise); up runs each time the leg (re)connects.
// Neither may block for long.
Join(topic string, deliver func(payload []byte, from netip.AddrPort), up func()) (Leg, error)
}
A Carrier moves sealed payloads for topics. It never holds a key: Open seals, opens, filters and de-duplicates, so every carrier gets the same checks. A Carrier may serve many rooms at once.
type Channel ¶
type Channel struct {
// contains filtered or unexported fields
}
Channel is a peer's one reliable, ordered message channel ("sync"). Each message is sent as a 4-byte big-endian length, then the bytes, cut into SCTP messages of at most 16 KiB and joined again on arrival.
type Config ¶
type Config struct {
// Self is this member's id; it must match the Signaler's.
Self string
// Identity signs this side of each connection's auth handshake.
Identity Identity
// Accept is the app's decision on a peer, called once both auth
// signatures verified: pub is the key the peer proved it holds. A nil
// return lets the peer up. Required: during a pairing it may accept any
// key (and record it); afterwards it should check pub against what it
// knows about peer.
Accept func(peer string, pub ed25519.PublicKey) error
// Allow is asked before any PeerConnection is made for peer, for a local
// Connect, an incoming offer or an incoming want. nil allows everyone in
// the room.
Allow func(peer string) bool
// Offerer reports whether this side makes the offer to peer. Both sides
// must agree. nil: the lower id offers.
Offerer func(peer string) bool
// ICE is read for each new connection (Relay.Servers). nil means
// DefaultSTUN; a func returning nil means no servers (loopback tests).
ICE func() []ICEServer
// Tune adjusts the pion SettingEngine after go-room's own settings
// (tests: LoopbackOnly()).
Tune func(*webrtc.SettingEngine)
// RelayOnly makes every connection go through a TURN relay from ICE
// (ICE transport policy "relay"): tests that prove a relayed path, or
// an app that must not reveal its addresses to peers.
RelayOnly bool
// MaxMessage caps one Channel message, both ways (default 4 MiB).
MaxMessage int
// MaxStreams caps the streams this side opens to one peer at a time
// (default 4): OpenStream waits for a free slot. Incoming streams may run
// to twice this, because the opener frees a slot on its own Close while
// this side frees it only when its handler closes the stream; past that,
// an incoming stream is closed at once (the opener reads io.EOF). Both
// sides should use the same value.
MaxStreams int
// AuthTimeout bounds the handshake once the data channel is open
// (default 10 s); ConnectTimeout bounds a whole attempt (default 30 s).
AuthTimeout time.Duration
ConnectTimeout time.Duration
// Logf, when set, gets diagnostics (never secrets).
Logf func(format string, args ...any)
}
Config sets up a Mesh.
type Envelope ¶
type Envelope struct {
V int `json:"v"`
ID string `json:"id"`
From string `json:"from"`
To string `json:"to,omitempty"`
Type string `json:"type"`
At int64 `json:"at"` // unix ms
Body json.RawMessage `json:"body,omitempty"`
}
Envelope is one signaling message. To == "" is for everyone. The JSON shape is Toru's, so Toru can move onto go-room without a wire change.
type Hub ¶ added in v0.2.0
type Hub struct {
// contains filtered or unexported fields
}
Hub is a running signaling hub.
func (*Hub) Close ¶ added in v0.2.0
func (h *Hub) Close()
Close stops every listener and connection and closes guest topics.
func (*Hub) CloseGuest ¶ added in v0.2.0
CloseGuest closes a guest topic and drops its guests; members still subscribed keep it only if AllowTopic allows it on its own.
func (*Hub) Drop ¶ added in v0.2.0
Drop closes every connection of member id and its guest topics (a revoke). Authorize must refuse id from now on, or it reconnects.
func (*Hub) Local ¶ added in v0.2.0
Local is the hub as a Carrier for the server's own rooms, in process: they are subscribers like any connection, without TLS. By default the topics they join are the ones members may subscribe to.
func (*Hub) OpenGuest ¶ added in v0.2.0
OpenGuest opens a guest topic for the server's own pairing window: guests naming it in their hello are admitted until ttl (at most MaxGuestTTL) or CloseGuest.
func (*Hub) OpenInvite ¶ added in v0.3.0
OpenInvite opens an invite guest topic for the server's own invite window (a shared board's invite): as OpenGuest, for at most MaxInviteTTL.
func (*Hub) Recheck ¶ added in v0.3.0
func (h *Hub) Recheck()
Recheck applies AllowTopic again to every connection: a subscription it no longer allows ends, and one asked for that it now allows starts. Call it after the state AllowTopic reads changes (a board's members or epoch). To drop another person's devices entirely, Drop each of them (Peers lists them with their Person).
type HubClient ¶ added in v0.2.0
type HubClient struct {
// contains filtered or unexported fields
}
HubClient is a connection to a hub that reconnects by itself, 1 s, 2 s, 4 s ... up to 60 s apart.
func DialHub ¶ added in v0.2.0
func DialHub(cfg HubDialConfig) *HubClient
DialHub starts connecting to the hub in the background.
func (*HubClient) Addr ¶ added in v0.2.0
Addr is the address that completed the handshake ("" while down).
func (*HubClient) Close ¶ added in v0.2.0
func (c *HubClient) Close()
Close disconnects and stops reconnecting.
func (*HubClient) CloseGuest ¶ added in v0.2.0
CloseGuest closes a guest topic this member opened.
func (*HubClient) OpenGuest ¶ added in v0.2.0
OpenGuest asks the hub to admit guests to topic (a pairing room) for ttl (at most MaxGuestTTL). It waits for the connection up to ctx, and is sent again on every reconnect until ttl runs out or CloseGuest.
func (*HubClient) OpenInvite ¶ added in v0.3.0
OpenInvite is OpenGuest for an invite window (a shared board's invite), open for ttl (at most MaxInviteTTL). A hub older than go-room v0.3.0 treats it as a pairing window and closes it after MaxGuestTTL.
func (*HubClient) Server ¶ added in v0.2.0
Server is the hub's device id and key from its last welcome.
type HubConfig ¶ added in v0.2.0
type HubConfig struct {
// Self is the server device's id; Identity signs its welcome.
Self string
Identity Identity
// Authorize decides on a member that proved it holds pub's private key;
// nil error admits it. Required.
Authorize func(id string, pub ed25519.PublicKey) error
// AuthorizeBoard, when set, is asked about a device Authorize refused:
// another person's device that is a member of a board this server
// serves. It returns the person the device belongs to (a nil error
// admits it as a board peer, HubPeer.Person set). It runs outside the
// hub's lock.
AuthorizeBoard func(id string, pub ed25519.PublicKey) (person string, err error)
// AllowTopic decides a member's subscriptions other than open guest
// topics, and every subscription of a board peer (c.Person set). nil:
// a member gets only topics the server's own rooms (Local) have open,
// and a board peer gets nothing. It runs with the hub's lock held:
// answer from the app's own state and don't call back into the hub.
// After the state it reads changes, call Recheck.
AllowTopic func(c HubPeer, topic string) bool
// TURN, when set, answers turn.get: members get Issue(id), guests
// IssueGuest until their topic closes.
TURN *TURNServer
// PublicUDP, when set, is the public address of TURN's UDP port as the
// router mapped it; it rides in turn.creds so a device that dialed the
// public TCP address finds UDP when the router gave it another port.
PublicUDP func() netip.AddrPort
// Bridge, when set, carries open guest topics between this LAN and the
// hub both ways, so a device at home with only a pairing code reaches a
// member away from home.
Bridge *LAN
// Limits; the zero value is the defaults.
Limits HubLimits
// Logf, when set, gets diagnostics (never payloads or credentials).
Logf func(format string, args ...any)
}
HubConfig sets up a hub.
type HubDialConfig ¶ added in v0.2.0
type HubDialConfig struct {
// Self is this member's device id (unused for a guest).
Self string
// Identity signs the hello; nil makes this a guest.
Identity *Identity
// GuestTopic is a guest's one topic (its pairing room).
GuestTopic string
// Addrs lists host:port addresses for the hub. They are dialed in
// parallel and the first to complete the handshake wins; it is read
// again on every reconnect.
Addrs func() []string
// Expect checks the hub's welcome: the server device's id and the key
// it proved. Required for members (Irori: a certified, unrevoked
// device); optional for a guest, which records the key instead.
Expect func(id string, pub ed25519.PublicKey) error
// Dial opens the TCP connection (default net.Dialer; tests: 127.0.0.1).
Dial func(ctx context.Context, addr string) (net.Conn, error)
// TURNURIs gives the ICE URLs for TURN credentials from the hub reached
// at addr. nil: stun:, turn:?transport=udp and turn:?transport=tcp at
// addr itself, since a home server's TURN shares the hub's port number.
TURNURIs func(addr string) []string
// OnUp, when set, runs (on its own goroutine) each time the connection
// comes up: Irori kicks its TURN credential loop there, so relay
// credentials don't wait out a backoff that ran while the hub was down.
OnUp func()
// Logf, when set, gets diagnostics (never payloads or credentials).
Logf func(format string, args ...any)
}
HubDialConfig sets up a connection to a hub.
type HubLimits ¶ added in v0.2.0
type HubLimits struct {
Members int // member connections [64]
Guests int // guest connections [8]
HandshakesPerIP int // handshakes in flight per remote IP (an IPv6 /64 is one) [4]
Handshakes int // handshakes in flight in total [64]
TopicsPerConn int // subscriptions per connection [8]
GuestsPerMember int // pairing guest topics one member may have open [2]
InvitesPerMember int // invite guest topics one member may have open, beside those [2]
BoardPeers int // board peer connections [32]
PubRate float64 // pub frames per second per connection [50]
PubBurst float64 // [100]
FailBackoff time.Duration // refuse an IP this long after a failed handshake, doubling to 30 s [1 s]; negative: off
}
HubLimits bound what a hub on someone's desktop will take. Zero values are the defaults in brackets.
type HubPeer ¶ added in v0.2.0
type HubPeer struct {
ID string // the member's device id ("" for a guest)
Pub ed25519.PublicKey
Guest bool
Remote netip.AddrPort
// Person is set on a board peer: the person (as AuthorizeBoard named
// it) whose device this is. "" for the server's own devices and guests.
Person string
}
HubPeer is one connected client as the hub sees it.
type ICEServer ¶
type ICEServer struct {
URLs []string `json:"urls"`
Username string `json:"username,omitempty"`
Credential string `json:"credential,omitempty"`
}
ICEServer is one STUN or TURN server, in the shape RTCPeerConnection takes.
type Identity ¶
Identity is this member's long-term ed25519 key, as a public key and a signing function, so the private key can stay wherever the app keeps it.
func KeyIdentity ¶
func KeyIdentity(priv ed25519.PrivateKey) Identity
KeyIdentity is an Identity for a private key held in memory.
type LAN ¶ added in v0.2.0
type LAN struct {
// contains filtered or unexported fields
}
LAN is one UDP socket carrying every room opened on it.
func (*LAN) Close ¶ added in v0.2.0
func (l *LAN) Close()
Close stops reading and closes the socket and every leg.
type LANConfig ¶ added in v0.2.0
type LANConfig struct {
// Port is the fixed UDP port every device uses (Irori: 38419).
Port int
// Conn, when set, is used instead of binding the wildcard address on
// Port (tests: a MemLAN conn or a 127.0.0.1 socket). The LAN closes it.
Conn net.PacketConn
// Targets is where envelopes for everyone go. nil: the directed
// broadcast address of each up private IPv4 interface on Port, skipping
// networks on the Public firewall profile on Windows.
Targets func() []netip.AddrPort
// MaxRate caps datagrams opened per second (default DefaultLANRate); the
// rest are dropped unopened.
MaxRate int
// Now is the clock for the tag window (tests).
Now func() time.Time
// Logf, when set, gets diagnostics (never payloads).
Logf func(format string, args ...any)
}
LANConfig sets up the LAN carrier.
type Leg ¶ added in v0.2.0
type Leg interface {
// Publish sends one sealed payload. to is the member it is for ("" =
// everyone); a carrier may ignore it (a hub or a broker sends to every
// subscriber), and the LAN carrier unicasts when it knows the address.
Publish(to string, payload []byte) error
// Seen tells the leg that a payload it delivered from addr opened as an
// authentic envelope from member.
Seen(member string, from netip.AddrPort)
// Up reports whether the leg can publish right now.
Up() bool
// Close stops carrying the topic.
Close()
}
A Leg is one topic on one Carrier.
type LocalCloseError ¶
type LocalCloseError struct{ Reason string }
LocalCloseError is why a peer went down when this side closed it.
func (*LocalCloseError) Error ¶
func (e *LocalCloseError) Error() string
type LocalSubnet ¶ added in v0.2.0
type LocalSubnet struct {
Index int // interface index
Prefix netip.Prefix // address and mask, e.g. 192.168.1.20/24
}
LocalSubnet is one up IPv4 interface address with a private address.
func LocalSubnets ¶ added in v0.2.0
func LocalSubnets() []LocalSubnet
LocalSubnets lists the up, non-loopback interfaces' private IPv4 addresses (RFC 1918). The LAN carrier broadcasts to these; the TURN server's default peer filter allows them.
type MemHub ¶
type MemHub struct {
// Tap, when set, sees every payload published (topic, sealed bytes):
// tests use it to check what a carrier would see.
Tap func(topic string, payload []byte)
// contains filtered or unexported fields
}
MemHub is an in-process stand-in for a hub or broker, for tests: every Signaler opened on it publishes sealed bytes to the hub, and the hub hands them to every other leg subscribed to the same topic, each in order on its own goroutine. Sealing, filtering, de-duplication and the age check are the same code every carrier gets; only the transport differs.
func (*MemHub) Carrier ¶ added in v0.2.0
Carrier returns the hub as a Carrier, to open beside others.
type MemLAN ¶ added in v0.2.0
type MemLAN struct {
// Drop, when set, is asked about every datagram (from, to); true loses
// it, as a lossy network would.
Drop func(from, to netip.AddrPort) bool
// contains filtered or unexported fields
}
MemLAN is an in-process broadcast domain for tests: each Conn is a net.PacketConn with an address of your choosing, a datagram to one Conn's address reaches that Conn, and one to Broadcast() reaches every other. There are no sockets.
func NewMemLAN ¶ added in v0.2.0
func NewMemLAN() *MemLAN
NewMemLAN returns an empty broadcast domain.
type Mesh ¶
type Mesh struct {
// contains filtered or unexported fields
}
Mesh makes and keeps authenticated connections to other members of one room. It owns the rtc.* envelopes on its Signaler; use one Mesh per Signaler. Closing the Mesh doesn't close the Signaler.
func (*Mesh) Close ¶
func (m *Mesh) Close()
Close closes every connection (telling each peer) and stops handling the signaler's rtc.* envelopes. It waits for pion to finish closing, and for queued OnPeer/OnPeerDown callbacks to run; don't call it from one.
func (*Mesh) Connect ¶
Connect makes sure there is (or soon will be) a connection to peer: it offers if this side is the offerer, and asks peer to offer (rtc.want) otherwise. It is idempotent while an attempt is running or the peer is up, and after a failed attempt it waits out a backoff (1 s doubling to 60 s) before offering again. Call it whenever peer is seen in the room.
func (*Mesh) OnPeer ¶
OnPeer sets the handler for a peer coming up: both auth signatures verified and Accept passed on both sides. Set it before Connect.
func (*Mesh) OnPeerDown ¶
OnPeerDown sets the handler for an up peer going away, with why. After a drop that neither side asked for (the connection failed), Mesh tries to reconnect on its own for about a minute; after a Close, a bye or a refusal it doesn't. Apps still call Connect when they see the peer again. When a newer connection replaces an up one, OnPeerDown for the old *Peer (ErrReplaced) comes just before OnPeer for the new one.
type Peer ¶
type Peer struct {
// contains filtered or unexported fields
}
Peer is one authenticated connection to another member.
func (*Peer) Close ¶
Close closes the connection and every stream on it, and tells the peer why with an rtc.bye.
func (*Peer) Fingerprints ¶
Fingerprints are the SHA-256 fingerprints of this side's and the peer's DTLS certificates, as the connected transport saw them (not the SDP), in lower-case colon hex. Both sides see the same pair, swapped; a pairing SAS over both catches a man in the middle.
func (*Peer) OnStream ¶
OnStream sets the handler for streams the peer opens. Streams that arrived before it was set are handed to it at once. Without a handler, streams wait (up to MaxStreams) until one is set.
func (*Peer) OpenStream ¶
OpenStream opens a new byte stream to the peer. It waits for one of the MaxStreams slots, then for the channel to open.
func (*Peer) Path ¶
Path is "relay" when the selected ICE pair goes through TURN, "direct" otherwise ("" before a pair is selected).
type Relay ¶
type Relay struct {
// contains filtered or unexported fields
}
Relay keeps the ICE list: STUN always, TURN while credentials are good.
func NewRelay ¶
func NewRelay(cfg RelayConfig, peer string, onChange func()) *Relay
NewRelay reads the relay setup; peer is sent to the credential endpoint (the TURN REST username is usually "expiry:peer"). onChange, if set, runs after every fetch.
func (*Relay) Kick ¶ added in v0.2.0
func (r *Relay) Kick()
Kick fetches again now, with the backoff reset, unless the credentials in hand are good: call it when the source of credentials came back (a hub reconnected), so a backoff that grew while it was down isn't waited out.
func (*Relay) Servers ¶
Servers is the ICE list right now: STUN, then TURN while the credentials haven't expired. Pass it as Config.ICE.
func (*Relay) Start ¶
func (r *Relay) Start()
Start begins fetching and refreshing credentials; a no-op with no CredentialURL or Fetch. It never blocks.
type RelayConfig ¶
type RelayConfig struct {
// CredentialURL answers GET ?peer=<peer> with {username, credential (or
// password), ttl, uris}. It must be https, except on loopback.
CredentialURL string
// TurnURLs, when set, replace the uris the credentials come with.
TurnURLs []string
// Fetch, when set, replaces the HTTP fetch from CredentialURL: the loop
// calls it for fresh credentials (an app with a hub: HubClient.TURN).
Fetch func(ctx context.Context) (TURNCreds, error)
// Logf, when set, gets credential failures.
Logf func(format string, args ...any)
}
RelayConfig sets up an optional TURN relay with short-lived credentials (the TURN REST scheme).
type RelayStatus ¶
type RelayStatus struct {
State string `json:"state"`
Detail string `json:"detail,omitempty"`
Expires int64 `json:"expires,omitempty"` // unix ms the current credentials stop working
}
RelayStatus is how the TURN relay is doing.
type Secret ¶
Secret is what members of one room share: the key every envelope is sealed with, and the broker topic they meet on. Topic is not secret on its own (brokers see it), but it is derived from the same secret, so it names nobody. Callers may rewrite Topic's prefix (keeping the derived suffix) to stay on an existing wire format; Toru's is "toru/v1/".
func SecretFromCode ¶
SecretFromCode derives a room's secret from a room code (any spelling NormalizeCode accepts): scrypt(code, salt = app), as Toru does, so guessing codes from a captured topic or message is expensive on top of the ~79-bit code space. The first 32 bytes are the sealing key; the topic is app + "/" + hex(the next 16).
func SecretFromKey ¶
SecretFromKey derives a room's secret from a high-entropy key (32 random bytes or more), such as a secret every device of one person holds: HKDF-SHA256(ikm, salt = app, info), 48 bytes, split as in SecretFromCode. It is fast, so never feed it something a person typed: use SecretFromCode.
type SignalOptions ¶
type SignalOptions struct {
// MaxAge drops envelopes older or further ahead than this (default
// DefaultMaxAge).
MaxAge time.Duration
// OnMsg gets every envelope from another member, addressed to everyone
// or to self, that isn't one of Mesh's own rtc.* types. It runs on the
// signaler's receive goroutine; don't block it for long.
OnMsg func(Envelope)
// OnConnect fires whenever a carrier (re)connects, so callers can
// announce themselves.
OnConnect func()
// OnHeard, when set, gets the sender and source address of an
// authentic envelope that arrived with one (the LAN carrier): the first
// copy of each envelope that came with an address, even when another
// carrier delivered it first. How an app learns where a member is on the
// local network. It runs on the carrier's receive goroutine.
OnHeard func(from string, addr netip.AddrPort)
}
SignalOptions tune a Signaler. The zero value is Toru's behaviour.
type Signaler ¶
type Signaler interface {
// Send seals one envelope to member to ("" = everyone) and publishes it
// on every carrier.
Send(to, typ string, body any) error
// Connected reports how many carriers are up right now.
Connected() int
// Self is this member's id.
Self() string
// Topic is the room's topic (from its Secret).
Topic() string
// Close leaves the room.
Close()
// contains filtered or unexported methods
}
Signaler is a live connection to one room over one or more carriers: Open, MemHub.Open (tests) or mqtt.OpenSignal (public brokers). The unexported method keeps implementations inside go-room, because Mesh relies on how they seal, filter and route.
func Open ¶ added in v0.2.0
Open joins the room for s as member self over every carrier given. Each envelope is sealed once and published on all of them, and the first copy to arrive wins (the rest are dropped by id), so one carrier being down or slow changes nothing. It returns at once; carriers connect (and reconnect) in the background, and OnConnect fires on each.
type Stream ¶
type Stream struct {
// contains filtered or unexported fields
}
Stream is a byte stream over its own data channel, opened with Peer.OpenStream or received through Peer.OnStream. Writes go out in 16 KiB pieces under the same flow control as Channel. Close flushes what was written, then closes the channel; the other side's Read returns io.EOF. Close also unblocks a Write stuck on a reader that stopped reading (that Write returns ErrClosed, and Close then skips the flush). When the peer goes down, Read usually returns the peer's error instead, but a connection that dies at the wrong moment can still look like io.EOF: check lengths or hashes before trusting a stream as complete.
func (*Stream) Close ¶
Close flushes what was written (up to 30 s) and closes the stream. If it had to stop a blocked Write, it doesn't flush: the data is cut anyway.
type TURNConfig ¶ added in v0.2.0
type TURNConfig struct {
// UDP is the UDP socket (port N); STUN Binding is answered there too.
UDP net.PacketConn
// TCP, when set, takes TURN over TCP (SplitTCP's TURN side).
TCP net.Listener
// RelayIP is where relayed sockets bind: the server's LAN address
// (tests: 127.0.0.1). It must be IPv4.
RelayIP netip.Addr
// Key is the HMAC key credentials are made with; the app keeps it.
Key [32]byte
// Realm defaults to DefaultTURNRealm.
Realm string
// TTL is how long issued credentials last (default 12 h). A relayed
// connection lasts only as long as the credentials it was made with.
TTL time.Duration
// Authorize is asked on every authenticated request with the user from
// the username (a device id, or "board-<person>" for another person's
// devices, IssueBoard); non-nil refuses. Guest users are checked by the
// server itself. nil admits every user with valid credentials.
Authorize func(user string) error
// AllowPeer decides which peer addresses a user may relay to, on every
// permission and every packet out. nil: members to this host's private
// subnets and to relayed addresses on this server; guests only to
// relayed addresses. Loopback, link-local, multicast and public
// addresses are never in the default. Board users (IssueBoard) relay
// only to this server's relayed addresses whatever AllowPeer says.
AllowPeer func(user string, ip netip.Addr) bool
// MaxPerUser and MaxTotal cap allocations (defaults 8 and 32);
// MaxGuests caps the allocations of every guest together (default 4),
// so pairing guests never take the relay from members.
MaxPerUser, MaxTotal, MaxGuests int
// MaxBoardPerUser caps one other person's allocations, across all of
// their devices (default 4), inside MaxTotal.
MaxBoardPerUser int
// MaxTCP and MaxTCPPerIP cap TURN-over-TCP connections (defaults 64
// and 8; an IPv6 /64 counts as one IP).
MaxTCP, MaxTCPPerIP int
// Logf, when set, gets diagnostics (never credentials).
Logf func(format string, args ...any)
}
TURNConfig sets up a TURN server on sockets the app opened.
type TURNCreds ¶ added in v0.2.0
type TURNCreds struct {
Username string `json:"username"`
Credential string `json:"credential"`
TTL time.Duration `json:"-"`
URIs []string `json:"uris,omitempty"`
}
TURNCreds are one set of short-lived TURN credentials and the URLs they work on.
type TURNServer ¶ added in v0.2.0
type TURNServer struct {
// contains filtered or unexported fields
}
TURNServer is a running TURN server.
func NewTURN ¶ added in v0.2.0
func NewTURN(cfg TURNConfig) (*TURNServer, error)
NewTURN starts the TURN server.
func (*TURNServer) Close ¶ added in v0.2.0
func (t *TURNServer) Close()
Close stops the server and closes its sockets.
func (*TURNServer) Issue ¶ added in v0.2.0
func (t *TURNServer) Issue(user string) TURNCreds
Issue mints credentials for user (a device id; no ':').
func (*TURNServer) IssueBoard ¶ added in v0.3.0
func (t *TURNServer) IssueBoard(person string) TURNCreds
IssueBoard mints credentials for another person's devices on a shared board: one user per person ("board-" + person, no ':'), so they share MaxBoardPerUser allocations. Their allocations relay only to this server's relayed addresses, never to the LAN.
func (*TURNServer) IssueGuest ¶ added in v0.2.0
func (t *TURNServer) IssueGuest(until time.Time) TURNCreds
IssueGuest mints credentials for a pairing guest, valid until until (at most the TTL). A guest's allocation may send only to relayed addresses on this server, unless AllowPeer says otherwise.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package mqtt carries go-room signaling over public MQTT brokers, as Toru does.
|
Package mqtt carries go-room signaling over public MQTT brokers, as Toru does. |
|
Package portmap asks the home router to forward ports to this machine, over UPnP-IGD (through goupnp) or NAT-PMP (RFC 6886, go-room's own client), so devices away from home can reach a home server.
|
Package portmap asks the home router to forward ports to this machine, over UPnP-IGD (through goupnp) or NAT-PMP (RFC 6886, go-room's own client), so devices away from home can reach a home server. |