Dynamically Changing Network Configurations

Hi,
I want to dynamically change the network configurations for a ping application I’m working on. I’ve also implemented a graphical interface for it, but I’m having trouble connecting these changes to the network. Is there any documentation or example that could help me with this?

@hamed You would need to reconfigure the <lxip> or <lwip> node in <vfs> of your ping component or the vfs server. Assuming ping uses <lwip/lxip> directly, you can route the “ROM” called “config” of your component to one that provides a dynamic configuration. As an example you can have a look at vfs_cfg.run where a dynamic_rom is used to reconfigure the socket_fs.

Thank you for the information. I encountered a similar approach in the mixer program. I attempted to implement it within the code and apply changes, but I was unsuccessful. It seems that I did not fully grasp how to correctly utilize the socket_fs.config or mixer.config files. Furthermore, the example you mentioned still applies the configuration statically, but in a step-by-step manner. My objective is to enable dynamic changes through code and user interaction.

I also integrated the following section into the code and attempted to make a modification, subsequently printing the result to verify that the change had been applied. However, this approach did not yield the desired outcome.

		Genode::Attached_rom_dataspace _config { env, "config" };

		char xml_data[2048];
		size_t xml_used = 0;
		try {
			Genode::Xml_generator xml(xml_data, sizeof(xml_data), "config", [&] {
				xml.node("domain", [&] {
					xml.attribute("name", "dynamic config test");
				});
			});

			xml_used = xml.used();

		} catch (...) { Genode::warning("could generate 'mixer.config'"); }

		write_config(config_file, xml_data, xml_used);

		Genode::log("..................", _config.xml().sub_node("domain").attribute_value("name",  Name()));
		```

One way to achieve this would be to use a Reporter/Expanding_reporter and generate the complete configuration (<config> .... </config>) into the report. The Report service of the component generating the configuration would then be routed to a report_rom where the mixer could retrieve the ROM with label “config”. Every time the report is changed, the mixer or any other client will receive a signal that the configuration has been updated and react accordingly. In case of your ping program the configuration update will be handled by vfs_lxip/lwip for the <lwip>/<lxip> nodes. Otherwise a Signal_handler is required in order to react to the signal. You can grep for “handle_config” to find some examples. For example, the mixer uses _handle_config_update to react to configuration changes, this is set in the _handler_config signal handler, which in turn is connected to the

    Genode::Attached_rom_dataspace _config_rom { env, "config" };
    ...
    _config_rom.sigh(_handler_config);

Hello again,

I’ve been working on modifying XML configurations using the Genode::Reporter and Genode::Attached_rom_dataspace classes, and I’ve gained a good understanding of how the changes are applied.

Additionally, I’ve explored the nic_router_uplinks program and learned how dynamic_rom functions.

However, I’m facing a challenge when it comes to making network changes at runtime. Since I need to update configurations dynamically, I’m relying on dynamic_rom. The issue is that the file I create in dynamic_rom, such as setting.config, is not linked to the VFS, which prevents me from writing to it to apply the changes. Furthermore, because I’m making these changes within the nic_router module, I’m not in the right environment to use Genode::Reporter.

What would be the best approach to resolve this? (same config change in sculpt os)

Sounds like a <ram> node might better serve your use-case. Well that depends on the components.

Are there two components (let’s call them ‘client’ and ‘server’) communicating, instead of just one ?

If so, then the prime option would be ssumpf’s advice : it would be most simple and elegant. The ‘client’ sends configuration instructions through a Reporter, the ‘server’ receives them through an Attached_rom (and gets notified of updates thanks to a signal handler), and the interface between the client and server is a report_rom component.

However, you might opt for a more complex setup with a vfs server : instantiate a vfs component, setup its filesystem section with a <ram/> node ; maybe use an <import>....</import> to create the initial settings.config contents ; then write changes to that file (writing to it will be allowed in this context, since it’s an actual file in the ram disk) from the client, and read its contents in the server.
But in that setup it’s not obvious how the server gets ‘notified’ of settings change. As an initial, naïve approach, it could “poll” the file once per second, looking for updated settings. But this second system seems more awkward than the Reporter/Attached_rom system.

Does that make sense ?

Looking at sculpt.run, it seems SculptOS uses a more advanced scheme than described above, so as to allow changes to have an original ROM origin, yet also allow them to be modified by writing/clobbering them with classic open/read/write/close calls (e.g. running Vim in a bash terminal), and re-export the files as ROMs, benefitting from signal-handler notifications ; IIRC it uses a combination of file server, <ram/> node, <import>...node, fs_rom server and such.

Here’s the updated text:


I reviewed the client-server process and implemented the approach you suggested. In fact, this process is a typical example from the Genode framework’s sample codes, and it works well when both modules are under my control.

However, when it comes to network configuration where I’m using the nic_router, this module is not under my control, and its behavior regarding configurations is predefined. For instance, if you define an interface for the desired domain, it exits DHCP mode and is set to the specified address. My goal is to be able to change the interface’s address at runtime.

To achieve this, the Genode framework provides a dynamic_rom module, which allows configuration changes to be applied dynamically at runtime to the target module. Thus, I followed the approach used in the nic_router_uplinks program by utilizing dynamic_rom as shown below:

<start name="dynamic_rom" priority="-1">
    <resource name="RAM" quantum="4M"/>
    <provides><service name="ROM"/> </provides>
    <config verbose="yes">
        <rom name="router.config">
            <inline>
                <config>
                   ....
                    <domain name="uplink">
                        <nat domain="downlink" icmp-ids="999"/>
                    </domain>

                    <domain name="downlink" interface="10.0.3.56/24">
                        <dhcp-server ip_first="10.0.3.5" ip_last="10.0.3.5"/>
                        <icmp dst="0.0.0.0/0" domain="uplink" />
                    </domain>
                </config>
            </inline>
    </config>
</start>

<start name="nic_router" caps="200" priority="-2">
    <resource name="RAM" quantum="10M"/>
    <provides>
        <service name="Nic"/>
        <service name="Uplink"/>
    </provides>
    <route>
        <service name="ROM" label="config">
            <child name="dynamic_rom" label="router.config"/>
        </service>
        <any-service> <parent/> <any-child/> </any-service>
    </route>
</start>

The key point here is to be able to modify the router.config file at runtime using a function like the one below (as an example), which I am directly borrowing from the mixer sample program:

static int write_config(char const *file, char const *data, size_t length)
{
    if (length == 0) return 0;

    QFile mixer_file(file);
    if (!mixer_file.open(QIODevice::WriteOnly)) {
        Genode::error("could not open '", file, "'");
        return -1;
    }

    mixer_file.write(data, length);
    mixer_file.close();

    return 0;
}

This function is derived from the mixer example. However, the issue is that since the file is virtually defined in ROM and doesn’t have a traditional file system structure, writing to it isn’t possible. This is something the Sculpt OS has solved and is actively using. I aim to understand exactly how to bind the file to the file system in a way that dynamic_rom can access it.

Thank you, now I think I understand what’s happening and how to help you.

In his first response, ssumpf suggested dynamic_rom…

… as just a way for you to create a ‘proof of concept’, I believe.

In his next message after your needs were defined more precisely, he then suggested to replace dynamic_rom with a hand-crafted ROM ↔ Report relationship, between the client and the server.

You see, the name ‘dynamic_rom’ could be interpreted in a fuzzy way, for someone new to Genode’s inventory of components. That happened to me all the time in my first years with Genode. The name does not mean that others ROMs are “static”, and that you need to utilize dynamic_rom if you want a “dynamic” ROM – the terminology is this, instead : ROM refers, quite litterally, to the fact that a client can only read (Read Only) the data provided by the interface, it does not mean that the data is cast in stone, never changes : the component that serves that data is allowed to change it at will, the data is dynamic, if the data provider wants it to be dynamic. It’s just that the data is read/write on the server side, byt read-only-memory on the client side.

The way this works is, the data-serving component sends a signal whenever the data payload changes – or even, it does not even use the “ROM” programming interface, it uses the “Report” API. So “Report” and “ROM” are two sides of the same coin, two perspectives on the same data. And that duality is exposed in the report_rom component, which “connects” the client and server with a convenient (configurable) interim interface.

So what does the dynamic_rom component bring to the table, if it’s not really “dynamicity”, because ROM dynamicity already exists in the basic API ? Well it brings the ability to script the dynamicity of the ROM : it has an internal script language with if… else… wait X millliseconds… commands, that it can understand, and you may provide it in order to “script” ROM changes in a simple textual XML sequence, without writing C++ code. That makes it useful for testing (Quality Assurance) purposes, mainly. Or for creating demos to showcase something.

So bottom line :slight_smile: , if you want to hardcode the configuration of nic_router, make it follow always the same scripted configuration changes, for testing purposes, then use dynamic_router.
But if you want to implement a real-world scenario (not a test scenario), where the user (or an application) really controls the configuration of nic_router, you should not use dynamic_rom, it seems to me.


If I followed correctly, what you need is to control the XML configuration of the nic_router component. This is a pattern that repeats many, many times in Genode-based systems, so it’s well understood and resolved, lots of maturity in that area (like in other parts of Genode for that matter ^^). I’ve been through that intensely in the last year or so, so while it’s fresh in my mind I’ll write a few lines about it:

  • Genode components expect to access their configuration through the API called “ROM”, specifically in a ROM named “config”
  • that “config” name can be “re routed” through the “route” XML nodes, to something more specific (for example “nicrouter.config”)
  • that allows you to apply clever tricks, to make e.g. nic_router transparently retrieve its config from something completely different than the boot-module XML configuration : a scripted ROM, a filesystem file, for example.

ssumpf and me might write more in the next post, but first let’s give you time to digest the above and check that we’re on the same page :slight_smile:

1 Like

Thank you for the detailed explanation! I now understand the role of dynamic_rom and its place in Genode’s configuration system. I had forgotten the precise definition of ROM in the context of Genode.

It seems that the challenge is now well-defined. Could you please provide further details?

Well, since nic_router’s configuration needs to be “driven” from the outside (from another component, let’s call it “my_ping_app”), instead of being static, you’ll want to route nic_router’s config to the report_rom ‘interim’ component. From memory, it might look something like this:

<route>
  <!-- re-route our config, tell nic_router (and parent init) to read it from report_rom, and to relabel it from 'config' to 'config.nicrouter' at the same time: -->
  <service name="ROM" label="config"> <child name="report_rom" label="config.nicrouter"/> </service>
..
</route>

In this context, the word “child” could actually be understood as “sibling”, that is, the report_rom is a brother/sister of nic_router rather than a child of it.

The above is a pattern which is highly recurrent in Genode, so if you ‘grep’ for it there will be many hits.

And report_rom needs then to be configured to be the “in-between” interim component. Careful for this one, as there are many ways to get the wording wrong, and only half of them will result in a warning to LOG, the other half tend to fail silently ^^

I don’t recall the syntax off the top of my head, but it’s in the “policy” XML node, and it’s a series of pairs of input/output (Report input, ROM output), so in your case you would have to configure it to allow “report my_ping_application → config.nicrouter” and output label prefix “nic_router” or something like that.

Then write code using class ‘Reporter’ or ‘Expanding_reporter’ in your my_ping_application, to feed (report) some xml config code to nic_router.

That addresses the Report/ROM scenario.

For the other scenario, to be able to write the config with POSIX functions (open/write/close()), you’d need a more complex setup that I’m not familiar with.

Thank you, I think I’ve understood it to some extent, but it’s still a bit unclear. I’ll need to practice and try to implement it to fully grasp it. I appreciate your help

I believe I have understood the workflow, but the outcome wasn’t as expected. Either I’ve missed a step or something is incomplete.

Initially, I defined a route for the nic_router:

<route>
    <service name="ROM" label="config"> <child name="test_reports"/ </service>
    <any-service> <parent/> <any-child/> </any-service>
</route>

Subsequently, within the test_reports module, which is of type report_rom, I configured the following:

<start name="test_reports">
    <binary name="report_rom"/>
    <resource name="RAM" quantum="1M"/>
    <provides>
        <service name="Report"/>
        <service name="ROM"/>
    </provides>
    <config verbose="no">
        <policy label="nic_router -> config" report="test_wm_config-> config"/>
    </config>
</start>

In the test_wm_config module, I implemented logic similar to this:

void Panel::_config_toggled()
{
    Genode::log("*********************************************************************************");
    Genode::Reporter::Xml_generator xml(_nic_config_request, [&]() {
        xml.attribute("verbose_domain_state", "yes");

        xml.node("policy", [&]() {
            xml.attribute("label_prefix", "falkon");
            xml.attribute("domain", "downlink");
        });

        xml.node("policy", [&]() {
            xml.attribute("label_prefix", "drivers -> nic_drv");
            xml.attribute("domain", "uplink");
        });

        xml.node("domain", [&]() {
            xml.attribute("name", "uplink");

            xml.node("nat", [&]() {
                xml.attribute("domain", "downlink");
                xml.attribute("tcp-ports", "16384");
                xml.attribute("udp-ports", "16384");
                xml.attribute("icmp-ids", "16384");
            });
        });

        xml.node("domain", [&]() {
            xml.attribute("name", "downlink");
            xml.attribute("interface", "10.0.3.1/24");

            xml.node("dhcp-server", [&]() {
                xml.attribute("ip_first", "10.0.3.2");
                xml.attribute("ip_last", "10.0.3.2");

                xml.node("dns-server", [&]() {
                    xml.attribute("ip", "8.8.8.8");
                });
            });

            xml.node("tcp", [&]() {
                xml.attribute("dst", "0.0.0.0/0");

                xml.node("permit-any", [&]() {
                    xml.attribute("domain", "uplink");
                });
            });

            xml.node("udp", [&]() {
                xml.attribute("dst", "0.0.0.0/0");

                xml.node("permit-any", [&]() {
                    xml.attribute("domain", "uplink");
                });
            });

            xml.node("icmp", [&]() {
                xml.attribute("dst", "0.0.0.0/0");
                xml.attribute("domain", "uplink");
            });
        });
    });
}

init function {
_nic_config_request(env, "config");
_nic_config_request.enabled(true);
}

Now, it seems that the network configuration changes aren’t being applied. Could it be that there’s a missing step, such as a command or signal (akin to those in traditional operating systems) that needs to be invoked after applying the changes to ensure they take effect?

I see a few things to change, possible “typos”. Those tend to not always be signaled as errors or warnings in the LOG.

→ missing closing chevron “>” after “child name test_reports”. In the normal build process this would be caught by xmllint, but if the build didn’t call it somehow it might have been silently ‘accepted’.

→ you’ll probably want to set “verbose=yes”, at least in the early debugging stages. Verbosity can be overhwelming for “high traffic” report_roms, but this one seems to be used only by nic_router, so it will be low volume logging and should be very useful (i.e. if you see the reports coming in then you know the problem is downstream, but if you don’t see reports being applied then you know the problem is upstream).

→ missing white space between “test_wm_config” and the arrow ; this might be silently accepted, yet fail to be parsed by the report_rom component.

DId you ‘route’ the Report ? That is, in the route config of test_wm_config, did you add “service… name=Report… to child name=test_reports”… ?