Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions networking/generate_network_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,35 @@
import os
import stat
import ipaddress
import re

HEX_ESCAPE_RE = re.compile(r'\\x([0-9A-Fa-f]{2})')

SYS_CON_DIR = "/etc/NetworkManager/system-connections"

def is_escaped_essid(essid: str) -> bool:
"""True if the ESSID contains at least one \\xHH hex escape sequence."""
return HEX_ESCAPE_RE.search(essid) is not None

def bytes_to_nm_ssid_value(raw: bytes) -> str:
return ";".join(str(b) for b in raw) + ";"

def unescape_regex(essid: str) -> bytes:
"""
Only touch \\xHH sequences with a targeted regex substitution, leave
every other character exactly as-is, then encode the result as Latin-1
(a 1:1 char -> byte mapping) to recover the raw bytes.
"""
unescaped_chars = HEX_ESCAPE_RE.sub(lambda m: chr(int(m.group(1), 16)), essid)
return unescaped_chars.encode("latin-1")

def escaped_hex_to_bytes_string(raw_essid_from_iwlist: str) -> str:
"""Return the exact text to put after 'ssid=' in the .nmconnection file."""
if is_escaped_essid(raw_essid_from_iwlist):
raw_bytes = unescape_regex(raw_essid_from_iwlist)
Comment on lines +15 to +34

@mutability mutability Jun 25, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This technically works but it's a bit of a case of getting the right answer for the wrong reason.

ideally what we want to do is:

  1. take the raw ESSID and turn it into bytes
  2. take those bytes and represent them in a way that networkmanager understands

the current code muddies the waters by basing the decisions in (2) on the format of the raw ESSID, not the bytes it represents.

I'll put together some code to do this a bit more cleanly.

return bytes_to_nm_ssid_value(raw_bytes)
return raw_essid_from_iwlist

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as mentioned in the previous PR, you need to handle the case where the SSID looks like a decimal-octet-string such as 101;


def calculate_brd_by_hand(address: str, netmask: str) -> str:
return format(ipaddress.IPv4Network(f"{address}/{netmask}", strict=False).broadcast_address)

Expand Down Expand Up @@ -119,6 +145,8 @@ def get_wireless_conn_file(config: ConfigGroup):
psk = config.get("wireless-password")
connect = "true" if config.get("wireless-network") else "false"

ssid = escaped_hex_to_bytes_string(ssid)

ssid = escape_backslashes_for_network_manager(ssid)
psk = escape_backslashes_for_network_manager(psk)

Expand Down
66 changes: 65 additions & 1 deletion networking/test_generate_network_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,71 @@ def get(k):
c.get = Mock(side_effect=get)
template = get_wireless_conn_file(c)
assert template == wireless_template.format("method=auto")


@mock.patch("generate_network_config.configure_static_network", side_effect=mock_csn)
def test_get_wireless_conn_file_ascii_ssid(self, csn_mock):
"""Test ASCII-only SSID like 'MyNetwork' from iwlist."""
c = Mock()

def get(k):
if k == "wireless-type":
return "dhcp"
elif k == "wireless-ssid":
return "MyNetwork" # ASCII SSID
elif k == "wireless-password":
return "password123"
else:
return None
c.get = Mock(side_effect=get)

template = get_wireless_conn_file(c)
# Verify ssid=MyNetwork appears in output (ASCII SSIDs pass through unchanged)
assert "ssid=MyNetwork" in template
assert "psk=password123" in template
assert "method=auto" in template

@mock.patch("generate_network_config.configure_static_network", side_effect=mock_csn)
def test_get_wireless_conn_file_mixed_ssid(self, csn_mock):
"""Test mixed ASCII and non-ASCII SSID."""
c = Mock()

def get(k):
if k == "wireless-type":
return "dhcp"
elif k == "wireless-ssid":
# München in UTF-8 hex escapes (as iwlist would output)
return r"M\xC3\xBCnchen"
elif k == "wireless-password":
return "password123"
else:
return None
c.get = Mock(side_effect=get)

template = get_wireless_conn_file(c)
# Verify ssid=77;195;188;110;99;104;101;110; (byte values for München)
assert "ssid=77;195;188;110;99;104;101;110;" in template

@mock.patch("generate_network_config.configure_static_network", side_effect=mock_csn)
def test_get_wireless_conn_file_non_ascii_ssid(self, csn_mock):
"""Test non-ASCII SSID from iwlist hex-escaped output."""
c = Mock()

def get(k):
if k == "wireless-type":
return "dhcp"
elif k == "wireless-ssid":
# "😂" -(non-ASCII)
return r"\xF0\x9F\x98\x82"
elif k == "wireless-password":
return "password123"
else:
return None
c.get = Mock(side_effect=get)

template = get_wireless_conn_file(c)
# Verify correct byte conversion for mixed SSID
assert "ssid=240;159;152;130;" in template

def test_calculate_brd_by_hand(self):
brd = calculate_brd_by_hand("192.168.1.24", 8)
assert brd == "192.255.255.255"
Expand Down