Adding a static IP from a container using DBUS interface - Device appears to have disconnected permanently and other issues

See code below. I took some information from the wifi connect block as well as the documentation from DBUS (add-connection.py\dbus\python\examples - NetworkManager/NetworkManager - Network connection manager and user applications (mirrored from https://gitlab.freedesktop.org/NetworkManager/NetworkManager)).

Multiple issues arose:

  1. Balena documentation says that Python base images have DBUS interface already installed but it was not. I tried -python images as well as -ubuntu-python -debian-python etc. And none of them gave me access to the DBUS. They all gave the error that my python script could not import dbus. So I went through a lot of different build setups and finally got is working by manually installing dependencies via apt-get and then installing the pip package dbus-python.
  2. Once that was done I was able to set a static IP with the defaults in the script below and that worked fine.
  3. Once the device rebooted it completely lost connection. I distinctly set the interface to the second ethernet port enp2s0 and all the settings you see in the script. The device was connected to a wifi network as well and never had any issues until this instance. It also has an ethernet connection in the primary ethernet port. Now I can no longer connect.

Did I do something wrong in the python script below with the settings or what? In my mind this should not have affected the wifi connection.

Background:

  • this is hosted on balena cloud with the most up to date generic amd64 ext OS as of 6 weeks ago.
  • was accessible via the wifi connect for weeks prior
  • Device is an off the shelf intel mini PC

Dockerfile

FROM balenalib/%%BALENA_MACHINE_NAME%%-debian-python:3.9.7-buster


RUN apt-get update -y && apt-get install build-essential python-dbus libdbus-1-3 -y libdbus-glib-1-dev libdbus-1-dev libglib2.0-dev python3-dbus gcc meson
RUN apt-get install -y dbus-x11
RUN pip3 install --upgrade pip 
RUN pip3 install dbus-python


COPY ./static_ip_service/set_ip.py /app/set_ip.py

# Run your Python script
CMD ["python3", "/app/set_ip.py"]

set_ip.py

#!/usr/bin/env python
import os
import dbus
import uuid
import socket
import struct


def ip_to_int(ip_string):
    return struct.unpack("=I", socket.inet_aton(ip_string))[0]


# Read environment variables or set to default values
INTERFACE = os.environ.get('INTERFACE', 'enp2s0')
IP_ADDRESS = os.environ.get('IP_ADDRESS', '192.168.1.1')
NETMASK = os.environ.get('NETMASK', '255.255.255.0')
GATEWAY = os.environ.get('GATEWAY', '192.168.1.1')
DNS = os.environ.get('DNS', '192.168.1.1')

# full duplex
s_wired = dbus.Dictionary({"duplex": "full"})

# Prepare dictionaries for dbus
s_con = dbus.Dictionary({
    'type': '802-3-ethernet',
    'uuid': str(uuid.uuid4()),
    'id': INTERFACE,
})

addr1 = dbus.Array(
    [ip_to_int(IP_ADDRESS),
        ip_to_int(NETMASK), ip_to_int(GATEWAY)],
    signature=dbus.Signature("u"),
)
print(addr1)
addr1 = (
    dbus.UInt32(ip_to_int(IP_ADDRESS)),
    dbus.UInt32(ip_to_int(NETMASK)),
    dbus.UInt32(ip_to_int(GATEWAY)),
)
addr1 = dbus.Dictionary({"address": IP_ADDRESS, "prefix": dbus.UInt32(8)})

s_ip4 = dbus.Dictionary(
    {
        "address-data": dbus.Array([addr1], signature=dbus.Signature("a{sv}")),
        "gateway": GATEWAY,
        "dns": dbus.Array([dbus.UInt32(int(ip_to_int(DNS)))], signature=dbus.Signature("u")),
        "method": "manual",
    }
)
print(s_ip4)
# s_ip4 = dbus.Dictionary({
#     'addresses': dbus.Array([addr1], signature=dbus.Signature('aau')),
#     # s_ip4 = dbus.Dictionary({
#     #     'addresses': dbus.Array([addr1], signature=dbus.Signature("au")),
#     'method': 'manual',
#     'dns': dbus.Array([dbus.UInt32(int(ip_to_int(DNS)))], signature=dbus.Signature('u'))
# })
# print(s_ip4)

s_ip6 = dbus.Dictionary({"method": "ignore"})

con = dbus.Dictionary(
    {"802-3-ethernet": s_wired, "connection": s_con, "ipv4": s_ip4, "ipv6": s_ip6}
)
print(con)
try:
    # Initialize dbus connection
    bus = dbus.SystemBus()
    proxy = bus.get_object("org.freedesktop.NetworkManager",
                           "/org/freedesktop/NetworkManager/Settings")
    settings = dbus.Interface(proxy, "org.freedesktop.NetworkManager.Settings")

    # List existing connections
    conn_list = settings.ListConnections()
    print("Listing existing connections...")

    # Loop through to find if connection already exists
    for path in conn_list:
        conn_proxy = bus.get_object("org.freedesktop.NetworkManager", path)
        conn_settings = dbus.Interface(
            conn_proxy, "org.freedesktop.NetworkManager.Settings.Connection")
        config = conn_settings.GetSettings()

        # Match by some unique property, like 'id'
        if config['connection']['id'] == INTERFACE:
            print(
                f"Found existing connection with ID {INTERFACE}. Updating...")
            # Update existing connection
            new_settings = {"ipv4": s_ip4}
            conn_settings.Update(new_settings)
            print(f"Updated connection {INTERFACE}")
            break
    else:
        # Add new connection
        print(
            f"No existing connection with ID {INTERFACE} found. Creating a new one...")
        settings.AddConnection(con)
        # print("New connection added.")
        print("Creating connection:", s_con["id"], "-", s_con["uuid"])
        print("Static IP address set successfully"
              f"\nInterface: {INTERFACE}"
              f"\nIP Address: {IP_ADDRESS}"
              f"\nNetmask: {NETMASK}"
              f"\nGateway: {GATEWAY}"
              f"\nDNS: {DNS}"
              )

except dbus.exceptions.DBusException as e:
    print(f"An error occurred: {e}")


# Initialize dbus connection
# bus = dbus.SystemBus()
# proxy = bus.get_object("org.freedesktop.NetworkManager",
#                        "/org/freedesktop/NetworkManager/Settings")
# settings = dbus.Interface(proxy, "org.freedesktop.NetworkManager.Settings")


# Add Connection
# settings.AddConnection(con)

Hello @zswiftscience

Where did you read this? If we say this we might need to correct it. However i can’t find anywhere where we say this.

Did you move this to a different type of network? Could you please confirm that the ip addresses and DNS that you define are compatible with the new network?

On the other hand, i was checking the dbus block that we published to see examples that could help you on how to configure dbus. GitHub - balena-labs-projects/dbus

Thanks for the quick response!
DBUS in python package reference in balena docs:

I didn’t move it to a new network. The specified port controls an unmanaged ethernet switch with IP cameras. There is no network control on that switch and therefore on the port.

What’s interesting is that I didn’t touch network settings (as far as I know) on the other ethernet port and especially the wifi connection. Also, it’s worth noting that I can add a static IP for that port the standard way by putting the network file in the boot/system-connections folder. And this works perfectly. I can then run a dhcp server which is the goal from that port and access the IP cameras.

The plan for now is to just add the network file to boot directory.

1 Like

Hi @mpous,

So I have used the boot file configured for the enp2s0 interface and setting a static IP. That is definitely working for my local DHCP server for the ethernet switch and IP cameras attached. However, another problem arose where any time I reboot by using

    url = str(os.environ['BALENA_SUPERVISOR_ADDRESS']) + \
        "/v1/reboot?apikey=" + str(os.environ['BALENA_SUPERVISOR_API_KEY'])

    data = {'force': True}

    # sending post request and saving response as response object
    r = requests.post(url=url, data=data)

    # extracting response text
    res = r.text
    print("Reboot res is:%s" % res)

It just does not reconnect after printing.

 device_maintenance_service  Reboot res is:{"Data":"OK","Error":null}

When I power cycle it comes right back online. Is it locking the services or something? Unfortunately, I can’t get more logs because this device VPN is likely being blocked by my customer’s firewall.

Any ideas on this?

1 Like

@zswiftscience this is odd! as you are trying to reboot from the Supervisor API call. Do you have any update lock on your device/fleet?

Did you try the force property? Read more here Interacting with the balena Supervisor - Balena Documentation

In the meantime, could you please confirm your device type, balenaOS and supervisor versions?