diff --git a/docker/routlin-dash/app/pages/radius/action.py b/docker/routlin-dash/app/pages/radius/action.py index 3598870..49f5fdc 100644 --- a/docker/routlin-dash/app/pages/radius/action.py +++ b/docker/routlin-dash/app/pages/radius/action.py @@ -71,7 +71,7 @@ def auth_mode_save(): eap_protocol = 'eap_peap' tunneled_reply = 'tunneled_reply' in request.form include_length = 'include_length' in request.form - mab_fallback = 'mab_fallback' in request.form + mab_first = 'mab_first' in request.form inner_protocol = request.form.get('inner_protocol', '') _valid_inner = { @@ -85,7 +85,7 @@ def auth_mode_save(): if auth_mode == 'eap_password': after['eap_protocol'] = eap_protocol after['tunneled_reply'] = tunneled_reply and eap_protocol in ('eap_peap', 'eap_ttls') - after['mab_fallback'] = mab_fallback + after['mab_first'] = mab_first if eap_protocol in _valid_inner and inner_protocol in _valid_inner[eap_protocol]: after['inner_protocol'] = inner_protocol else: @@ -96,7 +96,7 @@ def auth_mode_save(): after.pop('include_length', None) elif auth_mode == 'eap_credential': after['include_length'] = include_length - after['mab_fallback'] = mab_fallback + after['mab_first'] = mab_first after.pop('eap_protocol', None) after.pop('tunneled_reply', None) after.pop('inner_protocol', None) @@ -105,7 +105,7 @@ def auth_mode_save(): after.pop('tunneled_reply', None) after.pop('inner_protocol', None) after.pop('include_length', None) - after.pop('mab_fallback', None) + after.pop('mab_first', None) cfg.setdefault('radius', {})['options'] = after changes = config_utils.diff_fields(before, after) diff --git a/docker/routlin-dash/app/pages/radius/content.json b/docker/routlin-dash/app/pages/radius/content.json index f223ecd..3c5a4e1 100644 --- a/docker/routlin-dash/app/pages/radius/content.json +++ b/docker/routlin-dash/app/pages/radius/content.json @@ -196,11 +196,11 @@ { "type": "field", "label": "", - "name": "mab_fallback", + "name": "mab_first", "input_type": "checkbox", "checkbox_label": "Try MAB first before prompting supplicant", - "value": "%RADIUS_MAB_FALLBACK%", - "hint": "When a device fails or skips 802.1X, RADIUS will attempt to authenticate it by MAC address before rejecting it. Useful for networks with a mix of 802.1X-capable and non-802.1X devices." + "value": "%RADIUS_MAB_FIRST%", + "hint": "RADIUS checks the device's MAC address first. Known devices (those with a DHCP reservation) are admitted immediately without waiting for 802.1X negotiation or credential entry. Unknown devices fall through to 802.1X." }, { "type": "raw_html", diff --git a/docker/routlin-dash/app/pages/radius/view.py b/docker/routlin-dash/app/pages/radius/view.py index 6f3d24f..5862fd5 100644 --- a/docker/routlin-dash/app/pages/radius/view.py +++ b/docker/routlin-dash/app/pages/radius/view.py @@ -122,7 +122,7 @@ def collect_tokens(cfg): tokens['RADIUS_TUNNELED_REPLY'] = 'true' if fr_opts.get('tunneled_reply', False) else '' tokens['RADIUS_INCLUDE_LENGTH'] = 'true' if fr_opts.get('include_length', False) else '' - tokens['RADIUS_MAB_FALLBACK'] = 'true' if fr_opts.get('mab_fallback', False) else '' + tokens['RADIUS_MAB_FIRST'] = 'true' if fr_opts.get('mab_first', True) else '' vlans = cfg.get('vlans', []) default_vlan = next((v['name'] for v in vlans if v.get('radius_default') is True), '') diff --git a/routlin/mod_radius.py b/routlin/mod_radius.py index 8c794bf..b55e7da 100644 --- a/routlin/mod_radius.py +++ b/routlin/mod_radius.py @@ -118,9 +118,12 @@ def build_radius_users(data): if default_vlan is None: return None - fr_opts = data.get('radius', {}).get('options', {}) - mac_fmt = fr_opts.get('mac_format', 'aabbccddeeff') - apply_to = fr_opts.get('apply_to', 'all') + fr_opts = data.get('radius', {}).get('options', {}) + mac_fmt = fr_opts.get('mac_format', 'aabbccddeeff') + apply_to = fr_opts.get('apply_to', 'all') + auth_mode = fr_opts.get('auth_mode', 'mab') + mab_first = fr_opts.get('mab_first', True) + emit_mac_entries = (auth_mode == 'mab') or mab_first lines = [ "# Generated by core.py -- do not edit manually.", @@ -129,25 +132,26 @@ def build_radius_users(data): ] vlan_by_name = {v["name"]: v for v in data.get("vlans", [])} - for r in data.get("dhcp_reservations", []): - if r.get("enabled") is not True: - continue - raw_mac = r.get("mac", "") - if not raw_mac: - continue - vlan = vlan_by_name.get(r.get("vlan", "")) - if not vlan: - continue - mac = fmt_mac(raw_mac, mac_fmt) - vlan_id = vlan.get('vlan_id') - lines += [ - f"# {r['description']} -> VLAN {vlan_id} ({vlan['name']})", - f"{mac} Cleartext-Password := \"{mac}\"", - f" Tunnel-Type = VLAN,", - f" Tunnel-Medium-Type = IEEE-802,", - f" Tunnel-Private-Group-Id = \"{vlan_id}\"", - "", - ] + if emit_mac_entries: + for r in data.get("dhcp_reservations", []): + if r.get("enabled") is not True: + continue + raw_mac = r.get("mac", "") + if not raw_mac: + continue + vlan = vlan_by_name.get(r.get("vlan", "")) + if not vlan: + continue + mac = fmt_mac(raw_mac, mac_fmt) + vlan_id = vlan.get('vlan_id') + lines += [ + f"# {r['description']} -> VLAN {vlan_id} ({vlan['name']})", + f"{mac} Cleartext-Password := \"{mac}\"", + f" Tunnel-Type = VLAN,", + f" Tunnel-Medium-Type = IEEE-802,", + f" Tunnel-Private-Group-Id = \"{vlan_id}\"", + "", + ] default_id = default_vlan.get('vlan_id') ap_ips = fr_opts.get('ap_ips', [])