A packet-level, bottom-up PowerShell toolkit and a fully evidence-backed case study of a fiber connection that never authenticated — root-caused to the operator’s OLT.
🌐 Language / زبان: English (this document) · فارسی — Persian
PPPoE VLAN GPON Diagnostics is an open-source PPPoE and GPON fiber troubleshooting toolkit for
Windows, written in PowerShell. Use it to find out why a fiber line will not
connect — when the ONT shows a healthy optical link but PPPoE dial-up fails with
Windows error 651 and no PADO packet ever comes back.
It ships 116 diagnostic tools that:
- sweep every 802.1Q VLAN ID from 1 to 4094 looking for a PPPoE Access Concentrator,
- build and decode raw PPPoE Discovery frames (
PADI/PADO/PADR/PADS/PADT), - read Huawei ONT optical power, PON/PLOAM state, GEM ports and T-CONTs over HTTP and the telnet engineering CLI,
- and prove the capture pipeline itself is not blind before you trust a silent result.
The repository also contains the complete, reproducible investigation that used these tools to isolate a real 8-month outage to an ISP-side OLT provisioning gap — plus a ready-to-send hand-off letter you can give your operator.
🔎 Searching for one of these?
PPPoE error 651 fiber·no PADO received·GPON ONU registered but no internet·which VLAN does my ISP use·802.1Q VLAN scanner Windows·Huawei HG8310M telnet commands·ONU state O5 but PPPoE fails·GEM port not provisioned— see the FAQ.
TL;DR — After a full bottom-up, packet-level investigation (VLAN 1–4094 sweep, a fixed-and-proven analyzer, and telnet engineering-CLI diagnostics), the verified root cause is that the OLT registered the ONU (state O5, ONU-ID 43) but never provisioned a data-service GEM port / T-CONT / service-VLAN / bridge via OMCI. The upstream
PADIis therefore dropped at the PON layer, so noPADOreturns on any VLAN. This is an ISP / OLT-side provisioning gap — not a modem, VLAN, firmware, or optical fault, and no ONT firmware upgrade can fix it.
⚠️ Privacy: every credential, serial number, MAC address, public IP and hostname in this repository has been masked (<REDACTED_*>). See §11.
📄 ISP hand-off: a ready-to-send, bilingual (EN + FA) provisioning request for the operator is in
docs/ISP_HANDOFF.md.
Prerequisites: Windows with PowerShell 5.1+ (or
pwsh7+), Npcap installed in WinPcap-compatible mode, and Wireshark (fortshark/dumpcap).
# 0. Clone, and turn on the secret-leak guard (recommended)
git clone https://github.com/0xRadikal/pppoe-vlan-gpon-diagnostics.git
cd pppoe-vlan-gpon-diagnostics
git config core.hooksPath .githooks
# 1. FIRST: prove the toolchain can actually detect a reply.
# A silent VLAN sweep is meaningless if your analyzer is blind.
pwsh -File tools/fvselftest.ps1 # expect: 22 passed, 0 failed
# 2. Point the tools at your own machine (nothing is hard-coded)
$env:FV_MODEM_IP = '192.168.100.1' # your ONT's management IP
$env:FV_MODEM_PW = '<your-password>' # never committed; read from the environment
# 3. Sweep every VLAN for a PPPoE Access Concentrator, then analyse
pwsh -File tools/vlan_scanner.ps1No PADO on any VLAN? Then the problem is upstream of your VLAN tag —
jump straight to §6 · Root Cause and
§7 · What the ISP Must Do.
- Overview
- Topology
- Methodology — Trust Nothing, Verify Everything
- PPPoE / GPON Primer
- Key Findings
- Root Cause (Verified, Not Guessed)
- What the ISP Must Do
- Repository Layout
- Complete Tool Reference (every file, linked)
- Telnet Command Lists
- Security & Privacy
- Status & Remaining Work
- FAQ — Common PPPoE / GPON Questions
- Glossary
- Contributing, License & Citation
A residential GPON fiber line fails to establish a PPPoE session (Windows error 651 = "the remote computer did not respond") and has done so for roughly 8 months, even though the optical layer is perfectly healthy (PLOAM state O5, normal Rx/Tx power).
This repository is the complete, auditable record of the investigation:
- the tooling built to generate and capture packet-level evidence (PowerShell +
C# P/Invoke to
wpcap.dll, driven remotely over a reverse-SSH tunnel — the Windows host has no Python); - the experiments run (a full VLAN 1–4094 discovery sweep, positive-control injection, dial captures, web-UI probing, telnet engineering-CLI diagnostics);
- and the verified root cause with the exact evidence that supports it.
Every claim below is backed by a captured frame or a device-CLI readout. No guessing.
┌────────┐ ┌──────────────────┐ reverse SSH ┌────────────────────┐
│ Agent │──▶│ Ubuntu server │──127.0.0.1:2222─▶│ Windows 10 host │
└────────┘ │ <REDACTED_SERVER_IP> │ tunnel │ <REDACTED_HOSTNAME> │
└──────────────────┘ └─────────┬──────────┘
│ Realtek Ethernet NIC
▼
┌──────────────────────────────┐
│ Hyper-V External vSwitch │
│ "PPPoE-VLAN827" (+ MonTrunk) │
└───────────────┬────────────────┘
▼
┌──────────────────────────────┐
│ Huawei HG8310M ONT │
│ 192.168.100.1 · pure bridge │
└───────────────┬────────────────┘
▼ GPON fiber
┌──────────────────────────────┐
│ Fiberhome OLT (ISP) │
└───────────────┬────────────────┘
▼
Internet / BRAS
Injection / sniffing plane: because the physical NIC is hidden beneath the
Hyper-V vSwitch and invisible to Npcap, a second trunk-mode management vNIC —
MonTrunk — is attached to the same vSwitch as an inject-and-sniff port
(Npcap device \Device\NPF_{…E684ADD}). All raw 802.1Q-tagged PPPoE frames are sent
and captured there.
The investigation followed the OSI stack bottom-up (L1 → L7) and applied two independent techniques to make every result trustworthy rather than merely plausible:
| # | Principle | How it was enforced |
|---|---|---|
| 1 | Positive control | Before trusting any "0 PADO" result, synthetic PADO/PADS frames were injected on VLAN 827 to prove the detector is not blind (see positive_control.ps1). |
| 2 | Cross-layer corroboration | The L2 sweep result was cross-checked against the ONT's own GPON engineering-CLI counters over telnet — two independent sources agreeing. |
| 3 | Well-formedness proof | Our own PADIs were verified to be correctly 802.1Q-tagged (eth.type==0x8100, vlan.etype==0x8863) before concluding "no reply" (see deep_frame_probe.ps1). |
| 4 | Field-name verification | The tshark filter fields themselves were validated — which uncovered a real analyzer bug (below). |
The original analyzer filtered on pppoed.code==0x07. That field does not exist:
the protocol filter is pppoed, but the code field lives on the parent PPPoE
layer and is pppoe.code (Host-Uniq is pppoed.tags.host_uniq).
The failure mode is worse than returning zero — tshark aborts the whole command:
$ tshark -r capture.pcapng -Y "pppoed.code==0x07"
tshark: Neither "pppoed.code" nor "0x07" are field or protocol names.
$ tshark -r capture.pcapng -T fields -e pppoed.code
tshark: Some fields aren't valid:
pppoed.code # ← no output at allBecause the callers redirect stderr to $null and run with
$ErrorActionPreference='SilentlyContinue', that error was invisible: the
operator saw an empty result and read it as "no PADO", when in reality no search
ran at all. A real PADO would have been missed.
- After the fix, the positive control correctly reported
PADO=6 / PADS=6and extractedvlan.id=827— proving the analyzer now works. - The full 4094-VLAN sweep was then re-analysed and still showed 0 PADO everywhere — now a trustworthy zero.
This is exactly why the "check your own scanner, maybe it has a bug" instruction mattered: the suspicion was correct, and the bug was real.
A later audit of the published tree found the correction had only been applied to
2 of 32 scripts — 26 files still used the invalid pppoed.code, and
scanner_selfcheck.ps1 also used a non-existent pppoed.tag.host_uniq (the
correct name is plural: pppoed.tags.host_uniq). Those files were silently
producing no output. All of them are now fixed, and the toolchain ships a
self-test that makes this class of bug impossible to reintroduce unnoticed:
.\tools\fvselftest.ps1 # exit 0 = a "0 PADO" result is meaningfulfvselftest.ps1 synthesises a capture that provably contains a
PADI/PADO/PADR/PADS/PADT set, then asserts that every field name and display
filter used in this repository finds exactly what it should (22 assertions). It
requires only tshark — no ONT, no Npcap, no hardware. If it fails, no capture
result from these tools may be trusted.
diag_fields.ps1intentionally still probes both spellings — that is the tool whose job is to demonstrate which name is correct.
PPPoE Discovery stage (click to expand)
| Frame | Code | EtherType | Direction |
|---|---|---|---|
| PADI — Initiation | 0x09 |
0x8863 (discovery) |
client → broadcast |
| PADO — Offer | 0x07 |
0x8863 |
server → client |
| PADR — Request | 0x19 |
0x8863 |
client → server |
| PADS — Session-confirm | 0x65 |
0x8863 |
server → client |
| PADT — Terminate | 0xa7 |
0x8863 |
either |
| session data | — | 0x8864 (session) |
both |
If the client sends PADI and never receives PADO, Windows reports error 651.
The whole investigation is essentially: "why does our PADI never earn a PADO?"
802.1Q VLAN tagging & GPON PLOAM state machine (click to expand)
- 802.1Q tag EtherType =
0x8100; VLAN 827 =0x033b. - GPON PLOAM registration:
O1 → O2 → O3 (ranging / SN) → O4 → O5 (operational). ONU-ID is assigned duringO3 → O4. - After registration the OLT pushes the data service (GEM port + T-CONT + service-VLAN + bridge mapping) to the ONU via OMCI. This is the step that is missing here.
One 802.1Q-tagged PADI was transmitted per VLAN (Host-Uniq encodes the VLAN id
so any reply could be attributed to its VLAN). Result:
VLAN 827 is therefore not "the wrong VLAN" — no VLAN answers at all. The fault lies below the PPPoE/VLAN layer.
Telnet (port 23) is open; engineer shell reached via <TELNET_USER> → su → SU_WAP>:
| Command | Result | Interpretation |
|---|---|---|
display onu info |
onuid:43 · reg status:O5 |
ONU is fully registered; OLT accepted the SN & assigned ONU-ID 43 |
| PLOAM log | O1→O2→O3→O4→O5 |
Ranging + registration succeeded → SN/PLOAM auth is NOT the problem |
display optic |
Rx ≈ −24.2 dBm / Tx ≈ +2.3 dBm | Optics normal |
display pon statistics (2 snapshots) |
Rx gem +15 000, Tx gem frozen, Tx octets = 0, Tx unicast = 0 |
Downstream floods arrive; no user data ever leaves upstream |
display poncnt gemport upstatistic |
ERROR 0xfc000105 |
No upstream data GEM port exists |
display wan layer all |
(empty) | Pure bridge (expected) |
display macaddress |
PC MAC learned on Port 1 / VLAN 1 | LAN → ONT bridge path is healthy |
The single most damning number: Tx octets = 0 — over its entire uptime the ONU
has sent zero user bytes upstream (only OMCI management traffic). There is simply
no upstream data path for a PADI to travel through.
The ONT is optically synced and GPON-registered (
O5,ONU-ID 43), but the OLT has NOT provisioned a data-service GEM port + T-CONT + service-VLAN + bridge mapping (normally pushed via OMCI after registration) for this ONU.Consequently the upstream
PADIis dropped at the PON layer, so noPADOever returns — on any VLAN. This is an OLT-side (ISP) service-provisioning gap, not a modem-config, VLAN, firmware, or optical fault.
The missing piece (GEM / T-CONT / service-VLAN profile) is pushed by the OLT via OMCI. No ONT firmware version can synthesise an upstream data service the OLT has not provisioned. Firmware may be upgraded for stability, but it cannot substitute for OLT-side service configuration.
The ONU is registered — ONU-ID 43, state O5, SN <REDACTED_SN>, Rx ≈ −24.2 dBm.
Please add / repair the DATA service for this ONU on the OLT:
- a GEM port bound to a T-CONT,
- carrying the correct service VLAN,
- with a bridge / line-profile so PPPoE (EtherType
0x8863/0x8864) is carried upstream to the BRAS.
Currently there is no upstream data GEM (Tx octets = 0), so the PPPoE PADI
never reaches the BRAS and no PADO is ever returned.
📄 A ready-to-send, bilingual (English + Persian) version of this request — with the full evidence table — is provided in
docs/ISP_HANDOFF.md.
pppoe-vlan-gpon-diagnostics/
├── README.md ← this document (English)
├── README.fa.md ← Persian translation (نسخهٔ فارسی)
├── .gitignore ← excludes secrets & packet captures
├── docs/
│ ├── SESSION_LOG.md ← full, timestamped investigation log + root cause
│ └── ISP_HANDOFF.md ← bilingual (EN+FA) ISP provisioning hand-off
├── telnet-cmds/ ← read-only telnet command lists (see §10)
│ ├── tcmds_read.txt
│ ├── tcmds_omci.txt
│ ├── tcmds_omci2.txt
│ ├── tcmds_flow.txt
│ └── tcmds_gem.txt
└── tools/ ← 116 PowerShell + shell tools (see §9)
├── fvconfig.ps1 ← shared config: resolves MAC / tshark / Npcap / creds
└── fvselftest.ps1 ← proves the toolchain is not blind (run this first)
Every environment-specific value (host MAC, Npcap device GUID, Wireshark path,
passwords) is resolved at run time by tools/fvconfig.ps1.
Earlier revisions hard-coded a real MAC and a machine-specific Npcap GUID, and —
after the public scrubbing pass — left literal <REDACTED_MAC> /
<REDACTED_WEB_PW> strings being used as live values, which produced
tshark: Syntax error near "<" and failed web logins. Nothing is hard-coded now,
and an unresolvable value raises a clear error instead of failing silently.
| Variable | Purpose | Default |
|---|---|---|
FV_SRC_MAC |
MAC used to send PADIs / identify our own frames | auto-detected from the first Up physical adapter |
FV_ADAPTER |
adapter name to derive the MAC and Npcap GUID from | — |
FV_MONTRUNK_MAC |
Hyper-V monitor/trunk vNIC MAC | unset → clause omitted from filters |
FV_NPCAP_DEV |
capture/inject device, e.g. \Device\NPF_{GUID} |
derived from FV_ADAPTER |
FV_NPCAP_DEV_PHYS / FV_PHYS_ADAPTER |
physical NIC below the vSwitch (where the vSwitch's 802.1Q tag is visible) | first Up physical adapter, else FV_NPCAP_DEV |
FV_NPCAP_DEV_VNIC / FV_VNIC_ADAPTER |
VLAN vEthernet vNIC above the vSwitch | FV_NPCAP_DEV |
FV_WIRESHARK_DIR |
Wireshark folder | PATH, then the standard install locations |
FV_MODEM_IP / FV_MODEM_USER |
ONT web UI | 192.168.100.1 / telecomadmin |
FV_MODEM_PW |
ONT web password — required, never committed | none (raises an error) |
FV_TELNET_CREDS / FV_TELNET_CREDS_FILE |
telnet creds as user:pass |
falls back to the web credentials |
# 1. verify the toolchain can actually detect a reply (no hardware needed)
.\tools\fvselftest.ps1
# 2. point the tools at this machine
$env:FV_ADAPTER = 'MonTrunk'
$env:FV_MODEM_PW = '<ont-web-password>'
. .\tools\fvconfig.ps1 ; Show-FvConfig # prints resolved values, never the password
# 3. sweep, then analyse
.\tools\vlan_scanner.ps1 -Start 1 -End 4094
.\tools\scan_analyze.ps1 -Pcap C:\Windows\Temp\scan_1_4094.pcapng
⚠️ do_reboot.ps1is the only service-interrupting tool. It now requires explicit confirmation — use-WhatIfto preview,-Forcefor unattended runs.
📄 Full narrative: docs/SESSION_LOG.md
· ISP hand-off: docs/ISP_HANDOFF.md
· Persian README: README.fa.md
Every file under tools/ is
listed below, grouped by purpose, with a direct link and a description of what it
does and why it exists. The tools were built iteratively during a live
investigation, so some groups contain several evolutionary variants of the same idea
— these are all kept for a complete, auditable record and are marked accordingly.
Legend: ⭐ = primary/decisive tool · 🔁 = evolutionary variant/iteration · 🧪 = one-off experiment
| Tool | What it does |
|---|---|
⭐ fvselftest.ps1 |
Run this first. Builds a synthetic capture that provably contains PADI/PADO/PADR/PADS/PADT, then asserts that every field name and display filter this repo uses finds exactly what it should (22 assertions). Needs only tshark — no ONT, no Npcap, no hardware. Exit 0 is the precondition for trusting any "0 PADO" result. |
⭐ fvconfig.ps1 |
Shared configuration module (dot-sourced by every tool). Resolves the source MAC, Npcap device, tshark/dumpcap paths and modem/telnet credentials at run time from environment variables or live adapter discovery, and throws when a value cannot be resolved — so no placeholder can ever be used as a live value. Show-FvConfig prints what was resolved (never the password). See §8.1. |
⭐ vlan_scanner.ps1 |
The VLAN scanner. C# P/Invoke to wpcap.dll; sends one 802.1Q-tagged PADI per VLAN on the MonTrunk trunk port, encoding the VLAN id in the Host-Uniq tag ("VL"+hi+lo) so replies can be attributed. Params -Start 1 -End 4094, 8 ms spacing. This is the engine of the full 1–4094 sweep. |
⭐ scan_analyze.ps1 |
The analyzer (bug-fixed). tshark-based; counts PADO/PADS and extracts VLAN + Host-Uniq. Contains the critical pppoed.code → pppoe.code fix for Wireshark 4.6.5 — now applied across all analyzer scripts, not just this one (see the follow-up audit). Usage: scan_analyze.ps1 -Pcap <capture.pcapng>. |
⭐ positive_control.ps1 |
Proves the analyzer is not blind. Injects synthetic PADO (0x07) + PADS (0x65) frames on VLAN 827 (fake server MAC → our MAC) via wpcap, then confirms scan_analyze.ps1 detects them. Validated PADO=6/PADS=6. |
⭐ diag_fields.ps1 |
Field-name discovery. Runs the positive-control pcap through both pppoed.code and pppoe.code filters to prove which field name is correct in tshark 4.6.5. This is how the analyzer bug was pinned down. |
⭐ deep_frame_probe.ps1 |
Proves our PADIs are well-formed. Confirms eth.src==our MAC, eth.type==0x8100, vlan.etype==0x8863 — i.e. our probes really are correctly VLAN-tagged PPPoE-discovery frames before we conclude "no reply". |
⭐ telnet_login2.ps1 |
The telnet key. Raw-TCP telnet client (no telnet.exe dependency) with IAC negotiation and a credential-fallback list; on success runs ? to enumerate the CLI and probes su (engineer mode). Read-only. (Working credentials masked.) |
⭐ telnet_run.ps1 |
The telnet workhorse. Generic runner: logs in, enters su (SU_WAP>), then executes a list of commands from a -CmdFile and prints each command + output. Reads credentials from an external file (not hard-coded). Used with the telnet-cmds/ lists. |
⭐ finalize.ps1 |
Restore-to-stable. Returns the main vNIC to untagged (so the modem web panel + management path stay reachable), keeps MonTrunk enabled, and writes a ready-to-run "go-live" script to re-tag VLAN 827 and dial the moment the ISP provisions the service. |
| Tool | What it does |
|---|---|
caps.ps1 |
Reports Windows edition, Hyper-V feature/module state, and NIC driver info — a capability snapshot of the host. |
mkvsw.ps1 |
Creates the External Hyper-V vSwitch bound to the Realtek NIC (AllowManagementOS=true) and resets the driver-level RegVlanid to 0. |
vsw.ps1 |
Lists existing VM switches, host vNICs, and all net adapters (including hidden/virtual). |
trunkcap.ps1 |
Adds the second management vNIC MonTrunk on the same vSwitch in trunk mode (AllowedVlanIdList 1-1000) so 802.1Q tags are visible to capture. |
cleanmon.ps1 |
Disables all L3 protocol bindings on MonTrunk (but not Npcap) so RAS/PPPoE can't accidentally use it — keeps it a pure sniffer. |
chkmon.ps1 |
Prints the current protocol bindings on MonTrunk and PPPoE-VLAN827. |
whichnic.ps1 |
Maps every adapter's MAC/ifIndex/status/description — used to identify which vNIC is which. |
iflist.ps1 |
Lists dumpcap capture interfaces and maps NetAdapter name → GUID (needed to build the \Device\NPF_{GUID} path). |
caplist.ps1 |
Prints the dumpcap version and its numbered capture-interface list. |
pychk.ps1 |
Checks whether Python / Scapy exist on the host — they don't, which is why the C#/wpcap approach was chosen. |
inspect.ps1 |
Inspects NIC filter-driver stacking, ESET/firewall processes, Windows Firewall profiles, and RAS/PPPoE phonebook entries. |
| Tool | What it does |
|---|---|
setvlan.ps1 |
Reads and sets the driver-level RegVlanid (VLAN 827) advanced property on the physical Ethernet adapter. |
vlandef.ps1 |
Shows the current RegVlanid value and the IP config on Ethernet. |
vlanverify.ps1 |
Confirms RegVlanid and restarts the adapter to force the VLAN setting to apply. |
vlanproof.ps1 |
Clears ARP and pings the modem to prove the VLAN vNIC path works. |
vlantest.ps1 🧪 |
Captures and counts how many frames actually carry a VLAN tag — an early check that tagging is happening. |
untag.ps1 |
Sets the PPPoE-VLAN827 vNIC back to untagged and re-tests the modem HTTP GET (restore reachability). |
vsweep.ps1 🔁 |
Parametrised access/trunk VLAN setter (-Mode access -Vlan 827) used as a building block by the sweep scripts. |
multisweep.ps1 🔁 |
Dials across a small explicit VLAN list (e.g. 838,10,11,20,100) and reports PPPoE results — a pre-cursor to the full scanner. |
quicksweep.ps1 🔁 |
Fast two/three-VLAN dial sweep for quick spot-checks. |
bgsweep.ps1 🔁 |
Background sweep: sets a VLAN, dials, captures, and analyzes — an automation harness. |
run_827.ps1 🔁 |
Sets the vNIC to VLAN 827, dials, captures on MonTrunk, and reports PPPoE codes. |
run_311.ps1 🔁 |
Same harness targeting VLAN 311 (candidate VLAN test). |
run_312.ps1 🔁 |
Same harness targeting VLAN 312 (candidate VLAN test). |
run_unt.ps1 🔁 |
Same harness in the untagged case (PC untagged, ONT tags). |
run827.ps1 🔁 |
Minimal one-liner wrapper that invokes the VLAN-827 run. |
These scripts set VLAN 827, start a dumpcap capture on MonTrunk, then trigger a
PPPoE dial (rasdial "Broadband Connection") so a real PADI is emitted and any
reply would be captured. They differ mainly in how aggressively they dial and how
wide the capture window is.
| Tool | What it does |
|---|---|
cap_dial.ps1 🔁 |
Baseline: start capture → one dial attempt → stop → keep pcap for analysis. |
cap2.ps1 🔁 |
Capture + single dial, filtering the rasdial output for error/success codes. |
cap2way.ps1 🔁 |
Two-way capture variant that reads creds and reports the dial line + PPPoE frames. |
cap3.ps1 🔁 |
Capture + dial specifically on VLAN 827. |
cap827.ps1 🔁 |
Captures on the physical Realtek (below the vSwitch) to see the tag the vSwitch applies. |
captest.ps1 🧪 |
Sanity test of dumpcap itself, routing stderr to a file to debug capture start-up. |
dial827.ps1 🔁 |
Just dials over the VLAN-827 vSwitch and prints the rasdial status (no capture). |
dbg_dial.ps1 🔁 |
Parametrised (-Vlan) debug dial: enables MonTrunk, sets VLAN, captures 16 s, dials, then dumps all PPPoED frames + distinct PADI source MACs. |
mondial.ps1 🔁 |
Finds the MonTrunk NPF device, captures, and dials — monitor-port dial harness. |
megadial.ps1 🔁 |
Hammer dial: 45 s capture, dials 6× to guarantee multiple PADI bursts, then summarises our_PADI / PADO / inbound_real_PPPoED (filtering out our own + MonTrunk MAC). |
proof_padi.ps1 ⭐ |
Dials 3× inside a 22 s window and proves our tagged PADI (0x09, vlan 827) frames actually appear on the wire, then reports PADO count and restores untagged. |
final_probe.ps1 🔁 |
Late-stage combined set-VLAN → capture → dial → analyze probe. |
passive_listen.ps1 🧪 |
Passive listen only: main vNIC untagged, just observe what the network/ONT emits unsolicited. |
physcap_test.ps1 🧪 |
Disables MonTrunk so RAS is forced onto the tagged path, then captures — a control for "is MonTrunk interfering?". |
scenarioB.ps1 🧪 |
Scenario B (PC untagged, ONT tags): dials untagged and compares what the PC sends (vEthernet-827) vs what appears on the trunk — tests whether the ONT itself should add the tag. |
scenarioB2.ps1 🧪 |
Scenario B refinement: 22 s capture, dials twice to generate multiple PADI bursts, then lists all PPPoE frames + distinct VLAN ids seen. |
| Tool | What it does |
|---|---|
analyze.ps1 🔁 |
Minimal: total packet count in a pcap. |
analyze2.ps1 🔁 |
Details PPPoE-Discovery frames and decodes the code byte (0x09/0x07/0x19/0x65). |
analyze3.ps1 🔁 |
Adds a protocol-hierarchy view plus PPPoE VLAN + code listing. |
analyze_ms.ps1 🔁 |
Per-file tallies of tagged PADI, any PADI, PADO, and inbound PPPoED (note: pre-fix field names). |
analyze_sweep.ps1 🔁 |
Batch analyzer that walks each sweep pcap and dumps raw PPPoED frame data. |
scanner_selfcheck.ps1 🔁 |
Early self-check of the scanner/analyzer chain (superseded by deep_frame_probe + positive_control). |
b2dump.ps1 🧪 |
Dumps every frame in a small capture with type/proto/src/dst/vlan. |
diag2.ps1 🧪 |
Lists all frames with eth.type + src/dst + protocol (quick triage). |
montags.ps1 🧪 |
Lists all frames by direction/vlan/ethertype and isolates frames not sourced from our MAC (i.e. genuinely inbound). |
inspect_inbound.ps1 🧪 |
Focuses on inbound PPPoED frames (not our MAC) in a capture. |
ipv6_detail.ps1 🧪 |
Inspects IPv6 source + ICMPv6 types (rules out IPv6 RA noise being mistaken for a reply). |
framedump.ps1 🧪 |
Dumps the modem web-UI frame.asp menu items (targets/links) — web-UI mapping helper. |
diag1.ps1 🧪 |
Starts a background capture for ad-hoc diagnosis. |
| Tool | What it does |
|---|---|
⭐ telnet_login2.ps1 |
Raw-TCP telnet login with IAC negotiation + credential fallback list; enumerates the CLI (?) and probes su. Read-only. |
⭐ telnet_run.ps1 |
Generic command runner: login → su (SU_WAP>) → run commands from a -CmdFile → print output. Creds read from an external file. |
telnet_probe.ps1 🔁 |
First telnet probe (raw socket, no telnet.exe); logs in and issues only safe read/help commands to learn the CLI. Superseded by telnet_login2 + telnet_run. |
All of these authenticate to the HG8310M web UI (GetRandCount.asp token → base64
password → POST /login.cgi, using the working web-admin account) and then GET
status pages. They are read-only; the shared login pattern lives in
hwlib.ps1.
| Tool | What it does |
|---|---|
hwlib.ps1 ⭐ |
Shared library: HW-Connect (login, session in $global:HWS) + HW-Get (authenticated GET). Dot-sourced by the probes below. |
modem_login_test.ps1 |
Verifies the web-UI login flow works and reports the result. |
modem_inline.ps1 |
Self-contained inline login (documents the exact token/base64/cookie/POST sequence). |
modemping.ps1 |
Times ICMP ping + raw HTTP GET latency to the modem. |
modemretry.ps1 |
Waits for the web server to recover from locked sessions, then retries TCP 80 + a fresh GET. |
modemstat.ps1 |
Logs in and pulls general modem status pages. |
probe_modem.ps1 |
Tests which device/optic/eth info pages actually return content (page discovery). |
d2.ps1 🧪 |
Scrapes index.asp for frames/iframes and all .asp/.html/.cgi links (site-map). |
d3.ps1 🧪 |
Login + page-fetch iteration variant. |
d4.ps1 🧪 |
Login + targeted page-fetch variant. |
discover.ps1 🧪 |
Extracts the frame/menu structure from index.asp. |
menuscan.ps1 🧪 |
Enumerates the web-UI menu tree after login. |
framefull.ps1 🧪 |
Lists all .asp paths referenced anywhere in frame.asp. |
wanpages.ps1 🧪 |
Fetches candidate WAN/PPPoE config pages. |
wancheck.ps1 🧪 |
Checks the WAN configuration page(s) for existing PPPoE/bridge setup. |
wan_deep_check.ps1 🧪 |
Deeper WAN-layer inspection via the web UI. |
wan_urls_probe.ps1 ⭐ |
Probes ~24 standard Huawei WAN/PPPoE/VLAN config-page paths across firmware variants and reports which actually EXIST (HTTP 200 + non-trivial length). Confirms this pure-bridge unit exposes no WAN service page. |
read_reset_page.ps1 🧪 |
Reads reset.asp to enumerate reboot/restore-default forms & CGI actions (read-only; nothing triggered). |
gpon_auth_probe.ps1 ⭐ |
Read-only probe for a GPON auth-config page (SN / PLOAM / LOID / equipment-id). Result: no such page in this web UI; get.cgi returns a stub. |
modem_config_backup.ps1 ⭐ |
Full config backup downloader: logs in and pulls the encrypted hw_ctree.xml (tries several backup endpoints), saving a timestamped copy. Read-only — intended to back up before any future change. |
| Tool | What it does |
|---|---|
read_sn_optic.ps1 ⭐ |
Reads device info (SN / model / versions) from deviceinfo.asp and optical info (Tx/Rx power, temperature, bias) from opticinfo.asp. |
optic.ps1 🔁 |
Pulls the optic-info page and prints Tx/Rx power. |
optic2.ps1 🔁 |
Optic reader variant (works around routing to reach the modem directly). |
opticfind.ps1 🔁 |
Locates the correct optic-info page across firmware layouts. |
optread.ps1 🔁 |
Reads and parses optical power values. |
optfinal.ps1 🔁 |
Final consolidated optic reader. |
fulloptic.ps1 🔁 |
Full optical dump plus all index.asp hrefs/InitValue pages. |
deepoptic.ps1 🔁 |
Robust optic reader with login retries. |
sninfo.ps1 🔁 |
Extracts SN / serial / equipment-id tokens from device-info pages. |
ponstate.ps1 ⭐ |
Reads PON/registration state indicators from the web UI. |
linkstate.ps1 🔁 |
Reads optical LinkStatus / LinkTime from the web UI. |
dump.ps1 🧪 |
Logs in and grabs the optic-info page in one shot (quick dump). |
extract_vals.ps1 🧪 |
Extracts interesting JS variable assignments (values) from web pages. |
media_check.ps1 🧪 |
Polls the PPPoE-VLAN827 vNIC media/connection state over time after setting VLAN 827. |
| Tool | What it does |
|---|---|
backup.ps1 ⭐ |
Saves the current network state (adapters, vSwitch, advanced props, RegVlanid) so every change is revertible. |
state.ps1 |
Quick state: which iface holds 192.168.100.2, plus a modem ping. |
state_now.ps1 |
Snapshot of vSwitch, ManagementOS vNIC VLAN mode, and all net adapters. |
verify_state.ps1 ⭐ |
Verifies the management path (Wi-Fi/Tailscale up) + vSwitch/vNIC VLAN state — the pre/post-change safety check. |
verify2.ps1 |
Confirms SSH/host alive + management adapters up. |
chk.ps1 |
Confirms tagging works: physical NIC IP gone, VLAN-827 vNIC present, untagged modem unreachable from tagged vNIC. |
routes.ps1 |
Lists all non-loopback IPv4 interfaces and the route to 192.168.100.1. |
fixroute.ps1 |
Adds a low-metric on-link route for the modem subnet via the VLAN-827 vNIC. |
xray.ps1 |
Inspects the xray_tun adapter routes/metrics (a co-existing tunnel on the host). |
do_reboot.ps1 |
Ensures untagged (panel reachable after boot) then reboots the modem via the web UI. The only state-changing modem action; used deliberately, with the management path preserved. |
⭐ finalize.ps1 |
Restore-to-stable + writes the "go-live" script (see §9.0). |
| Tool | What it does |
|---|---|
poc_send.ps1 ⭐ |
Proof-of-concept C# P/Invoke to wpcap.dll (pcap_open_live / pcap_sendpacket / pcap_close) that sends one raw 802.1Q-tagged PADI frame — the injection primitive the scanner is built on. |
poc_verify.ps1 ⭐ |
Sends a frame and verifies it was captured — proves the inject+sniff loop on MonTrunk works end-to-end. |
These Bash helpers drive the Windows host from the Ubuntu server. They read
credentials from local *.env files at runtime (via awk) and never hard-code
or print them.
| Tool | What it does |
|---|---|
win.sh ⭐ |
Runs a single command on the Windows host over the reverse-SSH tunnel (sshpass + port 2222). |
ps.sh ⭐ |
Executes a PowerShell script from stdin on Windows, base64-encoding it (UTF-16LE -EncodedCommand) to avoid escaping issues. |
runfile.sh ⭐ |
scp's a local .ps1 to the Windows host and executes it — the reliable path for long scripts (used for the scanner/analyzer). |
putcreds.sh |
Writes the PPPoE credential drop-file on Windows via stdin (never on the command line). |
Read-only command lists fed to telnet_run.ps1
(-CmdFile). They target the HG8310M engineer shell (SU_WAP>).
| List | Purpose / key commands |
|---|---|
tcmds_read.txt |
Core read-outs: display version / inner version / sysinfo / sn / optic / onu info / access mode / optmode, get rogue status, display pppoe_em result, omcicmd alarm show, display pon statistics, display wan layer all, display startup info. |
tcmds_omci.txt |
Minimal OMCI MIB dump: omcicmd mib show. |
tcmds_omci2.txt |
Deeper OMCI MIB (mib show 0/256/266/268/281), show flow/qos, pm show, display oam l2shell ethvlan, display flow. |
tcmds_flow.txt |
Flow / port / MAC counters: omcicmd show flow 0, display port statistics, display macaddress, display lanmac/wanmac/productmac, display onu info, display pon statistics. |
tcmds_gem.txt |
GEM / T-CONT decisive probes: omcicmd show flow/qos all, get poncnt upgemport, display poncnt gemport upstatistic, display poncnt up/dnstatistic, display wan layer all, display access mode. This is where ERROR 0xfc000105 (no upstream data GEM) came from. |
This is a public repository. Before publishing, every secret was masked and verified absent from the working tree, the commit blobs, and the live remote:
| Secret | Handling |
|---|---|
| Web-admin password | → <REDACTED_WEB_PW> (30 files) |
| Telnet password(s) | → <REDACTED_TELNET_PW*> |
| GPON serial number | → <REDACTED_SN> / <REDACTED_SN_SUFFIX> |
| Host MAC address | resolved at run time (see §8.1) |
| Public server IP / hostname | → <REDACTED_SERVER_IP> / <REDACTED_HOSTNAME> |
| Server-side secret file paths | genericised |
Audit correction. The first public revision claimed every MAC was masked, but the real host MAC survived as a raw byte array in four scripts (
[byte[]]@(0x..,0x..,...)invlan_scanner.ps1,positive_control.ps1,poc_send.ps1,poc_verify.ps1) and the Hyper-V vNIC MAC appeared in five more, because the scrubbing pass only matched colon-separated notation. Those literals have been removed — the MAC is now discovered from the adapter at run time, so the masking claim is finally accurate. A machine-specific Npcap device GUID was removed for the same reason.
- Shell helpers read credentials from local
*.envfiles at runtime; nothing is hard-coded. - Passwords come from environment variables (
FV_MODEM_PW,FV_TELNET_CREDS) and a missing value raises an error — the scripts can no longer send a placeholder string as a password and mistake the failed login for an empty response. .gitignoreexcludes all*.env, credential drop-files (pc.txt,mc*.txt), tokens, and packet captures (*.pcapng) so they can never be committed.- The GitHub token used to push is never stored in git config or committed — it is supplied only in the push URL at push time.
These scripts are diagnostic tools for the owner's own equipment. The default credentials they reference are widely documented vendor defaults; they are masked here purely as good hygiene for a public repo.
The tree is checked with PSScriptAnalyzer 1.25.0 plus an AST parse of every
script. The pass below was mechanical but verified, not assumed: each rewrite was
applied by walking the PowerShell token stream (never a blind regex), so words
such as Select inside comments, strings, here-strings, hash keys and property
accesses were provably left alone, and every file was byte-compared afterwards.
| Finding | Before | After | Why it mattered |
|---|---|---|---|
| Total findings | 150 | 36 | see the accounting of the remainder below |
PSAvoidUsingComputerNameHardcoded (Error) |
4 | 0 | the modem IP was baked into Test-Connection/Test-NetConnection |
PSAvoidUsingCmdletAliases |
71 → really 73 | 0 | Select/Where/Sort are not defined in every host; the expanded names always are |
PSUseBOMForUnicodeEncodedFile |
24 | 0 | the Persian banners rendered as mojibake under Windows PowerShell 5.1, which reads a BOM-less file with the ANSI code page |
PSUseDeclaredVarsMoreThanAssignments |
13 | 0 | two of these were real bugs, see below |
PSAvoidUsingEmptyCatchBlock |
2 | 0 | the swallowed errors are intentional and are now documented as such |
The analyzer itself undercounts, so it was not trusted either. Run on Linux, PSScriptAnalyzer reported 71 aliases; the true figure is 73.
Sort,Group,cat,ls,sleepand friends are aliases only on Windows PowerShell hosts, so on Linux the rule simply cannot see them (routes.ps1andxray.ps1each hid oneSort). The counts above were therefore re-derived independently by walking the AST token stream and matching onTokenFlags.CommandName— which is host-independent — and that count is what the table reports.
Two of the "unused variable" findings were not cosmetic at all:
- The retry result was discarded. In
run_311/312/827/unt.ps1andvsweep.ps1a failed dial triggered a second attempt into$d2, but the report printed the first attempt's$r1, so a successful retry was reported as a failure.$r1is now recomputed from the retry. - A second dial was never reported.
scenarioB2.ps1dialled twice on purpose but only ever printeddial1; it now prints both.
The modem address is no longer hard-coded anywhere: all 87 occurrences across
46 files now resolve through Get-FvModemIp (FV_MODEM_IP), leaving only the
single default inside fvconfig.ps1.
The admin account had the same defect, and it was found by the new commit
hook rather than by reading the code. FV_MODEM_USER was advertised in §8.1 as
an override, but only 3 of the 33 scripts actually called Get-FvModemUser; the
other 30 posted a literal UserName=telecomadmin, so exporting the variable
changed nothing — a silent no-op of exactly the same shape as the modem-IP bug.
All 27 login bodies now interpolate $(Get-FvModemUser). Every one of those
files already dot-sourced fvconfig.ps1, which was verified before the change,
and the override was then confirmed end-to-end (default → root → admin@isp →
default).
A trap worth recording. PowerShell variable names are case-insensitive, so
$modemIpand$ModemIPare the same variable. Adding a naive$modemIp = Get-FvModemIpline to the telnet scripts silently overwrote the caller's-ModemIPargument. Those scripts now useif (-not $ModemIP) { $ModemIP = Get-FvModemIp }, and the precedence (explicit argument →FV_MODEM_IP→ default) is covered by a test.
These are not a backlog. Every one was read at its call site and left in place on purpose, because the mechanical "fix" would have made the code worse.
| Rule | Left | Where | Why it stays |
|---|---|---|---|
PSAvoidUsingPositionalParameters |
21 | all in fvselftest.ps1 |
Every hit is a call to Assert-Equal, a local three-parameter test helper defined in that same file (-Name -Actual -Expected) — not a cmdlet. Assert-Equal 'PADI' (Count-Filter …) 1 is the readable form; naming all three would bury the assertions in noise. |
PSUseShouldProcessForStateChangingFunctions |
5 | New-TestFrame, New-PADI ×3, New-PPPoEDisc |
The rule fires on the verb, not on behaviour. These are pure functions: they write bytes into a MemoryStream and return a byte[]. They touch no file, registry, service or NIC, so -WhatIf would be meaningless. (The functions that do change state — reboot, VLAN set — already have ShouldProcess; see §8.1.) |
PSAvoidGlobalVars |
4 | $global:base, $global:HWS (hwlib.ps1), $global:S (read_reset_page.ps1) |
Cross-scope reuse is the feature. hwlib.ps1 is dot-sourced by five probes that must share one authenticated modem session; $global:S deliberately leaves the logged-in session in the caller's shell for interactive follow-up. Scoping them to $script: would break the dot-source contract. |
PSUseApprovedVerbs |
4 | HW-Connect, HW-Get, HW-Try, Count-Filter |
Renaming is a breaking change to a published API: the HW-* helpers are documented in §10 of this file and called from six scripts. Count-Filter is a private one-liner inside the self-test. No approved verb conveys "HuaweiWeb-authenticated GET". |
PSUseSingularNouns |
2 | Convert-FvMacToBytes, Get-FvSrcMacBytes |
The plural is factually correct — both return a 6-element byte[], and the singular …ToByte would actively mislead a reader into expecting one byte. Renaming would also break four call sites. |
In short: the 4 Errors and every finding that indicated a real defect were fixed; what remains is 36 style opinions that conflict with either correctness or a documented interface, and are recorded here rather than silently churned.
Removing the leaked values once is not enough — nothing stopped them coming
back. .githooks/pre-commit
scans only the staged content and refuses the commit if a live value
reappears. Enable it once per clone:
git config core.hooksPath .githooksIt blocks a hard-coded modem IP, an Npcap adapter GUID, any bare GUID, a
literal password, a real MAC address, a literal telecomadmin, an inlined PPPoE
subscriber account, and any *.env file. It deliberately allows the
Get-Fv* indirections, the single default inside fvconfig.ps1, the
documentation placeholders (aa:bb:cc:dd:ee:ff, 02:00:00:00:00:01,
ff:ff:ff:ff:ff:ff), $(Get-FvModemUser) subexpressions, and descriptive
comments. Markdown is exempt from the "which address/account is named" rules
only — this report is about this equipment, so the prose may name them — but a
GUID, MAC or password is still blocked in Markdown too.
The hook is a security control, so it was tested like one rather than assumed to
work: 13 hostile inputs were all caught and 9 legitimate ones all passed, with
zero false positives. Two real defects in the hook itself were found and fixed
that way — grep -n prefixes each candidate with <lineno>:, which silently
defeated a ^\s*# comment allow-list, and the hook flagged its own pattern
table until .githooks/ was exempted.
--no-verifybypasses the hook. That is intentional for a genuine new placeholder, but it means the hook is a safety net, not a boundary: the real protection is that no script needs a live value any more.
| Aspect | Result |
|---|---|
| VLAN result | All 4094 VLANs swept → 0 PADO (verified with a positive control). |
| PPPoE result | Still failing — blocked upstream at the PON layer. |
| Internet result | Not achievable from the customer side alone. |
| Root cause | OLT-side data-service provisioning gap (no upstream GEM). |
| Firmware | Cannot fix this class of fault (service is OLT-pushed via OMCI). |
- ISP action: provision the data-service GEM/T-CONT/VLAN/bridge for this ONU (see §7 and the hand-off doc
docs/ISP_HANDOFF.md). - Full config backup of the modem before any future change (tool ready:
modem_config_backup.ps1). - Firmware upgrade for stability — with the honest caveat that it will not create the missing upstream service.
- Go-live: once the ISP provisions the service, run the script emitted by
finalize.ps1to re-tag VLAN 827 and dial.
What causes PPPoE error 651 on a fiber (GPON) connection?
Windows error 651 means the PPPoE client sent a PADI (PPPoE Active Discovery
Initiation) broadcast and never received a PADO offer. It is a symptom, not a
cause. The real cause is anywhere along this chain:
- Wrong or missing 802.1Q VLAN tag — the ISP expects tagged frames and you are sending untagged (or vice-versa). Sweep with
vlan_scanner.ps1. - Blind capture pipeline — you are getting a
PADObut your analyzer/filter is not seeing it. Always runfvselftest.ps1first. - Wrong network adapter — frames leave a different NIC than the one you capture on. Use
whichnic.ps1. - No upstream data path at the PON layer — the ONU is registered but has no data-service GEM port / T-CONT, so the
PADIis discarded before it reaches the BRAS. This was the cause in this repository's case — see §6. - Genuinely wrong credentials — but note this produces a different failure (you would get
PADO/PADSand then fail in PPP authentication, typically error 691, not 651).
My ONU is in state O5 with good optical power, so why is there no internet?
O5 only proves the ONU completed PLOAM ranging and is operational at the
transmission layer. It says nothing about whether a service was provisioned.
For traffic to flow, the OLT must additionally push, via OMCI:
- a T-CONT (upstream bandwidth allocation),
- a GEM port mapped to that T-CONT for user data,
- a service VLAN and a bridge/mapper binding the GEM to the UNI port.
If the operator registered your ONU (giving you O5 and an ONU-ID) but never
completed the data-service half, you get exactly this signature: perfect optics,
healthy PON state, and zero traffic. See §5 and §6.
How do I find out which VLAN ID my ISP uses for PPPoE?
Sweep it empirically. The toolkit sends a tagged PADI on every VLAN ID from
1 to 4094 and watches for a PADO:
pwsh -File tools/fvselftest.ps1 # 1. prove the analyzer is NOT blind
pwsh -File tools/vlan_scanner.ps1 # 2. sweep 1-4094Each probe stamps a Host-Uniq tag of "VL" + <vlan-hi> + <vlan-lo> (VLAN 827 →
564c033b), so a returning PADO tells you exactly which VLAN answered — even
if several replies arrive out of order.
Can a firmware upgrade on my ONT fix this?
No — not for this class of fault. The missing GEM port / T-CONT / service VLAN is pushed to the ONU by the OLT over OMCI. It is operator-side configuration state, not ONT-side firmware. Reflashing, factory-resetting, or replacing the ONT cannot create a service the OLT never provisioned.
Is this only useful for Huawei HG8310M?
No. The PPPoE Discovery and 802.1Q layers are vendor-neutral — the VLAN sweep, frame builder and analyzer work against any PPPoE Access Concentrator.
Only the device-readout tools (HTTP endpoints and the telnet engineering CLI) are Huawei-specific, and they were developed against an HG8310M. Other Huawei ONTs (HG8010, HG8240, HG8245, HG8145) commonly expose similar endpoints, but this is not verified here — treat it as untested.
Will this reconfigure or damage my modem or my ISP's equipment?
The diagnostic tools are read-only: they send standard PPPoE Discovery frames and read state over HTTP/telnet. They do not reprovision an OLT.
Two tools do change device state and are named accordingly — do_reboot.ps1 and
the config-backup/restore paths. Review any such script before running it, and read
SECURITY.md first.
Where are the credentials stored? Is anything secret committed?
Nothing secret is committed, by design:
- Every sensitive value is read from an
FV_*environment variable throughtools/fvconfig.ps1. Get-FvModemPasswordthrows rather than falling back to a default.- A pre-commit leak guard blocks any commit containing a modem IP, adapter GUID, real MAC address, literal account name, inline PPPoE credential, or a
*.envfile. - Everything in the documentation is masked as
<REDACTED_*>.
See §11 · Security & Privacy and SECURITY.md.
| Term | Meaning |
|---|---|
| PPPoE | Point-to-Point Protocol over Ethernet — the dial-up-style session most DSL/fiber ISPs use to authenticate a subscriber. |
| PADI / PADO / PADR / PADS / PADT | The five PPPoE Discovery packet types (codes 0x09, 0x07, 0x19, 0x65, 0xa7). The client broadcasts PADI; a server must answer PADO. |
| Error 651 | Windows dial-up error meaning "the modem reported an error" — in practice, no PADO was received. |
| 802.1Q / VLAN tag | A 4-byte Ethernet header (TPID 0x8100) carrying a 12-bit VLAN ID (1–4094). Many ISPs require a specific ID. |
| GPON | Gigabit Passive Optical Network — the fiber access technology (ITU-T G.984). |
| ONT / ONU | Optical Network Terminal / Unit — the fiber modem at the subscriber's premises. |
| OLT | Optical Line Terminal — the operator-side device that controls every ONU on the PON. |
| PLOAM state O1–O5 | The ONU activation state machine. O5 = operational, i.e. ranged and running — but not necessarily provisioned with a data service. |
| OMCI | ONU Management and Control Interface — the protocol the OLT uses to configure the ONU. Service config is pushed down, never set locally. |
| GEM port | GPON Encapsulation Method port — a logical channel carrying user traffic. No GEM port ⇒ no data path. |
| T-CONT | Transmission Container — the upstream bandwidth allocation an ONU needs in order to send. |
| BRAS / BNG | Broadband Remote Access Server — the box that actually terminates PPPoE and answers PADO. |
| Npcap | The Windows packet-capture driver used to send/receive raw frames. |
| Host-Uniq | A PPPoE tag echoed back by the server; this toolkit encodes the probed VLAN ID into it to correlate replies. |
| 🤝 Contributing | See CONTRIBUTING.md. The rule is claims must be provable, not plausible — bring a packet capture or a CLI transcript. |
| 🐛 Report a bug / get help | Use the issue templates. Redact all secrets first. |
| 🔒 Security | See SECURITY.md. Please report vulnerabilities privately. |
| 📜 License | MIT — free to use, modify and distribute. |
| 🤗 Code of Conduct | CODE_OF_CONDUCT.md |
| 📚 Citation | Machine-readable metadata in CITATION.cff. GitHub renders a "Cite this repository" button from it. |
If this toolkit or the case study helped you diagnose your own line, please ⭐ star the repository — it helps other people stuck on error 651 find it.
Keywords: PPPoE, PPPoE error 651, no PADO, PADI, PPPoE discovery, GPON, PON, ONU, ONT, OLT, OMCI, GEM port, T-CONT, 802.1Q, VLAN tagging, VLAN scanner, VLAN sweep, fiber troubleshooting, ISP provisioning, Huawei HG8310M, Npcap, tshark, Wireshark, packet analysis, PowerShell networking, network diagnostics.
Built with a bottom-up, evidence-first methodology — no guessing, every claim backed by a captured frame or a device-CLI readout.