realrasengan b71a9c5824 fix key repeat: only create new state when previous is acked (match Dart client)
The Dart mosh client only calls sendNew() when hasPendingState is false.
The Go client was calling SetPending on every tick with dirty=true, which
reset diffSent and caused new sequence numbers for the same cumulative
payload. Now tick() checks AckedByRemote >= SentNum before calling
SetPending — at most one unacked state in flight, matching the Dart
client's behavior.
2026-03-30 19:56:45 -07:00
2026-03-14 20:34:30 -07:00
2026-03-14 20:34:30 -07:00

mosh-go

Pure Go mosh. Client and server. No C dependencies, no CGO.

Wire-compatible with the standard C mosh implementation. You can use the Go client against a C mosh-server, or the Go server with a C mosh-client, or go pure Go on both ends.

Implements the full mosh wire protocol: AES-128-OCB3 authenticated encryption, SSP transport with sequencing and retransmission, datagram fragmentation, and the protobuf state sync layer.

Install

go install github.com/unixshells/mosh-go/cmd/mosh@latest
go install github.com/unixshells/mosh-go/cmd/mosh-server@latest
go install github.com/unixshells/mosh-go/cmd/mosh-client@latest

Usage

The mosh command works like the C version -- SSHs to the host, starts mosh-server, connects over UDP:

mosh user@host
mosh -p 2222 user@host            # custom SSH port
mosh -i ~/.ssh/id_ed25519 user@host

Or use the server and client separately:

# On the server:
mosh-server                       # prints MOSH CONNECT <port> <key>
mosh-server -p 60001              # specific port
mosh-server -s /bin/zsh           # specific shell

# On the client:
MOSH_KEY=<key> mosh-client <host> <port>

Library

import mosh "github.com/unixshells/mosh-go"

// Server
srv, _ := mosh.NewServer("/bin/bash", 60000, 60999)
go srv.Serve()
fmt.Println(srv.ConnectLine())

// Client
c, _ := mosh.Dial("192.168.1.5", 60001, "base64key")
c.Resize(80, 24)
c.Send([]byte("ls\n"))
out := c.Recv(time.Second)

Embedding

ServeRW runs the server protocol over an io.ReadWriteCloser instead of a PTY. No shell is spawned -- the caller provides the I/O. This is how latch bridges mosh into its session model:

srv, _ := mosh.NewServer("", 0, 0)
fmt.Println(srv.ConnectLine())
srv.ServeRW(rw, func(cols, rows uint16) {
	// handle resize
})

Compatibility

Tested against the C reference implementation:

Client Server Status
Go Go always tested
Go C mosh-server tested if installed
C mosh-client Go tested if installed

All three pass. Run go test -race ./... to verify.

Latch extensions

The protobuf layer supports optional latch extension fields. Standard mosh clients and servers ignore them (forward-compatible).

  • Capability negotiation (TransportInstruction field 8): bitfield exchanged on every datagram. Both sides AND their caps to get the intersection. No caps = classic mosh mode.
  • Session control (Host/UserInstruction field 9): list, switch, and create latch sessions over the mosh connection.

Files

  • server.go -- server, PTY lifecycle, ServeRW bridge mode
  • client.go -- client, send/recv loops
  • transport.go -- SSP sequencing, timestamps, RTT, retransmission
  • ocb.go -- AES-128-OCB3 (RFC 7253)
  • fragment.go -- datagram fragmentation and reassembly
  • pb.go -- protobuf encoding (transport, host, user, latch extensions)
  • framebuffer.go -- terminal state tracking for diff protocol

Dependencies

creack/pty and the Go standard library.

License

Copyright (c) 2026 Unix Shells. MIT license. See LICENSE.

S
Description
Mirror of ys-ll/mosh-go (GitHub), kept for paraterm builds
https://unixshells.com/
Readme MIT
5.1 MiB
Languages
Go 100%