Coverage Report

Created: 2025-07-11 06:11

/src/openvswitch/lib/ofp-ct.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright (c) 2023, Red Hat, 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 <stdbool.h>
19
#include <stdint.h>
20
#include <sys/types.h>
21
#include <netinet/in.h>
22
#include <netinet/icmp6.h>
23
24
#include "ct-dpif.h"
25
#include "openvswitch/ofp-ct.h"
26
#include "openflow/nicira-ext.h"
27
#include "openvswitch/dynamic-string.h"
28
#include "openvswitch/ofp-msgs.h"
29
#include "openvswitch/ofp-parse.h"
30
#include "openvswitch/ofp-errors.h"
31
#include "openvswitch/ofp-prop.h"
32
#include "openvswitch/ofp-util.h"
33
#include "openvswitch/packets.h"
34
#include "openvswitch/vlog.h"
35
36
VLOG_DEFINE_THIS_MODULE(ofp_ct);
37
38
static void
39
ofp_ct_tuple_format(struct ds *ds, const struct ofp_ct_tuple *tuple,
40
                    uint8_t ip_proto, uint16_t l3_type)
41
0
{
42
0
    ds_put_cstr(ds, l3_type == AF_INET ? "ct_nw_src=": "ct_ipv6_src=");
43
0
    ipv6_format_mapped(&tuple->src, ds);
44
0
    ds_put_cstr(ds, l3_type == AF_INET ? ",ct_nw_dst=": ",ct_ipv6_dst=");
45
0
    ipv6_format_mapped(&tuple->dst, ds);
46
0
    if (ip_proto == IPPROTO_ICMP || ip_proto == IPPROTO_ICMPV6) {
47
0
        ds_put_format(ds, ",icmp_id=%u,icmp_type=%u,icmp_code=%u",
48
0
                      ntohs(tuple->icmp_id), tuple->icmp_type,
49
0
                      tuple->icmp_code);
50
0
    } else {
51
0
        ds_put_format(ds, ",ct_tp_src=%u,ct_tp_dst=%u", ntohs(tuple->src_port),
52
0
                      ntohs(tuple->dst_port));
53
0
    }
54
0
}
55
56
static bool
57
ofp_ct_tuple_is_zero(const struct ofp_ct_tuple *tuple, uint8_t ip_proto)
58
0
{
59
0
    bool is_zero = ipv6_is_zero(&tuple->src) && ipv6_is_zero(&tuple->dst);
60
61
0
    if (!(ip_proto == IPPROTO_ICMP || ip_proto == IPPROTO_ICMPV6)) {
62
0
        is_zero = is_zero && !tuple->src_port && !tuple->dst_port;
63
0
    }
64
65
0
    return is_zero;
66
0
}
67
68
static bool
69
ofp_ct_tuple_is_five_tuple(const struct ofp_ct_tuple *tuple, uint8_t ip_proto)
70
0
{
71
    /* First check if we have address. */
72
0
    bool five_tuple = !ipv6_is_zero(&tuple->src) && !ipv6_is_zero(&tuple->dst);
73
74
0
    if (!(ip_proto == IPPROTO_ICMP || ip_proto == IPPROTO_ICMPV6)) {
75
0
        five_tuple = five_tuple && tuple->src_port && tuple->dst_port;
76
0
    }
77
78
0
    return five_tuple;
79
0
}
80
81
bool
82
ofp_ct_match_is_five_tuple(const struct ofp_ct_match *match)
83
0
{
84
0
    return ofp_ct_tuple_is_five_tuple(&match->tuple_orig, match->ip_proto) &&
85
0
           ofp_ct_tuple_is_zero(&match->tuple_reply, match->ip_proto) &&
86
0
           !match->mark_mask && ovs_u128_is_zero(match->labels_mask);
87
0
}
88
89
bool
90
ofp_ct_match_is_zero(const struct ofp_ct_match *match)
91
0
{
92
0
    return !match->ip_proto && !match->l3_type &&
93
0
           ofp_ct_tuple_is_zero(&match->tuple_orig, match->ip_proto) &&
94
0
           ofp_ct_tuple_is_zero(&match->tuple_reply, match->ip_proto) &&
95
0
           !match->mark_mask && ovs_u128_is_zero(match->labels_mask);
96
0
}
97
98
void
99
ofp_ct_match_format(struct ds *ds, const struct ofp_ct_match *match)
100
0
{
101
0
    if (match->mark_mask) {
102
0
        ds_put_format(ds, "mark=%#"PRIx32, match->mark);
103
0
        if (match->mark_mask != UINT32_MAX) {
104
0
            ds_put_format(ds, "/%#"PRIx32, match->mark_mask);
105
0
        }
106
0
        ds_put_char(ds, ' ');
107
0
    }
108
109
0
    if (!ovs_u128_is_zero(match->labels_mask)) {
110
0
        ovs_be128 be_value = hton128(match->labels);
111
0
        ovs_be128 be_mask = hton128(match->labels_mask);
112
113
0
        ds_put_cstr(ds, "labels=");
114
0
        ds_put_hex(ds, &be_value, sizeof be_value);
115
116
0
        if (!ovs_u128_is_ones(match->labels_mask)) {
117
0
            ds_put_char(ds, '/');
118
0
            ds_put_hex(ds, &be_mask, sizeof be_mask);
119
0
        }
120
0
        ds_put_char(ds, ' ');
121
0
    }
122
123
0
    ds_put_cstr(ds, "'");
124
0
    ofp_ct_tuple_format(ds, &match->tuple_orig, match->ip_proto,
125
0
                        match->l3_type);
126
0
    ds_put_format(ds, ",ct_nw_proto=%u' '", match->ip_proto);
127
0
    ofp_ct_tuple_format(ds, &match->tuple_reply, match->ip_proto,
128
0
                        match->l3_type);
129
0
    ds_put_cstr(ds, "'");
130
0
}
131
132
static inline bool
133
ofp_ct_masked_parse(const char *s, uint8_t *val, size_t val_len,
134
                    uint8_t *mask, size_t mask_len)
135
0
{
136
0
    char *tail;
137
0
    if (!parse_int_string(s, val, val_len, &tail)) {
138
0
        if (*tail != '/' || parse_int_string(tail + 1, mask,
139
0
                                             mask_len, &tail)) {
140
0
            memset(mask, UINT8_MAX, mask_len);
141
0
        }
142
143
0
        return true;
144
0
    }
145
146
0
    return false;
147
0
}
148
149
/* Parses a specification of a conntrack 5-tuple from 's' into 'tuple'.
150
 * Returns true on success.  Otherwise, returns false and puts the error
151
 * message in 'ds'. */
152
static bool
153
ofp_ct_tuple_parse(struct ofp_ct_tuple *tuple, const char *s,
154
                   struct ds *ds, uint8_t *ip_proto, uint16_t *l3_type)
155
0
{
156
0
    char *pos, *key, *value, *copy;
157
158
0
    pos = copy = xstrdup(s);
159
0
    while (ofputil_parse_key_value(&pos, &key, &value)) {
160
0
        if (!*value) {
161
0
            ds_put_format(ds, "field %s missing value", key);
162
0
            goto error;
163
0
        }
164
165
0
        if (!strcmp(key, "ct_nw_src") || !strcmp(key, "ct_nw_dst")) {
166
0
            struct in6_addr *addr = key[6] == 's' ? &tuple->src : &tuple->dst;
167
168
0
            if (*l3_type && *l3_type != AF_INET) {
169
0
                ds_put_format(ds ,"the L3 protocol does not match %s", value);
170
0
                goto error;
171
0
            }
172
173
0
            if (!ipv6_is_zero(addr)) {
174
0
                ds_put_format(ds, "%s is set multiple times", key);
175
0
                goto error;
176
0
            }
177
178
0
            ovs_be32 ip = 0;
179
0
            if (!ip_parse(value, &ip)) {
180
0
                goto error_with_msg;
181
0
            }
182
183
0
            *l3_type = AF_INET;
184
0
            *addr = in6_addr_mapped_ipv4(ip);
185
0
        } else if (!strcmp(key, "ct_ipv6_src") ||
186
0
                   !strcmp(key, "ct_ipv6_dst")) {
187
0
            struct in6_addr *addr = key[8] == 's' ? &tuple->src : &tuple->dst;
188
189
0
            if (*l3_type && *l3_type != AF_INET6) {
190
0
                ds_put_format(ds, "the L3 protocol does not match %s", value);
191
0
                goto error;
192
0
            }
193
194
0
            if (!ipv6_is_zero(addr)) {
195
0
                ds_put_format(ds, "%s is set multiple times", key);
196
0
                goto error;
197
0
            }
198
199
200
0
            if (!ipv6_parse(value, addr)) {
201
0
                goto error_with_msg;
202
0
            }
203
204
0
            *l3_type = AF_INET6;
205
0
        } else if (!strcmp(key, "ct_nw_proto")) {
206
0
            if (*ip_proto) {
207
0
                ds_put_format(ds, "%s is set multiple times", key);
208
0
            }
209
0
            char *err = str_to_u8(value, key, ip_proto);
210
211
0
            if (err) {
212
0
                free(err);
213
0
                goto error_with_msg;
214
0
            }
215
0
        } else if (!strcmp(key, "ct_tp_src") || !strcmp(key, "ct_tp_dst")) {
216
0
            uint16_t port;
217
0
            char *err = str_to_u16(value, key, &port);
218
219
0
            if (err) {
220
0
                free(err);
221
0
                goto error_with_msg;
222
0
            }
223
0
            if (key[6] == 's') {
224
0
                tuple->src_port = htons(port);
225
0
            } else {
226
0
                tuple->dst_port = htons(port);
227
0
            }
228
0
        } else if (!strcmp(key, "icmp_type") || !strcmp(key, "icmp_code") ||
229
0
                   !strcmp(key, "icmp_id")) {
230
0
            if (*ip_proto != IPPROTO_ICMP && *ip_proto != IPPROTO_ICMPV6) {
231
0
                ds_put_cstr(ds, "invalid L4 fields");
232
0
                goto error;
233
0
            }
234
0
            uint16_t icmp_id;
235
0
            char *err;
236
237
0
            if (key[5] == 't') {
238
0
                err = str_to_u8(value, key, &tuple->icmp_type);
239
0
            } else if (key[5] == 'c') {
240
0
                err = str_to_u8(value, key, &tuple->icmp_code);
241
0
            } else {
242
0
                err = str_to_u16(value, key, &icmp_id);
243
0
                tuple->icmp_id = htons(icmp_id);
244
0
            }
245
0
            if (err) {
246
0
                free(err);
247
0
                goto error_with_msg;
248
0
            }
249
0
        } else {
250
0
            ds_put_format(ds, "invalid conntrack tuple field: %s", key);
251
0
            goto error;
252
0
        }
253
0
    }
254
255
0
    if (!*ip_proto && (tuple->src_port || tuple->dst_port)) {
256
0
        ds_put_cstr(ds, "port is set without protocol");
257
0
        goto error;
258
0
    }
259
260
0
    free(copy);
261
0
    return true;
262
263
0
error_with_msg:
264
0
    ds_put_format(ds, "failed to parse field %s", key);
265
0
error:
266
0
    free(copy);
267
0
    return false;
268
0
}
269
270
/* Parses a specification of a conntrack match from 'argv' into 'match'.
271
 * Returns true on success. Otherwise, returns false and puts the error
272
 * message in 'ds'. */
273
bool
274
ofp_ct_match_parse(const char **argv, int argc, struct ds *ds,
275
                   struct ofp_ct_match *match, bool *with_zone,
276
                   uint16_t *zone_id)
277
0
{
278
0
    int args = argc;
279
280
    /* Parse zone. */
281
0
    if (args && !strncmp(argv[argc - args], "zone=", 5)) {
282
0
        if (!ovs_scan(argv[argc - args], "zone=%"SCNu16, zone_id)) {
283
0
            ds_put_cstr(ds, "failed to parse zone");
284
0
            return false;
285
0
        }
286
0
        *with_zone = true;
287
0
        args--;
288
0
    }
289
290
    /* Parse mark. */
291
0
    if (args && !strncmp(argv[argc - args], "mark=", 5)) {
292
0
        const char *s = argv[argc - args] + 5;
293
0
        ovs_be32 mark_be;
294
0
        ovs_be32 mask_be;
295
296
0
        if (ofp_ct_masked_parse(s, (uint8_t *) &mark_be, sizeof mark_be,
297
0
                                (uint8_t *) &mask_be, sizeof mask_be)) {
298
0
            match->mark = ntohl(mark_be);
299
0
            match->mark_mask = ntohl(mask_be);
300
0
        } else {
301
0
            ds_put_cstr(ds, "failed to parse mark");
302
0
            return false;
303
0
        }
304
0
        args--;
305
0
    }
306
307
    /* Parse labels. */
308
0
    if (args && !strncmp(argv[argc - args], "labels=", 7)) {
309
0
        const char *s = argv[argc - args] + 7;
310
0
        ovs_be128 labels_be;
311
0
        ovs_be128 mask_be;
312
313
0
        if (ofp_ct_masked_parse(s, (uint8_t *) &labels_be, sizeof labels_be,
314
0
                                 (uint8_t *) &mask_be, sizeof mask_be)) {
315
0
            match->labels = ntoh128(labels_be);
316
0
            match->labels_mask = ntoh128(mask_be);
317
0
        } else {
318
0
            ds_put_cstr(ds, "failed to parse labels");
319
0
            return false;
320
0
        }
321
0
        args--;
322
0
    }
323
324
    /* Parse ct tuples. */
325
0
    for (int i = 0; i < 2; i++) {
326
0
        if (!args) {
327
0
            break;
328
0
        }
329
330
0
        struct ofp_ct_tuple *tuple =
331
0
                i ? &match->tuple_reply : &match->tuple_orig;
332
0
        const char *arg = argv[argc - args];
333
334
0
        if (arg[0] && !ofp_ct_tuple_parse(tuple, arg, ds, &match->ip_proto,
335
0
                                          &match->l3_type)) {
336
0
            return false;
337
0
        }
338
0
        args--;
339
0
    }
340
341
0
    if (args > 0) {
342
0
        ds_put_cstr(ds, "invalid arguments");
343
0
        return false;
344
0
    }
345
346
0
    return true;
347
0
}
348
349
static enum ofperr
350
ofpprop_pull_ipv6(struct ofpbuf *property, struct in6_addr *addr,
351
                  uint16_t *l3_type)
352
0
{
353
0
    if (ofpbuf_msgsize(property) < sizeof *addr) {
354
0
        return OFPERR_OFPBPC_BAD_LEN;
355
0
    }
356
357
0
    memcpy(addr, property->msg, sizeof *addr);
358
359
0
    uint16_t l3 = 0;
360
0
    if (!ipv6_is_zero(addr)) {
361
0
        l3 = IN6_IS_ADDR_V4MAPPED(addr) ? AF_INET : AF_INET6;
362
0
    }
363
364
0
    if (*l3_type && l3 && *l3_type != l3) {
365
0
        return OFPERR_OFPBPC_BAD_VALUE;
366
0
    }
367
368
0
    *l3_type = l3;
369
370
0
    return 0;
371
0
}
372
373
static enum ofperr
374
ofp_ct_tuple_decode_nested(struct ofpbuf *property, struct ofp_ct_tuple *tuple,
375
                           uint16_t *l3_type)
376
0
{
377
0
    struct ofpbuf nested;
378
0
    enum ofperr error = ofpprop_parse_nested(property, &nested);
379
0
    if (error) {
380
0
        return error;
381
0
    }
382
383
0
    while (nested.size) {
384
0
        struct ofpbuf inner;
385
0
        uint64_t type;
386
387
0
        error = ofpprop_pull(&nested, &inner, &type);
388
0
        if (error) {
389
0
            return error;
390
0
        }
391
0
        switch (type) {
392
0
        case NXT_CT_TUPLE_SRC:
393
0
            error = ofpprop_pull_ipv6(&inner, &tuple->src, l3_type);
394
0
            break;
395
396
0
        case NXT_CT_TUPLE_DST:
397
0
            error = ofpprop_pull_ipv6(&inner, &tuple->dst, l3_type);
398
0
            break;
399
400
0
        case NXT_CT_TUPLE_SRC_PORT:
401
0
            error = ofpprop_parse_be16(&inner, &tuple->src_port);
402
0
            break;
403
404
0
        case NXT_CT_TUPLE_DST_PORT:
405
0
            error = ofpprop_parse_be16(&inner, &tuple->dst_port);
406
0
            break;
407
408
0
        case NXT_CT_TUPLE_ICMP_ID:
409
0
            error = ofpprop_parse_be16(&inner, &tuple->icmp_id);
410
0
            break;
411
412
0
        case NXT_CT_TUPLE_ICMP_TYPE:
413
0
            error = ofpprop_parse_u8(&inner, &tuple->icmp_type);
414
0
            break;
415
416
0
        case NXT_CT_TUPLE_ICMP_CODE:
417
0
            error = ofpprop_parse_u8(&inner, &tuple->icmp_code);
418
0
            break;
419
420
0
        default:
421
0
            error = OFPPROP_UNKNOWN(false, "NXT_CT_TUPLE", type);
422
0
            break;
423
0
        }
424
425
0
        if (error) {
426
0
            return error;
427
0
        }
428
0
    }
429
430
0
    return 0;
431
0
}
432
433
static void
434
ofp_ct_tuple_encode(const struct ofp_ct_tuple *tuple, struct ofpbuf *buf,
435
                    enum nx_ct_flush_tlv_type type, uint8_t ip_proto)
436
0
{
437
    /* 128 B is enough to hold the whole tuple. */
438
0
    uint8_t stub[128];
439
0
    struct ofpbuf nested = OFPBUF_STUB_INITIALIZER(stub);
440
441
0
    if (!ipv6_is_zero(&tuple->src)) {
442
0
        ofpprop_put(&nested, NXT_CT_TUPLE_SRC, &tuple->src, sizeof tuple->src);
443
0
    }
444
445
0
    if (!ipv6_is_zero(&tuple->dst)) {
446
0
        ofpprop_put(&nested, NXT_CT_TUPLE_DST, &tuple->dst, sizeof tuple->dst);
447
0
    }
448
449
0
    if (ip_proto == IPPROTO_ICMP || ip_proto == IPPROTO_ICMPV6) {
450
0
        ofpprop_put_be16(&nested, NXT_CT_TUPLE_ICMP_ID, tuple->icmp_id);
451
0
        ofpprop_put_u8(&nested, NXT_CT_TUPLE_ICMP_TYPE, tuple->icmp_type);
452
0
        ofpprop_put_u8(&nested, NXT_CT_TUPLE_ICMP_CODE, tuple->icmp_code);
453
0
    } else {
454
0
        if (tuple->src_port) {
455
0
            ofpprop_put_be16(&nested, NXT_CT_TUPLE_SRC_PORT, tuple->src_port);
456
0
        }
457
458
0
        if (tuple->dst_port) {
459
0
            ofpprop_put_be16(&nested, NXT_CT_TUPLE_DST_PORT, tuple->dst_port);
460
0
        }
461
0
    }
462
463
0
    if (nested.size) {
464
0
        ofpprop_put_nested(buf, type, &nested);
465
0
    }
466
467
0
    ofpbuf_uninit(&nested);
468
0
}
469
470
enum ofperr
471
ofp_ct_match_decode(struct ofp_ct_match *match, bool *with_zone,
472
                    uint16_t *zone_id, const struct ofp_header *oh)
473
0
{
474
0
    uint32_t tlv_flags = 0;
475
0
    struct ofpbuf msg = ofpbuf_const_initializer(oh, ntohs(oh->length));
476
0
    ofpraw_pull_assert(&msg);
477
478
0
    const struct nx_ct_flush *nx_flush = ofpbuf_pull(&msg, sizeof *nx_flush);
479
480
0
    if (!is_all_zeros(nx_flush->pad, sizeof nx_flush->pad)) {
481
0
        return OFPERR_NXBRC_MUST_BE_ZERO;
482
0
    }
483
484
0
    match->ip_proto = nx_flush->ip_proto;
485
486
0
    struct ofp_ct_tuple *orig = &match->tuple_orig;
487
0
    struct ofp_ct_tuple *reply = &match->tuple_reply;
488
489
0
    while (msg.size) {
490
0
        struct ofpbuf property;
491
0
        uint64_t type;
492
493
0
        enum ofperr error = ofpprop_pull(&msg, &property, &type);
494
0
        if (error) {
495
0
            return error;
496
0
        }
497
498
0
        switch (type) {
499
0
        case NXT_CT_ORIG_TUPLE:
500
0
            error = ofp_ct_tuple_decode_nested(&property, orig,
501
0
                                               &match->l3_type);
502
0
            break;
503
504
0
        case NXT_CT_REPLY_TUPLE:
505
0
            error = ofp_ct_tuple_decode_nested(&property, reply,
506
0
                                               &match->l3_type);
507
0
            break;
508
509
0
        case NXT_CT_ZONE_ID:
510
0
            if (with_zone) {
511
0
                *with_zone = true;
512
0
            }
513
0
            error = ofpprop_parse_u16(&property, zone_id);
514
0
            break;
515
516
0
        case NXT_CT_MARK:
517
0
            error = ofpprop_parse_u32(&property, &match->mark);
518
0
            break;
519
520
0
        case NXT_CT_MARK_MASK:
521
0
            error = ofpprop_parse_u32(&property, &match->mark_mask);
522
0
            break;
523
524
0
        case NXT_CT_LABELS:
525
0
            error = ofpprop_parse_u128(&property, &match->labels);
526
0
            break;
527
528
0
        case NXT_CT_LABELS_MASK:
529
0
            error = ofpprop_parse_u128(&property, &match->labels_mask);
530
0
            break;
531
532
0
        default:
533
0
            error = OFPPROP_UNKNOWN(false, "NXT_CT_FLUSH", type);
534
0
            break;
535
0
        }
536
537
0
        if (error) {
538
0
            return error;
539
0
        }
540
541
0
        if (type < (sizeof tlv_flags * CHAR_BIT)) {
542
0
            tlv_flags |= (UINT32_C(1) << type);
543
0
        }
544
0
    }
545
546
    /* Consider the mask being all ones if it's not present but the value
547
     * is specified. */
548
0
    if (tlv_flags & (UINT32_C(1) << NXT_CT_MARK) &&
549
0
        !(tlv_flags & (UINT32_C(1) << NXT_CT_MARK_MASK))) {
550
0
        match->mark_mask = UINT32_MAX;
551
0
    }
552
553
0
    if (tlv_flags & (UINT32_C(1) << NXT_CT_LABELS) &&
554
0
        !(tlv_flags & (UINT32_C(1) << NXT_CT_LABELS_MASK))) {
555
0
        match->labels_mask = OVS_U128_MAX;
556
0
    }
557
558
0
    return 0;
559
0
}
560
561
struct ofpbuf *
562
ofp_ct_match_encode(const struct ofp_ct_match *match, uint16_t *zone_id,
563
                    enum ofp_version version)
564
0
{
565
0
    struct ofpbuf *msg = ofpraw_alloc(OFPRAW_NXT_CT_FLUSH, version, 0);
566
0
    struct nx_ct_flush *nx_flush = ofpbuf_put_zeros(msg, sizeof *nx_flush);
567
0
    const struct ofp_ct_tuple *orig = &match->tuple_orig;
568
0
    const struct ofp_ct_tuple *reply = &match->tuple_reply;
569
570
0
    nx_flush->ip_proto = match->ip_proto;
571
572
0
    ofp_ct_tuple_encode(orig, msg, NXT_CT_ORIG_TUPLE,match->ip_proto);
573
0
    ofp_ct_tuple_encode(reply, msg, NXT_CT_REPLY_TUPLE, match->ip_proto);
574
575
0
    if (zone_id) {
576
0
        ofpprop_put_u16(msg, NXT_CT_ZONE_ID, *zone_id);
577
0
    }
578
579
0
    if (match->mark_mask) {
580
0
        ofpprop_put_u32(msg, NXT_CT_MARK, match->mark);
581
0
        if (match->mark_mask != UINT32_MAX) {
582
0
            ofpprop_put_u32(msg, NXT_CT_MARK_MASK, match->mark_mask);
583
0
        }
584
0
    }
585
586
0
    if (!ovs_u128_is_zero(match->labels_mask)) {
587
0
        ofpprop_put_u128(msg, NXT_CT_LABELS, match->labels);
588
0
        if (!ovs_u128_is_ones(match->labels_mask)) {
589
0
            ofpprop_put_u128(msg, NXT_CT_LABELS_MASK, match->labels_mask);
590
0
        }
591
0
    }
592
593
0
    return msg;
594
0
}