/* SPDX-License-Identifier: LGPL-2.1-only */
/*
 * Copyright (c) 2018 Wang Jian <jianjian.wang1@gmail.com>
 */

/**
 * @ingroup link
 * @defgroup geneve Geneve
 * Generic Network Virtualization Encapsulation
 *
 * @details
 * \b Link Type Name: "geneve"
 *
 * @route_doc{link_geneve, Geneve Documentation}
 *
 * @{
 */
#include <netlink-private/netlink.h>
#include <netlink/netlink.h>
#include <netlink/utils.h>
#include <netlink/object.h>
#include <netlink/route/rtnl.h>
#include <netlink-private/route/link/api.h>
#include <netlink/route/link/geneve.h>


/** @cond SKIP */
#define GENEVE_ATTR_ID          (1<<0)
#define GENEVE_ATTR_REMOTE      (1<<1)
#define GENEVE_ATTR_REMOTE6     (1<<2)
#define GENEVE_ATTR_TTL         (1<<3)
#define GENEVE_ATTR_TOS         (1<<4)
#define GENEVE_ATTR_LABEL       (1<<5)
#define GENEVE_ATTR_PORT        (1<<6)
#define GENEVE_ATTR_FLAGS       (1<<7)
#define GENEVE_ATTR_UDP_CSUM    (1<<8)
#define GENEVE_ATTR_UDP_ZERO_CSUM6_TX   (1<<9)
#define GENEVE_ATTR_UDP_ZERO_CSUM6_RX   (1<<10)

struct geneve_info
{
        uint32_t        id;
        uint32_t        remote;
        struct in6_addr remote6;
        uint8_t         ttl;
        uint8_t         tos;
        uint32_t        label;
        uint16_t        port;
        uint8_t         flags;
        uint8_t         udp_csum;
        uint8_t         udp_zero_csum6_tx;
        uint8_t         udp_zero_csum6_rx;
        uint32_t        mask;
};

/** @endcond */

static struct nla_policy geneve_policy[IFLA_GENEVE_MAX + 1] = {
        [IFLA_GENEVE_ID] = { .type = NLA_U32 },
        [IFLA_GENEVE_REMOTE]    = { .minlen = sizeof(uint32_t) },
        [IFLA_GENEVE_REMOTE6]   = { .minlen = sizeof(struct in6_addr) },
        [IFLA_GENEVE_TTL]       = { .type = NLA_U8 },
        [IFLA_GENEVE_TOS]       = { .type = NLA_U8 },
        [IFLA_GENEVE_LABEL]     = { .type = NLA_U32 },
        [IFLA_GENEVE_PORT]      = { .type = NLA_U16 },
        [IFLA_GENEVE_COLLECT_METADATA]  = { .type = NLA_FLAG },
        [IFLA_GENEVE_UDP_CSUM]  = { .type = NLA_U8 },
        [IFLA_GENEVE_UDP_ZERO_CSUM6_TX] = { .type = NLA_U8 },
        [IFLA_GENEVE_UDP_ZERO_CSUM6_RX] = { .type = NLA_U8 },
};

static int geneve_alloc(struct rtnl_link *link)
{
        struct geneve_info *geneve;

        if (link->l_info)
                memset(link->l_info, 0, sizeof(*geneve));
        else {
                if ((geneve = calloc(1, sizeof(*geneve))) == NULL)
                                return -NLE_NOMEM;
                link->l_info = geneve;
        }

        return 0;
}

static int geneve_parse(struct rtnl_link *link, struct nlattr *data,
                        struct nlattr *xstats)
{
        struct nlattr *tb[IFLA_GENEVE_MAX + 1];
        struct geneve_info *geneve;
        int err = 0;

        NL_DBG(3, "Parsing Geneve link info\n");

        err = nla_parse_nested(tb, IFLA_GENEVE_MAX, data, geneve_policy);
        if (err < 0)
                return err;

        err = geneve_alloc(link);
        if (err < 0)
                return err;

        geneve = link->l_info;

        if (tb[IFLA_GENEVE_ID]) {
                geneve->id = nla_get_u32(tb[IFLA_GENEVE_ID]);
                geneve->mask |= GENEVE_ATTR_ID;
        }

        if (tb[IFLA_GENEVE_REMOTE]) {
                nla_memcpy(&geneve->remote, tb[IFLA_GENEVE_REMOTE],
                                sizeof(geneve->remote));
                geneve->mask |= GENEVE_ATTR_REMOTE;
                geneve->mask &= ~GENEVE_ATTR_REMOTE6;
        }
        if (tb[IFLA_GENEVE_REMOTE6]) {
                nla_memcpy(&geneve->remote6, tb[IFLA_GENEVE_REMOTE6],
                                sizeof(geneve->remote6));
                geneve->mask |= GENEVE_ATTR_REMOTE6;
                geneve->mask &= ~GENEVE_ATTR_REMOTE;
        }

        if (tb[IFLA_GENEVE_TTL]) {
                geneve->ttl = nla_get_u8(tb[IFLA_GENEVE_TTL]);
                geneve->mask |= GENEVE_ATTR_TTL;
        }

        if (tb[IFLA_GENEVE_TOS]) {
                geneve->tos = nla_get_u8(tb[IFLA_GENEVE_TOS]);
                geneve->mask |= GENEVE_ATTR_TOS;
        }

        if (tb[IFLA_GENEVE_LABEL]) {
                geneve->label = nla_get_u32(tb[IFLA_GENEVE_LABEL]);
                geneve->mask |= GENEVE_ATTR_LABEL;
        }

        if (tb[IFLA_GENEVE_PORT]) {
                geneve->port  = nla_get_u16(tb[IFLA_GENEVE_PORT]);
                geneve->mask |= GENEVE_ATTR_PORT;
        }

        if (tb[IFLA_GENEVE_COLLECT_METADATA])
                geneve->flags |= RTNL_LINK_GENEVE_F_COLLECT_METADATA;

        if (tb[IFLA_GENEVE_UDP_CSUM]) {
                geneve->udp_csum = nla_get_u8(tb[IFLA_GENEVE_UDP_CSUM]);
                geneve->mask |= GENEVE_ATTR_UDP_CSUM;
        }

        if (tb[IFLA_GENEVE_UDP_ZERO_CSUM6_TX]) {
                geneve->udp_zero_csum6_tx = nla_get_u8(tb[IFLA_GENEVE_UDP_ZERO_CSUM6_TX]);
                geneve->mask |= GENEVE_ATTR_UDP_ZERO_CSUM6_TX;
        }

        if (tb[IFLA_GENEVE_UDP_ZERO_CSUM6_RX]) {
                geneve->udp_zero_csum6_rx = nla_get_u8(tb[IFLA_GENEVE_UDP_ZERO_CSUM6_RX]);
                geneve->mask |= GENEVE_ATTR_UDP_ZERO_CSUM6_RX;
        }

        return err;
}

static void geneve_free(struct rtnl_link *link)
{
        struct geneve_info *geneve = link->l_info;

        free(geneve);
        link->l_info = NULL;
}

static void geneve_dump_line(struct rtnl_link *link, struct nl_dump_params *p)
{
        struct geneve_info *geneve = link->l_info;

        nl_dump(p, "geneve-id %u", geneve->id);
}

static void geneve_dump_details(struct rtnl_link *link, struct nl_dump_params *p)
{
        struct geneve_info *geneve = link->l_info;
        char addr[INET6_ADDRSTRLEN];

        nl_dump_line(p, "    geneve-id %u\n", geneve->id);

        if (geneve->mask & GENEVE_ATTR_REMOTE) {
                nl_dump(p, "     remote ");
                nl_dump_line(p, "%s\n",
                             _nl_inet_ntop(AF_INET, &geneve->remote, addr));
        } else if (geneve->mask & GENEVE_ATTR_REMOTE6) {
                nl_dump(p, "      remote ");
                nl_dump_line(p, "%s\n",
                             _nl_inet_ntop(AF_INET6, &geneve->remote6, addr));
        }

        if (geneve->mask & GENEVE_ATTR_TTL) {
                nl_dump(p, "      ttl ");
                nl_dump_line(p, "%u\n", geneve->ttl);
        }

        if (geneve->mask & GENEVE_ATTR_TOS) {
                nl_dump(p, "      tos ");
                nl_dump_line(p, "%u\n", geneve->tos);
        }

        if (geneve->mask & GENEVE_ATTR_PORT) {
                nl_dump(p, "      port ");
                nl_dump_line(p, "%u\n", ntohs(geneve->port));
        }

        if (geneve->mask & GENEVE_ATTR_LABEL) {
                nl_dump(p, "      label ");
                nl_dump_line(p, "%u\n", ntohl(geneve->label));
        }

        if (geneve->mask & GENEVE_ATTR_UDP_CSUM) {
                nl_dump(p, "      UDP checksum ");
                if (geneve->udp_csum)
                        nl_dump_line(p, "enabled (%#x)\n", geneve->udp_csum);
                else
                        nl_dump_line(p, "disabled\n");
        }

        if (geneve->mask & GENEVE_ATTR_UDP_ZERO_CSUM6_TX) {
                nl_dump(p, "      udp-zero-csum6-tx ");
                if (geneve->udp_zero_csum6_tx)
                        nl_dump_line(p, "enabled (%#x)\n", geneve->udp_zero_csum6_tx);
                else
                        nl_dump_line(p, "disabled\n");
        }

        if (geneve->mask & GENEVE_ATTR_UDP_ZERO_CSUM6_RX) {
                nl_dump(p, "      udp-zero-csum6-rx ");
                if (geneve->udp_zero_csum6_rx)
                        nl_dump_line(p, "enabled (%#x)\n", geneve->udp_zero_csum6_rx);
                else
                        nl_dump_line(p, "disabled\n");
        }

        if (geneve->flags & RTNL_LINK_GENEVE_F_COLLECT_METADATA)
                nl_dump(p, "      collect-metadata\n");
}

static int geneve_clone(struct rtnl_link *dst, struct rtnl_link *src)
{
        struct geneve_info *gdst, *gsrc;
        int err;

        gsrc = src->l_info;
        dst->l_info = NULL;
        err = rtnl_link_set_type(dst, "geneve");
        if (err < 0)
                return err;

        gdst = dst->l_info;

        if (!gsrc || !gdst)
                return -NLE_NOMEM;

        memcpy(gdst, gsrc, sizeof(struct geneve_info));

        return 0;
}

static int geneve_put_attrs(struct nl_msg *msg, struct rtnl_link *link)
{
        struct geneve_info  *geneve = link->l_info;
        struct nlattr *data;

        if (!(data = nla_nest_start(msg, IFLA_INFO_DATA)))
                return -NLE_MSGSIZE;

        if (geneve->mask & GENEVE_ATTR_ID)
                NLA_PUT_U32(msg, IFLA_GENEVE_ID, geneve->id);

        if (geneve->mask & GENEVE_ATTR_REMOTE)
                NLA_PUT(msg, IFLA_GENEVE_REMOTE,
                                sizeof(geneve->remote), &geneve->remote);

        if (geneve->mask & GENEVE_ATTR_REMOTE6)
                NLA_PUT(msg, IFLA_GENEVE_REMOTE6,
                                sizeof(geneve->remote6), &geneve->remote6);

        if (geneve->mask & GENEVE_ATTR_TTL)
                NLA_PUT_U8(msg, IFLA_GENEVE_TTL, geneve->ttl);

        if (geneve->mask & GENEVE_ATTR_TOS)
                NLA_PUT_U8(msg, IFLA_GENEVE_TOS, geneve->tos);

        if (geneve->mask & GENEVE_ATTR_LABEL)
                NLA_PUT_U32(msg, IFLA_GENEVE_LABEL, geneve->label);

        if (geneve->mask & GENEVE_ATTR_PORT)
                NLA_PUT_U32(msg, IFLA_GENEVE_PORT, geneve->port);

        if (geneve->mask & GENEVE_ATTR_UDP_CSUM)
                NLA_PUT_U8(msg, IFLA_GENEVE_UDP_CSUM, geneve->udp_csum);

        if (geneve->mask & GENEVE_ATTR_UDP_ZERO_CSUM6_TX)
                NLA_PUT_U8(msg, IFLA_GENEVE_UDP_ZERO_CSUM6_TX, geneve->udp_zero_csum6_tx);

        if (geneve->mask & GENEVE_ATTR_UDP_ZERO_CSUM6_RX)
                NLA_PUT_U8(msg, IFLA_GENEVE_UDP_ZERO_CSUM6_RX, geneve->udp_zero_csum6_rx);

        if (geneve->flags & RTNL_LINK_GENEVE_F_COLLECT_METADATA)
                NLA_PUT_FLAG(msg, IFLA_GENEVE_COLLECT_METADATA);

        nla_nest_end(msg, data);

nla_put_failure:

        return 0;
}

static struct rtnl_link_info_ops geneve_info_ops = {
        .io_name        = "geneve",
        .io_alloc       = geneve_alloc,
        .io_parse       = geneve_parse,
        .io_dump        = {
                [NL_DUMP_LINE]          = geneve_dump_line,
                [NL_DUMP_DETAILS]       = geneve_dump_details,
        },
        .io_clone       = geneve_clone,
        .io_put_attrs   = geneve_put_attrs,
        .io_free        = geneve_free,
};


/** @cond SKIP */
#define IS_GENEVE_LINK_ASSERT(link) \
        if ((link)->l_info_ops != &geneve_info_ops) { \
                APPBUG("Link is not a geneve link. set type \"geneve\" first."); \
                return -NLE_OPNOTSUPP; \
        }
/** @endcond */

/**
 * @name Geneve Object
 * @{
 */

/**
 * Allocate link object of type Geneve
 *
 * @return Allocated link object or NULL.
 */
struct rtnl_link *rtnl_link_geneve_alloc(void)
{
        struct rtnl_link *link;

        if (!(link = rtnl_link_alloc()))
                return NULL;

        if (rtnl_link_set_type(link, "geneve") < 0) {
                rtnl_link_put(link);
                return NULL;
        }

        return link;
}

/**
 * Check if link is a Geneve link
 * @arg link    Link object
 *
 * @return True if link is a Geneve link, otherwisee false is returned.
 */
int rtnl_link_is_geneve(struct rtnl_link *link)
{
        return link->l_info_ops && !strcmp(link->l_info_ops->io_name, "geneve");
}

/**
 * Set Geneve Network Indentifier
 * @arg link    Link object
 * @arg id      Geneve network identifier
 *
 * @return 0 on success or a negative error code
 */
int rtnl_link_geneve_set_id(struct rtnl_link *link, uint32_t id)
{
        struct geneve_info *geneve = link->l_info;

        IS_GENEVE_LINK_ASSERT(link);

        if (id > RTNL_GENEVE_ID_MAX)
                return -NLE_INVAL;

        geneve->id = id;
        geneve->mask |= GENEVE_ATTR_ID;

        return 0;
}

/**
 * Get Geneve Network Identifier
 * @arg link    Link object
 * @arg id      Pointer to store network identifier
 *
 * @return 0 on success or a negative error code
 */
int rtnl_link_geneve_get_id(struct rtnl_link *link, uint32_t *id)
{
        struct geneve_info *geneve = link->l_info;

        IS_GENEVE_LINK_ASSERT(link);

        if (!id)
                return -NLE_INVAL;

        if (geneve->mask & GENEVE_ATTR_ID)
                *id = geneve->id;
        else
                return -NLE_AGAIN;

        return 0;
}

/**
 * Set Geneve unicast destination IP address
 * @arg link    Link object
 * @arg addr    The unicast destination IP address
 *
 * @return 0 on success or a negative error code
 */
int rtnl_link_geneve_set_remote(struct rtnl_link *link, struct nl_addr *addr)
{
        struct geneve_info *geneve = link->l_info;

        IS_GENEVE_LINK_ASSERT(link);

        if ((nl_addr_get_family(addr) == AF_INET) &&
                (nl_addr_get_len(addr) == sizeof(geneve->remote))) {
                memcpy(&geneve->remote, nl_addr_get_binary_addr(addr),
                                sizeof(geneve->remote));
                geneve->mask |= GENEVE_ATTR_REMOTE;
                geneve->mask &= ~GENEVE_ATTR_REMOTE6;
        } else if ((nl_addr_get_family(addr) == AF_INET6) &&
                        (nl_addr_get_len(addr) == sizeof(geneve->remote6))) {
                memcpy(&geneve->remote6, nl_addr_get_binary_addr(addr),
                                sizeof(geneve->remote6));
                geneve->mask |= GENEVE_ATTR_REMOTE6;
                geneve->mask &= ~GENEVE_ATTR_REMOTE;
        } else
                return -NLE_INVAL;

        return 0;
}

/**
 * Get Geneve unicast destination IP address
 * @arg link    Link object
 * @arg addr    Pointer to store unicast destination IP addree
 *
 * @return 0 on success or a a negative error code
 */
int rtnl_link_geneve_get_remote(struct rtnl_link *link, struct nl_addr **addr)
{
        struct geneve_info *geneve = link->l_info;

        IS_GENEVE_LINK_ASSERT(link);

        if (!addr)
                return -NLE_INVAL;

        if (geneve->mask & GENEVE_ATTR_REMOTE)
                *addr = nl_addr_build(AF_INET, &geneve->remote, sizeof(geneve->remote));
        else if (geneve->mask & GENEVE_ATTR_REMOTE6)
                *addr = nl_addr_build(AF_INET6, &geneve->remote6, sizeof(geneve->remote6));
        else
                return -NLE_AGAIN;

        return 0;
}

/**
 * Set IP TTL value to use for Geneve
 * @arg link    Link object
 * @arg ttl     TTL value
 *
 * @return 0 on success or a negative error code
 */
int rtnl_link_geneve_set_ttl(struct rtnl_link *link, uint8_t ttl)
{
        struct geneve_info *geneve = link->l_info;

        IS_GENEVE_LINK_ASSERT(link);

        geneve->ttl = ttl;
        geneve->mask |= GENEVE_ATTR_TTL;

        return 0;
}

/**
 * Get IP TTL value to use for Geneve
 * @arg link    Link object
 *
 * @return TTL value on success or a negative error code
 */
int rtnl_link_geneve_get_ttl(struct rtnl_link *link)
{
        struct geneve_info *geneve = link->l_info;

        IS_GENEVE_LINK_ASSERT(link);

        if (!(geneve->mask & GENEVE_ATTR_TTL))
                return -NLE_AGAIN;

        return geneve->ttl;
}

/**
 * Set IP ToS value to use for Geneve
 * @arg link    Link object
 * @arg tos     ToS value
 *
 * @return 0 on success or a negative error code
 */
int rtnl_link_geneve_set_tos(struct rtnl_link *link, uint8_t tos)
{
        struct geneve_info *geneve = link->l_info;

        IS_GENEVE_LINK_ASSERT(link);

        geneve->tos = tos;
        geneve->mask |= GENEVE_ATTR_TOS;

        return 0;
}

/**
 * Get IP ToS value to use for Geneve
 * @arg link    Link object
 *
 * @return ToS value on success or a negative error code
 */
int rtnl_link_geneve_get_tos(struct rtnl_link *link)
{
        struct geneve_info *geneve = link->l_info;

        IS_GENEVE_LINK_ASSERT(link);

        if (!(geneve->mask & GENEVE_ATTR_TOS))
                return -NLE_AGAIN;

        return geneve->tos;
}

/**
 * Set UDP destination port to use for Geneve
 * @arg link    Link object
 * @arg port    Destination port
 *
 * @return 0 on success or a negative error code
 */

int rtnl_link_geneve_set_port(struct rtnl_link *link,  uint32_t port)
{
        struct geneve_info *geneve = link->l_info;

        IS_GENEVE_LINK_ASSERT(link);

        geneve->port = htons(port);
        geneve->mask |= GENEVE_ATTR_PORT;

        return 0;
}

/**
 * Get UDP destination port to use for Geneve
 * @arg link    Link object
 * @arg port    Pointer to store destination port
 *
 * @return 0 on success or a negative error code
 */
int rtnl_link_geneve_get_port(struct rtnl_link *link, uint32_t *port)
{
        struct geneve_info *geneve = link->l_info;

        IS_GENEVE_LINK_ASSERT(link);

        if (!port)
                return -NLE_INVAL;

        if (!(geneve->mask & GENEVE_ATTR_PORT))
                return -NLE_NOATTR;

        *port = ntohs(geneve->port);

        return 0;
}

/**
 * Set flow label to use for Geneve
 * @arg link    Link object
 * @arg label   Destination label
 *
 * @return 0 on success or a negative error code
 */
int rtnl_link_geneve_set_label(struct rtnl_link *link, uint32_t label)
{
        struct geneve_info *geneve = link->l_info;

        IS_GENEVE_LINK_ASSERT(link);

        geneve->label = htonl(label);
        geneve->mask |= GENEVE_ATTR_LABEL;

        return 0;
}

/**
 * Get flow label to use for Geneve
 * @arg link    Link object
 * @arg label   Pointer to store destination label
 *
 * @return 0 on success or a negative error code
 */
int rtnl_link_geneve_get_label(struct rtnl_link *link, uint32_t *label)
{
        struct geneve_info *geneve = link->l_info;

        IS_GENEVE_LINK_ASSERT(link);

        if (!label)
                return -NLE_INVAL;
        if (!(geneve->mask & GENEVE_ATTR_LABEL))
                return -NLE_NOATTR;

        *label = ntohl(geneve->label);

        return 0;
}

/**
 * Set UDP checksum status to use for Geneve
 * @arg link    Link object
 * @arg csum    Status value
 *
 * @return 0 on success or a negative error code
 */
int rtnl_link_geneve_set_udp_csum(struct rtnl_link *link, uint8_t csum)
{
        struct geneve_info *geneve = link->l_info;

        IS_GENEVE_LINK_ASSERT(link);

        geneve->udp_csum = csum;
        geneve->mask |= GENEVE_ATTR_UDP_CSUM;

        return 0;
}

/**
 * Get UDP checksum status to use for Geneve
 * @arg link    Link object
 *
 * @return status value on success or a negative error code
 */
int rtnl_link_geneve_get_udp_csum(struct rtnl_link *link)
{
        struct geneve_info *geneve = link->l_info;

        IS_GENEVE_LINK_ASSERT(link);

        if (!(geneve->mask & GENEVE_ATTR_UDP_CSUM))
                return -NLE_NOATTR;

        return geneve->udp_csum;
}

/**
 * Set skip UDP checksum transmitted over IPv6 status to use for Geneve
 * @arg link    Link object
 * @arg csum    Status value
 *
 * @return 0 on success or a negative error code
 */
int rtnl_link_geneve_set_udp_zero_csum6_tx(struct rtnl_link *link, uint8_t csum)
{
        struct geneve_info *geneve = link->l_info;

        IS_GENEVE_LINK_ASSERT(link);

        geneve->udp_zero_csum6_tx = csum;
        geneve->mask |= GENEVE_ATTR_UDP_ZERO_CSUM6_TX;

        return 0;
}

/**
 * Get skip UDP checksum transmitted over IPv6 status to use for Geneve
 * @arg link    Link object
 *
 * @return Status value on success or a negative error code
 */
int rtnl_link_geneve_get_udp_zero_csum6_tx(struct rtnl_link *link)
{
        struct geneve_info *geneve = link->l_info;

        IS_GENEVE_LINK_ASSERT(link);

        if (!(geneve->mask & GENEVE_ATTR_UDP_ZERO_CSUM6_TX))
                return -NLE_NOATTR;

        return geneve->udp_zero_csum6_tx;
}

/**
 * Set skip UDP checksum received over IPv6 status to use for Geneve
 * @arg link    Link object
 * @arg csum    Status value
 *
 * @return 0 on success or a negative error code
 */
int rtnl_link_geneve_set_udp_zero_csum6_rx(struct rtnl_link *link, uint8_t csum)
{
        struct geneve_info *geneve = link->l_info;

        IS_GENEVE_LINK_ASSERT(link);

        geneve->udp_zero_csum6_rx = csum;
        geneve->mask |= GENEVE_ATTR_UDP_ZERO_CSUM6_RX;

        return 0;
}

/**
 * Get skip UDP checksum received over IPv6  status to use for Geneve
 * @arg link    Link object
 *
 * @return Status value on success or a negative error code
 */
int rtnl_link_geneve_get_udp_zero_csum6_rx(struct rtnl_link *link)
{
        struct geneve_info *geneve = link->l_info;

        IS_GENEVE_LINK_ASSERT(link);

        if (!(geneve->mask & GENEVE_ATTR_UDP_ZERO_CSUM6_RX))
                return -NLE_NOATTR;

        return geneve->udp_zero_csum6_rx;
}

/**
 * Set Geneve flags
 * @arg link    Link object
 * @arg flags   Which flags to set
 * @arg enable  Boolean enabling or disabling flag
 *
 * @return 0 on success or a negative error code
 */
int rtnl_link_geneve_set_flags(struct rtnl_link *link, uint8_t flags, int enable)
{
        struct geneve_info *geneve = link->l_info;

        IS_GENEVE_LINK_ASSERT(link);

        if (flags & ~RTNL_LINK_GENEVE_F_COLLECT_METADATA)
                return -NLE_INVAL;

        if (enable)
                geneve->flags = flags;
        else
                geneve->flags &= ~flags;

        return 0;
}

/**
 * Get Geneve flags
 * @arg link    Link object
 * @arg flags   Pointer to store flags
 *
 * @return 0 on success or a negative error code
 */
int rtnl_link_geneve_get_flags(struct rtnl_link *link, uint8_t *flags)
{
        struct geneve_info *geneve = link->l_info;

        IS_GENEVE_LINK_ASSERT(link);

        *flags = geneve->flags;
        return 0;
}

/** @} */
static void __init geneve_init(void)
{
        rtnl_link_register_info(&geneve_info_ops);
}

static void __exit geneve_exit(void)
{
        rtnl_link_unregister_info(&geneve_info_ops);
}

/** @} */
