Coverage Report

Created: 2025-07-01 06:51

/src/openvswitch/lib/unixctl.c
Line
Count
Source (jump to first uncovered line)
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
 * For Windows, a local named pipe is used. A file is created in 'path'
320
 * which may be:
321
 *
322
 *      - NULL, in which case <rundir>/<program>.ctl is used.
323
 *
324
 *      - An absolute path that gives the name of the file.
325
 *
326
 * For both POSIX and Windows, if the path is "none", the function will
327
 * return successfully but no socket will actually be created.
328
 *
329
 * A program that (optionally) daemonizes itself should call this function
330
 * *after* daemonization, so that the socket name contains the pid of the
331
 * daemon instead of the pid of the program that exited.  (Otherwise,
332
 * "ovs-appctl --target=<program>" will fail.)
333
 *
334
 * Returns 0 if successful, otherwise a positive errno value.  If successful,
335
 * sets '*serverp' to the new unixctl_server (or to NULL if 'path' was "none"),
336
 * otherwise to NULL. */
337
int
338
unixctl_server_create(const char *path, struct unixctl_server **serverp)
339
0
{
340
0
    *serverp = NULL;
341
0
    if (path && !strcmp(path, "none")) {
342
0
        return 0;
343
0
    }
344
345
#ifdef _WIN32
346
    enum { WINDOWS = 1 };
347
#else
348
0
    enum { WINDOWS = 0 };
349
0
#endif
350
351
0
    long int pid = getpid();
352
0
    char *abs_path
353
0
        = (path ? abs_file_name(ovs_rundir(), path)
354
0
           : WINDOWS ? xasprintf("%s/%s.ctl", ovs_rundir(), program_name)
355
0
           : xasprintf("%s/%s.%ld.ctl", ovs_rundir(), program_name, pid));
356
357
0
    struct pstream *listener;
358
0
    char *punix_path = xasprintf("punix:%s", abs_path);
359
0
    int error = pstream_open(punix_path, &listener, 0);
360
0
    free(punix_path);
361
362
0
    if (error) {
363
0
        ovs_error(error, "%s: could not initialize control socket", abs_path);
364
0
        free(abs_path);
365
0
        return error;
366
0
    }
367
368
0
    unixctl_command_register("list-commands", "", 0, 0, unixctl_list_commands,
369
0
                             NULL);
370
0
    unixctl_command_register("version", "", 0, 0, unixctl_version, NULL);
371
0
    unixctl_command_register("set-options", "[--format text|json]", 1, 2,
372
0
                             unixctl_set_options, NULL);
373
374
0
    struct unixctl_server *server = xmalloc(sizeof *server);
375
0
    server->listener = listener;
376
0
    server->path = abs_path;
377
0
    ovs_list_init(&server->conns);
378
0
    *serverp = server;
379
0
    return 0;
380
0
}
381
382
static void
383
process_command(struct unixctl_conn *conn, struct jsonrpc_msg *request)
384
0
{
385
0
    char *error = NULL;
386
387
0
    struct unixctl_command *command;
388
0
    const struct json *params;
389
390
0
    COVERAGE_INC(unixctl_received);
391
0
    conn->request_id = json_clone(request->id);
392
393
0
    if (VLOG_IS_DBG_ENABLED()) {
394
0
        char *params_s = json_to_string(request->params, 0);
395
0
        char *id_s = json_to_string(request->id, 0);
396
0
        VLOG_DBG("received request %s%s, id=%s",
397
0
                 request->method, params_s, id_s);
398
0
        free(params_s);
399
0
        free(id_s);
400
0
    }
401
402
0
    params = request->params;
403
0
    command = shash_find_data(&commands, request->method);
404
0
    if (!command) {
405
0
        error = xasprintf("\"%s\" is not a valid command (use "
406
0
                          "\"list-commands\" to see a list of valid commands)",
407
0
                          request->method);
408
0
    } else if (json_array_size(params) < command->min_args) {
409
0
        error = xasprintf("\"%s\" command requires at least %d arguments",
410
0
                          request->method, command->min_args);
411
0
    } else if (json_array_size(params) > command->max_args) {
412
0
        error = xasprintf("\"%s\" command takes at most %d arguments",
413
0
                          request->method, command->max_args);
414
0
    } else {
415
0
        struct svec argv = SVEC_EMPTY_INITIALIZER;
416
0
        int  i, n = json_array_size(params);
417
418
0
        svec_add(&argv, request->method);
419
0
        for (i = 0; i < n; i++) {
420
0
            const struct json *elem = json_array_at(params, i);
421
422
0
            if (elem->type != JSON_STRING) {
423
0
                error = xasprintf("\"%s\" command has non-string argument",
424
0
                                  request->method);
425
0
                break;
426
0
            }
427
0
            svec_add(&argv, json_string(elem));
428
0
        }
429
0
        svec_terminate(&argv);
430
431
0
        if (!error) {
432
0
            command->cb(conn, argv.n, (const char **) argv.names,
433
0
                        command->aux);
434
0
        }
435
436
0
        svec_destroy(&argv);
437
0
    }
438
439
0
    if (error) {
440
0
        unixctl_command_reply_error(conn, error);
441
0
        free(error);
442
0
    }
443
0
}
444
445
static int
446
run_connection(struct unixctl_conn *conn)
447
0
{
448
0
    int error, i;
449
450
0
    jsonrpc_run(conn->rpc);
451
0
    error = jsonrpc_get_status(conn->rpc);
452
0
    if (error || jsonrpc_get_backlog(conn->rpc)) {
453
0
        return error;
454
0
    }
455
456
0
    for (i = 0; i < 10; i++) {
457
0
        struct jsonrpc_msg *msg;
458
459
0
        if (error || conn->request_id) {
460
0
            break;
461
0
        }
462
463
0
        jsonrpc_recv(conn->rpc, &msg);
464
0
        if (msg) {
465
0
            if (msg->type == JSONRPC_REQUEST) {
466
0
                process_command(conn, msg);
467
0
            } else {
468
0
                VLOG_WARN_RL(&rl, "%s: received unexpected %s message",
469
0
                             jsonrpc_get_name(conn->rpc),
470
0
                             jsonrpc_msg_type_to_string(msg->type));
471
0
                error = EINVAL;
472
0
            }
473
0
            jsonrpc_msg_destroy(msg);
474
0
        }
475
0
        error = error ? error : jsonrpc_get_status(conn->rpc);
476
0
    }
477
478
0
    return error;
479
0
}
480
481
static void
482
kill_connection(struct unixctl_conn *conn)
483
0
{
484
0
    ovs_list_remove(&conn->node);
485
0
    jsonrpc_close(conn->rpc);
486
0
    json_destroy(conn->request_id);
487
0
    free(conn);
488
0
}
489
490
void
491
unixctl_server_run(struct unixctl_server *server)
492
0
{
493
0
    if (!server) {
494
0
        return;
495
0
    }
496
497
0
    for (int i = 0; i < 10; i++) {
498
0
        struct stream *stream;
499
0
        int error;
500
501
0
        error = pstream_accept(server->listener, &stream);
502
0
        if (!error) {
503
0
            struct unixctl_conn *conn = xzalloc(sizeof *conn);
504
0
            ovs_list_push_back(&server->conns, &conn->node);
505
0
            conn->rpc = jsonrpc_open(stream);
506
0
            conn->fmt = UNIXCTL_OUTPUT_FMT_TEXT;
507
0
        } else if (error == EAGAIN) {
508
0
            break;
509
0
        } else {
510
0
            VLOG_WARN_RL(&rl, "%s: accept failed: %s",
511
0
                         pstream_get_name(server->listener),
512
0
                         ovs_strerror(error));
513
0
        }
514
0
    }
515
516
0
    struct unixctl_conn *conn;
517
0
    LIST_FOR_EACH_SAFE (conn, node, &server->conns) {
518
0
        int error = run_connection(conn);
519
0
        if (error && error != EAGAIN) {
520
0
            kill_connection(conn);
521
0
        }
522
0
    }
523
0
}
524
525
void
526
unixctl_server_wait(struct unixctl_server *server)
527
0
{
528
0
    struct unixctl_conn *conn;
529
530
0
    if (!server) {
531
0
        return;
532
0
    }
533
534
0
    pstream_wait(server->listener);
535
0
    LIST_FOR_EACH (conn, node, &server->conns) {
536
0
        jsonrpc_wait(conn->rpc);
537
0
        if (!jsonrpc_get_backlog(conn->rpc) && !conn->request_id) {
538
0
            jsonrpc_recv_wait(conn->rpc);
539
0
        }
540
0
    }
541
0
}
542
543
/* Destroys 'server' and stops listening for connections. */
544
void
545
unixctl_server_destroy(struct unixctl_server *server)
546
0
{
547
0
    if (server) {
548
0
        struct unixctl_conn *conn;
549
550
0
        LIST_FOR_EACH_SAFE (conn, node, &server->conns) {
551
0
            kill_connection(conn);
552
0
        }
553
554
0
        free(server->path);
555
0
        pstream_close(server->listener);
556
0
        free(server);
557
0
    }
558
0
}
559
560
const char *
561
unixctl_server_get_path(const struct unixctl_server *server)
562
0
{
563
0
    return server ? server->path : NULL;
564
0
}
565

566
/* On POSIX based systems, connects to a unixctl server socket.  'path' should
567
 * be the name of a unixctl server socket.  If it does not start with '/', it
568
 * will be prefixed with the rundir (e.g. /usr/local/var/run/openvswitch).
569
 *
570
 * On Windows, connects to a local named pipe. A file which resides in
571
 * 'path' is used to mimic the behavior of a Unix domain socket.
572
 * 'path' should be an absolute path of the file.
573
 *
574
 * Returns 0 if successful, otherwise a positive errno value.  If successful,
575
 * sets '*client' to the new jsonrpc, otherwise to NULL. */
576
int
577
unixctl_client_create(const char *path, struct jsonrpc **client)
578
0
{
579
0
    struct stream *stream;
580
0
    int error;
581
582
0
    char *abs_path = abs_file_name(ovs_rundir(), path);
583
0
    char *unix_path = xasprintf("unix:%s", abs_path);
584
585
0
    *client = NULL;
586
587
0
    error = stream_open_block(stream_open(unix_path, &stream, DSCP_DEFAULT),
588
0
                              -1, &stream);
589
0
    free(unix_path);
590
0
    free(abs_path);
591
592
0
    if (error) {
593
0
        VLOG_WARN("failed to connect to %s", path);
594
0
        return error;
595
0
    }
596
597
0
    *client = jsonrpc_open(stream);
598
0
    return 0;
599
0
}
600
601
/* Executes 'command' on the server with an argument vector 'argv' containing
602
 * 'argc' elements.  If successfully communicated with the server, returns 0
603
 * and sets '*result', or '*err' (not both) to the result or error the server
604
 * returned.  Otherwise, sets '*result' and '*err' to NULL and returns a
605
 * positive errno value.  The caller is responsible for freeing '*result' or
606
 * '*err' if not NULL. */
607
int
608
unixctl_client_transact(struct jsonrpc *client, const char *command, int argc,
609
                        char *argv[], struct json **result, struct json **err)
610
0
{
611
0
    struct jsonrpc_msg *request, *reply;
612
0
    struct json **json_args, *params;
613
0
    int error, i;
614
615
0
    *result = NULL;
616
0
    *err = NULL;
617
618
0
    json_args = xmalloc(argc * sizeof *json_args);
619
0
    for (i = 0; i < argc; i++) {
620
0
        json_args[i] = json_string_create(argv[i]);
621
0
    }
622
0
    params = json_array_create(json_args, argc);
623
0
    request = jsonrpc_create_request(command, params, NULL);
624
625
0
    error = jsonrpc_transact_block(client, request, &reply);
626
0
    if (error) {
627
0
        VLOG_WARN("error communicating with %s: %s", jsonrpc_get_name(client),
628
0
                  ovs_retval_to_string(error));
629
0
        return error;
630
0
    }
631
632
0
    if (reply->result && reply->error) {
633
0
        VLOG_WARN("unexpected response when communicating with %s: %s\n %s",
634
0
                  jsonrpc_get_name(client),
635
0
                  json_to_string(reply->result, JSSF_SORT),
636
0
                  json_to_string(reply->error, JSSF_SORT));
637
0
        error = EINVAL;
638
0
    } else {
639
0
        *result = json_nullable_clone(reply->result);
640
0
        *err = json_nullable_clone(reply->error);
641
0
    }
642
643
0
    jsonrpc_msg_destroy(reply);
644
0
    return error;
645
0
}