diff options
author | Jo-Philipp Wich <jo@mein.io> | 2021-08-03 16:53:58 +0200 |
---|---|---|
committer | Jo-Philipp Wich <jo@mein.io> | 2021-09-15 20:33:22 +0200 |
commit | cbae3cba1964ebf367dd84524c6403a308302ace (patch) | |
tree | a48e590d190cf0a78b784c3ff337247fdf6faf05 /lib/rtnl.c | |
parent | e6dd389d06d9d33195afcf77a740df072298959e (diff) |
lib: introduce Linux route netlink binding
Signed-off-by: Jo-Philipp Wich <jo@mein.io>
Diffstat (limited to 'lib/rtnl.c')
-rw-r--r-- | lib/rtnl.c | 3524 |
1 files changed, 3524 insertions, 0 deletions
diff --git a/lib/rtnl.c b/lib/rtnl.c new file mode 100644 index 0000000..2644aac --- /dev/null +++ b/lib/rtnl.c @@ -0,0 +1,3524 @@ +/* +Copyright 2021 Jo-Philipp Wich <jo@mein.io> + +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 <stdio.h> +#include <stdint.h> +#include <stdbool.h> +#include <stdarg.h> +#include <unistd.h> +#include <errno.h> +#include <string.h> +#include <limits.h> +#include <math.h> +#include <assert.h> +#include <endian.h> + +#include <netinet/ether.h> +#include <arpa/inet.h> +#include <netlink/msg.h> +#include <netlink/attr.h> +#include <netlink/socket.h> + +#include <linux/rtnetlink.h> +#include <linux/if_tunnel.h> +#include <linux/ip6_tunnel.h> +#include <linux/lwtunnel.h> +#include <linux/mpls.h> +#include <linux/mpls_iptunnel.h> +#include <linux/seg6.h> +#include <linux/seg6_iptunnel.h> +#include <linux/seg6_hmac.h> +#include <linux/veth.h> +#include <linux/ila.h> +#include <linux/fib_rules.h> +#include <linux/if_addrlabel.h> +#include <linux/if_bridge.h> +#include <linux/netconf.h> + +#include "ucode/module.h" + +#define err_return(code, ...) do { set_error(code, __VA_ARGS__); return NULL; } while(0) + +#define NLM_F_STRICT_CHK (1 << 15) + +/* Can't use net/if.h for declarations as it clashes with linux/if.h + * on certain musl versions. + * Ref: https://www.openwall.com/lists/musl/2017/04/16/1 */ +extern unsigned int if_nametoindex (const char *); +extern char *if_indextoname (unsigned int ifindex, char *ifname); + +static struct { + int code; + char *msg; +} last_error; + +__attribute__((format(printf, 2, 3))) static void +set_error(int errcode, const char *fmt, ...) { + va_list ap; + + free(last_error.msg); + + last_error.code = errcode; + last_error.msg = NULL; + + if (fmt) { + va_start(ap, fmt); + vasprintf(&last_error.msg, fmt, ap); + va_end(ap); + } +} + +typedef struct { + uint8_t family; + uint8_t mask; + uint8_t alen; + uint8_t bitlen; + union { + struct in_addr in; + struct in6_addr in6; + struct mpls_label mpls[16]; + } addr; +} uc_nl_cidr_t; + +static bool +uc_nl_parse_u32(uc_value_t *val, uint32_t *n) +{ + uc_type_t t; + int64_t i; + double d; + + t = ucv_cast_number(val, &i, &d); + + if (t == UC_DOUBLE) { + if (isnan(d) || d < 0 || d > UINT32_MAX) + return false; + + i = (int64_t)d; + + if (d - i > 0) + return false; + } + else if (errno != 0) { + return false; + } + + if (i < 0 || i > UINT32_MAX) + return false; + + *n = (uint32_t)i; + + return true; +} + +static bool +uc_nl_parse_s32(uc_value_t *val, uint32_t *n) +{ + uc_type_t t; + int64_t i; + double d; + + t = ucv_cast_number(val, &i, &d); + + if (t == UC_DOUBLE) { + if (isnan(d) || d < INT32_MIN || d > INT32_MAX) + return false; + + i = (int64_t)d; + + if (d - i > 0) + return false; + } + else if (errno != 0) { + return false; + } + + if (i < INT32_MIN || i > INT32_MAX) + return false; + + *n = (uint32_t)i; + + return true; +} + +static bool +uc_nl_parse_u64(uc_value_t *val, uint64_t *n) +{ + uc_type_t t; + int64_t i; + double d; + + if (ucv_type(val) == UC_INTEGER) { + *n = ucv_uint64_get(val); + + return true; + } + + t = ucv_cast_number(val, &i, &d); + + if (t == UC_DOUBLE) { + if (isnan(d) || d < 0) + return false; + + i = (int64_t)d; + + if (d - i > 0) + return false; + } + else if (errno != 0) { + return false; + } + + if (i < 0) + return false; + + *n = (uint64_t)i; + + return true; +} + +static const char * +addr64_ntop(const void *addr, char *buf, size_t buflen) +{ + const union { uint64_t u64; uint16_t u16[4]; } *a64 = addr; + int len; + + errno = 0; + + len = snprintf(buf, buflen, "%04x:%04x:%04x:%04x", + ntohs(a64->u16[0]), ntohs(a64->u16[1]), + ntohs(a64->u16[2]), ntohs(a64->u16[3])); + + if ((size_t)len >= buflen) { + errno = ENOSPC; + + return NULL; + } + + return buf; +} + +static int +addr64_pton(const char *src, void *dst) +{ + union { uint64_t u64; uint16_t u16[4]; } *a64 = dst; + unsigned long n; + size_t i; + char *e; + + for (i = 0; i < ARRAY_SIZE(a64->u16); i++) { + n = strtoul(src, &e, 16); + + if (e == src || n > 0xffff) + return -1; + + a64->u16[i] = htons(n); + + if (*e == 0) + break; + + if (i >= 3 || *e != ':') + return -1; + + src += (e - src) + 1; + } + + return 0; +} + +static const char * +mpls_ntop(const void *addr, size_t addrlen, char *buf, size_t buflen) +{ + const struct mpls_label *p = addr; + size_t remlen = buflen; + uint32_t entry, label; + char *s = buf; + int len; + + errno = 0; + + while (addrlen >= sizeof(*p)) { + entry = ntohl(p->entry); + label = (entry & MPLS_LS_LABEL_MASK) >> MPLS_LS_LABEL_SHIFT; + + len = snprintf(s, remlen, "%u", label); + + if ((size_t)len >= remlen) + break; + + if (entry & MPLS_LS_S_MASK) + return buf; + + s += len; + remlen -= len; + + if (remlen) { + *s++ = '/'; + remlen--; + } + + p++; + + addrlen -= sizeof(*p); + } + + errno = ENOSPC; + + return NULL; +} + +static int +mpls_pton(int af, const char *src, void *dst, size_t dstlen) +{ + size_t max = dstlen / sizeof(struct mpls_label); + struct mpls_label *p = dst; + uint32_t label; + char *e; + + errno = 0; + + if (af != AF_MPLS) { + errno = EAFNOSUPPORT; + + return -1; + } + + while (max > 0) { + label = strtoul(src, &e, 0); + + if (label >= (1 << 20)) + return 0; + + if (e == src) + return 0; + + p->entry = htonl(label << MPLS_LS_LABEL_SHIFT); + + if (*e == 0) { + p->entry |= htonl(1 << MPLS_LS_S_SHIFT); + + return 1; + } + + if (*e != '/') + return 0; + + src += (e - src) + 1; + max--; + p++; + } + + errno = ENOSPC; + + return -1; +} + +static bool +uc_nl_parse_cidr(uc_vm_t *vm, uc_value_t *val, uc_nl_cidr_t *p) +{ + char *s = ucv_to_string(vm, val); + struct in6_addr mask6 = { 0 }; + struct in_addr mask = { 0 }; + bool valid = true; + char *m, *e; + long n = 0; + size_t i; + + if (!s) + return false; + + m = strchr(s, '/'); + + if (m) + *m++ = '\0'; + + if (inet_pton(AF_INET6, s, &p->addr.in6) == 1) { + if (m) { + if (inet_pton(AF_INET6, m, &mask6) == 1) { + while (n < 128 && (mask6.s6_addr[n / 8] << (n % 8)) & 128) + n++; + } + else { + n = strtol(m, &e, 10); + + if (e == m || *e || n < 0 || n > 128) + valid = false; + } + + p->mask = (uint8_t)n; + } + else { + p->mask = 128; + } + + p->family = AF_INET6; + p->alen = sizeof(mask6); + p->bitlen = p->alen * 8; + } + else if (strchr(s, '.') && inet_pton(AF_INET, s, &p->addr.in) == 1) { + if (m) { + if (inet_pton(AF_INET, m, &mask) == 1) { + mask.s_addr = ntohl(mask.s_addr); + + while (n < 32 && (mask.s_addr << n) & 0x80000000) + n++; + } + else { + n = strtol(m, &e, 10); + + if (e == m || *e || n < 0 || n > 32) + valid = false; + } + + p->mask = (uint8_t)n; + } + else { + p->mask = 32; + } + + p->family = AF_INET; + p->alen = sizeof(mask); + p->bitlen = p->alen * 8; + } + else { + if (m) + m[-1] = '/'; + + if (mpls_pton(AF_MPLS, s, &p->addr.mpls, sizeof(p->addr.mpls)) == 1) { + p->family = AF_MPLS; + p->alen = 0; + + for (i = 0; i < ARRAY_SIZE(p->addr.mpls); i++) { + p->alen += sizeof(struct mpls_label); + + if (ntohl(p->addr.mpls[i].entry) & MPLS_LS_S_MASK) + break; + } + + p->bitlen = p->alen * 8; + p->mask = p->bitlen; + } + else { + valid = false; + } + } + + free(s); + + return valid; +} + +typedef enum { + DT_FLAG, + DT_BOOL, + DT_U8, + DT_U16, + DT_U32, + DT_S32, + DT_U64, + DT_STRING, + DT_NETDEV, + DT_LLADDR, + DT_INADDR, + DT_IN6ADDR, + DT_U64ADDR, + DT_MPLSADDR, + DT_ANYADDR, + DT_BRIDGEID, + DT_LINKINFO, + DT_MULTIPATH, + DT_NUMRANGE, + DT_FLAGS, + DT_ENCAP, + DT_SRH, + DT_IPOPTS, + DT_U32_OR_MEMBER, + DT_NESTED, +} uc_nl_attr_datatype_t; + +enum { + DF_NO_SET = (1 << 0), + DF_NO_GET = (1 << 1), + DF_ALLOW_NONE = (1 << 2), + DF_BYTESWAP = (1 << 3), + DF_MAX_1 = (1 << 4), + DF_MAX_255 = (1 << 5), + DF_MAX_65535 = (1 << 6), + DF_MAX_16777215 = (1 << 7), + DF_STORE_MASK = (1 << 8), + DF_MULTIPLE = (1 << 9), + DF_FLAT = (1 << 10), +}; + +typedef struct uc_nl_attr_spec { + size_t attr; + const char *key; + uc_nl_attr_datatype_t type; + uint32_t flags; + const void *auxdata; +} uc_nl_attr_spec_t; + +typedef struct uc_nl_nested_spec { + size_t headsize; + size_t nattrs; + const uc_nl_attr_spec_t attrs[]; +} uc_nl_nested_spec_t; + +#define SIZE(type) (void *)(uintptr_t)sizeof(struct type) +#define MEMBER(type, field) (void *)(uintptr_t)offsetof(struct type, field) + +static const uc_nl_nested_spec_t route_cacheinfo_rta = { + .headsize = NLA_ALIGN(sizeof(struct rta_cacheinfo)), + .nattrs = 8, + .attrs = { + { RTA_UNSPEC, "clntref", DT_U32, 0, MEMBER(rta_cacheinfo, rta_clntref) }, + { RTA_UNSPEC, "lastuse", DT_U32, 0, MEMBER(rta_cacheinfo, rta_lastuse) }, + { RTA_UNSPEC, "expires", DT_S32, 0, MEMBER(rta_cacheinfo, rta_expires) }, + { RTA_UNSPEC, "error", DT_U32, 0, MEMBER(rta_cacheinfo, rta_error) }, + { RTA_UNSPEC, "used", DT_U32, 0, MEMBER(rta_cacheinfo, rta_used) }, + { RTA_UNSPEC, "id", DT_U32, 0, MEMBER(rta_cacheinfo, rta_id) }, + { RTA_UNSPEC, "ts", DT_U32, 0, MEMBER(rta_cacheinfo, rta_ts) }, + { RTA_UNSPEC, "tsage", DT_U32, 0, MEMBER(rta_cacheinfo, rta_tsage) }, + } +}; + +static const uc_nl_nested_spec_t route_metrics_rta = { + .headsize = 0, + .nattrs = 16, + .attrs = { + { RTAX_MTU, "mtu", DT_U32, 0, NULL }, + { RTAX_HOPLIMIT, "hoplimit", DT_U32, DF_MAX_255, NULL }, + { RTAX_ADVMSS, "advmss", DT_U32, 0, NULL }, + { RTAX_REORDERING, "reordering", DT_U32, 0, NULL }, + { RTAX_RTT, "rtt", DT_U32, 0, NULL }, + { RTAX_WINDOW, "window", DT_U32, 0, NULL }, + { RTAX_CWND, "cwnd", DT_U32, 0, NULL }, + { RTAX_INITCWND, "initcwnd", DT_U32, 0, NULL }, + { RTAX_INITRWND, "initrwnd", DT_U32, 0, NULL }, + { RTAX_FEATURES, "ecn", DT_U32, DF_MAX_1, NULL }, + { RTAX_QUICKACK, "quickack", DT_U32, DF_MAX_1, NULL }, + { RTAX_CC_ALGO, "cc_algo", DT_STRING, 0, NULL }, + { RTAX_RTTVAR, "rttvar", DT_U32, 0, NULL }, + { RTAX_SSTHRESH, "ssthresh", DT_U32, 0, NULL }, + { RTAX_FASTOPEN_NO_COOKIE, "fastopen_no_cookie", DT_U32, DF_MAX_1, NULL }, + { RTAX_LOCK, "lock", DT_U32, 0, NULL }, + } +}; + +static const uc_nl_nested_spec_t route_msg = { + .headsize = NLA_ALIGN(sizeof(struct rtmsg)), + .nattrs = 28, + .attrs = { + { RTA_UNSPEC, "family", DT_U8, 0, MEMBER(rtmsg, rtm_family) }, + { RTA_UNSPEC, "tos", DT_U8, 0, MEMBER(rtmsg, rtm_tos) }, + { RTA_UNSPEC, "protocol", DT_U8, 0, MEMBER(rtmsg, rtm_protocol) }, + { RTA_UNSPEC, "scope", DT_U8, 0, MEMBER(rtmsg, rtm_scope) }, + { RTA_UNSPEC, "type", DT_U8, 0, MEMBER(rtmsg, rtm_type) }, + { RTA_UNSPEC, "flags", DT_U32, 0, MEMBER(rtmsg, rtm_flags) }, + { RTA_SRC, "src", DT_ANYADDR, DF_STORE_MASK, MEMBER(rtmsg, rtm_src_len) }, + { RTA_DST, "dst", DT_ANYADDR, DF_STORE_MASK, MEMBER(rtmsg, rtm_dst_len) }, + { RTA_IIF, "iif", DT_NETDEV, 0, NULL }, + { RTA_OIF, "oif", DT_NETDEV, 0, NULL }, + { RTA_GATEWAY, "gateway", DT_ANYADDR, 0, NULL }, + { RTA_PRIORITY, "priority", DT_U32, 0, NULL }, + { RTA_PREFSRC, "prefsrc", DT_ANYADDR, 0, NULL }, + { RTA_METRICS, "metrics", DT_NESTED, 0, &route_metrics_rta }, + { RTA_MULTIPATH, "multipath", DT_MULTIPATH, 0, NULL }, + { RTA_FLOW, "flow", DT_U32, 0, NULL }, + { RTA_CACHEINFO, "cacheinfo", DT_NESTED, DF_NO_SET, &route_cacheinfo_rta }, + { RTA_TABLE, "table", DT_U32_OR_MEMBER, DF_MAX_255, MEMBER(rtmsg, rtm_table) }, + { RTA_MARK, "mark", DT_U32, 0, NULL }, + //RTA_MFC_STATS, + { RTA_PREF, "pref", DT_U8, 0, NULL }, + { RTA_ENCAP, "encap", DT_ENCAP, 0, NULL }, + { RTA_EXPIRES, "expires", DT_U32, 0, NULL }, + { RTA_UID, "uid", DT_U32, 0, NULL }, + { RTA_TTL_PROPAGATE, "ttl_propagate", DT_BOOL, 0, NULL }, + { RTA_IP_PROTO, "ip_proto", DT_U8, 0, NULL }, + { RTA_SPORT, "sport", DT_U16, DF_BYTESWAP, NULL }, + { RTA_DPORT, "dport", DT_U16, DF_BYTESWAP, NULL }, + { RTA_NH_ID, "nh_id", DT_U32, 0, NULL }, + } +}; + +static const uc_nl_attr_spec_t route_encap_mpls_attrs[] = { + { MPLS_IPTUNNEL_DST, "dst", DT_MPLSADDR, 0, NULL }, + { MPLS_IPTUNNEL_TTL, "ttl", DT_U8, 0, NULL }, +}; + +static const uc_nl_attr_spec_t route_encap_ip_attrs[] = { + { LWTUNNEL_IP_ID, "id", DT_U64, DF_BYTESWAP, NULL }, + { LWTUNNEL_IP_DST, "dst", DT_INADDR, 0, NULL }, + { LWTUNNEL_IP_SRC, "src", DT_INADDR, 0, NULL }, + { LWTUNNEL_IP_TOS, "tos", DT_U8, 0, NULL }, + { LWTUNNEL_IP_TTL, "ttl", DT_U8, 0, NULL }, + { LWTUNNEL_IP_OPTS, "opts", DT_IPOPTS, 0, NULL }, + { LWTUNNEL_IP_FLAGS, "flags", DT_U16, 0, NULL }, +}; + +static const uc_nl_attr_spec_t route_encap_ila_attrs[] = { + { ILA_ATTR_LOCATOR, "locator", DT_U64ADDR, 0, NULL }, + { ILA_ATTR_CSUM_MODE, "csum_mode", DT_U8, 0, NULL }, + { ILA_ATTR_IDENT_TYPE, "ident_type", DT_U8, 0, NULL }, + { ILA_ATTR_HOOK_TYPE, "hook_type", DT_U8, 0, NULL }, +}; + +static const uc_nl_attr_spec_t route_encap_ip6_attrs[] = { + { LWTUNNEL_IP6_ID, "id", DT_U64, DF_BYTESWAP, NULL }, + { LWTUNNEL_IP6_DST, "dst", DT_IN6ADDR, 0, NULL }, + { LWTUNNEL_IP6_SRC, "src", DT_IN6ADDR, 0, NULL }, + { LWTUNNEL_IP6_TC, "tc", DT_U32, 0, NULL }, + { LWTUNNEL_IP6_HOPLIMIT, "hoplimit", DT_U8, 0, NULL }, + { LWTUNNEL_IP6_OPTS, "opts", DT_IPOPTS, 0, NULL }, + { LWTUNNEL_IP6_FLAGS, "flags", DT_U16, 0, NULL }, +}; + +static const uc_nl_attr_spec_t route_encap_seg6_attrs[] = { + { SEG6_IPTUNNEL_SRH, "srh", DT_SRH, 0, NULL }, +}; + +static const uc_nl_nested_spec_t link_attrs_af_spec_inet6_rta = { + .headsize = 0, + .nattrs = 1, + .attrs = { + { IFLA_INET6_ADDR_GEN_MODE, "mode", DT_U8, 0, NULL }, + } +}; + +static const uc_nl_nested_spec_t link_attrs_bridge_vinfo_rta = { + .headsize = sizeof(struct bridge_vlan_info), + .nattrs = 2, + .attrs = { + { IFLA_UNSPEC, "flags", DT_U16, 0, MEMBER(bridge_vlan_info, flags) }, + { IFLA_UNSPEC, "vid", DT_U16, 0, MEMBER(bridge_vlan_info, vid) }, + } +}; + +static const uc_nl_nested_spec_t link_attrs_af_spec_rta = { + .headsize = 0, + .nattrs = 3, + .attrs = { + { AF_INET6, "inet6", DT_NESTED, 0, &link_attrs_af_spec_inet6_rta }, + { IFLA_BRIDGE_FLAGS, "bridge_flags", DT_U16, 0, NULL }, + { IFLA_BRIDGE_VLAN_INFO, "bridge_vlan_info", DT_NESTED, DF_MULTIPLE|DF_FLAT, &link_attrs_bridge_vinfo_rta }, + } +}; + +static const uc_nl_nested_spec_t link_msg = { + .headsize = NLA_ALIGN(sizeof(struct ifinfomsg)), + .nattrs = 23, + .attrs = { + { IFLA_UNSPEC, "family", DT_U8, 0, MEMBER(ifinfomsg, ifi_family) }, + { IFLA_UNSPEC, "type", DT_U16, 0, MEMBER(ifinfomsg, ifi_type) }, + { IFLA_UNSPEC, "dev", DT_NETDEV, 0, MEMBER(ifinfomsg, ifi_index) }, + { IFLA_UNSPEC, "flags", DT_FLAGS, 0, MEMBER(ifinfomsg, ifi_flags) }, + { IFLA_ADDRESS, "address", DT_LLADDR, 0, NULL }, + { IFLA_BROADCAST, "broadcast", DT_LLADDR, 0, NULL }, + { IFLA_TXQLEN, "txqlen", DT_U32, 0, NULL }, + { IFLA_MTU, "mtu", DT_U32, 0, NULL }, + /* { IFLA_NETNS_PID, "netns", DT_U32, 0, NULL }, */ + { IFLA_CARRIER, "carrier", DT_BOOL, 0, NULL }, + /* IFLA_VFINFO_LIST */ + { IFLA_MASTER, "master", DT_NETDEV, DF_ALLOW_NONE, NULL }, + { IFLA_IFALIAS, "ifalias", DT_STRING, 0, NULL }, + { IFLA_LINKMODE, "linkmode", DT_U8, 0, NULL }, + { IFLA_OPERSTATE, "operstate", DT_U8, 0, NULL }, + { IFLA_NUM_TX_QUEUES, "num_tx_queues", DT_U32, 0, NULL }, + { IFLA_NUM_RX_QUEUES, "num_rx_queues", DT_U32, 0, NULL }, + { IFLA_AF_SPEC, "af_spec", DT_NESTED, 0, &link_attrs_af_spec_rta }, + { IFLA_LINK_NETNSID, "link_netnsid", DT_U32, 0, NULL }, + { IFLA_PROTO_DOWN, "proto_down", DT_BOOL, 0, NULL }, + { IFLA_GROUP, "group", DT_U32, 0, NULL }, + { IFLA_LINK, "link", DT_NETDEV, 0, NULL }, + { IFLA_IFNAME, "ifname", DT_STRING, 0, NULL }, + { IFLA_LINKINFO, "linkinfo", DT_LINKINFO, 0, NULL }, /* XXX: DF_NO_GET ? */ + { IFLA_EXT_MASK, "ext_mask", DT_U32, 0, NULL }, + } +}; + +static const uc_nl_attr_spec_t link_bareudp_attrs[] = { + { IFLA_BAREUDP_ETHERTYPE, "ethertype", DT_U16, 0, NULL }, + { IFLA_BAREUDP_MULTIPROTO_MODE, "multiproto_mode", DT_FLAG, 0, NULL }, + { IFLA_BAREUDP_PORT, "port", DT_U16, 0, NULL }, + { IFLA_BAREUDP_SRCPORT_MIN, "srcport_min", DT_U16, 0, NULL }, +}; + +static const uc_nl_nested_spec_t link_bond_ad_info_rta = { + .headsize = 0, + .nattrs = 5, + .attrs = { + { IFLA_BOND_AD_INFO_ACTOR_KEY, "ad_info_actor_key", DT_U16, DF_NO_SET, NULL }, + { IFLA_BOND_AD_INFO_AGGREGATOR, "ad_info_aggregator", DT_U16, DF_NO_SET, NULL }, + { IFLA_BOND_AD_INFO_NUM_PORTS, "ad_info_num_ports", DT_U16, DF_NO_SET, NULL }, + { IFLA_BOND_AD_INFO_PARTNER_KEY, "ad_info_partner_key", DT_U16, DF_NO_SET, NULL }, + { IFLA_BOND_AD_INFO_PARTNER_MAC, "ad_info_partner_mac", DT_LLADDR, DF_NO_SET, NULL }, + } +}; + +static const uc_nl_attr_spec_t link_bond_attrs[] = { + { IFLA_BOND_ACTIVE_SLAVE, "active_slave", DT_NETDEV, DF_ALLOW_NONE, NULL }, + { IFLA_BOND_AD_ACTOR_SYSTEM, "ad_actor_system", DT_LLADDR, 0, NULL }, + { IFLA_BOND_AD_ACTOR_SYS_PRIO, "ad_actor_sys_prio", DT_U16, 0, NULL }, + { IFLA_BOND_AD_INFO, "ad_info", DT_NESTED, DF_NO_SET, &link_bond_ad_info_rta }, + { IFLA_BOND_AD_LACP_RATE, "ad_lacp_rate", DT_U8, 0, NULL }, + { IFLA_BOND_AD_SELECT, "ad_select", DT_U8, 0, NULL }, + { IFLA_BOND_AD_USER_PORT_KEY, "ad_user_port_key", DT_U16, 0, NULL }, + { IFLA_BOND_ALL_SLAVES_ACTIVE, "all_slaves_active", DT_U8, 0, NULL }, + { IFLA_BOND_ARP_ALL_TARGETS, "arp_all_targets", DT_U32, 0, NULL }, + { IFLA_BOND_ARP_INTERVAL, "arp_interval", DT_U32, 0, NULL }, + { IFLA_BOND_ARP_IP_TARGET, "arp_ip_target", DT_INADDR, DF_MULTIPLE, NULL }, + { IFLA_BOND_ARP_VALIDATE, "arp_validate", DT_U32, 0, NULL }, + { IFLA_BOND_DOWNDELAY, "downdelay", DT_U32, 0, NULL }, + { IFLA_BOND_FAIL_OVER_MAC, "fail_over_mac", DT_U8, 0, NULL }, + { IFLA_BOND_LP_INTERVAL, "lp_interval", DT_U32, 0, NULL }, + { IFLA_BOND_MIIMON, "miimon", DT_U32, 0, NULL }, + { IFLA_BOND_MIN_LINKS, "min_links", DT_U32, 0, NULL }, + { IFLA_BOND_MODE, "mode", DT_U8, 0, NULL }, + { IFLA_BOND_NUM_PEER_NOTIF, "num_peer_notif", DT_U8, 0, NULL }, + { IFLA_BOND_PACKETS_PER_SLAVE, "packets_per_slave", DT_U32, 0, NULL }, + { IFLA_BOND_PRIMARY, "primary", DT_NETDEV, 0, NULL }, + { IFLA_BOND_PRIMARY_RESELECT, "primary_reselect", DT_U8, 0, NULL }, + { IFLA_BOND_RESEND_IGMP, "resend_igmp", DT_U32, 0, NULL }, + { IFLA_BOND_TLB_DYNAMIC_LB, "tlb_dynamic_lb", DT_U8, 0, NULL }, + { IFLA_BOND_UPDELAY, "updelay", DT_U32, 0, NULL }, + { IFLA_BOND_USE_CARRIER, "use_carrier", DT_U8, 0, NULL }, + { IFLA_BOND_XMIT_HASH_POLICY, "xmit_hash_policy", DT_U8, 0, NULL }, +}; + +static const uc_nl_attr_spec_t link_bond_slave_attrs[] = { + { IFLA_BOND_SLAVE_AD_ACTOR_OPER_PORT_STATE, "ad_actor_oper_port_state", DT_U8, DF_NO_SET, NULL }, + { IFLA_BOND_SLAVE_AD_AGGREGATOR_ID, "ad_aggregator_id", DT_U16, DF_NO_SET, NULL }, + { IFLA_BOND_SLAVE_AD_PARTNER_OPER_PORT_STATE, "ad_partner_oper_port_state", DT_U8, DF_NO_SET, NULL }, + { IFLA_BOND_SLAVE_LINK_FAILURE_COUNT, "link_failure_count", DT_U32, DF_NO_SET, NULL }, + { IFLA_BOND_SLAVE_MII_STATUS, "mii_status", DT_U8, DF_NO_SET, NULL }, + { IFLA_BOND_SLAVE_PERM_HWADDR, "perm_hwaddr", DT_LLADDR, DF_NO_SET, NULL }, + { IFLA_BOND_SLAVE_QUEUE_ID, "queue_id", DT_U16, 0, NULL }, + { IFLA_BOND_SLAVE_STATE, "state", DT_U8, DF_NO_SET, NULL }, +}; + +static const uc_nl_attr_spec_t link_bridge_attrs[] = { + { IFLA_BR_AGEING_TIME, "ageing_time", DT_U32, 0, NULL }, + { IFLA_BR_BRIDGE_ID, "bridge_id", DT_BRIDGEID, DF_NO_SET, NULL }, + { IFLA_BR_FDB_FLUSH, "fdb_flush", DT_FLAG, DF_NO_GET, NULL }, + { IFLA_BR_FORWARD_DELAY, "forward_delay", DT_U32, 0, NULL }, + { IFLA_BR_GC_TIMER, "gc_timer", DT_U64, DF_NO_SET, NULL }, + { IFLA_BR_GROUP_ADDR, "group_addr", DT_LLADDR, 0, NULL }, + { IFLA_BR_GROUP_FWD_MASK, "group_fwd_mask", DT_U16, 0, NULL }, + { IFLA_BR_HELLO_TIME, "hello_time", DT_U32, 0, NULL }, + { IFLA_BR_HELLO_TIMER, "hello_timer", DT_U64, DF_NO_SET, NULL }, + { IFLA_BR_MAX_AGE, "max_age", DT_U32, 0, NULL }, + { IFLA_BR_MCAST_HASH_ELASTICITY, "mcast_hash_elasticity", DT_U32, 0, NULL }, + { IFLA_BR_MCAST_HASH_MAX, "mcast_hash_max", DT_U32, 0, NULL }, + { IFLA_BR_MCAST_IGMP_VERSION, "mcast_igmp_version", DT_U8, 0, NULL }, + { IFLA_BR_MCAST_LAST_MEMBER_CNT, "mcast_last_member_cnt", DT_U32, 0, NULL }, + { IFLA_BR_MCAST_LAST_MEMBER_INTVL, "mcast_last_member_intvl", DT_U64, 0, NULL }, + { IFLA_BR_MCAST_MEMBERSHIP_INTVL, "mcast_membership_intvl", DT_U64, 0, NULL }, + { IFLA_BR_MCAST_MLD_VERSION, "mcast_mld_version", DT_U8, 0, NULL }, + { IFLA_BR_MCAST_QUERIER, "mcast_querier", DT_U8, 0, NULL }, + { IFLA_BR_MCAST_QUERIER_INTVL, "mcast_querier_intvl", DT_U64, 0, NULL }, + { IFLA_BR_MCAST_QUERY_INTVL, "mcast_query_intvl", DT_U64, 0, NULL }, + { IFLA_BR_MCAST_QUERY_RESPONSE_INTVL, "mcast_query_response_intvl", DT_U64, 0, NULL }, + { IFLA_BR_MCAST_QUERY_USE_IFADDR, "mcast_query_use_ifaddr", DT_U8, 0, NULL }, + { IFLA_BR_MCAST_ROUTER, "mcast_router", DT_U8, 0, NULL }, + { IFLA_BR_MCAST_SNOOPING, "mcast_snooping", DT_U8, 0, NULL }, + { IFLA_BR_MCAST_STARTUP_QUERY_CNT, "mcast_startup_query_cnt", DT_U32, 0, NULL }, + { IFLA_BR_MCAST_STARTUP_QUERY_INTVL, "mcast_startup_query_intvl", DT_U64, 0, NULL }, + { IFLA_BR_MCAST_STATS_ENABLED, "mcast_stats_enabled", DT_U8, 0, NULL }, + { IFLA_BR_NF_CALL_ARPTABLES, "nf_call_arptables", DT_U8, 0, NULL }, + { IFLA_BR_NF_CALL_IP6TABLES, "nf_call_ip6tables", DT_U8, 0, NULL }, + { IFLA_BR_NF_CALL_IPTABLES, "nf_call_iptables", DT_U8, 0, NULL }, + { IFLA_BR_PRIORITY, "priority", DT_U16, 0, NULL }, + { IFLA_BR_ROOT_ID, "root_id", DT_BRIDGEID, DF_NO_SET, NULL }, + { IFLA_BR_ROOT_PATH_COST, "root_path_cost", DT_U32, DF_NO_SET, NULL }, + { IFLA_BR_ROOT_PORT, "root_port", DT_U16, DF_NO_SET, NULL }, + { IFLA_BR_STP_STATE, "stp_state", DT_U32, 0, NULL }, + { IFLA_BR_TCN_TIMER, "tcn_timer", DT_U64, DF_NO_SET, NULL }, + { IFLA_BR_TOPOLOGY_CHANGE, "topology_change", DT_U8, DF_NO_SET, NULL }, + { IFLA_BR_TOPOLOGY_CHANGE_DETECTED, "topology_change_detected", DT_U8, DF_NO_SET, NULL }, + { IFLA_BR_TOPOLOGY_CHANGE_TIMER, "topology_change_timer", DT_U64, DF_NO_SET, NULL }, + { IFLA_BR_VLAN_DEFAULT_PVID, "vlan_default_pvid", DT_U16, 0, NULL }, + { IFLA_BR_VLAN_FILTERING, "vlan_filtering", DT_U8, 0, NULL }, + { IFLA_BR_VLAN_PROTOCOL, "vlan_protocol", DT_U16, 0, NULL }, + { IFLA_BR_VLAN_STATS_ENABLED, "vlan_stats_enabled", DT_U8, 0, NULL }, +}; + +static const uc_nl_attr_spec_t link_bridge_slave_attrs[] = { + { IFLA_BRPORT_BACKUP_PORT, "backup_port", DT_NETDEV, 0, NULL }, + //{ IFLA_BRPORT_BCAST_FLOOD, "bcast-flood", DT_??, 0, NULL }, + { IFLA_BRPORT_BRIDGE_ID, "bridge_id", DT_BRIDGEID, DF_NO_SET, NULL }, + { IFLA_BRPORT_CONFIG_PENDING, "config_pending", DT_U8, DF_NO_SET, NULL }, + { IFLA_BRPORT_COST, "cost", DT_U32, 0, NULL }, + { IFLA_BRPORT_DESIGNATED_COST, "designated_cost", DT_U16, DF_NO_SET, NULL }, + { IFLA_BRPORT_DESIGNATED_PORT, "designated_port", DT_U16, DF_NO_SET, NULL }, + { IFLA_BRPORT_FAST_LEAVE, "fast_leave", DT_U8, 0, NULL }, + { IFLA_BRPORT_FLUSH, "flush", DT_FLAG, DF_NO_GET, NULL }, + { IFLA_BRPORT_FORWARD_DELAY_TIMER, "forward_delay_timer", DT_U64, DF_NO_SET, NULL }, + { IFLA_BRPORT_GROUP_FWD_MASK, "group_fwd_mask", DT_U16, 0, NULL }, + { IFLA_BRPORT_GUARD, "guard", DT_U8, 0, NULL }, + { IFLA_BRPORT_HOLD_TIMER, "hold_timer", DT_U64, DF_NO_SET, NULL }, + { IFLA_BRPORT_ID, "id", DT_U16, DF_NO_SET, NULL }, + { IFLA_BRPORT_ISOLATED, "isolated", DT_U8, 0, NULL }, + { IFLA_BRPORT_LEARNING, "learning", DT_U8, 0, NULL }, + { IFLA_BRPORT_LEARNING_SYNC, "learning_sync", DT_U8, 0, NULL }, + { IFLA_BRPORT_MCAST_FLOOD, "mcast_flood", DT_U8, 0, NULL }, + { IFLA_BRPORT_MCAST_TO_UCAST, "mcast_to_ucast", DT_U8, 0, NULL }, + { IFLA_BRPORT_MESSAGE_AGE_TIMER, "message_age_timer", DT_U64, DF_NO_SET, NULL }, + { IFLA_BRPORT_MODE, "mode", DT_U8, 0, NULL }, + { IFLA_BRPORT_MULTICAST_ROUTER, "multicast_router", DT_U8, 0, NULL }, + { IFLA_BRPORT_NEIGH_SUPPRESS, "neigh_suppress", DT_U8, 0, NULL }, + { IFLA_BRPORT_NO, "no", DT_U16, DF_NO_SET, NULL }, + { IFLA_BRPORT_PRIORITY, "priority", DT_U16, 0, NULL }, + { IFLA_BRPORT_PROTECT, "protect", DT_U8, 0, NULL }, + { IFLA_BRPORT_PROXYARP, "proxyarp", DT_U8, DF_NO_SET, NULL }, + { IFLA_BRPORT_PROXYARP_WIFI, "proxyarp_wifi", DT_U8, DF_NO_SET, NULL }, + { IFLA_BRPORT_ROOT_ID, "root_id", DT_BRIDGEID, DF_NO_SET, NULL }, + { IFLA_BRPORT_STATE, "state", DT_U8, 0, NULL }, + { IFLA_BRPORT_TOPOLOGY_CHANGE_ACK, "topology_change_ack", DT_U8, DF_NO_SET, NULL }, + { IFLA_BRPORT_UNICAST_FLOOD, "unicast_flood", DT_U8, 0, NULL }, + { IFLA_BRPORT_VLAN_TUNNEL, "vlan_tunnel", DT_U8, 0, NULL }, +}; + +static const uc_nl_attr_spec_t link_geneve_attrs[] = { + { IFLA_GENEVE_COLLECT_METADATA, "collect_metadata", DT_FLAG, DF_NO_GET, NULL }, + { IFLA_GENEVE_ID, "id", DT_U32, 0, NULL }, + { IFLA_GENEVE_LABEL, "label", DT_U32, 0, NULL }, + { IFLA_GENEVE_PORT, "port", DT_U16, 0, NULL }, + { IFLA_GENEVE_REMOTE, "remote", DT_INADDR, 0, NULL }, + { IFLA_GENEVE_REMOTE6, "remote6", DT_IN6ADDR, 0, NULL }, + { IFLA_GENEVE_TOS, "tos", DT_U8, 0, NULL }, + { IFLA_GENEVE_TTL, "ttl", DT_U8, 0, NULL }, + { IFLA_GENEVE_UDP_CSUM, "udp_csum", DT_U8, 0, NULL }, + { IFLA_GENEVE_UDP_ZERO_CSUM6_RX, "udp_zero_csum6_rx", DT_U8, 0, NULL }, + { IFLA_GENEVE_UDP_ZERO_CSUM6_TX, "udp_zero_csum6_tx", DT_U8, 0, NULL }, +}; + +static const uc_nl_attr_spec_t link_hsr_attrs[] = { + { IFLA_HSR_MULTICAST_SPEC, "multicast_spec", DT_STRING, DF_NO_GET, NULL }, + { IFLA_HSR_SEQ_NR, "seq_nr", DT_U16, DF_NO_SET, NULL }, + { IFLA_HSR_SLAVE1, "slave1", DT_NETDEV, 0, NULL }, + { IFLA_HSR_SLAVE2, "slave2", DT_NETDEV, 0, NULL }, + { IFLA_HSR_SUPERVISION_ADDR, "supervision_addr", DT_LLADDR, DF_NO_SET, NULL }, + { IFLA_HSR_VERSION, "version", DT_STRING, DF_NO_GET, NULL }, +}; + +static const uc_nl_attr_spec_t link_ipoib_attrs[] = { + { IFLA_IPOIB_MODE, "mode", DT_U16, 0, NULL }, + { IFLA_IPOIB_PKEY, "pkey", DT_U16, 0, NULL }, + { IFLA_IPOIB_UMCAST, "umcast", DT_U16, 0, NULL }, +}; + +static const uc_nl_attr_spec_t link_ipvlan_attrs[] = { + { IFLA_IPVLAN_FLAGS, "flags", DT_U16, 0, NULL }, + { IFLA_IPVLAN_MODE, "mode", DT_U16, 0, NULL }, +}; + +static const uc_nl_attr_spec_t link_macvlan_attrs[] = { + { IFLA_MACVLAN_FLAGS, "flags", DT_U16, 0, NULL }, + { IFLA_MACVLAN_MACADDR, "macaddr", DT_LLADDR, DF_NO_GET, NULL }, + { IFLA_MACVLAN_MACADDR_COUNT, "macaddr_count", DT_U32, DF_NO_SET, NULL }, + { IFLA_MACVLAN_MACADDR_DATA, "macaddr_data", DT_LLADDR, DF_MULTIPLE, (void *)IFLA_MACVLAN_MACADDR }, + { IFLA_MACVLAN_MACADDR_MODE, "macaddr_mode", DT_U32, DF_NO_GET, NULL }, + { IFLA_MACVLAN_MODE, "mode", DT_U32, 0, NULL }, +}; + +static const uc_nl_attr_spec_t link_rmnet_attrs[] = { + //{ IFLA_RMNET_FLAGS, "flags", DT_??, 0, NULL }, + { IFLA_RMNET_MUX_ID, "mux_id", DT_U16, 0, NULL }, +}; + + +static const uc_nl_attr_spec_t link_vlan_attrs[] = { + { IFLA_VLAN_EGRESS_QOS, "egress_qos_map", DT_NUMRANGE, DF_MULTIPLE, (void *)IFLA_VLAN_QOS_MAPPING }, + { IFLA_VLAN_FLAGS, "flags", DT_FLAGS, 0, NULL }, + { IFLA_VLAN_ID, "id", DT_U16, 0, NULL }, + { IFLA_VLAN_INGRESS_QOS, "ingress_qos_map", DT_NUMRANGE, DF_MULTIPLE, (void *)IFLA_VLAN_QOS_MAPPING }, + { IFLA_VLAN_PROTOCOL, "protocol", DT_U16, 0, NULL }, +}; + +static const uc_nl_attr_spec_t link_vrf_attrs[] = { + { IFLA_VRF_PORT_TABLE, "port_table", DT_U32, DF_NO_SET, NULL }, + { IFLA_VRF_TABLE, "table", DT_U32, 0, NULL }, +}; + +static const uc_nl_attr_spec_t link_vxlan_attrs[] = { + { IFLA_VXLAN_AGEING, "ageing", DT_U32, 0, NULL }, + { IFLA_VXLAN_COLLECT_METADATA, "collect_metadata", DT_U8, 0, NULL }, + { IFLA_VXLAN_GBP, "gbp", DT_FLAG, 0, NULL }, + { IFLA_VXLAN_GPE, "gpe", DT_FLAG, 0, NULL }, + { IFLA_VXLAN_GROUP, "group", DT_INADDR, 0, NULL }, + { IFLA_VXLAN_GROUP6, "group6", DT_IN6ADDR, 0, NULL }, + { IFLA_VXLAN_ID, "id", DT_U32, 0, NULL }, + { IFLA_VXLAN_L2MISS, "l2miss", DT_U8, 0, NULL }, + { IFLA_VXLAN_L3MISS, "l3miss", DT_U8, 0, NULL }, + { IFLA_VXLAN_LABEL, "label", DT_U32, 0, NULL }, + { IFLA_VXLAN_LEARNING, "learning", DT_U8, 0, NULL }, + { IFLA_VXLAN_LIMIT, "limit", DT_U32, 0, NULL }, + { IFLA_VXLAN_LINK, "link", DT_U32, 0, NULL }, + { IFLA_VXLAN_LOCAL, "local", DT_INADDR, 0, NULL }, + { IFLA_VXLAN_LOCAL6, "local6", DT_IN6ADDR, 0, NULL }, + { IFLA_VXLAN_PORT, "port", DT_U16, DF_BYTESWAP, NULL }, + { IFLA_VXLAN_PORT_RANGE, "port_range", DT_NUMRANGE, DF_MAX_65535|DF_BYTESWAP, NULL }, + { IFLA_VXLAN_PROXY, "proxy", DT_U8, 0, NULL }, + //{ IFLA_VXLAN_REMCSUM_NOPARTIAL, "remcsum-nopartial", DT_??, 0, NULL }, + { IFLA_VXLAN_REMCSUM_RX, "remcsum_rx", DT_BOOL, 0, NULL }, + { IFLA_VXLAN_REMCSUM_TX, "remcsum_tx", DT_BOOL, 0, NULL }, + { IFLA_VXLAN_RSC, "rsc", DT_BOOL, 0, NULL }, + { IFLA_VXLAN_TOS, "tos", DT_U8, 0, NULL }, + { IFLA_VXLAN_TTL, "ttl", DT_U8, 0, NULL }, + { IFLA_VXLAN_TTL_INHERIT, "ttl_inherit", DT_FLAG, 0, NULL }, + { IFLA_VXLAN_UDP_CSUM, "udp_csum", DT_BOOL, 0, NULL }, + { IFLA_VXLAN_UDP_ZERO_CSUM6_RX, "udp_zero_csum6_rx", DT_BOOL, 0, NULL }, + { IFLA_VXLAN_UDP_ZERO_CSUM6_TX, "udp_zero_csum6_tx", DT_BOOL, 0, NULL }, +}; + +static const uc_nl_attr_spec_t link_gre_attrs[] = { + { IFLA_GRE_COLLECT_METADATA, "collect_metadata", DT_FLAG, 0, NULL }, + { IFLA_GRE_ENCAP_DPORT, "encap_dport", DT_U16, DF_BYTESWAP, NULL }, + { IFLA_GRE_ENCAP_FLAGS, "encap_flags", DT_U16, 0, NULL }, + { IFLA_GRE_ENCAP_LIMIT, "encap_limit", DT_U8, 0, NULL }, + { IFLA_GRE_ENCAP_SPORT, "encap_sport", DT_U16, DF_BYTESWAP, NULL }, + { IFLA_GRE_ENCAP_TYPE, "encap_type", DT_U16, 0, NULL }, + { IFLA_GRE_ERSPAN_DIR, "erspan_dir", DT_U8, 0, NULL }, + { IFLA_GRE_ERSPAN_HWID, "erspan_hwid", DT_U16, 0, NULL }, + { IFLA_GRE_ERSPAN_INDEX, "erspan_index", DT_U32, 0, NULL }, + { IFLA_GRE_ERSPAN_VER, "erspan_ver", DT_U8, 0, NULL }, + { IFLA_GRE_FLAGS, "flags", DT_U32, 0, NULL }, + { IFLA_GRE_FLOWINFO, "flowinfo", DT_U32, DF_BYTESWAP, NULL }, + { IFLA_GRE_FWMARK, "fwmark", DT_U32, 0, NULL }, + { IFLA_GRE_IFLAGS, "iflags", DT_U16, 0, NULL }, + { IFLA_GRE_IGNORE_DF, "ignore_df", DT_BOOL, 0, NULL }, + { IFLA_GRE_IKEY, "ikey", DT_U32, 0, NULL }, + { IFLA_GRE_LINK, "link", DT_NETDEV, 0, NULL }, + { IFLA_GRE_LOCAL, "local", DT_ANYADDR, 0, NULL }, + { IFLA_GRE_OFLAGS, "oflags", DT_U16, 0, NULL }, + { IFLA_GRE_OKEY, "okey", DT_U32, 0, NULL }, + { IFLA_GRE_PMTUDISC, "pmtudisc", DT_BOOL, 0, NULL }, + { IFLA_GRE_REMOTE, "remote", DT_ANYADDR, 0, NULL }, + { IFLA_GRE_TOS, "tos", DT_U8, 0, NULL }, + { IFLA_GRE_TTL, "ttl", DT_U8, 0, NULL }, +}; + +#define link_gretap_attrs link_gre_attrs +#define link_erspan_attrs link_gre_attrs +#define link_ip6gre_attrs link_gre_attrs +#define link_ip6gretap_attrs link_gre_attrs +#define link_ip6erspan_attrs link_gre_attrs + +static const uc_nl_attr_spec_t link_ip6tnl_attrs[] = { + { IFLA_IPTUN_6RD_PREFIX, "6rd_prefix", DT_IN6ADDR, 0, NULL }, + { IFLA_IPTUN_6RD_PREFIXLEN, "6rd_prefixlen", DT_U16, 0, NULL }, + { IFLA_IPTUN_6RD_RELAY_PREFIX, "6rd_relay_prefix", DT_INADDR, 0, NULL }, + { IFLA_IPTUN_6RD_RELAY_PREFIXLEN, "6rd_relay_prefixlen", DT_U16, 0, NULL }, + { IFLA_IPTUN_COLLECT_METADATA, "collect_metadata", DT_BOOL, 0, NULL }, + { IFLA_IPTUN_ENCAP_DPORT, "encap_dport", DT_U16, DF_BYTESWAP, NULL }, + { IFLA_IPTUN_ENCAP_FLAGS, "encap_flags", DT_U16, 0, NULL }, + { IFLA_IPTUN_ENCAP_LIMIT, "encap_limit", DT_U8, 0, NULL }, + { IFLA_IPTUN_ENCAP_SPORT, "encap_sport", DT_U16, DF_BYTESWAP, NULL }, + { IFLA_IPTUN_ENCAP_TYPE, "encap_type", DT_U16, 0, NULL }, + { IFLA_IPTUN_FLAGS, "flags", DT_U16, 0, NULL }, + { IFLA_IPTUN_FLOWINFO, "flowinfo", DT_U32, DF_BYTESWAP, NULL }, + { IFLA_IPTUN_FWMARK, "fwmark", DT_U32, 0, NULL }, + { IFLA_IPTUN_LINK, "link", DT_NETDEV, 0, NULL }, + { IFLA_IPTUN_LOCAL, "local", DT_ANYADDR, 0, NULL }, + { IFLA_IPTUN_PMTUDISC, "pmtudisc", DT_BOOL, 0, NULL }, + { IFLA_IPTUN_PROTO, "proto", DT_U8, 0, NULL }, + { IFLA_IPTUN_REMOTE, "remote", DT_ANYADDR, 0, NULL }, + { IFLA_IPTUN_TOS, "tos", DT_U8, 0, NULL }, + { IFLA_IPTUN_TTL, "ttl", DT_U8, 0, NULL }, +}; + +#define link_ipip_attrs link_ip6tnl_attrs +#define link_sit_attrs link_ip6tnl_attrs + +static const uc_nl_attr_spec_t link_veth_attrs[] = { + { VETH_INFO_PEER, "info_peer", DT_NESTED, 0, &link_msg }, +}; + +static const uc_nl_attr_spec_t link_vti_attrs[] = { + { IFLA_VTI_FWMARK, "fwmark", DT_U32, 0, NULL }, + { IFLA_VTI_IKEY, "ikey", DT_U32, 0, NULL }, + { IFLA_VTI_LINK, "link", DT_U32, 0, NULL }, + { IFLA_VTI_LOCAL, "local", DT_ANYADDR, 0, NULL }, + { IFLA_VTI_OKEY, "okey", DT_U32, 0, NULL }, + { IFLA_VTI_REMOTE, "remote", DT_ANYADDR, 0, NULL }, +}; + +#define link_vti6_attrs link_vti_attrs + +static const uc_nl_attr_spec_t link_xfrm_attrs[] = { + { IFLA_XFRM_IF_ID, "if_id", DT_U32, 0, NULL }, + { IFLA_XFRM_LINK, "link", DT_NETDEV, 0, NULL }, +}; + +static const uc_nl_attr_spec_t lwtipopt_erspan_attrs[] = { + { LWTUNNEL_IP_OPT_ERSPAN_VER, "ver", DT_U8, 0, NULL }, + { LWTUNNEL_IP_OPT_ERSPAN_INDEX, "index", DT_U16, DF_BYTESWAP, NULL }, + { LWTUNNEL_IP_OPT_ERSPAN_DIR, "dir", DT_U8, 0, NULL }, + { LWTUNNEL_IP_OPT_ERSPAN_HWID, "hwid", DT_U8, 0, NULL }, +}; + +static const uc_nl_attr_spec_t lwtipopt_geneve_attrs[] = { + { LWTUNNEL_IP_OPT_GENEVE_CLASS, "class", DT_U16, DF_BYTESWAP, NULL }, + { LWTUNNEL_IP_OPT_GENEVE_TYPE, "type", DT_U8, 0, NULL }, + { LWTUNNEL_IP_OPT_GENEVE_DATA, "data", DT_STRING, 0, NULL }, +}; + +static const uc_nl_attr_spec_t lwtipopt_vxlan_attrs[] = { + { LWTUNNEL_IP_OPT_VXLAN_GBP, "gbp", DT_U32, 0, NULL }, +}; + +static const uc_nl_nested_spec_t neigh_cacheinfo_rta = { + .headsize = NLA_ALIGN(sizeof(struct nda_cacheinfo)), + .nattrs = 4, + .attrs = { + { NDA_UNSPEC, "confirmed", DT_U32, 0, MEMBER(nda_cacheinfo, ndm_confirmed) }, + { NDA_UNSPEC, "used", DT_U32, 0, MEMBER(nda_cacheinfo, ndm_used) }, + { NDA_UNSPEC, "updated", DT_U32, 0, MEMBER(nda_cacheinfo, ndm_updated) }, + { NDA_UNSPEC, "refcnt", DT_U32, 0, MEMBER(nda_cacheinfo, ndm_refcnt) }, + } +}; + +static const uc_nl_nested_spec_t neigh_msg = { + .headsize = NLA_ALIGN(sizeof(struct ndmsg)), + .nattrs = 16, + .attrs = { + { NDA_UNSPEC, "family", DT_U8, 0, MEMBER(ndmsg, ndm_family) }, + { NDA_UNSPEC, "dev" /* actually ifindex, but avoid clash with NDA_IFINDEX */, DT_NETDEV, DF_ALLOW_NONE, MEMBER(ndmsg, ndm_ifindex) }, + { NDA_UNSPEC, "state", DT_U16, 0, MEMBER(ndmsg, ndm_state) }, + { NDA_UNSPEC, "flags", DT_U8, 0, MEMBER(ndmsg, ndm_flags) }, + { NDA_UNSPEC, "type", DT_U8, 0, MEMBER(ndmsg, ndm_type) }, + { NDA_CACHEINFO, "cacheinfo", DT_NESTED, DF_NO_SET, &neigh_cacheinfo_rta }, + { NDA_DST, "dst", DT_ANYADDR, 0, NULL }, + { NDA_IFINDEX, "ifindex", DT_NETDEV, 0, NULL }, + { NDA_LINK_NETNSID, "link_netnsid", DT_U32, DF_NO_SET, NULL }, + { NDA_LLADDR, "lladdr", DT_LLADDR, 0, NULL }, + { NDA_MASTER, "master", DT_NETDEV, 0, NULL }, + { NDA_PORT, "port", DT_U16, DF_BYTESWAP, NULL }, + { NDA_PROBES, "probes", DT_U32, DF_NO_SET, NULL }, + { NDA_SRC_VNI, "src_vni", DT_U32, DF_NO_SET, NULL }, + { NDA_VLAN, "vlan", DT_U16, 0, NULL }, + { NDA_VNI, "vni", DT_U32, DF_MAX_16777215, NULL }, + } +}; + +static const uc_nl_nested_spec_t addr_cacheinfo_rta = { + .headsize = NLA_ALIGN(sizeof(struct ifa_cacheinfo)), + .nattrs = 4, + .attrs = { + { IFA_UNSPEC, "preferred", DT_U32, 0, MEMBER(ifa_cacheinfo, ifa_prefered) }, + { IFA_UNSPEC, "valid", DT_U32, 0, MEMBER(ifa_cacheinfo, ifa_valid) }, + { IFA_UNSPEC, "cstamp", DT_U32, 0, MEMBER(ifa_cacheinfo, cstamp) }, + { IFA_UNSPEC, "tstamp", DT_U32, 0, MEMBER(ifa_cacheinfo, tstamp) }, + } +}; + +static const uc_nl_nested_spec_t addr_msg = { + .headsize = NLA_ALIGN(sizeof(struct ifaddrmsg)), + .nattrs = 11, + .attrs = { + { IFA_UNSPEC, "family", DT_U8, 0, MEMBER(ifaddrmsg, ifa_family) }, + { IFA_FLAGS, "flags", DT_U32_OR_MEMBER, DF_MAX_255, MEMBER(ifaddrmsg, ifa_flags) }, + { IFA_UNSPEC, "scope", DT_U8, 0, MEMBER(ifaddrmsg, ifa_scope) }, + { IFA_UNSPEC, "dev", DT_NETDEV, 0, MEMBER(ifaddrmsg, ifa_index) }, + { IFA_ADDRESS, "address", DT_ANYADDR, DF_STORE_MASK, MEMBER(ifaddrmsg, ifa_prefixlen) }, + { IFA_LOCAL, "local", DT_ANYADDR, 0, NULL }, + { IFA_LABEL, "label", DT_STRING, 0, NULL }, + { IFA_BROADCAST, "broadcast", DT_ANYADDR, 0, NULL }, + { IFA_ANYCAST, "anycast", DT_ANYADDR, 0, NULL }, + { IFA_CACHEINFO, "cacheinfo", DT_NESTED, DF_NO_SET, &addr_cacheinfo_rta }, + { IFA_RT_PRIORITY, "metric", DT_U32, 0, NULL }, + } +}; + +static const uc_nl_nested_spec_t rule_msg = { + .headsize = NLA_ALIGN(sizeof(struct fib_rule_hdr)), + .nattrs = 23, + .attrs = { + { FRA_UNSPEC, "family", DT_U8, 0, MEMBER(fib_rule_hdr, family) }, + { FRA_UNSPEC, "tos", DT_U8, 0, MEMBER(fib_rule_hdr, tos) }, + { FRA_UNSPEC, "action", DT_U8, 0, MEMBER(fib_rule_hdr, action) }, + { FRA_UNSPEC, "flags", DT_U32, 0, MEMBER(fib_rule_hdr, flags) }, + { FRA_PRIORITY, "priority", DT_U32, 0, NULL }, + { FRA_SRC, "src", DT_ANYADDR, DF_STORE_MASK, MEMBER(fib_rule_hdr, src_len) }, + { FRA_DST, "dst", DT_ANYADDR, DF_STORE_MASK, MEMBER(fib_rule_hdr, dst_len) }, + { FRA_FWMARK, "fwmark", DT_U32, 0, NULL }, + { FRA_FWMASK, "fwmask", DT_U32, 0, NULL }, + { FRA_IFNAME, "iif", DT_NETDEV, 0, NULL }, + { FRA_OIFNAME, "oif", DT_NETDEV, 0, NULL }, + { FRA_L3MDEV, "l3mdev", DT_U8, 0, NULL }, + { FRA_UID_RANGE, "uid_range", DT_NUMRANGE, 0, NULL }, + { FRA_IP_PROTO, "ip_proto", DT_U8, 0, NULL }, + { FRA_SPORT_RANGE, "sport_range", DT_NUMRANGE, DF_MAX_65535, NULL }, + { FRA_DPORT_RANGE, "dport_range", DT_NUMRANGE, DF_MAX_65535, NULL }, + { FRA_TABLE, "table", DT_U32_OR_MEMBER, DF_MAX_255, MEMBER(fib_rule_hdr, table) }, + { FRA_SUPPRESS_PREFIXLEN, "suppress_prefixlen", DT_S32, 0, NULL }, + { FRA_SUPPRESS_IFGROUP, "suppress_ifgroup", DT_U32, 0, NULL }, + { FRA_FLOW, "flow", DT_U32, 0, NULL }, + { RTA_GATEWAY, "gateway", DT_ANYADDR, 0, NULL }, + { FRA_GOTO, "goto", DT_U32, 0, NULL }, + { FRA_PROTOCOL, "protocol", DT_U8, 0, NULL }, + } +}; + +#define IFAL_UNSPEC 0 + +static const uc_nl_nested_spec_t addrlabel_msg = { + .headsize = NLA_ALIGN(sizeof(struct ifaddrlblmsg)), + .nattrs = 6, + .attrs = { + { IFAL_UNSPEC, "family", DT_U8, 0, MEMBER(ifaddrlblmsg, ifal_family) }, + { IFAL_UNSPEC, "flags", DT_U8, 0, MEMBER(ifaddrlblmsg, ifal_flags) }, + { IFAL_UNSPEC, "dev", DT_NETDEV, 0, MEMBER(ifaddrlblmsg, ifal_index) }, + { IFAL_UNSPEC, "seq", DT_U32, 0, MEMBER(ifaddrlblmsg, ifal_seq) }, + { IFAL_ADDRESS, "address", DT_ANYADDR, DF_STORE_MASK, MEMBER(ifaddrlblmsg, ifal_prefixlen) }, + { IFAL_LABEL, "label", DT_U32, 0, NULL }, + } +}; + +static const uc_nl_nested_spec_t neightbl_params_rta = { + .headsize = 0, + .nattrs = 13, + .attrs = { + { NDTPA_IFINDEX, "dev", DT_NETDEV, 0, NULL }, + { NDTPA_BASE_REACHABLE_TIME, "base_reachable_time", DT_U64, 0, NULL }, + { NDTPA_RETRANS_TIME, "retrans_time", DT_U64, 0, NULL }, + { NDTPA_GC_STALETIME, "gc_staletime", DT_U64, 0, NULL }, + { NDTPA_DELAY_PROBE_TIME, "delay_probe_time", DT_U64, 0, NULL }, + { NDTPA_QUEUE_LEN, "queue_len", DT_U32, 0, NULL }, + { NDTPA_APP_PROBES, "app_probes", DT_U32, 0, NULL }, + { NDTPA_UCAST_PROBES, "ucast_probes", DT_U32, 0, NULL }, + { NDTPA_MCAST_PROBES, "mcast_probes", DT_U32, 0, NULL }, + { NDTPA_ANYCAST_DELAY, "anycast_delay", DT_U64, 0, NULL }, + { NDTPA_PROXY_DELAY, "proxy_delay", DT_U64, 0, NULL }, + { NDTPA_PROXY_QLEN, "proxy_qlen", DT_U32, 0, NULL }, + { NDTPA_LOCKTIME, "locktime", DT_U64, 0, NULL }, + } +}; + +static const uc_nl_nested_spec_t neightbl_config_rta = { + .headsize = NLA_ALIGN(sizeof(struct ndt_config)), + .nattrs = 9, + .attrs = { + { NDTA_UNSPEC, "key_len", DT_U16, 0, MEMBER(ndt_config, ndtc_key_len) }, + { NDTA_UNSPEC, "entry_size", DT_U16, 0, MEMBER(ndt_config, ndtc_entry_size) }, + { NDTA_UNSPEC, "entries", DT_U32, 0, MEMBER(ndt_config, ndtc_entries) }, + { NDTA_UNSPEC, "last_flush", DT_U32, 0, MEMBER(ndt_config, ndtc_last_flush) }, + { NDTA_UNSPEC, "last_rand", DT_U32, 0, MEMBER(ndt_config, ndtc_last_rand) }, + { NDTA_UNSPEC, "hash_rnd", DT_U32, 0, MEMBER(ndt_config, ndtc_hash_rnd) }, + { NDTA_UNSPEC, "hash_mask", DT_U32, 0, MEMBER(ndt_config, ndtc_hash_mask) }, + { NDTA_UNSPEC, "hash_chain_gc", DT_U32, 0, MEMBER(ndt_config, ndtc_hash_chain_gc) }, + { NDTA_UNSPEC, "proxy_qlen", DT_U32, 0, MEMBER(ndt_config, ndtc_proxy_qlen) }, + } +}; + +static const uc_nl_nested_spec_t neightbl_stats_rta = { + .headsize = NLA_ALIGN(sizeof(struct ndt_stats)), + .nattrs = 10, + .attrs = { + { NDTA_UNSPEC, "allocs", DT_U64, 0, MEMBER(ndt_stats, ndts_allocs) }, + { NDTA_UNSPEC, "destroys", DT_U64, 0, MEMBER(ndt_stats, ndts_destroys) }, + { NDTA_UNSPEC, "hash_grows", DT_U64, 0, MEMBER(ndt_stats, ndts_hash_grows) }, + { NDTA_UNSPEC, "res_failed", DT_U64, 0, MEMBER(ndt_stats, ndts_res_failed) }, + { NDTA_UNSPEC, "lookups", DT_U64, 0, MEMBER(ndt_stats, ndts_lookups) }, + { NDTA_UNSPEC, "hits", DT_U64, 0, MEMBER(ndt_stats, ndts_hits) }, + { NDTA_UNSPEC, "rcv_probes_mcast", DT_U64, 0, MEMBER(ndt_stats, ndts_rcv_probes_mcast) }, + { NDTA_UNSPEC, "rcv_probes_ucast", DT_U64, 0, MEMBER(ndt_stats, ndts_rcv_probes_ucast) }, + { NDTA_UNSPEC, "periodic_gc_runs", DT_U64, 0, MEMBER(ndt_stats, ndts_periodic_gc_runs) }, + { NDTA_UNSPEC, "forced_gc_runs", DT_U64, 0, MEMBER(ndt_stats, ndts_forced_gc_runs) }, + } +}; + +static const uc_nl_nested_spec_t neightbl_msg = { + .headsize = NLA_ALIGN(sizeof(struct ndtmsg)), + .nattrs = 9, + .attrs = { + { NDTA_UNSPEC, "family", DT_U8, 0, MEMBER(ndtmsg, ndtm_family) }, + { NDTA_NAME, "name", DT_STRING, 0, NULL }, + { NDTA_THRESH1, "thresh1", DT_U32, 0, NULL }, + { NDTA_THRESH2, "thresh2", DT_U32, 0, NULL }, + { NDTA_THRESH3, "thresh3", DT_U32, 0, NULL }, + { NDTA_GC_INTERVAL, "gc_interval", DT_U64, 0, NULL }, + { NDTA_PARMS, "params", DT_NESTED, 0, &neightbl_params_rta }, + { NDTA_CONFIG, "config", DT_NESTED, DF_NO_SET, &neightbl_config_rta }, + { NDTA_STATS, "stats", DT_NESTED, DF_NO_SET, &neightbl_stats_rta }, + } +}; + +static const uc_nl_nested_spec_t netconf_msg = { + .headsize = NLA_ALIGN(sizeof(struct netconfmsg)), + .nattrs = 8, + .attrs = { + { NETCONFA_UNSPEC, "family", DT_U8, 0, MEMBER(netconfmsg, ncm_family) }, + { NETCONFA_IFINDEX, "dev", DT_NETDEV, 0, NULL }, + { NETCONFA_FORWARDING, "forwarding", DT_U32, DF_NO_SET, NULL }, + { NETCONFA_RP_FILTER, "rp_filter", DT_U32, DF_NO_SET, NULL }, + { NETCONFA_MC_FORWARDING, "mc_forwarding", DT_U32, DF_NO_SET, NULL }, + { NETCONFA_PROXY_NEIGH, "proxy_neigh", DT_U32, DF_NO_SET, NULL }, + { NETCONFA_IGNORE_ROUTES_WITH_LINKDOWN, "ignore_routes_with_linkdown", DT_U32, DF_NO_SET, NULL }, + { NETCONFA_INPUT, "input", DT_U32, DF_NO_SET, NULL }, + } +}; + + +static bool +nla_check_len(struct nlattr *nla, size_t sz) +{ + return (nla && nla_len(nla) >= (ssize_t)sz); +} + +static bool +nla_parse_error(const uc_nl_attr_spec_t *spec, uc_vm_t *vm, uc_value_t *v, const char *msg) +{ + char *s; + + s = ucv_to_string(vm, v); + + set_error(NLE_INVAL, "%s `%s` has invalid value `%s`: %s", + spec->attr ? "attribute" : "field", + spec->key, + s, + msg); + + free(s); + + return false; +} + +static void +uc_nl_put_struct_member(char *base, const void *offset, size_t datalen, void *data) +{ + memcpy(base + (uintptr_t)offset, data, datalen); +} + +static void +uc_nl_put_struct_member_u8(char *base, const void *offset, uint8_t u8) +{ + base[(uintptr_t)offset] = u8; +} + +static void +uc_nl_put_struct_member_u16(char *base, const void *offset, uint16_t u16) +{ + uc_nl_put_struct_member(base, offset, sizeof(u16), &u16); +} + +static void +uc_nl_put_struct_member_u32(char *base, const void *offset, uint32_t u32) +{ + uc_nl_put_struct_member(base, offset, sizeof(u32), &u32); +} + +static void * +uc_nl_get_struct_member(char *base, const void *offset, size_t datalen, void *data) +{ + memcpy(data, base + (uintptr_t)offset, datalen); + + return data; +} + +static uint8_t +uc_nl_get_struct_member_u8(char *base, const void *offset) +{ + return (uint8_t)base[(uintptr_t)offset]; +} + +static uint16_t +uc_nl_get_struct_member_u16(char *base, const void *offset) +{ + uint16_t u16; + + uc_nl_get_struct_member(base, offset, sizeof(u16), &u16); + + return u16; +} + +static uint32_t +uc_nl_get_struct_member_u32(char *base, const void *offset) +{ + uint32_t u32; + + uc_nl_get_struct_member(base, offset, sizeof(u32), &u32); + + return u32; +} + +static uint64_t +uc_nl_get_struct_member_u64(char *base, const void *offset) +{ + uint64_t u64; + + uc_nl_get_struct_member(base, offset, sizeof(u64), &u64); + + return u64; +} + +static bool +uc_nl_parse_attr(const uc_nl_attr_spec_t *spec, struct nl_msg *msg, char *base, uc_vm_t *vm, uc_value_t *val, size_t idx); + +static uc_value_t * +uc_nl_convert_attr(const uc_nl_attr_spec_t *spec, struct nl_msg *msg, char *base, struct nlattr **tb, uc_vm_t *vm); + +static bool +uc_nl_convert_attrs(struct nl_msg *msg, void *buf, size_t buflen, size_t headsize, const uc_nl_attr_spec_t *attrs, size_t nattrs, uc_vm_t *vm, uc_value_t *obj) +{ + struct nlattr **tb, *nla, *nla_nest; + size_t i, maxattr = 0; + uc_value_t *v, *arr; + int rem; + + for (i = 0; i < nattrs; i++) + if (attrs[i].attr > maxattr) + maxattr = attrs[i].attr; + + tb = calloc(maxattr + 1, sizeof(struct nlattr *)); + + if (!tb) + return false; + + nla_parse(tb, maxattr, buf + headsize, buflen - headsize, NULL); + + for (i = 0; i < nattrs; i++) { + if (attrs[i].attr != 0 && !tb[attrs[i].attr]) + continue; + + if (attrs[i].flags & DF_NO_GET) + continue; + + if (attrs[i].flags & DF_MULTIPLE) { + arr = ucv_array_new(vm); + nla_nest = tb[attrs[i].attr]; + + nla_for_each_attr(nla, nla_data(nla_nest), nla_len(nla_nest), rem) { + if (attrs[i].auxdata && nla_type(nla) != (intptr_t)attrs[i].auxdata) + continue; + + tb[attrs[i].attr] = nla; + + v = uc_nl_convert_attr(&attrs[i], msg, (char *)buf, tb, vm); + + if (!v) + continue; + + ucv_array_push(arr, v); + } + + if (!ucv_array_length(arr)) { + ucv_put(arr); + + continue; + } + + v = arr; + } + else { + v = uc_nl_convert_attr(&attrs[i], msg, (char *)buf, tb, vm); + + if (!v) + continue; + } + + ucv_object_add(obj, attrs[i].key, v); + } + + free(tb); + + return true; +} + +static bool +uc_nl_parse_attrs(struct nl_msg *msg, char *base, const uc_nl_attr_spec_t *attrs, size_t nattrs, uc_vm_t *vm, uc_value_t *obj) +{ + struct nlattr *nla_nest = NULL; + size_t i, j, idx; + uc_value_t *v; + bool exists; + + for (i = 0; i < nattrs; i++) { + v = ucv_object_get(obj, attrs[i].key, &exists); + + if (!exists) + continue; + + if (attrs[i].flags & DF_MULTIPLE) { + if (!(attrs[i].flags & DF_FLAT)) + nla_nest = nla_nest_start(msg, attrs[i].attr); + + if (ucv_type(v) == UC_ARRAY) { + for (j = 0; j < ucv_array_length(v); j++) { + if (attrs[i].flags & DF_FLAT) + idx = attrs[i].attr; + else if (attrs[i].auxdata) + idx = (uintptr_t)attrs[i].auxdata; + else + idx = j; + + if (!uc_nl_parse_attr(&attrs[i], msg, base, vm, ucv_array_get(v, j), idx)) + return false; + } + } + else { + if (attrs[i].flags & DF_FLAT) + idx = attrs[i].attr; + else if (attrs[i].auxdata) + idx = (uintptr_t)attrs[i].auxdata; + else + idx = 0; + + if (!uc_nl_parse_attr(&attrs[i], msg, base, vm, v, idx)) + return false; + } + + if (nla_nest) + nla_nest_end(msg, nla_nest); + } + else if (!uc_nl_parse_attr(&attrs[i], msg, base, vm, v, 0)) { + return false; + } + } + + return true; +} + +static bool +uc_nl_parse_rta_nexthop(struct nl_msg *msg, uc_vm_t *vm, uc_value_t *val) +{ + struct { uint16_t family; char addr[sizeof(struct in6_addr)]; } via; + struct nlmsghdr *hdr = nlmsg_hdr(msg); + struct rtmsg *rtm = NLMSG_DATA(hdr); + struct nlattr *rta_gateway; + struct rtnexthop *rtnh; + uc_nl_cidr_t cidr = { 0 }; + uc_value_t *v; + uint32_t u; + int aflen; + char *s; + + if (ucv_type(val) != UC_OBJECT) + return false; + + if (uc_nl_parse_cidr(vm, ucv_object_get(val, "via", NULL), &cidr)) + return false; + + aflen = (cidr.family == AF_INET6 ? sizeof(cidr.addr.in6) : sizeof(cidr.addr.in)); + + if (cidr.mask != (aflen * 8)) + return false; + + rta_gateway = nla_reserve(msg, RTA_GATEWAY, sizeof(*rtnh)); + + rtnh = nla_data(rta_gateway); + rtnh->rtnh_len = sizeof(*rtnh); + + if (rtm->rtm_family == AF_UNSPEC) + rtm->rtm_family = cidr.family; + + if (cidr.family == rtm->rtm_family) { + nla_put(msg, RTA_GATEWAY, aflen, &cidr.addr.in6); + rtnh->rtnh_len += nla_total_size(aflen); + } + else { + via.family = cidr.family; + memcpy(via.addr, &cidr.addr.in6, aflen); + nla_put(msg, RTA_VIA, sizeof(via.family) + aflen, &via); + rtnh->rtnh_len += nla_total_size(sizeof(via.family) + aflen); + } + + v = ucv_object_get(val, "dev", NULL); + s = ucv_string_get(v); + + if (s) { + rtnh->rtnh_ifindex = if_nametoindex(s); + + if (rtnh->rtnh_ifindex == 0) + return false; + } + + v = ucv_object_get(val, "weight", NULL); + + if (v) { + if (!uc_nl_parse_u32(v, &u) || u == 0 || u > 256) + return false; + + rtnh->rtnh_hops = u - 1; + } + + if (ucv_is_truish(ucv_object_get(val, "onlink", NULL))) + rtnh->rtnh_flags |= RTNH_F_ONLINK; + + v = ucv_object_get(val, "realm", NULL); + + if (v) { + if (!uc_nl_parse_u32(v, &u)) + return false; + + nla_put_u32(msg, RTA_FLOW, u); + rtnh->rtnh_len += nla_total_size(sizeof(uint32_t)); + } + + v = ucv_object_get(val, "as", NULL); + + if (v) { + if (!uc_nl_parse_cidr(vm, v, &cidr) || cidr.family != rtm->rtm_family) + return false; + + if (cidr.mask != cidr.bitlen) + return false; + + nla_put(msg, RTA_NEWDST, cidr.alen, &cidr.addr.in6); + rtnh->rtnh_len += nla_total_size(cidr.alen); + } + + /* XXX: nla_nest_end(rta_gateway) ? */ + + return true; +} + +static bool +uc_nl_parse_rta_multipath(const uc_nl_attr_spec_t *spec, struct nl_msg *msg, char *base, uc_vm_t *vm, uc_value_t *val) +{ + struct nlattr *rta_multipath = nla_nest_start(msg, spec->attr); + size_t i; + + for (i = 0; i < ucv_array_length(val); i++) + if (!uc_nl_parse_rta_nexthop(msg, vm, ucv_array_get(val, i))) + return false; + + nla_nest_end(msg, rta_multipath); + + return true; +} + +static uc_value_t * +uc_nl_convert_rta_encap(const uc_nl_attr_spec_t *spec, struct nl_msg *msg, struct nlattr **tb, uc_vm_t *vm); + +static uc_value_t * +uc_nl_convert_rta_multipath(const uc_nl_attr_spec_t *spec, struct nl_msg *msg, struct nlattr **tb, uc_vm_t *vm) +{ + uc_nl_attr_spec_t encap_spec = { .attr = RTA_ENCAP }; + struct rtnexthop *nh = nla_data(tb[spec->attr]); + struct nlattr *multipath_tb[RTA_MAX + 1]; + size_t len = nla_len(tb[spec->attr]); + uc_value_t *nh_obj, *nh_arr; + char buf[INET6_ADDRSTRLEN]; + struct rtvia *via; + int af; + + nh_arr = ucv_array_new(vm); + + while (len >= sizeof(*nh)) { + if ((size_t)NLA_ALIGN(nh->rtnh_len) > len) + break; + + nh_obj = ucv_object_new(vm); + ucv_array_push(nh_arr, nh_obj); + + nla_parse(multipath_tb, RTA_MAX + 1, (struct nlattr *)RTNH_DATA(nh), nh->rtnh_len - sizeof(*nh), NULL); + + if (multipath_tb[RTA_GATEWAY]) { + switch (nla_len(multipath_tb[RTA_GATEWAY])) { + case 4: af = AF_INET; break; + case 16: af = AF_INET6; break; + default: af = AF_UNSPEC; break; + } + + if (inet_ntop(af, nla_data(multipath_tb[RTA_GATEWAY]), buf, sizeof(buf))) + ucv_object_add(nh_obj, "via", ucv_string_new(buf)); + } + + if (multipath_tb[RTA_VIA]) { + if (nla_len(multipath_tb[RTA_VIA]) > (ssize_t)sizeof(*via)) { + via = nla_data(multipath_tb[RTA_VIA]); + af = via->rtvia_family; + + if ((af == AF_INET && + nla_len(multipath_tb[RTA_VIA]) == sizeof(*via) + sizeof(struct in_addr)) || + (af == AF_INET6 && + nla_len(multipath_tb[RTA_VIA]) == sizeof(*via) + sizeof(struct in6_addr))) { + if (inet_ntop(af, via->rtvia_addr, buf, sizeof(buf))) + ucv_object_add(nh_obj, "via", ucv_string_new(buf)); + } + } + } + + if (if_indextoname(nh->rtnh_ifindex, buf)) + ucv_object_add(nh_obj, "dev", ucv_string_new(buf)); + + ucv_object_add(nh_obj, "weight", ucv_int64_new(nh->rtnh_hops + 1)); + ucv_object_add(nh_obj, "onlink", ucv_boolean_new(nh->rtnh_flags & RTNH_F_ONLINK)); + + if (multipath_tb[RTA_FLOW] && nla_len(multipath_tb[RTA_FLOW]) == sizeof(uint32_t)) + ucv_object_add(nh_obj, "realm", ucv_int64_new(nla_get_u32(multipath_tb[RTA_FLOW]))); + + if (multipath_tb[RTA_ENCAP]) + ucv_object_add(nh_obj, "encap", + uc_nl_convert_rta_encap(&encap_spec, msg, multipath_tb, vm)); + + if (multipath_tb[RTA_NEWDST]) { + switch (nla_len(multipath_tb[RTA_NEWDST])) { + case 4: af = AF_INET; break; + case 16: af = AF_INET6; break; + default: af = AF_UNSPEC; break; + } + + if (inet_ntop(af, nla_data(multipath_tb[RTA_NEWDST]), buf, sizeof(buf))) + ucv_object_add(nh_obj, "as", ucv_string_new(buf)); + } + + len -= NLA_ALIGN(nh->rtnh_len); + nh = RTNH_NEXT(nh); + } + + return nh_arr; +} + +static bool +parse_num(const uc_nl_attr_spec_t *spec, uc_vm_t *vm, uc_value_t *val, void *dst) +{ + int64_t n = ucv_int64_get(val); + uint32_t *u32; + uint16_t *u16; + uint8_t *u8; + + if (spec->flags & DF_MAX_255) { + if (n < 0 || n > 255) + return nla_parse_error(spec, vm, val, "number out of range 0-255"); + + u8 = dst; *u8 = n; + } + else if (spec->flags & DF_MAX_65535) { + if (n < 0 || n > 65535) + return nla_parse_error(spec, vm, val, "number out of range 0-65535"); + + u16 = dst; *u16 = n; + + if (spec->flags & DF_BYTESWAP) + *u16 = htons(*u16); + } + else if (spec->flags & DF_MAX_16777215) { + if (n < 0 || n > 16777215) + return nla_parse_error(spec, vm, val, "number out of range 0-16777215"); + + u32 = dst; *u32 = n; + + if (spec->flags & DF_BYTESWAP) + *u32 = htonl(*u32); + } + else { + if (n < 0 || n > 4294967295) + return nla_parse_error(spec, vm, val, "number out of range 0-4294967295"); + + u32 = dst; *u32 = n; + + if (spec->flags & DF_BYTESWAP) + *u32 = htonl(*u32); + } + + return true; +} + +static bool +uc_nl_parse_rta_numrange(const uc_nl_attr_spec_t *spec, struct nl_msg *msg, char *base, uc_vm_t *vm, uc_value_t *val) +{ + union { + struct { uint8_t low; uint8_t high; } u8; + struct { uint16_t low; uint16_t high; } u16; + struct { uint32_t low; uint32_t high; } u32; + } ranges = { 0 }; + + void *d1, *d2; + size_t len; + + if (ucv_array_length(val) != 2 || + ucv_type(ucv_array_get(val, 0)) != UC_INTEGER || + ucv_type(ucv_array_get(val, 1)) != UC_INTEGER) + return nla_parse_error(spec, vm, val, "not a two-element array of numbers"); + + if (spec->flags & DF_MAX_255) { + len = sizeof(ranges.u8); + d1 = &ranges.u8.low; + d2 = &ranges.u8.high; + } + else if (spec->flags & DF_MAX_65535) { + len = sizeof(ranges.u16); + d1 = &ranges.u16.low; + d2 = &ranges.u16.high; + } + else { + len = sizeof(ranges.u32); + d1 = &ranges.u32.low; + d2 = &ranges.u32.high; + } + + if (!parse_num(spec, vm, ucv_array_get(val, 0), d1) || + !parse_num(spec, vm, ucv_array_get(val, 1), d2)) + return false; + + nla_put(msg, spec->attr, len, d1); + + return true; +} + +static uc_value_t * +uc_nl_convert_rta_numrange(const uc_nl_attr_spec_t *spec, struct nl_msg *msg, struct nlattr **tb, uc_vm_t *vm) +{ + union { + struct { uint8_t low; uint8_t high; } *u8; + struct { uint16_t low; uint16_t high; } *u16; + struct { uint32_t low; uint32_t high; } *u32; + } ranges = { 0 }; + + bool swap = (spec->flags & DF_BYTESWAP); + uc_value_t *arr, *n1, *n2; + + if (spec->flags & DF_MAX_255) { + if (!nla_check_len(tb[spec->attr], sizeof(*ranges.u8))) + return NULL; + + ranges.u8 = nla_data(tb[spec->attr]); + n1 = ucv_int64_new(ranges.u8->low); + n2 = ucv_int64_new(ranges.u8->high); + } + else if (spec->flags & DF_MAX_65535) { + if (!nla_check_len(tb[spec->attr], sizeof(*ranges.u16))) + return NULL; + + ranges.u16 = nla_data(tb[spec->attr]); + n1 = ucv_int64_new(swap ? ntohs(ranges.u16->low) : ranges.u16->low); + n2 = ucv_int64_new(swap ? ntohs(ranges.u16->high) : ranges.u16->high); + } + else { + if (!nla_check_len(tb[spec->attr], sizeof(*ranges.u32))) + return NULL; + + ranges.u32 = nla_data(tb[spec->attr]); + n1 = ucv_int64_new(swap ? ntohl(ranges.u32->low) : ranges.u32->low); + n2 = ucv_int64_new(swap ? ntohl(ranges.u32->high) : ranges.u32->high); + } + + arr = ucv_array_new(vm); + + ucv_array_push(arr, n1); + ucv_array_push(arr, n2); + + return arr; +} + + +#define LINK_TYPE(name) \ + { #name, link_##name##_attrs, ARRAY_SIZE(link_##name##_attrs) } + +static const struct { + const char *name; + const uc_nl_attr_spec_t *attrs; + size_t nattrs; +} link_types[] = { + LINK_TYPE(bareudp), + LINK_TYPE(bond), + LINK_TYPE(bond_slave), + LINK_TYPE(bridge), + LINK_TYPE(bridge_slave), + LINK_TYPE(geneve), + LINK_TYPE(hsr), + LINK_TYPE(ipoib), + LINK_TYPE(ipvlan), + LINK_TYPE(macvlan), + LINK_TYPE(rmnet), + LINK_TYPE(vlan), + LINK_TYPE(vrf), + //LINK_TYPE(vxcan), + LINK_TYPE(vxlan), + //LINK_TYPE(xdp), + //LINK_TYPE(xstats), + LINK_TYPE(gre), + LINK_TYPE(gretap), + LINK_TYPE(erspan), + LINK_TYPE(ip6gre), + LINK_TYPE(ip6gretap), + LINK_TYPE(ip6erspan), + LINK_TYPE(ip6tnl), + LINK_TYPE(ipip), + LINK_TYPE(sit), + LINK_TYPE(veth), + LINK_TYPE(vti), + LINK_TYPE(vti6), + LINK_TYPE(xfrm), +}; + +static bool +uc_nl_parse_rta_linkinfo(const uc_nl_attr_spec_t *spec, struct nl_msg *msg, char *base, uc_vm_t *vm, uc_value_t *val) +{ + const uc_nl_attr_spec_t *attrs = NULL; + struct nlattr *li_nla, *info_nla; + size_t i, nattrs = 0; + char *kind, *p; + uc_value_t *k; + + k = ucv_object_get(val, "type", NULL); + kind = ucv_string_get(k); + + if (!kind) + return nla_parse_error(spec, vm, val, "linkinfo does not specify kind"); + + li_nla = nla_nest_start(msg, spec->attr); + + nla_put_string(msg, IFLA_INFO_KIND, kind); + + for (i = 0; i < ARRAY_SIZE(link_types); i++) { + if (!strcmp(link_types[i].name, kind)) { + attrs = link_types[i].attrs; + nattrs = link_types[i].nattrs; + break; + } + } + + p = strchr(kind, '_'); + + if (!p || strcmp(p, "_slave")) + info_nla = nla_nest_start(msg, IFLA_INFO_DATA); + else + info_nla = nla_nest_start(msg, IFLA_INFO_SLAVE_DATA); + + if (!uc_nl_parse_attrs(msg, base, attrs, nattrs, vm, val)) + return false; + + nla_nest_end(msg, info_nla); + nla_nest_end(msg, li_nla); + + return true; +} + +static uc_value_t * +uc_nl_convert_rta_linkinfo_data(uc_value_t *obj, size_t attr, struct nl_msg *msg, struct nlattr **tb, uc_vm_t *vm) +{ + const uc_nl_attr_spec_t *attrs = NULL; + size_t i, nattrs = 0; + uc_value_t *v; + bool rv; + + if (!tb[attr] || nla_len(tb[attr]) < 1) + return NULL; + + v = ucv_string_new_length(nla_data(tb[attr]), nla_len(tb[attr]) - 1); + + ucv_object_add(obj, "type", v); + + for (i = 0; i < ARRAY_SIZE(link_types); i++) { + if (!strcmp(link_types[i].name, ucv_string_get(v))) { + attrs = link_types[i].attrs; + nattrs = link_types[i].nattrs; + break; + } + } + + if (nattrs > 0) { + attr = (attr == IFLA_INFO_KIND) ? IFLA_INFO_DATA : IFLA_INFO_SLAVE_DATA; + rv = uc_nl_convert_attrs(msg, nla_data(tb[attr]), nla_len(tb[attr]), 0, attrs, nattrs, vm, obj); + + if (!rv) + return NULL; + } + + return obj; +} + +static uc_value_t * +uc_nl_convert_rta_linkinfo(const uc_nl_attr_spec_t *spec, struct nl_msg *msg, struct nlattr **tb, uc_vm_t *vm) +{ + struct nlattr *linkinfo_tb[IFLA_INFO_MAX]; + uc_value_t *info_obj, *slave_obj; + + if (!tb[spec->attr]) + return NULL; + + nla_parse(linkinfo_tb, IFLA_INFO_MAX, nla_data(tb[spec->attr]), nla_len(tb[spec->attr]), NULL); + + info_obj = ucv_object_new(vm); + + if (linkinfo_tb[IFLA_INFO_KIND]) { + if (!uc_nl_convert_rta_linkinfo_data(info_obj, IFLA_INFO_KIND, msg, linkinfo_tb, vm)) { + ucv_put(info_obj); + + return NULL; + } + } + + if (linkinfo_tb[IFLA_INFO_SLAVE_KIND]) { + slave_obj = ucv_object_new(vm); + + if (!uc_nl_convert_rta_linkinfo_data(slave_obj, IFLA_INFO_SLAVE_KIND, msg, linkinfo_tb, vm)) { + ucv_put(info_obj); + ucv_put(slave_obj); + + return NULL; + } + + ucv_object_add(info_obj, "slave", slave_obj); + } + + return info_obj; +} + +static uc_value_t * +uc_nl_convert_rta_bridgeid(const uc_nl_attr_spec_t *spec, struct nl_msg *msg, struct nlattr **tb, uc_vm_t *vm) +{ + char buf[sizeof("ffff.ff:ff:ff:ff:ff:ff")]; + struct ifla_bridge_id *id; + + if (!nla_check_len(tb[spec->attr], sizeof(*id))) + return NULL; + + id = nla_data(tb[spec->attr]); + + snprintf(buf, sizeof(buf), "%02x%02x.%02x:%02x:%02x:%02x:%02x:%02x", + id->prio[0], id->prio[1], + id->addr[0], id->addr[1], + id->addr[2], id->addr[3], + id->addr[4], id->addr[5]); + + return ucv_string_new(buf); +} + +static bool +uc_nl_parse_rta_srh(const uc_nl_attr_spec_t *spec, struct nl_msg *msg, char *base, uc_vm_t *vm, uc_value_t *val) +{ + uc_value_t *mode, *hmac, *segs, *seg; + struct seg6_iptunnel_encap *tun; + struct sr6_tlv_hmac *tlv; + struct ipv6_sr_hdr *srh; + size_t i, nsegs, srhlen; + char *s; + + mode = ucv_object_get(val, "mode", NULL); + hmac = ucv_object_get(val, "hmac", NULL); + segs = ucv_object_get(val, "segs", NULL); + + if (mode != NULL && + (ucv_type(mode) != UC_INTEGER || + ucv_int64_get(mode) < 0 || + ucv_int64_get(mode) > UINT32_MAX)) + return nla_parse_error(spec, vm, val, "srh mode not an integer in range 0-4294967295"); + + if (hmac != NULL && + (ucv_type(hmac) != UC_INTEGER || + ucv_int64_get(hmac) < 0 || + ucv_int64_get(hmac) > UINT32_MAX)) + return nla_parse_error(spec, vm, val, "srh hmac not an integer in range 0-4294967295"); + + if (ucv_type(segs) != UC_ARRAY || + ucv_array_length(segs) == 0) + return nla_parse_error(spec, vm, val, "srh segs array missing or empty"); + + nsegs = ucv_array_length(segs); + + if (!mode || !ucv_int64_get(mode)) + nsegs++; + + srhlen = 8 + 16 * nsegs; + + if (hmac && ucv_int64_get(hmac)) + srhlen += 40; + + + tun = calloc(1, sizeof(*tun) + srhlen); + + if (!tun) + return nla_parse_error(spec, vm, val, "cannot allocate srh header"); + + tun->mode = (int)ucv_int64_get(mode); + + srh = tun->srh; + srh->hdrlen = (srhlen >> 3) - 1; + srh->type = 4; + srh->segments_left = nsegs - 1; + srh->first_segment = nsegs - 1; + + if (hmac && ucv_int64_get(hmac)) + srh->flags |= SR6_FLAG1_HMAC; + + for (i = 0; i < ucv_array_length(segs); i++) { + seg = ucv_array_get(segs, i); + s = ucv_string_get(seg); + + if (!s || inet_pton(AF_INET6, s, &srh->segments[--nsegs]) != 1) { + free(tun); + + return nla_parse_error(spec, vm, val, "srh segs array contains invalid IPv6 address"); + } + } + + if (hmac && ucv_int64_get(hmac)) { + tlv = (struct sr6_tlv_hmac *)((char *)srh + srhlen - 40); + tlv->tlvhdr.type = SR6_TLV_HMAC; + tlv->tlvhdr.len = 38; + tlv->hmackeyid = htonl((uint32_t)ucv_int64_get(hmac)); + } + + nla_put(msg, spec->attr, sizeof(*tun) + srhlen, tun); + free(tun); + + return true; +} + +static uc_value_t * +uc_nl_convert_rta_srh(const uc_nl_attr_spec_t *spec, struct nl_msg *msg, struct nlattr **tb, uc_vm_t *vm) +{ + char buf[INET6_ADDRSTRLEN], *p, *e; + struct seg6_iptunnel_encap *tun; + uc_value_t *tun_obj, *seg_arr; + struct sr6_tlv_hmac *tlv; + size_t i; + + if (!nla_check_len(tb[spec->attr], sizeof(*tun))) + return NULL; + + tun = nla_data(tb[spec->attr]); + tun_obj = ucv_object_new(vm); + + ucv_object_add(tun_obj, "mode", ucv_int64_new(tun->mode)); + + seg_arr = ucv_array_new(vm); + + p = (char *)tun->srh->segments; + e = (char *)tun + nla_len(tb[spec->attr]); + + for (i = tun->srh->first_segment + 1; + p + sizeof(struct in6_addr) <= e && i > 0; + i--, p += sizeof(struct in6_addr)) { + if (inet_ntop(AF_INET6, p, buf, sizeof(buf))) + ucv_array_push(seg_arr, ucv_string_new(buf)); + else + ucv_array_push(seg_arr, NULL); + } + + ucv_object_add(tun_obj, "segs", seg_arr); + + if (sr_has_hmac(tun->srh)) { + i = ((tun->srh->hdrlen + 1) << 3) - 40; + tlv = (struct sr6_tlv_hmac *)((char *)tun->srh + i); + + ucv_object_add(tun_obj, "hmac", ucv_int64_new(ntohl(tlv->hmackeyid))); + } + + return tun_obj; +} + +#define ENCAP_TYPE(name, type) \ + { #name, LWTUNNEL_ENCAP_##type, route_encap_##name##_attrs, ARRAY_SIZE(route_encap_##name##_attrs) } + +static const struct { + const char *name; + uint16_t type; + const uc_nl_attr_spec_t *attrs; + size_t nattrs; +} encap_types[] = { + ENCAP_TYPE(mpls, MPLS), + ENCAP_TYPE(ip, IP), + ENCAP_TYPE(ip6, IP6), + ENCAP_TYPE(ila, ILA), + //ENCAP_TYPE(bpf, BPF), + ENCAP_TYPE(seg6, SEG6), + //ENCAP_TYPE(seg6local, SEG6_LOCAL), + //ENCAP_TYPE(rpl, RPL), +}; + +static bool +uc_nl_parse_rta_encap(const uc_nl_attr_spec_t *spec, struct nl_msg *msg, char *base, uc_vm_t *vm, uc_value_t *val) +{ + const uc_nl_attr_spec_t *attrs = NULL; + struct nlattr *enc_nla; + size_t i, nattrs = 0; + uint16_t ntype = 0; + uc_value_t *t; + char *type; + + t = ucv_object_get(val, "type", NULL); + type = ucv_string_get(t); + + if (!type) + return nla_parse_error(spec, vm, val, "encap does not specify type"); + + for (i = 0; i < ARRAY_SIZE(encap_types); i++) { + if (!strcmp(encap_types[i].name, type)) { + ntype = encap_types[i].type; + attrs = encap_types[i].attrs; + nattrs = encap_types[i].nattrs; + break; + } + } + + if (!ntype) + return nla_parse_error(spec, vm, val, "encap specifies unknown type"); + + nla_put_u16(msg, RTA_ENCAP_TYPE, ntype); + + enc_nla = nla_nest_start(msg, spec->attr); + + if (!uc_nl_parse_attrs(msg, base, attrs, nattrs, vm, val)) + return false; + + nla_nest_end(msg, enc_nla); + + return true; +} + +static uc_value_t * +uc_nl_convert_rta_encap(const uc_nl_attr_spec_t *spec, struct nl_msg *msg, struct nlattr **tb, uc_vm_t *vm) +{ + const uc_nl_attr_spec_t *attrs = NULL; + const char *name = NULL; + uc_value_t *encap_obj; + size_t i, nattrs = 0; + bool rv; + + if (!tb[spec->attr] || + !nla_check_len(tb[RTA_ENCAP_TYPE], sizeof(uint16_t))) + return NULL; + + for (i = 0; i < ARRAY_SIZE(encap_types); i++) { + if (encap_types[i].type != nla_get_u16(tb[RTA_ENCAP_TYPE])) + continue; + + name = encap_types[i].name; + attrs = encap_types[i].attrs; + nattrs = encap_types[i].nattrs; + + break; + } + + if (!name) + return NULL; + + encap_obj = ucv_object_new(vm); + + rv = uc_nl_convert_attrs(msg, + nla_data(tb[spec->attr]), nla_len(tb[spec->attr]), 0, + attrs, nattrs, vm, encap_obj); + + if (!rv) { + ucv_put(encap_obj); + + return NULL; + } + + ucv_object_add(encap_obj, "type", ucv_string_new(name)); + + return encap_obj; +} + +#define IPOPTS_TYPE(name, type, multiple) \ + { #name, LWTUNNEL_IP_OPTS_##type, multiple, lwtipopt_##name##_attrs, ARRAY_SIZE(lwtipopt_##name##_attrs) } + +static const struct { + const char *name; + uint16_t type; + bool multiple; + const uc_nl_attr_spec_t *attrs; + size_t nattrs; +} lwtipopt_types[] = { + IPOPTS_TYPE(erspan, ERSPAN, false), + IPOPTS_TYPE(geneve, GENEVE, true), + IPOPTS_TYPE(vxlan, VXLAN, false), +}; + +static bool +uc_nl_parse_rta_ipopts(const uc_nl_attr_spec_t *spec, struct nl_msg *msg, char *base, uc_vm_t *vm, uc_value_t *val) +{ + const uc_nl_attr_spec_t *attrs = NULL; + struct nlattr *opt_nla, *type_nla; + bool exists, multiple = false; + size_t i, j, nattrs = 0; + uint16_t ntype = 0; + uc_value_t *item; + + ucv_object_foreach(val, type, v) { + for (i = 0; i < ARRAY_SIZE(lwtipopt_types); i++) { + if (!strcmp(lwtipopt_types[i].name, type)) { + val = v; + ntype = lwtipopt_types[i].type; + attrs = lwtipopt_types[i].attrs; + nattrs = lwtipopt_types[i].nattrs; + multiple = lwtipopt_types[i].multiple; + break; + } + } + } + + if (!ntype) + return nla_parse_error(spec, vm, val, "unknown IP options type specified"); + + opt_nla = nla_nest_start(msg, spec->attr); + + j = 0; + item = (ucv_type(val) == UC_ARRAY) ? ucv_array_get(val, j++) : val; + + while (true) { + type_nla = nla_nest_start(msg, ntype); + + for (i = 0; i < nattrs; i++) { + v = ucv_object_get(item, attrs[i].key, &exists); + + if (!exists) + continue; + + if (!uc_nl_parse_attr(&attrs[i], msg, nla_data(type_nla), vm, v, 0)) + return false; + } + + nla_nest_end(msg, type_nla); + + if (!multiple || ucv_type(val) != UC_ARRAY || j >= ucv_array_length(val)) + break; + + item = ucv_array_get(val, j++); + } + + nla_nest_end(msg, opt_nla); + + return true; +} + +static uc_value_t * +uc_nl_convert_rta_ipopts(const uc_nl_attr_spec_t *spec, struct nl_msg *msg, struct nlattr **tb, uc_vm_t *vm) +{ + struct nlattr *opt_tb[LWTUNNEL_IP_OPTS_MAX + 1]; + const uc_nl_attr_spec_t *attrs = NULL; + uc_value_t *opt_obj, *type_obj; + const char *name = NULL; + size_t i, nattrs = 0; + uint16_t type = 0; + bool rv; + + if (!tb[spec->attr] || + !nla_parse(opt_tb, LWTUNNEL_IP_OPTS_MAX, nla_data(tb[spec->attr]), nla_len(tb[spec->attr]), NULL)) + return NULL; + + for (i = 0; i < ARRAY_SIZE(lwtipopt_types); i++) { + if (!opt_tb[lwtipopt_types[i].type]) + continue; + + type = lwtipopt_types[i].type; + name = lwtipopt_types[i].name; + attrs = lwtipopt_types[i].attrs; + nattrs = lwtipopt_types[i].nattrs; + + break; + } + + if (!name) + return NULL; + + type_obj = ucv_object_new(vm); + + rv = uc_nl_convert_attrs(msg, + nla_data(opt_tb[type]), nla_len(opt_tb[type]), 0, + attrs, nattrs, vm, type_obj); + + if (!rv) { + ucv_put(type_obj); + + return NULL; + } + + opt_obj = ucv_object_new(vm); + + ucv_object_add(opt_obj, name, type_obj); + + return opt_obj; +} + +static bool +uc_nl_parse_rta_u32_or_member(const uc_nl_attr_spec_t *spec, struct nl_msg *msg, char *base, uc_vm_t *vm, uc_value_t *val) +{ + uint32_t u32; + + if (!uc_nl_parse_u32(val, &u32)) + return nla_parse_error(spec, vm, val, "not an integer or out of range 0-4294967295"); + + if (spec->flags & DF_MAX_255) { + if (u32 <= 255) { + uc_nl_put_struct_member_u8(base, spec->auxdata, u32); + + return true; + } + + uc_nl_put_struct_member_u8(base, spec->auxdata, 0); + } + else if (spec->flags & DF_MAX_65535) { + if (u32 <= 65535) { + uc_nl_put_struct_member_u16(base, spec->auxdata, + (spec->flags & DF_BYTESWAP) ? htons((uint16_t)u32) : (uint16_t)u32); + + return true; + } + + uc_nl_put_struct_member_u16(base, spec->auxdata, 0); + } + else if (spec->flags & DF_MAX_16777215) { + if (u32 <= 16777215) { + uc_nl_put_struct_member_u32(base, spec->auxdata, + (spec->flags & DF_BYTESWAP) ? htonl(u32) : u32); + + return true; + } + + uc_nl_put_struct_member_u32(base, spec->auxdata, 0); + } + + nla_put_u32(msg, spec->attr, + (spec->flags & DF_BYTESWAP) ? htonl(u32) : u32); + + return true; +} + +static uc_value_t * +uc_nl_convert_rta_u32_or_member(const uc_nl_attr_spec_t *spec, struct nl_msg *msg, char *base, struct nlattr **tb, uc_vm_t *vm) +{ + uint32_t u32 = 0; + + if (nla_check_len(tb[spec->attr], sizeof(uint32_t))) { + if (spec->flags & DF_BYTESWAP) + u32 = ntohl(nla_get_u32(tb[spec->attr])); + else + u32 = nla_get_u32(tb[spec->attr]); + } + else if (spec->flags & DF_MAX_255) { + u32 = uc_nl_get_struct_member_u8(base, spec->auxdata); + } + else if (spec->flags & DF_MAX_65535) { + if (spec->flags & DF_BYTESWAP) + u32 = ntohs(uc_nl_get_struct_member_u16(base, spec->auxdata)); + else + u32 = uc_nl_get_struct_member_u16(base, spec->auxdata); + } + else if (spec->flags & DF_MAX_16777215) { + if (spec->flags & DF_BYTESWAP) + u32 = ntohl(uc_nl_get_struct_member_u32(base, spec->auxdata)); + else + u32 = uc_nl_get_struct_member_u32(base, spec->auxdata); + } + else { + return NULL; + } + + return ucv_uint64_new(u32); +} + +static bool +uc_nl_parse_rta_nested(const uc_nl_attr_spec_t *spec, struct nl_msg *msg, char *base, uc_vm_t *vm, uc_value_t *val) +{ + const uc_nl_nested_spec_t *nest = spec->auxdata; + struct nlattr *nested_nla; + + nested_nla = nla_reserve(msg, spec->attr, nest->headsize); + + if (!uc_nl_parse_attrs(msg, nla_data(nested_nla), nest->attrs, nest->nattrs, vm, val)) + return false; + + nla_nest_end(msg, nested_nla); + + return true; +} + +static uc_value_t * +uc_nl_convert_rta_nested(const uc_nl_attr_spec_t *spec, struct nl_msg *msg, struct nlattr **tb, uc_vm_t *vm) +{ + const uc_nl_nested_spec_t *nest = spec->auxdata; + uc_value_t *nested_obj; + bool rv; + + if (!nla_check_len(tb[spec->attr], nest->headsize)) + return NULL; + + nested_obj = ucv_object_new(vm); + + rv = uc_nl_convert_attrs(msg, + nla_data(tb[spec->attr]), nla_len(tb[spec->attr]), nest->headsize, + nest->attrs, nest->nattrs, + vm, nested_obj); + + if (!rv) { + ucv_put(nested_obj); + + return NULL; + } + + return nested_obj; +} + + +static bool +uc_nl_parse_attr(const uc_nl_attr_spec_t *spec, struct nl_msg *msg, char *base, uc_vm_t *vm, uc_value_t *val, size_t idx) +{ + uc_nl_cidr_t cidr = { 0 }; + struct ether_addr *ea; + uint64_t u64; + uint32_t u32; + uint16_t u16; + size_t attr; + char *s; + + if (spec->flags & DF_MULTIPLE) + attr = idx; + else + attr = spec->attr; + + switch (spec->type) { + case DT_U8: + if (!uc_nl_parse_u32(val, &u32) || u32 > 255) + return nla_parse_error(spec, vm, val, "not an integer or out of range 0-255"); + + if ((spec->flags & DF_MAX_1) && u32 > 1) + return nla_parse_error(spec, vm, val, "integer must be 0 or 1"); + + if (spec->attr == 0) + uc_nl_put_struct_member_u8(base, spec->auxdata, u32); + else + nla_put_u8(msg, attr, u32); + + break; + + case DT_U16: + if (!uc_nl_parse_u32(val, &u32) || u32 > 65535) + return nla_parse_error(spec, vm, val, "not an integer or out of range 0-65535"); + + u16 = (uint16_t)u32; + + if (spec->flags & DF_BYTESWAP) + u16 = htons(u16); + + if ((spec->flags & DF_MAX_1) && u32 > 1) + return nla_parse_error(spec, vm, val, "integer must be 0 or 1"); + else if ((spec->flags & DF_MAX_255) && u32 > 255) + return nla_parse_error(spec, vm, val, "integer out of range 0-255"); + + if (spec->attr == 0) + uc_nl_put_struct_member_u16(base, spec->auxdata, u16); + else + nla_put_u16(msg, attr, u16); + + break; + + case DT_S32: + case DT_U32: + if (spec->type == DT_S32 && !uc_nl_parse_s32(val, &u32)) + return nla_parse_error(spec, vm, val, "not an integer or out of range -2147483648-2147483647"); + else if (spec->type == DT_U32 && !uc_nl_parse_u32(val, &u32)) + return nla_parse_error(spec, vm, val, "not an integer or out of range 0-4294967295"); + + if (spec->flags & DF_BYTESWAP) + u32 = htonl(u32); + + if ((spec->flags & DF_MAX_1) && u32 > 1) + return nla_parse_error(spec, vm, val, "integer must be 0 or 1"); + else if ((spec->flags & DF_MAX_255) && u32 > 255) + return nla_parse_error(spec, vm, val, "integer out of range 0-255"); + else if ((spec->flags & DF_MAX_65535) && u32 > 65535) + return nla_parse_error(spec, vm, val, "integer out of range 0-65535"); + else if ((spec->flags & DF_MAX_16777215) && u32 > 16777215) + return nla_parse_error(spec, vm, val, "integer out of range 0-16777215"); + + if (spec->attr == 0) + uc_nl_put_struct_member_u32(base, spec->auxdata, u32); + else + nla_put_u32(msg, attr, u32); + + break; + + case DT_U64: + assert(spec->attr != 0); + + if (!uc_nl_parse_u64(val, &u64)) + return nla_parse_error(spec, vm, val, "not an integer or negative"); + + if (spec->flags & DF_BYTESWAP) + u64 = htobe64(u64); + + nla_put_u64(msg, attr, u64); + break; + + case DT_BOOL: + u32 = (uint32_t)ucv_is_truish(val); + + if (spec->attr == 0) + uc_nl_put_struct_member_u8(base, spec->auxdata, u32); + else + nla_put_u8(msg, attr, u32); + + break; + + case DT_FLAG: + u32 = (uint32_t)ucv_is_truish(val); + + if (spec->attr == 0) + uc_nl_put_struct_member_u8(base, spec->auxdata, u32); + else if (u32 == 1) + nla_put_flag(msg, attr); + + break; + + case DT_STRING: + assert(spec->attr != 0); + + s = ucv_to_string(vm, val); + + if (!s) + return nla_parse_error(spec, vm, val, "out of memory"); + + nla_put_string(msg, attr, s); + free(s); + + break; + + case DT_NETDEV: + if (ucv_type(val) == UC_INTEGER) { + if (ucv_int64_get(val) < 0 || + ucv_int64_get(val) > UINT32_MAX) + return nla_parse_error(spec, vm, val, "interface index out of range 0-4294967295"); + + u32 = (uint32_t)ucv_int64_get(val); + } + else { + s = ucv_to_string(vm, val); + + if (!s) + return nla_parse_error(spec, vm, val, "out of memory"); + + u32 = if_nametoindex(s); + + free(s); + } + + if (u32 == 0 && !(spec->flags & DF_ALLOW_NONE)) + return nla_parse_error(spec, vm, val, "interface not found"); + + if (spec->attr == 0) + uc_nl_put_struct_member_u32(base, spec->auxdata, u32); + else + nla_put_u32(msg, attr, u32); + + break; + + case DT_LLADDR: + assert(spec->attr != 0); + + s = ucv_to_string(vm, val); + + if (!s) + return nla_parse_error(spec, vm, val, "out of memory"); + + ea = ether_aton(s); + + free(s); + + if (!ea) + return nla_parse_error(spec, vm, val, "invalid MAC address"); + + nla_put(msg, attr, sizeof(*ea), ea); + + break; + + case DT_U64ADDR: + assert(spec->attr != 0); + + if (ucv_type(val) == UC_INTEGER) { + u64 = ucv_uint64_get(val); + } + else { + s = ucv_to_string(vm, val); + + if (!s) + return nla_parse_error(spec, vm, val, "out of memory"); + + u16 = addr64_pton(s, &u64); + + free(s); + + if (u16 != 1) + return nla_parse_error(spec, vm, val, "invalid address"); + } + + nla_put_u64(msg, attr, u64); + + break; + + case DT_INADDR: + case DT_IN6ADDR: + case DT_MPLSADDR: + case DT_ANYADDR: + assert(spec->attr != 0); + + if (!uc_nl_parse_cidr(vm, val, &cidr)) + return nla_parse_error(spec, vm, val, "invalid IP address"); + + if ((spec->type == DT_INADDR && cidr.family != AF_INET) || + (spec->type == DT_IN6ADDR && cidr.family != AF_INET6) || + (spec->type == DT_MPLSADDR && cidr.family != AF_MPLS)) + return nla_parse_error(spec, vm, val, "wrong address family"); + + if (spec->flags & DF_STORE_MASK) + uc_nl_put_struct_member_u8(base, spec->auxdata, cidr.mask); + else if (cidr.mask != cidr.bitlen) + return nla_parse_error(spec, vm, val, "address range given but single address expected"); + + nla_put(msg, attr, cidr.alen, &cidr.addr.in6); + + break; + + case DT_MULTIPATH: + if (!uc_nl_parse_rta_multipath(spec, msg, base, vm, val)) + return nla_parse_error(spec, vm, val, "invalid nexthop data"); + + break; + + case DT_NUMRANGE: + if (!uc_nl_parse_rta_numrange(spec, msg, base, vm, val)) + return false; + + break; + + case DT_FLAGS: + if (ucv_array_length(val) == 2) { + if (ucv_type(ucv_array_get(val, 0)) != UC_INTEGER || + ucv_type(ucv_array_get(val, 1)) != UC_INTEGER) + return nla_parse_error(spec, vm, val, "flag or mask value not an integer"); + + if (!uc_nl_parse_u32(ucv_array_get(val, 0), &u32)) + return nla_parse_error(spec, vm, val, "flag value not an integer or out of range 0-4294967295"); + + memcpy(&u64, &u32, sizeof(u32)); + + if (!uc_nl_parse_u32(ucv_array_get(val, 1), &u32)) + return nla_parse_error(spec, vm, val, "mask value not an integer or out of range 0-4294967295"); + + memcpy((char *)&u64 + sizeof(u32), &u32, sizeof(u32)); + } + else if (ucv_type(val) == UC_INTEGER) { + if (!uc_nl_parse_u32(val, &u32)) + return nla_parse_error(spec, vm, val, "flag value not an integer or out of range 0-4294967295"); + + memcpy(&u64, &u32, sizeof(u32)); + memset((char *)&u64 + sizeof(u32), 0xff, sizeof(u32)); + } + else { + return nla_parse_error(spec, vm, val, "value neither an array of flags, mask nor an integer"); + } + + if (spec->attr == 0) + uc_nl_put_struct_member(base, spec->auxdata, sizeof(u64), &u64); + else + nla_put_u64(msg, attr, u64); + + break; + + case DT_LINKINFO: + if (!uc_nl_parse_rta_linkinfo(spec, msg, base, vm, val)) + return false; + + break; + + case DT_SRH: + if (!uc_nl_parse_rta_srh(spec, msg, base, vm, val)) + return false; + + break; + + case DT_ENCAP: + if (!uc_nl_parse_rta_encap(spec, msg, base, vm, val)) + return false; + + break; + + case DT_IPOPTS: + if (!uc_nl_parse_rta_ipopts(spec, msg, base, vm, val)) + return false; + + break; + + case DT_U32_OR_MEMBER: + if (!uc_nl_parse_rta_u32_or_member(spec, msg, base, vm, val)) + return false; + + break; + + case DT_NESTED: + if (!uc_nl_parse_rta_nested(spec, msg, base, vm, val)) + return false; + + break; + + default: + assert(0); + } + + return true; +} + +static uc_value_t * +uc_nl_convert_attr(const uc_nl_attr_spec_t *spec, struct nl_msg *msg, char *base, struct nlattr **tb, uc_vm_t *vm) +{ + union { uint8_t u8; uint16_t u16; uint32_t u32; uint64_t u64; size_t sz; } t = { 0 }; + struct { uint32_t flags; uint32_t mask; } flags; + char buf[sizeof(struct mpls_label) * 16]; + struct nlmsghdr *hdr = nlmsg_hdr(msg); + struct rtgenmsg *rtg = nlmsg_data(hdr); + struct ether_addr *ea; + uc_value_t *v; + char *s; + + switch (spec->type) { + case DT_U8: + if (spec->attr == 0) + t.u8 = uc_nl_get_struct_member_u8(base, spec->auxdata); + else if (nla_check_len(tb[spec->attr], sizeof(t.u8))) + t.u8 = nla_get_u8(tb[spec->attr]); + + return ucv_uint64_new(t.u8); + + case DT_U16: + if (spec->attr == 0) + t.u16 = uc_nl_get_struct_member_u16(base, spec->auxdata); + else if (nla_check_len(tb[spec->attr], sizeof(t.u16))) + t.u16 = nla_get_u16(tb[spec->attr]); + + if (spec->flags & DF_BYTESWAP) + t.u16 = ntohs(t.u16); + + return ucv_uint64_new(t.u16); + + case DT_U32: + case DT_S32: + if (spec->attr == 0) + t.u32 = uc_nl_get_struct_member_u32(base, spec->auxdata); + else if (nla_check_len(tb[spec->attr], sizeof(t.u32))) + t.u32 = nla_get_u32(tb[spec->attr]); + + if (spec->flags & DF_BYTESWAP) + t.u32 = ntohl(t.u32); + + if (spec->type == DT_S32) + return ucv_int64_new((int32_t)t.u32); + + return ucv_uint64_new(t.u32); + + case DT_U64: + if (spec->attr == 0) + t.u64 = uc_nl_get_struct_member_u64(base, spec->auxdata); + else if (nla_check_len(tb[spec->attr], sizeof(t.u64))) + memcpy(&t.u64, nla_data(tb[spec->attr]), sizeof(t.u64)); + + return ucv_uint64_new(t.u64); + + case DT_BOOL: + if (spec->attr == 0) + t.u8 = uc_nl_get_struct_member_u8(base, spec->auxdata); + else if (nla_check_len(tb[spec->attr], sizeof(t.u8))) + t.u8 = nla_get_u8(tb[spec->attr]); + + return ucv_boolean_new(t.u8 != 0); + + case DT_FLAG: + if (spec->attr == 0) + t.u8 = uc_nl_get_struct_member_u8(base, spec->auxdata); + else if (tb[spec->attr] != NULL) + t.u8 = 1; + + return ucv_boolean_new(t.u8 != 0); + + case DT_STRING: + assert(spec->attr != 0); + + if (!nla_check_len(tb[spec->attr], 1)) + return NULL; + + return ucv_string_new_length( + nla_data(tb[spec->attr]), nla_len(tb[spec->attr]) - 1); + + case DT_NETDEV: + if (spec->attr == 0) + t.u32 = uc_nl_get_struct_member_u32(base, spec->auxdata); + else if (nla_check_len(tb[spec->attr], sizeof(t.u32))) + t.u32 = nla_get_u32(tb[spec->attr]); + + if (if_indextoname(t.u32, buf)) + return ucv_string_new(buf); + else if (spec->flags & DF_ALLOW_NONE) + return ucv_int64_new(0); + + return NULL; + + case DT_LLADDR: + assert(spec->attr != 0); + + if (!nla_check_len(tb[spec->attr], sizeof(*ea))) + return NULL; + + ea = nla_data(tb[spec->attr]); + + snprintf(buf, sizeof(buf), "%02x:%02x:%02x:%02x:%02x:%02x", + ea->ether_addr_octet[0], ea->ether_addr_octet[1], + ea->ether_addr_octet[2], ea->ether_addr_octet[3], + ea->ether_addr_octet[4], ea->ether_addr_octet[5]); + + return ucv_string_new(buf); + + case DT_U64ADDR: + assert(spec->attr != 0); + + if (!nla_check_len(tb[spec->attr], sizeof(uint64_t)) || + !addr64_ntop(nla_data(tb[spec->attr]), buf, sizeof(buf))) + return NULL; + + return ucv_string_new(buf); + + case DT_INADDR: + case DT_IN6ADDR: + case DT_MPLSADDR: + case DT_ANYADDR: + assert(spec->attr != 0); + + t.sz = (size_t)nla_len(tb[spec->attr]); + + switch (spec->type) { + case DT_INADDR: + if (t.sz < sizeof(struct in_addr) || + !inet_ntop(AF_INET, nla_data(tb[spec->attr]), buf, sizeof(buf))) + return NULL; + + break; + + case DT_IN6ADDR: + if (t.sz < sizeof(struct in6_addr) || + !inet_ntop(AF_INET6, nla_data(tb[spec->attr]), buf, sizeof(buf))) + return NULL; + + break; + + case DT_MPLSADDR: + if (t.sz < sizeof(struct mpls_label) || + !mpls_ntop(nla_data(tb[spec->attr]), t.sz, buf, sizeof(buf))) + return NULL; + + break; + + default: + switch (rtg->rtgen_family) { + case AF_MPLS: + if (t.sz < sizeof(struct mpls_label) || + !mpls_ntop(nla_data(tb[spec->attr]), t.sz, buf, sizeof(buf))) + return NULL; + + break; + + case AF_INET6: + if (t.sz < sizeof(struct in6_addr) || + !inet_ntop(AF_INET6, nla_data(tb[spec->attr]), buf, sizeof(buf))) + return NULL; + + break; + + case AF_INET: + if (t.sz < sizeof(struct in_addr) || + !inet_ntop(AF_INET, nla_data(tb[spec->attr]), buf, sizeof(buf))) + return NULL; + + break; + + default: + return NULL; + } + + break; + } + + if (spec->flags & DF_STORE_MASK) { + s = buf + strlen(buf); + snprintf(s, buf + sizeof(buf) - s, "/%hhu", + uc_nl_get_struct_member_u8(base, spec->auxdata)); + } + + return ucv_string_new(buf); + + case DT_MULTIPATH: + return uc_nl_convert_rta_multipath(spec, msg, tb, vm); + + case DT_NUMRANGE: + return uc_nl_convert_rta_numrange(spec, msg, tb, vm); + + case DT_FLAGS: + if (spec->attr == 0) + uc_nl_get_struct_member(base, spec->auxdata, sizeof(flags), &flags); + else if (nla_check_len(tb[spec->attr], sizeof(flags))) + memcpy(&flags, nla_data(tb[spec->attr]), sizeof(flags)); + else + return NULL; + + if (flags.mask == 0) + return ucv_uint64_new(flags.flags); + + v = ucv_array_new(vm); + + ucv_array_push(v, ucv_uint64_new(flags.flags)); + ucv_array_push(v, ucv_uint64_new(flags.mask)); + + return v; + + case DT_LINKINFO: + return uc_nl_convert_rta_linkinfo(spec, msg, tb, vm); + + case DT_BRIDGEID: + return uc_nl_convert_rta_bridgeid(spec, msg, tb, vm); + + case DT_SRH: + return uc_nl_convert_rta_srh(spec, msg, tb, vm); + + case DT_ENCAP: + return uc_nl_convert_rta_encap(spec, msg, tb, vm); + + case DT_IPOPTS: + return uc_nl_convert_rta_ipopts(spec, msg, tb, vm); + + case DT_U32_OR_MEMBER: + return uc_nl_convert_rta_u32_or_member(spec, msg, base, tb, vm); + + case DT_NESTED: + return uc_nl_convert_rta_nested(spec, msg, tb, vm); + + default: + assert(0); + } + + return NULL; +} + + +static struct nl_sock *sock = NULL; + +typedef enum { + STATE_UNREPLIED, + STATE_CONTINUE, + STATE_REPLIED, + STATE_ERROR +} reply_state_t; + +typedef struct { + reply_state_t state; + uc_vm_t *vm; + uc_value_t *res; + int family; + const uc_nl_nested_spec_t *spec; +} request_state_t; + + +static uc_value_t * +uc_nl_error(uc_vm_t *vm, size_t nargs) +{ + uc_stringbuf_t *buf; + const char *s; + + if (last_error.code == 0) + return NULL; + + buf = ucv_stringbuf_new(); + + if (last_error.code == NLE_FAILURE && last_error.msg) { + ucv_stringbuf_addstr(buf, last_error.msg, strlen(last_error.msg)); + } + else { + s = nl_geterror(last_error.code); + + ucv_stringbuf_addstr(buf, s, strlen(s)); + + if (last_error.msg) + ucv_stringbuf_printf(buf, ": %s", last_error.msg); + } + + set_error(0, NULL); + + return ucv_stringbuf_finish(buf); +} + +/* + * route functions + */ + +static int +cb_done(struct nl_msg *msg, void *arg) +{ + request_state_t *s = arg; + + s->state = STATE_REPLIED; + + return NL_STOP; +} + +static int +cb_error(struct sockaddr_nl *nla, struct nlmsgerr *err, void *arg) +{ + request_state_t *s = arg; + int errnum = err->error; + + set_error(NLE_FAILURE, "RTNETLINK answers: %s", + strerror(errnum < 0 ? -errnum : errnum)); + + s->state = STATE_ERROR; + + return NL_STOP; +} + +static int +cb_reply(struct nl_msg *msg, void *arg) +{ + struct nlmsghdr *hdr = nlmsg_hdr(msg); + request_state_t *s = arg; + uc_value_t *o; + bool rv; + + if (RTM_FAM(hdr->nlmsg_type) != s->family) + return NL_SKIP; + + if (s->spec) { + if (nlmsg_attrlen(hdr, 0) < (ssize_t)s->spec->headsize) + return NL_SKIP; + + o = ucv_object_new(s->vm); + + rv = uc_nl_convert_attrs(msg, + nlmsg_attrdata(hdr, 0), + nlmsg_attrlen(hdr, 0), + s->spec->headsize, + s->spec->attrs, s->spec->nattrs, s->vm, o); + + if (rv) { + if (hdr->nlmsg_flags & NLM_F_MULTI) { + if (!s->res) + s->res = ucv_array_new(s->vm); + + ucv_array_push(s->res, o); + } + else { + s->res = o; + } + } + else { + ucv_put(o); + } + } + + if (hdr->nlmsg_flags & NLM_F_MULTI) + s->state = STATE_CONTINUE; + else + s->state = STATE_REPLIED; + + return NL_SKIP; +} + + +static const struct { + int family; + const uc_nl_nested_spec_t *spec; +} rtm_families[] = { + { RTM_FAM(RTM_GETLINK), &link_msg }, + { RTM_FAM(RTM_GETROUTE), &route_msg }, + { RTM_FAM(RTM_GETNEIGH), &neigh_msg }, + { RTM_FAM(RTM_GETADDR), &addr_msg }, + { RTM_FAM(RTM_GETRULE), &rule_msg }, + { RTM_FAM(RTM_GETADDRLABEL), &addrlabel_msg }, + { RTM_FAM(RTM_GETNEIGHTBL), &neightbl_msg }, + { RTM_FAM(RTM_GETNETCONF), &netconf_msg }, +}; + +static uc_value_t * +uc_nl_request(uc_vm_t *vm, size_t nargs) +{ + uc_value_t *cmd = uc_fn_arg(0); + uc_value_t *flags = uc_fn_arg(1); + uc_value_t *payload = uc_fn_arg(2); + request_state_t st = { .vm = vm }; + uint16_t flagval = 0; + int enable = 1, err; + struct nl_msg *msg; + struct nl_cb *cb; + size_t i; + + if (ucv_type(cmd) != UC_INTEGER || ucv_int64_get(cmd) < 0 || + (flags != NULL && ucv_type(flags) != UC_INTEGER) || + (payload != NULL && ucv_type(payload) != UC_OBJECT)) + err_return(NLE_INVAL, NULL); + + if (flags) { + if (ucv_int64_get(flags) < 0 || ucv_int64_get(flags) > 0xffff) + err_return(NLE_INVAL, NULL); + else + flagval = (uint16_t)ucv_int64_get(flags); + } + + for (i = 0; i < ARRAY_SIZE(rtm_families); i++) { + if (rtm_families[i].family == RTM_FAM(ucv_int64_get(cmd))) { + st.spec = rtm_families[i].spec; + st.family = rtm_families[i].family; + break; + } + } + + if (!sock) { + sock = nl_socket_alloc(); + + if (!sock) + err_return(NLE_NOMEM, NULL); + + err = nl_connect(sock, NETLINK_ROUTE); + + if (err != 0) + err_return(err, NULL); + + if (flagval & NLM_F_STRICT_CHK) { + if (setsockopt(sock->s_fd, SOL_NETLINK, NETLINK_GET_STRICT_CHK, &enable, sizeof(enable)) < 0) + err_return(nl_syserr2nlerr(errno), "Unable to enable NETLINK_GET_STRICT_CHK"); + + flagval &= ~NLM_F_STRICT_CHK; + } + } + + msg = nlmsg_alloc_simple(ucv_int64_get(cmd), NLM_F_REQUEST | flagval); + + if (!msg) + err_return(NLE_NOMEM, NULL); + + if (st.spec) { + nlmsg_reserve(msg, st.spec->headsize, 0); + + if (!uc_nl_parse_attrs(msg, NLMSG_DATA(nlmsg_hdr(msg)), st.spec->attrs, st.spec->nattrs, vm, payload)) { + nlmsg_free(msg); + + return NULL; + } + } + + cb = nl_cb_alloc(NL_CB_DEFAULT); + + if (!cb) { + nlmsg_free(msg); + err_return(NLE_NOMEM, NULL); + } + + nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, cb_reply, &st); + nl_cb_set(cb, NL_CB_FINISH, NL_CB_CUSTOM, cb_done, &st); + nl_cb_err(cb, NL_CB_CUSTOM, cb_error, &st); + + nl_send_auto_complete(sock, msg); + + do { + err = nl_recvmsgs(sock, cb); + + if (err && st.state != STATE_ERROR) { + set_error(err, NULL); + + st.state = STATE_ERROR; + } + } + while (st.state == STATE_CONTINUE); + + nlmsg_free(msg); + nl_cb_put(cb); + + switch (st.state) { + case STATE_REPLIED: + return st.res; + + case STATE_UNREPLIED: + return ucv_boolean_new(true); + + case STATE_ERROR: + return ucv_boolean_new(false); + + default: + set_error(NLE_FAILURE, "Interrupted reply"); + + return ucv_boolean_new(false); + } +} + + +static void +register_constants(uc_vm_t *vm, uc_value_t *scope) +{ + uc_value_t *c = ucv_object_new(vm); + +#define ADD_CONST(x) ucv_object_add(c, #x, ucv_int64_new(x)) + + ADD_CONST(NLM_F_ACK); + ADD_CONST(NLM_F_ACK_TLVS); + ADD_CONST(NLM_F_APPEND); + ADD_CONST(NLM_F_ATOMIC); + ADD_CONST(NLM_F_CAPPED); + ADD_CONST(NLM_F_CREATE); + ADD_CONST(NLM_F_DUMP); + ADD_CONST(NLM_F_DUMP_FILTERED); + ADD_CONST(NLM_F_DUMP_INTR); + ADD_CONST(NLM_F_ECHO); + ADD_CONST(NLM_F_EXCL); + ADD_CONST(NLM_F_MATCH); + ADD_CONST(NLM_F_MULTI); + ADD_CONST(NLM_F_NONREC); + ADD_CONST(NLM_F_REPLACE); + ADD_CONST(NLM_F_REQUEST); + ADD_CONST(NLM_F_ROOT); + ADD_CONST(NLM_F_STRICT_CHK); /* custom */ + + ADD_CONST(IN6_ADDR_GEN_MODE_EUI64); + ADD_CONST(IN6_ADDR_GEN_MODE_NONE); + ADD_CONST(IN6_ADDR_GEN_MODE_STABLE_PRIVACY); + ADD_CONST(IN6_ADDR_GEN_MODE_RANDOM); + + ADD_CONST(BRIDGE_MODE_UNSPEC); + ADD_CONST(BRIDGE_MODE_HAIRPIN); + + ADD_CONST(MACVLAN_MODE_PRIVATE); + ADD_CONST(MACVLAN_MODE_VEPA); + ADD_CONST(MACVLAN_MODE_BRIDGE); + ADD_CONST(MACVLAN_MODE_PASSTHRU); + ADD_CONST(MACVLAN_MODE_SOURCE); + + ADD_CONST(MACVLAN_MACADDR_ADD); + ADD_CONST(MACVLAN_MACADDR_DEL); + ADD_CONST(MACVLAN_MACADDR_FLUSH); + ADD_CONST(MACVLAN_MACADDR_SET); + + ADD_CONST(MACSEC_VALIDATE_DISABLED); + ADD_CONST(MACSEC_VALIDATE_CHECK); + ADD_CONST(MACSEC_VALIDATE_STRICT); + ADD_CONST(MACSEC_VALIDATE_MAX); + + ADD_CONST(MACSEC_OFFLOAD_OFF); + ADD_CONST(MACSEC_OFFLOAD_PHY); + ADD_CONST(MACSEC_OFFLOAD_MAC); + ADD_CONST(MACSEC_OFFLOAD_MAX); + + ADD_CONST(IPVLAN_MODE_L2); + ADD_CONST(IPVLAN_MODE_L3); + ADD_CONST(IPVLAN_MODE_L3S); + + ADD_CONST(VXLAN_DF_UNSET); + ADD_CONST(VXLAN_DF_SET); + ADD_CONST(VXLAN_DF_INHERIT); + ADD_CONST(VXLAN_DF_MAX); + + ADD_CONST(GENEVE_DF_UNSET); + ADD_CONST(GENEVE_DF_SET); + ADD_CONST(GENEVE_DF_INHERIT); + ADD_CONST(GENEVE_DF_MAX); + + ADD_CONST(GTP_ROLE_GGSN); + ADD_CONST(GTP_ROLE_SGSN); + + ADD_CONST(PORT_REQUEST_PREASSOCIATE); + ADD_CONST(PORT_REQUEST_PREASSOCIATE_RR); + ADD_CONST(PORT_REQUEST_ASSOCIATE); + ADD_CONST(PORT_REQUEST_DISASSOCIATE); + + ADD_CONST(PORT_VDP_RESPONSE_SUCCESS); + ADD_CONST(PORT_VDP_RESPONSE_INVALID_FORMAT); + ADD_CONST(PORT_VDP_RESPONSE_INSUFFICIENT_RESOURCES); + ADD_CONST(PORT_VDP_RESPONSE_UNUSED_VTID); + ADD_CONST(PORT_VDP_RESPONSE_VTID_VIOLATION); + ADD_CONST(PORT_VDP_RESPONSE_VTID_VERSION_VIOALTION); + ADD_CONST(PORT_VDP_RESPONSE_OUT_OF_SYNC); + ADD_CONST(PORT_PROFILE_RESPONSE_SUCCESS); + ADD_CONST(PORT_PROFILE_RESPONSE_INPROGRESS); + ADD_CONST(PORT_PROFILE_RESPONSE_INVALID); + ADD_CONST(PORT_PROFILE_RESPONSE_BADSTATE); + ADD_CONST(PORT_PROFILE_RESPONSE_INSUFFICIENT_RESOURCES); + ADD_CONST(PORT_PROFILE_RESPONSE_ERROR); + + ADD_CONST(IPOIB_MODE_DATAGRAM); + ADD_CONST(IPOIB_MODE_CONNECTED); + + ADD_CONST(HSR_PROTOCOL_HSR); + ADD_CONST(HSR_PROTOCOL_PRP); + + ADD_CONST(LINK_XSTATS_TYPE_UNSPEC); + ADD_CONST(LINK_XSTATS_TYPE_BRIDGE); + ADD_CONST(LINK_XSTATS_TYPE_BOND); + + ADD_CONST(XDP_ATTACHED_NONE); + ADD_CONST(XDP_ATTACHED_DRV); + ADD_CONST(XDP_ATTACHED_SKB); + ADD_CONST(XDP_ATTACHED_HW); + ADD_CONST(XDP_ATTACHED_MULTI); + + ADD_CONST(FDB_NOTIFY_BIT); + ADD_CONST(FDB_NOTIFY_INACTIVE_BIT); + + ADD_CONST(RTM_BASE); + ADD_CONST(RTM_NEWLINK); + ADD_CONST(RTM_DELLINK); + ADD_CONST(RTM_GETLINK); + ADD_CONST(RTM_SETLINK); + ADD_CONST(RTM_NEWADDR); + ADD_CONST(RTM_DELADDR); + ADD_CONST(RTM_GETADDR); + ADD_CONST(RTM_NEWROUTE); + ADD_CONST(RTM_DELROUTE); + ADD_CONST(RTM_GETROUTE); + ADD_CONST(RTM_NEWNEIGH); + ADD_CONST(RTM_DELNEIGH); + ADD_CONST(RTM_GETNEIGH); + ADD_CONST(RTM_NEWRULE); + ADD_CONST(RTM_DELRULE); + ADD_CONST(RTM_GETRULE); + ADD_CONST(RTM_NEWQDISC); + ADD_CONST(RTM_DELQDISC); + ADD_CONST(RTM_GETQDISC); + ADD_CONST(RTM_NEWTCLASS); + ADD_CONST(RTM_DELTCLASS); + ADD_CONST(RTM_GETTCLASS); + ADD_CONST(RTM_NEWTFILTER); + ADD_CONST(RTM_DELTFILTER); + ADD_CONST(RTM_GETTFILTER); + ADD_CONST(RTM_NEWACTION); + ADD_CONST(RTM_DELACTION); + ADD_CONST(RTM_GETACTION); + ADD_CONST(RTM_NEWPREFIX); + ADD_CONST(RTM_GETMULTICAST); + ADD_CONST(RTM_GETANYCAST); + ADD_CONST(RTM_NEWNEIGHTBL); + ADD_CONST(RTM_GETNEIGHTBL); + ADD_CONST(RTM_SETNEIGHTBL); + ADD_CONST(RTM_NEWNDUSEROPT); + ADD_CONST(RTM_NEWADDRLABEL); + ADD_CONST(RTM_DELADDRLABEL); + ADD_CONST(RTM_GETADDRLABEL); + ADD_CONST(RTM_GETDCB); + ADD_CONST(RTM_SETDCB); + ADD_CONST(RTM_NEWNETCONF); + ADD_CONST(RTM_DELNETCONF); + ADD_CONST(RTM_GETNETCONF); + ADD_CONST(RTM_NEWMDB); + ADD_CONST(RTM_DELMDB); + ADD_CONST(RTM_GETMDB); + ADD_CONST(RTM_NEWNSID); + ADD_CONST(RTM_DELNSID); + ADD_CONST(RTM_GETNSID); + ADD_CONST(RTM_NEWSTATS); + ADD_CONST(RTM_GETSTATS); + ADD_CONST(RTM_NEWCACHEREPORT); + ADD_CONST(RTM_NEWCHAIN); + ADD_CONST(RTM_DELCHAIN); + ADD_CONST(RTM_GETCHAIN); + ADD_CONST(RTM_NEWNEXTHOP); + ADD_CONST(RTM_DELNEXTHOP); + ADD_CONST(RTM_GETNEXTHOP); + ADD_CONST(RTM_NEWLINKPROP); + ADD_CONST(RTM_DELLINKPROP); + ADD_CONST(RTM_GETLINKPROP); + ADD_CONST(RTM_NEWVLAN); + ADD_CONST(RTM_DELVLAN); + ADD_CONST(RTM_GETVLAN); + + ADD_CONST(RTN_UNSPEC); + ADD_CONST(RTN_UNICAST); + ADD_CONST(RTN_LOCAL); + ADD_CONST(RTN_BROADCAST); + ADD_CONST(RTN_ANYCAST); + ADD_CONST(RTN_MULTICAST); + ADD_CONST(RTN_BLACKHOLE); + ADD_CONST(RTN_UNREACHABLE); + ADD_CONST(RTN_PROHIBIT); + ADD_CONST(RTN_THROW); + ADD_CONST(RTN_NAT); + ADD_CONST(RTN_XRESOLVE); + + ADD_CONST(RT_SCOPE_UNIVERSE); + ADD_CONST(RT_SCOPE_SITE); + ADD_CONST(RT_SCOPE_LINK); + ADD_CONST(RT_SCOPE_HOST); + ADD_CONST(RT_SCOPE_NOWHERE); + + ADD_CONST(RT_TABLE_UNSPEC); + ADD_CONST(RT_TABLE_COMPAT); + ADD_CONST(RT_TABLE_DEFAULT); + ADD_CONST(RT_TABLE_MAIN); + ADD_CONST(RT_TABLE_LOCAL); + ADD_CONST(RT_TABLE_MAX); + + /* required to construct RTAX_LOCK */ + ADD_CONST(RTAX_MTU); + ADD_CONST(RTAX_HOPLIMIT); + ADD_CONST(RTAX_ADVMSS); + ADD_CONST(RTAX_REORDERING); + ADD_CONST(RTAX_RTT); + ADD_CONST(RTAX_WINDOW); + ADD_CONST(RTAX_CWND); + ADD_CONST(RTAX_INITCWND); + ADD_CONST(RTAX_INITRWND); + ADD_CONST(RTAX_FEATURES); + ADD_CONST(RTAX_QUICKACK); + ADD_CONST(RTAX_CC_ALGO); + ADD_CONST(RTAX_RTTVAR); + ADD_CONST(RTAX_SSTHRESH); + ADD_CONST(RTAX_FASTOPEN_NO_COOKIE); + + ADD_CONST(PREFIX_UNSPEC); + ADD_CONST(PREFIX_ADDRESS); + ADD_CONST(PREFIX_CACHEINFO); + + ADD_CONST(NDUSEROPT_UNSPEC); + ADD_CONST(NDUSEROPT_SRCADDR); + + ADD_CONST(RTNLGRP_NONE); + ADD_CONST(RTNLGRP_LINK); + ADD_CONST(RTNLGRP_NOTIFY); + ADD_CONST(RTNLGRP_NEIGH); + ADD_CONST(RTNLGRP_TC); + ADD_CONST(RTNLGRP_IPV4_IFADDR); + ADD_CONST(RTNLGRP_IPV4_MROUTE); + ADD_CONST(RTNLGRP_IPV4_ROUTE); + ADD_CONST(RTNLGRP_IPV4_RULE); + ADD_CONST(RTNLGRP_IPV6_IFADDR); + ADD_CONST(RTNLGRP_IPV6_MROUTE); + ADD_CONST(RTNLGRP_IPV6_ROUTE); + ADD_CONST(RTNLGRP_IPV6_IFINFO); + ADD_CONST(RTNLGRP_DECnet_IFADDR); + ADD_CONST(RTNLGRP_NOP2); + ADD_CONST(RTNLGRP_DECnet_ROUTE); + ADD_CONST(RTNLGRP_DECnet_RULE); + ADD_CONST(RTNLGRP_NOP4); + ADD_CONST(RTNLGRP_IPV6_PREFIX); + ADD_CONST(RTNLGRP_IPV6_RULE); + ADD_CONST(RTNLGRP_ND_USEROPT); + ADD_CONST(RTNLGRP_PHONET_IFADDR); + ADD_CONST(RTNLGRP_PHONET_ROUTE); + ADD_CONST(RTNLGRP_DCB); + ADD_CONST(RTNLGRP_IPV4_NETCONF); + ADD_CONST(RTNLGRP_IPV6_NETCONF); + ADD_CONST(RTNLGRP_MDB); + ADD_CONST(RTNLGRP_MPLS_ROUTE); + ADD_CONST(RTNLGRP_NSID); + ADD_CONST(RTNLGRP_MPLS_NETCONF); + ADD_CONST(RTNLGRP_IPV4_MROUTE_R); + ADD_CONST(RTNLGRP_IPV6_MROUTE_R); + ADD_CONST(RTNLGRP_NEXTHOP); + ADD_CONST(RTNLGRP_BRVLAN); + + ADD_CONST(RTM_F_CLONED); + ADD_CONST(RTM_F_EQUALIZE); + ADD_CONST(RTM_F_FIB_MATCH); + ADD_CONST(RTM_F_LOOKUP_TABLE); + ADD_CONST(RTM_F_NOTIFY); + ADD_CONST(RTM_F_PREFIX); + + ADD_CONST(AF_UNSPEC); + ADD_CONST(AF_INET); + ADD_CONST(AF_INET6); + ADD_CONST(AF_MPLS); + ADD_CONST(AF_BRIDGE); + + ADD_CONST(GRE_CSUM); + ADD_CONST(GRE_ROUTING); + ADD_CONST(GRE_KEY); + ADD_CONST(GRE_SEQ); + ADD_CONST(GRE_STRICT); + ADD_CONST(GRE_REC); + ADD_CONST(GRE_ACK); + + ADD_CONST(TUNNEL_ENCAP_NONE); + ADD_CONST(TUNNEL_ENCAP_FOU); + ADD_CONST(TUNNEL_ENCAP_GUE); + ADD_CONST(TUNNEL_ENCAP_MPLS); + + ADD_CONST(TUNNEL_ENCAP_FLAG_CSUM); + ADD_CONST(TUNNEL_ENCAP_FLAG_CSUM6); + ADD_CONST(TUNNEL_ENCAP_FLAG_REMCSUM); + + ADD_CONST(IP6_TNL_F_ALLOW_LOCAL_REMOTE); + ADD_CONST(IP6_TNL_F_IGN_ENCAP_LIMIT); + ADD_CONST(IP6_TNL_F_MIP6_DEV); + ADD_CONST(IP6_TNL_F_RCV_DSCP_COPY); + ADD_CONST(IP6_TNL_F_USE_ORIG_FLOWLABEL); + ADD_CONST(IP6_TNL_F_USE_ORIG_FWMARK); + ADD_CONST(IP6_TNL_F_USE_ORIG_TCLASS); + + ADD_CONST(NTF_EXT_LEARNED); + ADD_CONST(NTF_MASTER); + ADD_CONST(NTF_OFFLOADED); + ADD_CONST(NTF_PROXY); + ADD_CONST(NTF_ROUTER); + ADD_CONST(NTF_SELF); + ADD_CONST(NTF_STICKY); + ADD_CONST(NTF_USE); + + ADD_CONST(NUD_DELAY); + ADD_CONST(NUD_FAILED); + ADD_CONST(NUD_INCOMPLETE); + ADD_CONST(NUD_NOARP); + ADD_CONST(NUD_NONE); + ADD_CONST(NUD_PERMANENT); + ADD_CONST(NUD_PROBE); + ADD_CONST(NUD_REACHABLE); + ADD_CONST(NUD_STALE); + + ADD_CONST(IFA_F_DADFAILED); + ADD_CONST(IFA_F_DEPRECATED); + ADD_CONST(IFA_F_HOMEADDRESS); + ADD_CONST(IFA_F_MANAGETEMPADDR); + ADD_CONST(IFA_F_MCAUTOJOIN); + ADD_CONST(IFA_F_NODAD); + ADD_CONST(IFA_F_NOPREFIXROUTE); + ADD_CONST(IFA_F_OPTIMISTIC); + ADD_CONST(IFA_F_PERMANENT); + ADD_CONST(IFA_F_SECONDARY); + ADD_CONST(IFA_F_STABLE_PRIVACY); + ADD_CONST(IFA_F_TEMPORARY); + ADD_CONST(IFA_F_TENTATIVE); + + ADD_CONST(FIB_RULE_PERMANENT); + ADD_CONST(FIB_RULE_INVERT); + ADD_CONST(FIB_RULE_UNRESOLVED); + ADD_CONST(FIB_RULE_IIF_DETACHED); + ADD_CONST(FIB_RULE_DEV_DETACHED); + ADD_CONST(FIB_RULE_OIF_DETACHED); + + ADD_CONST(FR_ACT_TO_TBL); + ADD_CONST(FR_ACT_GOTO); + ADD_CONST(FR_ACT_NOP); + ADD_CONST(FR_ACT_BLACKHOLE); + ADD_CONST(FR_ACT_UNREACHABLE); + ADD_CONST(FR_ACT_PROHIBIT); + + ADD_CONST(NETCONFA_IFINDEX_ALL); + ADD_CONST(NETCONFA_IFINDEX_DEFAULT); + + ADD_CONST(BRIDGE_VLAN_INFO_MASTER); + ADD_CONST(BRIDGE_VLAN_INFO_PVID); + ADD_CONST(BRIDGE_VLAN_INFO_UNTAGGED); + ADD_CONST(BRIDGE_VLAN_INFO_RANGE_BEGIN); + ADD_CONST(BRIDGE_VLAN_INFO_RANGE_END); + ADD_CONST(BRIDGE_VLAN_INFO_BRENTRY); + + ucv_object_add(scope, "const", c); +}; + +static const uc_function_list_t global_fns[] = { + { "error", uc_nl_error }, + { "request", uc_nl_request }, +}; + + +void uc_module_init(uc_vm_t *vm, uc_value_t *scope) +{ + uc_function_list_register(scope, global_fns); + + register_constants(vm, scope); +} |