From 6c269bf7a581e3b742f6f4638a80196ca346df87 Mon Sep 17 00:00:00 2001 From: VAIO73 <50487331+Vaso73@users.noreply.github.com> Date: Sun, 18 Oct 2026 18:50:54 +0000 Subject: [PATCH] fix: preserve runtime address order for local icon lookup --- AppImage/scripts/local_app_icons.py | 6 +++++- AppImage/scripts/tests/test_local_app_icons.py | 12 ++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/AppImage/scripts/local_app_icons.py b/AppImage/scripts/local_app_icons.py index d61dc6f4..edb576db 100644 --- a/AppImage/scripts/local_app_icons.py +++ b/AppImage/scripts/local_app_icons.py @@ -94,7 +94,11 @@ def _origin(vmid, payload): for a in interface.get('addr_info', []) if 'local' in a} except (ValueError, TypeError, KeyError) as exc: raise IconError('host address check unavailable') from exc - addresses = sorted({str(ipaddress.ip_address(ip)) for ip in ips if _private_ip(ip)}, key=lambda ip: ':' in ip) + # Preserve lxc-info's reported order (CT215: eth0 then Docker bridges). + # Unordered set iteration selected an internal bridge at random. Keep + # runtime order within each address family, including when deduplicating. + addresses = list(dict.fromkeys(str(ipaddress.ip_address(ip)) for ip in ips if _private_ip(ip))) + addresses.sort(key=lambda ip: ':' in ip) addresses = [ip for ip in addresses if ip not in host_ips] if not addresses: raise IconError('container has no isolated private address') diff --git a/AppImage/scripts/tests/test_local_app_icons.py b/AppImage/scripts/tests/test_local_app_icons.py index 0e9aede7..2d187339 100644 --- a/AppImage/scripts/tests/test_local_app_icons.py +++ b/AppImage/scripts/tests/test_local_app_icons.py @@ -145,6 +145,18 @@ class Icons(unittest.TestCase): self.assertTrue(icons._same_origin('https://10.0.0.42:443', 'https://10.0.0.42/icon')) self.assertFalse(icons._same_origin('https://10.0.0.42', 'https://user@10.0.0.42/icon')) + def test_multiple_private_addresses_retain_runtime_order_not_set_order(self): + # Real CT215 lxc-info: eth0 followed by three Docker bridges and ULA. + observed = ['10.0.0.27', '172.17.0.1', '172.18.0.1', '172.19.0.1', + 'fd40:359f:8a1b:f9b3:be24:11ff:fe85:bd05'] + for addresses in (observed, list(reversed(observed))): + expected = next(ip for ip in addresses if ':' not in ip) + def resolve(args): + return '\n'.join(addresses + addresses) if args[0] == 'lxc-info' else self.resolve(args) + with patch.object(icons, '_run', side_effect=resolve), patch.object(icons, '_worker', return_value=(self.png, 'image/png')) as worker: + icons.discover(101, self.payload) + worker.assert_called_once_with(f'http://{expected}:8080') + def test_address_change_or_stop_during_request_cannot_cache(self): for second in ['http://10.0.0.43:8080', icons.IconError('stopped')]: with patch.object(icons, '_origin', side_effect=['http://10.0.0.42:8080', second]), patch.object(icons, '_worker', return_value=(self.png, 'image/png')), patch.object(icons, 'store') as store: