Wednesday, January 13, 2016

Connections in NetworkManager

Connections, as defined and used by NM, are very close to PvDs. The goal of this post is to analyse data structures/functions for connections within NetworkManager and so that plan of integrating PvDs into NM can be developed. This is done in a separate post.

A definition of connection in NetworkManager can be found in the comment within the libnm-core/nm-connection.c file:
An #NMConnection describes all the settings and configuration values that are necessary to configure network devices for operation on a specific network. Connections are the fundamental operating object for NetworkManager; no device is connected without a #NMConnection, or disconnected without having been connected with a #NMConnection.

Each #NMConnection contains a list of #NMSetting objects usually referenced by name (using nm_connection_get_setting_by_name()) or by type (with nm_connection_get_setting()). The settings describe the actual parameters with which the network devices are configured, including device-specific parameters (MTU, SSID, APN, channel, rate, etc) and IP-level parameters (addresses, routes, addressing methods, etc).
In the following text we'll see how connections are implemented in the NM code, how they are initialized and how they are accessed over the DBus. Note that there are some parts related to connections that are specific for a system/distribution on which NM is running. In that case I concentrate on how things are done on Fedora (and very likely all RHEL derivatives).

Class and interface hierarchy


The base for all connection objects in NetworkManager is the interface defined in the files libnm-core/nm-connection.[ch]. The interface is implemented by the following classes:
  • Class NMSettingsConnectionClass defined in the files  src/settings/nm-settings-connection.[ch].

    This class is used by NetworkManager daemon and it is exported via DBus interface.
     
  • Class NMRemoteConnectionClass defined in the files libnm/nm-remote-connection.[ch].

    Used by clients in clients subdirectory.
     
  • Class NMSimpleConnectionClass defined in the files libnm-core/nm-simple-connection.[ch].

    This is the object passed via DBus so it is for communicating connections from and to NetworkManager and its clients.

Accessing individual connection data stored in NetworkManager


Each connection, active or not, known to the NetworkManager is exposed through DBus on path /org/freedesktop/NetworkManager/Settings/%u where %u is a sequence number assigned to each connection. Interface implemented by each connection is org.freedesktop.NetworkManager.Settings.Connection. The interface is described in introspection/nm-settings-connection.xml file.

To invoke a method defined in interface on the given object you can use dbus-send command line tool like this:
dbus-send --print-reply --system \
    --dest=org.freedesktop.NetworkManager \
    /org/freedesktop/NetworkManager/Settings/0 \
    org.freedesktop.NetworkManager.Settings.Connection.GetSettings
In this particular case we are invoking GetSettings method on object /org/freedesktop/NetworkManager/Settings/0 which will return us its configuration parameters. Note that invoking this particular method is easy since there are no arguments to the method.

The class that represents connection, and that is answering to DBus messages, is declared in src/settings/nm-settings-connection.[ch] files. This class implements interface NM_TYPE_CONNECTION and also subclasses NM_TYPE_EXPORTED_OBJECT class.  The NM_TYPE_EXPORTED_OBJECT class has all the methods necessary to expose the object on DBus.

To see what functions are called when DBus methods are called take a look at the end of the source file nm-settings-connection.c. There, you'll find the following code:
nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (class),
    NMDBUS_TYPE_SETTINGS_CONNECTION_SKELETON,
    "Update", impl_settings_connection_update,
    "UpdateUnsaved", impl_settings_connection_update_unsaved,
    "Delete", impl_settings_connection_delete,
    "GetSettings", impl_settings_connection_get_settings,
    "GetSecrets", impl_settings_connection_get_secrets,
    "ClearSecrets", impl_settings_connection_clear_secrets,
    "Save", impl_settings_connection_save,
    NULL);
What this code does is that it binds GBus methods to function that should be called. When we called GetSettings method, obviously that ended up in the function impl_settings_get_settings().

The first step done when processing GetSettings method is authorization check. After authorization check has succeeded, the return message is constructed in get_settings_auth_cb() method.


Accessing and manipulating all connections in NetworkManager


NetworkManager exposes interface org.freedesktop.NetworkManager.Setting on object org.freedesktop.NetworkManager.Setting that, among other things, allows the user to retrieve list of all the known connections to the NetworkManager.  To get all connections you would could use the following dbus-send command:
dbus-send --print-reply --type=method_call --system \
        --dest=org.freedesktop.NetworkManager \
        /org/freedesktop/NetworkManager/Settings \
        org.freedesktop.DBus.Properties.Get \
        string:org.freedesktop.NetworkManager.Settings \
        string:"Connections"
This would get you something like the following output:
variant array [
   object path "/org/freedesktop/NetworkManager/Settings/10"
   object path "/org/freedesktop/NetworkManager/Settings/11"
   object path "/org/freedesktop/NetworkManager/Settings/12"
   object path "/org/freedesktop/NetworkManager/Settings/13"
   object path "/org/freedesktop/NetworkManager/Settings/14"
   object path "/org/freedesktop/NetworkManager/Settings/15"
   object path "/org/freedesktop/NetworkManager/Settings/0"
   object path "/org/freedesktop/NetworkManager/Settings/1"
   object path "/org/freedesktop/NetworkManager/Settings/2"
   object path "/org/freedesktop/NetworkManager/Settings/3"
   object path "/org/freedesktop/NetworkManager/Settings/4"
   object path "/org/freedesktop/NetworkManager/Settings/5"
   object path "/org/freedesktop/NetworkManager/Settings/6"
   object path "/org/freedesktop/NetworkManager/Settings/7"
   object path "/org/freedesktop/NetworkManager/Settings/8"
   object path "/org/freedesktop/NetworkManager/Settings/9"
   object path "/org/freedesktop/NetworkManager/Settings/16"
]
The exact output will depend on your particular setup and usage.

The given interface and property is implemented by class NMSettingsClass (defined in src/settings/nm-settings.[ch]). This class implements interface NM_TYPE_CONNECTION_PROVIDED (defined in src/nm-connection-provider.[ch]). There is only one object of this class in NetworkManager and it is instantiated when NetworkManager is starting.

Looking in the file src/settings/nm-settings.c you can see at the end registration of function to be called when DBus messages are received. DBus interface of this module is defined in introspection/nm-settings.xml file. Here is the relevant code that binds DBus methos to the functions that implement them:
nm_exported_object_class_add_interface (
        NM_EXPORTED_OBJECT_CLASS (class),
        NMDBUS_TYPE_SETTINGS_SKELETON,
        "ListConnections", impl_settings_list_connections,
        "GetConnectionByUuid", impl_settings_get_connection_by_uuid,
        "AddConnection", impl_settings_add_connection,
        "AddConnectionUnsaved", impl_settings_add_connection_unsaved,
        "LoadConnections", impl_settings_load_connections,
        "ReloadConnections", impl_settings_reload_connections,
        "SaveHostname", impl_settings_save_hostname,
        NULL);
So, when we called ListConnections method, obviously that ended up in the function impl_settings_list_connections(). Here, we'll emphasize one more method, LoadConnections. This DBus method, implemented in impl_settings_load_connections(), load all connections defined in the system. We'll take a look now at that method.

Initializing connections


All network connections are loaded and initialized from two sources: system dependent network configuration and VPN configuration scripts.

System dependent network configuration


There are several types of distributions with different network configuration mechanisms. Since that part is obviously system dependent, NetworkManager has a plugin system that isolates the majority of NetworkManager code from those system dependent parts. Plugins can be found in the directory src/settings/plugins. Additionally, all the plugins are based on the src/settings/plugin.[ch] base class. In the case of Fedora Linux (as well as RHEL, CentOS and other derivatives) network configuration is recorded in scripts in the directory /etc/sysconfig/network-scripts. and the plugin that handles those configuration files is stored in the directory src/settings/plugins/ifcfg-rh.

The initialization of connections stored in the directory /etc/sysconfig/network-scripts is done when NetworkManager bootstraps and instantiates object NMSettings of type NMSettingsClass. This is performed in the function nm_manager_start() in the file src/nm-manager.c. There, the method nm_settings_start() is called on the NMSettings object which in turn first initializes all plugins (as, by default, found in the directory /usr/lib64/NetworkManager/). It then calls private method load_connections() to actually load all connections. Note that in the directory  /usr/lib64/NetworkManager/ there are plugins of other types too, but only plugins that have prefix libnm-settings-plugin- are loaded. Which plugins should be loaded are can be defined in three places (lowest to highest priority):

  1. Compile time defaults, as given to configure.sh script, or, by default for RHEL type systems "ifcfg-rh,ibft" plugins.
  2. In configuration file /etc/NetworkManager/NetworkManager.conf.
  3. As specified in the command line.

Method load_connections() iterates over every defined plugin and asks each plugin for all registered connections it knows by calling a method get_connections() within a specific plugin. For RedHat type of distributions the the plugin that handles all connections is src/nm-settings/plugins/ifcfg-rh/plugin.c and in that file function get_connections() is called. Now, if called for the first time, this function will in turn call read_connections() within the same plugin/file that will read all available connections. Basically, it opens directory /etc/sysconfig/network-scripts and builds a list of all files in the directory. Than, it tries to open each file and only those that were parsed properly are left as valid connections. Each found connection is stored in object NMIfcfgConnection of type NMIfcfgConnectionClass. These objects are defined in files src/nm-settings/plugins/ifcfg-rh/nm-ifcfg-connection.[ch].

When all the connections were loaded, read_connections() returns a list of all known connections to the plugin. The function load_connections() then, for each connection reported by each plugin, calls claim_connection() method in nm-connection.c. This function, among other tasks, exports the connection via DBus in a form described above, in the section Accessing individual connection data stored in NetworkManager.

VPN configuration scripts


Details about VPN connections are stored in /etc/NetworkManager/system-connections directory, one subdirectory per VPN. Those files are read by src/vpn-manager/nm-vpn-manager.c when the object is initialized and as such initialized when VPN manager is initialized. VPN manager also also monitors changes in the VPN configuration directory and acts appropriately.

Properties of a connection


Each connection has a set of properties attached to it in a form of a key-value pairs.

Activating a connection


A connection is activated by calling ActivateConnection DBus method. This method is implemented in the NetworkManager's main class/object, NMManager. This class/object is a singleton object who's impementation is in src/nm-manager.[ch] files. Looking at the code that binds DBus methods to the functions that implement them we can see that ActivateConnection is implemented by the function impl_manager_activate_connection(). The ActivateConnection method, and its implementation function, accept several parameters:
  1. Connection that should be activated identified by its connection path (e.g. "/org/freedesktop/NetworkManager/Settings/2").
  2. Device on which connection should be activated identified by its path (e.g. "/org/freedesktop/NetworkManager/Devices/2").
  3. Specific object?
Some of the input argument can be unspecified. To make them unspecified in DBus call they should be set to "/" and this will be translated into NULL pointer in the impl_manager_activate_connection() function. Of all combinations of parameters (with respect to being NULL or non-NULL) the following ones are allowed:
  1. When connection path is not specified device must be given. In that case all the connections for that device will be retrieved and the best one will be selected. "The best one" is defined as the most recently used one.
  2. If connection path is specified, then device might or might not be specified. In case it is not defined the best device for the given connection will be selected. To determine "the best device" first list of all devices is retrieved and then for each device status is checked (must be managed, available, compatible with the requested connection). Note that "compatible with the requested connection" means, for example, you can not start wireless connection on a wired connection.
There are "software only", or virtual, connections. Those are checked in the function nm_connection_is_virtual() which is implemented in the file libnm-core/nm-connection.c. When this post was written, the following connections were defined as virtual, or software only:
  • Bond
  • Team
  • Bridge
  • VLAN
  • TUN
  • IPtunnel
  • MACVLAN
  • VXLAN
Finally, there are also VPN connections that also don't have associated devices.

When all the checks are performed, devices and connections are found, then an object of type NMActiveConnection is created in the function _new_active_connection(). Here, in case VPN connection is started, VPN establishment is initiated and you can read more about that process in another post.


Sunday, January 3, 2016

Few tips about GObject for C++ programmers

While studying NetworkManager code I got more and more comfortable with OO programming retrofitted into C programming language using GObject library. At first I was confused because it is quite complex and I didn't well understand how it works. Furthermore, the documentation for beginners is lacking and scare. But, the more I learned the more logical it seemed to me. Since I'm well acquainted with C programming language and to some extent to C++ I decided to write about OO model in GObject, NetworkManager and C based on what someone might expect from C++. As usual, this is for my later reference, but also for everyone else wanting to learn how to use or understand GObject. I'll write in a form of tips, or details you should know in order to better understand how it works.

Declaring a class


In C++ language there are two parts of class implementation. First, there is a class declaration and then there is a class definition. Class declaration goes into a header file (e.g. Point.h) and there you'll find something like this:
class Point {
}
Now, the question is how to do this in C using GObject? The process is a bit more involved due to the characteristics of a C programming language. Anyway, the recipe is the following one. In the header file (e.g. point.h) that declares class method you would put the following code:
#include <glib-object.h>

#define TYPE_POINT (point_get_type())

typedef struct _PointClass {
    GObjectClass parent_class;
} PointClass;

typedef struct _Point {
    GObject parent_instance;
} Point;

GType point_get_type(void);
This code declares two structures, one that will be used by a class (PointClass) and the another one for each object of the given class type (Point). You would also need to define class and object initialization functions as follows:
#include "point.h"

typedef struct _PointPrivate PointPrivate;

G_DEFINE_TYPE (Point, point, G_TYPE_OBJECT);

static void point_class_init (PointClass* klass)
{
}

static void point_init (Point* self)
{
}
Finally, you can write a main function (in e.g. main.c file) which doesn't do anything, but, nevertheless the class is here. That's how you would declare a class.

The code for this example can be found on GitHub.

Final and derivable classes


The distinction between the two is that you can not subclass final class, while on the other hand, derivable classes can be sub classed. I don't want to go into discussions why final classes, but only into technical details related to their use in GObject. GObject documentation recommends to use final classes if you can, and only then derivable classes. If you take a look at some other sources, especially for C++, you'll find doubts about benefits of final classes.

So, in C++ (at least from C++11) you would declare final class like this:
class Point final {
};
Now, if you try to subclass it:
class Point2 : public Point {
};
You'll receive compiler error due to the final keyword prohibiting class Point to be subclassed:
g++ -Wall -std=c++11 main.cpp -o main
In file included from main.cpp:1:0:
Point.h:6:7: error: cannot derive from ‘final’ base ‘Point’ in derived type ‘Point2’
 class Point2 : public Point {
       ^
Note that nothing special has to be done in order for a class to be derivable. Only if you want a class to be final you have to explicitly say so. So, what about GObject and final and derivable classes?

Two macros are used in GObject to create classes for the purpose of creating final and derivable classes. To define final class use G_DECLARE_FINAL_TYPE macro. You should modify header file from the previous example like this:
#include <glib-object.h>

#define TYPE_POINT (point_get_type())
G_DECLARE_FINAL_TYPE(Point, point, , POINT, GObject)

typedef struct _Point {
    GObject parent_instance;
} Point;
Note that there is no definition of type PointClass! The reason is that the macro G_DECLARE_FINAL_TYPE declares it. Also, point_get_type() isn't declared because G_DECLARE_FINAL_TYPE declares it. That's basically it for final class.

To declare derivable class, macro G_DECLARE_DERIVABLE_TYPE is used. In this case, as in the previous, you should only change header file which should look like this:
#include <glib-object.h>

#define TYPE_POINT (point_get_type())
G_DECLARE_DERIVABLE_TYPE(Point, point, , POINT, GObject)

typedef struct _PointClass {
    GObjectClass parent_instance;
} PointClass;
This time, PointClass type is defined explicitly while Point is defined by G_DECLARE_DERIVABLE_TYPE macro.

The code for this example can be found on GitHub.

Instantiate the object of a given class


The next thing is how would you instantiate an object of some class. For sample, to instantiate an object of a class written in C++ from the previous section in the main function you would do the following:
Point* point = new Point();
To do the same thing in C/GObject combo, you again have to do a bit more work. Actually, you have to define allocator for a class, i.e. something equivalent to a new keyword in C++. The way to create a new object of a given type is:
Point *point = someclass_new();
So, you need to define the function someclass_new(). Our will be very simple one:
Point* point_new(void)
{
        return g_object_new(TYPE_POINT, NULL);
}
Place it at the end of the file point.c and in the main function create an object of the Point class by calling the function point_new(). As a final note, instantiating final or derivable class is the same.

The code for this example can be found on GitHub.

Instantiate a singleton object


One feature used by NetworkManager a lot are singleton objects. Namely, some objects control system wide resources and thus there is a need to have a single object control a single resource. For example, main component of a NetworkManager is the object NM_TYPE_MANAGER and it is necessary to have only one such object. There is also a single object for a configuration held in the object/class NM_TYPE_CONFIG.

So, how is singleton object created? NetworkManager has some infrastructure that makes this task really simple. You start with a regular class/object. Then in the C file with an object implementation you should call the following two macros/functions:
NM_DEFINE_SINGLETON_INSTANCE (NMNetnsController);
NM_DEFINE_SINGLETON_REGISTER (NMNetnsController);
Then, in function that allocates an object, you have a variable singleton_instance that should be NULL until the object is created and then it should contain a pointer to a singleton object. Additionally, after creating object, you should call function nm_singleton_instance_register().

You are tasked with taking care that no new object of the given type is created, i.e. you can shoot yourself in the foot in case you don't take care to check the content of the variable singleton_instance. singleton_instance is a file global variable declared by the macros shown above.

Private attributes


The next step is to add private attributes to our class. Let's suppose that we need to add x and y coordinates. In C++ we would modify the class declaration in the following way:
class Point {
private:
        int x, y;
}
And that's basically it for C++ version. In case of C/GObject, you would define struct for private data in C file (point.c) with the following content:
typedef struct _PointPrivate PointPrivate;

struct _PointPrivate {
        int x, y;
};
First, observe that the structure is placed in C file, not in the header file. The reason is that this is private/internal structure so no users of a class should know the content of the given structure. Furthermore, keep in mode that this structure isn't required to be called as shown in the example by the GObject system, you can call it whatever you want, but it is a good practice and strongly suggested to add Private suffix to the object name for the readability reasons.

Private part for the object should be allocated when the class is initialized. To achieve that add the following line in the class initialization function, i.e. in point_class_init() function:
g_type_class_add_private (klass, sizeof (PointPrivate));
Before finishing with private attributes, there is one more thing we need to discuss, and that is how to access private part of the object. For that purpose it is good to declare the following macro:
#define POINT_GET_PRIVATE(object)      \
          (G_TYPE_INSTANCE_GET_PRIVATE((object), TYPE_POINT, PointPrivate))
This macro takes the pointer to an object and returns pointer to the private data of a given object. Note that the last argument to G_TYPE_INSTANCE_GET_PRIVATE is type (structure definition) of a private data. So, when you have a method that accesses the object's private data you would, at the start of the method, call the mentioned macro to obtain pointer to private data structure and then you would access it as usual.

The code for this example can be found on GitHub.

Public methods


After we added private attributes we want to add public methods that will allow us to get and set the values for the given private attributes. So, in C++ we would modify Point class definition as follows:
class Point {
private:
        int x, y;
public:
        void setValue(int x, int y);
        int getx(void);
        int gety(void);
};
The public methods should be defined in a C++ file (i.e. Point.cpp):
#include "Point.h"

void Point::setValue(int x, int y)
{
        this->x = x;
        this->y = y;
}

int Point::getx(void)
{
        return this->x;
}

int Point::gety(void)
{
        return this->y;
}
And, obviously, the methods would be used in a following way in the main function:
point->setValue(1, 1);
std::cout << "x=" << point->getx() << ", y=" << point->gety() << std::endl;
Now, to achieve the same with GObject system several changes to the existing C code are necessary. First, we'll define a helper macro in a C file (point.c) that we will use to obtain private data of an object of class Point:
#define POINT_GET_PRIVATE(object)      \
          (G_TYPE_INSTANCE_GET_PRIVATE((object), TYPE_POINT, PointPrivate))
Next, public methods are just global functions with a prefix of a object name and with the first argument being the pointer to an object of a given class. In our case, we have three public methods each with the following prototype:
void point_set_value(Point *point, int x, int y);
int point_get_x(Point *point);
int point_get_y(Point *point);
Those declaration should go into the header file (point.h) because they are accessible to any user of object Point, while their definitions should go into the C file (point.c):
void point_set_value(Point* point, int x, int y)
{
        PointPrivate* priv = POINT_GET_PRIVATE(point);

        priv->x = x;
        priv->y = y;
}

int point_get_x(Point* point)
{
        PointPrivate* priv = POINT_GET_PRIVATE(point);

        return priv->x;
}

int point_get_y(Point* point)
{
        PointPrivate* priv = POINT_GET_PRIVATE(point);

        return priv->y;
}
Finally, you should use those public methods after object of class Point is allocated in the main function. Here is the example:
point_set_value(point, 1, 1);
printf("x=%d, y=%d\n", point_get_x(point), point_get_y(point));
And that's it. The code for this example can be found in GitHub repository.

Virtual methods


The methods implemented using GObject in the previous section are non-virtual public methods. They are easy to implement, but the least flexible. So, to implement public virtual methods in GObject we need to:
  1. In class structure (PointClass) add function pointer for each virtual method we need.
  2. In header file declare dispatcher methods that will be called by the user of the class.
  3. Define dispatcher methods in the source file.
  4. Define implementation of virtual methods in the source file.
  5. Initialize function pointers with the implementation of virtual methods.
Here are the steps in more details with the concrete example of Point class. Before continuing, note that virtual methods are possible only in derivable classes, not in final!

Step 1 is to add function pointer fields in the class structure. That means that you class structure definition has to have the following format:
typedef struct _PointClass
{
        GObjectClass parent_class;
        void (*set_value) (Point *self, int x, int y);
        int (*get_x) (Point *self);
        int (*get_y) (Point *self);
} PointClass;
Note the bold parts, those are function pointer that will point to implementation of virtual methods. Each method takes, as the first argument, pointer to the object it has to act upon.

Step 2 is to declare dispatcher methods. Those are very similar to non-virtual public methods in appearance, i.e. the name consists of a object name prefix and the function name. Again, this is recommended, not something that is mandated by GObject system. Anyway, in our case those are the lines you have to add to header file:
void point_set_value(Point* self, int x, int y);
int point_get_x(Point* self);
int point_get_y(Point* self);
Step 3 is to define dispatcher methods in the source file. I'll do it only for one method, the rest are the same. So, in case of point_set_value() it would look like this:
void point_set_value(Point* self, int x, int y)
{
PointClass *klass;
klass = _POINT_GET_CLASS(self);
klass->set_value(self, x, y);
}
As you can see, what the method does is it access class definition, and then invokes appropriate function through its pointer. I have to stress here two things:
  1. I skipped assertions, i.e. if the object/classes are of right type!
  2. Note the leading underscore in _POINT_GET_CLASS. It is the consequence of skipping module name when calling G_DECLARE_DERIVABLE_TYPE macro, i.e. I skipped third argument!
So, any user of a class will call those public methods which will dispatch the call to the real method.

In step 4 we have to define implementation of virtual methods in the source file. Again, I'll show it only for a single method. Also, you'll note that the implementation is the same as for non-virtual public methods. Only we have to call them differently so that compiler can differentiate between different function. I decided to use prefix objectname_private_, but it is my choice. I didn't manage to find something recommended by GObject. So, here it goes for the function to set value:
void point_private_set_value(Point* self, int x, int y)
{
PointPrivate *priv = POINT_GET_PRIVATE(self);
priv->x = x;
priv->y = y;
}
Finally, step 5 is to initialize pointers to dispatchers which in turn will call real methods. This has to be done by modifying definition of PointClass structure (or class structure in general). Here is how this structure should look like:
static void
point_class_init (PointClass* klass)
{
g_type_class_add_private (klass, sizeof (PointPrivate));
klass->set_value = point_private_set_value;
klass->get_y = point_private_get_y;
klass->get_x = point_private_get_x;

}
Note that we have the part that adds private data for the objects. The bold parts are the ones that initialize virtual functions.

Anyway, that's it for virtual function. You can compile the code on GitHub.

Inheritence and subclassing


Inheritance allows for specialization of some class without touching that class internals. It is one of very important OO mechanisms and as such it is also supported in GObject and used by, e.g. NetworkManager.

So, suppose we want to define 3D point by basing its implementation on Point class while in the same time adding only z coordinate. In C++ we would declare new class and say explicitly that we inherit base class Point:
#include "Point.h"

class Point3D: public Point
{
private:
        int z;
public:
        void setValue(int x, int y, int z);
        int getz(void);
}
As you can see we include declaration of 2D point and then we add third coordinate as well as new methods, one to retrieve the third coordinate and another one to be able to set all three coordinates. There is also definition (i.e. implementation) of a new class which is placed in Point3D.cpp file:
#include "Point3D.h"

void Point3D::setValue(int x, int y, int z)
{
        Point::setValue(x, y);
        this->z = z;
}

int Point3D::getz(void)
{
        return this->z;
}
Note how the base class method is called, by prefixing the method name with the base class name.

As usual, the question is how this is done in C using GObject library. First, GObject make distinction between final and derivable classes, as we already saw above. Base class must be derivable, while subclass might be either final or derivable.

So, we are going to define derivable class Point as follows:
#include <glib-object.h>

#define TYPE_POINT (point_get_type())
G_DECLARE_DERIVABLE_TYPE(Point, point, , POINT, GObject)

typedef struct _PointClass {
        GObjectClass parent_class;
} PointClass;

Point* point_new(void);

void point_set_value(Point *point, int x, int y);
int point_get_x(Point *point);
int point_get_y(Point *point);
This is almost the same as original Point declaration we had. The implementation part isn't changed. Now, Point3D is declared as follows:
#include <glib-object.h>
#include "point.h"

#define TYPE_POINT3D (point3d_get_type())
G_DECLARE_FINAL_TYPE(Point3D, point3d, , POINT3d, Point)

typedef struct _Point3D {
        Point parent_instance;
} Point3D;

Point3D* point3d_new(void);

void point3d_set_value(Point3D* point, int x, int y, int z);
int point3d_get_z(Point3D* point);
Note the last parameter in G_DECLARE_FINAL_TYPE is Point. Basically, that's the base class and up until now we had there GObject from which all object descend.

Defining and implementing interfaces


Interfaces in OO languages are used as a form of a contract between different components. Some languages, as Java for example, have direct support for defining interfaces, while others, like C++, don't have and instead use some indirect methods. Here I would like to show how interfaces are implemented in GObject and for that purpose I'll declare interface that will be implemented by Point class used so far for examples. We'll start with C++, as usual.

To define interface in C++ abstract classes are used. Abstract class can not be instantiated because it has at least one pure virtual method that needs to be implemented by subclass. So, here is how interface for Point class would look like in C++:
class PointInterface {
public:
virtual void setValue(int x, int y) = 0;
virtual int getx(void) = 0;
virtual int gety(void) = 0;
};
As you can see, the pure virtual method is declared by having "equal zero" addition. Now, just one more change is necessary and that is to declare that Point class implements this interface/abstract class. To do that only a single change is necessary, i.e.:
class Point : public PointInterface { ... }
In other words, Point inherits PointInterface.

So, how to do this in GObject? Here is the official documentation and we are going to do this for our existing Point class.

TBC...

Constructor


In the previous examples we already saw constructors, class constructor (*_class_init) and object constructor (*_init). Class constructor is called only once, when a first object of a given class is instantiated. Object constructor, on the other hand, is called every time an object of a given class is instantiated. You can easily see this modifying the previous example so that you place printf() statements into appropriate constructors (point_class_init() for a class constructor and point_init() for an object constructor). Then, by creating two objects of a Point class you'll clearly see that the class constructor is called only once, while the object constructor is called as many times as you instantiated objects.

Destructor


Destructor isn't necessary



Object/class initialization and removal

When creating a new object using g_object_new() function there is a certain sequence of steps executed. For an object that is first of its class, the sequence is:

  1. _class_init()
  2. constructor()
  3. constructed()
  4. _init()

Of those four, the first and fourth, are mandatory and have to be defined in the source. The second and third steps are optional and you define them in class constructor (_class_init()) by assigning appropriate pointers to the class interface.

Note that g_object_new() accepts an object type that should be created and a sequence of attribute-value pairs terminated with a single NULL. Those attribute-value pairs are used to set properties of an object (installed using g_object_class_install_property() function) but only after object constructor finishes. Also, the additional requirement is to define set_property class method that will be called to set each property.

For objects of next type only object constructor is called and then properties are initialized.

When object is removed then its destructor is called, if it exists. If this is the last object of its class, then finalize method is called, again, if it exists.

Name spaces


TBD


Literature

If you try to Google for GObject examples or tutorials you'll find some materials from Gnome Web site. But, the truth is that those tutorials leave some open questions and it is really hard to find something else. In the end I managed to find some references that I think are very valuable so here they are:

Saturday, January 2, 2016

Processing RA messages in NetworkManager

The goal of this post is to analyze processing of RA messages through the NetworkManager code, starting with the initial reception all the way through the assignment of parameters to a device through which the RA was received. As a special case will also take a look what happens when RS is sent, i.e. what is different in comparison to unsolicited RAs. But first, we'll take a look at the relevant code organization and initialization process.

Code to process RAs and initialization phase


For receiving RA and sending RA NetworkManager uses libndp library. This library is used in class NM_TYPE_LNDP_RDISC (defined in the file src/rdisc/nm-lndp-rdisc.c) which is a platform specific class tailored for the Linux OS. This class inherits from class NM_TYPE_RDISC (defined in the file src/rdisc/nm-rdisc.c) which is a platform independent base class. It contains functionality that is platform independent so theoretically NetworkManager can be more easily ported to, e.g. FreeBSD.

To create a new object of the type NM_TYPE_LNDP_RDISC it is necessary to call function nm_lndp_rdisc_new(). This is, for example, done by the class NM_DEVICE_TYPE in function addrconf6_start() for each device for which IPv6 configuration is started.

Now, if NetworkManager will use RAs or not depends on the configuration setting for IPv6 that the user defines. If you go to configuration dialog for some network interface there is a setting for IPv6 configuration which might be ON or OFF. In case it is OFF, no IPv6 configuration will be done. If IPv6 configuration is enabled (switch placed in ON state) then the specific configuration methods should be selected. The selected option is checked in the function src/devices/nm-device.c:act_stage3_ip6_config_start(), where, depending on the option selected, a specific initialization is started:
  • Automatic (method NM_SETTING_IP6_CONFIG_METHOD_AUTO)

    Start full IPv6 configuration by calling src/devices/nm-device.c:addrconf6_start() function.
     
  • Automatic, DHCP only (method NM_SETTING_IP6_CONFIG_METHOD_DHCP)

    Only configuration based on DHCP parameters received will be done. This type of configuration is initiated by calling function src/devices/nm-device.c:dhcp_start().
     
  • Manual (method NM_SETTING_IP6_CONFIG_METHOD_MANUAL)

    Manual configuration using parameters specified in the configuration dialog and nothing else. The configuration of this type is initiated by calling function nm_ip6_config_new() which returns appropriate IPv6 configuration object.
     
  • Link-local Only (method NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL)

    Initiate only a link-local address configuration by calling function src/devices/nm-device.c:linklocal6_start().
Since in this post we are concerned with RA processing than we are obviously interested only in Automatic configuration type, the one that calls addrconf6_start() function. This function, in turn, calls function src/nm-device.c:linklocal6_start() to ensure that link local configuration is present. It might happen that link local address isn't configured and so RA configuration must wait, or link local configuration is still present. In either case, when link local configuration is present RA processing can start. RA processing is kicked off by calling src/nm-device.c:addrconf6_start_with_link_ready() which in turn calls src/nm-rdisc.c:nm_rdisc_start() to kick off RA configuration.

nm_rdisc_start() is called with a pointer to NM_LNDP_RDISC class (defined in src/rdisc/nm_lndp_rdisc.c). Note that a method (nm_rdisc_start()) from a base class (NM_RDISC_TYPE, defined in src/rdisc/nm_rdisc.c) is called with a pointer to a subclass of a NM_RDISC_TYPE! Method in a base class does the following:
  1. Checks that there is a subclass which defined virtual method start() (gassert(klass->start)).
  2. Initializes timeout for the configuration process. If timeout fires, then rdisc_ra_timeout_cb() will be called that emits NM_RDISC_RA_TIMEOUT signal.
  3. Invokes a method start() from a subclass. Subclass is, as already said, NM_LNDP_RDISC and the given method registers a callback src/rdisc/nm-lndp-rdisc.c:receive_ra() with libndp library. The callback is called by libndp library whenever RA is received.
  4. Starts solicit process by invoking solicit() method. This method schedules RS to be sent in certain amount of time (variable next) by send_rs() method. This method, actually, invokes send_rs() method from a subclass (src/nm-rdisc/nm-rdisc-linux.c:send_rs()) which sends RS using libndp library. Note that the number of RSes sent is bounded and after certain amount of them sent the process is stopped under the assumption that there is no IPv6 capable router on the network.
  5. After RA has been received and processed the application of configuration parameters is done in src/device/nm-device.c:rdisc_config_changed() method. This callback is achieved by registering to NM_RDISC_CONFIG_CHANGED signal that is emitted by src/rdisc/nm-rdisc.c class whenever IPv6 configuration changes.
So, in conclusion, when link local configuration is finished, RA processing is started. The RA processing consists of waiting for RA in src/rdisc/nm-lndp-rdisc.c:receive_ra(). If RA doesn't arrive is certain amount of time then RS is sent in function src/nm-rdisc/nm-rdisc-linux.c:send_rs().

RA processing


When RA is received it is processed by the function src/rdisc/nm-lndp-rdisc.c:receive_ra(). The following configuration options are processed from RA by the given function:
  1. DHCP level.
  2. Default gateway.
  3. Addresses and routes.
  4. DNS information (RDNSS option).
  5. DNS search list (DNSSL option).
  6. Hop limit.
  7. MTU.
All the options that were parsed are stored (or removed from) a private attributes of the base object (NMRDisc defined in src/rdisc/nm-rdisc.h).

Finally, the method src/nm-rdisc.c:nm_rdisc_ra_received() is called to cancel all the timeouts. It will also emit signal NM_RDISC_CONFIG_CHANGED that will trigger application of received configuration parameters to a networking device.

Processing RS/RA


The RS/RA processing differs only by the fact that RS is sent after certain amount of time has passed and RA wasn't received, as described in the Code to process RAs and initialization phase section. After RS is sent, the RA processing is the same as it would be without RS being sent.

Applying IPv6 configuration data


Application of received IPv6 configuration data is done in the method src/device/nm-device.c:rdisc_config_changed(). IPv6 configuration is stored in IPv6 configuration object NM_TYPE_IP6_CONFIG defined in src/nm-ip6-config.c.

Note that this isn't the real application of configuration data, but only that the configuration data is stored in the appropriate object.

The function that really applies configuration data is src/devices/nm-device.c:ip6_config_merge_and_apply().

About Me

scientist, consultant, security specialist, networking guy, system administrator, philosopher ;)

Blog Archive