feat(drivers): add ARRIS SURFboard SB8200 (CBN firmware) support - #800
Merged
Conversation
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
force-pushed
the
feat/sb8200-cbn-driver
branch
from
August 18, 2026 10:20
0380d1a to
ee8cda5
Compare
Contributor
Author
|
Hi, adding comment so it's easier to find on Github. ARRIS SURFboard SB8200 DOCSIS 3.1 Cable Modem: |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
surfboarddriver targets.Registry key
sb8200_cbn, format profilesb8200_cbn_xml. Verified against a live unit on Charter/Spectrum:HwModelSB8200v3, firmwareAC01.01.008_122722_8200.03.07.733Why a new driver rather than extending
surfboardPrior art in #312 (HTML fallback for broken HNAP firmware), #304, #308, #310, #316 and #318 already handles SB8200 firmware variants, but neither of the
surfboarddriver's transports exists on this unit:surfboardtransportPOST /HNAP1/(primary)GET /cmconnectionstatus.html(the #312 fallback)/common_page/login.htmlGET /cmswinfo.html/common_page/login.htmlGET /RgConnect.aspBondedtable forarris_htmlto parseThis 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
surfboardwould mean a third transport inside a driver whoseFORMAT_FAMILIESalready spans two profiles, so a separate driver keeps the one-waydriver -> format profiledependency the architecture doc describes.API surface
HTTPS only (port 80 redirects, HSTS is set).
POST /xml/getter.xmlwithtoken=<sessionToken cookie>&fun=<code>:GlobalSettings-HwModel,AccessLevelcm_system_infodownstreamOFDM_tabledownstream_tableupstream_tableupstreamOFDMA_tablesignal_tablesetter.xml)A sweep of
fun=1..300found no other codes that return data.Authentication reproduces the firmware's
CBN_Encrypt():The
sessionTokencookie 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
Noneand a diagnostic rather than discarding channels the modem did return:302is session loss and may spend the call's single re-authentication200with an empty body means the firmware does not serve that table, and must not spend a login on the modem's rate limiterA 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: identityand any otherContent-Encodingis 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,srateis 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:
signal_tablereports0/0/0for that samedsidReported as measured codewords they put
ds_uncorr_pctat 54.6% and pinned downstream health atcriticalon every poll, emitting a falseerror_spikewarning every 60 seconds. With the lane left counter-unsupported, health readstoleratedand downstream uncorrectables read 7.7k, the real SC-QAM figure. The channel is still reported; only its codeword counters are withheld, soerror_counter_coverageshowsunsupported_channels: 1rather 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=0and no rows, so no captured payload shows the field names. Rather than inventing them, the parser emits anunsupported_lanediagnostic if a row ever appears.Per the data contract,
cm_serial_numberis read by the firmware but deliberately never surfaced in device info, logs, or diagnostics.Testing
tests/test_sb8200_cbn_driver.py, plus 3 boundary cases in the format-case registry.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.requests.Responseover a real urllib3 stream: at the limit, one byte over, and a gzip payload that is small on the wire and 32 MB decoded.200, persistent HTTP and transport failure,302expiry with at most one re-authentication, and preservation of the SC-QAM channels in each case.