Coverage Report

Created: 2026-08-15 06:27

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/openvswitch/lib/unixctl.c
Line
Count
Source
1
/*
2
 * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2016 Nicira, Inc.
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at:
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
17
#include <config.h>
18
#include "unixctl.h"
19
#include <errno.h>
20
#include <getopt.h>
21
#include <unistd.h>
22
#include "command-line.h"
23
#include "coverage.h"
24
#include "dirs.h"
25
#include "openvswitch/dynamic-string.h"
26
#include "openvswitch/json.h"
27
#include "jsonrpc.h"
28
#include "openvswitch/list.h"
29
#include "openvswitch/poll-loop.h"
30
#include "openvswitch/shash.h"
31
#include "stream.h"
32
#include "stream-provider.h"
33
#include "svec.h"
34
#include "openvswitch/vlog.h"
35
36
VLOG_DEFINE_THIS_MODULE(unixctl);
37
38
COVERAGE_DEFINE(unixctl_received);
39
COVERAGE_DEFINE(unixctl_replied);
40

41
struct unixctl_command {
42
    const char *usage;
43
    int min_args, max_args;
44
    unixctl_cb_func *cb;
45
    void *aux;
46
};
47
48
struct unixctl_conn {
49
    struct ovs_list node;
50
    struct jsonrpc *rpc;
51
52
    /* Only one request can be in progress at a time.  While the request is
53
     * being processed, 'request_id' is populated, otherwise it is null. */
54
    struct json *request_id;   /* ID of the currently active request. */
55
56
    enum unixctl_output_fmt fmt; /* Output format of current connection. */
57
};
58
59
/* Server for control connection. */
60
struct unixctl_server {
61
    struct pstream *listener;
62
    struct ovs_list conns;
63
    char *path;
64
};
65
66
static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 5);
67
68
static struct shash commands = SHASH_INITIALIZER(&commands);
69
70
const char *
71
unixctl_output_fmt_to_string(enum unixctl_output_fmt fmt)
72
0
{
73
0
    switch (fmt) {
74
0
    case UNIXCTL_OUTPUT_FMT_TEXT: return "text";
75
0
    case UNIXCTL_OUTPUT_FMT_JSON: return "json";
76
0
    default: return "<unknown>";
77
0
    }
78
0
}
79
80
bool
81
unixctl_output_fmt_from_string(const char *string,
82
                               enum unixctl_output_fmt *fmt)
83
0
{
84
0
    if (!strcasecmp(string, "text")) {
85
0
        *fmt = UNIXCTL_OUTPUT_FMT_TEXT;
86
0
    } else if (!strcasecmp(string, "json")) {
87
0
        *fmt = UNIXCTL_OUTPUT_FMT_JSON;
88
0
    } else {
89
0
        return false;
90
0
    }
91
0
    return true;
92
0
}
93
94
static void
95
unixctl_list_commands(struct unixctl_conn *conn, int argc OVS_UNUSED,
96
                      const char *argv[] OVS_UNUSED, void *aux OVS_UNUSED)
97
0
{
98
0
    if (unixctl_command_get_output_format(conn) == UNIXCTL_OUTPUT_FMT_JSON) {
99
0
        struct json *json_commands = json_object_create();
100
0
        const struct shash_node *node;
101
102
0
        SHASH_FOR_EACH (node, &commands) {
103
0
            const struct unixctl_command *command = node->data;
104
105
0
            if (command->usage) {
106
0
                json_object_put_string(json_commands, node->name,
107
0
                                       command->usage);
108
0
            }
109
0
        }
110
0
        unixctl_command_reply_json(conn, json_commands);
111
0
    } else {
112
0
        struct ds ds = DS_EMPTY_INITIALIZER;
113
0
        const struct shash_node **nodes = shash_sort(&commands);
114
0
        size_t i;
115
116
0
        ds_put_cstr(&ds, "The available commands are:\n");
117
118
0
        for (i = 0; i < shash_count(&commands); ++i) {
119
0
            const struct shash_node *node = nodes[i];
120
0
            const struct unixctl_command *command = node->data;
121
122
0
            if (command->usage) {
123
0
                ds_put_format(&ds, "  %-23s %s\n", node->name,
124
0
                              command->usage);
125
0
            }
126
0
        }
127
0
        free(nodes);
128
129
0
        unixctl_command_reply(conn, ds_cstr(&ds));
130
0
        ds_destroy(&ds);
131
0
    }
132
0
}
133
134
static void
135
unixctl_version(struct unixctl_conn *conn, int argc OVS_UNUSED,
136
                const char *argv[] OVS_UNUSED, void *aux OVS_UNUSED)
137
0
{
138
0
    unixctl_command_reply(conn, ovs_get_program_version());
139
0
}
140
141
static void
142
unixctl_set_options(struct unixctl_conn *conn, int argc, const char *argv[],
143
                    void *aux OVS_UNUSED)
144
0
{
145
0
    struct ovs_cmdl_parsed_option *parsed_options = NULL;
146
0
    size_t n_parsed_options;
147
0
    char *error = NULL;
148
149
0
    static const struct option options[] = {
150
0
        {"format", required_argument, NULL, 'f'},
151
0
        {NULL, 0, NULL, 0},
152
0
    };
153
154
0
    error = ovs_cmdl_parse_all(argc--, (char **) (argv++), options,
155
0
                               &parsed_options, &n_parsed_options);
156
0
    if (error) {
157
0
        goto error;
158
0
    }
159
160
0
    for (size_t i = 0; i < n_parsed_options; i++) {
161
0
        struct ovs_cmdl_parsed_option *parsed_option = &parsed_options[i];
162
163
0
        switch (parsed_option->o->val) {
164
0
        case 'f':
165
0
            if (!unixctl_output_fmt_from_string(parsed_option->arg,
166
0
                                                &conn->fmt)) {
167
0
                error = xasprintf("option format has invalid value %s",
168
0
                                  parsed_option->arg);
169
0
                goto error;
170
0
            }
171
0
            break;
172
173
0
        default:
174
0
            OVS_NOT_REACHED();
175
0
        }
176
0
    }
177
178
0
    unixctl_command_reply(conn, NULL);
179
0
    free(parsed_options);
180
0
    return;
181
0
error:
182
0
    unixctl_command_reply_error(conn, error);
183
0
    free(error);
184
0
    free(parsed_options);
185
0
}
186
187
/* Registers a unixctl command with the given 'name'.  'usage' describes the
188
 * arguments to the command; it is used only for presentation to the user in
189
 * "list-commands" output.  (If 'usage' is NULL, then the command is hidden.)
190
 *
191
 * 'cb' is called when the command is received.  It is passed an array
192
 * containing the command name and arguments, plus a copy of 'aux'.  Normally
193
 * 'cb' should reply by calling unixctl_command_reply() or
194
 * unixctl_command_reply_error() before it returns, but if the command cannot
195
 * be handled immediately then it can defer the reply until later.  A given
196
 * connection can only process a single request at a time, so a reply must be
197
 * made eventually to avoid blocking that connection. */
198
void
199
unixctl_command_register(const char *name, const char *usage,
200
                         int min_args, int max_args,
201
                         unixctl_cb_func *cb, void *aux)
202
0
{
203
0
    struct unixctl_command *command;
204
0
    struct unixctl_command *lookup = shash_find_data(&commands, name);
205
206
0
    ovs_assert(!lookup || lookup->cb == cb);
207
208
0
    if (lookup) {
209
0
        return;
210
0
    }
211
212
0
    command = xmalloc(sizeof *command);
213
0
    command->usage = usage;
214
0
    command->min_args = min_args;
215
0
    command->max_args = max_args;
216
0
    command->cb = cb;
217
0
    command->aux = aux;
218
0
    shash_add(&commands, name, command);
219
0
}
220
221
enum unixctl_output_fmt
222
unixctl_command_get_output_format(struct unixctl_conn *conn)
223
0
{
224
0
    return conn->fmt;
225
0
}
226
227
/* Takes ownership of the 'body'. */
228
static void
229
unixctl_command_reply__(struct unixctl_conn *conn,
230
                        bool success, struct json *body)
231
0
{
232
0
    struct jsonrpc_msg *reply;
233
234
0
    COVERAGE_INC(unixctl_replied);
235
0
    ovs_assert(conn->request_id);
236
237
0
    if (success) {
238
0
        reply = jsonrpc_create_reply(body, conn->request_id);
239
0
    } else {
240
0
        reply = jsonrpc_create_error(body, conn->request_id);
241
0
    }
242
243
0
    if (VLOG_IS_DBG_ENABLED()) {
244
0
        char *id = json_to_string(conn->request_id, 0);
245
0
        char *msg = json_to_string(body, JSSF_SORT);
246
247
0
        VLOG_DBG("replying with %s, id=%s: \"%s\"",
248
0
                 success ? "success" : "error", id, msg);
249
0
        free(msg);
250
0
        free(id);
251
0
    }
252
253
    /* If jsonrpc_send() returns an error, the run loop will take care of the
254
     * problem eventually. */
255
0
    jsonrpc_send(conn->rpc, reply);
256
0
    json_destroy(conn->request_id);
257
0
    conn->request_id = NULL;
258
0
}
259
260
/* Replies to the active unixctl connection 'conn'.  'result' is sent to the
261
 * client indicating the command was processed successfully.  'result' should
262
 * be plain-text; use unixctl_command_reply_json() to return a JSON document
263
 * when JSON output has been requested.  Only one call to
264
 * unixctl_command_reply*() functions may be made per request. */
265
void
266
unixctl_command_reply(struct unixctl_conn *conn, const char *result)
267
0
{
268
0
    struct json *json_result = json_string_create(result ? result : "");
269
270
0
    if (conn->fmt == UNIXCTL_OUTPUT_FMT_JSON) {
271
        /* Wrap plain-text reply in provisional JSON document when JSON output
272
         * has been requested. */
273
0
        struct json *json_reply = json_object_create();
274
275
0
        json_object_put_string(json_reply, "reply-format", "plain");
276
0
        json_object_put(json_reply, "reply", json_result);
277
278
0
        json_result = json_reply;
279
0
    }
280
281
0
    unixctl_command_reply__(conn, true, json_result);
282
0
}
283
284
/* Replies to the active unixctl connection 'conn'.  'body' is sent to the
285
 * client indicating the command was processed successfully.  Use this function
286
 * when JSON output has been requested; otherwise use unixctl_command_reply()
287
 * for plain-text output.  Only one call to unixctl_command_reply*() functions
288
 * may be made per request.
289
 *
290
 * Takes ownership of the 'body'. */
291
void
292
unixctl_command_reply_json(struct unixctl_conn *conn, struct json *body)
293
0
{
294
0
    ovs_assert(conn->fmt == UNIXCTL_OUTPUT_FMT_JSON);
295
0
    unixctl_command_reply__(conn, true, body);
296
0
}
297
298
/* Replies to the active unixctl connection 'conn'. 'error' is sent to the
299
 * client indicating an error occurred processing the command.  'error' should
300
 * be plain-text.  Only one call to unixctl_command_reply*() functions may be
301
 * made per request. */
302
void
303
unixctl_command_reply_error(struct unixctl_conn *conn, const char *error)
304
0
{
305
0
    unixctl_command_reply__(conn, false,
306
0
                            json_string_create(error ? error : ""));
307
0
}
308
309
/* Creates a unixctl server listening on 'path', which for POSIX may be:
310
 *
311
 *      - NULL, in which case <rundir>/<program>.<pid>.ctl is used.
312
 *
313
 *      - A name that does not start with '/', in which case it is put in
314
 *        <rundir>.
315
 *
316
 *      - An absolute path (starting with '/') that gives the exact name of
317
 *        the Unix domain socket to listen on.
318
 *
319
 * If the path is "none", the function will return successfully but no socket
320
 * will actually be created.
321
 *
322
 * A program that (optionally) daemonizes itself should call this function
323
 * *after* daemonization, so that the socket name contains the pid of the
324
 * daemon instead of the pid of the program that exited.  (Otherwise,
325
 * "ovs-appctl --target=<program>" will fail.)
326
 *
327
 * Returns 0 if successful, otherwise a positive errno value.  If successful,
328
 * sets '*serverp' to the new unixctl_server (or to NULL if 'path' was "none"),
329
 * otherwise to NULL. */
330
int
331
unixctl_server_create(const char *path, struct unixctl_server **serverp)
332
0
{
333
0
    *serverp = NULL;
334
0
    if (path && !strcmp(path, "none")) {
335
0
        return 0;
336
0
    }
337
338
0
    long int pid = getpid();
339
0
    char *abs_path
340
0
        = (path ? abs_file_name(ovs_rundir(), path)
341
0
           : xasprintf("%s/%s.%ld.ctl", ovs_rundir(), program_name, pid));
342
343
0
    struct pstream *listener;
344
0
    char *punix_path = xasprintf("punix:%s", abs_path);
345
0
    int error = pstream_open(punix_path, &listener, 0);
346
0
    free(punix_path);
347
348
0
    if (error) {
349
0
        ovs_error(error, "%s: could not initialize control socket", abs_path);
350
0
        free(abs_path);
351
0
        return error;
352
0
    }
353
354
0
    unixctl_command_register("list-commands", "", 0, 0, unixctl_list_commands,
355
0
                             NULL);
356
0
    unixctl_command_register("version", "", 0, 0, unixctl_version, NULL);
357
0
    unixctl_command_register("set-options", "[--format text|json]", 1, 2,
358
0
                             unixctl_set_options, NULL);
359
360
0
    struct unixctl_server *server = xmalloc(sizeof *server);
361
0
    server->listener = listener;
362
0
    server->path = abs_path;
363
0
    ovs_list_init(&server->conns);
364
0
    *serverp = server;
365
0
    return 0;
366
0
}
367
368
static void
369
process_command(struct unixctl_conn *conn, struct jsonrpc_msg *request)
370
0
{
371
0
    char *error = NULL;
372
373
0
    struct unixctl_command *command;
374
0
    const struct json *params;
375
376
0
    COVERAGE_INC(unixctl_received);
377
0
    conn->request_id = json_clone(request->id);
378
379
0
    if (VLOG_IS_DBG_ENABLED()) {
380
0
        char *params_s = json_to_string(request->params, 0);
381
0
        char *id_s = json_to_string(request->id, 0);
382
0
        VLOG_DBG("received request %s%s, id=%s",
383
0
                 request->method, params_s, id_s);
384
0
        free(params_s);
385
0
        free(id_s);
386
0
    }
387
388
0
    params = request->params;
389
0
    command = shash_find_data(&commands, request->method);
390
0
    if (!command) {
391
0
        error = xasprintf("\"%s\" is not a valid command (use "
392
0
                          "\"list-commands\" to see a list of valid commands)",
393
0
                          request->method);
394
0
    } else if (json_array_size(params) < command->min_args) {
395
0
        error = xasprintf("\"%s\" command requires at least %d arguments",
396
0
                          request->method, command->min_args);
397
0
    } else if (json_array_size(params) > command->max_args) {
398
0
        error = xasprintf("\"%s\" command takes at most %d arguments",
399
0
                          request->method, command->max_args);
400
0
    } else {
401
0
        struct svec argv = SVEC_EMPTY_INITIALIZER;
402
0
        int  i, n = json_array_size(params);
403
404
0
        svec_add(&argv, request->method);
405
0
        for (i = 0; i < n; i++) {
406
0
            const struct json *elem = json_array_at(params, i);
407
408
0
            if (elem->type != JSON_STRING) {
409
0
                error = xasprintf("\"%s\" command has non-string argument",
410
0
                                  request->method);
411
0
                break;
412
0
            }
413
0
            svec_add(&argv, json_string(elem));
414
0
        }
415
0
        svec_terminate(&argv);
416
417
0
        if (!error) {
418
0
            command->cb(conn, argv.n, (const char **) argv.names,
419
0
                        command->aux);
420
0
        }
421
422
0
        svec_destroy(&argv);
423
0
    }
424
425
0
    if (error) {
426
0
        unixctl_command_reply_error(conn, error);
427
0
        free(error);
428
0
    }
429
0
}
430
431
static int
432
run_connection(struct unixctl_conn *conn)
433
0
{
434
0
    int error, i;
435
436
0
    jsonrpc_run(conn->rpc);
437
0
    error = jsonrpc_get_status(conn->rpc);
438
0
    if (error || jsonrpc_get_backlog(conn->rpc)) {
439
0
        return error;
440
0
    }
441
442
0
    for (i = 0; i < 10; i++) {
443
0
        struct jsonrpc_msg *msg;
444
445
0
        if (error || conn->request_id) {
446
0
            break;
447
0
        }
448
449
0
        jsonrpc_recv(conn->rpc, &msg);
450
0
        if (msg) {
451
0
            if (msg->type == JSONRPC_REQUEST) {
452
0
                process_command(conn, msg);
453
0
            } else {
454
0
                VLOG_WARN_RL(&rl, "%s: received unexpected %s message",
455
0
                             jsonrpc_get_name(conn->rpc),
456
0
                             jsonrpc_msg_type_to_string(msg->type));
457
0
                error = EINVAL;
458
0
            }
459
0
            jsonrpc_msg_destroy(msg);
460
0
        }
461
0
        error = error ? error : jsonrpc_get_status(conn->rpc);
462
0
    }
463
464
0
    return error;
465
0
}
466
467
static void
468
kill_connection(struct unixctl_conn *conn)
469
0
{
470
0
    ovs_list_remove(&conn->node);
471
0
    jsonrpc_close(conn->rpc);
472
0
    json_destroy(conn->request_id);
473
0
    free(conn);
474
0
}
475
476
void
477
unixctl_server_run(struct unixctl_server *server)
478
0
{
479
0
    if (!server) {
480
0
        return;
481
0
    }
482
483
0
    for (int i = 0; i < 10; i++) {
484
0
        struct stream *stream;
485
0
        int error;
486
487
0
        error = pstream_accept(server->listener, &stream);
488
0
        if (!error) {
489
0
            struct unixctl_conn *conn = xzalloc(sizeof *conn);
490
0
            ovs_list_push_back(&server->conns, &conn->node);
491
0
            conn->rpc = jsonrpc_open(stream);
492
0
            conn->fmt = UNIXCTL_OUTPUT_FMT_TEXT;
493
0
        } else if (error == EAGAIN) {
494
0
            break;
495
0
        } else {
496
0
            VLOG_WARN_RL(&rl, "%s: accept failed: %s",
497
0
                         pstream_get_name(server->listener),
498
0
                         ovs_strerror(error));
499
0
        }
500
0
    }
501
502
0
    struct unixctl_conn *conn;
503
0
    LIST_FOR_EACH_SAFE (conn, node, &server->conns) {
504
0
        int error = run_connection(conn);
505
0
        if (error && error != EAGAIN) {
506
0
            kill_connection(conn);
507
0
        }
508
0
    }
509
0
}
510
511
void
512
unixctl_server_wait(struct unixctl_server *server)
513
0
{
514
0
    struct unixctl_conn *conn;
515
516
0
    if (!server) {
517
0
        return;
518
0
    }
519
520
0
    pstream_wait(server->listener);
521
0
    LIST_FOR_EACH (conn, node, &server->conns) {
522
0
        jsonrpc_wait(conn->rpc);
523
0
        if (!jsonrpc_get_backlog(conn->rpc) && !conn->request_id) {
524
0
            jsonrpc_recv_wait(conn->rpc);
525
0
        }
526
0
    }
527
0
}
528
529
/* Destroys 'server' and stops listening for connections. */
530
void
531
unixctl_server_destroy(struct unixctl_server *server)
532
0
{
533
0
    if (server) {
534
0
        struct unixctl_conn *conn;
535
536
0
        LIST_FOR_EACH_SAFE (conn, node, &server->conns) {
537
0
            kill_connection(conn);
538
0
        }
539
540
0
        free(server->path);
541
0
        pstream_close(server->listener);
542
0
        free(server);
543
0
    }
544
0
}
545
546
const char *
547
unixctl_server_get_path(const struct unixctl_server *server)
548
0
{
549
0
    return server ? server->path : NULL;
550
0
}
551

552
/* On POSIX based systems, connects to a unixctl server socket.  'path' should
553
 * be the name of a unixctl server socket.  If it does not start with '/', it
554
 * will be prefixed with the rundir (e.g. /usr/local/var/run/openvswitch).
555
 *
556
 * Returns 0 if successful, otherwise a positive errno value.  If successful,
557
 * sets '*client' to the new jsonrpc, otherwise to NULL. */
558
int
559
unixctl_client_create(const char *path, struct jsonrpc **client)
560
0
{
561
0
    struct stream *stream;
562
0
    int error;
563
564
0
    char *abs_path = abs_file_name(ovs_rundir(), path);
565
0
    char *unix_path = xasprintf("unix:%s", abs_path);
566
567
0
    *client = NULL;
568
569
0
    error = stream_open_block(stream_open(unix_path, &stream, DSCP_DEFAULT),
570
0
                              -1, &stream);
571
0
    free(unix_path);
572
0
    free(abs_path);
573
574
0
    if (error) {
575
0
        VLOG_WARN("failed to connect to %s", path);
576
0
        return error;
577
0
    }
578
579
0
    *client = jsonrpc_open(stream);
580
0
    return 0;
581
0
}
582
583
/* Executes 'command' on the server with an argument vector 'argv' containing
584
 * 'argc' elements.  If successfully communicated with the server, returns 0
585
 * and sets '*result', or '*err' (not both) to the result or error the server
586
 * returned.  Otherwise, sets '*result' and '*err' to NULL and returns a
587
 * positive errno value.  The caller is responsible for freeing '*result' or
588
 * '*err' if not NULL. */
589
int
590
unixctl_client_transact(struct jsonrpc *client, const char *command, int argc,
591
                        char *argv[], struct json **result, struct json **err)
592
0
{
593
0
    struct jsonrpc_msg *request, *reply;
594
0
    struct json **json_args, *params;
595
0
    int error, i;
596
597
0
    *result = NULL;
598
0
    *err = NULL;
599
600
0
    json_args = xmalloc(argc * sizeof *json_args);
601
0
    for (i = 0; i < argc; i++) {
602
0
        json_args[i] = json_string_create(argv[i]);
603
0
    }
604
0
    params = json_array_create(json_args, argc);
605
0
    request = jsonrpc_create_request(command, params, NULL);
606
607
0
    error = jsonrpc_transact_block(client, request, &reply);
608
0
    if (error) {
609
0
        VLOG_WARN("error communicating with %s: %s", jsonrpc_get_name(client),
610
0
                  ovs_retval_to_string(error));
611
0
        return error;
612
0
    }
613
614
0
    if (reply->result && reply->error) {
615
0
        VLOG_WARN("unexpected response when communicating with %s: %s\n %s",
616
0
                  jsonrpc_get_name(client),
617
0
                  json_to_string(reply->result, JSSF_SORT),
618
0
                  json_to_string(reply->error, JSSF_SORT));
619
0
        error = EINVAL;
620
0
    } else {
621
0
        *result = json_nullable_clone(reply->result);
622
0
        *err = json_nullable_clone(reply->error);
623
0
    }
624
625
0
    jsonrpc_msg_destroy(reply);
626
0
    return error;
627
0
}