Skip to content

feat(drivers): add ARRIS SURFboard SB8200 (CBN firmware) support - #800

Merged
itsDNNS merged 3 commits into
itsDNNS:mainfrom
s0kil:feat/sb8200-cbn-driver
Aug 18, 2026
Merged

feat(drivers): add ARRIS SURFboard SB8200 (CBN firmware) support#800
itsDNNS merged 3 commits into
itsDNNS:mainfrom
s0kil:feat/sb8200-cbn-driver

Conversation

@s0kil

@s0kil s0kil commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds support for ARRIS SURFboard SB8200 units built by Compal Broadband Networks (CBN), which serve a completely different management interface from the SB8200 firmware the existing surfboard driver targets.

Registry key sb8200_cbn, format profile sb8200_cbn_xml. Verified against a live unit on Charter/Spectrum:

  • HwModel SB8200v3, firmware AC01.01.008_122722_8200.03.07.733
  • 31 SC-QAM downstream + 1 OFDM downstream (4096QAM) + 4 ATDMA upstream

Why a new driver rather than extending surfboard

Prior art in #312 (HTML fallback for broken HNAP firmware), #304, #308, #310, #316 and #318 already handles SB8200 firmware variants, but neither of the surfboard driver's transports exists on this unit:

surfboard transport result on this unit
POST /HNAP1/ (primary) 404
GET /cmconnectionstatus.html (the #312 fallback) 302 to /common_page/login.html
GET /cmswinfo.html 302 to /common_page/login.html
GET /RgConnect.asp 404
the page those redirect to 8470-byte CBN login page, contains no Bonded table for arris_html to parse

This is not a degraded HNAP variant that a fallback can rescue: it is a different web stack (lighttpd + CBN UI) with its own XML API and its own authentication scheme. Extending surfboard would mean a third transport inside a driver whose FORMAT_FAMILIES already spans two profiles, so a separate driver keeps the one-way driver -> format profile dependency the architecture doc describes.

API surface

HTTPS only (port 80 redirects, HSTS is set). POST /xml/getter.xml with token=<sessionToken cookie>&fun=<code>:

fun payload used for
1 GlobalSettings - HwModel, AccessLevel pre-auth; supplies the login envelope tag
2 cm_system_info device info, DOCSIS mode
9 downstreamOFDM_table DS 3.1
10 downstream_table DS 3.0
11 upstream_table US 3.0
6 upstreamOFDMA_table US 3.1
19 signal_table downstream codeword counts
15 / 16 login / logout (setter.xml) session

A sweep of fun=1..300 found no other codes that return data.

Authentication reproduces the firmware's CBN_Encrypt():

key   = SHA256(sessionToken)      iv = MD5(sessionToken)
field = base64("HS:" + HwModel + ":" + hex(AES-256-CBC/PKCS7(value, key, iv)))
POST /xml/setter.xml  token=<t>&fun=15&Username=<field>&Password=<field>

The sessionToken cookie rotates on every response, one session is allowed at a time, the session is bound to the client address, and repeated logins are rate-limited. The driver therefore holds one session across polls, re-authenticates at most once per driver call, and releases the session on shutdown.

Required and optional tables

Only the two SC-QAM tables are required. The OFDM, OFDMA, and codeword tables enrich the result, so one unavailable endpoint degrades to None and a diagnostic rather than discarding channels the modem did return:

  • a 302 is session loss and may spend the call's single re-authentication
  • a 200 with an empty body means the firmware does not serve that table, and must not spend a login on the modem's rate limiter
  • a transport or HTTP failure is logged by failure class only, so no response body reaches the log

A missing required table stays an error. It is never converted into a healthy, empty channel list.

Responses are size-bounded against the bytes the process actually holds: the session asks for Accept-Encoding: identity and any other Content-Encoding is refused before the body is buffered, so a compressed answer cannot inflate past the limit while it is read.

Three judgement calls worth reviewing

1. Annex B symbol rates are injected. The downstream table omits the symbol rate. The captured SC-QAM carriers sit on a 6 MHz raster (411/417 MHz adjacent), so without an injected rate the analyzer falls back to the EuroDOCSIS 8 MHz default (6952 kSym/s) and every downstream capacity estimate reads ~30% high. Uses the same table and rationale as the CM1000 parser (256QAM -> 5361, 64QAM -> 5057). Upstream needs no assumption, srate is reported.

2. The OFDM codeword counters are reported as unsupported, not as measured values. On this firmware they are not comparable to the SC-QAM counters:

  • they climb ~1.3 million uncorrectables per minute on a locked 4096QAM channel at 33 dB MER, while the entire 31-channel SC-QAM cohort adds 0-1
  • uncorrectables exceed correctables
  • the modem's own signal_table reports 0/0/0 for that same dsid

Reported as measured codewords they put ds_uncorr_pct at 54.6% and pinned downstream health at critical on every poll, emitting a false error_spike warning every 60 seconds. With the lane left counter-unsupported, health reads tolerated and downstream uncorrectables read 7.7k, the real SC-QAM figure. The channel is still reported; only its codeword counters are withheld, so error_counter_coverage shows unsupported_channels: 1 rather than a measured zero, per the data contract's "unsupported must stay distinguishable from measured zero".

3. OFDMA is refused rather than guessed. This unit reports us_num=0 and no rows, so no captured payload shows the field names. Rather than inventing them, the parser emits an unsupported_lane diagnostic if a row ever appears.

Per the data contract, cm_serial_number is read by the firmware but deliberately never surfaced in device info, logs, or diagnostics.

Testing

  • 63 tests in tests/test_sb8200_cbn_driver.py, plus 3 boundary cases in the format-case registry.
  • The login-envelope test asserts against ciphertext generated independently with openssl, and a second test decrypts the envelope with the token the request actually carried, so a stale-token regression fails the suite. All fixture tokens, SIDs, and credentials are synthetic.
  • The fake session rotates the CSRF token the way the firmware does.
  • The transport bound is exercised against a real requests.Response over a real urllib3 stream: at the limit, one byte over, and a gzip payload that is small on the wire and 32 MB decoded.
  • Optional-table coverage: empty 200, persistent HTTP and transport failure, 302 expiry with at most one re-authentication, and preservation of the SC-QAM channels in each case.
  • Live-verified against the device end to end, including deliberately invalidating the session mid-run and confirming automatic re-authentication.

s0kil and others added 3 commits August 18, 2026 11:35
SB8200 units built by Compal Broadband Networks serve a CBN web UI rather
than the HNAP1 interface the other SURFboard models expose, so /HNAP1/
returns 404 and the existing surfboard driver cannot drive them. Add a
sb8200_cbn driver and a sb8200_cbn_xml format profile for that transport.

Channel tables are read from /xml/getter.xml with numeric function codes
(10 SC-QAM downstream, 11 upstream, 9 OFDM, 6 OFDMA, 19 codewords, 2 system
info). Login reproduces the firmware's CBN_Encrypt envelope: username and
password are AES-256-CBC encrypted with a key and IV derived from the
rotating sessionToken cookie, then wrapped as HS:<HwModel>:<hex>.

Notes on the profile:

- Codeword counts live in their own table keyed by dsid, which matches the
  SC-QAM chid, so the downstream lane joins the two.
- The downstream table omits the symbol rate and the modem is an Annex B
  6 MHz device, so Annex B rates are injected. Without them the analyzer
  falls back to the EuroDOCSIS 8 MHz default and every downstream capacity
  estimate reads ~30% high.
- The OFDM codeword counters this firmware reports are not comparable to the
  SC-QAM ones. Measured on a locked 4096QAM channel at 33 dB MER they climb
  by ~1.3 million uncorrectables per minute while the whole SC-QAM cohort
  adds 0-1, uncorrectables exceed correctables, and the modem's own codeword
  table reports zero for the same dsid. Reported as measured codewords they
  pinned downstream health at critical on every poll, so the lane is left
  counter-unsupported instead.
- OFDMA is reported as unsupported rather than guessed; no captured payload
  from this firmware has ever contained an active OFDMA row.
- The serial number exposed by the system info table is deliberately not
  surfaced in device info.

The firmware allows a single Web-UI session and rate-limits repeated login
attempts, so the session is held across polls, re-authenticated at most once
per expiry, and released on shutdown.
Adversarial review of the new driver found defects that live testing against
the device confirmed.

Session handling:

- The login envelope was keyed with a stale CSRF token. The token rotates on
  every response, and resolving the hardware model issues a request, so the
  first login of each driver instance encrypted the credentials with the
  pre-rotation token while posting the post-rotation one. The model is now
  resolved before the token is read.
- A lapsed session answers every table code with a 302 and an empty body,
  which was previously read as a permanent failure. Both shapes are now
  treated as session loss and recovered with a single re-authentication,
  bounded to one login per call so a failure cannot hammer the modem's login
  rate limiter.
- Logout now runs before the cookies identifying the session are discarded,
  so a failed login cannot strand an open session on a modem that permits
  only one. Cookie removal no longer silently no-ops, and the finalizer
  closes the session and cannot raise.
- Login now requires a well-formed `successful` prefix and a syntactically
  valid SID; `unsuccessful;SID=...` previously passed a substring test.

Parsing:

- Reuse `primitives.parse_optional_finite_float` instead of a local variant
  that accepted inf/nan. A non-finite value crashed the poll with
  OverflowError, escaping the profile's degrade-to-diagnostic contract, and a
  non-finite power reached storage as invalid JSON.
- Validate the root tag of every table. An unauthenticated modem answers with
  a page rather than an error, which parsed as a table holding no channels
  and would surface as a total signal loss.
- Report unknown lock markers, duplicate codeword rows, and unparsable
  codeword rows instead of dropping them silently.
- Take the five payloads keyword-only; drop the redundant `type` on upstream
  channels, which duplicated `multiplex`.

Transport:

- Bound the response before it is buffered rather than after, and skip the
  login page body entirely.

Tests now rotate the session token like the firmware does, which is what hid
the stale-token defect, and cover the redirect, persistent-redirect, foreign
document, unknown lock, duplicate codeword, non-finite, transport error, and
session release paths.
Maintainer follow-up to the contributed driver, kept in its own commit.

Transport:

- `raw.read(n, decode_content=True)` bounds the bytes taken off the socket,
  not the bytes they expand to, so a compressed answer well under the limit
  on the wire could still inflate far past it in memory before the length
  check ran. The session now asks for `identity`, and a response carrying
  any other `Content-Encoding` is refused before anything is buffered.

Table contract:

- The SC-QAM downstream and upstream tables stay required. The OFDM, OFDMA,
  and codeword tables only enrich the result, so an unavailable one degrades
  to `None` instead of discarding the channels the modem did return. A `302`
  remains session loss and may still spend the call's single
  re-authentication; a `200` with an empty body means the firmware does not
  serve that table and must not spend a login on its rate limiter. A
  transport failure is logged by failure class alone, so no response body
  reaches the log.
- The re-authentication budget moved onto the driver call, so a poll reading
  five tables costs the modem's login limiter at most one login. A missing
  required table is still an error rather than a healthy empty channel list.

Session cookie:

- The login body carries the SID and the modem may also set it. Because
  `RequestsCookieJar.set` only replaces a cookie with the same domain and
  path, the two coexisted, were sent together, and made every later read of
  the cookie raise `CookieConflictError`. Existing SID cookies are dropped
  before the one from the login body is installed.

Fixtures and docs:

- The captured session token and SID and the OpenSSL reference ciphertexts
  are replaced with synthetic values, which also clears the public secret
  scanner in `tests/test_defensive_review_docs.py`.
- Transport tests now run against a real `requests.Response` over a real
  urllib3 stream rather than asserting a recorded mock call signature, and
  cover the limit, one byte over it, and a gzip payload that is small on the
  wire and 32 MB decoded.
- A parser test pins the 6 MHz raster the captured downstream carriers sit
  on, which is the basis for the injected Annex B symbol rates.
- The public launch page still claimed 20 modem families.
@itsDNNS
itsDNNS force-pushed the feat/sb8200-cbn-driver branch from 0380d1a to ee8cda5 Compare August 18, 2026 10:20
@itsDNNS
itsDNNS merged commit ef1bc45 into itsDNNS:main Aug 18, 2026
11 checks passed
@s0kil

s0kil commented Aug 18, 2026

Copy link
Copy Markdown
Contributor Author

Hi, adding comment so it's easier to find on Github.

ARRIS SURFboard SB8200 DOCSIS 3.1 Cable Modem:
Brand ARRIS
Internet Service Provider Cox, Spectrum, Xfinity
Modem Type Cable
UPC 612572215760
Global Trade Identification Number 00612572215760
Unit Count 1 Count
Manufacturer Vantiva
Number of Items 1
Built-In Media 2-year warranty card, Power Supply, Quick-Start guide, SB8200 Cable Modem
Item Type Name Docsis 3.1 Cable Modem
Item Weight 1.5 pounds
Model Number SB8200
Mfr Part Number SB8200

https://www.amazon.com/dp/B07DY16W2Z

@s0kil
s0kil deleted the feat/sb8200-cbn-driver branch August 18, 2026 13:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants