amplifi

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Oct 10, 2026 License: Apache-2.0 Imports: 21 Imported by: 0

README

amplifi

A Go library and CLI for Ubiquiti AmpliFi routers over their local control protocol, without a cloud account. The protocol was reverse-engineered from the Android app.

A unit is any AmpliFi box. The router is the unit connected to the internet; the other units are mesh points. A client is a device on the network.

Install

Download a tarball for macOS or Linux (amd64 or arm64), or a .deb, from the releases page, then check it against SHA256SUMS:

sudo dpkg -i amplifi_*_amd64.deb   # Debian, Ubuntu, Raspberry Pi OS

The macOS binaries are not signed. If you downloaded one in a browser, clear the quarantine flag before running it:

xattr -d com.apple.quarantine amplifi

To build from source:

make install-deps  # revive and staticcheck, used by make
make               # vet, lint, test and build bin/amplifi
make install       # install amplifi into $GOBIN

Requires Go 1.26 or later. The library and CLI are pure Go. libsodium is only needed for make sodium-test, which checks the secretstream implementation against it.

CLI

Find units on the local network:

amplifi discover
NAME     IP          MODEL     FIRMWARE              MAC
router   192.0.2.1   AFi-R-HD  AFi-R.4.0.3.0-...     00:00:5e:00:53:01
office   192.0.2.15  AFi-P-HD  AFi-P-HD.4.0.3.0-...  00:00:5e:00:53:02

Log in once to save the login. The first unit you log in to becomes the default unit, which every command uses unless -H/--host (or $AMPLIFI_HOST) names another one by name, IP address or MAC address:

amplifi login 192.0.2.1             # prompts for the admin password
amplifi status                      # the default unit
amplifi status -H office            # another saved unit
amplifi login 192.0.2.15 --default  # make another unit the default
amplifi logout office               # delete a saved login

--password or $AMPLIFI_PASSWORD gives the password for a unit without a saved login.

Commands:

status       health summary of a unit and its mesh
watch        events the unit sends, until Ctrl-C
device       board, firmware, throughput, MAC addresses, properties, display
mesh         mesh points, registered units, Ethernet ports, pairing, factory reset
clients      clients, history, signal, rename, pause and resume
wifi         networks, guest, IoT, radios, interfaces, channels, scan, WPS
net          WAN, LAN, interfaces, UPnP, QoS
dhcp         DHCP pools and static leases
portfwd      port forwarding rules
access       scheduled access control (parental controls)
firmware     update check, status and upgrade
speedtest    speed test, status and history
iperf        iperf server on the unit
site         mesh-wide settings such as time zone and DFS
admin        admin password
support      support bundle
stats        statistics reset
homekit      HomeKit service
teleport     Teleport remote access
reboot       reboot a unit
raw          any method, property or ubus call

Each command has --help. Read commands print tables, or JSON with -o json; a group that lists its items has a list subcommand, also called ls. Write commands print the changes and ask for confirmation; -y/--yes skips the question. A factory reset instead asks you to type the unit name, and only --yes-really skips that. Use --log-level debug to follow the protocol.

Saved logins are stored in $XDG_CONFIG_HOME/amplifi/credentials.json, by default ~/.config/amplifi/credentials.json, with mode 0600. Each entry holds the unit's name, IP address, MAC address and SRP password hash. The password itself is not stored, but the hash grants the same access, so protect the file like a password. If the admin password changes, run login again.

Library

The root package holds the session. Each functional area is its own package whose functions take the client:

c, err := amplifi.Dial(ctx, "192.0.2.1", password)
if err != nil {
	return err // errors.Is(err, amplifi.ErrAuth) on a wrong password
}
defer c.Close()

board, err := device.GetBoard(ctx, c)
peers, err := mesh.Peers(ctx, c)

mac, err := amplifi.ParseMAC("00:00:5e:00:53:10")
err = clients.SetPaused(ctx, c, mac, true)

// Later, without the password:
c2, err := amplifi.DialWithHash(ctx, "192.0.2.1", c.PasswordHash())

The functions follow one naming scheme:

GetX           fetch one value, such as device.GetBoard or wifi.GetConfig
Xs             list values, such as mesh.Peers or dhcp.StaticLeases
UpdateX        change the fields set in an XUpdate; nil fields stay as they are
SetX           replace a value, such as network.SetQoS or clients.SetPaused
AddX           add an item, returning its ID where the router has none
DeleteXs       delete items by key
StartX, StopX  start or stop background work, polled with GetX

Addresses are netip.Addr or netip.Prefix, MAC addresses amplifi.MAC, timestamps time.Time and confirmed durations time.Duration, which encode as seconds in JSON. Values whose unit is not yet known are plain integers, and their documentation says so.

For anything without a typed function, Client.Call, CallJSON, Property and Ubus reach the router directly, and Client.Events streams its events. discovery.Scan finds units on the LAN.

Status

The read commands have been tested against real hardware. The write commands have not been tested yet. They show what they will change and ask before doing it, but treat each one as untested until it has been tried on your own network.

Layout

*.go                   session: Dial, Client, Call, Property, Events, MAC, Role
device/                the unit the session is connected to
mesh/                  mesh points, registered units, ports, pairing
clients/               clients on the network
wifi/                  Wi-Fi networks, radios, channels, scan, WPS
network/               WAN, LAN, interfaces, UPnP, QoS
dhcp/                  DHCP pools and static leases
portforward/           port forwarding rules
access/                scheduled access control
teleport/              Teleport remote access
firmware/              mesh-wide firmware updates
system/                site, admin password, speed test, iperf, support, HomeKit
discovery/             UDP discovery on port 10001
internal/rpc           helpers shared by the area packages
internal/wire          JSON envelopes and msgpack codec
internal/srp           SRP-6a login
internal/secretstream  session encryption
internal/creds         saved logins for the CLI
internal/cli           CLI commands
internal/appinfo       build version, set by the Makefile
cmd/amplifi            CLI entry point

Releasing

Push a version tag. The release workflow runs the checks and tests, builds the tarballs, .deb packages and SHA256SUMS with make dist, and publishes a GitHub release listing the commits since the previous tag. Tags with a suffix, such as v1.2.0-rc.1, become pre-releases.

git tag -a v1.2.0 -m "v1.2.0"
git push origin v1.2.0

make dist VERSION=v1.2.0 builds the same files locally into dist/. It needs nfpm.

License

Apache License 2.0. See LICENSE.

Documentation

Overview

Package amplifi controls Ubiquiti AmpliFi routers and mesh points over their local control protocol, the same one the AmpliFi phone app uses on the LAN. No cloud account is involved.

A unit is any AmpliFi box. The router is the unit connected to the internet; mesh points are the others.

This package holds the session: Dial logs in with the admin password and returns an encrypted Client. The functional areas live in their own packages, which take the client as their second argument:

c, err := amplifi.Dial(ctx, "192.0.2.10", password)
if err != nil {
	return err
}
defer c.Close()

board, err := device.GetBoard(ctx, c)
peers, err := mesh.Peers(ctx, c)
cfg, err := wifi.GetConfig(ctx, c)

The area packages are device, mesh, clients, wifi, network, dhcp, portforward, access, teleport, firmware and system; discovery finds units on the LAN. Client.Call, CallJSON and Property reach any method directly, and Client.Events streams the events the unit pushes.

The wire protocol is a WebSocket on port 9016 carrying AllJoyn-style JSON method calls, authenticated with SRP-6a and encrypted with libsodium's secretstream construction.

Units use self-signed TLS certificates, so the certificate chain is not verified. To detect a replaced or impersonated unit, save Client.CertificateFingerprint after the first login and pass it to later dials with WithCertificateFingerprint.

Index

Constants

View Source
const (
	IfaceDevice      = "Device"
	IfaceClusterNode = "ClusterNode"
	IfaceFwUpdate    = "FwUpdate"
	IfaceMagicLink   = "MagicLink"
	IfaceUnsecure    = "Unsecure"
)

Interface names accepted by Call, CallJSON, Property and SetProperty. The short names expand to the full com.ubnt.UnifiHome.* form.

View Source
const Port = 9016

Port is the TCP port the control WebSocket listens on, on the router and on every mesh point.

Variables

View Source
var ErrAuth = errors.New("authentication failed")

ErrAuth is returned by Dial when the router rejects the login, typically because the password is wrong. Test for it with errors.Is.

Functions

func IsRouterError

func IsRouterError(err error) bool

IsRouterError reports whether err is, or wraps, a RouterError: the router understood the request and refused it, as opposed to a transport failure.

Types

type CertificateMismatchError

type CertificateMismatchError struct {
	Want string
	Got  string
}

CertificateMismatchError is returned by Dial when the unit's certificate does not have the fingerprint given with WithCertificateFingerprint. Test for it with errors.As.

func (*CertificateMismatchError) Error

func (e *CertificateMismatchError) Error() string

Error formats the error with both fingerprints.

type Client

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

Client is an authenticated, encrypted control session with one unit. It is not safe for concurrent use.

func Dial

func Dial(ctx context.Context, host, password string, opts ...Option) (*Client, error)

Dial connects to the unit at host, logs in with the admin password and switches the session to encrypted framing. The certificate chain is not verified because units use self-signed certificates; see WithCertificateFingerprint.

func DialWithHash

func DialWithHash(ctx context.Context, host string, hash []byte, opts ...Option) (*Client, error)

DialWithHash is Dial with a password hash from Client.PasswordHash instead of the password. It fails with ErrAuth if the password has since changed or host is a different unit.

func (*Client) Call

func (c *Client) Call(ctx context.Context, iface, method string, arg any) (any, error)

Call invokes any method with a msgpack argument and returns the decoded result. It is the escape hatch for methods without a typed wrapper.

In arg, map[string]any keys that are decimal numbers become integer option codes and []byte becomes binary. A nil arg sends no argument. Raw msgpack can be passed as RawMsgpack. The result is a decoded msgpack value (maps are map[string]any keyed as described for Event.Payload) or, for methods that answer in JSON, the JSON value.

func (*Client) CallDisconnecting

func (c *Client) CallDisconnecting(ctx context.Context, iface, method string, arg any) error

CallDisconnecting invokes a method after which the unit may drop the connection instead of replying, such as Reboot or FactoryReset. Once the request is sent, a closed connection or no reply within a few seconds counts as success, since a unit going down may do either; a router error in a reply is returned. The session is unusable afterwards.

func (*Client) CallJSON

func (c *Client) CallJSON(ctx context.Context, iface, method string, arg any) (any, error)

CallJSON invokes a method that takes a JSON argument (payload.value).

func (*Client) CertificateFingerprint

func (c *Client) CertificateFingerprint() string

CertificateFingerprint returns the fingerprint of the unit's TLS certificate in the format SSH uses: "SHA256:" followed by the unpadded base64 SHA-256 digest of the certificate's DER encoding.

func (*Client) Close

func (c *Client) Close() error

Close closes the session.

func (*Client) Events

func (c *Client) Events(ctx context.Context) iter.Seq2[Event, error]

Events streams the unit's events until ctx is done, keeping the session alive with periodic pings. Iteration ends with a non-nil error if the connection fails or the unit stops answering pings, and silently when ctx is done. The session must not be used for calls while iterating.

func (*Client) PasswordHash

func (c *Client) PasswordHash() []byte

PasswordHash returns the hash this session logged in with. It lets DialWithHash log in to the same unit later without the password. The hash cannot be turned back into the password, but it grants the same access, so store it as carefully as the password itself.

func (*Client) Property

func (c *Client) Property(ctx context.Context, iface, name string) (any, error)

Property reads a property such as Device.FriendlyName or Unsecure.Uptime.

func (*Client) SetProperty

func (c *Client) SetProperty(ctx context.Context, iface, name string, value any) error

SetProperty writes a property.

func (*Client) Ubus

func (c *Client) Ubus(ctx context.Context, path, method string, args map[string]any) (any, error)

Ubus calls an OpenWrt ubus method on the unit through Device.UbusCall, for example Ubus(ctx, "perfd", "mac-table", nil). args is sent as JSON. The router answers with JSON, which is returned decoded.

type Event

type Event struct {
	Time      time.Time
	Interface string // e.g. "ClusterNode"
	Name      string // e.g. "ClientMacDiscovered"

	// Payload is the decoded payload: a JSON value or a decoded msgpack
	// value, or nil if the event carries none. In msgpack payloads binary
	// values are []byte, and binary map keys are strings of the raw bytes.
	Payload any
}

Event is a message the unit pushes on its own, such as a client joining (ClusterNode.ClientMacDiscovered) or a configuration change (ClusterNode.WifiConfigChanged). The protocol calls these signals.

type MAC

type MAC net.HardwareAddr

MAC is a hardware address. Unlike net.HardwareAddr it encodes as the usual colon-separated string in JSON and other text formats.

func ParseMAC

func ParseMAC(s string) (MAC, error)

ParseMAC parses a colon-, dash- or dot-separated 6 byte MAC address.

func (MAC) Equal

func (m MAC) Equal(o MAC) bool

Equal reports whether m and o are the same address.

func (MAC) MarshalText

func (m MAC) MarshalText() ([]byte, error)

MarshalText implements encoding.TextMarshaler.

func (MAC) String

func (m MAC) String() string

String formats m as aa:bb:cc:dd:ee:ff, or "" if it is empty.

func (*MAC) UnmarshalText

func (m *MAC) UnmarshalText(text []byte) error

UnmarshalText implements encoding.TextUnmarshaler. Empty text yields an empty MAC.

type Option

type Option func(*Client)

Option configures Dial.

func WithCertificateFingerprint

func WithCertificateFingerprint(fp string) Option

WithCertificateFingerprint makes Dial fail with a CertificateMismatchError, before sending anything, unless the unit's TLS certificate has fingerprint fp, in the format of Client.CertificateFingerprint.

func WithLogger

func WithLogger(log *slog.Logger) Option

WithLogger sets the logger used for protocol tracing. The default discards all output.

type RawMsgpack

type RawMsgpack []byte

RawMsgpack is an already encoded msgpack argument for Call.

type Role

type Role string

Role is a unit's place in the mesh.

const (
	RoleRouter    Role = "router"
	RoleMeshPoint Role = "mesh-point"
)

Unit roles.

type RouterError

type RouterError struct {
	Code int
	Msg  string
}

RouterError is an error reported by the router in a response's payload.error. Test for it with errors.As.

func (*RouterError) Error

func (e *RouterError) Error() string

Error formats the error as "router error <code>: <msg>".

Directories

Path Synopsis
Package access manages scheduled access control, the app's parental controls.
Package access manages scheduled access control, the app's parental controls.
Package clients covers the clients on the network: the mesh's current and historic client lists, Wi-Fi association details and signal quality, client counts, custom client settings (name, pause), fingerprints and the guest Wi-Fi whitelist.
Package clients covers the clients on the network: the mesh's current and historic client lists, Wi-Fi association details and signal quality, client counts, custom client settings (name, pause), fingerprints and the guest Wi-Fi whitelist.
cmd
amplifi command
Command amplifi inspects and controls AmpliFi routers from the command line.
Command amplifi inspects and controls AmpliFi routers from the command line.
Package device reads and changes the state of the unit a session is connected to: its board, running firmware, throughput, internet check, MAC addresses, identity configuration, properties and display settings, and reboots, upgrades or factory resets it.
Package device reads and changes the state of the unit a session is connected to: its board, running firmware, throughput, internet check, MAC addresses, identity configuration, properties and display settings, and reboots, upgrades or factory resets it.
Package dhcp reads and changes a unit's DHCP server: the address pools of each network and the static leases (address reservations).
Package dhcp reads and changes a unit's DHCP server: the address pools of each network and the static leases (address reservations).
Package discovery finds AmpliFi units on the local network using the Ubiquiti UDP discovery protocol on port 10001.
Package discovery finds AmpliFi units on the local network using the Ubiquiti UDP discovery protocol on port 10001.
Package firmware checks for and installs firmware upgrades across an AmpliFi mesh (the FwUpdate interface), reports their progress, and reads and writes the automatic update settings.
Package firmware checks for and installs firmware upgrades across an AmpliFi mesh (the FwUpdate interface), reports their progress, and reads and writes the automatic update settings.
internal
appinfo
Package appinfo holds build information injected at link time with -ldflags "-X github.com/borud/amplifi/internal/appinfo.Version=...".
Package appinfo holds build information injected at link time with -ldflags "-X github.com/borud/amplifi/internal/appinfo.Version=...".
cli
Package cli implements the amplifi command-line interface.
Package cli implements the amplifi command-line interface.
creds
Package creds stores saved logins for the CLI so the admin password is entered once per unit.
Package creds stores saved logins for the CLI so the admin password is entered once per unit.
rpc
Package rpc holds the helpers the area packages (device, wifi, clients, ...) share on top of amplifi.Client's public Call and CallJSON: typed result decoding, read-modify-write setters, and access to the integer-keyed option maps the Device and ClusterNode interfaces use.
Package rpc holds the helpers the area packages (device, wifi, clients, ...) share on top of amplifi.Client's public Call and CallJSON: typed result decoding, read-modify-write setters, and access to the integer-keyed option maps the Device and ClusterNode interfaces use.
secretstream
Package secretstream frames and encrypts AmpliFi control messages with crypto_secretstream_xchacha20poly1305, matching the app's EncryptionHelper (libsodium via lazysodium) byte-for-byte.
Package secretstream frames and encrypts AmpliFi control messages with crypto_secretstream_xchacha20poly1305, matching the app's EncryptionHelper (libsodium via lazysodium) byte-for-byte.
srp
Package srp implements the client side of the AmpliFi SRP-6a login: the RFC 5054 2048-bit group with SHA-256 and the app's custom ("csrpcompat") x, client-evidence and server-evidence routines.
Package srp implements the client side of the AmpliFi SRP-6a login: the RFC 5054 2048-bit group with SHA-256 and the app's custom ("csrpcompat") x, client-evidence and server-evidence routines.
wire
Package wire defines the JSON envelopes exchanged on the AmpliFi control WebSocket.
Package wire defines the JSON envelopes exchanged on the AmpliFi control WebSocket.
Package mesh covers the units that make up the mesh: the connected mesh points, every registered unit, backhaul signal quality, the Ethernet ports of every unit, factory pairing of new mesh points, and ignoring or factory resetting units.
Package mesh covers the units that make up the mesh: the connected mesh points, every registered unit, backhaul signal quality, the Ethernet ports of every unit, factory pairing of new mesh points, and ignoring or factory resetting units.
Package network reads and changes a unit's IP networking: the WAN and LAN configuration, the logical network interfaces, UPnP/NAT-PMP and QoS. The DHCP server and port forwarding are in packages dhcp and portforward.
Package network reads and changes a unit's IP networking: the WAN and LAN configuration, the logical network interfaces, UPnP/NAT-PMP and QoS. The DHCP server and port forwarding are in packages dhcp and portforward.
Package portforward reads and changes a unit's port forwarding rules.
Package portforward reads and changes a unit's port forwarding rules.
Package system covers the mesh-wide and unit-level maintenance functions of an AmpliFi mesh: the site configuration, the admin password, the internet speed test, iperf, support bundles, statistics and HomeKit.
Package system covers the mesh-wide and unit-level maintenance functions of an AmpliFi mesh: the site configuration, the admin password, the internet speed test, iperf, support bundles, statistics and HomeKit.
Package teleport controls Teleport, AmpliFi's remote access VPN, through the router's MagicLink interface: connectivity, invite codes and the clients that have joined.
Package teleport controls Teleport, AmpliFi's remote access VPN, through the router's MagicLink interface: connectivity, invite codes and the clients that have joined.
Package wifi reads and changes a unit's Wi-Fi: the main, guest and IoT networks, radios and interfaces, channels and countries, DFS radar events, the signal quality scan and WPS push-button pairing.
Package wifi reads and changes a unit's Wi-Fi: the main, guest and IoT networks, radios and interfaces, channels and countries, DFS radar events, the signal quality scan and WPS push-button pairing.

Jump to

Keyboard shortcuts

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