Discovering how to modify ONOS hosts information with different providers - ederollora/OpenNetworkOperatingSystem GitHub Wiki
TLDR; If you provide the key and value "ips" : []
in your JSON config via Network Config Host Provider, then you can not modify IP addresses via another host provider.
While I was working for the X2Rail project, one of my apps needed to modify hosts information. I was initially using the default hostProvider (org.onosproject.hostprovider) provided by ONOS but switched to the Network Config Host Provider. You can see here how to use it. One needs to send a list of hosts and their MAC address+VLAN, location, IPs, annotations and so on. When that is sent to http://:8181/onos/v1/network/configuration/hosts and gets configured into ONOS, one can check it running hosts command in the ONOS CLI.
This is very useful because one can prevent fake hosts from maliciously getting inserted into the host list. One can think of the way to create spoofed packets with random MACs addresses to create new HostIDs. One can imagine the mess in the ONOS Web GUI with this kind of spoofed packets being converted into malicious hosts within ONOS. With the use of the Network Config Host Provider one can determine which hosts exactly appear at the CLI, GUI and prevent anyone from modifying it.
Now, the app for the X2Rail-3 was trying to implement slicing within trains using OpenFlow switches. It does not exactly follow the same "rules" as one can see in network slicing papers because the requirements are different (i.e. it could be possible that different slice tenants need to communicate, thus not having 100% isolation), but the main features were the same: forwarding isolation, independent address space, (possibly) different QoS using Meters for bandwidth or queue config if possible, etc.
One of the features within the slicing app to support independent addressing between slices is a proper DHCP server. The app needed a DHCP server to handle all the addressing per slice. I used the ONF's DHCP application to start but there were significant changes, specially regarding slice management for addressing, and several attack preventions (spoofing, etc.). One of the changes relied on modifying host information when a DHCP ACK was sent. The app needed to change a particular hosts IP address so that ONOS future forwarding app could properly get paths between two tenants. So let's check the original DHCP application. The original DHCP app used this ProvideId (PID):
private static final ProviderId PID = new ProviderId("of", "org.onosproject.provider.host", true);
All I learnt so far was by inspecting the code so I could be wrong, but, I think this means that DHCP depends on the original "org.onosproject.hostprovider" that I stopped using (at least, if I used the aforementioned ProviderID, ONOS complaint that "org.onosproject.provider.host" was not registered. My idea was then to use my own provider PID. I managed to create one that did not conflict with any other but I had a big issue, whenever I tried to add the IPs via the function hostDetected
I was never able to do so. I could change all the parameters one can imagine, replaceIPs in hostDetected
, build the HostDescription differently (adding annotations) but I could never make it work to also change IPs. See my code:
HostDescription desc = new DefaultHostDescription(mac, vlanId, hostLocation, Sets.newHashSet(ipAssigned), true, annotations); // true for configured, this would show another "provider=" when running hosts in the ONOS CLI
hostProviderService.hostDetected(hostId, desc, true); // true for replaceIps
Now see the only changes I managed to achieve. This is the output when running hosts before and after running hostDetected
:
# before
id=00:00:00:00:00:01/None, mac=00:00:00:00:00:01, locations=[of:0000000000000002/2], auxLocations=null, vlan=None,
ip(s)=[], gridX=null, gridY=null, latitude=null, locType=none, longitude=null, name=HOST_1, innerVlan=None,
outerTPID=vlan, provider=host:org.onosproject.netcfghost, configured=true
# after
id=00:00:00:00:00:01/None, mac=00:00:00:00:00:01, locations=[of:0000000000000002/2], auxLocations=null, vlan=None,
ip(s)=[], locType=none, name=HOST_1, x2rail.dhco=configured, innerVlan=None, outerTPID=unknown,
provider=x2r:dk.dtu.x2rail.dhcp, configured=true
As you can see I could change the provider=
and I also managed to change the annotations (x2rail.dhco=configured
), but not the IPs and I knew I added them to the HostDescription.
Originally I thought the web JSON config I sent to Network Config Host Provider was via this web resource: HostsWebResource.java](https://github.com/opennetworkinglab/onos/blob/master/web/api/src/main/java/org/onosproject/rest/resources/HostsWebResource.java)`. I saw @Path("hosts")
(because the path for the REST API used ended like network/configuration/hosts) and I was convinced that the configuration was done via public Response createAndAddHost(InputStream stream)
, I mean, some things look strange (like no for loop to configure all hosts??) but I was somehow convinced it was this way. I was also pretty sure that NetworkConfigHostProvider.java was somehow involved in this process but I could not figure out how. I even opened whole ONOS in Jetbrains in order to debug the core code. It was funny because no matter were I placed the breaks in the previous two classes, nothing happened. So I could be wrong and none of those classes that were ever used to configure hosts using the REST API of Network Config Host Provider*.
If Network Config Host Provider was used, I imagine some kind of "network" and "config" service or API had to be used for this. Via the search engine in Github I discovered NetworkConfigWebResource.java because they had the path @Path("network/configuration")
. This made more sense then the previous. I actually found this function @Path("{subjectClassKey}")
which might make reference to "hosts" in path "network/configuration/hosts" and then use this host as a variable to drive configuration further into the core (like configure only hosts configuration using config classes). Yep. I was right, I placed debugging breaks and I found the class. Now, if the web resource used this method I could to the same from my class, and well, maybe. I discovered that the process ended using service.applyConfig(subjectClassKey, subject, configKey, subjectNode.path(configKey));
. I could generate a similar piece of code and try to configure the hosts again. I did not like that subject (I think) was a JSON string and ignored if my host config had to be converted to JSON first or could be done in any other way. I didn't really like this way but it might have been possible. It was 2 a.m. at in the morning when I discovered this and I was not fully convinced to it this way, I preferred the hostDetected
function. So how can I do this if hostDetected
was not able to modify IPs before?
I decided to discover why IP addresses were not modified. I mean I could modify annotations or the name fo the config class too. I placed break points in the hostDetected
function. I discovered it was part of the HostManager.java class. Once I could debug the code at hostDetected
I saw that when I tried to modify the hosts information this calls were chained provider.hostDetected() -> validateHost() -> BasicHostOperator.combine()
. At the combine
function everything became clear. Before the provider merged the information I added to HostDescription
it would first get any configuration (if there was any) and apply that on top of my own. For example, at BasicHostOperator.java and function combine
one can see that the ipAddress that first the one for the HostDescription
I provided. BUT if any configuration was provided before (like the one I did via REST) that was the one overriding my hostDescription. This made sense because this is the problem I saw with the hosts command. And actually it does some kind of sense because network config might need to override any description changes tried afterwards. However, I said, I never provided an IP addresses in the network host configuration JSON because I new I would need to change it later on. See one of my host's JSON configuration:
{
// ...
"00:00:00:00:00:01/None": {
"basic": {
"locations": ["of:0000000000000002/2"],
"ips": [ ],
"name": "HOST_1"
}
},
//...
}
The only reason why BasicHostOperator put some preference for the IPs via network config is, and only if, the IP addresses configured via network config were not null. Let me explain. The code runs this:
Set<IpAddress> ipAddresses = descr.ipAddress();
Set<IpAddress> cfgIpAddresses = cfg.ipAddresses();
if (cfgIpAddresses != null) {
ipAddresses = cfgIpAddresses;
}
if cfgIpAddresses
is different to null then the configured IPs (cfgIpAddresses) had more priority than the one provided ni the new HostDescription. Right, but I provided no IPs in my JSON configuration. Oh! Well, I provided no IPs but there might be a difference between providing an empty "ip" array AND no "ip" key and value. I think this can make a difference between a null and non null value. Let's try:
{
// ...
"00:00:00:00:00:01/None": {
"basic": {
"locations": ["of:0000000000000002/2"],
"name": "HOST_1"
}
},
//...
}
Let's try the code again and verify via hosts command. Surprise:
#before
id=00:00:00:00:00:01/None, mac=00:00:00:00:00:01, locations=[of:0000000000000002/2], auxLocations=null, vlan=None, ip(s)=[], gridX=null, gridY=null, latitude=null, locType=none, longitude=null, name=HOST_1, innerVlan=None, outerTPID=vlan, provider=host:org.onosproject.netcfghost, configured=true
#after
id=00:00:00:00:00:01/None, mac=00:00:00:00:00:01, locations=[of:0000000000000002/2], auxLocations=null, vlan=None, ip(s)=[192.168.1.223], locType=none, name=HOST_1, x2rail.dhco=configured, innerVlan=None, outerTPID=unknown, provider=x2r:dk.dtu.x2rail.dhcp, configured=true
The only change I needed after a full day of investigation was to provide no "ips" : [] empty array in the REST configuration :)