Skip to content

Latest commit

Β 

History

25 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Telekit

Telekit is a peer-to-peer transport library for Go with built-in authentication and encrypted signaling. It uses MQTT, NATS, Centrifugo, or WebSocket for signaling, then exposes the negotiated transport through standard net.Conn and net.Listener interfaces.

Telekit preview

Use case

Telekit was built for collecting data from sensors behind NAT. The collector acts as the room server, makes outbound connections only, and does not need a public application listener. Sensors authenticate through a signaling service, use Pion ICE for hole punching, and then negotiate QUIC, KCP, SCTP, or raw UDP.

Compared with a conventional public TCP/UDP service:

Public TCP/UDP collector Telekit collector
Application listener Publicly reachable address and port No public application listener
Discovery Clients connect directly to the collector Both sides connect outward to signaling
Address disclosure Endpoint is visible before authentication ICE data is released only after PSK authentication
Data path Public server socket Pion ICE path plus QUIC/HTTP/3/KCP/SCTP/Raw UDP
Go integration net.Conn net.Conn

The trade-off is extra signaling and ICE complexity. Direct connectivity is not guaranteed, and strict or symmetric NATs may require TURN. In addition to the default QUIC transport, applications can select the HTTP/3 transport; unauthenticated HTTP/3 requests can be served by a configured reverse-proxy fallback.

UPnP IGD, NAT-PMP, and PCP port mappings can be enabled as additional ICE candidate sources:

peerapi.NewAPI(roomID, adapter,
    peerapi.WithSTUNServer("stun://stun.cloudflare.com:3478"),
    peerapi.WithUPnP("telekit sensor-room"),
    peerapi.WithNATPMP(),
    peerapi.WithPCP(),
)

Enable each method independently on either or both peers. If discovery or mapping fails, normal host, STUN, TURN, and relay candidates remain available. A successful mapping is renewed when the protocol uses a finite lease and removed when its ICE candidate closes. UPnP requires IGD support; NAT-PMP and PCP use the default IPv4 gateway on UDP port 5351. Pass an empty UPnP name to use Telekit.

ICE traversal and port mapping are separate packages. traversal.Service coordinates ICE candidate gathering, while mapping mechanisms implement the small portmapping.Mapper interface:

type Mapper interface {
    MapUDP(context.Context) (*portmapping.Mapping, error)
}

peerapi.NewAPI(roomID, adapter,
    peerapi.WithPortMapper(myMapper),
)

traversal.Service adapts each portmapping.Mapping into an ICE server-reflexive candidate. The mapping's PacketConn.Close implementation is responsible for releasing the external mapping. Lower-level relay or candidate mechanisms can implement traversal.CandidateProvider and be registered with peerapi.WithCandidateSource. Protocol implementations live in portmapping/upnp, portmapping/natpmp, and portmapping/pcp; relays remain independent because they provide a forwarding data path rather than a router port mapping.

Architecture

                        Encrypted signaling
                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                    β”‚ MQTT / NATS /          β”‚
                    β”‚ Centrifugo / WebSocket β”‚
                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                β”‚
                        outbound connections
                                β”‚
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        β”‚                                                       β”‚
sensor clients behind NAT                          collector server behind NAT
        β”‚                                                       β”‚
        └── Pion ICE >>> QUIC / HTTP/3 / KCP / SCTP / Raw UDP β”€β”€β”˜

Security properties

  • PSK authentication finishes before any ICE candidate is disclosed.
  • Transport capabilities, selection, ICE credentials, and candidates are encrypted with a derived session key.
  • Clients pin the server's Ed25519 public key.
  • Each connection uses ephemeral X25519 and HKDF to derive its own session key.
  • Post-handshake signaling payloads are authenticated and encrypted, with sequence numbers and replay windows.
  • Application and heartbeat frames use directional session keys, authenticated sequence numbers, and the selected transport's own mechanisms.
  • HTTP/3 transport data is carried in an authenticated HTTP/3 request body. Invalid HTTP/3 requests are handled by the configured fallback site.

Configure the HTTP/3 transport on the server with a real certificate and an upstream fallback website:

transporthttp3.New(
	transporthttp3.WithTLSConfig(serverTLSConfig),
	transporthttp3.WithFallbackURL("https://www.example.com"),
)

Fingerprint camouflage is opt-in. Enable Chrome-compatible QUIC handshake generation and NaiveProxy traffic padding on HTTP/3 explicitly:

transporthttp3.New(
	transporthttp3.WithChromeParrot(true),
	transporthttp3.WithTrafficPadding(true),
)

ChromeParrot covers the TLS ClientHello, QUIC transport parameters, connection IDs, and Initial-packet behavior. Traffic padding randomizes the HTTP headers and the first eight frames in each stream direction. Either option can be enabled independently. The raw QUIC transport also provides transportquic.WithChromeParrot(true), but its telekit-quic ALPN remains protocol-specific, so HTTP/3 is preferred when browser-like traffic is needed.

The fallback is only used when the HTTP/3 request does not contain a valid Telekit session token. The default self-signed certificate is suitable for development; production deployments should use a certificate matching the configured server name.

  • Frame sizes, buffers, handshakes, connection counts, and request rates are bounded by configuration.

The signaling service can still observe routing identifiers, timing, and ciphertext sizes, and can drop, delay, replay, or flood messages. STUN/TURN servers see the network information required by their protocols. An authenticated but compromised client can disclose the Candidate information for that connection.

Signaling adapters

type Adapter interface {
    Connect() error
    Disconnect() error
    Publish(roomID string, typ MessageType, payload []byte) error
    Subscribe(roomID string, typ MessageType, handler Handler) (Subscription, error)
}
Adapter Route Default base Configuration
MQTT {baseTopic}/{room}/{type} telekit mqtt.WithBaseTopic(...)
NATS {baseSubject}.{room}.{type} telekit nats.NewAdapterWithBaseSubject(...)
Centrifugo {baseChannel}:{room}:{type} telekit centrifugo.WithBaseChannel(...)
WebSocket {baseURL}/{room} β€” Adapter URL
mqttAdapter, _ := mqtt.NewMQTTAdapter(
    mqttURL,
    mqtt.WithBaseTopic("sensors/telekit"),
)

natsAdapter, _ := nats.NewAdapterWithBaseSubject(
    natsURL,
    "sensors.telekit",
)

centrifugoAdapter, _ := centrifugo.NewAdapter(
    centrifugoURL,
    centrifugo.WithBaseChannel("sensors:telekit"),
)

Both peers must use the same base route, and Broker ACLs must authorize it. Each route segment accepts only letters, digits, underscores, and hyphens.

All signaling adapters expose WithReconnectBackoff(...), WithOnConnect(...), WithConnectionLostHandler(...), and WithReconnectingHandler(...) (NATS also exposes WithMaxReconnects(...)). MQTT uses QoS 1 by default and restores subscriptions after reconnecting. A client also attempts one fresh signaling handshake and ICE negotiation when the application heartbeat declares a data transport dead; this creates a new session and does not resume buffered application data.

net.Conn API

A client dials with a room, timeout, device PSK, and pinned server key:

conn, err := client.Dial(
    "sensor-room",
    30*time.Second,
    adapter,
    peer.PreSharedKey{
        ClientID:        "sensor-01",
        Key:             sensorKey,
        ServerPublicKey: pinnedServerPublicKey,
    },
)
if err != nil {
    return err
}
defer conn.Close()

_, err = io.Copy(conn, sensorReader)

// Select explicitly with a transport implementation when needed:
// &client.Options{Transport: transportkcp.New()}
// nil selects the raw QUIC transport.

The server validates device keys and accepts standard net.Conn values:

listener, err := server.NewListener(
    "sensor-room",
    adapter,
    peer.StaticKeyring{"sensor-01": sensorKey},
    &server.Options{IdentityKey: serverIdentityPrivateKey},
)
if err != nil {
    return err
}
defer listener.Close()

for {
    conn, err := listener.Accept()
    if err != nil {
        return err
    }
    go collect(conn)
}

Connections expose only the standard net.Conn contract: reads, writes, close, addresses, and read/write deadlines. Data-channel message callbacks are an internal transport detail.

License

MIT License Β© 2026 AnyShake Project

About

πŸ“‘ A peer-to-peer transport library for Go with built-in authentication and encrypted signaling.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages