Skip to content

Repository files navigation

moca-status

CI Release Go Reference License

Read everything a ScreenBeam / Actiontec MoCA adapter knows about your coax network — over plain HTTP, with no vendor tooling and no SSH.

A single static binary with no dependencies. Cross-compiles to anything Go targets, so it runs on the Pi, NAS or router that actually sits near your coax.

These adapters ship a small web UI that shows a fraction of what the hardware measures, and no usable CLI. Behind that UI is an undocumented JSON API. This tool speaks it directly: the full node table, the coordinator election, per-link PHY rates, the signal quality behind those rates, Ethernet negotiation, 64-bit frame counters, the band and channel plan, and the security configuration.

It also interprets what it reads, rather than just printing it — which link is degraded, whether that is attenuation or reflections, whether the fault is a node or the path between two nodes, and what will actually change if you retune.

$ moca-status -diagnose

  WARN  link-rate        node 0 (Master Bedroom) <-> node 2 (Office) runs 1674/1706 Mbps,
                         well below the 3515 Mbps best link on this coax
  INFO  link-cause       node 0 (Master Bedroom) <-> node 2 (Office) is limited by
                         attenuation rather than reflections - it carries 11386 bits per
                         symbol against a median of 20517 while its cyclic prefix is
                         normal, so look for signal loss on that run: a long cable, an
                         extra splitter, a corroded or loose fitting

Install

Download a binary. Nothing to install — the binary is static and has no runtime.

# pick your platform from the latest release
curl -sSL https://github.com/kevinelliott/moca-status/releases/latest/download/moca-status-linux-arm64.tar.gz \
  | tar xz && ./moca-status -version

Or run the container. Host networking matters: the adapters are on your LAN, and -stress sends UDP that NAT would reshape.

docker run --rm --network host ghcr.io/kevinelliott/moca-status 192.168.1.10

# with a config file
docker run --rm --network host \
  -v $PWD/moca_hosts.json:/moca_hosts.json:ro \
  ghcr.io/kevinelliott/moca-status -diagnose

Images are published for linux/amd64, linux/arm64 and linux/arm/v7.

Or build it. Needs Go 1.21+ (1.25+ recommended; the toolchain is pinned in mise.toml). Nothing else — there are no third-party dependencies.

Quick start

git clone https://github.com/kevinelliott/moca-status.git
cd moca-status
make

# point it at your adapters
cp moca_hosts.example.json moca_hosts.json
$EDITOR moca_hosts.json

./moca-status

Or skip the config entirely and pass addresses on the command line:

./moca-status 192.168.1.10 192.168.1.11

Flags take one dash or two — -diagnose and --diagnose both work.

Building for somewhere else

make cross     # linux/amd64, linux/arm64, linux/armv6, darwin/arm64, darwin/amd64

Every adapter is queried concurrently, so a sweep costs about as long as the slowest single adapter rather than the sum of all of them.

The toolchain is pinned in mise.toml (Go 1.25.3). Any Go 1.21+ will build it, but older toolchains on recent macOS need workarounds the Makefile no longer carries: Go 1.21's internal linker omits LC_UUID, which recent dyld rejects, and the externally-linked result then needs an ad-hoc signature. Go 1.25 emits a linker-signed binary itself, so a plain go build just works.

Configuration

Site-specific settings live in a config file so the source carries no particular network's addresses. It is searched for in this order:

  1. $MOCA_CONFIG
  2. ./moca_hosts.json
  3. ~/.config/moca-status/hosts.json
{
  "hosts": ["192.168.1.10", "192.168.1.11", "192.168.1.12"],
  "expected": {
    "aa:bb:cc:00:00:01": "Office",
    "aa:bb:cc:00:00:02": "Living Room",
    "aa:bb:cc:00:00:03": "Bedroom"
  }
}

expected is optional. It names adapters in the output, and lets the diagnostics tell you when an adapter you expect has vanished from the coax entirely — which is not otherwise obvious, since an adapter can drop off the bus while still appearing on your network.

Credentials default to the vendor's admin / screenbeam; override with -user / -password.


Commands

Full readout — the default

Everything the adapter knows about itself and the network.

$ ./moca-status 192.168.1.13

========================================================================
  192.168.1.13   aa:bb:cc:00:00:03   "Living Room"
========================================================================
  DEVICE
    chip / SoC       MXL371x.1.18.14   (chip id 0x16)
    firmware words   1.18.14.1, 1.17.5.0
    adapter name     LivingRoom
    gpio             0xfd
  NETWORK INTERFACE
    address          192.168.1.13 / 255.255.255.0   gw 192.168.1.1
    DHCP false       auto-IP false
    ethernet port 0  down   10Mbps    half
    ethernet port 1  up     1Gbps     full
  MoCA
    link             UP
    node id 3        NC = node 2
    version          node 2.5   network 2.5
    beacon channel   1150 MHz     primary LOF 1175   secondary LOF 1300
    2.5 channels     first 1175, count 5
    preferred NC false  network search true   LOF 1150 MHz  band 0x1
    tx power 10      beacon power 10    hw cfg 0x80000040  scan mask 0x2
  SECURITY
    privacy          disabled   (mode mask 0x0)
    privacy password 99999999988888888
    enhanced privacy ENABLED   (mode 0x7)
    enhanced password 99999999988888888777
  FRAME COUNTERS
    tx   good 306559         bad 0        dropped 0
    rx   good 131258         bad 0        dropped 0
  COAX  (bitmask 0xf -> 4 nodes)
    node 0  aa:bb:cc:00:00:00  MoCA 2.5  Master Bedroom
    node 1  aa:bb:cc:00:00:01  MoCA 2.5  Guest Room
    node 2  aa:bb:cc:00:00:02  MoCA 2.5  Office          [NC]
    node 3  aa:bb:cc:00:00:03  MoCA 2.5  Living Room     [self]
  PHY RATES, Mbps (NPER; diagonal = GCD broadcast)
      from\to        0       1       2       3
      node 0       570    2838    1652    3527
      node 1      2851     684    3446    3316
      node 2      1677    3408     578    2496
      node 3      3525    3269    2467     679

-brief drops the rate matrix. -vlper shows VLPER rates instead of NPER.

-signal — what is limiting each link

The rate alone does not tell you what to go and look at. The FMR payload carries two independent quantities, and they fail for different physical reasons:

meaning rises / falls with
ofdmb bits carried per OFDM symbol falls with attenuation and poor SNR
gap cyclic prefix length rises with reflections and delay spread
$ ./moca-status --signal

  LINK SIGNAL QUALITY
========================================================================
  ofdmb = bits per OFDM symbol, falls with attenuation
  gap   = cyclic prefix, rises with reflections and delay spread
  network median: ofdmb 20506, gap 18

  link                              gap    ofdmb     Mbps   reading
  Master Bedroom <-> Guest Room      20    19380     2871   normal
  Master Bedroom <-> Office          20    11362     1672   attenuated
  Master Bedroom <-> Living Room     20    23750     3516   normal
  Guest Room <-> Office              14    22722     3414   normal
  Guest Room <-> Living Room         12    21737     3298   normal
  Office <-> Living Room             15    15969     2382   normal

A slow link with normal gap and low ofdmb is losing signal — a long run, an extra splitter, a corroded fitting. A slow link with normal ofdmb and high gap is seeing reflections — a stub or an unterminated leg. Different symptom, different repair.

-diagnose — findings, worst first

Reasons across every adapter at once, so it catches what no single readout shows.

$ ./moca-status --diagnose

  DIAGNOSTICS
========================================================================
  WARN: 1   INFO: 3

  --- WARN ---
  WARN  link-rate        node 0 (Master Bedroom) <-> node 2 (Office) runs 1674/1706 Mbps,
                         well below the 3515 Mbps best link on this coax
  --- INFO ---
  INFO  link-cause       node 0 (Master Bedroom) <-> node 2 (Office) is limited by
                         attenuation rather than reflections - it carries 11386 bits per
                         symbol against a median of 20517 while its cyclic prefix is
                         normal, so look for signal loss on that run: a long cable, an
                         extra splitter, a corroded or loose fitting
  INFO  bottleneck       192.168.1.12 negotiates 1000 Mbps on Ethernet but MoCA 2.5
                         carries up to 2500 Mbps here - the Ethernet port is the limit
  INFO  bottleneck       192.168.1.13 negotiates 1000 Mbps on Ethernet but MoCA 2.5
                         carries up to 2500 Mbps here - the Ethernet port is the limit

Add -v to print the full readout as well.

All checks
category catches
reachability an adapter that stopped answering
membership an expected adapter absent from the coax; a node bridging but not directly queryable
consistency adapters disagreeing on the node bitmask or the coordinator; mismatched LOF, band mask, tx power, beacon power
coordinator no Preferred NC set, two contending, or one set that has not taken over
moca-version a pre-2.0 node forcing the network into mixed mode
link-rate links degraded in absolute terms and relative to the best link on the bus
link-cause whether a slow link is attenuation, reflections, or both
node-local a node slow to every peer (its own drop) versus one slow pair (the path)
link-asymmetry directional impairment, which points at a splitter leg rather than plain loss
broadcast a low GCD rate, which multicast and MoCA control traffic ride
frame-errors bad and dropped frames as ppm of good
ethernet port down, half duplex
bottleneck an Ethernet port slower than the MoCA generation's rated throughput
security privacy settings that differ between adapters
config DHCP on a management interface, network search switched off

-watch — poll and report only what changes

$ ./moca-status --watch --interval 30

[18:43:15] watching 4 adapter(s) every 30s - Ctrl-C to stop
[18:43:37] baseline: 4/4 reachable, 4 node(s) on coax, NC=node 2, 2 finding(s)
[18:51:02] ALERT   node 3 (Living Room) left the coax
[18:51:02] NEW     CRIT membership  Living Room (aa:bb:cc:00:00:03) is not on the coax
[18:53:30] ALERT   node 3 (Living Room) joined the coax
[18:53:30] CLEARED CRIT membership  Living Room (aa:bb:cc:00:00:03) is not on the coax
[19:04:11] ERROR   192.168.1.12 rx bad +37 in 30s (total 37)
[19:12:40] ALERT   192.168.1.11 tx counters reset (3052298 -> 1204) - the adapter rebooted

Reports nodes joining or leaving, the coordinator moving, link and config changes, error counters incrementing with their delta, counters resetting (an adapter rebooted), link rates moving more than 15%, and findings appearing or clearing.

Real PHY rates jitter about 1% between polls and the good-frame counters climb constantly, so both are ignored by design — a stable network prints nothing after the baseline line. That is asserted by the test suite, not assumed.

-count N stops after N polls, which makes it usable from cron.

-plan — band and channel plan, read off the hardware

The plan is not assumed. Each band carries a 64-bit scan mask and an offset, and freq = (63 - bit + offset) * 25 MHz. That decode reproduces the published MoCA band plans exactly, which is how it was verified: band E resolves to 500–600 MHz, band F to 675–850 MHz, D-High to 1400–1600 MHz.

$ ./moca-status --plan

  CHANNEL PLAN
========================================================================
  hwCfgWord 0x80000040   band mask 0x1   current LOF 1150 MHz
  band     hw   on   channels (MHz)
  D-Ext    yes  yes  1150, 1200, 1250, 1300, 1350, 1400, 1450, 1500, 1550, 1600
  D-Low    no   no   1150, 1200
  D-High   no   no   1400, 1450, 1500, 1550, 1600
  E        no   no   500, 525, 550, 575, 600
  F-SAT    no   no   675, 700, 725, 750, 775, 800, 825, 850
  F-CBL    no   no   675, 700, 725, 750, 775, 800, 825, 850
  H        no   no   975, 1000, 1025

  - coax profile: none - dedicated MoCA coax - the whole band is available
  - bands this hardware supports: D-Ext
  - only one band is supported, so there is no alternative band to move
    to; tuning here means choosing a channel within it
  - coax loss rises with frequency, so on a dedicated line the lowest
    channel is the best default: 1150 MHz
  - current LOF 1150 MHz is already the best available choice

hwCfgWord bit 31 - bandIdx says whether the hardware supports a band at all, so the tool will tell you plainly when there is nothing to retune.

If something else shares your coax, say so and the advice changes:

$ ./moca-status --plan --shared-coax docsis31

  - coax profile: docsis31 - DOCSIS 3.1 downstream reaches 1218 MHz
  - channels overlapping docsis31: 1150, 1200, 1250
  - lowest channel clear of docsis31: 1300 MHz (fit a point-of-entry
    filter as well, so MoCA does not leave the house)
  - current LOF 1150 MHz OVERLAPS docsis31 - move to 1300 MHz
profile ceiling
none — dedicated MoCA coax (default)
docsis30 1002 MHz DOCSIS 3.0 downstream
docsis31 1218 MHz DOCSIS 3.1 downstream
docsis40 1794 MHz DOCSIS 4.0 extended spectrum
satellite 2150 MHz DirecTV / SWM

A MoCA channel centred at f occupies roughly f ± 50 MHz, so it is clear only when f − 50 sits above the ceiling. When nothing clears, the tool says so rather than inventing a recommendation.

-stress — load the coax and watch the PHY

Marginal coax passes an idle check and only misbehaves when busy, either by incrementing error counters or by backing the modulation off. Neither shows up in a static readout.

$ ./moca-status --stress --target 192.168.1.50 --stress-seconds 25 --stress-mbps 800

  sent 1606848 datagrams, 17997 Mbit in 25s (720 Mbit/s offered)

  adapter                 frames tx      frames rx     errors
  192.168.1.10               243311          96452          0
  192.168.1.11                34282          10327          0
  192.168.1.12                29474         213835          0
  192.168.1.13                33171          25416          0

  link                             before    after   change
  node 0 -> node 2                   1671     1654    -1.0%
  node 1 -> node 2                   3438     3424    -0.4%

  RESULT: no frame errors and no meaningful rate change - the coax handled the load.

-target should be a host behind a MoCA-attached switch. UDP is used, so no listener is needed — the frames still have to cross the coax to reach it. The tool warns if the counters barely move, meaning your target was not actually across the link.

-via-adapters — no far-side host needed

A remote adapter's own management interface sits on the far side of the coax, so traffic addressed to it must cross the link being tested.

$ ./moca-status --stress --via-adapters --stress-seconds 12 --stress-mbps 200

  coordinator (head end): 192.168.1.10 - traffic to it never crosses the coax, skipped

  adapter          link                   sent      crossed   errors   rate chg
  192.168.1.11     node 2 -> 1          207060        38456        0      +0.3%
  192.168.1.12     node 2 -> 0          208845        57953        0      -0.2%
  192.168.1.13     node 2 -> 3          207060        40986        0      +0.4%

The limitation is real and worth understanding. Those frames terminate on the adapter's small CPU instead of passing through its bridge in hardware, so it drops most of them — measured, about 28% arrived, against 88% when the target was a real host on the far side. Use -via-adapters to prove a link is clean; use -target to measure what it can carry.

-json

Everything above, machine-readable. With -diagnose, findings are included and each carries a stable key identifying its subject, so you can diff runs without matching on prose.

{
  "adapters": [ { "ip": "192.168.1.10", "moca_link": "up", "...": "..." } ],
  "findings": [
    { "severity": "WARN", "category": "link-rate", "key": "link:0-2",
      "message": "node 0 (Master Bedroom) <-> node 2 (Office) runs 1674/1706 Mbps ..." }
  ]
}

The API, for anyone else poking at these adapters

Reverse-engineered from the adapters' own UI (index / devStatus / devSetup / security / phyRates .html + main.js).

GET  http://<ip>/phyRates.html          -> sets the csrf_token cookie (Basic auth)
POST http://<ip>/ms/<tree>/<hexid>
     headers: X-CSRF-TOKEN: <csrf_token cookie>
              Content-Type: application/x-www-form-urlencoded
     body:    {"data":[...]}            <- JSON, despite that content type
     reply:   {"data":["0x..","0x.."]}  <- 32-bit words as hex strings
GET  http://<ip>/ms/<tree>/<hexid>/GET  -> same shape, for config reads

The id may be hex with 0x or plain decimal — the vendor's own UI uses both.

endpoint body returns
/ms/0/0x14 [0] frame counters, 64-bit as hi<<32|lo: [12] tx good, [30] tx bad, [48] tx dropped, [66] rx good, [84] rx bad, [102] rx dropped
/ms/0/0x15 [] [0] node id, [1] NC node id, [5] MoCA link, [11] network version, [12] node bitmask, [18]/[19] LOF offsets, [21..] SoC version ASCII, [28] band gap mode
/ms/0/0x16 [node] [0..1] MAC, [4]&0xff MoCA version
/ms/0/0x1D [1<<node, ver] FMR payload; PHY data from word 10
/ms/0/0x24 [] beacon channel
/ms/0/0x7f [] [2] first channel, [3] channel count
/ms/1/0xb17 [] gpio

Config reads (/GET): 0x1001 network search · 0x1002 preferred NC · 0x1003 LOF · 0x1009 band mask · 0x100e beacon power · 0x10a8 tx power · 0x10f5 hw config word · 0x1049 privacy password · 0x1059 privacy mode mask · 0x130D enhanced privacy · 0x1400 enhanced password · 0x1063+band scan mask · 0x106b+band scan offset

Device tree (/ms/1/, /GET): 0x103 MAC · 0x20b IP · 0x210 netmask · 0x211 gateway · 0x212 adapter name index · 0x217 DHCP · 0x218 auto-IP · 0x302 firmware · 0x303 chip id · 0x307 Ethernet link/speed/duplex

The node bitmask is the authoritative test of whether an adapter is really on the coax. It comes from the MoCA layer itself, so it stays correct even when the adapter has no IP and is invisible to ARP, DHCP, packet capture and your controller — which is exactly the state a bridging adapter with no management address is in.

A note on the web server

The adapters run a single-threaded InterNiche webserver that intermittently answers 401 or drops the connection when requests land close together, and a full readout makes about twenty requests per adapter. The client retries rather than treating that as fatal — an unretried transient 401 reads as "adapter absent", which is wrong and alarming.

Telnet

Port 23 is open on these adapters and drops straight to a > prompt with no authentication at all, exposing reg_wr / mdio_wr / smmd_wr — raw writes that can brick the device until a power cycle. It offers nothing useful in return: every read on the MDIO bus returns 0xffff, meaning nothing is attached to it. There is no setting to disable it, so the only control is network placement. This tool does not use it.


Tests

make test

51 checks, no hardware required. Every diagnostic is driven against the fault it is meant to catch, and — just as importantly — a healthy network and a network whose numbers merely drift must both stay silent. That second half is what catches the interesting bugs: it is how an earlier bottleneck check was found to be comparing Ethernet speed against the raw PHY rate rather than the generation's rated throughput, which flagged even a correctly matched 2.5 Gb port.

Run it after changing any threshold in diagnose.go (RateCrit, RateWarn, AsymmetryWarn, ErrorPPMCrit, PhyDrift).

Compatibility

Developed against ScreenBeam ECB6250 (MaxLinear MXL371x, SoC 1.18.14) running MoCA 2.5. Other MaxLinear-based Actiontec/ScreenBeam adapters share the same /ms/ API and are likely to work; the tool reads the chip id and band support from the hardware rather than assuming them.

If you try it on other hardware, an issue with the output of -plan and the full readout would be welcome.

License

MIT

About

Status, diagnostics and load testing for ScreenBeam/Actiontec MoCA adapters over their undocumented HTTP API. Single static binary.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages