Multihomed network causes Supervisor fail to open

Hi Everyone,

I am running HomeAssistant on a Raspberry Pi 3b+. It is running fine when connected to a ethernet cable. However, i also want to use the built in wifi adapter to connect straight to my Solar Inverter WiFi. So in this situation Home Assistant is running “MultiHomed”. I do this by using the NMCLI commandlet within SSH. Connecting to the wifi goes fine and its receiving a DHCP ip adres and creates a Wifi profile that persists after a reboot… However, when i reboot HA i am unable to navigate to the Supervisor pane within the GUI. It keeps loading. When i logon with SSH i get an error “Unexpected server response: Error 500”. In the logging i see this:

2021-03-07 21:25:33 ERROR (MainThread) [homeassistant.components.hassio.handler] /network/info return code 500
2021-03-07 21:25:33 ERROR (MainThread) [homeassistant.components.hassio] Failed to to call /network/info -

When i remove the wifi profile with the NMCLI command and after a reboot it starts running fine again. And i am able to navigate to Supervisor again within the GUI. Anyone an idea what is going wrong?

Regards,

Marc

1 Like

Hi,

same problem here, Pi 4b. But using it only via wifi, no ethernet plugged in.

2021-03-08 21:35:17 ERROR (MainThread) [aiohttp.server] Error handling request
Traceback (most recent call last):
  File "/usr/local/lib/python3.8/site-packages/aiohttp/web_protocol.py", line 314, in data_received
    messages, upgraded, tail = self._request_parser.feed_data(data)
  File "aiohttp/_http_parser.pyx", line 546, in aiohttp._http_parser.HttpParser.feed_data
aiohttp.http_exceptions.BadStatusLine: 400, message="Bad status line 'invalid HTTP method'"
2021-03-08 22:02:33 ERROR (MainThread) [aiohttp.server] Error handling request
Traceback (most recent call last):
  File "/usr/local/lib/python3.8/site-packages/aiohttp/web_protocol.py", line 314, in data_received
    messages, upgraded, tail = self._request_parser.feed_data(data)
  File "aiohttp/_http_parser.pyx", line 546, in aiohttp._http_parser.HttpParser.feed_data
aiohttp.http_exceptions.BadStatusLine: 400, message="Bad status line 'invalid HTTP method'"
2021-03-08 22:59:03 ERROR (MainThread) [homeassistant.components.hassio.handler] /network/info return code 500
2021-03-08 22:59:03 ERROR (MainThread) [homeassistant.components.hassio] Failed to to call /network/info -
2021-03-08 23:01:51 ERROR (MainThread) [aiohttp.server] Error handling request
Traceback (most recent call last):
  File "/usr/local/lib/python3.8/site-packages/aiohttp/web_protocol.py", line 314, in data_received
    messages, upgraded, tail = self._request_parser.feed_data(data)
  File "aiohttp/_http_parser.pyx", line 546, in aiohttp._http_parser.HttpParser.feed_data
aiohttp.http_exceptions.BadStatusLine: 400, message="Bad status line 'invalid HTTP method'"
2021-03-08 23:21:10 ERROR (MainThread) [homeassistant.components.hassio.handler] /network/info return code 500
2021-03-08 23:21:10 ERROR (MainThread) [homeassistant.components.hassio] Failed to to call /network/info -
2021-03-08 23:49:49 ERROR (MainThread) [aiohttp.server] Error handling request
Traceback (most recent call last):
  File "/usr/local/lib/python3.8/site-packages/aiohttp/web_protocol.py", line 314, in data_received
    messages, upgraded, tail = self._request_parser.feed_data(data)
  File "aiohttp/_http_parser.pyx", line 546, in aiohttp._http_parser.HttpParser.feed_data
aiohttp.http_exceptions.BadStatusLine: 400, message="Bad status line 'invalid HTTP method'"
2021-03-08 23:58:10 ERROR (MainThread) [aiohttp.server] Error handling request
Traceback (most recent call last):
  File "/usr/local/lib/python3.8/site-packages/aiohttp/web_protocol.py", line 314, in data_received
    messages, upgraded, tail = self._request_parser.feed_data(data)
  File "aiohttp/_http_parser.pyx", line 546, in aiohttp._http_parser.HttpParser.feed_data
aiohttp.http_exceptions.BadStatusLine: 400, message="Bad status line 'invalid HTTP method'"
2021-03-09 00:01:52 ERROR (MainThread) [homeassistant.components.hassio.handler] /network/info return code 500
2021-03-09 00:01:52 ERROR (MainThread) [homeassistant.components.hassio] Failed to to call /network/info -
2021-03-09 00:21:24 ERROR (MainThread) [homeassistant.components.hassio.handler] /network/info return code 500
2021-03-09 00:21:24 ERROR (MainThread) [homeassistant.components.hassio] Failed to to call /network/info -

Regards,

chris

1 Like

I have same problem too

Hi,

try shutdow, disconect power for 20 sec and boot it up again.

Regards

chris

Hi! I have the same problem. Did it help to reboot?

Unfortunatly not. :frowning:

I found this in homeassistant supervisor logs:

21-03-31 14:44:34 ERROR (MainThread) [aiohttp.server] Error handling request
Traceback (most recent call last):
  File "/usr/local/lib/python3.8/site-packages/aiohttp/web_protocol.py", line 422, in _handle_request
    resp = await self._request_handler(request)
  File "/usr/local/lib/python3.8/site-packages/sentry_sdk/integrations/aiohttp.py", line 123, in sentry_app_handle
    reraise(*_capture_exception(hub))
  File "/usr/local/lib/python3.8/site-packages/sentry_sdk/_compat.py", line 54, in reraise
    raise value
  File "/usr/local/lib/python3.8/site-packages/sentry_sdk/integrations/aiohttp.py", line 113, in sentry_app_handle
    response = await old_handle(self, request)
  File "/usr/local/lib/python3.8/site-packages/aiohttp/web_app.py", line 499, in _handle
    resp = await handler(request)
  File "/usr/local/lib/python3.8/site-packages/aiohttp/web_middlewares.py", line 119, in impl
    return await handler(request)
  File "/usr/src/supervisor/supervisor/api/security.py", line 135, in system_validation
    return await handler(request)
  File "/usr/src/supervisor/supervisor/api/security.py", line 197, in token_validation
    return await handler(request)
  File "/usr/src/supervisor/supervisor/api/utils.py", line 65, in wrap_api
    answer = await method(api, *args, **kwargs)
  File "/usr/src/supervisor/supervisor/api/network.py", line 166, in info
    for interface in self.sys_host.network.interfaces
  File "/usr/src/supervisor/supervisor/host/network.py", line 65, in interfaces
    interfaces.append(Interface.from_dbus_interface(inet))
  File "/usr/src/supervisor/supervisor/host/network.py", line 307, in from_dbus_interface
    Interface._map_nm_wifi(inet),
  File "/usr/src/supervisor/supervisor/host/network.py", line 352, in _map_nm_wifi
    if inet.settings.wireless_security.key_mgmt == "none":
AttributeError: 'NoneType' object has no attribute 'key_mgmt'

I changed /usr/src/supervisor/supervisor/host/network.py file and now it works

Here’s my changed network.py:
in text

"""Info control for host."""
from __future__ import annotations

import asyncio
from ipaddress import IPv4Address, IPv4Interface, IPv6Address, IPv6Interface
import logging
from typing import List, Optional, Union

import attr

from ..const import ATTR_HOST_INTERNET
from ..coresys import CoreSys, CoreSysAttributes
from ..dbus.const import (
    DBUS_NAME_NM_CONNECTION_ACTIVE_CHANGED,
    ConnectionStateType,
    DeviceType,
    InterfaceMethod as NMInterfaceMethod,
    WirelessMethodType,
)
from ..dbus.network.accesspoint import NetworkWirelessAP
from ..dbus.network.connection import NetworkConnection
from ..dbus.network.interface import NetworkInterface
from ..dbus.payloads.generate import interface_update_payload
from ..exceptions import (
    DBusError,
    DBusNotConnectedError,
    DBusProgramError,
    HostNetworkError,
    HostNetworkNotFound,
    HostNotSupportedError,
)
from .const import AuthMethod, InterfaceMethod, InterfaceType, WifiMode

_LOGGER: logging.Logger = logging.getLogger(__name__)


class NetworkManager(CoreSysAttributes):
    """Handle local network setup."""

    def __init__(self, coresys: CoreSys):
        """Initialize system center handling."""
        self.coresys: CoreSys = coresys
        self._connectivity: Optional[bool] = None

    @property
    def connectivity(self) -> Optional[bool]:
        """Return true current connectivity state."""
        return self._connectivity

    @connectivity.setter
    def connectivity(self, state: bool) -> None:
        """Set host connectivity state."""
        if self._connectivity == state:
            return
        self._connectivity = state
        self.sys_homeassistant.websocket.supervisor_update_event(
            "network", {ATTR_HOST_INTERNET: state}
        )

    @property
    def interfaces(self) -> List[Interface]:
        """Return a dictionary of active interfaces."""
        interfaces: List[Interface] = []
        for inet in self.sys_dbus.network.interfaces.values():
            interfaces.append(Interface.from_dbus_interface(inet))

        return interfaces

    @property
    def dns_servers(self) -> List[str]:
        """Return a list of local DNS servers."""
        # Read all local dns servers
        servers: List[str] = []
        for config in self.sys_dbus.network.dns.configuration:
            if config.vpn or not config.nameservers:
                continue
            servers.extend(config.nameservers)

        return list(dict.fromkeys(servers))

    async def check_connectivity(self):
        """Check the internet connection.

        ConnectionState 4 == FULL (has internet)
        https://developer.gnome.org/NetworkManager/stable/nm-dbus-types.html#NMConnectivityState
        """
        if not self.sys_dbus.network.connectivity_enabled:
            return

        # Check connectivity
        try:
            state = await self.sys_dbus.network.check_connectivity()
            self.connectivity = state[0] == 4
        except DBusError as err:
            _LOGGER.warning("Can't update connectivity information: %s", err)
            self.connectivity = False

    def get(self, inet_name: str) -> Interface:
        """Return interface from interface name."""
        if inet_name not in self.sys_dbus.network.interfaces:
            raise HostNetworkNotFound()

        return Interface.from_dbus_interface(
            self.sys_dbus.network.interfaces[inet_name]
        )

    async def update(self):
        """Update properties over dbus."""
        _LOGGER.info("Updating local network information")
        try:
            await self.sys_dbus.network.update()
        except DBusError:
            _LOGGER.warning("Can't update network information!")
        except DBusNotConnectedError as err:
            _LOGGER.error("No network D-Bus connection available")
            raise HostNotSupportedError() from err

        await self.check_connectivity()

    async def apply_changes(self, interface: Interface) -> None:
        """Apply Interface changes to host."""
        inet = self.sys_dbus.network.interfaces.get(interface.name)

        # Update exist configuration
        if (
            inet
            and inet.settings
            and inet.settings.connection.interface_name == interface.name
            and interface.enabled
        ):
            settings = interface_update_payload(
                interface,
                name=inet.settings.connection.id,
                uuid=inet.settings.connection.uuid,
            )

            try:
                await inet.settings.update(settings)
                await self.sys_dbus.network.activate_connection(
                    inet.settings.object_path, inet.object_path
                )
            except DBusError as err:
                _LOGGER.error("Can't update config on %s: %s", interface.name, err)
                raise HostNetworkError() from err

        # Create new configuration and activate interface
        elif inet and interface.enabled:
            settings = interface_update_payload(interface)

            try:
                await self.sys_dbus.network.add_and_activate_connection(
                    settings, inet.object_path
                )
            except DBusError as err:
                _LOGGER.error(
                    "Can't create config and activate %s: %s", interface.name, err
                )
                raise HostNetworkError() from err

        # Remove config from interface
        elif inet and inet.settings and not interface.enabled:
            try:
                await inet.settings.delete()
            except DBusError as err:
                _LOGGER.error("Can't disable interface %s: %s", interface.name, err)
                raise HostNetworkError() from err

        # Create new interface (like vlan)
        elif not inet:
            settings = interface_update_payload(interface)

            try:
                await self.sys_dbus.network.settings.add_connection(settings)
            except DBusError as err:
                _LOGGER.error("Can't create new interface: %s", err)
                raise HostNetworkError() from err
        else:
            _LOGGER.warning("Requested Network interface update is not possible")
            raise HostNetworkError()

        await self.sys_dbus.network.dbus.wait_signal(
            DBUS_NAME_NM_CONNECTION_ACTIVE_CHANGED
        )
        await self.update()

    async def scan_wifi(self, interface: Interface) -> List[AccessPoint]:
        """Scan on Interface for AccessPoint."""
        inet = self.sys_dbus.network.interfaces.get(interface.name)

        if inet.type != DeviceType.WIRELESS:
            _LOGGER.error("Can only scan with wireless card - %s", interface.name)
            raise HostNotSupportedError()

        # Request Scan
        try:
            await inet.wireless.request_scan()
        except DBusProgramError as err:
            _LOGGER.debug("Can't request a new scan: %s", err)
        except DBusError as err:
            raise HostNetworkError() from err
        else:
            await asyncio.sleep(5)

        # Process AP
        accesspoints: List[AccessPoint] = []
        for ap_object in (await inet.wireless.get_all_accesspoints())[0]:
            accesspoint = NetworkWirelessAP(ap_object)

            try:
                await accesspoint.connect()
            except DBusError as err:
                _LOGGER.warning("Can't process an AP: %s", err)
                continue
            else:
                accesspoints.append(
                    AccessPoint(
                        WifiMode[WirelessMethodType(accesspoint.mode).name],
                        accesspoint.ssid,
                        accesspoint.mac,
                        accesspoint.frequency,
                        accesspoint.strength,
                    )
                )

        return accesspoints


@attr.s(slots=True)
class AccessPoint:
    """Represent a wifi configuration."""

    mode: WifiMode = attr.ib()
    ssid: str = attr.ib()
    mac: str = attr.ib()
    frequency: int = attr.ib()
    signal: int = attr.ib()


@attr.s(slots=True)
class IpConfig:
    """Represent a IP configuration."""

    method: InterfaceMethod = attr.ib()
    address: List[Union[IPv4Interface, IPv6Interface]] = attr.ib()
    gateway: Optional[Union[IPv4Address, IPv6Address]] = attr.ib()
    nameservers: List[Union[IPv4Address, IPv6Address]] = attr.ib()


@attr.s(slots=True)
class WifiConfig:
    """Represent a wifi configuration."""

    mode: WifiMode = attr.ib()
    ssid: str = attr.ib()
    auth: AuthMethod = attr.ib()
    psk: Optional[str] = attr.ib()
    signal: Optional[int] = attr.ib()


@attr.s(slots=True)
class VlanConfig:
    """Represent a vlan configuration."""

    id: int = attr.ib()
    interface: str = attr.ib()


@attr.s(slots=True)
class Interface:
    """Represent a host network interface."""

    name: str = attr.ib()
    enabled: bool = attr.ib()
    connected: bool = attr.ib()
    primary: bool = attr.ib()
    type: InterfaceType = attr.ib()
    ipv4: Optional[IpConfig] = attr.ib()
    ipv6: Optional[IpConfig] = attr.ib()
    wifi: Optional[WifiConfig] = attr.ib()
    vlan: Optional[VlanConfig] = attr.ib()

    @staticmethod
    def from_dbus_interface(inet: NetworkInterface) -> Interface:
        """Concert a dbus interface into normal Interface."""
        return Interface(
            inet.name,
            inet.settings is not None,
            Interface._map_nm_connected(inet.connection),
            inet.primary,
            Interface._map_nm_type(inet.type),
            IpConfig(
                Interface._map_nm_method(inet.settings.ipv4.method),
                inet.connection.ipv4.address,
                inet.connection.ipv4.gateway,
                inet.connection.ipv4.nameservers,
            )
            if inet.connection and inet.connection.ipv4
            else IpConfig(InterfaceMethod.DISABLED, [], None, []),
            IpConfig(
                Interface._map_nm_method(inet.settings.ipv6.method),
                inet.connection.ipv6.address,
                inet.connection.ipv6.gateway,
                inet.connection.ipv6.nameservers,
            )
            if inet.connection and inet.connection.ipv6
            else IpConfig(InterfaceMethod.DISABLED, [], None, []),
            Interface._map_nm_wifi(inet),
            Interface._map_nm_vlan(inet),
        )

    @staticmethod
    def _map_nm_method(method: str) -> InterfaceMethod:
        """Map IP interface method."""
        mapping = {
            NMInterfaceMethod.AUTO: InterfaceMethod.AUTO,
            NMInterfaceMethod.DISABLED: InterfaceMethod.DISABLED,
            NMInterfaceMethod.MANUAL: InterfaceMethod.STATIC,
        }

        return mapping.get(method, InterfaceMethod.DISABLED)

    @staticmethod
    def _map_nm_connected(connection: Optional[NetworkConnection]) -> bool:
        """Map connectivity state."""
        if not connection:
            return False

        return connection.state in (
            ConnectionStateType.ACTIVATED,
            ConnectionStateType.ACTIVATING,
        )

    @staticmethod
    def _map_nm_type(device_type: int) -> InterfaceType:
        mapping = {
            DeviceType.ETHERNET: InterfaceType.ETHERNET,
            DeviceType.WIRELESS: InterfaceType.WIRELESS,
            DeviceType.VLAN: InterfaceType.VLAN,
        }
        return mapping[device_type]

    @staticmethod
    def _map_nm_wifi(inet: NetworkInterface) -> Optional[WifiConfig]:
        """Create mapping to nm wifi property."""
        if inet.type != DeviceType.WIRELESS or not inet.settings:
            return None

        # Authentication
        auth = None
        if not inet.settings.wireless_security:
            auth = AuthMethod.OPEN
        elif inet.settings.wireless_security.key_mgmt == "none":
                auth = AuthMethod.WEP
        elif inet.settings.wireless_security.key_mgmt == "wpa-psk":
                auth = AuthMethod.WPA_PSK

        # WifiMode
        mode = WifiMode.INFRASTRUCTURE
        if inet.settings.wireless.mode:
            mode = WifiMode(inet.settings.wireless.mode)

        # Signal
        if inet.wireless:
            signal = inet.wireless.active.strength
        else:
            signal = None

        if not inet.settings.wireless_security:
            passwd = None
        else:
            passwd = inet.settings.wireless_security.psk

        return WifiConfig(
            mode,
            inet.settings.wireless.ssid,
            auth,
            passwd,
            signal,
        )

    @staticmethod
    def _map_nm_vlan(inet: NetworkInterface) -> Optional[WifiConfig]:
        """Create mapping to nm vlan property."""
        if inet.type != DeviceType.VLAN or not inet.settings:
            return None

        return VlanConfig(inet.settings.vlan.id, inet.settings.vlan.parent)

and in file (download from pastebin)
https://pastebin.com/DdQACT6N

1 Like

How did you change that file? I cant seem to find the supervisor path by using SSH.

That’s the supervisor source code. To change that only on your install you need to be using the ssh & web terminal addon from the community add-ons, not the official ssh addon. And you need to have protection mode turned off so you can use the docker cli. Then you can exec into the hassio_supervisor container, change source code and issue a docker restart to that container.

@eparla774 if that’s your solution though you should submit a PR to https://github.com/home-assistant/supervisor . I mean sounds like you literally just fixed a bug so you could submit that change to the repo.

Changing the supervisor code locally may be possible but it’s not a good idea. You’ll get a warning about “unsupported installation” for running unverified code and won’t be able to do things like updates until you repair supervisor. It’s also transient, next time you restart supervisor it’s going to rebuild the container which returns it to running the official code.

Much better for everyone to just submit a PR with the change and get it fixed at the source. Would also suggest sharing the link here to get some others to emphasize the urgency on the pr so the team knows it’s blocking people.

I opened the issue, link:
https://github.com/home-assistant/supervisor/issues/2779

I opened the Pull Request, link:
https://github.com/home-assistant/supervisor/pull/2821

Pull Request Merged