summaryrefslogtreecommitdiff
path: root/lib/ofp-switch.c
blob: 2f7a1cad299a4437e35e8c62e49a5f7e55b0aa75 (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
386
387
388
389
390
391
392
393
394
395
/*
 * Copyright (c) 2008-2017 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 "openvswitch/ofp-switch.h"
#include "byte-order.h"
#include "openvswitch/ofpbuf.h"
#include "openvswitch/ofp-actions.h"
#include "openvswitch/ofp-errors.h"
#include "openvswitch/ofp-msgs.h"
#include "openvswitch/ofp-port.h"
#include "openvswitch/ofp-print.h"
#include "util.h"

/* ofputil_switch_features */

#define OFPC_COMMON (OFPC_FLOW_STATS | OFPC_TABLE_STATS | OFPC_PORT_STATS | \
                     OFPC_IP_REASM | OFPC_QUEUE_STATS)
BUILD_ASSERT_DECL((int) OFPUTIL_C_FLOW_STATS == OFPC_FLOW_STATS);
BUILD_ASSERT_DECL((int) OFPUTIL_C_TABLE_STATS == OFPC_TABLE_STATS);
BUILD_ASSERT_DECL((int) OFPUTIL_C_PORT_STATS == OFPC_PORT_STATS);
BUILD_ASSERT_DECL((int) OFPUTIL_C_IP_REASM == OFPC_IP_REASM);
BUILD_ASSERT_DECL((int) OFPUTIL_C_QUEUE_STATS == OFPC_QUEUE_STATS);
BUILD_ASSERT_DECL((int) OFPUTIL_C_ARP_MATCH_IP == OFPC_ARP_MATCH_IP);
BUILD_ASSERT_DECL((int) OFPUTIL_C_PORT_BLOCKED == OFPC12_PORT_BLOCKED);
BUILD_ASSERT_DECL((int) OFPUTIL_C_BUNDLES == OFPC14_BUNDLES);
BUILD_ASSERT_DECL((int) OFPUTIL_C_FLOW_MONITORING == OFPC14_FLOW_MONITORING);

static uint32_t
ofputil_capabilities_mask(enum ofp_version ofp_version)
{
    /* Handle capabilities whose bit is unique for all OpenFlow versions */
    switch (ofp_version) {
    case OFP10_VERSION:
    case OFP11_VERSION:
        return OFPC_COMMON | OFPC_ARP_MATCH_IP;
    case OFP12_VERSION:
    case OFP13_VERSION:
        return OFPC_COMMON | OFPC12_PORT_BLOCKED;
    case OFP14_VERSION:
    case OFP15_VERSION:
        return OFPC_COMMON | OFPC12_PORT_BLOCKED | OFPC14_BUNDLES
            | OFPC14_FLOW_MONITORING;
    default:
        /* Caller needs to check osf->header.version itself */
        return 0;
    }
}

/* Pulls an OpenFlow "switch_features" structure from 'b' and decodes it into
 * an abstract representation in '*features', readying 'b' to iterate over the
 * OpenFlow port structures following 'osf' with later calls to
 * ofputil_pull_phy_port().  Returns 0 if successful, otherwise an OFPERR_*
 * value.  */
enum ofperr
ofputil_pull_switch_features(struct ofpbuf *b,
                             struct ofputil_switch_features *features)
{
    const struct ofp_header *oh = b->data;
    enum ofpraw raw = ofpraw_pull_assert(b);
    const struct ofp_switch_features *osf = ofpbuf_pull(b, sizeof *osf);
    features->datapath_id = ntohll(osf->datapath_id);
    features->n_buffers = ntohl(osf->n_buffers);
    features->n_tables = osf->n_tables;
    features->auxiliary_id = 0;

    features->capabilities = ntohl(osf->capabilities) &
        ofputil_capabilities_mask(oh->version);

    if (raw == OFPRAW_OFPT10_FEATURES_REPLY) {
        if (osf->capabilities & htonl(OFPC10_STP)) {
            features->capabilities |= OFPUTIL_C_STP;
        }
        features->ofpacts = ofpact_bitmap_from_openflow(osf->actions,
                                                        OFP10_VERSION);
    } else if (raw == OFPRAW_OFPT11_FEATURES_REPLY
               || raw == OFPRAW_OFPT13_FEATURES_REPLY) {
        if (osf->capabilities & htonl(OFPC11_GROUP_STATS)) {
            features->capabilities |= OFPUTIL_C_GROUP_STATS;
        }
        features->ofpacts = 0;
        if (raw == OFPRAW_OFPT13_FEATURES_REPLY) {
            features->auxiliary_id = osf->auxiliary_id;
        }
    } else {
        return OFPERR_OFPBRC_BAD_VERSION;
    }

    return 0;
}

/* In OpenFlow 1.0, 1.1, and 1.2, an OFPT_FEATURES_REPLY message lists all the
 * switch's ports, unless there are too many to fit.  In OpenFlow 1.3 and
 * later, an OFPT_FEATURES_REPLY does not list ports at all.
 *
 * Given a buffer 'b' that contains a Features Reply message, this message
 * checks if it contains a complete list of the switch's ports.  Returns true,
 * if so.  Returns false if the list is missing (OF1.3+) or incomplete
 * (OF1.0/1.1/1.2), and in the latter case removes all of the ports from the
 * message.
 *
 * When this function returns false, the caller should send an OFPST_PORT_DESC
 * stats request to get the ports. */
bool
ofputil_switch_features_has_ports(struct ofpbuf *b)
{
    struct ofp_header *oh = b->data;
    size_t phy_port_size;

    if (oh->version >= OFP13_VERSION) {
        /* OpenFlow 1.3+ never has ports in the feature reply. */
        return false;
    }

    phy_port_size = (oh->version == OFP10_VERSION
                     ? sizeof(struct ofp10_phy_port)
                     : sizeof(struct ofp11_port));
    if (ntohs(oh->length) + phy_port_size <= UINT16_MAX) {
        /* There's room for additional ports in the feature reply.
         * Assume that the list is complete. */
        return true;
    }

    /* The feature reply has no room for more ports.  Probably the list is
     * truncated.  Drop the ports and tell the caller to retrieve them with
     * OFPST_PORT_DESC. */
    b->size = sizeof *oh + sizeof(struct ofp_switch_features);
    ofpmsg_update_length(b);
    return false;
}

/* Returns a buffer owned by the caller that encodes 'features' in the format
 * required by 'protocol' with the given 'xid'.  The caller should append port
 * information to the buffer with subsequent calls to
 * ofputil_put_switch_features_port(). */
struct ofpbuf *
ofputil_encode_switch_features(const struct ofputil_switch_features *features,
                               enum ofputil_protocol protocol, ovs_be32 xid)
{
    struct ofp_switch_features *osf;
    struct ofpbuf *b;
    enum ofp_version version;
    enum ofpraw raw;

    version = ofputil_protocol_to_ofp_version(protocol);
    switch (version) {
    case OFP10_VERSION:
        raw = OFPRAW_OFPT10_FEATURES_REPLY;
        break;
    case OFP11_VERSION:
    case OFP12_VERSION:
        raw = OFPRAW_OFPT11_FEATURES_REPLY;
        break;
    case OFP13_VERSION:
    case OFP14_VERSION:
    case OFP15_VERSION:
        raw = OFPRAW_OFPT13_FEATURES_REPLY;
        break;
    default:
        OVS_NOT_REACHED();
    }
    b = ofpraw_alloc_xid(raw, version, xid, 0);
    osf = ofpbuf_put_zeros(b, sizeof *osf);
    osf->datapath_id = htonll(features->datapath_id);
    osf->n_buffers = htonl(features->n_buffers);
    osf->n_tables = features->n_tables;

    osf->capabilities = htonl(features->capabilities &
                              ofputil_capabilities_mask(version));
    switch (version) {
    case OFP10_VERSION:
        if (features->capabilities & OFPUTIL_C_STP) {
            osf->capabilities |= htonl(OFPC10_STP);
        }
        osf->actions = ofpact_bitmap_to_openflow(features->ofpacts,
                                                 OFP10_VERSION);
        break;
    case OFP13_VERSION:
    case OFP14_VERSION:
    case OFP15_VERSION:
        osf->auxiliary_id = features->auxiliary_id;
        /* fall through */
    case OFP11_VERSION:
    case OFP12_VERSION:
        if (features->capabilities & OFPUTIL_C_GROUP_STATS) {
            osf->capabilities |= htonl(OFPC11_GROUP_STATS);
        }
        break;
    default:
        OVS_NOT_REACHED();
    }

    return b;
}

/* Encodes 'pp' into the format required by the switch_features message already
 * in 'b', which should have been returned by ofputil_encode_switch_features(),
 * and appends the encoded version to 'b'. */
void
ofputil_put_switch_features_port(const struct ofputil_phy_port *pp,
                                 struct ofpbuf *b)
{
    const struct ofp_header *oh = b->data;

    if (oh->version < OFP13_VERSION) {
        /* Try adding a port description to the message, but drop it again if
         * the buffer overflows.  (This possibility for overflow is why
         * OpenFlow 1.3+ moved port descriptions into a multipart message.)  */
        size_t start_ofs = b->size;
        ofputil_put_phy_port(oh->version, pp, b);
        if (b->size > UINT16_MAX) {
            b->size = start_ofs;
        }
    }
}

static const char *
ofputil_capabilities_to_name(uint32_t bit)
{
    enum ofputil_capabilities capabilities = bit;

    switch (capabilities) {
    case OFPUTIL_C_FLOW_STATS:   return "FLOW_STATS";
    case OFPUTIL_C_TABLE_STATS:  return "TABLE_STATS";
    case OFPUTIL_C_PORT_STATS:   return "PORT_STATS";
    case OFPUTIL_C_IP_REASM:     return "IP_REASM";
    case OFPUTIL_C_QUEUE_STATS:  return "QUEUE_STATS";
    case OFPUTIL_C_ARP_MATCH_IP: return "ARP_MATCH_IP";
    case OFPUTIL_C_STP:          return "STP";
    case OFPUTIL_C_GROUP_STATS:  return "GROUP_STATS";
    case OFPUTIL_C_PORT_BLOCKED: return "PORT_BLOCKED";
    case OFPUTIL_C_BUNDLES:      return "BUNDLES";
    case OFPUTIL_C_FLOW_MONITORING: return "FLOW_MONITORING";
    }

    return NULL;
}

void
ofputil_switch_features_format(struct ds *s,
                               const struct ofputil_switch_features *features)
{
    ds_put_format(s, " dpid:%016"PRIx64"\n", features->datapath_id);

    ds_put_format(s, "n_tables:%"PRIu8", n_buffers:%"PRIu32,
                  features->n_tables, features->n_buffers);
    if (features->auxiliary_id) {
        ds_put_format(s, ", auxiliary_id:%"PRIu8, features->auxiliary_id);
    }
    ds_put_char(s, '\n');

    ds_put_cstr(s, "capabilities: ");
    ofp_print_bit_names(s, features->capabilities,
                        ofputil_capabilities_to_name, ' ');
    ds_put_char(s, '\n');

    if (features->ofpacts) {
        ds_put_cstr(s, "actions: ");
        ofpact_bitmap_format(features->ofpacts, s);
        ds_put_char(s, '\n');
    }
}

const char *
ofputil_frag_handling_to_string(enum ofputil_frag_handling frag)
{
    switch (frag) {
    case OFPUTIL_FRAG_NORMAL:   return "normal";
    case OFPUTIL_FRAG_DROP:     return "drop";
    case OFPUTIL_FRAG_REASM:    return "reassemble";
    case OFPUTIL_FRAG_NX_MATCH: return "nx-match";
    }

    OVS_NOT_REACHED();
}

bool
ofputil_frag_handling_from_string(const char *s,
                                  enum ofputil_frag_handling *frag)
{
    if (!strcasecmp(s, "normal")) {
        *frag = OFPUTIL_FRAG_NORMAL;
    } else if (!strcasecmp(s, "drop")) {
        *frag = OFPUTIL_FRAG_DROP;
    } else if (!strcasecmp(s, "reassemble")) {
        *frag = OFPUTIL_FRAG_REASM;
    } else if (!strcasecmp(s, "nx-match")) {
        *frag = OFPUTIL_FRAG_NX_MATCH;
    } else {
        return false;
    }
    return true;
}

/* ofputil_switch_config */

/* Decodes 'oh', which must be an OFPT_GET_CONFIG_REPLY or OFPT_SET_CONFIG
 * message, into 'config'.  Returns false if 'oh' contained any flags that
 * aren't specified in its version of OpenFlow, true otherwise. */
static bool
ofputil_decode_switch_config(const struct ofp_header *oh,
                             struct ofputil_switch_config *config)
{
    struct ofpbuf b = ofpbuf_const_initializer(oh, ntohs(oh->length));
    ofpraw_pull_assert(&b);

    const struct ofp_switch_config *osc = ofpbuf_pull(&b, sizeof *osc);
    config->frag = ntohs(osc->flags) & OFPC_FRAG_MASK;
    config->miss_send_len = ntohs(osc->miss_send_len);

    ovs_be16 valid_mask = htons(OFPC_FRAG_MASK);
    if (oh->version < OFP13_VERSION) {
        const ovs_be16 ttl_bit = htons(OFPC_INVALID_TTL_TO_CONTROLLER);
        valid_mask |= ttl_bit;
        config->invalid_ttl_to_controller = (osc->flags & ttl_bit) != 0;
    } else {
        config->invalid_ttl_to_controller = -1;
    }

    return !(osc->flags & ~valid_mask);
}

void
ofputil_decode_get_config_reply(const struct ofp_header *oh,
                                struct ofputil_switch_config *config)
{
    ofputil_decode_switch_config(oh, config);
}

enum ofperr
ofputil_decode_set_config(const struct ofp_header *oh,
                          struct ofputil_switch_config *config)
{
    return (ofputil_decode_switch_config(oh, config)
            ? 0
            : OFPERR_OFPSCFC_BAD_FLAGS);
}

static struct ofpbuf *
ofputil_put_switch_config(const struct ofputil_switch_config *config,
                          struct ofpbuf *b)
{
    const struct ofp_header *oh = b->data;
    struct ofp_switch_config *osc = ofpbuf_put_zeros(b, sizeof *osc);
    osc->flags = htons(config->frag);
    if (config->invalid_ttl_to_controller > 0 && oh->version < OFP13_VERSION) {
        osc->flags |= htons(OFPC_INVALID_TTL_TO_CONTROLLER);
    }
    osc->miss_send_len = htons(config->miss_send_len);
    return b;
}

struct ofpbuf *
ofputil_encode_get_config_reply(const struct ofp_header *request,
                                const struct ofputil_switch_config *config)
{
    struct ofpbuf *b = ofpraw_alloc_reply(OFPRAW_OFPT_GET_CONFIG_REPLY,
                                          request, 0);
    return ofputil_put_switch_config(config, b);
}

struct ofpbuf *
ofputil_encode_set_config(const struct ofputil_switch_config *config,
                          enum ofp_version version)
{
    struct ofpbuf *b = ofpraw_alloc(OFPRAW_OFPT_SET_CONFIG, version, 0);
    return ofputil_put_switch_config(config, b);
}

void
ofputil_switch_config_format(struct ds *s,
                             const struct ofputil_switch_config *config)
{
    ds_put_format(s, " frags=%s",
                  ofputil_frag_handling_to_string(config->frag));

    if (config->invalid_ttl_to_controller > 0) {
        ds_put_format(s, " invalid_ttl_to_controller");
    }

    ds_put_format(s, " miss_send_len=%"PRIu16"\n", config->miss_send_len);
}