aboutsummaryrefslogtreecommitdiff
path: root/ovn/lib/actions.c
blob: ccf97f04723b213452ec379ef7dc6a4bd4d1b7b1 (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
/*
 * Copyright (c) 2015 Nicira, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at:
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <config.h>
#include "actions.h"
#include <stdarg.h>
#include <stdbool.h>
#include "compiler.h"
#include "dynamic-string.h"
#include "expr.h"
#include "lex.h"
#include "logical-fields.h"
#include "ofp-actions.h"
#include "ofpbuf.h"
#include "simap.h"

/* Context maintained during actions_parse(). */
struct action_context {
/* Input. */

    struct lexer *lexer;        /* Lexer for pulling more tokens. */
    const struct simap *ports;  /* Map from port name to number. */
    const struct shash *symtab; /* Symbol table. */

    /* OVN maps each logical flow table (ltable), one-to-one, onto a physical
     * OpenFlow flow table (ptable).  These members describe the mapping and
     * data related to flow tables. */
    uint8_t n_tables;           /* Number of flow tables. */
    uint8_t first_ptable;       /* First OpenFlow table. */
    uint8_t cur_ltable;         /* 0 <= cur_ltable < n_tables. */
    uint8_t output_ptable;      /* OpenFlow table for 'output' to resubmit. */
    const struct simap *ct_zones; /* Map from port name to conntrack zone. */

/* State. */
    char *error;                /* Error, if any, otherwise NULL. */

/* Output. */
    struct ofpbuf *ofpacts;     /* Actions. */
    struct expr *prereqs;       /* Prerequisites to apply to match. */
};

static bool
action_error_handle_common(struct action_context *ctx)
{
    if (ctx->error) {
        /* Already have an error, suppress this one since the cascade seems
         * unlikely to be useful. */
        return true;
    } else if (ctx->lexer->token.type == LEX_T_ERROR) {
        /* The lexer signaled an error.  Nothing at the action level
         * accepts an error token, so we'll inevitably end up here with some
         * meaningless parse error.  Report the lexical error instead. */
        ctx->error = xstrdup(ctx->lexer->token.s);
        return true;
    } else {
        return false;
    }
}

static void OVS_PRINTF_FORMAT(2, 3)
action_error(struct action_context *ctx, const char *message, ...)
{
    if (action_error_handle_common(ctx)) {
        return;
    }

    va_list args;
    va_start(args, message);
    ctx->error = xvasprintf(message, args);
    va_end(args);
}

static void OVS_PRINTF_FORMAT(2, 3)
action_syntax_error(struct action_context *ctx, const char *message, ...)
{
    if (action_error_handle_common(ctx)) {
        return;
    }

    struct ds s;

    ds_init(&s);
    ds_put_cstr(&s, "Syntax error");
    if (ctx->lexer->token.type == LEX_T_END) {
        ds_put_cstr(&s, " at end of input");
    } else if (ctx->lexer->start) {
        ds_put_format(&s, " at `%.*s'",
                      (int) (ctx->lexer->input - ctx->lexer->start),
                      ctx->lexer->start);
    }

    if (message) {
        ds_put_char(&s, ' ');

        va_list args;
        va_start(args, message);
        ds_put_format_valist(&s, message, args);
        va_end(args);
    }
    ds_put_char(&s, '.');

    ctx->error = ds_steal_cstr(&s);
}

/* Parses an assignment or exchange action. */
static void
parse_set_action(struct action_context *ctx)
{
    struct expr *prereqs;
    char *error;

    error = expr_parse_assignment(ctx->lexer, ctx->symtab, ctx->ports,
                                  ctx->ofpacts, &prereqs);
    if (error) {
        action_error(ctx, "%s", error);
        free(error);
        return;
    }

    ctx->prereqs = expr_combine(EXPR_T_AND, ctx->prereqs, prereqs);
}

static void
emit_resubmit(struct action_context *ctx, uint8_t table_id)
{
    struct ofpact_resubmit *resubmit = ofpact_put_RESUBMIT(ctx->ofpacts);
    resubmit->in_port = OFPP_IN_PORT;
    resubmit->table_id = table_id;
}

static bool
action_get_int(struct action_context *ctx, int *value)
{
    bool ok = lexer_get_int(ctx->lexer, value);
    if (!ok) {
        action_syntax_error(ctx, "expecting small integer");
    }
    return ok;
}

static void
parse_next_action(struct action_context *ctx)
{
    if (!ctx->n_tables) {
        action_error(ctx, "\"next\" action not allowed here.");
    } else if (lexer_match(ctx->lexer, LEX_T_LPAREN)) {
        int ltable;

        if (!action_get_int(ctx, &ltable)) {
            return;
        }
        if (!lexer_match(ctx->lexer, LEX_T_RPAREN)) {
            action_syntax_error(ctx, "expecting `)'");
            return;
        }

        if (ltable >= ctx->n_tables) {
            action_error(ctx, "\"next\" argument must be in range 0 to %d.",
                         ctx->n_tables - 1);
            return;
        }

        emit_resubmit(ctx, ctx->first_ptable + ltable);
    } else {
        if (ctx->cur_ltable < ctx->n_tables) {
            emit_resubmit(ctx, ctx->first_ptable + ctx->cur_ltable + 1);
        } else {
            action_error(ctx, "\"next\" action not allowed in last table.");
        }
    }
}

static void
emit_ct(struct action_context *ctx, bool recirc_next, bool commit)
{
    struct ofpact_conntrack *ct = ofpact_put_CT(ctx->ofpacts);
    ct->flags |= commit ? NX_CT_F_COMMIT : 0;

    /* If "recirc" is set, we automatically go to the next table. */
    if (recirc_next) {
        if (ctx->cur_ltable < ctx->n_tables) {
            ct->recirc_table = ctx->first_ptable + ctx->cur_ltable + 1;
        } else {
            action_error(ctx, "\"ct_next\" action not allowed in last table.");
            return;
        }
    } else {
        ct->recirc_table = NX_CT_RECIRC_NONE;
    }

    ct->zone_src.field = mf_from_id(MFF_LOG_CT_ZONE);
    ct->zone_src.ofs = 0;
    ct->zone_src.n_bits = 16;

    /* We do not support ALGs yet. */
    ct->alg = 0;

    /* CT only works with IP, so set up a prerequisite. */
    struct expr *expr;
    char *error;

    expr = expr_parse_string("ip", ctx->symtab, &error);
    ovs_assert(!error);
    ctx->prereqs = expr_combine(EXPR_T_AND, ctx->prereqs, expr);
}

static void
parse_actions(struct action_context *ctx)
{
    /* "drop;" by itself is a valid (empty) set of actions, but it can't be
     * combined with other actions because that doesn't make sense. */
    if (ctx->lexer->token.type == LEX_T_ID
        && !strcmp(ctx->lexer->token.s, "drop")
        && lexer_lookahead(ctx->lexer) == LEX_T_SEMICOLON) {
        lexer_get(ctx->lexer);  /* Skip "drop". */
        lexer_get(ctx->lexer);  /* Skip ";". */
        if (ctx->lexer->token.type != LEX_T_END) {
            action_syntax_error(ctx, "expecting end of input");
        }
        return;
    }

    while (ctx->lexer->token.type != LEX_T_END) {
        if (ctx->lexer->token.type != LEX_T_ID) {
            action_syntax_error(ctx, NULL);
            break;
        }

        enum lex_type lookahead = lexer_lookahead(ctx->lexer);
        if (lookahead == LEX_T_EQUALS || lookahead == LEX_T_EXCHANGE
            || lookahead == LEX_T_LSQUARE) {
            parse_set_action(ctx);
        } else if (lexer_match_id(ctx->lexer, "next")) {
            parse_next_action(ctx);
        } else if (lexer_match_id(ctx->lexer, "output")) {
            emit_resubmit(ctx, ctx->output_ptable);
        } else if (lexer_match_id(ctx->lexer, "ip4.ttl")) {
            if (lexer_match(ctx->lexer, LEX_T_DECREMENT)) {
                struct expr *e = expr_parse_string("ip4", ctx->symtab,
                                                   &ctx->error);
                ctx->prereqs = expr_combine(EXPR_T_AND, ctx->prereqs, e);
                ofpact_put_DEC_TTL(ctx->ofpacts);
            } else {
                action_syntax_error(ctx, "expecting `--'");
            }
        } else if (lexer_match_id(ctx->lexer, "ct_next")) {
            emit_ct(ctx, true, false);
        } else if (lexer_match_id(ctx->lexer, "ct_commit")) {
            emit_ct(ctx, false, true);
        } else {
            action_syntax_error(ctx, "expecting action");
        }
        if (!lexer_match(ctx->lexer, LEX_T_SEMICOLON)) {
            action_syntax_error(ctx, "expecting ';'");
        }
        if (ctx->error) {
            return;
        }
    }
}

/* Parses OVN actions, in the format described for the "actions" column in the
 * Logical_Flow table in ovn-sb(5), and appends the parsed versions of the
 * actions to 'ofpacts' as "struct ofpact"s.
 *
 * 'symtab' provides a table of "struct expr_symbol"s to support (as one would
 * provide to expr_parse()).
 *
 * 'ports' must be a map from strings (presumably names of ports) to integers
 * (as one would provide to expr_to_matches()).  Strings used in the actions
 * that are not in 'ports' are translated to zero.
 *
 * 'ct_zones' provides a map from a port name to its connection tracking zone.
 *
 * OVN maps each logical flow table (ltable), one-to-one, onto a physical
 * OpenFlow flow table (ptable).  A number of parameters describe this mapping
 * and data related to flow tables:
 *
 *     - 'first_ptable' and 'n_tables' define the range of OpenFlow tables to
 *       which the logical "next" action should be able to jump.  Logical table
 *       0 maps to OpenFlow table 'first_ptable', logical table 1 to
 *       'first_ptable + 1', and so on.  If 'n_tables' is 0 then "next" is
 *       disallowed entirely.
 *
 *     - 'cur_ltable' is an offset from 'first_ptable' (e.g. 0 <= cur_ltable <
 *       n_ptables) of the logical flow that contains the actions.  If
 *       cur_ltable + 1 < n_tables, then this defines the default table that
 *       "next" will jump to.
 *
 * 'next_table_id' should be the OpenFlow table to which the "next" action will
 * resubmit, or 0 to disable "next".
 *
 *     - 'output_ptable' should be the OpenFlow table to which the logical
 *       "output" action will resubmit
 *
 * Some actions add extra requirements (prerequisites) to the flow's match.  If
 * so, this function sets '*prereqsp' to the actions' prerequisites; otherwise,
 * it sets '*prereqsp' to NULL.  The caller owns '*prereqsp' and must
 * eventually free it.
 *
 * Returns NULL on success, otherwise a malloc()'d error message that the
 * caller must free.  On failure, 'ofpacts' has the same contents and
 * '*prereqsp' is set to NULL, but some tokens may have been consumed from
 * 'lexer'.
  */
char * OVS_WARN_UNUSED_RESULT
actions_parse(struct lexer *lexer, const struct shash *symtab,
              const struct simap *ports, const struct simap *ct_zones,
              uint8_t first_ptable, uint8_t n_tables, uint8_t cur_ltable,
              uint8_t output_ptable, struct ofpbuf *ofpacts,
              struct expr **prereqsp)
{
    size_t ofpacts_start = ofpacts->size;

    struct action_context ctx;
    ctx.lexer = lexer;
    ctx.symtab = symtab;
    ctx.ports = ports;
    ctx.ct_zones = ct_zones;
    ctx.first_ptable = first_ptable;
    ctx.n_tables = n_tables;
    ctx.cur_ltable = cur_ltable;
    ctx.output_ptable = output_ptable;
    ctx.error = NULL;
    ctx.ofpacts = ofpacts;
    ctx.prereqs = NULL;

    parse_actions(&ctx);

    if (!ctx.error) {
        *prereqsp = ctx.prereqs;
        return NULL;
    } else {
        ofpacts->size = ofpacts_start;
        expr_destroy(ctx.prereqs);
        *prereqsp = NULL;
        return ctx.error;
    }
}

/* Like actions_parse(), but the actions are taken from 's'. */
char * OVS_WARN_UNUSED_RESULT
actions_parse_string(const char *s, const struct shash *symtab,
                     const struct simap *ports, const struct simap *ct_zones,
                     uint8_t first_table, uint8_t n_tables, uint8_t cur_table,
                     uint8_t output_table, struct ofpbuf *ofpacts,
                     struct expr **prereqsp)
{
    struct lexer lexer;
    char *error;

    lexer_init(&lexer, s);
    lexer_get(&lexer);
    error = actions_parse(&lexer, symtab, ports, ct_zones, first_table,
                          n_tables, cur_table, output_table, ofpacts,
                          prereqsp);
    lexer_destroy(&lexer);

    return error;
}