Skip to content

Instantly share code, notes, and snippets.

@arapov
Last active July 28, 2026 08:34
Show Gist options
  • Select an option

  • Save arapov/a2019f5dac903864f36102184ff2755a to your computer and use it in GitHub Desktop.

Select an option

Save arapov/a2019f5dac903864f36102184ff2755a to your computer and use it in GitHub Desktop.
Your own XGS-PON SFP+ ONT stick on O2/CETIN (Czech ISP) — complete guide. FS XGS-SFP-ONT-MAC-I / MaxLinear PRX126: SN registration, the missing subscriber bridge port, the fix, persistence.

Your own XGS-PON SFP+ stick on O2/CETIN (Czech Republic) — the complete guide

Replacing the ISP-supplied ONT (Comtrend) with an FS XGS-SFP-ONT-MAC-I seated directly in the router's SFP+ port. Debugged and resolved in July 2026 — in production since. Everything below was verified on a real O2/CETIN line: the outputs in this text are real, not illustrative (the only two inferred items — baby-jumbo MTU and the exact VEIP-set flow under 1U — are explicitly marked as such in the text).

(Česká verze: fs-xgspon-stick-o2-cetin-cs.)

Verified setup: FS XGS-SFP-ONT-MAC-I (SKU 378865, MaxLinear PRX126 SoC, firmware LEDE 1.23.1 / v22.03.3_maxlinear) in a Ubiquiti UCG-Fiber (SFP+ WAN). The diagnostic method applies to any PRX126-class stick (WAS-110 included) and, in principle, to any operator whose OLT never creates the subscriber-side bridge port.


TL;DR — quick start

  1. Buy the right module. O2 lines on CETIN infrastructure are typically XGS-PON today (10G, 1270 nm up / 1577 nm down) — check your current ONT's label. FS sells both GPON and XGS-PON variants; the GPON one will never sync on an XGS line.
  2. Have O2 register your module's serial number. CETIN authorizes ONTs ONLY by GPON SN (no password, no registration ID). Send both forms — ASCII (FSOX--------) and full hex (46534F58--------) — and let CETIN's partner pick. It goes through O2 to CETIN's technical partner; the registration/swap itself happened same-day in my case (what takes a day or more is an UNblock after a quarantine, see below).
  3. Configure the stick (SSH root@192.168.101.1, empty password; from the router first: ip addr add 192.168.101.99/24 dev <sfp-iface>; the module must be seated with live, lit fiber — no light means no SGMII link and no management on strict-RX_LOS cages, see Warnings). First set a root password — the factory image ships without one (see Warnings):
    passwd
    Then the PON configuration:
    uci set gpon.ploam.nSerial='FSOX--------'   # 4 ASCII + 8 hex chars, NOT the full-hex form
    uci set gpon.ponip.pon_mode='xgspon'
    uci set omci.default.mib_file='/etc/mibs/prx300_1U.ini'
    uci commit && reboot
    Leave the remaining values (omci.default.loid/lpwd/omcc_version, gpon.ponip.iop_mask) at factory defaults — they are correct on the FS stick. Do NOT delete gpon.ploam.regID (see Warnings — a missing/empty regID bricks the whole PON stack).
  4. Router: PPPoE on VLAN 848, user O2, password O2 (IPTV is 835, VoIP 899). MTU 1492.
  5. Once the SN is registered the stick goes operational (pon psg → current=51), the OLT pushes its profile — and PPPoE still won't work ("Timeout waiting for PADO packets"). That's the trap. The fix — one command on the stick:
    brctl addif sw1 eth0_0
    PADO arrives within a minute. Why this happens and why this is the correct fix rather than a hack — see the deep dive below.
  6. The fix does not survive a reboot — install the guard (Persistence section): a tiny script + cron that restores the bridge automatically after every reboot/re-provision. Measured in a live-fire disconnect test: bridge restored in 79 s, full WAN including PPPoE back in ~3 minutes.
  7. If the stick never comes up and pon psg cycles current=23 → your SN is blocked on the OLT — nothing on the stick fixes that; CETIN's partner must lift it (see the SN block section).

Schema: how the whole thing works

internet (O2 BNG) ── CETIN OLT ═══ XGS-PON fiber (1577 nm ↓ / 1270 nm ↑, healthy Rx ≈ −17 dBm)
                                     │
                       FS stick (pon0)
                       ├── gem4095 ── sw-multicast          (multicast leg, IPTV)
                       └── gem1024 ── sw1 ═ eth0_0          (unicast; eth0_0 must be PUT there!)
                                             │ SGMII 10G (inside the SFP+ cage)
                       router: SFP+ iface ── <iface>.848 ── pppd (PPPoE O2/O2)
                       └ stick management: 192.168.101.1 (LCT, rides outside the data bridge)

Provisioning, step by step:

sequenceDiagram
    participant ONT as FS stick
    participant OLT as CETIN OLT
    ONT->>OLT: PLOAM: announcing SN (state O2.3)
    OLT->>ONT: ONU-ID assigned, ranging (O4) → operational (O5.1)
    OLT->>ONT: OMCI profile push: bridge, GEM 1024/4095, VLAN-848 filter
    OLT--xONT: SET VEIP (ME 329) — FAILS (a 1U stick has no VEIP)
    Note over ONT: subscriber-side bridge port is NEVER created<br/>sw1 is left holding only gem1024
    ONT->>ONT: manual fix: brctl addif sw1 eth0_0
    ONT->>OLT: PPPoE PADI (VLAN 848) → PADO → session
Loading

Honesty note: the "SET VEIP fails" step is documented from CETIN's partner OLT log (Jul 12, 1V mode); for 1U it is inferred from the profile's VEIP orientation — the 1U onboarding flow itself was never captured. The outcome (missing subscriber port) is verified directly from the MIB.


Deep dive: why PPPoE stays dead while everything looks green

How the OLT builds the data path

After admitting the module (PLOAM O5.1), the OLT pushes its service profile over OMCI as a set of Managed Entities (MEs). This is what the complete O2/CETIN profile looks like on this line (real dump):

omci_pipe.sh mib_dump | grep -E "^\|" | grep -iE "bridge port|GEM|VLAN tag"
| 47  | 257  (0x0101) | Bridge port config data      <- unicast bridge port (GEM side)
| 47  | 511  (0x01ff) | Bridge port config data      <- multicast
| 84  | 257  (0x0101) | VLAN tagging filter data     <- filter: TCI 0x0350 = VLAN 848
| 266 | 1024 (0x0400) | GEM interworking TP          <- unicast glue
| 268 | 1024 (0x0400) | GEM port network CTP         <- unicast GEM
| 268 | 4095 (0x0fff) | GEM port network CTP         <- multicast
| 281 | 4095 (0x0fff) | Multicast GEM TP

A working bridge needs a pair of bridge ports (ME 47) on the same bridge: one on the GEM side (TP type 0x05) and one subscriber-side — either a physical UNI (TP 0x01) or a VEIP (TP 0x0b). Look at the dump again: there is no ME 47 instance with TP type 0x01 or 0x0b anywhere. The OLT built half a bridge.

Why the subscriber port is missing on O2/CETIN specifically

CETIN requires VEIP from customer-owned ONTs ("the device must support VEIP" — that is the entire spec you'll get). The OLT profile therefore tries to set ME 329 (VEIP) at instance 0x1:

  • The FS stick in its default 1U personality (prx300_1U.ini) exposes a physical UNI (PPTP Ethernet UNI, ME 11) — there is no VEIP, the set fails, the OLT moves on and simply never creates the subscriber-side bridge port. The network side (GEM, VLAN filter) exists; the bridge stays half-built.
  • The 1V personality (prx300_1V.ini) does have a VEIP — but the factory file declares it at instance 0x0101, while the OLT sets 0x0001. The OLT-side result: Failed cause: OLT return instance invalid (0x3000e) — and support will tell you your device "can't do VEIP". That error is fully explained by the instance mismatch, and the fix is a one-liner (in /etc/mibs/prx300_1V.ini change 329 0x0101 to 329 0x0001) — but BEWARE: never try 1V remotely. In 1V the OLT's data bridge captures eth0_0, which also carries the stick's management, and you lose access to the module (structurally — not remotely recoverable). That is exactly why corrected-instance 1V was never verified end-to-end on this line (every test window was observationally blind) — and exactly why this guide recommends the 1U + guard path.

The most robust path in practice: stay on 1U, let the VEIP set fail harmlessly, and complete the one missing bridge attachment yourself.

Why brctl addif sw1 eth0_0 is the correct fix, not a hack

On the stick, the omcid daemon (MaxLinear pon_net_lib) translates OMCI into Linux networking. When an OLT creates a subscriber-side bridge port ME, omcid performs exactly: ip link set $dev master $br — it's right there in the vendor source (pon_net_lib, mac_bridge_port_config_data.c). The manual brctl addif sw1 eth0_0 is the same operation, at the same layer — you just run it yourself, because the OLT never sends the ME.

Fixing it "properly" at the OMCI layer from the ONT side is impossible: bridge-port MEs are set-by-create — only the OLT may create them. The layer below OMCI is the only one you have. And since it is pure runtime kernel state (no uci, no OMCI, no flash writes), the operation is fully reversible (brctl delif sw1 eth0_0) and invisible to the OLT.

Causality evidence from this line: an endless wall of Timeout waiting for PADO packets, then PADO + PADS + a public IP within 60 seconds of that one command, with nothing else changed anywhere. And in reverse: delif → WAN dies within ~70 s (LCP), addif → WAN comes back by itself. A/B/A verified.

The VLAN path (why no tag is touched anywhere)

The OLT-side filter ME 84/257 holds a single entry, TCI 0x0350 = VID 848, forward-op 0x10 (passes only frames tagged with that VID). The stick has no ME 171 (extended VLAN / re-tagging) and no ME 130 (p-mapper) instances; the sw1 bridge is VLAN-unaware (vlan_filtering=0) and forwards frames untouched. So: the router tags 848 (<iface>.848), the stick changes nothing, the OLT filter passes 848. That is why plain bridging is sufficient.


Diagnosis walkthrough (real outputs)

1. PLOAM state:

pon psg
errorcode=0 current=51 previous=40 time_curr=2233 change_reason=65535
current= Meaning
10/11/12 O1.x — downstream sync
23 O2.3 — announcing serial, waiting for ONU-ID (cycling here = SN blocked)
40 O4 — ranging
51 O5.1 — operational, OMCI live
errorcode=-1032 PON stack not running at all (see regID in Warnings)

A healthy onboard runs 10 → 12 → (23 → 40) → 51 in seconds. If you are not at 51, solve registration/blocking first — bridging is a later step.

2. Serial on the wire (what the module actually announces):

pon sng
errorcode=0 serial_no="FSOX--------"

3. What the OLT pushed — see the MIB dump in the deep dive. The key check: does any ME 47 instance have TP type 0x01/0x0b? (omci_pipe.sh meg 47 <instance>, attribute 3.) No → the OLT never created the subscriber port.

4. Bridge state on the stick:

brctl show
sw-multicast   ...   gem4095
sw1            ...   gem1024        <- ONLY gem1024: this is the defect

ip -o link show eth0_0
2: eth0_0: <...UP,LOWER_UP> ...     <- no "master sw1": the UNI leads nowhere

5. Router: Timeout waiting for PADO packets every ~40 s. The PADI leaves the router, lands on eth0_0, and dies there (not bridged) — the OLT never sees it. This is purely L2, pre-authentication: credentials are irrelevant.

Router capture tip: on switch-ASIC platforms (UniFi ipq9574 and friends) tcpdump on the physical interface does not see locally generated egress — capture on the VLAN sub-interface: tcpdump -i <iface>.848 pppoed.


SN block (when the stick never comes up at all)

The signature — pon psg cycles forever:

current=23 previous=12 time_curr=53   # ~60-70 s in O2.3
current=12 previous=11 time_curr=9    # briefly in O1.2, and again

The serial is announced correctly (verify with pon sng), but the OLT never assigns an ONU-ID. You cannot fix this from the stick. The OLT's rogue-ONT protection gets there after a day full of reboots, personality flips and half-dead states — every such event counts. A verified data point from this line: the block is at line/port level, not per-serial (it refused the original ISP ONT's serial identically).

What to do: stop rebooting (every attempt digs deeper) and reach out to O2 support — they interface with CETIN's technical partner, who is the only party able to unblock the SN on the OLT. Reference your order number and SN, and describe the state factually (serial announced, no ONU-ID ever assigned). Unblocking happened twice on this line; each time it required an explicit request, and the module admitted immediately after.


Persistence — the bridge guard (uni-bridge-guard)

The brctl addif is runtime state: it disappears on every stick reboot, omcid restart, and OLT re-provision (the OLT pushes its profile at every onboarding, and sw1 is rebuilt each time — without eth0_0). The solution is a tiny script run by cron every minute that completes the bridge whenever needed.

/root/uni-bridge-guard.sh (md5 2956349e7755a5b484b8bcf47914e84c):

#!/bin/sh
# uni-bridge-guard — re-attach eth0_0 to the OMCI unicast bridge (sw1) if an
# OMCI rebuild dropped it (stick reboot, omcid restart, OLT MIB re-push).
# Installed at /root/uni-bridge-guard.sh on the stick; run by cron every
# minute (/etc/crontabs/root); checks every 10 s within its minute.
# Acts ONLY when: sw1 exists AND holds a gem* port (OLT unicast provisioned)
# AND eth0_0 is not already enslaved. Worst failure mode: does nothing.
# Remove: rm /root/uni-bridge-guard.sh /etc/crontabs/root; /etc/init.d/cron stop
i=0
while [ $i -lt 5 ]; do
  if [ -d /sys/class/net/sw1 ] \
     && ls /sys/class/net/sw1/brif 2>/dev/null | grep -q "^gem" \
     && [ -d /sys/class/net/eth0_0 ] \
     && [ ! -d /sys/class/net/eth0_0/brport ]; then
    brctl addif sw1 eth0_0 && echo "$(date) re-attached eth0_0 to sw1" >> /root/uni-bridge-guard.log
  fi
  i=$((i+1))
  sleep 10
done

Install (on the stick) — safe even where a crontab already exists (WAS-110 with community firmware etc.):

# copy the script to /root/uni-bridge-guard.sh, then:
grep -q uni-bridge-guard /etc/crontabs/root 2>/dev/null || \
  echo '* * * * * /bin/sh /root/uni-bridge-guard.sh' >> /etc/crontabs/root
/etc/init.d/cron enable
/etc/init.d/cron start

Uninstall: remove only the guard's line (sed -i '/uni-bridge-guard/d' /etc/crontabs/root) plus rm /root/uni-bridge-guard.sh — the script header shows a blunter variant (deleting the whole crontab), which is fine only on a factory FS stick where the crontab is otherwise empty.

All three artifacts (script, crontab, rc.d symlink) live on the writable overlay → they survive reboots and power loss.

Why cron and not a daemon: this platform reliably kills resident processes — busybox has no timeout, omci_pipe.sh can block forever, and nohup over dropbear dies with its SSH session (both happened for real). A fresh process every minute has nothing to wedge. The guard acts ONLY when sw1 exists, holds a gem* port, and eth0_0 is unattached — so it stays silent through quarantine states, and if CETIN ever fixes the profile (the OLT creating the subscriber port itself), the guard goes permanently silent. Worst failure mode: it does nothing.

Measured in a live-fire test (deliberate brctl delif on a running session): LCP declared the session dead after ~70 s, the guard restored the bridge in 79 s, PPPoE renegotiated by itself — same public IP, ~3 minutes total outage, zero manual actions. Reboot survival is by mechanism (all artifacts on the persistent overlay, cron starts via rc.d); the full reboot path (crond at boot + OLT re-push timing) had not yet been exercised end-to-end at the time of writing — the first real reboot validates it.


Warnings (each one paid for in experience)

  • Never delete gpon.ploam.regID. It is a required libpon field: missing or empty → omcid crash-loops, the laser never fires, pon psg returns errorcode=-1032. CETIN ignores its content (SN-only auth) — but the field must exist. Check: uci get gpon.ploam.regID. If missing, fill with 36 zero bytes:
    uci set gpon.ploam.regID='0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00'
    uci commit gpon
  • The factory image has passwordless root SSH (and a web UI with root/root). Anyone who can reach 192.168.101.1 has root on the device your entire WAN traffic flows through — sniffing/MITM, killing the WAN, or corrupting the PON identity are all one login away. And note: the management alias on the router (ip addr add 192.168.101.99/24 ...) stays up until you remove it or the router reboots — so the path to the stick can remain open to your whole LAN unless the firewall blocks it. Set a password immediately after first login (passwd); the web server can simply be disabled (/etc/init.d/uhttpd stop && /etc/init.d/uhttpd disable — management is SSH anyway); remove the alias when you are done (ip addr del 192.168.101.99/24 dev <sfp-iface>) or firewall LAN → 192.168.101.0/24.
  • Enter the SN in uci as 4 ASCII + 8 hex chars (FSOX--------), NOT the full 16-char hex form — that gets mis-parsed and the stick announces a different serial than you think. Always verify with pon sng.
  • Never try the 1V (VEIP) personality remotely. The data bridge captures eth0_0 and management goes dark — structurally. The way back is catching a ~35 s boot window with a pre-staged script, or physical access. Console / on-site only.
  • Don't flip personalities repeatedly, and ration your reboots. 1U/1V report different equipment identities to the OLT; combined with reboot marathons this is the suspected trigger of the SN block (rogue-ONT protection). Budget: single-digit events per day, not dozens.
  • The stick has no RTC — its clock and file mtimes are meaningless until NTP syncs (which needs a working WAN/NAT path). Timestamp events from the router.
  • UCG-Fiber SFP+ is strict about RX_LOS: no light from the OLT → no SGMII link → no path to the stick's management. Dark fiber = unreachable stick; that's not a stick fault.
  • UniFi SSH is keyboard-interactive. sshpass -e ssh ... works; never add -o PreferredAuthentications=password — it breaks auth outright. And don't hammer SSH in bursts: the UCG rate-limits and returns Permission denied with the CORRECT password (looks exactly like a rotated password; it self-clears after a few quiet seconds).
  • MTU: keep PPPoE at 1492 (the standard PPPoE MTU on O2). RFC 4638 baby-jumbo (MTU 1500) was NOT tested on this installation — its frames (1522+ B on the wire) exceed what the stick's bridge path forwards at default MTUs, and O2's BNG support is unverified. Plan for 1492.

Provenance and sources

  • Everything above comes from one real, fully documented installation on O2/CETIN XGS-PON (2026): the outputs are real and every claim has a timestamped log behind it (the inferred exceptions are marked as such in the text). The journey included two SN blocks, an OLT-side profile regression and its restoration by CETIN's partner — the signatures described here ("O2.3 loop", "half-built bridge", "multicast-only profile") are photographed from life, not paraphrased from documentation.
  • The fix mechanism is verifiable in the chip vendor's own source: MaxLinear pon_net_lib — omcid performs exactly this enslave when a subscriber bridge port is created.
  • Independent precedent on the same silicon: WAS-110 on Fidium/GoNetSpeed (transscendsurvival.org, 2026-05) — the OLT likewise never created the subscriber-side bridge port; same fix at the Linux layer.
  • Further reading: hack-gpon.org (PPTP vs VEIP, ONT stick hardware), pon.wiki and the 8311 community (PLOAM/OMCI know-how, WAS-110).

Written from the operational documentation of that installation; questions welcome in the comments.

@arapov

arapov commented Jul 28, 2026 •

Copy link
Copy Markdown
Author

Related — and a useful contrast. A Czech thread covers the Vodafone-on-CETIN case using 8311 community firmware: https://www.techforum.cz/topic/64188-vlastní-ont-na-síti-cetin/

Same silicon (MaxLinear PRX126), same CETIN access, same SN-format and VLAN-848 gotchas. The key difference is why they never hit the missing-subscriber-bridge-port trap this guide is about: 8311 firmware presents VEIP correctly, so the OLT's VEIP set succeeds and omcid builds the subscriber bridge port itself — no manual brctl addif needed. They also spoof the original ONT's serial instead of registering their own.

Rough decision guide:

  • 8311 community firmware + willing to copy the ISP ONT's serial → their route; you likely won't see the PADO trap.
  • Stock FS firmware, your own registered SN, on O2/CETIN → this guide; expect the missing bridge port and the one-line fix.
    Different operators (Vodafone vs O2) may also push different profiles.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment