# Dynamic Backhaul Ethernet Management

The _map-agent_ dynamic backhaul Ethernet management (_dynbh_) is a compile-time
selectable component of _map-agent_. _Dynbh_ automatically detects whether a
port is an uplink (with a _map-controller_ present behind the connected port) or
a downstream/LAN client.

Selectable from OpenWrt configuration:
- `CONFIG_DYNBH`

Compile-time CFLAG:
- `DYNBH`

When compiled in, _dynbh_ can be further enabled or disabled at runtime via the
`dynbh` option in the `agent` UCI section. Defaults to enabled.

```
config agent
        option dynbh '1'
```

## Mechanism

When an Ethernet port is plugged in, _map-agent_ will:
1. Remove the port from the AL bridge.
2. Start backhaul (BH) port discovery by sending **AP-Autoconfiguration Search** messages over the connected port.

While BH discovery is ongoing, the port remains removed from the bridge to
prevent potential network loops. The **AP-Autoconfiguration Search** messages
(sent at 2-second intervals) include a _SearchedService_ TLV indicating the
Multi-AP Controller service.

BH port discovery ends if either:
* A Multi-AP Controller is found behind the port, or
* 10 seconds pass without detecting a Multi-AP Controller.

A Multi-AP Controller is identified when an **AP-Autoconfiguration Search** or
**AP-Autoconfiguration Response** message is received on the port, containing a
_SupportedService_ TLV that indicates support for the Multi-AP Controller role.

Alternatively, if an **AP-Autoconfiguration Response** message is received on
another port with a message identifier matching one previously sent over the BH
port, _map-agent_ infers that the BH port is an uplink.

Once a Multi-AP Controller is discovered, _map-agent_ marks the interface as the
active uplink and will:
1. Disconnect any connected Wi-Fi backhaul.
2. Add the interface back to the AL bridge.
3. Update the backhaul file with the new data (`/var/run/multiap/multiap.backhaul`).

## Ethernet Loop Prevention

In addition to dynamically identifying ports as uplink or LAN, _map-agent_ can
also detect looped Ethernet ports through which the controller is accessible.

The mechanism is the same as described in the previous section. However, if a
port is identified as having a map-controller while another port is already
acting as the active backhaul, map-agent marks the new port as a looped port.

A looped port remains excluded from the bridge as long as the active backhaul is
connected and a map-controller is accessible through it.

## State Machine

Dynbh may be in one of four states:
* **Discovery**
* **Uplink**
* **LAN**
* **Loop**
* **DHCP Server**
* **Unknown** (not connected)

**Discovery** is set when a port is connected. During Discovery, _map-agent_ sends
**AP-Autoconfiguration Search** messages, and if persistent controller detection
is enabled, also sends DHCP discovery messages.

**Uplink** is set if an **AP-Autoconfiguration Search** or
**AP-Autoconfiguration Response** message over the port indicates support for
the Multi-AP Controller role.

**LAN** is set if no Multi-AP Controller (or DHCP server, in the case of
persistent controller detection) is discovered on the port within 10 seconds of
connection.

**Loop** is set if a Multi-AP Controller is discovered on the port while another
port is already acting as the active uplink.

**DHCP Server** is set if no Multi-AP Controller is discovered and no other active
uplink exists, but the port provides a DHCP server.

**Unknown** is set when a port disconnects.

![dynbh state flowchart](dynbh_state.png "dynbh state flowchart")


## Dynamically Persisting Multi-AP Controller

This optional feature allows map-agent, when connected to a non-EasyMesh-capable
gateway, to dynamically assume the controller role and set up an EasyMesh
network.

The feature can be enabled through the OpenWrt configuration:

* `CONFIG_DYNBH_DYNAMICALLY_PERSIST_CONTROLLER`

Or by using the compile-time CFLAG:

* `PERSIST_CONTROLLER`

Additionally, the controller package must be included:

* `CONFIG_PACKAGE_map-controller`

By default, _map-controller_ is expected to be disabled on the local device.

### Mechanism
This mechanism builds on the previously described BH port discovery, but
additionally sends a DHCP discovery. If, by the end of the discovery period
(10 seconds), no Multi-AP Controller is found but a DHCP server is present,
_map-agent_ will start its local _map-controller_ and permanently assume the
Multi-AP Controller role.

Once the controller role is assumed, _dynbh_ functionality is disabled. Any
Ethernet port connected after this point is treated as a LAN port, and no
discovery period will take place.

### Configuration

The feature reads the `controller_select` section in the _map-agent_ UCI
configuration.

The `mode` option determines the feature’s behavior:
* **auto** — _map-agent_ searches for a Multi-AP Controller or a DHCP server,
and is ready to assume the controller role.
* **controller** — the device has assumed the controller role.
* **disabled** — the DHCP discovery mechanism is disabled because another
controller is present.

```
config controller_select
        option mode 'auto'
        option probe_int '20'
        option retry_int '9'
        option autostart '0'
        option local '0'
```

When the controller role is assumed, `local` is set to `1` and _dynbh_ is
effectively disabled.

## UBUS API

An additional method exposed by the `map.agent` object is `dynbh`.

### Example Ouput 1

This example shows:

* **eth1** - Performing discovery.
* **eth3** - One LAN client connected.
* **eth4** - Connected to _map-controller_.

```
root@Pulse-EX600-44d43771b730:~# ubus call map.agent dynbh
{
	"enabled": true,
	"ports": [
		{
			"ifname": "eth1",
			"connected": false,
			"state": "unknown"
		},
		{
			"ifname": "eth3",
			"connected": true,
			"state": "lan"
		},
		{
			"ifname": "eth4",
			"connected": true,
			"state": "uplink"
		}
	]
}
```

### Example Output 2

Below is an example with:
* **eth1** - Hosting a DHCP server.
* **eth3** - Not connected.
* **eth4** - Not connected.

Here `local` is set to `'1'` in the UCI configuration and `enabled` set to
`false`.
```
root@Pulse-EX600-44d43771b730:~# ubus call map.agent dynbh
{
	"enabled": false,
	"ports": [
		{
			"ifname": "eth1",
			"connected": true,
			"state": "dhcp_server"
		},
		{
			"ifname": "eth3",
			"connected": false,
			"state": "unknown"
		},
		{
			"ifname": "eth4",
			"connected": false,
			"state": "unknown"
		}
	]
}
```









