summaryrefslogtreecommitdiff
path: root/cleopatre/devkit/plcd/src/plcd_mcast.c
blob: d820681f51254e329c88e2d7af746a268ed832fe (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
/* SPC300 bundle {{{
 *
 * Copyright (C) 2010 Spidcom
 *
 * <<<Licence>>>
 *
 * }}} */
/**
 * \file    devkit/plcd/src/plcd_mcast.c
 * \brief   multicast related processing in the PLC Daemon
 * \ingroup plcd
 */

#include <errno.h>
#include <string.h>
#include <syslog.h>
#include <sys/stat.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>

#include "libmme.h"
#include "libspid.h"
#include "plcd.h"

/* Maximum number of multicast groups */
#define MCAST_GROUP_MAX_NB      ((uint8_t)12)

/* Maxium number of members in a multicast group */
#define MCAST_MEMBER_MAX_NB     ((uint8_t)1)

/* The mcast.info file has the following format:
 * group1_mac_addr<delimiter>member1_mac_addr<delimiter>member2_mac_addr<delimiter>membern_mac_addr<delimiter>
 * group2_mac_addr<delimiter>member1_mac_addr<delimiter>member2_mac_addr<delimiter>membern_mac_addr<delimiter>
 * groupn_mac_addr<delimiter>member1_mac_addr<delimiter>member2_mac_addr<delimiter>membern_mac_addr<delimiter>
 *
 * So we have at most (1 + MCAST_MEMBER_MAX_NB) mac_address and delimiter.
 * We add then length of one mac_address and delimiter, to be able to detect if there are more members than expected.
 * and each line ends with a \n
 * and then we have the \0
 */
#define LINE_MAX_SIZE    \
    (((LIBSPID_MAC_STR_LEN + strlen(LIBSPID_MCAST_INFO_DELIMITER)) * (1 + MCAST_MEMBER_MAX_NB + 1)) \
     + 1 \
     + 1 )

/**
 * Status returned by the function reading the group membership information
 * from the file.
 */
typedef enum
{
    /** Successful read. */
    MCAST_READ_SUCCESS,

    /** An error prevented the read. */
    MCAST_READ_ERROR,

    /** A parsing error occured during read. */
    MCAST_READ_PARSE_ERROR
} mcast_read_status_t;

/** Multicast group member */
typedef struct
{
    /** The MAC address of the member */
    unsigned char mac_address[ETHER_ADDR_LEN];
} mcast_member_t;

/** Multicast group */
typedef struct
{
    /** The MAC address of the group */
    unsigned char mac_address[ETHER_ADDR_LEN];

    /** The list of members of the group */
    mcast_member_t members[MCAST_MEMBER_MAX_NB];

    /** The number of members of the group */
    uint8_t member_count;

} mcast_group_t;

/**
 * Tell whether the provided MAC address is a multicast address or not.
 * \param  mac_addr  the multicast address to test.
 * \return  LIBSPID_TRUE if it's a multicast address,
 *          LIBSPID_FALSE if it's not.
 */
static libspid_boolean_t
is_multicast_mac_addr (const unsigned char *mac_addr)
{
    return ((mac_addr[0] == 0x01)
         && (mac_addr[1] == 0x00)
         && (mac_addr[2] == 0x5e));
}

/**
 * Read a file to extract the multicast groups and their members.
 * \param  groups  buffer where to store the groups read from the file.
 * \param  group_count  on success, the number of groups read from the file.
 * \return  see mcast_read_status_t.
 */
static mcast_read_status_t
plcd_mcast_read_groups (mcast_group_t *groups, uint8_t *group_count)
{
    PLCD_ASSERT (groups != NULL);
    PLCD_ASSERT (group_count != NULL);

    FILE *fp = fopen (LIBSPID_MCAST_INFO_PATH, "r");
    if (fp == NULL)
    {
        syslog (LOG_ERR, "fopen: %s", strerror (errno));
        return MCAST_READ_ERROR;
    }

    char line[LINE_MAX_SIZE];

    uint8_t group_idx = 0;
    char *ret;
    while (((ret = fgets (line, sizeof (line), fp)) != NULL)
          && (group_idx < MCAST_GROUP_MAX_NB))
    {
        {
            /* Remove \n to help strtok */

            char *last_char = &line[strlen (line) - 1];
            if (*last_char == '\n')
            {
                *last_char = '\0';
            }
        }

        /* The Group */
        char *token;
        token = strtok (line, LIBSPID_MCAST_INFO_DELIMITER); /* TODO: strtok_r ? */
        if ((token == NULL)
            || ((strlen (token) + 1) != LIBSPID_MAC_STR_LEN))
        {
            syslog (LOG_ERR, "parsing error: %s", token);
            return MCAST_READ_PARSE_ERROR;
        }

        libspid_error_t status;

        status = libspid_mac_str_to_bin (token, groups[group_idx].mac_address);
        if (status != LIBSPID_SUCCESS)
        {
            syslog (LOG_ERR, "libspid_mac_str_to_bin failed (%d)", status);
            return MCAST_READ_PARSE_ERROR;
        }

        if (!is_multicast_mac_addr (groups[group_idx].mac_address))
        {
            syslog (LOG_ERR, "not a multicast mac address");
            return -1;
        }

        /* The members */
        uint8_t member_idx = 0;
        while (((token = strtok (NULL, LIBSPID_MCAST_INFO_DELIMITER)) != NULL) /* TODO: strtok_r ? */
                && (member_idx < MCAST_MEMBER_MAX_NB))
        {
            if ((strlen (token) + 1) != LIBSPID_MAC_STR_LEN)
            {
                syslog (LOG_ERR, "parsing error: %s", token);
                return MCAST_READ_PARSE_ERROR;
            }

            status = libspid_mac_str_to_bin (token, groups[group_idx].members[member_idx].mac_address);
            if (status != LIBSPID_SUCCESS)
            {
                syslog (LOG_ERR, "libspid_mac_str_to_bin failed (%d)", status);
                return -1;
            }

            member_idx++;
        }

        if ((token != NULL) && (member_idx == MCAST_MEMBER_MAX_NB))
        {
            syslog (LOG_WARNING, "too many members in group. Group ignored.");

             /* Too many members, so we won't store this group.
              * If we store and send only the MCAST_MEMBER_MAX_NB first members to cesar,
              * the other members won't receive packets */
            continue;
        }

        if ((token == NULL) & (member_idx == 0))
        {
            syslog (LOG_ERR, "group with no members");
            return MCAST_READ_PARSE_ERROR;
        }

        groups[group_idx].member_count = member_idx;
        group_idx++;
    }

    if ((ret != NULL) && (group_idx == MCAST_GROUP_MAX_NB))
    {
        syslog (LOG_WARNING, "too many multicast groups. Remaining groups ignored.");
    }

    *group_count = group_idx;

    return MCAST_READ_SUCCESS;
}

static void
plcd_mcast_send_info (plcd_ctx_t *ctx,
                      const mcast_group_t *groups,
                      unsigned int group_count)
{
    PLCD_ASSERT (ctx != NULL);
    PLCD_ASSERT ((group_count == 0) || ((group_count > 0) && (groups != NULL)));

    syslog (LOG_INFO, __func__);

    mme_ctx_t request_ctx;
    mme_ctx_t confirm_ctx;
    unsigned char mme_buffer[ETH_DATA_LEN] = {0};

    mme_error_t status;

    status = mme_init (&request_ctx,
                       MME_TYPE_DRV_STA_SET_MCAST_LIST | MME_TYPE_REQ,
                       mme_buffer, sizeof (mme_buffer));

    if (status != MME_SUCCESS)
    {
        syslog (LOG_ERR, "mme_init failed (%d)", status);
        return;
    }

    status = mme_init (&confirm_ctx,
                       MME_TYPE_DRV_STA_SET_MCAST_LIST | MME_TYPE_CNF,
                       mme_buffer, sizeof (mme_buffer));

    if (status != MME_SUCCESS)
    {
        syslog (LOG_ERR, "mme_init failed (%d)", status);
        return;
    }

    unsigned int result_length;
    if (mme_put (&request_ctx,
                (void *)&group_count,
                sizeof (uint8_t),
                &result_length) != MME_SUCCESS)
    {
        syslog (LOG_ERR, "failed to mme_put group count");
        return;
    }

    uint8_t group_idx;
    for (group_idx = 0; group_idx < group_count; group_idx++)
    {
        const mcast_group_t *group = &groups[group_idx];

        result_length = 0;
        if (mme_put (&request_ctx,
                     (void *) group->mac_address,
                     ETHER_ADDR_LEN,
                     &result_length) != MME_SUCCESS)
        {
            syslog (LOG_ERR, "failed to mme_put group mac address");
            return;
        }

        result_length = 0;
        if (mme_put (&request_ctx,
                     (void *) &group->member_count,
                     sizeof (uint8_t),
                     &result_length) != MME_SUCCESS)
        {
            syslog (LOG_ERR, "failed to mme_put member count");
            return;
        }

        uint8_t member_idx;
        for (member_idx = 0; member_idx < group->member_count; member_idx++)
        {
            result_length = 0;
            if (mme_put (&request_ctx,
                        (void *)&group->members[member_idx],
                        ETHER_ADDR_LEN,
                        &result_length) != MME_SUCCESS)
            {
                syslog (LOG_ERR, "failed to mme_put member mac address");
                return;
            }
        }
    }

    syslog (LOG_INFO, "sending MCAST MME...");
    uint8_t confirm_result;
    if (plcd_send_recv_drv_mme (ctx, &request_ctx, &confirm_ctx, NULL)  == -1)
    {
        syslog (LOG_ERR, "send recv drv mme failed");
        return;
    }
    if (mme_pull (&confirm_ctx, &confirm_result, sizeof (confirm_result), &result_length) != MME_SUCCESS)
    {
        syslog (LOG_WARNING, "mme pull failed");
        return;
    }
    if (confirm_result != MME_RESULT_SUCCESS)
    {
        syslog (LOG_ERR, "confirmation result not successful: %d", confirm_result);
        return;
    }

    syslog (LOG_INFO, "sent successfully");
}

libspid_boolean_t
plcd_mcast_info_updated (plcd_ctx_t *ctx)
{
    PLCD_ASSERT (ctx != NULL);

    struct stat file_stat;
    memset (&file_stat, 0, sizeof (struct stat));

    if (stat (LIBSPID_MCAST_INFO_PATH, &file_stat) == 0)
    {
        /* >= instead of > because the precision is up to the second.
         * If we use >, we may miss an update that happened in the same second
         * as the last check but a few millisecondes later.
         * e.g: last_check at 345.00 secondes and update at 345.9999 seconds.
         * The downside is that we may process many times the same file if
         * multiple signals arrive in the same second as the last_check.*/
        if (file_stat.st_mtime >= ctx->mcast_last_check)
        {
            return LIBSPID_TRUE;
        }
    }
    else
    {
        syslog (LOG_ERR, "stat: %s", strerror (errno));
    }

    return LIBSPID_FALSE;
}

void
plcd_mcast_process (plcd_ctx_t *ctx)
{
    PLCD_ASSERT (ctx != NULL);

    uint8_t group_count = 0;
    mcast_group_t *groups = malloc (sizeof (mcast_group_t ) * MCAST_GROUP_MAX_NB);
    if (groups != NULL)
    {
        /* Record the time just _before_ reading from the file.
         * If we record the time after reading from the file, we could miss
         * an update of the file that happened during the read. */
        time_t last_file_check = time (NULL);

        mcast_read_status_t status =
            plcd_mcast_read_groups (groups, &group_count);

        if ((status == MCAST_READ_SUCCESS)
            || (status == MCAST_READ_PARSE_ERROR))
        {
            /* We timestamp only if we were able to read the file.
             * We don't update ctx->mcast_last_check on MCAST_READ_ERROR,
             * because if we do, that will prevent us from reading the file
             * again later.
             * We do want to try again later, in case the error was a one
             * time anomaly. */
            ctx->mcast_last_check = last_file_check;
        }
    }

    /* If an error occured, group_count is still 0. So as a precaution,
     * the multicast info will be cleared. This will make Cesar stop its
     * filtering. */

    /* Send the info to Cesar */
    plcd_mcast_send_info (ctx, groups, group_count);

    free (groups);
}