root/net/netfilter/nf_conntrack_labels.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 * test/set flag bits stored in conntrack extension area.
 *
 * (C) 2013 Astaro GmbH & Co KG
 */

#include <linux/export.h>
#include <linux/types.h>

#include <net/netfilter/nf_conntrack_ecache.h>
#include <net/netfilter/nf_conntrack_labels.h>

static int replace_u32(u32 *address, u32 mask, u32 new)
{
        u32 old, tmp;

        do {
                old = *address;
                tmp = (old & mask) ^ new;
                if (old == tmp)
                        return 0;
        } while (cmpxchg(address, old, tmp) != old);

        return 1;
}

int nf_connlabels_replace(struct nf_conn *ct,
                          const u32 *data,
                          const u32 *mask, unsigned int words32)
{
        struct nf_conn_labels *labels;
        unsigned int size, i;
        int changed = 0;
        u32 *dst;

        labels = nf_ct_labels_find(ct);
        if (!labels)
                return -ENOSPC;

        size = sizeof(labels->bits);
        if (size < (words32 * sizeof(u32)))
                words32 = size / sizeof(u32);

        dst = (u32 *) labels->bits;
        for (i = 0; i < words32; i++)
                changed |= replace_u32(&dst[i], mask ? ~mask[i] : 0, data[i]);

        size /= sizeof(u32);
        for (i = words32; i < size; i++) /* pad */
                replace_u32(&dst[i], 0, 0);

        if (changed)
                nf_conntrack_event_cache(IPCT_LABEL, ct);
        return 0;
}
EXPORT_SYMBOL_GPL(nf_connlabels_replace);

int nf_connlabels_get(struct net *net, unsigned int bits)
{
        int v;

        if (BIT_WORD(bits) >= NF_CT_LABELS_MAX_SIZE / sizeof(long))
                return -ERANGE;

        BUILD_BUG_ON(NF_CT_LABELS_MAX_SIZE / sizeof(long) >= U8_MAX);

        v = atomic_inc_return_relaxed(&net->ct.labels_used);
        WARN_ON_ONCE(v <= 0);

        return 0;
}
EXPORT_SYMBOL_GPL(nf_connlabels_get);

void nf_connlabels_put(struct net *net)
{
        int v = atomic_dec_return_relaxed(&net->ct.labels_used);

        WARN_ON_ONCE(v < 0);
}
EXPORT_SYMBOL_GPL(nf_connlabels_put);