# ACS URL Discovery over PPPoE Interface
### Feature Guide · TR-069 / CWMP

---

|                    |                                              |
|--------------------|----------------------------------------------|
| **Standard**       | Broadband Forum TR-069, TR-044               |
| **Version**        | 1.1 — May 2026                               |
| **Prepared by**    | Suvendhu Hansa                               |

---

## Table of Contents

1. [Overview](#1-overview)
2. [Background & Standards](#2-background-and-standards-reference)
3. [Server-Side Configuration](#3-server-side-configuration)
4. [Design Rationale](#4-design-rationale)
5. [Implementation](#5-implementation)
6. [Protocol Reference](#6-protocol-reference)
7. [Deployment](#7-deployment)
8. [Summary](#8-summary)

---

## 1. Overview

This document describes ACS (Auto-Configuration Server) URL discovery for TR-069-compliant
CPE devices operating over a PPPoE WAN interface. The feature implements the requirement
defined in **TR-044 §4.1.4**, which mandates that a CPE operating over PPPoE must send a
**DHCPINFORM** — rather than DHCPDISCOVER — to retrieve the ACS URL from a DHCP server,
since the IP address has already been assigned via IPCP (post-IP bootstrap).

A custom C utility (`pppoe-acs-discover`) constructs and transmits a compliant DHCPINFORM
packet carrying DHCP **Option 60** (Vendor Class Identifier set to `dslforum.org`) and
requesting **Option 43** (Vendor Encapsulated Options containing the ACS URL). A companion
shell utility (`pppoe-acs-update.sh`) handles interface parameter extraction, invokes
`pppoe-acs-discover`, and writes the discovered values to UCI. It is called by both the
interface hotplug and directly by `icwmpd` when re-discovery is needed at runtime.

```
┌─────────────────────────────────────────────────────────────────────┐
│                                                                     │
│  DHCPINFORM sent over pppoe-wan  --►  DHCPACK received with         │
│  Option 43 (TLV sub-option 1)    --►  ACS URL parsed and applied    │
│  for TR-069 client configuration.     Full end-to-end integration.  │
└─────────────────────────────────────────────────────────────────────┘
```

---

## 2. Background and Standards Reference

### 2.1 TR-069 ACS Bootstrap Requirement

The TR-069 specification defines the CPE WAN Management Protocol (CWMP), which enables
remote management of CPE devices by an ACS. For a CPE to initiate a TR-069 session, it
must first discover the URL of the ACS. One of the defined discovery mechanisms involves
DHCP **Option 43** (Vendor Encapsulated Options), where the CPE identifies itself with
**Option 60** set to `dslforum.org` and requests the ACS URL in return.

For a PPPoE device, the IP address is assigned via IPCP (IP Control Protocol). Because
the IP is already assigned, standard DHCP rules (RFC 2131) dictate that if a device needs
additional configuration options without requesting a new IP, it must use DHCPINFORM
populating `ciaddr` with its already assigned address.
This is the "Post-IP" bootstrap step — the device already has connectivity but needs to
discover its management endpoint.

---

### 2.2 TR-044 §4.1.4 — DHCPINFORM Requirement over PPPoE

**TR-044** (*TR-069 CPE WAN Management Protocol — Provisions for DSL Forum CPE*) §4.1.4
specifically addresses ACS URL discovery for CPE devices whose WAN IP is assigned via
PPPoE/IPCP rather than native DHCP. The section requires that such CPE:

- **SHALL** send a **DHCPINFORM** message (RFC 2131 §4.4.3) populated with the
  IPCP-assigned address in `ciaddr` to request additional provisioning parameters from
  the DHCP server.
- **SHALL** include **Option 60** set to the vendor class identifier `dslforum.org` to
  identify itself as a TR-069-capable CPE to the server.
- **SHALL** request **Option 43** (Vendor Encapsulated Options) via the Parameter Request
  List (Option 55) to retrieve the ACS URL and associated retry parameters.

This requirement distinguishes the PPPoE case from the standard DHCP case: because no
DHCP lease cycle takes place, the CPE cannot rely on the udhcpc hook receiving Option 43
in a DHCP offer. It must initiate the DHCPINFORM independently after IPCP completes.

---

### 2.3 Post-IP Bootstrap — Sequence Diagram

```
  CPE  ·  pppoe-wan (10.102.95.100)           Server  ·  ppp0 (10.102.95.1)
  ─────────────────────────────────────────────────────────────────────────────

          │                                                      │
  ────────┤  [1] PPPoE session up                                │
          │      IPCP assigns 10.102.95.100 to CPE               │
          │                                                      │
          │──── DHCPINFORM ─────────────────────────────────────►│
          │     src    10.102.95.100 : 68                        │  [2] vendor class
          │     dst    255.255.255.255 : 67                      │      "dslforum.org"
          │     ciaddr 10.102.95.100                             │      matched
          │     Opt 53 = 8 (INFORM)                              │      → build
          │     Opt 60 = "dslforum.org"                          │        Opt 43 TLV
          │     Opt 55 = [43]  flags = unicast                   │
          │                                                      │
          │◄─── DHCPACK ─────────────────────────────────────────│
          │     src    10.102.95.1 : 67                          │
          │     dst    10.102.95.100 : 68                        │
          │     Opt 53 = 5 (ACK)                                 │
          │     Opt 43 = TLV { 01 : 1d : https://acs... }        │
          │     Opt 60 = "dslforum.org"                          │
          │                                                      │
  ────────┤  [3] parse ACS URL from Option 43 sub-option 1       │
          │      uci set cwmp.acs.dhcp_url = <url>               │
          │      ubus call uci commit                            │
          │                                                      │
          │──── TR-069 SESSION BEGINS ──────────────────────────►│
          │                                                      │
```

---

### 2.4 DHCP Option 43 — TLV Byte Layout

Option 43 wraps the ACS URL in a TLV (Type-Length-Value) sub-option:

```
  byte 0      byte 1      bytes 2 … N+1
  ┌──────────┬──────────┬─────────────────────────────────────────────┐
  │   0x01   │   0x1d   │  68 74 74 70 73 3a 2f 2f 61 63 73 2e …      │
  ├──────────┼──────────┼─────────────────────────────────────────────┤
  │  Type    │  Length  │  Value  (ASCII)                             │
  │  = 1     │  = 29    │  "https://acs.example.com:7547/"            │
  │  ACS URL │          │                                             │
  └──────────┴──────────┴─────────────────────────────────────────────┘
```

Full hex encoding for `https://acs.example.com:7547/`:

```
01 1d  68 74 74 70 73 3a 2f 2f 61 63 73 2e 65 78 61
       6d 70 6c 65 2e 63 6f 6d 3a 37 35 34 37 2f
```

| Field    |  Bytes  | Value                                        |
|----------|---------|----------------------------------------------|
| Type     |  1      | `0x01` — sub-option 1 = ACS URL              |
| Length   |  1      | `0x1d` = 29 decimal                          |
| Value    |  29     | ASCII string `https://acs.example.com:7547/` |

---

## 3. Server-Side Configuration for Testing

### 3.1 Network Topology

```
  ┌──────────────────────────────────────┐      ┌──────────────────────────────────────┐
  │  CPE                                 │      │  Server                              │
  ├──────────────────────────────────────┤      ├──────────────────────────────────────┤
  │  Physical WAN                        │      │  Physical NIC                        │
  │    ae_wan                            │      │    eth1                              │
  │    MAC  00:AA:BB:01:23:41            │      │                                      │
  ├──────────────────────────────────────┤      ├──────────────────────────────────────┤
  │  PPPoE (logical)                     │      │  PPPoE (logical)                     │
  │    pppoe-wan                         │      │    ppp0                              │
  │    10.102.95.100 / 32                │      │    10.102.95.1                       │
  ├──────────────────────────────────────┤      ├──────────────────────────────────────┤
  │  Software                            │      │  Software                            │
  │    icwmpd       (TR-069 client)      │      │    pppoe-server  (rp-pppoe)          │
  │    pppoe-acs-discover                │      │    dnsmasq       (DHCP + Opt 43)     │
  └──────────────────┬───────────────────┘      └──────────────────┬───────────────────┘
                     │                                             │
                     ├──── Physical Ethernet  (ae_wan <-> eth1) ───┤
                     ╞══════════════ PPPoE Session  0x60 ══════════╡
                     │                                             │
```

### 3.2 dnsmasq Server Command

dnsmasq is the recommended DHCP server for this feature (see §6 for why ISC dhcpd
cannot be used).

```bash
dnsmasq \
  --no-daemon       \  # run in foreground for logging
  --port=0          \  # disable DNS (avoid port 53 conflict)
  --interface=ppp0  \  # listen on the PPPoE session interface
  --bind-interfaces \  # bind exclusively to ppp0, not 0.0.0.0
  --no-resolv       \  # ignore /etc/resolv.conf
  --no-hosts        \  # ignore /etc/hosts
  --log-dhcp        \  # log all DHCP transactions
  --dhcp-range=10.102.95.100,10.102.95.100,255.255.255.0,12h \
  --dhcp-option=vendor:dslforum.org,1,https://acs.example.com:7547/
  #                 ^-- Option 43 sub-option 1 returned only when
  #                     client Option 60 = "dslforum.org"
```

---

## 4. Design Rationale

Standard DHCP client utilities do not implement a **DHCPINFORM** mode. Their core
limitation is that they always begin with **DHCPDISCOVER** — they cannot send
**DHCPINFORM** as the first message. **DHCPINFORM** requires a client to already
hold an IP (`ciaddr`) and send it directly, bypassing the discovery cycle entirely.
This is architecturally incompatible with conventional DHCP client design.

```
  Standard DHCP client state machine (simplified)
  ─────────────────────────────────────────────────────────────────────

  START --► SELECTING --► REQUESTING --► BOUND --► RENEWING
                │                │
                │ DHCPDISCOVER   │ DHCPREQUEST
                │ (broadcast)    │
                ▼                ▼
           wait for         wait for
           DHCPOFFER        DHCPACK

  [N/A]  No DHCPINFORM branch exists.  ciaddr is only populated
         after a full lease cycle — which never happens over PPPoE.
```

```
┌─────────────────────────────────────────────────────────────────────┐
│  LIMITATION                                                         │
│                                                                     │
│  Standard DHCP clients fill ciaddr only after completing a full     │
│  DHCP lease cycle. In PPPoE the IP is assigned by IPCP, so a        │
│  conventional client never reaches that point.                      │
└─────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────┐
│  DECISION                                                           │
│                                                                     │
│  A custom C utility is the only viable path. Standalone executable. │
│  No dependencies beyond libc.                                       │
└─────────────────────────────────────────────────────────────────────┘
```

---

## 5. Implementation

### 5.1 DHCP Packet Construction

The utility hand-crafts a DHCP packet over a raw UDP socket bound to the
PPPoE interface. Every field is set explicitly to produce a valid DHCPINFORM.

| Field / Option | Value                | Rationale                                                          |
|----------------|----------------------|--------------------------------------------------------------------|
| `op`           | `1`                  | BOOTREQUEST                                                        |
| `htype / hlen` | `1 / 6`              | Ethernet hardware type, 6-byte MAC                                 |
| `xid`          | `getpid()`           | Unique transaction ID per invocation                               |
| `flags`        | `0x0000`             | Unicast — server replies directly to `ciaddr`                      |
| `ciaddr`       | `10.102.95.100`      | **IPCP-assigned IP** — key field distinguishing INFORM vs DISCOVER |
| `chaddr`       | `00:AA:BB:01:23:41`  | MAC of physical WAN interface                                      |
| Option 53      | `8`                  | DHCPINFORM message type                                            |
| Option 60      | `"dslforum.org"`     | Vendor class — triggers Option 43 response from server             |
| Option 55      | `[43]`               | Parameter request list — only ask for ACS URL                      |
| Option 255     | —                    | End of options                                                     |

### 5.2 Response Parsing

The utility handles two Option 43 encoding styles:

| Format          | Detection                              | Parsing                                              |
|-----------------|----------------------------------------|------------------------------------------------------|
| **TLV**         | First byte `0x01`, byte 1 is length    | Read sub-option type → length → value (ACS URL)      |
| **Plain text**  | All bytes are printable ASCII          | Treat entire option value as the URL string directly |

The TLV format is the standard TR-069 encoding. Plain-text fallback covers
non-standard server configurations.

### 5.3 Usage

```bash
# Syntax
pppoe-acs-discover <interface> <client_ip> <client_mac> <server_ip>

# Example
pppoe-acs-discover pppoe-wan 10.102.95.100 00:AA:BB:01:23:41 10.102.95.1
```

---

## 6. Protocol Reference

### 6.1 Packet Exchange

```
  CPE  (pppoe-wan · 10.102.95.100)              Server  (ppp0 · 10.102.95.1)
  ─────────────────────────────────────────────────────────────────────────────

        │                                                           │
        │  ┌──────────────────────────────────────────────┐         │
        │  │  DHCPINFORM                                  │         │
        │  │  src    10.102.95.100 : 68                   │         │
        │  │  dst    255.255.255.255 : 67                 │         │
        │  │  xid    0x7dcc                               │         │
        │  │  ciaddr 10.102.95.100                        │         │
        │  │  Opt 53 = 8  (DHCPINFORM)                    │         │
        │  │  Opt 60 = "dslforum.org"                     │         │
        │  │  Opt 55 = [43]   flags = 0x0000 (unicast)    │         │
        │  └──────────────────────────────────────────────┘         │
        │──────────────────────────────────────────────────────────►│
        │                                                           │
        │                             ┌──────────────────────────┐  │
        │                             │  vendor class matched    │  │
        │                             │  "dslforum.org"          │  │
        │                             │  → build Option 43 TLV   │  │
        │                             └──────────────────────────┘  │
        │                                                           │
        │◄──────────────────────────────────────────────────────────│
        │  ┌──────────────────────────────────────────────┐         │
        │  │  DHCPACK                                     │         │
        │  │  src       10.102.95.1 : 67                  │         │
        │  │  dst       10.102.95.100 : 68                │         │
        │  │  xid       0x7dcc  [OK - matched]            │         │
        │  │  Server-ID 10.102.95.1                       │         │
        │  │  Opt 53    = 5  (DHCPACK)                    │         │
        │  │  Opt 43    = TLV [01:1d:68:74:74:70:…]       │         │
        │  │  Opt 60    = "dslforum.org"                  │         │
        │  │  flags     = 0x0000 (unicast)                │         │
        │  └──────────────────────────────────────────────┘         │
        │                                                           │
```

### 6.2 Utility Output — CPE Side

```
root@cpe:~# pppoe-acs-discover pppoe-wan 10.102.95.100 00:aa:bb:01:23:41 10.102.95.1
──────────────────────────────────────────────────────
  Interface  :  pppoe-wan
  Client IP  :  10.102.95.100
  Server IP  :  10.102.95.1
  Vendor     :  dslforum.org
  Using MAC  :  00:aa:bb:01:23:41

Sending DHCPINFORM...  Sent 548 bytes
Waiting for DHCPACK (5 s timeout)...
Reply from: 10.102.95.1

──── DHCPACK received ────────────────────────────────
  DHCP Message Type  :  5 (ACK)
  Server ID          :  10.102.95.1
  Option 43 (Vendor Encapsulated):
    Raw bytes  :  01 1d 68 74 74 70 73 3a 2f 2f 61 63 73
                  2e 65 78 61 6d 70 6c 65 2e 63 6f 6d 3a
                  37 35 34 37 2f
    ACS URL    :  https://acs.example.com:7547/
──────────────────────────────────────────────────────
```

### 6.3 Server-Side Log — dnsmasq

```
root@server:~# dnsmasq --log-dhcp [...]
──────────────────────────────────────────────────────
dnsmasq-dhcp: DHCPINFORM(ppp0)  10.102.95.100   00:aa:bb:01:23:41
dnsmasq-dhcp: tags: ppp0
dnsmasq-dhcp: vendor class: dslforum.org
dnsmasq-dhcp: requested options: 43:vendor-encap

dnsmasq-dhcp: DHCPACK(ppp0)     10.102.95.100   00:aa:bb:01:23:41
dnsmasq-dhcp: sent size: 31  option: 43  vendor-encap
  01:1d:68:74:74:70:73:3a:2f:2f:61:63:73:2e:65:78:61:
  6d:70:6c:65:2e:63:6f:6d:3a:37:35:34:37:2f
dnsmasq-dhcp: sent size: 12  option: 60  vendor-class  dslforum.org
──────────────────────────────────────────────────────
```

---

## 7. Deployment

### 7.1 End-to-End Hotplug Integration

When the WAN PPPoE interface comes up, netifd fires the hotplug script
`20-pppoe-acs-discovery`, which validates the event and delegates all discovery work to
`pppoe-acs-update.sh`. No pre-configured ACS URL is needed — the URL is fetched live and
written to the cwmp UCI config before `icwmpd` starts its TR-069 session.

```
  ┌─────────────────────────────────────────────────────────────────────┐
  │  EVENT  Interface event fires                                       │
  │         ACTION=ifup  ·  INTERFACE=wan  ·  DEVICE=pppoe-wan          │
  └──────────────────────────────┬──────────────────────────────────────┘
                                 │
                                 ▼
  ┌─────────────────────────────────────────────────────────────────────┐
  │  STEP 1  Hotplug guard  [20-pppoe-acs-discovery]                    │
  │                                                                     │
  │  · Confirm INTERFACE == cwmp.cpe.default_wan_interface              │
  │  · Confirm cwmp.acs.dhcp_discovery is enabled                       │
  │  · Confirm interface proto == "pppoe"  (via ifstatus + jsonfilter)  │
  │  · sleep 5  (let PPPoE session fully stabilise)                     │
  │  · Verify /etc/icwmpd/pppoe-acs-update.sh is executable             │
  └──────────────────────────────┬──────────────────────────────────────┘
                                 │
                                 ▼
  ┌─────────────────────────────────────────────────────────────────────┐
  │  STEP 2  Detect IPCP addresses  [pppoe-acs-update.sh]               │
  │                                                                     │
  │  ifstatus <interface>                                               │
  │  L3_DEV    = pppoe-wan         (PPPoE logical interface)            │
  │  CLIENT_IP = 10.102.95.100     (IPCP-assigned to CPE)               │
  │  PEER_IP   = 10.102.95.1       (IPCP remote end / DHCP server)      │
  │  CLIENT_MAC read from /sys/class/net/<phys_device>/address          │
  └──────────────────────────────┬──────────────────────────────────────┘
                                 │
                                 ▼
  ┌─────────────────────────────────────────────────────────────────────┐
  │  STEP 3  ACS URL discovery  [pppoe-acs-update.sh → pppoe-acs-       │
  │                               discover]                             │
  │                                                                     │
  │  pppoe-acs-discover pppoe-wan $CLIENT_IP $CLIENT_MAC $PEER_IP       │
  │                                                                     │
  │    CPE ──── DHCPINFORM ─────────────────────────────────► Server    │
  │             ciaddr / Opt 60="dslforum.org" / Opt 55=[43]            │
  │    CPE ◄─── DHCPACK ───────────────────────────────────── Server    │
  │             Opt 43 = TLV { 01 : 1d : https://acs.../ }              │
  │                                                                     │
  │  ACS_URL = "https://acs.example.com:7547/"                          │
  └──────────────────────────────┬──────────────────────────────────────┘
                                 │
                                 ▼
  ┌─────────────────────────────────────────────────────────────────────┐
  │  STEP 4  Write to UCI  [pppoe-acs-update.sh]                        │
  │                                                                     │
  │  uci set cwmp.acs.dhcp_url="$ACS_URL"                               │
  │  ubus call uci commit '{"config":"cwmp"}'                           │
  └──────────────────────────────┬──────────────────────────────────────┘
                                 │
                                 ▼
  ╔═════════════════════════════════════════════════════════════════════╗
  ║  TR-069 session initiated  ·  Zero-Touch Provisioning complete      ║
  ╚═════════════════════════════════════════════════════════════════════╝
```

### 7.2 Retry when a PPPoE-discovered ACS is unreachable

The retry logic runs in two situations:

- **Daemon startup** — `cwmp_config_load()` blocks in `cwmp_init()` until an HTTP connection to the ACS succeeds.
- **After a session failure** — `cwmp_config_load()` is called again at the start of each retry attempt when the previous TR-069 session ended with failure.

In both cases, if the PPPoE-discovered ACS URL (`cwmp.acs.dhcp_url`) is unreachable for **300 seconds**, icwmpd re-triggers ACS URL discovery directly. It calls `/etc/icwmpd/pppoe-acs-update.sh <interface>` via `run_cmd()`, which re-runs DHCPINFORM on the PPPoE interface and updates UCI with any refreshed values.

```
icwmpd startup  OR  session failure retry
    │
    ├─ cwmp.acs.dhcp_url set? ──No──► connect to cwmp.acs.url (static)
    │
    Yes
    │
    ▼
try HTTP connection to dhcp_url
    │
    ├─ OK ──► proceed with TR-069 session; disable dhcp_discovery
    │
    └─ fail for 300 s
           │
           ├─ re-enable dhcp_discovery if needed
           │
           ├─ proto == pppoe ──► run_cmd("/etc/icwmpd/pppoe-acs-update.sh wan")
           │                         │
           │                         ├─► ifstatus → extract l3_device, IPs, MAC
           │                         ├─► pppoe-acs-discover → DHCPINFORM / DHCPACK
           │                         └─► uci set cwmp.acs.dhcp_url + commit
           │
           └─ reset 300 s timer, retry
```

This path bypasses the hotplug event guards (interface name check, dhcp_discovery flag, proto validation) since icwmpd has already confirmed the interface is PPPoE before calling the script.

### 7.3 Build-Time Configuration

The feature is controlled by the `ICWMP_ENABLE_ACS_DISCOVERY_OVER_PPPOE` compile-time
flag. It is **enabled by default**.

| Method            | How to set                                                 |
|-------------------|------------------------------------------------------------|
| CMake             | `-DICWMP_ENABLE_ACS_DISCOVERY_OVER_PPPOE=ON` (default)     |
| CMake (disable)   | `-DICWMP_ENABLE_ACS_DISCOVERY_OVER_PPPOE=OFF`              |
| Package config    | `CONFIG_ICWMP_ENABLE_ACS_DISCOVERY_OVER_PPPOE=y` (default) |

When the flag is enabled, the build produces the `pppoe-acs-discover` utility tool
and installs both the utility tool (`/usr/sbin/pppoe-acs-discover`) and the hotplug
script (`/etc/hotplug.d/iface/20-pppoe-acs-discovery`). When disabled, neither component
is built or installed and the feature is fully absent from the image.

### 7.4 Flow Summary

**Initial discovery (hotplug path):**

| Step | Component                      | Action                                                                       |
|------|--------------------------------|------------------------------------------------------------------------------|
| 1    | `20-pppoe-acs-discovery`       | Fires on `ifup`; validates WAN iface, `dhcp_discovery`, PPPoE proto;         |
| 2    | `pppoe-acs-update.sh`          | Reads IPCP client IP, peer IP, MAC from `ifstatus`                           |
| 3    | `pppoe-acs-discover`           | Sends DHCPINFORM; receives DHCPACK; parses Option 43 TLV                     |
| 4    | `pppoe-acs-update.sh`          | Writes discovered values to `cwmp` UCI config and commits                    |
| 5    | `icwmpd`                       | Connects to ACS to begin TR-069 session                                      |

**Retry path (ACS unreachable for 300 s — startup or session failure):**

| Step | Component                   | Action                                                              |
|------|-----------------------------|---------------------------------------------------------------------|
| 1    | `icwmpd` (`config.c`)       | Detects PPPoE proto via `ubus network.interface status`             |
| 2    | `pppoe-acs-update.sh`       | Called directly via `run_cmd()`; reads `ifstatus`, runs discovery   |
| 3    | `pppoe-acs-discover`        | Sends DHCPINFORM; receives DHCPACK; parses Option 43 TLV            |
| 4    | `pppoe-acs-update.sh`       | Writes discovered values to `cwmp` UCI config and commits           |
| 5    | `icwmpd`                    | Retries HTTP connection to the updated ACS URL                      |

---

## 8. Summary

This feature enables TR-069 ACS URL discovery for CPE devices operating over a PPPoE
WAN interface, implementing the DHCPINFORM requirement defined in **TR-044 §4.1.4** and
the RFC 2131 DHCPINFORM mechanism. The ACS URL is delivered via DHCP Option 43 in
compliance with the TR-069 vendor class identification scheme (`dslforum.org`).

The `pppoe-acs-discover` utility is a lightweight, dependency-free C program that fills
a genuine gap left by standard DHCP client tools, which cannot send DHCPINFORM when the
IP address has been assigned externally via IPCP. The companion shell script
`pppoe-acs-update.sh` wraps the utility: it reads the interface parameters from
`ifstatus`, calls `pppoe-acs-discover`, and writes the discovered values to UCI. This
script is the single point of invocation — called by the interface hotplug on PPPoE
session establishment and called directly by `icwmpd` when re-discovery is triggered at
runtime — enabling fully automatic ACS URL provisioning with no pre-configured URL
required in the device.

For testing purpose server-side architecture requires **dnsmasq** as the DHCP server, owing
to its native support for PPP interfaces — a capability absent in the ISC DHCP stack. The
vendor-class-conditional Option 43 response ensures that only TR-069-identified CPE
devices receive the ACS URL, leaving all other DHCP clients unaffected.

```
┌─────────────────────────────────────────────────────────────────────┐
│  COMPONENTS                                                         │
│                                                                     │
│  pppoe-acs-discover                                                 │
│    Custom C utility.  Sends DHCPINFORM, receives DHCPACK,           │
│    parses Option 43 TLV, prints ACS URL to stdout.                  │
│    No external dependencies.                                        │
│                                                                     │
│  pppoe-acs-update.sh                                                │
│    Utility script.  Reads interface parameters from ifstatus,       │
│    invokes pppoe-acs-discover, writes discovered values to UCI,     │
│    and commits.  Called by both the hotplug and icwmpd directly.    │
│                                                                     │
│  20-pppoe-acs-discovery                                             │
│    Interface hotplug script.  Triggers on WAN ifup, validates       │
│    PPPoE proto and dhcp_discovery flag, then delegates to           │
│    pppoe-acs-update.sh.  No manual intervention required.           │
└─────────────────────────────────────────────────────────────────────┘
```

---

_ACS URL Discovery over PPPoE · Broadband Forum TR-069 / CWMP · v1.0 · May 2026_
