C Bindings – Python extensions

This is my third post on C extensions for other languages. I started with Lua, and my second post was about creating a C library for Java. This time I’m going to display how this works for Python.

Why would I want to do this? I already explained it in the previous posts, but to sum it up: You get all the advantages of C – Better performance, smaller footprint and direct access to platform resources. But with the cost of losing the platform independency that these higher-level languages (/scripts) have.

Python has an extensive documentation,and also specifically on its C API. You can find it all here.

As with the previous examples, I will export my “Unix Domain Sockets” client library to Python. And of course, the code for this post is available on github as well. The library I wrote is compatible with Python 2.7, instructions on how to port python C extensions from v2.7 to v3 (and above) are found here.

Building a dynamic library for Python

You don’t use a makefile in order to build your library (although it can be done, probably not recommended). Instead, you create a Python script named “setup.py” where you specify, using Python classes, the information you usually write in your makefile. For example – what are the source files, which libraries you use, libraries locations, etc.
You can also define a different compiler (the default is gcc), if your library is for a different platform then the build machine.

This is my setup.py:

from distutils.core import setup, Extension

UnixSockets = Extension('UnixSockets',
                        sources = ['src/python/unixsockets.c'],
                        library_dirs = ['unix_sockets/'],
                        libraries = ['client'],
                        include_dirs = ['unix_sockets/src/include/'])

setup(name = 'UnixSockets',
      version = '1.0',
      description = 'Unix sockets library for python',
      ext_modules = [UnixSockets])

As you can see, the parameters for “Extension” really do remind a makefile content. This class defines what will be compiled (and linked) and how. The “setup” class defines the build output.
This is just a simple example, more information on creating the setup script can be found here. And these are distutils.core API specifications.

Some other definitions go into setup.cfg, I used this file in order to define the build location. More information about this file – Here.

Using setup.py, building our extension becomes very easy:

python setup.py build

And installing it is not harder than that:

sudo python setup.py install

Implementation

Just exposing the C API (open/close/read/write) to Python is too easy, and lack any architectonic sophistication – So what’s the fun about that?
Instead, I wanted to create a new Python class, which opens a socket on creation, and closes it once it is disposed by the interpreter’s garbage collector.

Defining a new class

This is how our Python object looks like:

typedef struct {
    PyObject_HEAD
    int fd;
} socket;

All Python classes must “inherit” (a C-style inheritance) from PyObject, hence the _HEAD macro. But this is not the whole class, we need some more definition in order to make this a new Python type. I see this struct just as an object prototype.

This is how we define a new type in Python:

static PyTypeObject socketType = {
    PyObject_HEAD_INIT(NULL)
    0,                         /*ob_size*/
    "UnixSockets.Socket",      /*tp_name*/
    sizeof(socket),            /*tp_basicsize*/
    0,                         /*tp_itemsize*/
    (destructor)socketDealloc, /*tp_dealloc*/
    0,                         /*tp_print*/
    0,                         /*tp_getattr*/
    0,                         /*tp_setattr*/
    0,                         /*tp_compare*/
    0,                         /*tp_repr*/
    0,                         /*tp_as_number*/
    0,                         /*tp_as_sequence*/
    0,                         /*tp_as_mapping*/
    0,                         /*tp_hash */
    0,                         /*tp_call*/
    0,                         /*tp_str*/
    0,                         /*tp_getattro*/
    0,                         /*tp_setattro*/
    0,                         /*tp_as_buffer*/
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
    "Unix socket",             /* tp_doc */
    0,                         /* tp_traverse */
    0,                         /* tp_clear */
    0,                         /* tp_richcompare */
    0,                         /* tp_weaklistoffset */
    0,                         /* tp_iter */
    0,                         /* tp_iternext */
    unixSocketsMethods,        /* tp_methods */
    0,                         /* tp_members */
    0,                         /* tp_getset */
    0,                         /* tp_base */
    0,                         /* tp_dict */
    0,                         /* tp_descr_get */
    0,                         /* tp_descr_set */
    0,                         /* tp_dictoffset */
    (initproc)socketInit,      /* tp_init */
    0,                         /* tp_alloc */
    socketNew,                 /* tp_new */
};

There’s a lot going on here. we define here the class name (“UnixSockets.socket”), constructor, destructor, initializer, and the class methods (unixSocketMethods).
This struct is registered as a Python type in the main function of the module (I will show this later).

Allocator and initializer

These are too different functions. Basically, in normal usage, both will be called when we create a new object, but while the allocator is called only once, upon creation, the initializer is actually the class’s __init__ function, which can be called independently (in some circumstances), and this needs to be taken into consideration – For example, in my code I prevent calling __init__ while the socket is already open (Another way to handle this is to close the socket and re-open it with the new file name).

The allocator

static PyObject *socketNew(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    socket *self;

    self = (socket *)type->tp_alloc(type, 0);
    if (self == NULL)
        return NULL;

    self->fd = 0;

    return (PyObject *)self;
}

This is a simple example of an allocator. We don’t care about the input arguments. We only allocate a new “socket” struct (using an allocator which is implemented by Python), and if the allocation goes well, we initialize the fd and return the allocated struct. This allocated struct will be passed to us later, on each method, as “self”.

The initializer

static int socketInit(socket *self, PyObject *args, PyObject *kwds)
{
    const char *socket_name;

    if (self->fd != 0) {
        PyErr_SetString(PyExc_BaseException,
                        "Cannot initialize an open socket");
        return -1;
    }

    if (!PyArg_ParseTuple(args, "s", &socket_name))
        return -1;

    self->fd = socket_open(socket_name);
    if (self->fd <= 0) {
        PyErr_SetFromErrno(PyExc_IOError);
        return -1;
    }

    return 0;
}

As I explained before, in order to prevent the user from trying to re-open an opened socket, I check if the file descriptor is non-zero. If it is, I raise an exception. There is an extensive API for exceptions handling,  and there’s a list of exception types you can throw.

Our initializer takes one parameter – The socket’s file name, if the parsing of the input tuple fails, the we return -1. Usually, when returning a error value (-1), we must first set an exception, as we do when the fd is not zero, or when socket_open fails. But PyArg_ParseTuple already takes care of this for us.

If socket_open fails, we set the exception message directly from errno, so the user can know exactly what did go wrong.

Deallocator and clear

“tp_clear” is not required in this type, since we don’t have any inner objects which can be visited – I preferred not to expose “fd”, because its value has no meaning at the Python level, but I could have made it a PyObject too, and so allow access to it. In such case, I must implement “tp_traverse” and “tp_clear”, and also add “Py_TPFLAGS_HAVE_GC” to tp_flags (more on this issue – here).

Deallocating is pretty simple:

static void socketDealloc(socket* self)
{
    if (self->fd > 0)
        socket_close(self->fd);

    self->ob_type->tp_free((PyObject*)self);
}

If the file descriptor is valid, then we close the socket, and then we use Python-provided (default) free method.

Class methods

Our class has two methods – “write” and “read” (“open” and “close” are already integrated into the constructor/destructor). If you look at our type’s definition, the tp_methods field is set to “unixSocketsMethods”, and here is how it is defined:

static PyMethodDef unixSocketsMethods[] = {
    { "write", (PyCFunction)socketWrite, METH_VARARGS, "Write to a Unix domain socket" },
    { "read", (PyCFunction)socketRead, METH_NOARGS, "Read from a Unix domain socket" },
    { NULL, NULL, 0, NULL }
};

We declare the method name, and define its callback. Then we define how the function is called, and give a description for the doc.

Write

static PyObject *socketWrite(socket *self, PyObject *args)
{
    const char *str;
    int res, len;

    if (!PyArg_ParseTuple(args, "s#", &str, &len))
        return NULL;

    res = socket_write(self->fd, str, len);
    if (res <= 0) {
        PyErr_SetFromErrno(PyExc_IOError);
        return NULL;
    }

    return Py_BuildValue("i", res);
}

As we declared this method, write takes one parameter – a string, from a tuple. We parse it into a string and its length, and write it to the socket. Upon failure, we set an exception and return NULL – This is how Python knows that the function fails and look for the exception. When we return NULL, we must also set an exception.
If succeeded, we return the number of bytes sent (as a PyObject).

Read

static PyObject *socketRead(socket *self)
{
    char str[MAX_BUFFER_LEN];
    int res;

    res = socket_read(self->fd, str, MAX_BUFFER_LEN);
    if (res <= 0) {
        PyErr_SetFromErrno(PyExc_IOError);
        return NULL;
    }
    str[res] = '\0';

    return Py_BuildValue("s", str);
}

Read takes no arguments. It tries to read a bufer from the socket. On fail, it set an exception and return NULL. When succeeds it terminates the string and returns a copy of it as a PyObject.

Initializing the module

Every Python C module must have a unique PyMODINIT_FUNC function, this function is called when the module is imported (by Python’s “import” directive).

This is how our init function looks like:

PyMODINIT_FUNC initUnixSockets(void)
{
    PyObject* m;

    if (PyType_Ready(&socketType) < 0)
        return;

    m = Py_InitModule3("UnixSockets", unixSocketsModuleMethods,
                       "Unix socket modules");
    if (m == NULL)
        return;

    Py_INCREF(&socketType);
    PyModule_AddObject(m, "Socket", (PyObject *)&socketType);
}

PyType_Ready “finalizes” our object type – Adds some necessary defaults to our new type. Then we register our module – This module doesn’t have any method of it own, so unixSocketsModuleMethods is just a “dummy” array of methods.
PyModule_AddObject registers our new type. We need socket type to have at least one reference for this, so we increment its reference before adding it.

Using the new module

This is how we use our new module (after building and installing it, of courses):

import sys, UnixSockets

if len(sys.argv) != 2:
    print('Usage: python {0} '.format(sys.argv[0]))
    quit()

socketName = sys.argv[1]

try:
    mySocket = UnixSockets.Socket(socketName)
except Exception as e:
    print('Error opening socket {0}: "{1}"'.format(socketName, e))
    quit()

words = [ 'Hi there', 'Just testing you from python', 'Third message', 'Another message, just before leaving', 'So long']

for word in words:
    try:
        writeRes = mySocket.write(word)
        print('* Sent "{0}", {1} bytes sent'.format(word, writeRes))
        resp = mySocket.read()
        print('  Listener response: {0}'.format(resp))
    except IOError as e:
        print('Error communicating with listener: "{0}"'.format(e))
        break

First, we import our library using the “import” directive. This script receives the socket name as a command line argument, and passes it as an argument to the object initializer. We do this inside a try-catch section, and if something goes wrong, we’ll catch the exception and print its error message, and here’s an example:

$ python tests/python/UnixSocket.py /tmp/sock
Error opening socket /tmp/sock: "[Errno 2] No such file or directory"

For the purpose of testing, we go over a list of strings, write each one to the socket, and read the listener’s answer. Again, if we get an exception, we print an error message.
Here’s the output of a successful execution of this script:

$ python tests/python/UnixSocket.py /tmp/sock1
* Sent "Hi there", 8 bytes sent
  Listener response: Recieved 8 Bytes
* Sent "Just testing you from python", 28 bytes sent
  Listener response: Recieved 28 Bytes
* Sent "Third message", 13 bytes sent
  Listener response: Recieved 13 Bytes
* Sent "Another message, just before leaving", 36 bytes sent
  Listener response: Recieved 36 Bytes
* Sent "So long", 7 bytes sent
  Listener response: Recieved 7 Bytes

Exactly what we wanted.

This is it for now,

Amnon.

C Bindings – Java

This is the second post in this serie. In this post I’m going to show how to create a C library for usage in Java.

Why would we want to do such a thing? Java is a platform-independent language, and C is platform dependent, so when we use a C library in Java, we’re binding ourselves to the platform this library is built for.
But when our project is intended to run on a dedicated platform, there is no such problem, and if this platform has some special hardware, or if we want to enhance our SW performance, we may have to use C. And if this platform runs Android OS, this becomes very useful, as we can develope the low-level stuff in C, and bind it to the application which is always written in Java.

We will make use of the “Java Native Interface” (JNI) framework in order to build our library. “Native” here stands for “platform-specific”. There’s an excellent Wikipedia page on JNI, I recommend reading it.

The code for this post can be found on github.

The header file

First thing we need to do is to include “jni.h”, this file contains all the definition we need for developing a Java module in C. Let’s look at the declaration of the function in our header file, and see some of these definitions:

JNIEXPORT jint JNICALL Java_LinuxSocket_open(JNIEnv *env, jobject obj,
                                             jstring jSocketName);
JNIEXPORT void JNICALL Java_LinuxSocket_close(JNIEnv *env, jobject obj,
                                              jint jSocket);
JNIEXPORT jstring JNICALL Java_LinuxSocket_read(JNIEnv *env, jobject obj,
                                                jint jSocket);
JNIEXPORT jint JNICALL Java_LinuxSocket_write(JNIEnv *env, jobject obj,
                                              jint jSocket, jstring jBuffer);
  • First thing we notice here is “JNIEXPORT” and “JNICALL” – This is how we make a “Java method” out of our C function.
  • Second thing is the function name. The underscores replace the dot operator, so we’re actually creating “Java.LinuxSocket.open”, where “LinuxSocket” is our library and “open” is a method in this library.
  • The first argument to each one of these function is a pointer to “JNIEnv”, this is our “link” to the Java runtime – We can use this parameter for creating and deleting Java objects (such as exceptions and strings), call Java objects’ methods, etc.
    The second parameter is a “jobject”, and this is the object which this method belongs to (we are not making use of it in our code)
  • We can see that we receive and return Java defined types: “jint”, “jstring” and so on.

Implementation

Let’s look at the “open” method:

#define MESSAGE_MAX_SIZE 256

JNIEXPORT jint JNICALL Java_UnixSocket_open(JNIEnv *env, jobject obj,
                                             jstring jSocketName)
{
    char message[MESSAGE_MAX_SIZE];

    const char *socket_name = (*env)->GetStringUTFChars(env, jSocketName, NULL);
    int s = socket_open(socket_name);
    
    if (s <= 0) {         jclass Exception = (*env)->FindClass(env, "java/lang/Exception");
        snprintf(message, MESSAGE_MAX_SIZE, "Couldn't open socket %s", socket_name);
        (*env)->ThrowNew(env, Exception, message);
    }

    (*env)->ReleaseStringUTFChars(env, jSocketName, socket_name);
    return (jint)s;
}

Right on the start we can see usage of the “JNIEnv” parameter. We use it for converting a Java string into a C-Style string. Notice that when we’re finished, we need to release this string, as we do right before we return.
We use “socket_open” which we get out of our marvelous unix-socket-client static library, and then we check for errors Java-style. In Java, a fully object-oriented language, the proper way of handling errors is by throwing an exception. So again, we’re using our “JNIEnv” parameter in order to create an exception object which we’re going to throw.
Even if an exception was thrown, the function is going to continue till its end (unlike the normal behaviour inside Java itself), so we have our chance to release the string before returning. In such case (exception thrown), there will be no meaning for the return value.

The rest of the functions are more or less the same, so we’ll leave them to the reader (as they say in math books), and go ahead to see how we’re going to use this library in Java.

By building our library as a shared object, and installing it at the proper place, we’re making our library available for Java applications.

The Java part

We create a class named “UnixSocket” – Same as our library functions (java.UnixSocket.*).

public class UnixSocket

In its “static” section we load our shared library.

static {
    try {
        System.loadLibrary("unixsockets");
    } catch (UnsatisfiedLinkError e) {
        System.err.println("Error: UnixSockets JNI Library failed to load\n" + e);
        System.exit(1);
    }
 }

The library functions are declared as “native”, so that Java knows that these are implemented in the JNI layer.

native private int open(String socketName);
native private void close(int socket);
native private String read(int socket);
native private int write(int socket, String out);

Our class saves the returned file descriptor as an int (fd). Here is the implementation of the class’s constructor, which calls “open”. Although it looks simple (as it actually is), you can see that it throws an exception. This is the exception we discussed in the C implementation above.

public UnixSocket(String socketName) throws Exception {
    fd = open(socketName);
 }

Testing

As I mentioned, the way I chose to implement this, “open” is called with the class constructor – We pass the socket name to the constructor, then “open” is called. Let’s see how this works out in the test program.

UnixSocket socket;
try {
    socket = new UnixSocket(args[0]);
} catch (Exception e) {
    System.out.println(e.getMessage());
    return;
}

If “open” succeeds, then socket is created successfully, otherwise, “open” (at the very bottom layer) will throw an exception, which is caught here. This exception holds an error message, and we can print it out

Now, the output of the Java test program – The program sends a (silly) string to the listener, and print out the message it got back from the listener:

$ java UnixSocketTest /tmp/socket1
Got response: Recieved 16 Byte
Got response: Recieved 15 Byte
Got response: Recieved 9 Byte
Got response: Recieved 8 Byte

And the output of the listener:

$ ./listenerd /tmp/socket1
[LISTENER] INFO  Starting "Unix Domain Sockets" demo listener
[LISTENER] INFO  * Starting communication with a client *
[LISTENER] INFO  Incoming message: Try this string
[LISTENER] INFO  Incoming message: Maybe this one
[LISTENER] INFO  Incoming message: Have fun
[LISTENER] INFO  Incoming message: Bye bye
[LISTENER] INFO  Client socket was closed by peer

Yes! Everything works!
Till next time,
Amnon.

Unix Domain Sockets

“Unix domain sockets” is a powerful socket-based IPC (inter-process communication) mechanism, it uses the same-old sockets API, and is simple to use… well, it has many features, so getting deep in it might be a bit complicated, but the basic use of Unix domain sockets it quite simple.

The man page is excellent, and it has a very good code example, almost as good as mine 🙂

All the code for this post can be found in github.

Unix-domain server

Basically, we create a “server” by opening a socket (either stream or datagram) and linking it to an address, which is actually a filename in our filesystem*. Then we listen for clients and wait for incoming data. Exactly as we do with network sockets.

* There are other binding options, but I’m going to review the filesystem one here.

In my example code, my listener waits for a client (using “accept”), and when a client connect, it handles the connection until the client closes (not optimal, because a client can starve other clients, but this is only an example).
In order to terminate the listener, it handles “Ctrl-C” – when this signal is received, it exits gracefully.

So, this is what we need to do:

  • Step 1: Create a socket and bind it to an address (a filename)
    1. Creating the socket:
       int sock = socket(AF_UNIX, SOCK_STREAM, 0);

      We are using the “AF_UNIX” family, which is the Unix-domain family, with SOCK_STREAM. We can also use SOCK_DGRAM if we want a datagram socket.

    2. Binding the socket:
       res = bind_address(sock, path);

      Where “sock” is the result from the call to “socket()”, and “path” is the filename that represents the socket. This filename mustn’t exist, therefore, we call “unlink(path)” before calling bind, to be sure that the filename is cleared.

    3. Listen:
       res = listen(sock, MAX_CLIENTS);

      “MAX_CLIENTS” is the length of the clients queue – We’re handling one client at a time.

  • Step 2: In a loop, wait for clients
     while (!quit) {
         int client_socket = accept(sock, NULL, NULL);
         if (client_socket < 0) {          if (errno != EINTR)              continue;          LOGE("Failed accepting client (%d: %s)", errno, strerror(errno));          break;      }      quit = handle_client(client_socket) > 0 ? 1 : 0;
         if (quit) 
             LOGI("Got termination command, exiting");
    
         close(client_socket);
    }
    

    Notes:
    Our socket is blocking, as we didn’t define it otherwise, so “accept()” will block until a client tries to connect.
    We exit only if accept fails on EINTR because we don’t want just any client connection error to kill our service.
    handle_client handles the input/output from/to the client. I defined it, arbitrarily, so that if it returns any positive value, then the listener exits.

    Regarding listener ending, I think it is important to define a SIGTERM handler, so that if the program needs to exit, it will do so gracefully (closing sockets and unlinking the address).

  • Step 3: When a client connects, receive data from it and send back result.
    This is really trivial, and it happens in “handle_client()”. We read the input data like so:

     len = read(sock, buff, MAX_BUFFER_LEN-1);

    Where “sock” is the client socket’s file descriptor that we received from “accept()”.
    Now we do all our processing on the input, and when we want to send data back to the client, we use “write”:

     res = write(sock, out_buff, outbuff_len);

    We can repeat reading/writing as long as the client is connected, and when we decide to terminate the connection, we close the client socket:

     close(client_socket);

Easy enough.

Unix-domain client

Well, this is even easier than creating a server.

  • Step 1: Create a new Unix-domain socket and connect to the server
    1. Creating a new client socket is exactly the same as for the server
    2. Connecting:
      struct sockaddr_un address;
      
      memset(&address, 0x00, sizeof(address));
      address.sun_family = AF_UNIX;
      strncpy(address.sun_path, socket_name, SOCKET_NAME_MAX_LEN);
      
      res = connect(sock, (struct sockaddr *)&address, sizeof(address));
  • Step 2: Read/Write data
    Again, exactly the same as explained above
  • Step 3: Disconnect
    Dito – Use close.

Execution example

Listener

$ ./listener /tmp/sock1
[LISTENER] INFO  Starting "Unix Domain Sockets" demo listener
[LISTENER] INFO  * Starting communication with a client *
[LISTENER] INFO  Incoming message: Test this string
[LISTENER] INFO  Client socket was closed by peer
[LISTENER] INFO  * Starting communication with a client *
[LISTENER] INFO  Incoming message: Take another one
[LISTENER] INFO  Client socket was closed by peer
^C[LISTENER] INFO  Terminating service

Client

$ ./test_client /tmp/sock1 "Test this string"
INFO  Starting "Unix Domain Sockets" demo client
INFO  Got back: Got your message
$ ./test_client /tmp/sock1 "Take another one"
INFO  Starting "Unix Domain Sockets" demo client
INFO  Got back: Got your message

Just what we wanted!
Until next time,
Amnon.

Using rtnetlink for detection of NICs status changes in C

I had a task once, where I had to update a configuration when a change was detected in the network interfaces. While the major part of the task was the configuration part, it’s interesting to see how we can detect networking changes in C, and it’s also a nice Netlink exercise.

Netlink is a socket-based IPC between the kernel and user-space, using packets communication (a datagram-oriented service). Netlink communication in user-space is done with the standard and familiar sockets API, similar to unix-domain sockets and INET sockets (See Netlink’s man page).

Netlink has a lot of families, which are sort of sub-protocols. Each family is designed for communication with a different kernel service. It also has a generic family, for user-customized communication.

What I’m going to show here is the usage of “RTNL” – Routing Netlink. This Netlink family is used for managing the routing services of the kernel. We can send commands to change routing, get routing information or “subscribe” for routing notification as (RTNL man page).

The complete code for this post can be downloaded from github.

Netlink messages

An incoming buffer from a netlink socket can contain one or more netlink messages. Each message consists of a header and payload. It looks something like this:

This is the header structure, I think it is pretty self-documenting:

struct nlmsghdr {
    __u32 nlmsg_len;    /* Length of message including header. */
    __u16 nlmsg_type;   /* Type of message content. */
    __u16 nlmsg_flags;  /* Additional flags. */
    __u32 nlmsg_seq;    /* Sequence number. */
    __u32 nlmsg_pid;    /* Sender port ID. */
};

The message payload itself depends on the type, as we’ll soon see with RT messages.

Netlink API offers convenient macros to iterate the messages, extract the payload, and such. You should use them instead of doing the casting yourself, since netlink uses strict alignments, which the macros already take into account.

More on netlink can be found in it’s man page. Or here, if you want to go deeper into Netlink.

RT Netlink

If we are receiving RT Netlink messages, nlmsg_type will indicate which routing message it is, and the payload will match the type. RT Netlink payload look something like this:

The “Message data” is a struct (as I said, it depends on the netlink message type), and the attributes struct looks like this:

struct rtattr {
    unsigned short rta_len;    /* Length of option */
    unsigned short rta_type;   /* Type of option */
    /* Data follows */
};

And here, again, the attribute’s data depends on the attribute’s type.

In our example, we wait for “RTM_NEWLINK” netlink message type, which payload is this:

struct ifinfomsg {
    unsigned char  ifi_family; /* AF_UNSPEC */
    unsigned short ifi_type;   /* Device type */
    int            ifi_index;  /* Interface index */
    unsigned int   ifi_flags;  /* Device flags  */
    unsigned int   ifi_change; /* change mask */
};
  • We will be interested only in messages that inform a change in network interfaces, that is, only messages with non-zero ifi_change.
  • ifi_flags will indicate if the device was taken up, or down.
  • In order to know which interface has changed its state, we’ll look for an attribute with rta_type “IFLA_IFNAME”, and read its payload, which type is char*.

Taking it into practice

Opening a netlink socket with RT netlink family is shown in the man page, and looks like this:

fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);

Binding the socket to a specific “Netlink address” – Here we specify which RT groups we want to “subscribe” to. We can subscribe to more than one by OR’ing them together. Here we subscribe only to “RTMGRP_LINK”:

sa.nl_family = AF_NETLINK;
sa.nl_groups = RTMGRP_LINK;
err = bind(fd, (struct sockaddr *) &sa, sizeof(sa));

Waiting for messages and receiving data is done with the regular sockets API. In my code I used “poll” and “recv”, but any other API will do. You can see this part in the code itself.

When we get the message buffer, we need to iterate the netlink messages it contains by iterating the header:

for (nh = (struct nlmsghdr *)buff; NLMSG_OK(nh, len); nh = NLMSG_NEXT(nh, len)) {
    .
    .
    .

And for each message we look for nlmsg_type “RTM_NEWLINK” with change indication:

    ifi = NLMSG_DATA(nh);
    if (nh->nlmsg_type != RTM_NEWLINK || !ifi->ifi_change)
        continue;

If we do find such a message, we iterate its attributes in order to find the interface’s name:

char *if_name = NULL;

for ( ; RTA_OK(rta, rta_len) ; rta = RTA_NEXT(rta, rta_len)) {
    if (rta->rta_type == IFLA_IFNAME) {
        if_name = (char*)RTA_DATA(rta);
        break;
    }
}

Where rta and rta_len is taken from the netlink message:

struct rtattr *rta = IFLA_RTA(ifi);
int rta_len = IFLA_PAYLOAD(nh);

Again, you can check out the code itself for the complete picture, I believe it’s clear enough.

Amnon.

Amnon

Advantures in Embeddedland · Post

Update Revert to draft Preview Close

ComposeHTML

Normal
Labels
C, C++, devices, linux, netlink, network interfaces, networking, nic, programming, rtnetlink, sockets, unix
Published on

5/20/16, 10:36 PM

Israel Daylight Time

Permalink
Location
Search Description
Options

Amnon

Advantures in Embeddedland · Post

Update Revert to draft Preview Close

ComposeHTML

Normal
Labels
C, C++, devices, linux, netlink, network interfaces, networking, nic, programming, rtnetlink, sockets, unix
Published on

5/20/16, 10:36 PM

Israel Daylight Time

Permalink
Location
Search Description
Options

Amnon

Advantures in Embeddedland · Post

Update Revert to draft Preview Close

ComposeHTML

Normal
Labels
C, C++, devices, linux, netlink, network interfaces, networking, nic, programming, rtnetlink, sockets, unix
Published on

5/20/16, 10:36 PM

Israel Daylight Time

Permalink
Location
Search Description
Options

Amnon

Advantures in Embeddedland · Post

Update Revert to draft Preview Close

ComposeHTML

Normal
Labels
C, C++, devices, linux, netlink, network interfaces, networking, nic, programming, rtnetlink, sockets, unix
Published on

5/20/16, 10:36 PM

Israel Daylight Time

Permalink
Location
Search Description
Options