Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+77
View File
@@ -0,0 +1,77 @@
# SPDX-License-Identifier: GPL-2.0
include ../../scripts/Makefile.arch
INSTALL ?= install
prefix ?= /usr
ifeq ($(LP64), 1)
libdir_relative = lib64
else
libdir_relative = lib
endif
libdir ?= $(prefix)/$(libdir_relative)
includedir ?= $(prefix)/include
SPECDIR=../../../Documentation/netlink/specs
SUBDIRS = lib generated ynltool tests
all: $(SUBDIRS) libynl.a
tests: | lib generated libynl.a
ynltool: | lib generated libynl.a
libynl.a: | lib generated
@echo -e "\tAR $@"
@$(AR) rcs $@ lib/ynl.o generated/*-user.o
$(SUBDIRS):
@if [ -f "$@/Makefile" ] ; then \
$(MAKE) -C $@ ; \
fi
clean distclean:
@for dir in $(SUBDIRS) ; do \
if [ -f "$$dir/Makefile" ] ; then \
$(MAKE) -C $$dir $@; \
fi \
done
rm -f libynl.a
rm -rf pyynl/__pycache__
rm -rf pyynl/lib/__pycache__
rm -rf pyynl.egg-info
rm -rf build
install: libynl.a lib/*.h ynltool
@echo -e "\tINSTALL libynl.a"
@$(INSTALL) -d $(DESTDIR)$(libdir)
@$(INSTALL) -m 0644 libynl.a $(DESTDIR)$(libdir)/libynl.a
@echo -e "\tINSTALL libynl headers"
@$(INSTALL) -d $(DESTDIR)$(includedir)/ynl
@$(INSTALL) -m 0644 lib/*.h $(DESTDIR)$(includedir)/ynl/
@echo -e "\tINSTALL pyynl"
@pip install --prefix=$(DESTDIR)$(prefix) .
@make -C generated install
@make -C ynltool install
run_tests:
@$(MAKE) -C tests run_tests
lint:
yamllint $(SPECDIR)
schema_check:
@N=1; \
for spec in $(SPECDIR)/*.yaml ; do \
NAME=$$(basename $$spec) ; \
OUTPUT=$$(./pyynl/cli.py --spec $$spec --validate) ; \
if [ $$? -eq 0 ] ; then \
echo "ok $$N $$NAME schema validation" ; \
else \
echo "not ok $$N $$NAME schema validation" ; \
echo "$$OUTPUT" ; \
echo ; \
fi ; \
N=$$((N+1)) ; \
done
.PHONY: all clean distclean install run_tests lint schema_check $(SUBDIRS)
+56
View File
@@ -0,0 +1,56 @@
# SPDX-License-Identifier: GPL-2.0
# Try to include uAPI headers from the kernel uapi/ path.
# Most code under tools/ requires the respective kernel uAPI headers
# to be copied to tools/include. The duplication is annoying.
# All the family headers should be self-contained. We avoid the copying
# by selectively including just the uAPI header of the family directly
# from the kernel sources.
UAPI_PATH:=../../../../include/uapi/
# scripts/headers_install.sh strips "_UAPI" from header guards so we
# need the explicit -D matching what's in /usr, to avoid multiple definitions.
get_hdr_inc=-D$(1) -include $(UAPI_PATH)/linux/$(2)
get_hdr_inc2=-D$(1) -D$(2) -include $(UAPI_PATH)/linux/$(3)
get_hdr_inc_drm=-D$(1) -include $(UAPI_PATH)/drm/$(2)
CFLAGS_dev-energymodel:=$(call get_hdr_inc,_LINUX_DEV_ENERGYMODEL_H,dev_energymodel.h)
CFLAGS_devlink:=$(call get_hdr_inc,_LINUX_DEVLINK_H_,devlink.h)
CFLAGS_dpll:=$(call get_hdr_inc,_LINUX_DPLL_H,dpll.h)
CFLAGS_drm_ras:=$(call get_hdr_inc_drm,_LINUX_DRM_RAS_H,drm_ras.h)
CFLAGS_ethtool:=$(call get_hdr_inc,_LINUX_TYPELIMITS_H,typelimits.h) \
$(call get_hdr_inc,_LINUX_ETHTOOL_H,ethtool.h) \
$(call get_hdr_inc,_LINUX_ETHTOOL_NETLINK_H_,ethtool_netlink.h) \
$(call get_hdr_inc,_LINUX_ETHTOOL_NETLINK_GENERATED_H,ethtool_netlink_generated.h)
CFLAGS_handshake:=$(call get_hdr_inc,_LINUX_HANDSHAKE_H,handshake.h)
CFLAGS_lockd_netlink:=$(call get_hdr_inc,_LINUX_LOCKD_NETLINK_H,lockd_netlink.h)
CFLAGS_mptcp_pm:=$(call get_hdr_inc,_LINUX_MPTCP_PM_H,mptcp_pm.h)
CFLAGS_net_shaper:=$(call get_hdr_inc,_LINUX_NET_SHAPER_H,net_shaper.h)
CFLAGS_netdev:=$(call get_hdr_inc,_LINUX_NETDEV_H,netdev.h)
CFLAGS_nl80211:=$(call get_hdr_inc,__LINUX_NL802121_H,nl80211.h)
CFLAGS_nlctrl:=$(call get_hdr_inc,__LINUX_GENERIC_NETLINK_H,genetlink.h)
CFLAGS_nfsd:=$(call get_hdr_inc,_LINUX_NFSD_NETLINK_H,nfsd_netlink.h)
CFLAGS_ovpn:=$(call get_hdr_inc,_LINUX_OVPN_H,ovpn.h)
CFLAGS_ovs_datapath:=$(call get_hdr_inc,__LINUX_OPENVSWITCH_H,openvswitch.h)
CFLAGS_ovs_flow:=$(call get_hdr_inc,__LINUX_OPENVSWITCH_H,openvswitch.h)
CFLAGS_ovs_vport:=$(call get_hdr_inc,__LINUX_OPENVSWITCH_H,openvswitch.h)
CFLAGS_psp:=$(call get_hdr_inc,_LINUX_PSP_H,psp.h)
CFLAGS_rt-addr:=$(call get_hdr_inc,__LINUX_RTNETLINK_H,rtnetlink.h) \
$(call get_hdr_inc,__LINUX_IF_ADDR_H,if_addr.h)
CFLAGS_rt-link:=$(call get_hdr_inc,__LINUX_RTNETLINK_H,rtnetlink.h) \
$(call get_hdr_inc,_LINUX_IF_LINK_H,if_link.h)
CFLAGS_rt-neigh:=$(call get_hdr_inc,__LINUX_RTNETLINK_H,rtnetlink.h) \
$(call get_hdr_inc,__LINUX_NEIGHBOUR_H,neighbour.h)
CFLAGS_rt-route:=$(call get_hdr_inc,__LINUX_RTNETLINK_H,rtnetlink.h)
CFLAGS_rt-rule:=$(call get_hdr_inc,__LINUX_FIB_RULES_H,fib_rules.h)
CFLAGS_tc:= $(call get_hdr_inc,__LINUX_RTNETLINK_H,rtnetlink.h) \
$(call get_hdr_inc,__LINUX_PKT_SCHED_H,pkt_sched.h) \
$(call get_hdr_inc,__LINUX_PKT_CLS_H,pkt_cls.h) \
$(call get_hdr_inc,_TC_CT_H,tc_act/tc_ct.h) \
$(call get_hdr_inc,_TC_MIRRED_H,tc_act/tc_mirred.h) \
$(call get_hdr_inc,_TC_SKBEDIT_H,tc_act/tc_skbedit.h) \
$(call get_hdr_inc,_TC_TUNNEL_KEY_H,tc_act/tc_tunnel_key.h)
CFLAGS_tcp_metrics:=$(call get_hdr_inc,_LINUX_TCP_METRICS_H,tcp_metrics.h)
CFLAGS_wireguard:=$(call get_hdr_inc2,_LINUX_WIREGUARD_H,_WG_UAPI_WIREGUARD_H,wireguard.h)
@@ -0,0 +1,3 @@
*-user.c
*-user.h
*.rst
@@ -0,0 +1,87 @@
# SPDX-License-Identifier: GPL-2.0
CC=gcc
CFLAGS += -std=gnu11 -O2 -W -Wall -Wextra -Wno-unused-parameter -Wshadow \
-I../lib/ -idirafter $(UAPI_PATH)
ifeq ("$(DEBUG)","1")
CFLAGS += -g -fsanitize=address -fsanitize=leak -static-libasan
endif
INSTALL ?= install
prefix ?= /usr
datarootdir ?= $(prefix)/share
docdir ?= $(datarootdir)/doc
includedir ?= $(prefix)/include
include ../Makefile.deps
YNL_GEN_ARG_ethtool:=--user-header linux/ethtool_netlink.h \
--exclude-op stats-get
TOOL:=../pyynl/ynl_gen_c.py
TOOL_RST:=../pyynl/ynl_gen_rst.py
SPECS_DIR:=../../../../Documentation/netlink/specs
SPECS_PATHS=$(wildcard $(SPECS_DIR)/*.yaml)
GENS_UNSUP=conntrack nftables
GENS=$(filter-out ${GENS_UNSUP},$(patsubst $(SPECS_DIR)/%.yaml,%,${SPECS_PATHS}))
SRCS=$(patsubst %,%-user.c,${GENS})
HDRS=$(patsubst %,%-user.h,${GENS})
OBJS=$(patsubst %,%-user.o,${GENS})
SPECS_PATHS=$(wildcard $(SPECS_DIR)/*.yaml)
SPECS=$(patsubst $(SPECS_DIR)/%.yaml,%,${SPECS_PATHS})
RSTS=$(patsubst %,%.rst,${SPECS})
all: protos.a $(HDRS) $(SRCS) $(KHDRS) $(KSRCS) $(UAPI) $(RSTS)
protos.a: $(OBJS)
@echo -e "\tAR $@"
@$(AR) rcs $@ $(OBJS)
%-user.h: $(SPECS_DIR)/%.yaml $(TOOL)
@echo -e "\tGEN $@"
@$(TOOL) --mode user --header --spec $< -o $@ $(YNL_GEN_ARG_$*)
%-user.c: $(SPECS_DIR)/%.yaml $(TOOL)
@echo -e "\tGEN $@"
@$(TOOL) --mode user --source --spec $< -o $@ $(YNL_GEN_ARG_$*)
%-user.o: %-user.c %-user.h
@echo -e "\tCC $@"
@$(COMPILE.c) $(CFLAGS_$*) -o $@ $<
%.rst: $(SPECS_DIR)/%.yaml $(TOOL_RST)
@echo -e "\tGEN_RST $@"
@$(TOOL_RST) -o $@ -i $<
clean:
rm -f *.o
distclean: clean
rm -f *.c *.h *.a *.rst
regen:
@../ynl-regen.sh
install-headers: $(HDRS)
@echo -e "\tINSTALL generated headers"
@$(INSTALL) -d $(DESTDIR)$(includedir)/ynl
@$(INSTALL) -m 0644 *.h $(DESTDIR)$(includedir)/ynl/
install-rsts: $(RSTS)
@echo -e "\tINSTALL generated docs"
@$(INSTALL) -d $(DESTDIR)$(docdir)/ynl
@$(INSTALL) -m 0644 $(RSTS) $(DESTDIR)$(docdir)/ynl/
install-specs:
@echo -e "\tINSTALL specs"
@$(INSTALL) -d $(DESTDIR)$(datarootdir)/ynl
@$(INSTALL) -m 0644 ../../../../Documentation/netlink/*.yaml $(DESTDIR)$(datarootdir)/ynl/
@$(INSTALL) -d $(DESTDIR)$(datarootdir)/ynl/specs
@$(INSTALL) -m 0644 $(SPECS_DIR)/*.yaml $(DESTDIR)$(datarootdir)/ynl/specs/
install: install-headers install-rsts install-specs
.PHONY: all clean distclean regen install install-headers install-rsts install-specs
.DEFAULT_GOAL: all
+1
View File
@@ -0,0 +1 @@
*.d
+30
View File
@@ -0,0 +1,30 @@
# SPDX-License-Identifier: GPL-2.0
CC=gcc
CFLAGS += -std=gnu11 -O2 -W -Wall -Wextra -Wno-unused-parameter -Wshadow
ifeq ("$(DEBUG)","1")
CFLAGS += -g -fsanitize=address -fsanitize=leak -static-libasan
endif
SRCS=$(wildcard *.c)
OBJS=$(patsubst %.c,%.o,${SRCS})
include $(wildcard *.d)
all: ynl.a
ynl.a: $(OBJS)
@echo -e "\tAR $@"
@$(AR) rcs $@ $(OBJS)
clean:
rm -f *.o *.d *~
distclean: clean
rm -f *.a
%.o: %.c
$(COMPILE.c) -MMD -c -o $@ $<
.PHONY: all clean distclean
.DEFAULT_GOAL=all
+478
View File
@@ -0,0 +1,478 @@
/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
#ifndef __YNL_C_PRIV_H
#define __YNL_C_PRIV_H 1
#include <stdbool.h>
#include <stddef.h>
#include <linux/types.h>
struct ynl_parse_arg;
/*
* YNL internals / low level stuff
*/
enum ynl_policy_type {
YNL_PT_REJECT = 1,
YNL_PT_IGNORE,
YNL_PT_NEST,
YNL_PT_FLAG,
YNL_PT_BINARY,
YNL_PT_U8,
YNL_PT_U16,
YNL_PT_U32,
YNL_PT_U64,
YNL_PT_UINT,
YNL_PT_NUL_STR,
YNL_PT_BITFIELD32,
YNL_PT_SUBMSG,
};
enum ynl_parse_result {
YNL_PARSE_CB_ERROR = -1,
YNL_PARSE_CB_STOP = 0,
YNL_PARSE_CB_OK = 1,
};
#define YNL_SOCKET_BUFFER_SIZE (1 << 17)
#define YNL_ARRAY_SIZE(array) (sizeof(array) ? \
sizeof(array) / sizeof(array[0]) : 0)
typedef int (*ynl_parse_cb_t)(const struct nlmsghdr *nlh,
struct ynl_parse_arg *yarg);
struct ynl_policy_attr {
enum ynl_policy_type type:8;
__u8 is_submsg:1;
__u8 is_selector:1;
__u16 selector_type;
unsigned int len;
const char *name;
const struct ynl_policy_nest *nest;
};
struct ynl_policy_nest {
unsigned int max_attr;
const struct ynl_policy_attr *table;
};
struct ynl_parse_arg {
struct ynl_sock *ys;
const struct ynl_policy_nest *rsp_policy;
void *data;
};
struct ynl_dump_list_type {
struct ynl_dump_list_type *next;
unsigned char data[] __attribute__((aligned(8)));
};
extern struct ynl_dump_list_type *YNL_LIST_END;
static inline bool ynl_dump_obj_is_last(void *obj)
{
unsigned long uptr = (unsigned long)obj;
uptr -= offsetof(struct ynl_dump_list_type, data);
return uptr == (unsigned long)YNL_LIST_END;
}
static inline void *ynl_dump_obj_next(void *obj)
{
unsigned long uptr = (unsigned long)obj;
struct ynl_dump_list_type *list;
uptr -= offsetof(struct ynl_dump_list_type, data);
list = (struct ynl_dump_list_type *)uptr;
uptr = (unsigned long)list->next;
uptr += offsetof(struct ynl_dump_list_type, data);
return (void *)uptr;
}
struct ynl_ntf_base_type {
__u16 family;
__u8 cmd;
struct ynl_ntf_base_type *next;
void (*free)(struct ynl_ntf_base_type *ntf);
unsigned char data[] __attribute__((aligned(8)));
};
struct nlmsghdr *ynl_msg_start_req(struct ynl_sock *ys, __u32 id, __u16 flags);
struct nlmsghdr *ynl_msg_start_dump(struct ynl_sock *ys, __u32 id);
struct nlmsghdr *
ynl_gemsg_start_req(struct ynl_sock *ys, __u32 id, __u8 cmd, __u8 version);
struct nlmsghdr *
ynl_gemsg_start_dump(struct ynl_sock *ys, __u32 id, __u8 cmd, __u8 version);
int ynl_submsg_failed(struct ynl_parse_arg *yarg, const char *field_name,
const char *sel_name);
/* YNL specific helpers used by the auto-generated code */
struct ynl_req_state {
struct ynl_parse_arg yarg;
ynl_parse_cb_t cb;
__u32 rsp_cmd;
};
struct ynl_dump_state {
struct ynl_parse_arg yarg;
void *first;
struct ynl_dump_list_type *last;
size_t alloc_sz;
ynl_parse_cb_t cb;
__u32 rsp_cmd;
};
struct ynl_ntf_info {
const struct ynl_policy_nest *policy;
ynl_parse_cb_t cb;
size_t alloc_sz;
void (*free)(struct ynl_ntf_base_type *ntf);
};
int ynl_exec(struct ynl_sock *ys, struct nlmsghdr *req_nlh,
struct ynl_req_state *yrs);
int ynl_exec_dump(struct ynl_sock *ys, struct nlmsghdr *req_nlh,
struct ynl_dump_state *yds);
void ynl_error_unknown_notification(struct ynl_sock *ys, __u8 cmd);
int ynl_error_parse(struct ynl_parse_arg *yarg, const char *msg);
/* Netlink message handling helpers */
#define YNL_MSG_OVERFLOW 1
static inline struct nlmsghdr *ynl_nlmsg_put_header(void *buf)
{
struct nlmsghdr *nlh = (struct nlmsghdr *)buf;
memset(nlh, 0, sizeof(*nlh));
nlh->nlmsg_len = NLMSG_HDRLEN;
return nlh;
}
static inline unsigned int ynl_nlmsg_data_len(const struct nlmsghdr *nlh)
{
return nlh->nlmsg_len - NLMSG_HDRLEN;
}
static inline void *ynl_nlmsg_data(const struct nlmsghdr *nlh)
{
return (unsigned char *)nlh + NLMSG_HDRLEN;
}
static inline void *
ynl_nlmsg_data_offset(const struct nlmsghdr *nlh, unsigned int offset)
{
return (unsigned char *)nlh + NLMSG_HDRLEN + offset;
}
static inline void *ynl_nlmsg_end_addr(const struct nlmsghdr *nlh)
{
return (char *)nlh + nlh->nlmsg_len;
}
static inline void *
ynl_nlmsg_put_extra_header(struct nlmsghdr *nlh, unsigned int size)
{
void *tail = ynl_nlmsg_end_addr(nlh);
nlh->nlmsg_len += NLMSG_ALIGN(size);
return tail;
}
/* Netlink attribute helpers */
static inline unsigned int ynl_attr_type(const struct nlattr *attr)
{
return attr->nla_type & NLA_TYPE_MASK;
}
static inline unsigned int ynl_attr_data_len(const struct nlattr *attr)
{
return attr->nla_len - NLA_HDRLEN;
}
static inline void *ynl_attr_data(const struct nlattr *attr)
{
return (unsigned char *)attr + NLA_HDRLEN;
}
static inline void *ynl_attr_data_end(const struct nlattr *attr)
{
return (char *)ynl_attr_data(attr) + ynl_attr_data_len(attr);
}
#define ynl_attr_for_each(attr, nlh, fixed_hdr_sz) \
for ((attr) = ynl_attr_first(nlh, (nlh)->nlmsg_len, \
NLMSG_HDRLEN + fixed_hdr_sz); attr; \
(attr) = ynl_attr_next(ynl_nlmsg_end_addr(nlh), attr))
#define ynl_attr_for_each_nested_off(attr, outer, offset) \
for ((attr) = ynl_attr_first(outer, outer->nla_len, \
sizeof(struct nlattr) + offset); \
attr; \
(attr) = ynl_attr_next(ynl_attr_data_end(outer), attr))
#define ynl_attr_for_each_nested(attr, outer) \
ynl_attr_for_each_nested_off(attr, outer, 0)
#define ynl_attr_for_each_payload(start, len, attr) \
for ((attr) = ynl_attr_first(start, len, 0); attr; \
(attr) = ynl_attr_next(start + len, attr))
static inline struct nlattr *
ynl_attr_if_good(const void *end, struct nlattr *attr)
{
if (attr + 1 > (const struct nlattr *)end)
return NULL;
if (ynl_attr_data_end(attr) > end)
return NULL;
return attr;
}
static inline struct nlattr *
ynl_attr_next(const void *end, const struct nlattr *prev)
{
struct nlattr *attr;
attr = (struct nlattr *)((char *)prev + NLA_ALIGN(prev->nla_len));
return ynl_attr_if_good(end, attr);
}
static inline struct nlattr *
ynl_attr_first(const void *start, size_t len, size_t skip)
{
struct nlattr *attr;
attr = (struct nlattr *)((char *)start + NLMSG_ALIGN(skip));
return ynl_attr_if_good((char *)start + len, attr);
}
static inline bool
__ynl_attr_put_overflow(struct nlmsghdr *nlh, size_t size)
{
bool o;
/* ynl_msg_start() stashed buffer length in nlmsg_pid. */
o = nlh->nlmsg_len + NLA_HDRLEN + NLMSG_ALIGN(size) > nlh->nlmsg_pid;
if (o)
/* YNL_MSG_OVERFLOW is < NLMSG_HDRLEN, all subsequent checks
* are guaranteed to fail.
*/
nlh->nlmsg_pid = YNL_MSG_OVERFLOW;
return o;
}
static inline struct nlattr *
ynl_attr_nest_start(struct nlmsghdr *nlh, unsigned int attr_type)
{
struct nlattr *attr;
if (__ynl_attr_put_overflow(nlh, 0))
return (struct nlattr *)ynl_nlmsg_end_addr(nlh) - 1;
attr = (struct nlattr *)ynl_nlmsg_end_addr(nlh);
attr->nla_type = attr_type | NLA_F_NESTED;
nlh->nlmsg_len += NLA_HDRLEN;
return attr;
}
static inline void
ynl_attr_nest_end(struct nlmsghdr *nlh, struct nlattr *attr)
{
attr->nla_len = (char *)ynl_nlmsg_end_addr(nlh) - (char *)attr;
}
static inline void
ynl_attr_put(struct nlmsghdr *nlh, unsigned int attr_type,
const void *value, size_t size)
{
struct nlattr *attr;
if (__ynl_attr_put_overflow(nlh, size))
return;
attr = (struct nlattr *)ynl_nlmsg_end_addr(nlh);
attr->nla_type = attr_type;
attr->nla_len = NLA_HDRLEN + size;
memcpy(ynl_attr_data(attr), value, size);
nlh->nlmsg_len += NLMSG_ALIGN(attr->nla_len);
}
static inline void
ynl_attr_put_str(struct nlmsghdr *nlh, unsigned int attr_type, const char *str)
{
struct nlattr *attr;
size_t len;
len = strlen(str) + 1;
if (__ynl_attr_put_overflow(nlh, len))
return;
attr = (struct nlattr *)ynl_nlmsg_end_addr(nlh);
attr->nla_type = attr_type;
strcpy((char *)ynl_attr_data(attr), str);
attr->nla_len = NLA_HDRLEN + len;
nlh->nlmsg_len += NLMSG_ALIGN(attr->nla_len);
}
static inline const char *ynl_attr_get_str(const struct nlattr *attr)
{
return (const char *)ynl_attr_data(attr);
}
static inline __s8 ynl_attr_get_s8(const struct nlattr *attr)
{
return *(__s8 *)ynl_attr_data(attr);
}
static inline __s16 ynl_attr_get_s16(const struct nlattr *attr)
{
return *(__s16 *)ynl_attr_data(attr);
}
static inline __s32 ynl_attr_get_s32(const struct nlattr *attr)
{
return *(__s32 *)ynl_attr_data(attr);
}
static inline __s64 ynl_attr_get_s64(const struct nlattr *attr)
{
__s64 tmp;
memcpy(&tmp, (unsigned char *)(attr + 1), sizeof(tmp));
return tmp;
}
static inline __u8 ynl_attr_get_u8(const struct nlattr *attr)
{
return *(__u8 *)ynl_attr_data(attr);
}
static inline __u16 ynl_attr_get_u16(const struct nlattr *attr)
{
return *(__u16 *)ynl_attr_data(attr);
}
static inline __u32 ynl_attr_get_u32(const struct nlattr *attr)
{
return *(__u32 *)ynl_attr_data(attr);
}
static inline __u64 ynl_attr_get_u64(const struct nlattr *attr)
{
__u64 tmp;
memcpy(&tmp, (unsigned char *)(attr + 1), sizeof(tmp));
return tmp;
}
static inline void
ynl_attr_put_s8(struct nlmsghdr *nlh, unsigned int attr_type, __s8 value)
{
ynl_attr_put(nlh, attr_type, &value, sizeof(value));
}
static inline void
ynl_attr_put_s16(struct nlmsghdr *nlh, unsigned int attr_type, __s16 value)
{
ynl_attr_put(nlh, attr_type, &value, sizeof(value));
}
static inline void
ynl_attr_put_s32(struct nlmsghdr *nlh, unsigned int attr_type, __s32 value)
{
ynl_attr_put(nlh, attr_type, &value, sizeof(value));
}
static inline void
ynl_attr_put_s64(struct nlmsghdr *nlh, unsigned int attr_type, __s64 value)
{
ynl_attr_put(nlh, attr_type, &value, sizeof(value));
}
static inline void
ynl_attr_put_u8(struct nlmsghdr *nlh, unsigned int attr_type, __u8 value)
{
ynl_attr_put(nlh, attr_type, &value, sizeof(value));
}
static inline void
ynl_attr_put_u16(struct nlmsghdr *nlh, unsigned int attr_type, __u16 value)
{
ynl_attr_put(nlh, attr_type, &value, sizeof(value));
}
static inline void
ynl_attr_put_u32(struct nlmsghdr *nlh, unsigned int attr_type, __u32 value)
{
ynl_attr_put(nlh, attr_type, &value, sizeof(value));
}
static inline void
ynl_attr_put_u64(struct nlmsghdr *nlh, unsigned int attr_type, __u64 value)
{
ynl_attr_put(nlh, attr_type, &value, sizeof(value));
}
static inline __u64 ynl_attr_get_uint(const struct nlattr *attr)
{
switch (ynl_attr_data_len(attr)) {
case 4:
return ynl_attr_get_u32(attr);
case 8:
return ynl_attr_get_u64(attr);
default:
return 0;
}
}
static inline __s64 ynl_attr_get_sint(const struct nlattr *attr)
{
switch (ynl_attr_data_len(attr)) {
case 4:
return ynl_attr_get_s32(attr);
case 8:
return ynl_attr_get_s64(attr);
default:
return 0;
}
}
static inline void
ynl_attr_put_uint(struct nlmsghdr *nlh, __u16 type, __u64 data)
{
if ((__u32)data == (__u64)data)
ynl_attr_put_u32(nlh, type, data);
else
ynl_attr_put_u64(nlh, type, data);
}
static inline void
ynl_attr_put_sint(struct nlmsghdr *nlh, __u16 type, __s64 data)
{
if ((__s32)data == (__s64)data)
ynl_attr_put_s32(nlh, type, data);
else
ynl_attr_put_s64(nlh, type, data);
}
int __ynl_attr_validate(struct ynl_parse_arg *yarg, const struct nlattr *attr,
unsigned int type);
static inline int ynl_attr_validate(struct ynl_parse_arg *yarg,
const struct nlattr *attr)
{
return __ynl_attr_validate(yarg, attr, ynl_attr_type(attr));
}
#endif
File diff suppressed because it is too large Load Diff
+143
View File
@@ -0,0 +1,143 @@
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
#ifndef __YNL_C_H
#define __YNL_C_H 1
#include <stdbool.h>
#include <stddef.h>
#include <linux/genetlink.h>
#include <linux/types.h>
#include "ynl-priv.h"
enum ynl_error_code {
YNL_ERROR_NONE = 0,
__YNL_ERRNO_END = 4096,
YNL_ERROR_INTERNAL,
YNL_ERROR_DUMP_INTER,
YNL_ERROR_EXPECT_ACK,
YNL_ERROR_EXPECT_MSG,
YNL_ERROR_UNEXPECT_MSG,
YNL_ERROR_ATTR_MISSING,
YNL_ERROR_ATTR_INVALID,
YNL_ERROR_UNKNOWN_NTF,
YNL_ERROR_INV_RESP,
YNL_ERROR_INPUT_INVALID,
YNL_ERROR_INPUT_TOO_BIG,
YNL_ERROR_SUBMSG_KEY,
};
/**
* struct ynl_error - error encountered by YNL
* @code: errno (low values) or YNL error code (enum ynl_error_code)
* @attr_offs: offset of bad attribute (for very advanced users)
* @msg: error message
*
* Error information for when YNL operations fail.
* Users should interact with the err member of struct ynl_sock directly.
* The main exception to that rule is ynl_sock_create().
*/
struct ynl_error {
enum ynl_error_code code;
unsigned int attr_offs;
char msg[512];
};
/**
* struct ynl_family - YNL family info
* Family description generated by codegen. Pass to ynl_sock_create().
*/
struct ynl_family {
/* private: */
const char *name;
size_t hdr_len;
bool is_classic;
__u16 classic_id;
const struct ynl_ntf_info *ntf_info;
unsigned int ntf_info_size;
};
/**
* struct ynl_sock - YNL wrapped netlink socket
* @err: YNL error descriptor, cleared on every request.
*/
struct ynl_sock {
struct ynl_error err;
/* private: */
const struct ynl_family *family;
int socket;
__u32 seq;
__u32 portid;
__u16 family_id;
unsigned int n_mcast_groups;
struct {
unsigned int id;
char name[GENL_NAMSIZ];
} *mcast_groups;
struct ynl_ntf_base_type *ntf_first;
struct ynl_ntf_base_type **ntf_last_next;
struct nlmsghdr *nlh;
const struct ynl_policy_nest *req_policy;
size_t req_hdr_len;
unsigned char *tx_buf;
unsigned char *rx_buf;
unsigned char raw_buf[];
};
/**
* struct ynl_string - parsed individual string
* @len: length of the string (excluding terminating character)
* @str: value of the string
*
* Parsed and nul-terminated string. This struct is only used for arrays of
* strings. Non-array string members are placed directly in respective types.
*/
struct ynl_string {
unsigned int len;
char str[];
};
struct ynl_sock *
ynl_sock_create(const struct ynl_family *yf, struct ynl_error *e);
void ynl_sock_destroy(struct ynl_sock *ys);
#define ynl_dump_foreach(dump, iter) \
for (typeof(dump->obj) *iter = &dump->obj; \
!ynl_dump_obj_is_last(iter); \
iter = ynl_dump_obj_next(iter))
/**
* ynl_dump_empty() - does the dump have no entries
* @dump: pointer to the dump list, as returned by a dump call
*
* Check if the dump is empty, i.e. contains no objects.
* Dump calls return NULL on error, and terminator element if empty.
*/
static inline bool ynl_dump_empty(void *dump)
{
return dump == (void *)YNL_LIST_END;
}
int ynl_subscribe(struct ynl_sock *ys, const char *grp_name);
int ynl_socket_get_fd(struct ynl_sock *ys);
int ynl_ntf_check(struct ynl_sock *ys);
/**
* ynl_has_ntf() - check if socket has *parsed* notifications
* @ys: active YNL socket
*
* Note that this does not take into account notifications sitting
* in netlink socket, just the notifications which have already been
* read and parsed (e.g. during a ynl_ntf_check() call).
*/
static inline bool ynl_has_ntf(struct ynl_sock *ys)
{
return ys->ntf_last_next != &ys->ntf_first;
}
struct ynl_ntf_base_type *ynl_ntf_dequeue(struct ynl_sock *ys);
void ynl_ntf_free(struct ynl_ntf_base_type *ntf);
#endif
+24
View File
@@ -0,0 +1,24 @@
[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"
[project]
name = "pyynl"
authors = [
{name = "Donald Hunter", email = "donald.hunter@gmail.com"},
{name = "Jakub Kicinski", email = "kuba@kernel.org"},
]
description = "yaml netlink (ynl)"
version = "0.0.1"
requires-python = ">=3.9"
dependencies = [
"pyyaml==6.*",
"jsonschema==4.*"
]
[tool.setuptools.packages.find]
include = ["pyynl", "pyynl.lib"]
[project.scripts]
ynl = "pyynl.cli:main"
ynl-ethtool = "pyynl.ethtool:main"
@@ -0,0 +1,2 @@
__pycache__/
lib/__pycache__/
+366
View File
@@ -0,0 +1,366 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
"""
YNL cli tool
"""
import argparse
import json
import os
import pathlib
import pprint
import shutil
import sys
import textwrap
# pylint: disable=no-name-in-module,wrong-import-position
sys.path.append(pathlib.Path(__file__).resolve().parent.as_posix())
from lib import YnlFamily, Netlink, NlError, SpecFamily, SpecException, YnlException
SYS_SCHEMA_DIR='/usr/share/ynl'
RELATIVE_SCHEMA_DIR='../../../../Documentation/netlink'
# pylint: disable=too-few-public-methods,too-many-locals
class Colors:
"""ANSI color and font modifier codes"""
RESET = '\033[0m'
BOLD = '\033[1m'
ITALICS = '\033[3m'
UNDERLINE = '\033[4m'
INVERT = '\033[7m'
def color(text, modifiers):
"""Add color to text if output is a TTY
Returns:
Colored text if stdout is a TTY, otherwise plain text
"""
if sys.stdout.isatty():
# Join the colors if they are a list, if it's a string this a noop
modifiers = "".join(modifiers)
return f"{modifiers}{text}{Colors.RESET}"
return text
def term_width():
""" Get terminal width in columns (80 if stdout is not a terminal) """
return shutil.get_terminal_size().columns
def schema_dir():
"""
Return the effective schema directory, preferring in-tree before
system schema directory.
"""
script_dir = os.path.dirname(os.path.abspath(__file__))
schema_dir_ = os.path.abspath(f"{script_dir}/{RELATIVE_SCHEMA_DIR}")
if not os.path.isdir(schema_dir_):
schema_dir_ = SYS_SCHEMA_DIR
if not os.path.isdir(schema_dir_):
raise YnlException(f"Schema directory {schema_dir_} does not exist")
return schema_dir_
def spec_dir():
"""
Return the effective spec directory, relative to the effective
schema directory.
"""
spec_dir_ = schema_dir() + '/specs'
if not os.path.isdir(spec_dir_):
raise YnlException(f"Spec directory {spec_dir_} does not exist")
return spec_dir_
class YnlEncoder(json.JSONEncoder):
"""A custom encoder for emitting JSON with ynl-specific instance types"""
def default(self, o):
if isinstance(o, bytes):
return bytes.hex(o)
if isinstance(o, set):
return sorted(o)
return json.JSONEncoder.default(self, o)
def print_attr_list(ynl, attr_names, attr_set, indent=2):
"""Print a list of attributes with their types and documentation."""
prefix = ' ' * indent
for attr_name in attr_names:
if attr_name in attr_set.attrs:
attr = attr_set.attrs[attr_name]
attr_info = f'{prefix}- {color(attr_name, Colors.BOLD)}: {attr.type}'
if 'enum' in attr.yaml:
enum_name = attr.yaml['enum']
attr_info += f" (enum: {enum_name})"
# Print enum values if available
if enum_name in ynl.consts:
const = ynl.consts[enum_name]
enum_values = list(const.entries.keys())
type_fmted = color(const.type.capitalize(), Colors.ITALICS)
attr_info += f"\n{prefix} {type_fmted}: {', '.join(enum_values)}"
# Show nested attributes reference and recursively display them
nested_set_name = None
if attr.type == 'nest' and 'nested-attributes' in attr.yaml:
nested_set_name = attr.yaml['nested-attributes']
attr_info += f" -> {nested_set_name}"
if attr.yaml.get('doc'):
doc_prefix = prefix + ' ' * 4
doc_text = textwrap.fill(attr.yaml['doc'], width=term_width(),
initial_indent=doc_prefix,
subsequent_indent=doc_prefix)
attr_info += f"\n{doc_text}"
print(attr_info)
# Recursively show nested attributes
if nested_set_name in ynl.attr_sets:
nested_set = ynl.attr_sets[nested_set_name]
# Filter out 'unspec' and other unused attrs
nested_names = [n for n in nested_set.attrs.keys()
if nested_set.attrs[n].type != 'unused']
if nested_names:
print_attr_list(ynl, nested_names, nested_set, indent + 4)
def print_mode_attrs(ynl, mode, mode_spec, attr_set, consistent_dd_reply=None):
"""Print a given mode (do/dump/event/notify)."""
mode_title = mode.capitalize()
if 'request' in mode_spec and 'attributes' in mode_spec['request']:
print(f'\n{mode_title} request attributes:')
print_attr_list(ynl, mode_spec['request']['attributes'], attr_set)
if 'reply' in mode_spec and 'attributes' in mode_spec['reply']:
if consistent_dd_reply and mode == "do":
title = None # Dump handling will print in combined format
elif consistent_dd_reply and mode == "dump":
title = 'Do and Dump'
else:
title = f'{mode_title}'
if title:
print(f'\n{title} reply attributes:')
print_attr_list(ynl, mode_spec['reply']['attributes'], attr_set)
def do_doc(ynl, op):
"""Handle --list-attrs $op, print the attr information to stdout"""
print(f'Operation: {color(op.name, Colors.BOLD)}')
print(op.yaml['doc'])
consistent_dd_reply = False
if 'do' in op.yaml and 'dump' in op.yaml and 'reply' in op.yaml['do'] and \
op.yaml['do']['reply'] == op.yaml['dump'].get('reply'):
consistent_dd_reply = True
for mode in ['do', 'dump']:
if mode in op.yaml:
print_mode_attrs(ynl, mode, op.yaml[mode], op.attr_set,
consistent_dd_reply=consistent_dd_reply)
if 'attributes' in op.yaml.get('event', {}):
print('\nEvent attributes:')
print_attr_list(ynl, op.yaml['event']['attributes'], op.attr_set)
if 'notify' in op.yaml:
mode_spec = op.yaml['notify']
ref_spec = ynl.msgs.get(mode_spec).yaml.get('do')
if not ref_spec:
ref_spec = ynl.msgs.get(mode_spec).yaml.get('dump')
if ref_spec:
print('\nNotification attributes:')
print_attr_list(ynl, ref_spec['reply']['attributes'], op.attr_set)
if 'mcgrp' in op.yaml:
print(f"\nMulticast group: {op.yaml['mcgrp']}")
# pylint: disable=too-many-locals,too-many-branches,too-many-statements
def main():
"""YNL cli tool"""
description = """
YNL CLI utility - a general purpose netlink utility that uses YAML
specs to drive protocol encoding and decoding.
"""
epilog = """
The --multi option can be repeated to include several do operations
in the same netlink payload.
"""
parser = argparse.ArgumentParser(description=description,
epilog=epilog, add_help=False)
gen_group = parser.add_argument_group('General options')
gen_group.add_argument('-h', '--help', action='help',
help='show this help message and exit')
spec_group = parser.add_argument_group('Netlink family selection')
spec_sel = spec_group.add_mutually_exclusive_group(required=True)
spec_sel.add_argument('--list-families', action='store_true',
help=('list Netlink families supported by YNL '
'(which have a spec available in the standard '
'system path)'))
spec_sel.add_argument('--family', dest='family', type=str,
help='name of the Netlink FAMILY to use')
spec_sel.add_argument('--spec', dest='spec', type=str,
help='full file path to the YAML spec file')
ops_group = parser.add_argument_group('Operations')
ops = ops_group.add_mutually_exclusive_group()
ops.add_argument('--do', dest='do', metavar='DO-OPERATION', type=str)
ops.add_argument('--dump', dest='dump', metavar='DUMP-OPERATION', type=str)
ops.add_argument('--multi', dest='multi', nargs=2, action='append',
metavar=('DO-OPERATION', 'JSON_TEXT'), type=str,
help="Multi-message operation sequence (for nftables)")
ops.add_argument('--list-ops', action='store_true',
help="List available --do and --dump operations")
ops.add_argument('--list-msgs', action='store_true',
help="List all messages of the family (incl. notifications)")
ops.add_argument('--list-attrs', '--doc', dest='list_attrs', metavar='MSG',
type=str, help='List attributes for a message / operation')
ops.add_argument('--validate', action='store_true',
help="Validate the spec against schema and exit")
io_group = parser.add_argument_group('Input / Output')
io_group.add_argument('--json', dest='json_text', type=str,
help=('Specify attributes of the message to send '
'to the kernel in JSON format. Can be left out '
'if the message is expected to be empty.'))
io_group.add_argument('--output-json', action='store_true',
help='Format output as JSON')
ntf_group = parser.add_argument_group('Notifications')
ntf_group.add_argument('--subscribe', dest='ntf', type=str)
ntf_group.add_argument('--duration', dest='duration', type=int,
help='when subscribed, watch for DURATION seconds')
ntf_group.add_argument('--sleep', dest='duration', type=int,
help='alias for duration')
nlflags = parser.add_argument_group('Netlink message flags (NLM_F_*)',
('Extra flags to set in nlmsg_flags of '
'the request, used mostly by older '
'Classic Netlink families.'))
nlflags.add_argument('--replace', dest='flags', action='append_const',
const=Netlink.NLM_F_REPLACE)
nlflags.add_argument('--excl', dest='flags', action='append_const',
const=Netlink.NLM_F_EXCL)
nlflags.add_argument('--create', dest='flags', action='append_const',
const=Netlink.NLM_F_CREATE)
nlflags.add_argument('--append', dest='flags', action='append_const',
const=Netlink.NLM_F_APPEND)
schema_group = parser.add_argument_group('Development options')
schema_group.add_argument('--schema', dest='schema', type=str,
help="JSON schema to validate the spec")
schema_group.add_argument('--no-schema', action='store_true')
dbg_group = parser.add_argument_group('Debug options')
io_group.add_argument('--policy', action='store_true',
help='Query kernel policy for the operation instead of executing it')
dbg_group.add_argument('--dbg-small-recv', default=0, const=4000,
action='store', nargs='?', type=int, metavar='INT',
help="Length of buffers used for recv()")
dbg_group.add_argument('--process-unknown', action=argparse.BooleanOptionalAction)
args = parser.parse_args()
def output(msg):
if args.output_json:
print(json.dumps(msg, cls=YnlEncoder))
else:
pprint.pprint(msg, width=term_width(), compact=True)
if args.list_families:
for filename in sorted(os.listdir(spec_dir())):
if filename.endswith('.yaml'):
print(filename.removesuffix('.yaml'))
return
if args.no_schema:
args.schema = ''
attrs = {}
if args.json_text:
attrs = json.loads(args.json_text)
if args.family:
spec = f"{spec_dir()}/{args.family}.yaml"
else:
spec = args.spec
if not os.path.isfile(spec):
raise YnlException(f"Spec file {spec} does not exist")
if args.validate:
try:
SpecFamily(spec, args.schema)
except SpecException as error:
print(error)
sys.exit(1)
return
if args.family: # set behaviour when using installed specs
if args.schema is None and spec.startswith(SYS_SCHEMA_DIR):
args.schema = '' # disable schema validation when installed
if args.process_unknown is None:
args.process_unknown = True
ynl = YnlFamily(spec, args.schema, args.process_unknown,
recv_size=args.dbg_small_recv)
if args.dbg_small_recv:
ynl.set_recv_dbg(True)
if args.policy:
if args.do:
pol = ynl.get_policy(args.do, 'do')
output(pol.to_dict() if pol else None)
args.do = None
if args.dump:
pol = ynl.get_policy(args.dump, 'dump')
output(pol.to_dict() if pol else None)
args.dump = None
if args.ntf:
ynl.ntf_subscribe(args.ntf)
if args.list_ops:
for op_name, op in ynl.ops.items():
print(op_name, " [", ", ".join(op.modes), "]")
if args.list_msgs:
for op_name, op in ynl.msgs.items():
print(op_name, " [", ", ".join(op.modes), "]")
if args.list_attrs:
op = ynl.msgs.get(args.list_attrs)
if not op:
print(f'Operation {args.list_attrs} not found')
sys.exit(1)
do_doc(ynl, op)
try:
if args.do:
reply = ynl.do(args.do, attrs, args.flags)
output(reply)
if args.dump:
reply = ynl.dump(args.dump, attrs)
output(reply)
if args.multi:
ops = [ (item[0], json.loads(item[1]), args.flags or []) for item in args.multi ]
reply = ynl.do_multi(ops)
output(reply)
if args.ntf:
for msg in ynl.poll_ntf(duration=args.duration):
output(msg)
except NlError as e:
print(e)
sys.exit(1)
except KeyboardInterrupt:
pass
except BrokenPipeError:
pass
if __name__ == "__main__":
main()
@@ -0,0 +1,16 @@
# SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
""" YNL library """
from .nlspec import SpecAttr, SpecAttrSet, SpecEnumEntry, SpecEnumSet, \
SpecFamily, SpecOperation, SpecSubMessage, SpecSubMessageFormat, \
SpecException
from .ynl import YnlFamily, Netlink, NlError, NlPolicy, YnlException
from .doc_generator import YnlDocGenerator
__all__ = ["SpecAttr", "SpecAttrSet", "SpecEnumEntry", "SpecEnumSet",
"SpecFamily", "SpecOperation", "SpecSubMessage", "SpecSubMessageFormat",
"SpecException",
"YnlFamily", "Netlink", "NlError", "NlPolicy", "YnlException",
"YnlDocGenerator"]
@@ -0,0 +1,404 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0
# -*- coding: utf-8; mode: python -*-
"""
Class to auto generate the documentation for Netlink specifications.
:copyright: Copyright (C) 2023 Breno Leitao <leitao@debian.org>
:license: GPL Version 2, June 1991 see linux/COPYING for details.
This class performs extensive parsing to the Linux kernel's netlink YAML
spec files, in an effort to avoid needing to heavily mark up the original
YAML file.
This code is split in two classes:
1) RST formatters: Use to convert a string to a RST output
2) YAML Netlink (YNL) doc generator: Generate docs from YAML data
"""
from typing import Any, Dict, List
import yaml
LINE_STR = '__lineno__'
class NumberedSafeLoader(yaml.SafeLoader): # pylint: disable=R0901
"""Override the SafeLoader class to add line number to parsed data"""
def construct_mapping(self, node, *args, **kwargs):
mapping = super().construct_mapping(node, *args, **kwargs)
mapping[LINE_STR] = node.start_mark.line
return mapping
class RstFormatters:
"""RST Formatters"""
SPACE_PER_LEVEL = 4
@staticmethod
def headroom(level: int) -> str:
"""Return space to format"""
return " " * (level * RstFormatters.SPACE_PER_LEVEL)
@staticmethod
def bold(text: str) -> str:
"""Format bold text"""
return f"**{text}**"
@staticmethod
def inline(text: str) -> str:
"""Format inline text"""
return f"``{text}``"
@staticmethod
def sanitize(text: str) -> str:
"""Remove newlines and multiple spaces"""
# This is useful for some fields that are spread across multiple lines
return str(text).replace("\n", " ").strip()
def rst_fields(self, key: str, value: str, level: int = 0) -> str:
"""Return a RST formatted field"""
return self.headroom(level) + f":{key}: {value}"
def rst_definition(self, key: str, value: Any, level: int = 0) -> str:
"""Format a single rst definition"""
return self.headroom(level) + key + "\n" + self.headroom(level + 1) + str(value)
def rst_paragraph(self, paragraph: str, level: int = 0) -> str:
"""Return a formatted paragraph"""
return self.headroom(level) + paragraph
def rst_bullet(self, item: str, level: int = 0) -> str:
"""Return a formatted a bullet"""
return self.headroom(level) + f"- {item}"
@staticmethod
def rst_subsection(title: str) -> str:
"""Add a sub-section to the document"""
return f"{title}\n" + "-" * len(title)
@staticmethod
def rst_subsubsection(title: str) -> str:
"""Add a sub-sub-section to the document"""
return f"{title}\n" + "~" * len(title)
@staticmethod
def rst_section(namespace: str, prefix: str, title: str) -> str:
"""Add a section to the document"""
return f".. _{namespace}-{prefix}-{title}:\n\n{title}\n" + "=" * len(title)
@staticmethod
def rst_subtitle(title: str) -> str:
"""Add a subtitle to the document"""
return "\n" + "-" * len(title) + f"\n{title}\n" + "-" * len(title) + "\n\n"
@staticmethod
def rst_title(title: str) -> str:
"""Add a title to the document"""
return "=" * len(title) + f"\n{title}\n" + "=" * len(title) + "\n\n"
def rst_list_inline(self, list_: List[str], level: int = 0) -> str:
"""Format a list using inlines"""
return self.headroom(level) + "[" + ", ".join(self.inline(i) for i in list_) + "]"
@staticmethod
def rst_ref(namespace: str, prefix: str, name: str) -> str:
"""Add a hyperlink to the document"""
mappings = {'enum': 'definition',
'fixed-header': 'definition',
'nested-attributes': 'attribute-set',
'struct': 'definition'}
prefix = mappings.get(prefix, prefix)
return f":ref:`{namespace}-{prefix}-{name}`"
def rst_header(self) -> str:
"""The headers for all the auto generated RST files"""
lines = []
lines.append(self.rst_paragraph(".. SPDX-License-Identifier: GPL-2.0"))
lines.append(self.rst_paragraph(".. NOTE: This document was auto-generated.\n\n"))
return "\n".join(lines)
@staticmethod
def rst_toctree(maxdepth: int = 2) -> str:
"""Generate a toctree RST primitive"""
lines = []
lines.append(".. toctree::")
lines.append(f" :maxdepth: {maxdepth}\n\n")
return "\n".join(lines)
@staticmethod
def rst_label(title: str) -> str:
"""Return a formatted label"""
return f".. _{title}:\n\n"
@staticmethod
def rst_lineno(lineno: int) -> str:
"""Return a lineno comment"""
return f".. LINENO {lineno}\n"
class YnlDocGenerator:
"""YAML Netlink specs Parser"""
fmt = RstFormatters()
def parse_mcast_group(self, mcast_group: List[Dict[str, Any]]) -> str:
"""Parse 'multicast' group list and return a formatted string"""
lines = []
for group in mcast_group:
lines.append(self.fmt.rst_bullet(group["name"]))
return "\n".join(lines)
def parse_do(self, do_dict: Dict[str, Any], level: int = 0) -> str:
"""Parse 'do' section and return a formatted string"""
lines = []
if LINE_STR in do_dict:
lines.append(self.fmt.rst_lineno(do_dict[LINE_STR]))
for key in do_dict.keys():
if key == LINE_STR:
continue
lines.append(self.fmt.rst_paragraph(self.fmt.bold(key), level + 1))
if key in ['request', 'reply']:
lines.append(self.parse_op_attributes(do_dict[key], level + 1) + "\n")
else:
lines.append(self.fmt.headroom(level + 2) + do_dict[key] + "\n")
return "\n".join(lines)
def parse_op_attributes(self, attrs: Dict[str, Any], level: int = 0) -> str:
"""Parse 'attributes' section"""
if "attributes" not in attrs:
return ""
lines = [self.fmt.rst_fields("attributes",
self.fmt.rst_list_inline(attrs["attributes"]),
level + 1)]
return "\n".join(lines)
def parse_operations(self, operations: List[Dict[str, Any]], namespace: str) -> str:
"""Parse operations block"""
preprocessed = ["name", "doc", "title", "do", "dump", "flags", "event"]
linkable = ["fixed-header", "attribute-set"]
lines = []
for operation in operations:
if LINE_STR in operation:
lines.append(self.fmt.rst_lineno(operation[LINE_STR]))
lines.append(self.fmt.rst_section(namespace, 'operation',
operation["name"]))
lines.append(self.fmt.rst_paragraph(operation["doc"]) + "\n")
for key in operation.keys():
if key == LINE_STR:
continue
if key in preprocessed:
# Skip the special fields
continue
value = operation[key]
if key in linkable:
value = self.fmt.rst_ref(namespace, key, value)
lines.append(self.fmt.rst_fields(key, value, 0))
if 'flags' in operation:
lines.append(self.fmt.rst_fields('flags',
self.fmt.rst_list_inline(operation['flags'])))
if "do" in operation:
lines.append(self.fmt.rst_paragraph(":do:", 0))
lines.append(self.parse_do(operation["do"], 0))
if "dump" in operation:
lines.append(self.fmt.rst_paragraph(":dump:", 0))
lines.append(self.parse_do(operation["dump"], 0))
if "event" in operation:
lines.append(self.fmt.rst_paragraph(":event:", 0))
lines.append(self.parse_op_attributes(operation["event"], 0))
# New line after fields
lines.append("\n")
return "\n".join(lines)
def parse_entries(self, entries: List[Dict[str, Any]], level: int) -> str:
"""Parse a list of entries"""
ignored = ["pad"]
lines = []
for entry in entries:
if isinstance(entry, dict):
# entries could be a list or a dictionary
field_name = entry.get("name", "")
if field_name in ignored:
continue
type_ = entry.get("type")
if type_:
field_name += f" ({self.fmt.inline(type_)})"
lines.append(
self.fmt.rst_fields(field_name,
self.fmt.sanitize(entry.get("doc", "")),
level)
)
elif isinstance(entry, list):
lines.append(self.fmt.rst_list_inline(entry, level))
else:
lines.append(self.fmt.rst_bullet(self.fmt.inline(self.fmt.sanitize(entry)),
level))
lines.append("\n")
return "\n".join(lines)
def parse_definitions(self, defs: Dict[str, Any], namespace: str) -> str:
"""Parse definitions section"""
preprocessed = ["name", "entries", "members"]
ignored = ["render-max"] # This is not printed
lines = []
for definition in defs:
if LINE_STR in definition:
lines.append(self.fmt.rst_lineno(definition[LINE_STR]))
lines.append(self.fmt.rst_section(namespace, 'definition', definition["name"]))
for k in definition.keys():
if k == LINE_STR:
continue
if k in preprocessed + ignored:
continue
lines.append(self.fmt.rst_fields(k, self.fmt.sanitize(definition[k]), 0))
# Field list needs to finish with a new line
lines.append("\n")
if "entries" in definition:
lines.append(self.fmt.rst_paragraph(":entries:", 0))
lines.append(self.parse_entries(definition["entries"], 1))
if "members" in definition:
lines.append(self.fmt.rst_paragraph(":members:", 0))
lines.append(self.parse_entries(definition["members"], 1))
return "\n".join(lines)
def parse_attr_sets(self, entries: List[Dict[str, Any]], namespace: str) -> str:
"""Parse attribute from attribute-set"""
preprocessed = ["name", "type"]
linkable = ["enum", "nested-attributes", "struct", "sub-message"]
ignored = ["checks"]
lines = []
for entry in entries:
lines.append(self.fmt.rst_section(namespace, 'attribute-set',
entry["name"]))
if "doc" in entry:
lines.append(self.fmt.rst_paragraph(entry["doc"], 0) + "\n")
for attr in entry["attributes"]:
if LINE_STR in attr:
lines.append(self.fmt.rst_lineno(attr[LINE_STR]))
type_ = attr.get("type")
attr_line = attr["name"]
if type_:
# Add the attribute type in the same line
attr_line += f" ({self.fmt.inline(type_)})"
lines.append(self.fmt.rst_subsubsection(attr_line))
for k in attr.keys():
if k == LINE_STR:
continue
if k in preprocessed + ignored:
continue
if k in linkable:
value = self.fmt.rst_ref(namespace, k, attr[k])
else:
value = self.fmt.sanitize(attr[k])
lines.append(self.fmt.rst_fields(k, value, 0))
lines.append("\n")
return "\n".join(lines)
def parse_sub_messages(self, entries: List[Dict[str, Any]], namespace: str) -> str:
"""Parse sub-message definitions"""
lines = []
for entry in entries:
lines.append(self.fmt.rst_section(namespace, 'sub-message',
entry["name"]))
for fmt in entry["formats"]:
value = fmt["value"]
lines.append(self.fmt.rst_bullet(self.fmt.bold(value)))
for attr in ['fixed-header', 'attribute-set']:
if attr in fmt:
lines.append(self.fmt.rst_fields(attr,
self.fmt.rst_ref(namespace,
attr,
fmt[attr]),
1))
lines.append("\n")
return "\n".join(lines)
def parse_yaml(self, obj: Dict[str, Any]) -> str:
"""Format the whole YAML into a RST string"""
lines = []
# Main header
lineno = obj.get('__lineno__', 0)
lines.append(self.fmt.rst_lineno(lineno))
family = obj['name']
lines.append(self.fmt.rst_header())
lines.append(self.fmt.rst_label("netlink-" + family))
title = f"Family ``{family}`` netlink specification"
lines.append(self.fmt.rst_title(title))
lines.append(self.fmt.rst_paragraph(".. contents:: :depth: 3\n"))
if "doc" in obj:
lines.append(self.fmt.rst_subtitle("Summary"))
lines.append(self.fmt.rst_paragraph(obj["doc"], 0))
# Operations
if "operations" in obj:
lines.append(self.fmt.rst_subtitle("Operations"))
lines.append(self.parse_operations(obj["operations"]["list"],
family))
# Multicast groups
if "mcast-groups" in obj:
lines.append(self.fmt.rst_subtitle("Multicast groups"))
lines.append(self.parse_mcast_group(obj["mcast-groups"]["list"]))
# Definitions
if "definitions" in obj:
lines.append(self.fmt.rst_subtitle("Definitions"))
lines.append(self.parse_definitions(obj["definitions"], family))
# Attributes set
if "attribute-sets" in obj:
lines.append(self.fmt.rst_subtitle("Attribute sets"))
lines.append(self.parse_attr_sets(obj["attribute-sets"], family))
# Sub-messages
if "sub-messages" in obj:
lines.append(self.fmt.rst_subtitle("Sub-messages"))
lines.append(self.parse_sub_messages(obj["sub-messages"], family))
return "\n".join(lines)
# Main functions
# ==============
def parse_yaml_file(self, filename: str) -> str:
"""Transform the YAML specified by filename into an RST-formatted string"""
with open(filename, "r", encoding="utf-8") as spec_file:
numbered_yaml = yaml.load(spec_file, Loader=NumberedSafeLoader)
content = self.parse_yaml(numbered_yaml)
return content
@@ -0,0 +1,631 @@
# SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
#
# pylint: disable=missing-function-docstring, too-many-instance-attributes, too-many-branches
"""
The nlspec is a python library for parsing and using YNL netlink
specifications.
"""
import collections
import importlib
import os
import yaml as pyyaml
class SpecException(Exception):
"""Netlink spec exception.
"""
class SpecElement:
"""Netlink spec element.
Abstract element of the Netlink spec. Implements the dictionary interface
for access to the raw spec. Supports iterative resolution of dependencies
across elements and class inheritance levels. The elements of the spec
may refer to each other, and although loops should be very rare, having
to maintain correct ordering of instantiation is painful, so the resolve()
method should be used to perform parts of init which require access to
other parts of the spec.
Attributes:
yaml raw spec as loaded from the spec file
family back reference to the full family
name name of the entity as listed in the spec (optional)
ident_name name which can be safely used as identifier in code (optional)
"""
def __init__(self, family, yaml):
self.yaml = yaml
self.family = family
if 'name' in self.yaml:
self.name = self.yaml['name']
self.ident_name = self.name.replace('-', '_')
self._super_resolved = False
family.add_unresolved(self)
def __getitem__(self, key):
return self.yaml[key]
def __contains__(self, key):
return key in self.yaml
def get(self, key, default=None):
return self.yaml.get(key, default)
def resolve_up(self, up):
if not self._super_resolved:
up.resolve()
self._super_resolved = True
def resolve(self):
pass
class SpecEnumEntry(SpecElement):
""" Entry within an enum declared in the Netlink spec.
Attributes:
doc documentation string
enum_set back reference to the enum
value numerical value of this enum (use accessors in most situations!)
Methods:
raw_value raw value, i.e. the id in the enum, unlike user value which is a mask for flags
user_value user value, same as raw value for enums, for flags it's the mask
"""
def __init__(self, enum_set, yaml, prev, value_start):
if isinstance(yaml, str):
yaml = {'name': yaml}
super().__init__(enum_set.family, yaml)
self.doc = yaml.get('doc', '')
self.enum_set = enum_set
if 'value' in yaml:
self.value = yaml['value']
elif prev:
self.value = prev.value + 1
else:
self.value = value_start
def has_doc(self):
return bool(self.doc)
def raw_value(self):
return self.value
def user_value(self, as_flags=None):
if self.enum_set['type'] == 'flags' or as_flags:
return 1 << self.value
return self.value
class SpecEnumSet(SpecElement):
""" Enum type
Represents an enumeration (list of numerical constants)
as declared in the "definitions" section of the spec.
Attributes:
type enum or flags
entries entries by name
entries_by_val entries by value
Methods:
get_mask for flags compute the mask of all defined values
"""
def __init__(self, family, yaml):
super().__init__(family, yaml)
self.type = yaml['type']
prev_entry = None
value_start = self.yaml.get('value-start', 0)
self.entries = {}
self.entries_by_val = {}
for entry in self.yaml['entries']:
e = self.new_entry(entry, prev_entry, value_start)
self.entries[e.name] = e
self.entries_by_val[e.raw_value()] = e
prev_entry = e
def new_entry(self, entry, prev_entry, value_start):
return SpecEnumEntry(self, entry, prev_entry, value_start)
def has_doc(self):
if 'doc' in self.yaml:
return True
return self.has_entry_doc()
def has_entry_doc(self):
for entry in self.entries.values():
if entry.has_doc():
return True
return False
def get_mask(self, as_flags=None):
mask = 0
for e in self.entries.values():
mask += e.user_value(as_flags)
return mask
class SpecAttr(SpecElement):
""" Single Netlink attribute type
Represents a single attribute type within an attr space.
Attributes:
type string, attribute type
value numerical ID when serialized
attr_set Attribute Set containing this attr
is_multi bool, attr may repeat multiple times
struct_name string, name of struct definition
sub_type string, name of sub type
len integer, optional byte length of binary types
display_hint string, hint to help choose format specifier
when displaying the value
sub_message string, name of sub message type
selector string, name of attribute used to select
sub-message type
is_auto_scalar bool, attr is a variable-size scalar
"""
def __init__(self, family, attr_set, yaml, value):
super().__init__(family, yaml)
self.type = yaml['type']
self.value = value
self.attr_set = attr_set
self.is_multi = yaml.get('multi-attr', False)
self.struct_name = yaml.get('struct')
self.sub_type = yaml.get('sub-type')
self.byte_order = yaml.get('byte-order')
self.len = yaml.get('len')
self.display_hint = yaml.get('display-hint')
self.sub_message = yaml.get('sub-message')
self.selector = yaml.get('selector')
self.is_auto_scalar = self.type in ("sint", "uint")
class SpecAttrSet(SpecElement):
""" Netlink Attribute Set class.
Represents a ID space of attributes within Netlink.
Note that unlike other elements, which expose contents of the raw spec
via the dictionary interface Attribute Set exposes attributes by name.
Attributes:
attrs ordered dict of all attributes (indexed by name)
attrs_by_val ordered dict of all attributes (indexed by value)
subset_of parent set if this is a subset, otherwise None
"""
def __init__(self, family, yaml):
super().__init__(family, yaml)
self.subset_of = self.yaml.get('subset-of', None)
self.attrs = collections.OrderedDict()
self.attrs_by_val = collections.OrderedDict()
if self.subset_of is None:
val = 1
for elem in self.yaml['attributes']:
if 'value' in elem:
val = elem['value']
attr = self.new_attr(elem, val)
self.attrs[attr.name] = attr
self.attrs_by_val[attr.value] = attr
val += 1
else:
real_set = family.attr_sets[self.subset_of]
for elem in self.yaml['attributes']:
real_attr = real_set[elem['name']]
combined_elem = real_attr.yaml | elem
attr = self.new_attr(combined_elem, real_attr.value)
self.attrs[attr.name] = attr
self.attrs_by_val[attr.value] = attr
def new_attr(self, elem, value):
return SpecAttr(self.family, self, elem, value)
def __getitem__(self, key):
return self.attrs[key]
def __contains__(self, key):
return key in self.attrs
def __iter__(self):
yield from self.attrs
def items(self):
return self.attrs.items()
class SpecStructMember(SpecElement):
"""Struct member attribute
Represents a single struct member attribute.
Attributes:
type string, type of the member attribute
byte_order string or None for native byte order
enum string, name of the enum definition
len integer, optional byte length of binary types
display_hint string, hint to help choose format specifier
when displaying the value
struct string, name of nested struct type
"""
def __init__(self, family, yaml):
super().__init__(family, yaml)
self.type = yaml['type']
self.byte_order = yaml.get('byte-order')
self.enum = yaml.get('enum')
self.len = yaml.get('len')
self.display_hint = yaml.get('display-hint')
self.struct = yaml.get('struct')
class SpecStruct(SpecElement):
"""Netlink struct type
Represents a C struct definition.
Attributes:
members ordered list of struct members
"""
def __init__(self, family, yaml):
super().__init__(family, yaml)
self.members = []
for member in yaml.get('members', []):
self.members.append(self.new_member(family, member))
def new_member(self, family, elem):
return SpecStructMember(family, elem)
def __iter__(self):
yield from self.members
def items(self):
return self.members
class SpecSubMessage(SpecElement):
""" Netlink sub-message definition
Represents a set of sub-message formats for polymorphic nlattrs
that contain type-specific sub messages.
Attributes:
name string, name of sub-message definition
formats dict of sub-message formats indexed by match value
"""
def __init__(self, family, yaml):
super().__init__(family, yaml)
self.formats = collections.OrderedDict()
for elem in self.yaml['formats']:
msg_format = self.new_format(family, elem)
self.formats[msg_format.value] = msg_format
def new_format(self, family, msg_format):
return SpecSubMessageFormat(family, msg_format)
class SpecSubMessageFormat(SpecElement):
""" Netlink sub-message format definition
Represents a single format for a sub-message.
Attributes:
value attribute value to match against type selector
fixed_header string, name of fixed header, or None
attr_set string, name of attribute set, or None
"""
def __init__(self, family, yaml):
super().__init__(family, yaml)
self.value = yaml.get('value')
self.fixed_header = yaml.get('fixed-header')
self.attr_set = yaml.get('attribute-set')
class SpecOperation(SpecElement):
"""Netlink Operation
Information about a single Netlink operation.
Attributes:
value numerical ID when serialized, None if req/rsp values differ
req_value numerical ID when serialized, user -> kernel
rsp_value numerical ID when serialized, user <- kernel
modes supported operation modes (do, dump, event etc.)
is_call bool, whether the operation is a call
is_async bool, whether the operation is a notification
is_resv bool, whether the operation does not exist (it's just a reserved ID)
attr_set attribute set name
fixed_header string, optional name of fixed header struct
yaml raw spec as loaded from the spec file
"""
def __init__(self, family, yaml, req_value, rsp_value):
super().__init__(family, yaml)
self.value = req_value if req_value == rsp_value else None
self.req_value = req_value
self.rsp_value = rsp_value
self.modes = yaml.keys() & {'do', 'dump', 'event', 'notify'}
self.is_call = 'do' in yaml or 'dump' in yaml
self.is_async = 'notify' in yaml or 'event' in yaml
self.is_resv = not self.is_async and not self.is_call
self.fixed_header = self.yaml.get('fixed-header', family.fixed_header)
# Added by resolve:
self.attr_set = None
delattr(self, "attr_set")
def resolve(self):
self.resolve_up(super())
if 'attribute-set' in self.yaml:
attr_set_name = self.yaml['attribute-set']
elif 'notify' in self.yaml:
msg = self.family.msgs[self.yaml['notify']]
attr_set_name = msg['attribute-set']
elif self.is_resv:
attr_set_name = ''
else:
raise SpecException(f"Can't resolve attribute set for op '{self.name}'")
if attr_set_name:
self.attr_set = self.family.attr_sets[attr_set_name]
class SpecMcastGroup(SpecElement):
"""Netlink Multicast Group
Information about a multicast group.
Value is only used for classic netlink families that use the
netlink-raw schema. Genetlink families use dynamic ID allocation
where the ids of multicast groups get resolved at runtime. Value
will be None for genetlink families.
Attributes:
name name of the mulitcast group
value integer id of this multicast group for netlink-raw or None
yaml raw spec as loaded from the spec file
"""
def __init__(self, family, yaml):
super().__init__(family, yaml)
self.value = self.yaml.get('value')
class SpecFamily(SpecElement):
""" Netlink Family Spec class.
Netlink family information loaded from a spec (e.g. in YAML).
Takes care of unfolding implicit information which can be skipped
in the spec itself for brevity.
The class can be used like a dictionary to access the raw spec
elements but that's usually a bad idea.
Attributes:
proto protocol type (e.g. genetlink)
msg_id_model enum-model for operations (unified, directional etc.)
license spec license (loaded from an SPDX tag on the spec)
attr_sets dict of attribute sets
msgs dict of all messages (index by name)
sub_msgs dict of all sub messages (index by name)
ops dict of all valid requests / responses
ntfs dict of all async events
consts dict of all constants/enums
fixed_header string, optional name of family default fixed header struct
mcast_groups dict of all multicast groups (index by name)
kernel_family dict of kernel family attributes
"""
# To be loaded dynamically as needed
jsonschema = None
try:
_yaml_loader = pyyaml.CSafeLoader
except AttributeError:
_yaml_loader = pyyaml.SafeLoader
def __init__(self, spec_path, schema_path=None, exclude_ops=None):
with open(spec_path, "r", encoding='utf-8') as stream:
prefix = '# SPDX-License-Identifier: '
first = stream.readline().strip()
if not first.startswith(prefix):
raise SpecException('SPDX license tag required in the spec')
self.license = first[len(prefix):]
stream.seek(0)
spec = pyyaml.load(stream, Loader=self._yaml_loader)
self.fixed_header = None
self._resolution_list = []
super().__init__(self, spec)
self._exclude_ops = exclude_ops if exclude_ops else []
self.proto = self.yaml.get('protocol', 'genetlink')
self.msg_id_model = self.yaml['operations'].get('enum-model', 'unified')
if schema_path is None:
schema_path = os.path.dirname(os.path.dirname(spec_path)) + f'/{self.proto}.yaml'
if schema_path:
with open(schema_path, "r", encoding='utf-8') as stream:
schema = pyyaml.load(stream, Loader=self._yaml_loader)
if SpecFamily.jsonschema is None:
SpecFamily.jsonschema = importlib.import_module("jsonschema")
SpecFamily.jsonschema.validate(self.yaml, schema)
self.attr_sets = collections.OrderedDict()
self.sub_msgs = collections.OrderedDict()
self.msgs = collections.OrderedDict()
self.req_by_value = collections.OrderedDict()
self.rsp_by_value = collections.OrderedDict()
self.ops = collections.OrderedDict()
self.ntfs = collections.OrderedDict()
self.consts = collections.OrderedDict()
self.mcast_groups = collections.OrderedDict()
self.kernel_family = collections.OrderedDict(self.yaml.get('kernel-family', {}))
last_exception = None
while len(self._resolution_list) > 0:
resolved = []
unresolved = self._resolution_list
self._resolution_list = []
for elem in unresolved:
try:
elem.resolve()
except (KeyError, AttributeError) as e:
self._resolution_list.append(elem)
last_exception = e
continue
resolved.append(elem)
if len(resolved) == 0:
raise last_exception
def new_enum(self, elem):
return SpecEnumSet(self, elem)
def new_attr_set(self, elem):
return SpecAttrSet(self, elem)
def new_struct(self, elem):
return SpecStruct(self, elem)
def new_sub_message(self, elem):
return SpecSubMessage(self, elem)
def new_operation(self, elem, req_val, rsp_val):
return SpecOperation(self, elem, req_val, rsp_val)
def new_mcast_group(self, elem):
return SpecMcastGroup(self, elem)
def add_unresolved(self, elem):
self._resolution_list.append(elem)
def _dictify_ops_unified(self):
self.fixed_header = self.yaml['operations'].get('fixed-header')
val = 1
for elem in self.yaml['operations']['list']:
if 'value' in elem:
val = elem['value']
op = self.new_operation(elem, val, val)
val += 1
self.msgs[op.name] = op
def _dictify_ops_directional(self):
self.fixed_header = self.yaml['operations'].get('fixed-header')
req_val = rsp_val = 1
for elem in self.yaml['operations']['list']:
if 'notify' in elem or 'event' in elem:
if 'value' in elem:
rsp_val = elem['value']
req_val_next = req_val
rsp_val_next = rsp_val + 1
req_val = None
elif 'do' in elem or 'dump' in elem:
mode = elem['do'] if 'do' in elem else elem['dump']
v = mode.get('request', {}).get('value', None)
if v:
req_val = v
v = mode.get('reply', {}).get('value', None)
if v:
rsp_val = v
rsp_inc = 1 if 'reply' in mode else 0
req_val_next = req_val + 1
rsp_val_next = rsp_val + rsp_inc
else:
raise SpecException("Can't parse directional ops")
if req_val == req_val_next:
req_val = None
if rsp_val == rsp_val_next:
rsp_val = None
skip = False
for exclude in self._exclude_ops:
skip |= bool(exclude.match(elem['name']))
if not skip:
op = self.new_operation(elem, req_val, rsp_val)
self.msgs[op.name] = op
req_val = req_val_next
rsp_val = rsp_val_next
def find_operation(self, name):
"""
For a given operation name, find and return operation spec.
"""
for op in self.yaml['operations']['list']:
if name == op['name']:
return op
return None
def resolve(self):
self.resolve_up(super())
definitions = self.yaml.get('definitions', [])
for elem in definitions:
if elem['type'] == 'enum' or elem['type'] == 'flags':
self.consts[elem['name']] = self.new_enum(elem)
elif elem['type'] == 'struct':
self.consts[elem['name']] = self.new_struct(elem)
else:
self.consts[elem['name']] = elem
for elem in self.yaml['attribute-sets']:
attr_set = self.new_attr_set(elem)
self.attr_sets[elem['name']] = attr_set
for elem in self.yaml.get('sub-messages', []):
sub_message = self.new_sub_message(elem)
self.sub_msgs[sub_message.name] = sub_message
if self.msg_id_model == 'unified':
self._dictify_ops_unified()
elif self.msg_id_model == 'directional':
self._dictify_ops_directional()
for op in self.msgs.values():
if op.req_value is not None:
self.req_by_value[op.req_value] = op
if op.rsp_value is not None:
self.rsp_by_value[op.rsp_value] = op
if not op.is_async and 'attribute-set' in op:
self.ops[op.name] = op
elif op.is_async:
self.ntfs[op.name] = op
mcgs = self.yaml.get('mcast-groups')
if mcgs:
for elem in mcgs['list']:
mcg = self.new_mcast_group(elem)
self.mcast_groups[elem['name']] = mcg
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+85
View File
@@ -0,0 +1,85 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0
# -*- coding: utf-8; mode: python -*-
"""
Script to auto generate the documentation for Netlink specifications.
:copyright: Copyright (C) 2023 Breno Leitao <leitao@debian.org>
:license: GPL Version 2, June 1991 see linux/COPYING for details.
This script performs extensive parsing to the Linux kernel's netlink YAML
spec files, in an effort to avoid needing to heavily mark up the original
YAML file. It uses the library code from scripts/lib.
"""
import os.path
import pathlib
import sys
import argparse
import logging
# pylint: disable=no-name-in-module,wrong-import-position
sys.path.append(pathlib.Path(__file__).resolve().parent.as_posix())
from lib import YnlDocGenerator # pylint: disable=C0413
def parse_arguments() -> argparse.Namespace:
"""Parse arguments from user"""
parser = argparse.ArgumentParser(description="Netlink RST generator")
parser.add_argument("-v", "--verbose", action="store_true")
parser.add_argument("-o", "--output", help="Output file name")
# Index and input are mutually exclusive
group = parser.add_mutually_exclusive_group()
group.add_argument("-i", "--input", help="YAML file name")
args = parser.parse_args()
if args.verbose:
logging.basicConfig(level=logging.DEBUG)
if args.input and not os.path.isfile(args.input):
logging.warning("%s is not a valid file.", args.input)
sys.exit(-1)
if not args.output:
logging.error("No output file specified.")
sys.exit(-1)
if os.path.isfile(args.output):
logging.debug("%s already exists. Overwriting it.", args.output)
return args
def write_to_rstfile(content: str, filename: str) -> None:
"""Write the generated content into an RST file"""
logging.debug("Saving RST file to %s", filename)
with open(filename, "w", encoding="utf-8") as rst_file:
rst_file.write(content)
# pylint: disable=broad-exception-caught
def main() -> None:
"""Main function that reads the YAML files and generates the RST files"""
args = parse_arguments()
parser = YnlDocGenerator()
if args.input:
logging.debug("Parsing %s", args.input)
try:
content = parser.parse_yaml_file(os.path.join(args.input))
except Exception as exception:
logging.warning("Failed to parse %s.", args.input)
logging.warning(exception)
sys.exit(-1)
write_to_rstfile(content, args.output)
if __name__ == "__main__":
main()
@@ -0,0 +1,2 @@
jsonschema==4.*
PyYAML==6.*
@@ -0,0 +1,10 @@
devlink
ethtool
netdev
ovs
rt-addr
rt-link
rt-route
tc
tc-filter-add
wireguard
+97
View File
@@ -0,0 +1,97 @@
# SPDX-License-Identifier: GPL-2.0
# Makefile for YNL tests
include ../Makefile.deps
CC=gcc
CFLAGS += -std=gnu11 -O2 -W -Wall -Wextra -Wno-unused-parameter -Wshadow \
-I../lib/ -I../generated/ -I../../../testing/selftests/ \
-idirafter $(UAPI_PATH)
ifneq ("$(NDEBUG)","1")
CFLAGS += -g -fsanitize=address -fsanitize=leak -static-libasan
endif
LDLIBS=../lib/ynl.a ../generated/protos.a
TEST_PROGS := \
devlink.sh \
ethtool.sh \
rt-addr.sh \
rt-route.sh \
test_ynl_cli.sh \
test_ynl_ethtool.sh \
# end of TEST_PROGS
TEST_GEN_PROGS := \
netdev \
ovs \
rt-link \
tc \
# end of TEST_GEN_PROGS
TEST_GEN_FILES := \
devlink \
ethtool \
rt-addr \
rt-route \
# end of TEST_GEN_FILES
TEST_FILES := \
ethtool.py \
ynl_nsim_lib.sh \
# end of TEST_FILES
CFLAGS_netdev:=$(CFLAGS_netdev) $(CFLAGS_rt-link)
CFLAGS_ovs:=$(CFLAGS_ovs_datapath)
include $(wildcard *.d)
INSTALL_PATH ?= $(DESTDIR)/usr/share/kselftest
all: $(TEST_GEN_PROGS) $(TEST_GEN_FILES)
../lib/ynl.a:
@$(MAKE) -C ../lib
../generated/protos.a:
@$(MAKE) -C ../generated
$(TEST_GEN_PROGS) $(TEST_GEN_FILES): %: %.c ../lib/ynl.a ../generated/protos.a
@echo -e '\tCC test $@'
@$(COMPILE.c) $(CFLAGS_$@) $@.c -o $@.o
@$(LINK.c) $@.o -o $@ $(LDLIBS)
run_tests:
@for test in $(TEST_PROGS); do \
./$$test; \
done
install: $(TEST_GEN_PROGS) $(TEST_GEN_FILES)
@mkdir -p $(INSTALL_PATH)/ynl
@cp ../../../testing/selftests/kselftest/ktap_helpers.sh $(INSTALL_PATH)/
@for test in $(TEST_PROGS); do \
name=$$(basename $$test); \
sed -e 's|^ynl=.*|ynl="ynl"|' \
-e 's|^ynl_ethtool=.*|ynl_ethtool="ynl-ethtool"|' \
-e 's|KSELFTEST_KTAP_HELPERS=.*|KSELFTEST_KTAP_HELPERS="$(INSTALL_PATH)/ktap_helpers.sh"|' \
$$test > $(INSTALL_PATH)/ynl/$$name; \
chmod +x $(INSTALL_PATH)/ynl/$$name; \
done
@for file in $(TEST_FILES); do \
cp $$file $(INSTALL_PATH)/ynl/$$file; \
done
@for bin in $(TEST_GEN_PROGS) $(TEST_GEN_FILES); do \
cp $$bin $(INSTALL_PATH)/ynl/$$bin; \
done
@for test in $(TEST_PROGS) $(TEST_GEN_PROGS); do \
echo "ynl:$$test"; \
done > $(INSTALL_PATH)/kselftest-list.txt
clean:
rm -f *.o *.d *~
distclean: clean
rm -f $(TEST_GEN_PROGS) $(TEST_GEN_FILES)
.PHONY: all install clean distclean run_tests
.DEFAULT_GOAL=all
+14
View File
@@ -0,0 +1,14 @@
CONFIG_DUMMY=m
CONFIG_INET_DIAG=y
CONFIG_IPV6=y
CONFIG_NET_ACT_VLAN=m
CONFIG_NET_CLS_ACT=y
CONFIG_NET_CLS_FLOWER=m
CONFIG_NET_SCH_FQ_CODEL=m
CONFIG_NET_SCH_INGRESS=m
CONFIG_NET_NS=y
CONFIG_NET_SCHED=y
CONFIG_NETDEVSIM=m
CONFIG_NETKIT=y
CONFIG_OPENVSWITCH=m
CONFIG_VETH=m
+101
View File
@@ -0,0 +1,101 @@
// SPDX-License-Identifier: GPL-2.0
#include <stdio.h>
#include <string.h>
#include <ynl.h>
#include <kselftest_harness.h>
#include "devlink-user.h"
FIXTURE(devlink)
{
struct ynl_sock *ys;
};
FIXTURE_SETUP(devlink)
{
self->ys = ynl_sock_create(&ynl_devlink_family, NULL);
ASSERT_NE(NULL, self->ys)
TH_LOG("failed to create devlink socket");
}
FIXTURE_TEARDOWN(devlink)
{
ynl_sock_destroy(self->ys);
}
TEST_F(devlink, dump)
{
struct devlink_get_list *devs;
devs = devlink_get_dump(self->ys);
ASSERT_NE(NULL, devs) {
TH_LOG("dump failed: %s", self->ys->err.msg);
}
if (ynl_dump_empty(devs)) {
devlink_get_list_free(devs);
SKIP(return, "no entries in dump");
}
ynl_dump_foreach(devs, d) {
EXPECT_TRUE((bool)d->_len.bus_name);
EXPECT_TRUE((bool)d->_len.dev_name);
ksft_print_msg("%s/%s\n", d->bus_name, d->dev_name);
}
devlink_get_list_free(devs);
}
TEST_F(devlink, info)
{
struct devlink_get_list *devs;
devs = devlink_get_dump(self->ys);
ASSERT_NE(NULL, devs) {
TH_LOG("dump failed: %s", self->ys->err.msg);
}
if (ynl_dump_empty(devs)) {
devlink_get_list_free(devs);
SKIP(return, "no devices to query");
}
ynl_dump_foreach(devs, d) {
struct devlink_info_get_req *info_req;
struct devlink_info_get_rsp *info_rsp;
unsigned int i;
EXPECT_TRUE((bool)d->_len.bus_name);
EXPECT_TRUE((bool)d->_len.dev_name);
ksft_print_msg("%s/%s:\n", d->bus_name, d->dev_name);
info_req = devlink_info_get_req_alloc();
ASSERT_NE(NULL, info_req);
devlink_info_get_req_set_bus_name(info_req, d->bus_name);
devlink_info_get_req_set_dev_name(info_req, d->dev_name);
info_rsp = devlink_info_get(self->ys, info_req);
devlink_info_get_req_free(info_req);
ASSERT_NE(NULL, info_rsp) {
devlink_get_list_free(devs);
TH_LOG("info_get failed: %s", self->ys->err.msg);
}
EXPECT_TRUE((bool)info_rsp->_len.info_driver_name);
if (info_rsp->_len.info_driver_name)
ksft_print_msg(" driver: %s\n",
info_rsp->info_driver_name);
if (info_rsp->_count.info_version_running)
ksft_print_msg(" running fw:\n");
for (i = 0; i < info_rsp->_count.info_version_running; i++)
ksft_print_msg(" %s: %s\n",
info_rsp->info_version_running[i].info_version_name,
info_rsp->info_version_running[i].info_version_value);
devlink_info_get_rsp_free(info_rsp);
}
devlink_get_list_free(devs);
}
TEST_HARNESS_MAIN
+5
View File
@@ -0,0 +1,5 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
source "$(dirname "$(realpath "$0")")/ynl_nsim_lib.sh"
nsim_setup
"$(dirname "$(realpath "$0")")/devlink"
+92
View File
@@ -0,0 +1,92 @@
// SPDX-License-Identifier: GPL-2.0
#include <stdio.h>
#include <string.h>
#include <ynl.h>
#include <net/if.h>
#include <kselftest_harness.h>
#include "ethtool-user.h"
FIXTURE(ethtool)
{
struct ynl_sock *ys;
};
FIXTURE_SETUP(ethtool)
{
self->ys = ynl_sock_create(&ynl_ethtool_family, NULL);
ASSERT_NE(NULL, self->ys)
TH_LOG("failed to create ethtool socket");
}
FIXTURE_TEARDOWN(ethtool)
{
ynl_sock_destroy(self->ys);
}
TEST_F(ethtool, channels)
{
struct ethtool_channels_get_req_dump creq = {};
struct ethtool_channels_get_list *channels;
creq._present.header = 1; /* ethtool needs an empty nest */
channels = ethtool_channels_get_dump(self->ys, &creq);
ASSERT_NE(NULL, channels) {
TH_LOG("channels dump failed: %s", self->ys->err.msg);
}
if (ynl_dump_empty(channels)) {
ethtool_channels_get_list_free(channels);
SKIP(return, "no entries in channels dump");
}
ynl_dump_foreach(channels, dev) {
EXPECT_TRUE((bool)dev->header._len.dev_name);
ksft_print_msg("%8s: ", dev->header.dev_name);
EXPECT_TRUE(dev->_present.rx_count ||
dev->_present.tx_count ||
dev->_present.combined_count);
if (dev->_present.rx_count)
printf("rx %d ", dev->rx_count);
if (dev->_present.tx_count)
printf("tx %d ", dev->tx_count);
if (dev->_present.combined_count)
printf("combined %d ", dev->combined_count);
printf("\n");
}
ethtool_channels_get_list_free(channels);
}
TEST_F(ethtool, rings)
{
struct ethtool_rings_get_req_dump rreq = {};
struct ethtool_rings_get_list *rings;
rreq._present.header = 1; /* ethtool needs an empty nest */
rings = ethtool_rings_get_dump(self->ys, &rreq);
ASSERT_NE(NULL, rings) {
TH_LOG("rings dump failed: %s", self->ys->err.msg);
}
if (ynl_dump_empty(rings)) {
ethtool_rings_get_list_free(rings);
SKIP(return, "no entries in rings dump");
}
ynl_dump_foreach(rings, dev) {
EXPECT_TRUE((bool)dev->header._len.dev_name);
ksft_print_msg("%8s: ", dev->header.dev_name);
EXPECT_TRUE(dev->_present.rx || dev->_present.tx);
if (dev->_present.rx)
printf("rx %d ", dev->rx);
if (dev->_present.tx)
printf("tx %d ", dev->tx);
printf("\n");
}
ethtool_rings_get_list_free(rings);
}
TEST_HARNESS_MAIN
+469
View File
@@ -0,0 +1,469 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
#
# pylint: disable=too-many-locals, too-many-branches, too-many-statements
# pylint: disable=too-many-return-statements
""" YNL ethtool utility """
import argparse
import pathlib
import pprint
import sys
import re
import os
# pylint: disable=no-name-in-module,wrong-import-position
sys.path.append(pathlib.Path(__file__).resolve().parent.parent.joinpath('pyynl').as_posix())
# pylint: disable=import-error
from cli import schema_dir, spec_dir
from lib import YnlFamily
def args_to_req(ynl, op_name, args, req):
"""
Verify and convert command-line arguments to the ynl-compatible request.
"""
valid_attrs = ynl.operation_do_attributes(op_name)
valid_attrs.remove('header') # not user-provided
if len(args) == 0:
print(f'no attributes, expected: {valid_attrs}')
sys.exit(1)
i = 0
while i < len(args):
attr = args[i]
if i + 1 >= len(args):
print(f'expected value for \'{attr}\'')
sys.exit(1)
if attr not in valid_attrs:
print(f'invalid attribute \'{attr}\', expected: {valid_attrs}')
sys.exit(1)
val = args[i+1]
i += 2
req[attr] = val
def print_field(reply, *desc):
"""
Pretty-print a set of fields from the reply. desc specifies the
fields and the optional type (bool/yn).
"""
if not reply:
return
if len(desc) == 0:
print_field(reply, *zip(reply.keys(), reply.keys()))
return
for spec in desc:
try:
field, name, tp = spec
except ValueError:
field, name = spec
tp = 'int'
value = reply.get(field, None)
if tp == 'yn':
value = 'yes' if value else 'no'
elif tp == 'bool' or isinstance(value, bool):
value = 'on' if value else 'off'
else:
value = 'n/a' if value is None else value
print(f'{name}: {value}')
def print_speed(name, value):
"""
Print out the speed-like strings from the value dict.
"""
speed_re = re.compile(r'[0-9]+base[^/]+/.+')
speed = [ k for k, v in value.items() if v and speed_re.match(k) ]
print(f'{name}: {" ".join(speed)}')
def do_set(ynl, args, op_name):
"""
Prepare request header, parse arguments and do a set operation.
"""
req = {
'header': {
'dev-name': args.device,
},
}
args_to_req(ynl, op_name, args.args, req)
ynl.do(op_name, req)
def do_get(ynl, args, op_name, extra=None):
"""
Prepare request header and get info for a specific device using doit.
"""
extra = extra or {}
req = {'header': {'dev-name': args.device}}
req['header'].update(extra.pop('header', {}))
req.update(extra)
reply = ynl.do(op_name, req)
if not reply:
return {}
if args.json:
pprint.PrettyPrinter().pprint(reply)
sys.exit(0)
reply.pop('header', None)
return reply
def bits_to_dict(attr):
"""
Convert ynl-formatted bitmask to a dict of bit=value.
"""
ret = {}
if 'bits' not in attr:
return {}
if 'bit' not in attr['bits']:
return {}
for bit in attr['bits']['bit']:
if bit['name'] == '':
continue
name = bit['name']
value = bit.get('value', False)
ret[name] = value
return ret
def main():
""" YNL ethtool utility """
parser = argparse.ArgumentParser(description='ethtool wannabe')
parser.add_argument('--json', action=argparse.BooleanOptionalAction)
parser.add_argument('--show-priv-flags', action=argparse.BooleanOptionalAction)
parser.add_argument('--set-priv-flags', action=argparse.BooleanOptionalAction)
parser.add_argument('--show-eee', action=argparse.BooleanOptionalAction)
parser.add_argument('--set-eee', action=argparse.BooleanOptionalAction)
parser.add_argument('-a', '--show-pause', action=argparse.BooleanOptionalAction)
parser.add_argument('-A', '--set-pause', action=argparse.BooleanOptionalAction)
parser.add_argument('-c', '--show-coalesce', action=argparse.BooleanOptionalAction)
parser.add_argument('-C', '--set-coalesce', action=argparse.BooleanOptionalAction)
parser.add_argument('-g', '--show-ring', action=argparse.BooleanOptionalAction)
parser.add_argument('-G', '--set-ring', action=argparse.BooleanOptionalAction)
parser.add_argument('-k', '--show-features', action=argparse.BooleanOptionalAction)
parser.add_argument('-K', '--set-features', action=argparse.BooleanOptionalAction)
parser.add_argument('-l', '--show-channels', action=argparse.BooleanOptionalAction)
parser.add_argument('-L', '--set-channels', action=argparse.BooleanOptionalAction)
parser.add_argument('-T', '--show-time-stamping', action=argparse.BooleanOptionalAction)
parser.add_argument('-S', '--statistics', action=argparse.BooleanOptionalAction)
# TODO: --show-tunnels tunnel-info-get
# TODO: --show-module module-get
# TODO: --get-plca-cfg plca-get
# TODO: --get-plca-status plca-get-status
# TODO: --show-mm mm-get
# TODO: --show-fec fec-get
# TODO: --dump-module-eerpom module-eeprom-get
# TODO: pse-get
# TODO: rss-get
parser.add_argument('device', metavar='device', type=str)
parser.add_argument('args', metavar='args', type=str, nargs='*')
dbg_group = parser.add_argument_group('Debug options')
dbg_group.add_argument('--dbg-small-recv', default=0, const=4000,
action='store', nargs='?', type=int, metavar='INT',
help="Length of buffers used for recv()")
args = parser.parse_args()
spec = os.path.join(spec_dir(), 'ethtool.yaml')
schema = os.path.join(schema_dir(), 'genetlink-legacy.yaml')
ynl = YnlFamily(spec, schema, recv_size=args.dbg_small_recv)
if args.dbg_small_recv:
ynl.set_recv_dbg(True)
if args.set_priv_flags:
# TODO: parse the bitmask
print("not implemented")
return
if args.set_eee:
do_set(ynl, args, 'eee-set')
return
if args.set_pause:
do_set(ynl, args, 'pause-set')
return
if args.set_coalesce:
do_set(ynl, args, 'coalesce-set')
return
if args.set_features:
# TODO: parse the bitmask
print("not implemented")
return
if args.set_channels:
do_set(ynl, args, 'channels-set')
return
if args.set_ring:
do_set(ynl, args, 'rings-set')
return
if args.show_priv_flags:
flags = bits_to_dict(do_get(ynl, args, 'privflags-get')['flags'])
print_field(flags)
return
if args.show_eee:
eee = do_get(ynl, args, 'eee-get')
ours = bits_to_dict(eee['modes-ours'])
peer = bits_to_dict(eee['modes-peer'])
if 'enabled' in eee:
status = 'enabled' if eee['enabled'] else 'disabled'
if 'active' in eee and eee['active']:
status = status + ' - active'
else:
status = status + ' - inactive'
else:
status = 'not supported'
print(f'EEE status: {status}')
print_field(eee, ('tx-lpi-timer', 'Tx LPI'))
print_speed('Advertised EEE link modes', ours)
print_speed('Link partner advertised EEE link modes', peer)
return
if args.show_pause:
print_field(do_get(ynl, args, 'pause-get'),
('autoneg', 'Autonegotiate', 'bool'),
('rx', 'RX', 'bool'),
('tx', 'TX', 'bool'))
return
if args.show_coalesce:
print_field(do_get(ynl, args, 'coalesce-get'))
return
if args.show_features:
reply = do_get(ynl, args, 'features-get')
available = bits_to_dict(reply['hw'])
requested = bits_to_dict(reply['wanted']).keys()
active = bits_to_dict(reply['active']).keys()
never_changed = bits_to_dict(reply['nochange']).keys()
for f in sorted(available):
value = "off"
if f in active:
value = "on"
fixed = ""
if f not in available or f in never_changed:
fixed = " [fixed]"
req = ""
if f in requested:
if f in active:
req = " [requested on]"
else:
req = " [requested off]"
print(f'{f}: {value}{fixed}{req}')
return
if args.show_channels:
reply = do_get(ynl, args, 'channels-get')
print(f'Channel parameters for {args.device}:')
print('Pre-set maximums:')
print_field(reply,
('rx-max', 'RX'),
('tx-max', 'TX'),
('other-max', 'Other'),
('combined-max', 'Combined'))
print('Current hardware settings:')
print_field(reply,
('rx-count', 'RX'),
('tx-count', 'TX'),
('other-count', 'Other'),
('combined-count', 'Combined'))
return
if args.show_ring:
reply = do_get(ynl, args, 'channels-get')
print(f'Ring parameters for {args.device}:')
print('Pre-set maximums:')
print_field(reply,
('rx-max', 'RX'),
('rx-mini-max', 'RX Mini'),
('rx-jumbo-max', 'RX Jumbo'),
('tx-max', 'TX'))
print('Current hardware settings:')
print_field(reply,
('rx', 'RX'),
('rx-mini', 'RX Mini'),
('rx-jumbo', 'RX Jumbo'),
('tx', 'TX'))
print_field(reply,
('rx-buf-len', 'RX Buf Len'),
('cqe-size', 'CQE Size'),
('tx-push', 'TX Push', 'bool'))
return
if args.statistics:
print('NIC statistics:')
# TODO: pass id?
strset = do_get(ynl, args, 'strset-get')
pprint.PrettyPrinter().pprint(strset)
req = {
'groups': {
'size': 1,
'bits': {
'bit':
# TODO: support passing the bitmask
#[
#{ 'name': 'eth-phy', 'value': True },
{ 'name': 'eth-mac', 'value': True },
#{ 'name': 'eth-ctrl', 'value': True },
#{ 'name': 'rmon', 'value': True },
#],
},
},
}
rsp = do_get(ynl, args, 'stats-get', req)
pprint.PrettyPrinter().pprint(rsp)
return
if args.show_time_stamping:
req = {
'header': {
'flags': 'stats',
},
}
tsinfo = do_get(ynl, args, 'tsinfo-get', req)
print(f'Time stamping parameters for {args.device}:')
print('Capabilities:')
_ = [print(f'\t{v}') for v in bits_to_dict(tsinfo['timestamping'])]
print(f'PTP Hardware Clock: {tsinfo.get("phc-index", "none")}')
if 'tx-types' in tsinfo:
print('Hardware Transmit Timestamp Modes:')
_ = [print(f'\t{v}') for v in bits_to_dict(tsinfo['tx-types'])]
else:
print('Hardware Transmit Timestamp Modes: none')
if 'rx-filters' in tsinfo:
print('Hardware Receive Filter Modes:')
_ = [print(f'\t{v}') for v in bits_to_dict(tsinfo['rx-filters'])]
else:
print('Hardware Receive Filter Modes: none')
if 'stats' in tsinfo and tsinfo['stats']:
print('Statistics:')
_ = [print(f'\t{k}: {v}') for k, v in tsinfo['stats'].items()]
return
print(f'Settings for {args.device}:')
linkmodes = do_get(ynl, args, 'linkmodes-get')
ours = bits_to_dict(linkmodes['ours'])
supported_ports = ('TP', 'AUI', 'BNC', 'MII', 'FIBRE', 'Backplane')
ports = [ p for p in supported_ports if ours.get(p, False)]
print(f'Supported ports: [ {" ".join(ports)} ]')
print_speed('Supported link modes', ours)
print_field(ours, ('Pause', 'Supported pause frame use', 'yn'))
print_field(ours, ('Autoneg', 'Supports auto-negotiation', 'yn'))
supported_fec = ('None', 'PS', 'BASER', 'LLRS')
fec = [ p for p in supported_fec if ours.get(p, False)]
fec_str = " ".join(fec)
if len(fec) == 0:
fec_str = "Not reported"
print(f'Supported FEC modes: {fec_str}')
speed = 'Unknown!'
if linkmodes['speed'] > 0 and linkmodes['speed'] < 0xffffffff:
speed = f'{linkmodes["speed"]}Mb/s'
print(f'Speed: {speed}')
duplex_modes = {
0: 'Half',
1: 'Full',
}
duplex = duplex_modes.get(linkmodes["duplex"], None)
if not duplex:
duplex = f'Unknown! ({linkmodes["duplex"]})'
print(f'Duplex: {duplex}')
autoneg = "off"
if linkmodes.get("autoneg", 0) != 0:
autoneg = "on"
print(f'Auto-negotiation: {autoneg}')
ports = {
0: 'Twisted Pair',
1: 'AUI',
2: 'MII',
3: 'FIBRE',
4: 'BNC',
5: 'Directly Attached Copper',
0xef: 'None',
}
linkinfo = do_get(ynl, args, 'linkinfo-get')
print(f'Port: {ports.get(linkinfo["port"], "Other")}')
print_field(linkinfo, ('phyaddr', 'PHYAD'))
transceiver = {
0: 'Internal',
1: 'External',
}
print(f'Transceiver: {transceiver.get(linkinfo["transceiver"], "Unknown")}')
mdix_ctrl = {
1: 'off',
2: 'on',
}
mdix = mdix_ctrl.get(linkinfo['tp-mdix-ctrl'], None)
if mdix:
mdix = mdix + ' (forced)'
else:
mdix = mdix_ctrl.get(linkinfo['tp-mdix'], 'Unknown (auto)')
print(f'MDI-X: {mdix}')
debug = do_get(ynl, args, 'debug-get')
msgmask = bits_to_dict(debug.get("msgmask", [])).keys()
print(f'Current message level: {" ".join(msgmask)}')
linkstate = do_get(ynl, args, 'linkstate-get')
detected_states = {
0: 'no',
1: 'yes',
}
# TODO: wol-get
detected = detected_states.get(linkstate['link'], 'unknown')
print(f'Link detected: {detected}')
if __name__ == '__main__':
main()
+5
View File
@@ -0,0 +1,5 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
source "$(dirname "$(realpath "$0")")/ynl_nsim_lib.sh"
nsim_setup
"$(dirname "$(realpath "$0")")/ethtool"
+231
View File
@@ -0,0 +1,231 @@
// SPDX-License-Identifier: GPL-2.0
#include <stdio.h>
#include <string.h>
#include <ynl.h>
#include <net/if.h>
#include <kselftest_harness.h>
#include "netdev-user.h"
#include "rt-link-user.h"
static void netdev_print_device(struct __test_metadata *_metadata,
struct netdev_dev_get_rsp *d, unsigned int op)
{
char ifname[IF_NAMESIZE];
const char *name;
EXPECT_TRUE((bool)d->_present.ifindex);
if (!d->_present.ifindex)
return;
name = if_indextoname(d->ifindex, ifname);
EXPECT_TRUE((bool)name);
if (name)
ksft_print_msg("%8s[%d]\t", name, d->ifindex);
else
ksft_print_msg("[%d]\t", d->ifindex);
EXPECT_TRUE((bool)d->_present.xdp_features);
if (!d->_present.xdp_features)
return;
printf("xdp-features (%llx):", d->xdp_features);
for (int i = 0; d->xdp_features >= 1U << i; i++) {
if (d->xdp_features & (1U << i))
printf(" %s", netdev_xdp_act_str(1 << i));
}
printf(" xdp-rx-metadata-features (%llx):",
d->xdp_rx_metadata_features);
for (int i = 0; d->xdp_rx_metadata_features >= 1U << i; i++) {
if (d->xdp_rx_metadata_features & (1U << i))
printf(" %s",
netdev_xdp_rx_metadata_str(1 << i));
}
printf(" xsk-features (%llx):", d->xsk_features);
for (int i = 0; d->xsk_features >= 1U << i; i++) {
if (d->xsk_features & (1U << i))
printf(" %s", netdev_xsk_flags_str(1 << i));
}
printf(" xdp-zc-max-segs=%u", d->xdp_zc_max_segs);
name = netdev_op_str(op);
if (name)
printf(" (ntf: %s)", name);
printf("\n");
}
static int veth_create(struct ynl_sock *ys_link)
{
struct rt_link_getlink_ntf *ntf_gl;
struct rt_link_newlink_req *req;
struct ynl_ntf_base_type *ntf;
int ret;
req = rt_link_newlink_req_alloc();
if (!req)
return -1;
rt_link_newlink_req_set_nlflags(req, NLM_F_CREATE | NLM_F_ECHO);
rt_link_newlink_req_set_linkinfo_kind(req, "veth");
ret = rt_link_newlink(ys_link, req);
rt_link_newlink_req_free(req);
if (ret)
return -1;
if (!ynl_has_ntf(ys_link))
return 0;
ntf = ynl_ntf_dequeue(ys_link);
if (!ntf || ntf->cmd != RTM_NEWLINK) {
ynl_ntf_free(ntf);
return 0;
}
ntf_gl = (void *)ntf;
ret = ntf_gl->obj._hdr.ifi_index;
ynl_ntf_free(ntf);
return ret;
}
static void veth_delete(struct __test_metadata *_metadata,
struct ynl_sock *ys_link, int ifindex)
{
struct rt_link_dellink_req *req;
req = rt_link_dellink_req_alloc();
ASSERT_NE(NULL, req);
req->_hdr.ifi_index = ifindex;
EXPECT_EQ(0, rt_link_dellink(ys_link, req));
rt_link_dellink_req_free(req);
}
FIXTURE(netdev)
{
struct ynl_sock *ys;
struct ynl_sock *ys_link;
};
FIXTURE_SETUP(netdev)
{
struct ynl_error yerr;
self->ys = ynl_sock_create(&ynl_netdev_family, &yerr);
ASSERT_NE(NULL, self->ys) {
TH_LOG("Failed to create YNL netdev socket: %s", yerr.msg);
}
}
FIXTURE_TEARDOWN(netdev)
{
if (self->ys_link)
ynl_sock_destroy(self->ys_link);
ynl_sock_destroy(self->ys);
}
TEST_F(netdev, dump)
{
struct netdev_dev_get_list *devs;
devs = netdev_dev_get_dump(self->ys);
ASSERT_NE(NULL, devs) {
TH_LOG("dump failed: %s", self->ys->err.msg);
}
if (ynl_dump_empty(devs)) {
netdev_dev_get_list_free(devs);
SKIP(return, "no entries in dump");
}
ynl_dump_foreach(devs, d)
netdev_print_device(_metadata, d, 0);
netdev_dev_get_list_free(devs);
}
TEST_F(netdev, get)
{
struct netdev_dev_get_list *devs;
struct netdev_dev_get_req *req;
struct netdev_dev_get_rsp *dev;
int ifindex = 0;
devs = netdev_dev_get_dump(self->ys);
ASSERT_NE(NULL, devs) {
TH_LOG("dump failed: %s", self->ys->err.msg);
}
ynl_dump_foreach(devs, d) {
if (d->_present.ifindex) {
ifindex = d->ifindex;
break;
}
}
netdev_dev_get_list_free(devs);
if (!ifindex)
SKIP(return, "no device to query");
req = netdev_dev_get_req_alloc();
ASSERT_NE(NULL, req);
netdev_dev_get_req_set_ifindex(req, ifindex);
dev = netdev_dev_get(self->ys, req);
netdev_dev_get_req_free(req);
ASSERT_NE(NULL, dev) {
TH_LOG("dev_get failed: %s", self->ys->err.msg);
}
netdev_print_device(_metadata, dev, 0);
netdev_dev_get_rsp_free(dev);
}
TEST_F(netdev, ntf_check)
{
struct ynl_ntf_base_type *ntf;
int veth_ifindex;
bool received;
int ret;
ret = ynl_subscribe(self->ys, "mgmt");
ASSERT_EQ(0, ret) {
TH_LOG("subscribe failed: %s", self->ys->err.msg);
}
self->ys_link = ynl_sock_create(&ynl_rt_link_family, NULL);
ASSERT_NE(NULL, self->ys_link)
TH_LOG("failed to create rt-link socket");
veth_ifindex = veth_create(self->ys_link);
ASSERT_GT(veth_ifindex, 0)
TH_LOG("failed to create veth");
ynl_ntf_check(self->ys);
ntf = ynl_ntf_dequeue(self->ys);
received = ntf;
if (ntf) {
netdev_print_device(_metadata,
(struct netdev_dev_get_rsp *)&ntf->data,
ntf->cmd);
ynl_ntf_free(ntf);
}
/* Drain any remaining notifications */
while ((ntf = ynl_ntf_dequeue(self->ys)))
ynl_ntf_free(ntf);
veth_delete(_metadata, self->ys_link, veth_ifindex);
ASSERT_TRUE(received)
TH_LOG("no notification received");
}
TEST_HARNESS_MAIN
+108
View File
@@ -0,0 +1,108 @@
// SPDX-License-Identifier: GPL-2.0
#include <stdio.h>
#include <string.h>
#include <ynl.h>
#include <kselftest_harness.h>
#include "ovs_datapath-user.h"
static void ovs_print_datapath(struct __test_metadata *_metadata,
struct ovs_datapath_get_rsp *dp)
{
EXPECT_TRUE((bool)dp->_len.name);
if (!dp->_len.name)
return;
EXPECT_TRUE((bool)dp->_hdr.dp_ifindex);
ksft_print_msg("%s(%d): pid:%u cache:%u\n",
dp->name, dp->_hdr.dp_ifindex,
dp->upcall_pid, dp->masks_cache_size);
}
FIXTURE(ovs)
{
struct ynl_sock *ys;
char *dp_name;
};
FIXTURE_SETUP(ovs)
{
self->ys = ynl_sock_create(&ynl_ovs_datapath_family, NULL);
ASSERT_NE(NULL, self->ys)
TH_LOG("failed to create OVS datapath socket");
}
FIXTURE_TEARDOWN(ovs)
{
if (self->dp_name) {
struct ovs_datapath_del_req *req;
req = ovs_datapath_del_req_alloc();
if (req) {
ovs_datapath_del_req_set_name(req, self->dp_name);
ovs_datapath_del(self->ys, req);
ovs_datapath_del_req_free(req);
}
}
ynl_sock_destroy(self->ys);
}
TEST_F(ovs, crud)
{
struct ovs_datapath_get_req_dump *dreq;
struct ovs_datapath_new_req *new_req;
struct ovs_datapath_get_list *dps;
struct ovs_datapath_get_rsp *dp;
struct ovs_datapath_get_req *req;
bool found = false;
int err;
new_req = ovs_datapath_new_req_alloc();
ASSERT_NE(NULL, new_req);
ovs_datapath_new_req_set_upcall_pid(new_req, 1);
ovs_datapath_new_req_set_name(new_req, "ynl-test");
err = ovs_datapath_new(self->ys, new_req);
ovs_datapath_new_req_free(new_req);
ASSERT_EQ(0, err) {
TH_LOG("new failed: %s", self->ys->err.msg);
}
self->dp_name = "ynl-test";
ksft_print_msg("get:\n");
req = ovs_datapath_get_req_alloc();
ASSERT_NE(NULL, req);
ovs_datapath_get_req_set_name(req, "ynl-test");
dp = ovs_datapath_get(self->ys, req);
ovs_datapath_get_req_free(req);
ASSERT_NE(NULL, dp) {
TH_LOG("get failed: %s", self->ys->err.msg);
}
ovs_print_datapath(_metadata, dp);
EXPECT_STREQ("ynl-test", dp->name);
ovs_datapath_get_rsp_free(dp);
ksft_print_msg("dump:\n");
dreq = ovs_datapath_get_req_dump_alloc();
ASSERT_NE(NULL, dreq);
dps = ovs_datapath_get_dump(self->ys, dreq);
ovs_datapath_get_req_dump_free(dreq);
ASSERT_NE(NULL, dps) {
TH_LOG("dump failed: %s", self->ys->err.msg);
}
ynl_dump_foreach(dps, d) {
ovs_print_datapath(_metadata, d);
if (d->name && !strcmp(d->name, "ynl-test"))
found = true;
}
ovs_datapath_get_list_free(dps);
EXPECT_TRUE(found);
}
TEST_HARNESS_MAIN
+111
View File
@@ -0,0 +1,111 @@
// SPDX-License-Identifier: GPL-2.0
#include <stdio.h>
#include <string.h>
#include <ynl.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <kselftest_harness.h>
#include "rt-addr-user.h"
static void rt_addr_print(struct __test_metadata *_metadata,
struct rt_addr_getaddr_rsp *a)
{
char ifname[IF_NAMESIZE];
char addr_str[64];
const char *addr;
const char *name;
name = if_indextoname(a->_hdr.ifa_index, ifname);
EXPECT_NE(NULL, name);
if (name)
ksft_print_msg("%16s: ", name);
EXPECT_TRUE(a->_len.address == 4 || a->_len.address == 16);
switch (a->_len.address) {
case 4:
addr = inet_ntop(AF_INET, a->address,
addr_str, sizeof(addr_str));
break;
case 16:
addr = inet_ntop(AF_INET6, a->address,
addr_str, sizeof(addr_str));
break;
default:
addr = NULL;
break;
}
if (addr)
printf("%s", addr);
else
printf("[%d]", a->_len.address);
printf("\n");
}
FIXTURE(rt_addr)
{
struct ynl_sock *ys;
};
FIXTURE_SETUP(rt_addr)
{
struct ynl_error yerr;
self->ys = ynl_sock_create(&ynl_rt_addr_family, &yerr);
ASSERT_NE(NULL, self->ys)
TH_LOG("failed to create rt-addr socket: %s", yerr.msg);
}
FIXTURE_TEARDOWN(rt_addr)
{
ynl_sock_destroy(self->ys);
}
TEST_F(rt_addr, dump)
{
struct rt_addr_getaddr_list *rsp;
struct rt_addr_getaddr_req *req;
struct in6_addr v6_expected;
struct in_addr v4_expected;
bool found_v4 = false;
bool found_v6 = false;
/* The bash wrapper for this test adds these addresses on nsim0,
* make sure we can find them in the dump.
*/
inet_pton(AF_INET, "192.168.1.1", &v4_expected);
inet_pton(AF_INET6, "2001:db8::1", &v6_expected);
req = rt_addr_getaddr_req_alloc();
ASSERT_NE(NULL, req);
rsp = rt_addr_getaddr_dump(self->ys, req);
rt_addr_getaddr_req_free(req);
ASSERT_NE(NULL, rsp) {
TH_LOG("dump failed: %s", self->ys->err.msg);
}
ASSERT_FALSE(ynl_dump_empty(rsp)) {
rt_addr_getaddr_list_free(rsp);
TH_LOG("no addresses reported");
}
ynl_dump_foreach(rsp, addr) {
rt_addr_print(_metadata, addr);
found_v4 |= addr->_len.address == 4 &&
!memcmp(addr->address, &v4_expected, 4);
found_v6 |= addr->_len.address == 16 &&
!memcmp(addr->address, &v6_expected, 16);
}
rt_addr_getaddr_list_free(rsp);
EXPECT_TRUE(found_v4);
EXPECT_TRUE(found_v6);
}
TEST_HARNESS_MAIN
+5
View File
@@ -0,0 +1,5 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
source "$(dirname "$(realpath "$0")")/ynl_nsim_lib.sh"
nsim_setup
"$(dirname "$(realpath "$0")")/rt-addr"
+206
View File
@@ -0,0 +1,206 @@
// SPDX-License-Identifier: GPL-2.0
#include <stdio.h>
#include <string.h>
#include <ynl.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <kselftest_harness.h>
#include "rt-link-user.h"
static void rt_link_print(struct __test_metadata *_metadata,
struct rt_link_getlink_rsp *r)
{
unsigned int i;
EXPECT_TRUE((bool)r->_hdr.ifi_index);
ksft_print_msg("%3d: ", r->_hdr.ifi_index);
EXPECT_TRUE((bool)r->_len.ifname);
if (r->_len.ifname)
printf("%6s: ", r->ifname);
if (r->_present.mtu)
printf("mtu %5d ", r->mtu);
if (r->linkinfo._len.kind)
printf("kind %-8s ", r->linkinfo.kind);
else
printf(" %8s ", "");
if (r->prop_list._count.alt_ifname) {
printf("altname ");
for (i = 0; i < r->prop_list._count.alt_ifname; i++)
printf("%s ", r->prop_list.alt_ifname[i]->str);
printf(" ");
}
if (r->linkinfo._present.data && r->linkinfo.data._present.netkit) {
struct rt_link_linkinfo_netkit_attrs *netkit;
const char *name;
netkit = &r->linkinfo.data.netkit;
printf("primary %d ", netkit->primary);
name = NULL;
if (netkit->_present.policy)
name = rt_link_netkit_policy_str(netkit->policy);
if (name)
printf("policy %s ", name);
}
printf("\n");
}
static int netkit_create(struct ynl_sock *ys)
{
struct rt_link_getlink_ntf *ntf_gl;
struct rt_link_newlink_req *req;
struct ynl_ntf_base_type *ntf;
int ret;
req = rt_link_newlink_req_alloc();
if (!req)
return -1;
rt_link_newlink_req_set_nlflags(req, NLM_F_CREATE | NLM_F_ECHO);
rt_link_newlink_req_set_linkinfo_kind(req, "netkit");
rt_link_newlink_req_set_linkinfo_data_netkit_policy(req, NETKIT_DROP);
ret = rt_link_newlink(ys, req);
rt_link_newlink_req_free(req);
if (ret)
return -1;
if (!ynl_has_ntf(ys))
return 0;
ntf = ynl_ntf_dequeue(ys);
if (!ntf || ntf->cmd != RTM_NEWLINK) {
ynl_ntf_free(ntf);
return 0;
}
ntf_gl = (void *)ntf;
ret = ntf_gl->obj._hdr.ifi_index;
ynl_ntf_free(ntf);
return ret;
}
static void netkit_delete(struct __test_metadata *_metadata,
struct ynl_sock *ys, int ifindex)
{
struct rt_link_dellink_req *req;
req = rt_link_dellink_req_alloc();
ASSERT_NE(NULL, req);
req->_hdr.ifi_index = ifindex;
EXPECT_EQ(0, rt_link_dellink(ys, req));
rt_link_dellink_req_free(req);
}
FIXTURE(rt_link)
{
struct ynl_sock *ys;
};
FIXTURE_SETUP(rt_link)
{
struct ynl_error yerr;
self->ys = ynl_sock_create(&ynl_rt_link_family, &yerr);
ASSERT_NE(NULL, self->ys) {
TH_LOG("failed to create rt-link socket: %s", yerr.msg);
}
}
FIXTURE_TEARDOWN(rt_link)
{
ynl_sock_destroy(self->ys);
}
TEST_F(rt_link, dump)
{
struct rt_link_getlink_req_dump *req;
struct rt_link_getlink_list *rsp;
req = rt_link_getlink_req_dump_alloc();
ASSERT_NE(NULL, req);
rsp = rt_link_getlink_dump(self->ys, req);
rt_link_getlink_req_dump_free(req);
ASSERT_NE(NULL, rsp) {
TH_LOG("dump failed: %s", self->ys->err.msg);
}
ASSERT_FALSE(ynl_dump_empty(rsp));
ynl_dump_foreach(rsp, link)
rt_link_print(_metadata, link);
rt_link_getlink_list_free(rsp);
}
TEST_F(rt_link, netkit)
{
struct rt_link_getlink_req_dump *dreq;
struct rt_link_getlink_list *rsp;
bool found = false;
int netkit_ifindex;
/* Create netkit with valid policy */
netkit_ifindex = netkit_create(self->ys);
ASSERT_GT(netkit_ifindex, 0)
TH_LOG("failed to create netkit: %s", self->ys->err.msg);
/* Verify it appears in a dump */
dreq = rt_link_getlink_req_dump_alloc();
ASSERT_NE(NULL, dreq);
rsp = rt_link_getlink_dump(self->ys, dreq);
rt_link_getlink_req_dump_free(dreq);
ASSERT_NE(NULL, rsp) {
TH_LOG("dump failed: %s", self->ys->err.msg);
}
ynl_dump_foreach(rsp, link) {
if (link->_hdr.ifi_index == netkit_ifindex) {
rt_link_print(_metadata, link);
found = true;
}
}
rt_link_getlink_list_free(rsp);
EXPECT_TRUE(found);
netkit_delete(_metadata, self->ys, netkit_ifindex);
}
TEST_F(rt_link, netkit_err_msg)
{
struct rt_link_newlink_req *req;
int ret;
/* Test creating netkit with bad policy - should fail */
req = rt_link_newlink_req_alloc();
ASSERT_NE(NULL, req);
rt_link_newlink_req_set_nlflags(req, NLM_F_CREATE);
rt_link_newlink_req_set_linkinfo_kind(req, "netkit");
rt_link_newlink_req_set_linkinfo_data_netkit_policy(req, 10);
ret = rt_link_newlink(self->ys, req);
rt_link_newlink_req_free(req);
EXPECT_NE(0, ret) {
TH_LOG("creating netkit with bad policy should fail");
}
/* Expect:
* Kernel error: 'Provided default xmit policy not supported' (bad attribute: .linkinfo.data(netkit).policy)
*/
EXPECT_NE(NULL, strstr(self->ys->err.msg, "bad attribute: .linkinfo.data(netkit).policy")) {
TH_LOG("expected extack msg not found: %s",
self->ys->err.msg);
}
}
TEST_HARNESS_MAIN
+113
View File
@@ -0,0 +1,113 @@
// SPDX-License-Identifier: GPL-2.0
#include <stdio.h>
#include <string.h>
#include <ynl.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <kselftest_harness.h>
#include "rt-route-user.h"
static void rt_route_print(struct __test_metadata *_metadata,
struct rt_route_getroute_rsp *r)
{
char ifname[IF_NAMESIZE];
char route_str[64];
const char *route;
const char *name;
/* Ignore local */
if (r->_hdr.rtm_table == RT_TABLE_LOCAL)
return;
if (r->_present.oif) {
name = if_indextoname(r->oif, ifname);
EXPECT_NE(NULL, name);
if (name)
ksft_print_msg("oif: %-16s ", name);
}
if (r->_len.dst) {
route = inet_ntop(r->_hdr.rtm_family, r->dst,
route_str, sizeof(route_str));
printf("dst: %s/%d", route, r->_hdr.rtm_dst_len);
}
if (r->_len.gateway) {
route = inet_ntop(r->_hdr.rtm_family, r->gateway,
route_str, sizeof(route_str));
printf("gateway: %s ", route);
}
printf("\n");
}
FIXTURE(rt_route)
{
struct ynl_sock *ys;
};
FIXTURE_SETUP(rt_route)
{
struct ynl_error yerr;
self->ys = ynl_sock_create(&ynl_rt_route_family, &yerr);
ASSERT_NE(NULL, self->ys)
TH_LOG("failed to create rt-route socket: %s", yerr.msg);
}
FIXTURE_TEARDOWN(rt_route)
{
ynl_sock_destroy(self->ys);
}
TEST_F(rt_route, dump)
{
struct rt_route_getroute_req_dump *req;
struct rt_route_getroute_list *rsp;
struct in6_addr v6_expected;
struct in_addr v4_expected;
bool found_v4 = false;
bool found_v6 = false;
/* The bash wrapper configures 192.168.1.1/24 and 2001:db8::1/64,
* make sure we can find the connected routes in the dump.
*/
inet_pton(AF_INET, "192.168.1.0", &v4_expected);
inet_pton(AF_INET6, "2001:db8::", &v6_expected);
req = rt_route_getroute_req_dump_alloc();
ASSERT_NE(NULL, req);
rsp = rt_route_getroute_dump(self->ys, req);
rt_route_getroute_req_dump_free(req);
ASSERT_NE(NULL, rsp) {
TH_LOG("dump failed: %s", self->ys->err.msg);
}
ASSERT_FALSE(ynl_dump_empty(rsp)) {
rt_route_getroute_list_free(rsp);
TH_LOG("no routes reported");
}
ynl_dump_foreach(rsp, route) {
rt_route_print(_metadata, route);
if (route->_hdr.rtm_table == RT_TABLE_LOCAL)
continue;
if (route->_len.dst == 4 && route->_hdr.rtm_dst_len == 24)
found_v4 |= !memcmp(route->dst, &v4_expected, 4);
if (route->_len.dst == 16 && route->_hdr.rtm_dst_len == 64)
found_v6 |= !memcmp(route->dst, &v6_expected, 16);
}
rt_route_getroute_list_free(rsp);
EXPECT_TRUE(found_v4);
EXPECT_TRUE(found_v6);
}
TEST_HARNESS_MAIN
+5
View File
@@ -0,0 +1,5 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
source "$(dirname "$(realpath "$0")")/ynl_nsim_lib.sh"
nsim_setup
"$(dirname "$(realpath "$0")")/rt-route"
+409
View File
@@ -0,0 +1,409 @@
// SPDX-License-Identifier: GPL-2.0
#define _GNU_SOURCE
#include <sched.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <linux/pkt_sched.h>
#include <linux/tc_act/tc_vlan.h>
#include <linux/tc_act/tc_gact.h>
#include <linux/if_ether.h>
#include <net/if.h>
#include <ynl.h>
#include <kselftest_harness.h>
#include "tc-user.h"
#define TC_HANDLE (0xFFFF << 16)
static bool tc_qdisc_print(struct __test_metadata *_metadata,
struct tc_getqdisc_rsp *q)
{
bool was_fq_codel = false;
char ifname[IF_NAMESIZE];
const char *name;
name = if_indextoname(q->_hdr.tcm_ifindex, ifname);
EXPECT_TRUE((bool)name);
ksft_print_msg("%16s: ", name ?: "no-name");
if (q->_len.kind) {
printf("%s ", q->kind);
if (q->options._present.fq_codel) {
struct tc_fq_codel_attrs *fq_codel;
struct tc_fq_codel_xstats *stats;
fq_codel = &q->options.fq_codel;
stats = q->stats2.app.fq_codel;
EXPECT_EQ(true,
fq_codel->_present.limit &&
fq_codel->_present.target &&
q->stats2.app._len.fq_codel);
if (fq_codel->_present.limit)
printf("limit: %dp ", fq_codel->limit);
if (fq_codel->_present.target)
printf("target: %dms ",
(fq_codel->target + 500) / 1000);
if (q->stats2.app._len.fq_codel)
printf("new_flow_cnt: %d ",
stats->qdisc_stats.new_flow_count);
was_fq_codel = true;
}
}
printf("\n");
return was_fq_codel;
}
static const char *vlan_act_name(struct tc_vlan *p)
{
switch (p->v_action) {
case TCA_VLAN_ACT_POP:
return "pop";
case TCA_VLAN_ACT_PUSH:
return "push";
case TCA_VLAN_ACT_MODIFY:
return "modify";
default:
break;
}
return "not supported";
}
static const char *gact_act_name(struct tc_gact *p)
{
switch (p->action) {
case TC_ACT_SHOT:
return "drop";
case TC_ACT_OK:
return "ok";
case TC_ACT_PIPE:
return "pipe";
default:
break;
}
return "not supported";
}
static void print_vlan(struct tc_act_vlan_attrs *vlan)
{
printf("%s ", vlan_act_name(vlan->parms));
if (vlan->_present.push_vlan_id)
printf("id %u ", vlan->push_vlan_id);
if (vlan->_present.push_vlan_protocol)
printf("protocol %#x ", ntohs(vlan->push_vlan_protocol));
if (vlan->_present.push_vlan_priority)
printf("priority %u ", vlan->push_vlan_priority);
}
static void print_gact(struct tc_act_gact_attrs *gact)
{
struct tc_gact *p = gact->parms;
printf("%s ", gact_act_name(p));
}
static void flower_print(struct tc_flower_attrs *flower, const char *kind)
{
struct tc_act_attrs *a;
unsigned int i;
ksft_print_msg("%s:\n", kind);
if (flower->_present.key_vlan_id)
ksft_print_msg(" vlan_id: %u\n", flower->key_vlan_id);
if (flower->_present.key_vlan_prio)
ksft_print_msg(" vlan_prio: %u\n", flower->key_vlan_prio);
if (flower->_present.key_num_of_vlans)
ksft_print_msg(" num_of_vlans: %u\n",
flower->key_num_of_vlans);
for (i = 0; i < flower->_count.act; i++) {
a = &flower->act[i];
ksft_print_msg("action order: %i %s ", i + 1, a->kind);
if (a->options._present.vlan)
print_vlan(&a->options.vlan);
else if (a->options._present.gact)
print_gact(&a->options.gact);
printf("\n");
}
}
static void tc_filter_print(struct __test_metadata *_metadata,
struct tc_gettfilter_rsp *f)
{
struct tc_options_msg *opt = &f->options;
if (opt->_present.flower) {
EXPECT_TRUE((bool)f->_len.kind);
flower_print(&opt->flower, f->kind);
} else if (f->_len.kind) {
ksft_print_msg("%s pref %u proto: %#x\n", f->kind,
(f->_hdr.tcm_info >> 16),
ntohs(TC_H_MIN(f->_hdr.tcm_info)));
}
}
static int tc_clsact_add(struct ynl_sock *ys, int ifi)
{
struct tc_newqdisc_req *req;
int ret;
req = tc_newqdisc_req_alloc();
if (!req)
return -1;
memset(req, 0, sizeof(*req));
req->_hdr.tcm_ifindex = ifi;
req->_hdr.tcm_parent = TC_H_CLSACT;
req->_hdr.tcm_handle = TC_HANDLE;
tc_newqdisc_req_set_nlflags(req,
NLM_F_REQUEST | NLM_F_EXCL | NLM_F_CREATE);
tc_newqdisc_req_set_kind(req, "clsact");
ret = tc_newqdisc(ys, req);
tc_newqdisc_req_free(req);
return ret;
}
static int tc_clsact_del(struct ynl_sock *ys, int ifi)
{
struct tc_delqdisc_req *req;
int ret;
req = tc_delqdisc_req_alloc();
if (!req)
return -1;
memset(req, 0, sizeof(*req));
req->_hdr.tcm_ifindex = ifi;
req->_hdr.tcm_parent = TC_H_CLSACT;
req->_hdr.tcm_handle = TC_HANDLE;
tc_delqdisc_req_set_nlflags(req, NLM_F_REQUEST);
ret = tc_delqdisc(ys, req);
tc_delqdisc_req_free(req);
return ret;
}
static int tc_filter_add(struct ynl_sock *ys, int ifi)
{
struct tc_newtfilter_req *req;
struct tc_act_attrs *acts;
struct tc_vlan p = {
.action = TC_ACT_PIPE,
.v_action = TCA_VLAN_ACT_PUSH
};
int ret;
req = tc_newtfilter_req_alloc();
if (!req)
return -1;
memset(req, 0, sizeof(*req));
acts = tc_act_attrs_alloc(3);
if (!acts) {
tc_newtfilter_req_free(req);
return -1;
}
memset(acts, 0, sizeof(*acts) * 3);
req->_hdr.tcm_ifindex = ifi;
req->_hdr.tcm_parent = TC_H_MAKE(TC_H_CLSACT, TC_H_MIN_INGRESS);
req->_hdr.tcm_info = TC_H_MAKE(1 << 16, htons(ETH_P_8021Q));
req->chain = 0;
tc_newtfilter_req_set_nlflags(req, NLM_F_REQUEST | NLM_F_EXCL | NLM_F_CREATE);
tc_newtfilter_req_set_kind(req, "flower");
tc_newtfilter_req_set_options_flower_key_vlan_id(req, 100);
tc_newtfilter_req_set_options_flower_key_vlan_prio(req, 5);
tc_newtfilter_req_set_options_flower_key_num_of_vlans(req, 3);
__tc_newtfilter_req_set_options_flower_act(req, acts, 3);
/* Skip action at index 0 because in TC, the action array
* index starts at 1, with each index defining the action's
* order. In contrast, in YNL indexed arrays start at index 0.
*/
tc_act_attrs_set_kind(&acts[1], "vlan");
tc_act_attrs_set_options_vlan_parms(&acts[1], &p, sizeof(p));
tc_act_attrs_set_options_vlan_push_vlan_id(&acts[1], 200);
tc_act_attrs_set_kind(&acts[2], "vlan");
tc_act_attrs_set_options_vlan_parms(&acts[2], &p, sizeof(p));
tc_act_attrs_set_options_vlan_push_vlan_id(&acts[2], 300);
tc_newtfilter_req_set_options_flower_flags(req, 0);
tc_newtfilter_req_set_options_flower_key_eth_type(req, htons(0x8100));
ret = tc_newtfilter(ys, req);
tc_newtfilter_req_free(req);
return ret;
}
static int tc_filter_del(struct ynl_sock *ys, int ifi)
{
struct tc_deltfilter_req *req;
int ret;
req = tc_deltfilter_req_alloc();
if (!req)
return -1;
memset(req, 0, sizeof(*req));
req->_hdr.tcm_ifindex = ifi;
req->_hdr.tcm_parent = TC_H_MAKE(TC_H_CLSACT, TC_H_MIN_INGRESS);
req->_hdr.tcm_info = TC_H_MAKE(1 << 16, htons(ETH_P_8021Q));
tc_deltfilter_req_set_nlflags(req, NLM_F_REQUEST);
ret = tc_deltfilter(ys, req);
tc_deltfilter_req_free(req);
return ret;
}
FIXTURE(tc)
{
struct ynl_sock *ys;
int ifindex;
};
FIXTURE_SETUP(tc)
{
struct ynl_error yerr;
int ret;
ret = unshare(CLONE_NEWNET);
ASSERT_EQ(0, ret);
self->ifindex = 1; /* loopback */
self->ys = ynl_sock_create(&ynl_tc_family, &yerr);
ASSERT_NE(NULL, self->ys) {
TH_LOG("failed to create tc socket: %s", yerr.msg);
}
}
FIXTURE_TEARDOWN(tc)
{
ynl_sock_destroy(self->ys);
}
TEST_F(tc, qdisc)
{
struct tc_getqdisc_req_dump *dreq;
struct tc_newqdisc_req *add_req;
struct tc_delqdisc_req *del_req;
struct tc_getqdisc_list *rsp;
bool found = false;
int ret;
add_req = tc_newqdisc_req_alloc();
ASSERT_NE(NULL, add_req);
memset(add_req, 0, sizeof(*add_req));
add_req->_hdr.tcm_ifindex = self->ifindex;
add_req->_hdr.tcm_parent = TC_H_ROOT;
tc_newqdisc_req_set_nlflags(add_req,
NLM_F_REQUEST | NLM_F_CREATE);
tc_newqdisc_req_set_kind(add_req, "fq_codel");
ret = tc_newqdisc(self->ys, add_req);
tc_newqdisc_req_free(add_req);
ASSERT_EQ(0, ret) {
TH_LOG("qdisc add failed: %s", self->ys->err.msg);
}
dreq = tc_getqdisc_req_dump_alloc();
ASSERT_NE(NULL, dreq);
rsp = tc_getqdisc_dump(self->ys, dreq);
tc_getqdisc_req_dump_free(dreq);
ASSERT_NE(NULL, rsp) {
TH_LOG("dump failed: %s", self->ys->err.msg);
}
ASSERT_FALSE(ynl_dump_empty(rsp));
ynl_dump_foreach(rsp, qdisc) {
found |= tc_qdisc_print(_metadata, qdisc);
}
tc_getqdisc_list_free(rsp);
EXPECT_TRUE(found);
del_req = tc_delqdisc_req_alloc();
ASSERT_NE(NULL, del_req);
memset(del_req, 0, sizeof(*del_req));
del_req->_hdr.tcm_ifindex = self->ifindex;
del_req->_hdr.tcm_parent = TC_H_ROOT;
tc_delqdisc_req_set_nlflags(del_req, NLM_F_REQUEST);
ret = tc_delqdisc(self->ys, del_req);
tc_delqdisc_req_free(del_req);
EXPECT_EQ(0, ret) {
TH_LOG("qdisc del failed: %s", self->ys->err.msg);
}
}
TEST_F(tc, flower)
{
struct tc_gettfilter_req_dump *dreq;
struct tc_gettfilter_list *rsp;
bool found = false;
int ret;
ret = tc_clsact_add(self->ys, self->ifindex);
if (ret)
SKIP(return, "clsact not supported: %s", self->ys->err.msg);
ret = tc_filter_add(self->ys, self->ifindex);
ASSERT_EQ(0, ret) {
TH_LOG("filter add failed: %s", self->ys->err.msg);
}
dreq = tc_gettfilter_req_dump_alloc();
ASSERT_NE(NULL, dreq);
memset(dreq, 0, sizeof(*dreq));
dreq->_hdr.tcm_ifindex = self->ifindex;
dreq->_hdr.tcm_parent = TC_H_MAKE(TC_H_CLSACT, TC_H_MIN_INGRESS);
dreq->_present.chain = 1;
dreq->chain = 0;
rsp = tc_gettfilter_dump(self->ys, dreq);
tc_gettfilter_req_dump_free(dreq);
ASSERT_NE(NULL, rsp) {
TH_LOG("filter dump failed: %s", self->ys->err.msg);
}
ynl_dump_foreach(rsp, flt) {
tc_filter_print(_metadata, flt);
if (flt->options._present.flower) {
EXPECT_EQ(100, flt->options.flower.key_vlan_id);
EXPECT_EQ(5, flt->options.flower.key_vlan_prio);
found = true;
}
}
tc_gettfilter_list_free(rsp);
EXPECT_TRUE(found);
ret = tc_filter_del(self->ys, self->ifindex);
EXPECT_EQ(0, ret) {
TH_LOG("filter del failed: %s", self->ys->err.msg);
}
ret = tc_clsact_del(self->ys, self->ifindex);
EXPECT_EQ(0, ret) {
TH_LOG("clsact del failed: %s", self->ys->err.msg);
}
}
TEST_HARNESS_MAIN
+327
View File
@@ -0,0 +1,327 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
# Test YNL CLI functionality
# Load KTAP test helpers
KSELFTEST_KTAP_HELPERS="$(dirname "$(realpath "$0")")/../../../testing/selftests/kselftest/ktap_helpers.sh"
# shellcheck source=../../../testing/selftests/kselftest/ktap_helpers.sh
source "$KSELFTEST_KTAP_HELPERS"
# Default ynl path for direct execution, can be overridden by make install
ynl="../pyynl/cli.py"
readonly NSIM_ID="1338"
readonly NSIM_DEV_NAME="nsim${NSIM_ID}"
readonly VETH_A="veth_a"
readonly VETH_B="veth_b"
testns="ynl-$(mktemp -u XXXXXX)"
TESTS_NO=0
# Test listing available families
cli_list_families()
{
if $ynl --list-families &>/dev/null; then
ktap_test_pass "YNL CLI list families"
else
ktap_test_fail "YNL CLI list families"
fi
}
TESTS_NO=$((TESTS_NO + 1))
# Test netdev family operations (dev-get, queue-get)
cli_netdev_ops()
{
local dev_output
local ifindex
ifindex=$(ip netns exec "$testns" cat /sys/class/net/"$NSIM_DEV_NAME"/ifindex 2>/dev/null)
dev_output=$(ip netns exec "$testns" $ynl --family netdev \
--do dev-get --json "{\"ifindex\": $ifindex}" 2>/dev/null)
if ! echo "$dev_output" | grep -q "ifindex"; then
ktap_test_fail "YNL CLI netdev operations (netdev dev-get output missing ifindex)"
return
fi
if ! ip netns exec "$testns" $ynl --family netdev \
--dump queue-get --json "{\"ifindex\": $ifindex}" &>/dev/null; then
ktap_test_fail "YNL CLI netdev operations (failed to get netdev queue info)"
return
fi
ktap_test_pass "YNL CLI netdev operations"
}
TESTS_NO=$((TESTS_NO + 1))
# Test ethtool family operations (rings-get, linkinfo-get)
cli_ethtool_ops()
{
local rings_output
local linkinfo_output
rings_output=$(ip netns exec "$testns" $ynl --family ethtool \
--do rings-get --json "{\"header\": {\"dev-name\": \"$NSIM_DEV_NAME\"}}" 2>/dev/null)
if ! echo "$rings_output" | grep -q "header"; then
ktap_test_fail "YNL CLI ethtool operations (ethtool rings-get output missing header)"
return
fi
linkinfo_output=$(ip netns exec "$testns" $ynl --family ethtool \
--do linkinfo-get --json "{\"header\": {\"dev-name\": \"$VETH_A\"}}" 2>/dev/null)
if ! echo "$linkinfo_output" | grep -q "header"; then
ktap_test_fail "YNL CLI ethtool operations (ethtool linkinfo-get output missing header)"
return
fi
ktap_test_pass "YNL CLI ethtool operations"
}
TESTS_NO=$((TESTS_NO + 1))
# Test rt-route family operations
cli_rt_route_ops()
{
local ifindex
if ! $ynl --list-families 2>/dev/null | grep -q "rt-route"; then
ktap_test_skip "YNL CLI rt-route operations (rt-route family not available)"
return
fi
ifindex=$(ip netns exec "$testns" cat /sys/class/net/"$NSIM_DEV_NAME"/ifindex 2>/dev/null)
# Add route: 192.0.2.0/24 dev $dev scope link
if ! ip netns exec "$testns" $ynl --family rt-route --do newroute --create \
--json "{\"dst\": \"192.0.2.0\", \"oif\": $ifindex, \"rtm-dst-len\": 24, \"rtm-family\": 2, \"rtm-scope\": 253, \"rtm-type\": 1, \"rtm-protocol\": 3, \"rtm-table\": 254}" &>/dev/null; then
ktap_test_fail "YNL CLI rt-route operations (failed to add route)"
return
fi
local route_output
route_output=$(ip netns exec "$testns" $ynl --family rt-route --dump getroute 2>/dev/null)
if echo "$route_output" | grep -q "192.0.2.0"; then
ktap_test_pass "YNL CLI rt-route operations"
else
ktap_test_fail "YNL CLI rt-route operations (failed to verify route)"
fi
ip netns exec "$testns" $ynl --family rt-route --do delroute \
--json "{\"dst\": \"192.0.2.0\", \"oif\": $ifindex, \"rtm-dst-len\": 24, \"rtm-family\": 2, \"rtm-scope\": 253, \"rtm-type\": 1, \"rtm-protocol\": 3, \"rtm-table\": 254}" &>/dev/null
}
TESTS_NO=$((TESTS_NO + 1))
# Test rt-addr family operations
cli_rt_addr_ops()
{
local ifindex
if ! $ynl --list-families 2>/dev/null | grep -q "rt-addr"; then
ktap_test_skip "YNL CLI rt-addr operations (rt-addr family not available)"
return
fi
ifindex=$(ip netns exec "$testns" cat /sys/class/net/"$NSIM_DEV_NAME"/ifindex 2>/dev/null)
if ! ip netns exec "$testns" $ynl --family rt-addr --do newaddr \
--json "{\"ifa-index\": $ifindex, \"local\": \"192.0.2.100\", \"ifa-prefixlen\": 24, \"ifa-family\": 2}" &>/dev/null; then
ktap_test_fail "YNL CLI rt-addr operations (failed to add address)"
return
fi
local addr_output
addr_output=$(ip netns exec "$testns" $ynl --family rt-addr --dump getaddr 2>/dev/null)
if echo "$addr_output" | grep -q "192.0.2.100"; then
ktap_test_pass "YNL CLI rt-addr operations"
else
ktap_test_fail "YNL CLI rt-addr operations (failed to verify address)"
fi
ip netns exec "$testns" $ynl --family rt-addr --do deladdr \
--json "{\"ifa-index\": $ifindex, \"local\": \"192.0.2.100\", \"ifa-prefixlen\": 24, \"ifa-family\": 2}" &>/dev/null
}
TESTS_NO=$((TESTS_NO + 1))
# Test rt-link family operations
cli_rt_link_ops()
{
if ! $ynl --list-families 2>/dev/null | grep -q "rt-link"; then
ktap_test_skip "YNL CLI rt-link operations (rt-link family not available)"
return
fi
if ! ip netns exec "$testns" $ynl --family rt-link --do newlink --create \
--json "{\"ifname\": \"dummy0\", \"linkinfo\": {\"kind\": \"dummy\"}}" &>/dev/null; then
ktap_test_fail "YNL CLI rt-link operations (failed to add link)"
return
fi
local link_output
link_output=$(ip netns exec "$testns" $ynl --family rt-link --dump getlink 2>/dev/null)
if echo "$link_output" | grep -q "$NSIM_DEV_NAME" && echo "$link_output" | grep -q "dummy0"; then
ktap_test_pass "YNL CLI rt-link operations"
else
ktap_test_fail "YNL CLI rt-link operations (failed to verify link)"
fi
ip netns exec "$testns" $ynl --family rt-link --do dellink \
--json "{\"ifname\": \"dummy0\"}" &>/dev/null
}
TESTS_NO=$((TESTS_NO + 1))
# Test rt-neigh family operations
cli_rt_neigh_ops()
{
local ifindex
if ! $ynl --list-families 2>/dev/null | grep -q "rt-neigh"; then
ktap_test_skip "YNL CLI rt-neigh operations (rt-neigh family not available)"
return
fi
ifindex=$(ip netns exec "$testns" cat /sys/class/net/"$NSIM_DEV_NAME"/ifindex 2>/dev/null)
# Add neighbor: 192.0.2.1 dev nsim1338 lladdr 11:22:33:44:55:66 PERMANENT
if ! ip netns exec "$testns" $ynl --family rt-neigh --do newneigh --create \
--json "{\"ndm-ifindex\": $ifindex, \"dst\": \"192.0.2.1\", \"lladdr\": \"11:22:33:44:55:66\", \"ndm-family\": 2, \"ndm-state\": 128}" &>/dev/null; then
ktap_test_fail "YNL CLI rt-neigh operations (failed to add neighbor)"
fi
local neigh_output
neigh_output=$(ip netns exec "$testns" $ynl --family rt-neigh --dump getneigh 2>/dev/null)
if echo "$neigh_output" | grep -q "192.0.2.1"; then
ktap_test_pass "YNL CLI rt-neigh operations"
else
ktap_test_fail "YNL CLI rt-neigh operations (failed to verify neighbor)"
fi
ip netns exec "$testns" $ynl --family rt-neigh --do delneigh \
--json "{\"ndm-ifindex\": $ifindex, \"dst\": \"192.0.2.1\", \"lladdr\": \"11:22:33:44:55:66\", \"ndm-family\": 2}" &>/dev/null
}
TESTS_NO=$((TESTS_NO + 1))
# Test rt-rule family operations
cli_rt_rule_ops()
{
if ! $ynl --list-families 2>/dev/null | grep -q "rt-rule"; then
ktap_test_skip "YNL CLI rt-rule operations (rt-rule family not available)"
return
fi
# Add rule: from 192.0.2.0/24 lookup 100 none
if ! ip netns exec "$testns" $ynl --family rt-rule --do newrule \
--json "{\"family\": 2, \"src-len\": 24, \"src\": \"192.0.2.0\", \"table\": 100}" &>/dev/null; then
ktap_test_fail "YNL CLI rt-rule operations (failed to add rule)"
return
fi
local rule_output
rule_output=$(ip netns exec "$testns" $ynl --family rt-rule --dump getrule 2>/dev/null)
if echo "$rule_output" | grep -q "192.0.2.0"; then
ktap_test_pass "YNL CLI rt-rule operations"
else
ktap_test_fail "YNL CLI rt-rule operations (failed to verify rule)"
fi
ip netns exec "$testns" $ynl --family rt-rule --do delrule \
--json "{\"family\": 2, \"src-len\": 24, \"src\": \"192.0.2.0\", \"table\": 100}" &>/dev/null
}
TESTS_NO=$((TESTS_NO + 1))
# Test nlctrl family operations
cli_nlctrl_ops()
{
local family_output
if ! family_output=$($ynl --family nlctrl \
--do getfamily --json "{\"family-name\": \"netdev\"}" 2>/dev/null); then
ktap_test_fail "YNL CLI nlctrl getfamily (failed to get nlctrl family info)"
return
fi
if ! echo "$family_output" | grep -q "family-name"; then
ktap_test_fail "YNL CLI nlctrl getfamily (nlctrl getfamily output missing family-name)"
return
fi
if ! echo "$family_output" | grep -q "family-id"; then
ktap_test_fail "YNL CLI nlctrl getfamily (nlctrl getfamily output missing family-id)"
return
fi
ktap_test_pass "YNL CLI nlctrl getfamily"
}
TESTS_NO=$((TESTS_NO + 1))
setup()
{
modprobe netdevsim &> /dev/null
if ! [ -f /sys/bus/netdevsim/new_device ]; then
ktap_skip_all "netdevsim module not available"
exit "$KSFT_SKIP"
fi
if ! ip netns add "$testns" 2>/dev/null; then
ktap_skip_all "failed to create test namespace"
exit "$KSFT_SKIP"
fi
echo "$NSIM_ID 1" | ip netns exec "$testns" tee /sys/bus/netdevsim/new_device >/dev/null 2>&1 || {
ktap_skip_all "failed to create netdevsim device"
exit "$KSFT_SKIP"
}
local dev
dev=$(ip netns exec "$testns" ls /sys/bus/netdevsim/devices/netdevsim$NSIM_ID/net 2>/dev/null | head -1)
if [[ -z "$dev" ]]; then
ktap_skip_all "failed to find netdevsim device"
exit "$KSFT_SKIP"
fi
ip -netns "$testns" link set dev "$dev" name "$NSIM_DEV_NAME" 2>/dev/null || {
ktap_skip_all "failed to rename netdevsim device"
exit "$KSFT_SKIP"
}
ip -netns "$testns" link set dev "$NSIM_DEV_NAME" up 2>/dev/null
if ! ip -n "$testns" link add "$VETH_A" type veth peer name "$VETH_B" 2>/dev/null; then
ktap_skip_all "failed to create veth pair"
exit "$KSFT_SKIP"
fi
ip -n "$testns" link set "$VETH_A" up 2>/dev/null
ip -n "$testns" link set "$VETH_B" up 2>/dev/null
}
cleanup()
{
ip netns exec "$testns" bash -c "echo $NSIM_ID > /sys/bus/netdevsim/del_device" 2>/dev/null || true
ip netns del "$testns" 2>/dev/null || true
}
# Check if ynl command is available
if ! command -v $ynl &>/dev/null && [[ ! -x $ynl ]]; then
ktap_skip_all "ynl command not found: $ynl"
exit "$KSFT_SKIP"
fi
trap cleanup EXIT
ktap_print_header
setup
ktap_set_plan "${TESTS_NO}"
cli_list_families
cli_netdev_ops
cli_ethtool_ops
cli_rt_route_ops
cli_rt_addr_ops
cli_rt_link_ops
cli_rt_neigh_ops
cli_rt_rule_ops
cli_nlctrl_ops
ktap_finished
+222
View File
@@ -0,0 +1,222 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
# Test YNL ethtool functionality
# Load KTAP test helpers
KSELFTEST_KTAP_HELPERS="$(dirname "$(realpath "$0")")/../../../testing/selftests/kselftest/ktap_helpers.sh"
# shellcheck source=../../../testing/selftests/kselftest/ktap_helpers.sh
source "$KSELFTEST_KTAP_HELPERS"
# Default ynl-ethtool path for direct execution, can be overridden by make install
ynl_ethtool="./ethtool.py"
readonly NSIM_ID="1337"
readonly NSIM_DEV_NAME="nsim${NSIM_ID}"
readonly VETH_A="veth_a"
readonly VETH_B="veth_b"
testns="ynl-ethtool-$(mktemp -u XXXXXX)"
TESTS_NO=0
# Uses veth device as netdevsim doesn't support basic ethtool device info
ethtool_device_info()
{
local info_output
info_output=$(ip netns exec "$testns" $ynl_ethtool "$VETH_A" 2>/dev/null)
if ! echo "$info_output" | grep -q "Settings for"; then
ktap_test_fail "YNL ethtool device info (device info output missing expected content)"
return
fi
ktap_test_pass "YNL ethtool device info"
}
TESTS_NO=$((TESTS_NO + 1))
ethtool_statistics()
{
local stats_output
stats_output=$(ip netns exec "$testns" $ynl_ethtool --statistics "$NSIM_DEV_NAME" 2>/dev/null)
if ! echo "$stats_output" | grep -q -E "(NIC statistics|packets|bytes)"; then
ktap_test_fail "YNL ethtool statistics (statistics output missing expected content)"
return
fi
ktap_test_pass "YNL ethtool statistics"
}
TESTS_NO=$((TESTS_NO + 1))
ethtool_ring_params()
{
local ring_output
ring_output=$(ip netns exec "$testns" $ynl_ethtool --show-ring "$NSIM_DEV_NAME" 2>/dev/null)
if ! echo "$ring_output" | grep -q -E "(Ring parameters|RX|TX)"; then
ktap_test_fail "YNL ethtool ring parameters (ring parameters output missing expected content)"
return
fi
if ! ip netns exec "$testns" $ynl_ethtool --set-ring "$NSIM_DEV_NAME" rx 64 2>/dev/null; then
ktap_test_fail "YNL ethtool ring parameters (set-ring command failed unexpectedly)"
return
fi
ktap_test_pass "YNL ethtool ring parameters (show/set)"
}
TESTS_NO=$((TESTS_NO + 1))
ethtool_coalesce_params()
{
if ! ip netns exec "$testns" $ynl_ethtool --show-coalesce "$NSIM_DEV_NAME" &>/dev/null; then
ktap_test_fail "YNL ethtool coalesce parameters (failed to get coalesce parameters)"
return
fi
if ! ip netns exec "$testns" $ynl_ethtool --set-coalesce "$NSIM_DEV_NAME" rx-usecs 50 2>/dev/null; then
ktap_test_fail "YNL ethtool coalesce parameters (set-coalesce command failed unexpectedly)"
return
fi
ktap_test_pass "YNL ethtool coalesce parameters (show/set)"
}
TESTS_NO=$((TESTS_NO + 1))
ethtool_pause_params()
{
if ! ip netns exec "$testns" $ynl_ethtool --show-pause "$NSIM_DEV_NAME" &>/dev/null; then
ktap_test_fail "YNL ethtool pause parameters (failed to get pause parameters)"
return
fi
if ! ip netns exec "$testns" $ynl_ethtool --set-pause "$NSIM_DEV_NAME" tx 1 rx 1 2>/dev/null; then
ktap_test_fail "YNL ethtool pause parameters (set-pause command failed unexpectedly)"
return
fi
ktap_test_pass "YNL ethtool pause parameters (show/set)"
}
TESTS_NO=$((TESTS_NO + 1))
ethtool_features_info()
{
local features_output
features_output=$(ip netns exec "$testns" $ynl_ethtool --show-features "$NSIM_DEV_NAME" 2>/dev/null)
if ! echo "$features_output" | grep -q -E "(Features|offload)"; then
ktap_test_fail "YNL ethtool features info (features output missing expected content)"
return
fi
ktap_test_pass "YNL ethtool features info (show/set)"
}
TESTS_NO=$((TESTS_NO + 1))
ethtool_channels_info()
{
local channels_output
channels_output=$(ip netns exec "$testns" $ynl_ethtool --show-channels "$NSIM_DEV_NAME" 2>/dev/null)
if ! echo "$channels_output" | grep -q -E "(Channel|Combined|RX|TX)"; then
ktap_test_fail "YNL ethtool channels info (channels output missing expected content)"
return
fi
if ! ip netns exec "$testns" $ynl_ethtool --set-channels "$NSIM_DEV_NAME" combined-count 1 2>/dev/null; then
ktap_test_fail "YNL ethtool channels info (set-channels command failed unexpectedly)"
return
fi
ktap_test_pass "YNL ethtool channels info (show/set)"
}
TESTS_NO=$((TESTS_NO + 1))
ethtool_time_stamping()
{
local ts_output
ts_output=$(ip netns exec "$testns" $ynl_ethtool --show-time-stamping "$NSIM_DEV_NAME" 2>/dev/null)
if ! echo "$ts_output" | grep -q -E "(Time stamping|timestamping|SOF_TIMESTAMPING)"; then
ktap_test_fail "YNL ethtool time stamping (time stamping output missing expected content)"
return
fi
ktap_test_pass "YNL ethtool time stamping"
}
TESTS_NO=$((TESTS_NO + 1))
setup()
{
modprobe netdevsim &> /dev/null
if ! [ -f /sys/bus/netdevsim/new_device ]; then
ktap_skip_all "netdevsim module not available"
exit "$KSFT_SKIP"
fi
if ! ip netns add "$testns" 2>/dev/null; then
ktap_skip_all "failed to create test namespace"
exit "$KSFT_SKIP"
fi
echo "$NSIM_ID 1" | ip netns exec "$testns" tee /sys/bus/netdevsim/new_device >/dev/null 2>&1 || {
ktap_skip_all "failed to create netdevsim device"
exit "$KSFT_SKIP"
}
local dev
dev=$(ip netns exec "$testns" ls /sys/bus/netdevsim/devices/netdevsim$NSIM_ID/net 2>/dev/null | head -1)
if [[ -z "$dev" ]]; then
ktap_skip_all "failed to find netdevsim device"
exit "$KSFT_SKIP"
fi
ip -netns "$testns" link set dev "$dev" name "$NSIM_DEV_NAME" 2>/dev/null || {
ktap_skip_all "failed to rename netdevsim device"
exit "$KSFT_SKIP"
}
ip -netns "$testns" link set dev "$NSIM_DEV_NAME" up 2>/dev/null
if ! ip -n "$testns" link add "$VETH_A" type veth peer name "$VETH_B" 2>/dev/null; then
ktap_skip_all "failed to create veth pair"
exit "$KSFT_SKIP"
fi
ip -n "$testns" link set "$VETH_A" up 2>/dev/null
ip -n "$testns" link set "$VETH_B" up 2>/dev/null
}
cleanup()
{
ip netns exec "$testns" bash -c "echo $NSIM_ID > /sys/bus/netdevsim/del_device" 2>/dev/null || true
ip netns del "$testns" 2>/dev/null || true
}
# Check if ynl-ethtool command is available
if ! command -v $ynl_ethtool &>/dev/null && [[ ! -x $ynl_ethtool ]]; then
ktap_skip_all "ynl-ethtool command not found: $ynl_ethtool"
exit "$KSFT_SKIP"
fi
trap cleanup EXIT
ktap_print_header
setup
ktap_set_plan "${TESTS_NO}"
ethtool_device_info
ethtool_statistics
ethtool_ring_params
ethtool_coalesce_params
ethtool_pause_params
ethtool_features_info
ethtool_channels_info
ethtool_time_stamping
ktap_finished
+106
View File
@@ -0,0 +1,106 @@
// SPDX-License-Identifier: GPL-2.0
#include <arpa/inet.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <ynl.h>
#include "wireguard-user.h"
static void print_allowed_ip(const struct wireguard_wgallowedip *aip)
{
char addr_out[INET6_ADDRSTRLEN];
if (!inet_ntop(aip->family, aip->ipaddr, addr_out, sizeof(addr_out))) {
addr_out[0] = '?';
addr_out[1] = '\0';
}
printf("\t\t\t%s/%u\n", addr_out, aip->cidr_mask);
}
/* Only printing public key in this demo. For better key formatting,
* use the constant-time implementation as found in wireguard-tools.
*/
static void print_peer_header(const struct wireguard_wgpeer *peer)
{
unsigned int len = peer->_len.public_key;
uint8_t *key = peer->public_key;
unsigned int i;
if (len != 32)
return;
printf("\tPeer ");
for (i = 0; i < len; i++)
printf("%02x", key[i]);
printf(":\n");
}
static void print_peer(const struct wireguard_wgpeer *peer)
{
unsigned int i;
print_peer_header(peer);
printf("\t\tData: rx: %llu / tx: %llu bytes\n",
peer->rx_bytes, peer->tx_bytes);
printf("\t\tAllowed IPs:\n");
for (i = 0; i < peer->_count.allowedips; i++)
print_allowed_ip(&peer->allowedips[i]);
}
static void build_request(struct wireguard_get_device_req *req, char *arg)
{
char *endptr;
int ifindex;
ifindex = strtol(arg, &endptr, 0);
if (endptr != arg + strlen(arg) || errno != 0)
ifindex = 0;
if (ifindex > 0)
wireguard_get_device_req_set_ifindex(req, ifindex);
else
wireguard_get_device_req_set_ifname(req, arg);
}
int main(int argc, char **argv)
{
struct wireguard_get_device_list *devs;
struct wireguard_get_device_req *req;
struct ynl_error yerr;
struct ynl_sock *ys;
if (argc < 2) {
fprintf(stderr, "usage: %s <ifindex|ifname>\n", argv[0]);
return 1;
}
ys = ynl_sock_create(&ynl_wireguard_family, &yerr);
if (!ys) {
fprintf(stderr, "YNL: %s\n", yerr.msg);
return 2;
}
req = wireguard_get_device_req_alloc();
build_request(req, argv[1]);
devs = wireguard_get_device_dump(ys, req);
if (!devs) {
fprintf(stderr, "YNL (%d): %s\n", ys->err.code, ys->err.msg);
wireguard_get_device_req_free(req);
ynl_sock_destroy(ys);
return 3;
}
ynl_dump_foreach(devs, d) {
unsigned int i;
printf("Interface %d: %s\n", d->ifindex, d->ifname);
for (i = 0; i < d->_count.peers; i++)
print_peer(&d->peers[i]);
}
wireguard_get_device_list_free(devs);
wireguard_get_device_req_free(req);
ynl_sock_destroy(ys);
return 0;
}
@@ -0,0 +1,35 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
# Shared netdevsim setup/cleanup for YNL C test wrappers
NSIM_ID="1337"
NSIM_DEV=""
KSFT_SKIP=4
nsim_cleanup() {
echo "$NSIM_ID" > /sys/bus/netdevsim/del_device 2>/dev/null || true
}
nsim_setup() {
modprobe netdevsim 2>/dev/null
if ! [ -f /sys/bus/netdevsim/new_device ]; then
echo "netdevsim module not available, skipping" >&2
exit "$KSFT_SKIP"
fi
trap nsim_cleanup EXIT
echo "$NSIM_ID 1" > /sys/bus/netdevsim/new_device
udevadm settle
NSIM_DEV=$(ls /sys/bus/netdevsim/devices/netdevsim${NSIM_ID}/net 2>/dev/null | head -1)
if [ -z "$NSIM_DEV" ]; then
echo "failed to find netdevsim device" >&2
exit 1
fi
ip link set dev "$NSIM_DEV" name nsim0
ip link set dev nsim0 up
ip addr add 192.168.1.1/24 dev nsim0
ip addr add 2001:db8::1/64 dev nsim0 nodad
}
+37
View File
@@ -0,0 +1,37 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
TOOL=$(dirname $(realpath $0))/pyynl/ynl_gen_c.py
force=
search=
while [ ! -z "$1" ]; do
case "$1" in
-f ) force=yes; shift ;;
-p ) search=$2; shift 2 ;;
* ) echo "Unrecognized option '$1'"; exit 1 ;;
esac
done
KDIR=$(dirname $(dirname $(dirname $(dirname $(realpath $0)))))
pushd ${search:-$KDIR} >>/dev/null
files=$(git grep --files-with-matches '^/\* YNL-GEN \(kernel\|uapi\|user\)')
for f in $files; do
# params: 0 1 2 3
# $YAML YNL-GEN kernel $mode
params=( $(git grep --no-line-number -B1 -h '/\* YNL-GEN' $f | sed 's@/\*\(.*\)\*/@\1@') )
args=$(sed -n 's@/\* YNL-ARG \(.*\) \*/@\1@p' $f)
if [ $f -nt ${params[0]} -a -z "$force" ]; then
echo -e "\tSKIP $f"
continue
fi
echo -e "\tGEN ${params[2]}\t$f"
$TOOL --cmp-out --mode ${params[2]} --${params[3]} \
--spec $KDIR/${params[0]} $args -o $f
done
popd >>/dev/null
@@ -0,0 +1,2 @@
ynltool
*.d
@@ -0,0 +1,55 @@
# SPDX-License-Identifier: GPL-2.0-only
include ../Makefile.deps
INSTALL ?= install
prefix ?= /usr
CC := gcc
CFLAGS := -Wall -Wextra -Werror -O2
ifeq ("$(DEBUG)","1")
CFLAGS += -g -fsanitize=address -fsanitize=leak -static-libasan
endif
CFLAGS += -I../lib -I../generated -I../../../include/uapi/
SRC_VERSION := \
$(shell make --no-print-directory -sC ../../../.. kernelversion 2>/dev/null || \
echo "unknown")
CFLAGS += -DSRC_VERSION='"$(SRC_VERSION)"'
SRCS := $(wildcard *.c)
OBJS := $(patsubst %.c,$(OUTPUT)%.o,$(SRCS))
YNLTOOL := $(OUTPUT)ynltool
include $(wildcard *.d)
all: $(YNLTOOL)
Q = @
$(YNLTOOL): ../libynl.a $(OBJS)
$(Q)echo -e "\tLINK $@"
$(Q)$(CC) $(CFLAGS) -o $@ $(OBJS) ../libynl.a -lm
%.o: %.c ../libynl.a
$(Q)echo -e "\tCC $@"
$(Q)$(COMPILE.c) -MMD -c -o $@ $<
../libynl.a:
$(Q)$(MAKE) -C ../
clean:
rm -f *.o *.d *~
distclean: clean
rm -f $(YNLTOOL)
bindir ?= /usr/bin
install: $(YNLTOOL)
$(INSTALL) -m 0755 $(YNLTOOL) $(DESTDIR)$(bindir)/$(YNLTOOL)
.PHONY: all clean distclean
.DEFAULT_GOAL=all
@@ -0,0 +1,288 @@
// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause)
/*
* Simple streaming JSON writer
*
* This takes care of the annoying bits of JSON syntax like the commas
* after elements
*
* Authors: Stephen Hemminger <stephen@networkplumber.org>
*/
#include <stdio.h>
#include <stdbool.h>
#include <stdarg.h>
#include <assert.h>
#include <malloc.h>
#include <inttypes.h>
#include <stdint.h>
#include "json_writer.h"
struct json_writer {
FILE *out;
unsigned depth;
bool pretty;
char sep;
};
static void jsonw_indent(json_writer_t *self)
{
unsigned i;
for (i = 0; i < self->depth; ++i)
fputs(" ", self->out);
}
static void jsonw_eol(json_writer_t *self)
{
if (!self->pretty)
return;
putc('\n', self->out);
jsonw_indent(self);
}
static void jsonw_eor(json_writer_t *self)
{
if (self->sep != '\0')
putc(self->sep, self->out);
self->sep = ',';
}
static void jsonw_puts(json_writer_t *self, const char *str)
{
putc('"', self->out);
for (; *str; ++str)
switch (*str) {
case '\t':
fputs("\\t", self->out);
break;
case '\n':
fputs("\\n", self->out);
break;
case '\r':
fputs("\\r", self->out);
break;
case '\f':
fputs("\\f", self->out);
break;
case '\b':
fputs("\\b", self->out);
break;
case '\\':
fputs("\\\\", self->out);
break;
case '"':
fputs("\\\"", self->out);
break;
default:
putc(*str, self->out);
}
putc('"', self->out);
}
json_writer_t *jsonw_new(FILE *f)
{
json_writer_t *self = malloc(sizeof(*self));
if (self) {
self->out = f;
self->depth = 0;
self->pretty = false;
self->sep = '\0';
}
return self;
}
void jsonw_destroy(json_writer_t **self_p)
{
json_writer_t *self = *self_p;
assert(self->depth == 0);
fputs("\n", self->out);
fflush(self->out);
free(self);
*self_p = NULL;
}
void jsonw_pretty(json_writer_t *self, bool on)
{
self->pretty = on;
}
void jsonw_reset(json_writer_t *self)
{
assert(self->depth == 0);
self->sep = '\0';
}
static void jsonw_begin(json_writer_t *self, int c)
{
jsonw_eor(self);
putc(c, self->out);
++self->depth;
self->sep = '\0';
}
static void jsonw_end(json_writer_t *self, int c)
{
assert(self->depth > 0);
--self->depth;
if (self->sep != '\0')
jsonw_eol(self);
putc(c, self->out);
self->sep = ',';
}
void jsonw_name(json_writer_t *self, const char *name)
{
jsonw_eor(self);
jsonw_eol(self);
self->sep = '\0';
jsonw_puts(self, name);
putc(':', self->out);
if (self->pretty)
putc(' ', self->out);
}
void jsonw_vprintf_enquote(json_writer_t *self, const char *fmt, va_list ap)
{
jsonw_eor(self);
putc('"', self->out);
vfprintf(self->out, fmt, ap);
putc('"', self->out);
}
void jsonw_printf(json_writer_t *self, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
jsonw_eor(self);
vfprintf(self->out, fmt, ap);
va_end(ap);
}
void jsonw_start_object(json_writer_t *self)
{
jsonw_begin(self, '{');
}
void jsonw_end_object(json_writer_t *self)
{
jsonw_end(self, '}');
}
void jsonw_start_array(json_writer_t *self)
{
jsonw_begin(self, '[');
}
void jsonw_end_array(json_writer_t *self)
{
jsonw_end(self, ']');
}
void jsonw_string(json_writer_t *self, const char *value)
{
jsonw_eor(self);
jsonw_puts(self, value);
}
void jsonw_bool(json_writer_t *self, bool val)
{
jsonw_printf(self, "%s", val ? "true" : "false");
}
void jsonw_null(json_writer_t *self)
{
jsonw_printf(self, "null");
}
void jsonw_float_fmt(json_writer_t *self, const char *fmt, double num)
{
jsonw_printf(self, fmt, num);
}
void jsonw_float(json_writer_t *self, double num)
{
jsonw_printf(self, "%g", num);
}
void jsonw_hu(json_writer_t *self, unsigned short num)
{
jsonw_printf(self, "%hu", num);
}
void jsonw_uint(json_writer_t *self, uint64_t num)
{
jsonw_printf(self, "%"PRIu64, num);
}
void jsonw_lluint(json_writer_t *self, unsigned long long int num)
{
jsonw_printf(self, "%llu", num);
}
void jsonw_int(json_writer_t *self, int64_t num)
{
jsonw_printf(self, "%"PRId64, num);
}
void jsonw_string_field(json_writer_t *self, const char *prop, const char *val)
{
jsonw_name(self, prop);
jsonw_string(self, val);
}
void jsonw_bool_field(json_writer_t *self, const char *prop, bool val)
{
jsonw_name(self, prop);
jsonw_bool(self, val);
}
void jsonw_float_field(json_writer_t *self, const char *prop, double val)
{
jsonw_name(self, prop);
jsonw_float(self, val);
}
void jsonw_float_field_fmt(json_writer_t *self,
const char *prop,
const char *fmt,
double val)
{
jsonw_name(self, prop);
jsonw_float_fmt(self, fmt, val);
}
void jsonw_uint_field(json_writer_t *self, const char *prop, uint64_t num)
{
jsonw_name(self, prop);
jsonw_uint(self, num);
}
void jsonw_hu_field(json_writer_t *self, const char *prop, unsigned short num)
{
jsonw_name(self, prop);
jsonw_hu(self, num);
}
void jsonw_lluint_field(json_writer_t *self,
const char *prop,
unsigned long long int num)
{
jsonw_name(self, prop);
jsonw_lluint(self, num);
}
void jsonw_int_field(json_writer_t *self, const char *prop, int64_t num)
{
jsonw_name(self, prop);
jsonw_int(self, num);
}
void jsonw_null_field(json_writer_t *self, const char *prop)
{
jsonw_name(self, prop);
jsonw_null(self);
}
@@ -0,0 +1,75 @@
/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
/*
* Simple streaming JSON writer
*
* This takes care of the annoying bits of JSON syntax like the commas
* after elements
*
* Authors: Stephen Hemminger <stephen@networkplumber.org>
*/
#ifndef _JSON_WRITER_H_
#define _JSON_WRITER_H_
#include <stdbool.h>
#include <stdint.h>
#include <stdarg.h>
#include <stdio.h>
/* Opaque class structure */
typedef struct json_writer json_writer_t;
/* Create a new JSON stream */
json_writer_t *jsonw_new(FILE *f);
/* End output to JSON stream */
void jsonw_destroy(json_writer_t **self_p);
/* Cause output to have pretty whitespace */
void jsonw_pretty(json_writer_t *self, bool on);
/* Reset separator to create new JSON */
void jsonw_reset(json_writer_t *self);
/* Add property name */
void jsonw_name(json_writer_t *self, const char *name);
/* Add value */
void __attribute__((format(printf, 2, 0))) jsonw_vprintf_enquote(json_writer_t *self,
const char *fmt,
va_list ap);
void __attribute__((format(printf, 2, 3))) jsonw_printf(json_writer_t *self,
const char *fmt, ...);
void jsonw_string(json_writer_t *self, const char *value);
void jsonw_bool(json_writer_t *self, bool value);
void jsonw_float(json_writer_t *self, double number);
void jsonw_float_fmt(json_writer_t *self, const char *fmt, double num);
void jsonw_uint(json_writer_t *self, uint64_t number);
void jsonw_hu(json_writer_t *self, unsigned short number);
void jsonw_int(json_writer_t *self, int64_t number);
void jsonw_null(json_writer_t *self);
void jsonw_lluint(json_writer_t *self, unsigned long long int num);
/* Useful Combinations of name and value */
void jsonw_string_field(json_writer_t *self, const char *prop, const char *val);
void jsonw_bool_field(json_writer_t *self, const char *prop, bool value);
void jsonw_float_field(json_writer_t *self, const char *prop, double num);
void jsonw_uint_field(json_writer_t *self, const char *prop, uint64_t num);
void jsonw_hu_field(json_writer_t *self, const char *prop, unsigned short num);
void jsonw_int_field(json_writer_t *self, const char *prop, int64_t num);
void jsonw_null_field(json_writer_t *self, const char *prop);
void jsonw_lluint_field(json_writer_t *self, const char *prop,
unsigned long long int num);
void jsonw_float_field_fmt(json_writer_t *self, const char *prop,
const char *fmt, double val);
/* Collections */
void jsonw_start_object(json_writer_t *self);
void jsonw_end_object(json_writer_t *self);
void jsonw_start_array(json_writer_t *self);
void jsonw_end_array(json_writer_t *self);
/* Override default exception handling */
typedef void (jsonw_err_handler_fn)(const char *);
#endif /* _JSON_WRITER_H_ */
+242
View File
@@ -0,0 +1,242 @@
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
/* Copyright (C) 2017-2018 Netronome Systems, Inc. */
/* Copyright Meta Platforms, Inc. and affiliates */
#include <ctype.h>
#include <errno.h>
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include "main.h"
const char *bin_name;
static int last_argc;
static char **last_argv;
static int (*last_do_help)(int argc, char **argv);
json_writer_t *json_wtr;
bool pretty_output;
bool json_output;
static void __attribute__((noreturn)) clean_and_exit(int i)
{
if (json_output)
jsonw_destroy(&json_wtr);
exit(i);
}
void usage(void)
{
last_do_help(last_argc - 1, last_argv + 1);
clean_and_exit(-1);
}
static int do_help(int argc __attribute__((unused)),
char **argv __attribute__((unused)))
{
if (json_output) {
jsonw_null(json_wtr);
return 0;
}
fprintf(stderr,
"Usage: %s [OPTIONS] OBJECT { COMMAND | help }\n"
" %s version\n"
"\n"
" OBJECT := { page-pool | qstats }\n"
" " HELP_SPEC_OPTIONS "\n"
"",
bin_name, bin_name);
return 0;
}
static int do_version(int argc __attribute__((unused)),
char **argv __attribute__((unused)))
{
if (json_output) {
jsonw_start_object(json_wtr);
jsonw_name(json_wtr, "version");
jsonw_printf(json_wtr, SRC_VERSION);
jsonw_end_object(json_wtr);
} else {
printf("%s " SRC_VERSION "\n", bin_name);
}
return 0;
}
static const struct cmd commands[] = {
{ "help", do_help },
{ "page-pool", do_page_pool },
{ "qstats", do_qstats },
{ "version", do_version },
{ 0 }
};
int cmd_select(const struct cmd *cmds, int argc, char **argv,
int (*help)(int argc, char **argv))
{
unsigned int i;
last_argc = argc;
last_argv = argv;
last_do_help = help;
if (argc < 1 && cmds[0].func)
return cmds[0].func(argc, argv);
for (i = 0; cmds[i].cmd; i++) {
if (is_prefix(*argv, cmds[i].cmd)) {
if (!cmds[i].func) {
p_err("command '%s' is not available", cmds[i].cmd);
return -1;
}
return cmds[i].func(argc - 1, argv + 1);
}
}
help(argc - 1, argv + 1);
return -1;
}
bool is_prefix(const char *pfx, const char *str)
{
if (!pfx)
return false;
if (strlen(str) < strlen(pfx))
return false;
return !memcmp(str, pfx, strlen(pfx));
}
/* Last argument MUST be NULL pointer */
int detect_common_prefix(const char *arg, ...)
{
unsigned int count = 0;
const char *ref;
char msg[256];
va_list ap;
snprintf(msg, sizeof(msg), "ambiguous prefix: '%s' could be '", arg);
va_start(ap, arg);
while ((ref = va_arg(ap, const char *))) {
if (!is_prefix(arg, ref))
continue;
count++;
if (count > 1)
strncat(msg, "' or '", sizeof(msg) - strlen(msg) - 1);
strncat(msg, ref, sizeof(msg) - strlen(msg) - 1);
}
va_end(ap);
strncat(msg, "'", sizeof(msg) - strlen(msg) - 1);
if (count >= 2) {
p_err("%s", msg);
return -1;
}
return 0;
}
void p_err(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
if (json_output) {
jsonw_start_object(json_wtr);
jsonw_name(json_wtr, "error");
jsonw_vprintf_enquote(json_wtr, fmt, ap);
jsonw_end_object(json_wtr);
} else {
fprintf(stderr, "Error: ");
vfprintf(stderr, fmt, ap);
fprintf(stderr, "\n");
}
va_end(ap);
}
void p_info(const char *fmt, ...)
{
va_list ap;
if (json_output)
return;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
fprintf(stderr, "\n");
va_end(ap);
}
int main(int argc, char **argv)
{
static const struct option options[] = {
{ "json", no_argument, NULL, 'j' },
{ "help", no_argument, NULL, 'h' },
{ "pretty", no_argument, NULL, 'p' },
{ "version", no_argument, NULL, 'V' },
{ 0 }
};
bool version_requested = false;
int opt, ret;
setlinebuf(stdout);
last_do_help = do_help;
pretty_output = false;
json_output = false;
bin_name = "ynltool";
opterr = 0;
while ((opt = getopt_long(argc, argv, "Vhjp",
options, NULL)) >= 0) {
switch (opt) {
case 'V':
version_requested = true;
break;
case 'h':
return do_help(argc, argv);
case 'p':
pretty_output = true;
/* fall through */
case 'j':
if (!json_output) {
json_wtr = jsonw_new(stdout);
if (!json_wtr) {
p_err("failed to create JSON writer");
return -1;
}
json_output = true;
}
jsonw_pretty(json_wtr, pretty_output);
break;
default:
p_err("unrecognized option '%s'", argv[optind - 1]);
if (json_output)
clean_and_exit(-1);
else
usage();
}
}
argc -= optind;
argv += optind;
if (argc < 0)
usage();
if (version_requested)
ret = do_version(argc, argv);
else
ret = cmd_select(commands, argc, argv, do_help);
if (json_output)
jsonw_destroy(&json_wtr);
return ret;
}
+66
View File
@@ -0,0 +1,66 @@
/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
/* Copyright (C) 2017-2018 Netronome Systems, Inc. */
/* Copyright Meta Platforms, Inc. and affiliates */
#ifndef __YNLTOOL_H
#define __YNLTOOL_H
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include "json_writer.h"
#define NEXT_ARG() ({ argc--; argv++; if (argc < 0) usage(); })
#define NEXT_ARGP() ({ (*argc)--; (*argv)++; if (*argc < 0) usage(); })
#define BAD_ARG() ({ p_err("what is '%s'?", *argv); -1; })
#define GET_ARG() ({ argc--; *argv++; })
#define REQ_ARGS(cnt) \
({ \
int _cnt = (cnt); \
bool _res; \
\
if (argc < _cnt) { \
p_err("'%s' needs at least %d arguments, %d found", \
argv[-1], _cnt, argc); \
_res = false; \
} else { \
_res = true; \
} \
_res; \
})
#define HELP_SPEC_OPTIONS \
"OPTIONS := { {-j|--json} [{-p|--pretty}] }"
extern const char *bin_name;
extern json_writer_t *json_wtr;
extern bool json_output;
extern bool pretty_output;
void __attribute__((format(printf, 1, 2))) p_err(const char *fmt, ...);
void __attribute__((format(printf, 1, 2))) p_info(const char *fmt, ...);
bool is_prefix(const char *pfx, const char *str);
int detect_common_prefix(const char *arg, ...);
void usage(void) __attribute__((noreturn));
struct cmd {
const char *cmd;
int (*func)(int argc, char **argv);
};
int cmd_select(const struct cmd *cmds, int argc, char **argv,
int (*help)(int argc, char **argv));
/* subcommands */
int do_page_pool(int argc, char **argv);
int do_qstats(int argc, char **argv);
#endif /* __YNLTOOL_H */
@@ -0,0 +1,463 @@
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <net/if.h>
#include <ynl.h>
#include "netdev-user.h"
#include "main.h"
struct pp_stat {
unsigned int ifc;
struct {
unsigned int cnt;
size_t refs, bytes;
} live[2];
size_t alloc_slow, alloc_fast, recycle_ring, recycle_cache;
};
struct pp_stats_array {
unsigned int i, max;
struct pp_stat *s;
};
static struct pp_stat *find_ifc(struct pp_stats_array *a, unsigned int ifindex)
{
unsigned int i;
for (i = 0; i < a->i; i++) {
if (a->s[i].ifc == ifindex)
return &a->s[i];
}
a->i++;
if (a->i == a->max) {
a->max *= 2;
a->s = reallocarray(a->s, a->max, sizeof(*a->s));
}
a->s[i].ifc = ifindex;
return &a->s[i];
}
static void count_pool(struct pp_stat *s, unsigned int l,
struct netdev_page_pool_get_rsp *pp)
{
s->live[l].cnt++;
if (pp->_present.inflight)
s->live[l].refs += pp->inflight;
if (pp->_present.inflight_mem)
s->live[l].bytes += pp->inflight_mem;
}
/* We don't know how many pages are sitting in cache and ring
* so we will under-count the recycling rate a bit.
*/
static void print_json_recycling_stats(struct pp_stat *s)
{
double recycle;
if (s->alloc_fast + s->alloc_slow) {
recycle = (double)(s->recycle_ring + s->recycle_cache) /
(s->alloc_fast + s->alloc_slow) * 100;
jsonw_float_field(json_wtr, "recycling_pct", recycle);
}
jsonw_name(json_wtr, "alloc");
jsonw_start_object(json_wtr);
jsonw_uint_field(json_wtr, "slow", s->alloc_slow);
jsonw_uint_field(json_wtr, "fast", s->alloc_fast);
jsonw_end_object(json_wtr);
jsonw_name(json_wtr, "recycle");
jsonw_start_object(json_wtr);
jsonw_uint_field(json_wtr, "ring", s->recycle_ring);
jsonw_uint_field(json_wtr, "cache", s->recycle_cache);
jsonw_end_object(json_wtr);
}
static void print_plain_recycling_stats(struct pp_stat *s)
{
double recycle;
if (s->alloc_fast + s->alloc_slow) {
recycle = (double)(s->recycle_ring + s->recycle_cache) /
(s->alloc_fast + s->alloc_slow) * 100;
printf("recycling: %.1lf%% (alloc: %zu:%zu recycle: %zu:%zu)",
recycle, s->alloc_slow, s->alloc_fast,
s->recycle_ring, s->recycle_cache);
}
}
static void print_json_stats(struct pp_stats_array *a)
{
jsonw_start_array(json_wtr);
for (unsigned int i = 0; i < a->i; i++) {
char ifname[IF_NAMESIZE];
struct pp_stat *s = &a->s[i];
const char *name;
jsonw_start_object(json_wtr);
if (!s->ifc) {
jsonw_string_field(json_wtr, "ifname", "<orphan>");
jsonw_uint_field(json_wtr, "ifindex", 0);
} else {
name = if_indextoname(s->ifc, ifname);
if (name)
jsonw_string_field(json_wtr, "ifname", name);
jsonw_uint_field(json_wtr, "ifindex", s->ifc);
}
jsonw_uint_field(json_wtr, "page_pools", s->live[1].cnt);
jsonw_uint_field(json_wtr, "zombies", s->live[0].cnt);
jsonw_name(json_wtr, "live");
jsonw_start_object(json_wtr);
jsonw_uint_field(json_wtr, "refs", s->live[1].refs);
jsonw_uint_field(json_wtr, "bytes", s->live[1].bytes);
jsonw_end_object(json_wtr);
jsonw_name(json_wtr, "zombie");
jsonw_start_object(json_wtr);
jsonw_uint_field(json_wtr, "refs", s->live[0].refs);
jsonw_uint_field(json_wtr, "bytes", s->live[0].bytes);
jsonw_end_object(json_wtr);
if (s->alloc_fast || s->alloc_slow)
print_json_recycling_stats(s);
jsonw_end_object(json_wtr);
}
jsonw_end_array(json_wtr);
}
static void print_plain_stats(struct pp_stats_array *a)
{
for (unsigned int i = 0; i < a->i; i++) {
char ifname[IF_NAMESIZE];
struct pp_stat *s = &a->s[i];
const char *name;
if (!s->ifc) {
printf("<orphan>\t");
} else {
name = if_indextoname(s->ifc, ifname);
if (name)
printf("%8s", name);
printf("[%u]\t", s->ifc);
}
printf("page pools: %u (zombies: %u)\n",
s->live[1].cnt, s->live[0].cnt);
printf("\t\trefs: %zu bytes: %zu (refs: %zu bytes: %zu)\n",
s->live[1].refs, s->live[1].bytes,
s->live[0].refs, s->live[0].bytes);
if (s->alloc_fast || s->alloc_slow) {
printf("\t\t");
print_plain_recycling_stats(s);
printf("\n");
}
}
}
static bool
find_pool_stat_in_list(struct netdev_page_pool_stats_get_list *pp_stats,
__u64 pool_id, struct pp_stat *pstat)
{
ynl_dump_foreach(pp_stats, pp) {
if (!pp->_present.info || !pp->info._present.id)
continue;
if (pp->info.id != pool_id)
continue;
memset(pstat, 0, sizeof(*pstat));
if (pp->_present.alloc_fast)
pstat->alloc_fast = pp->alloc_fast;
if (pp->_present.alloc_refill)
pstat->alloc_fast += pp->alloc_refill;
if (pp->_present.alloc_slow)
pstat->alloc_slow = pp->alloc_slow;
if (pp->_present.recycle_ring)
pstat->recycle_ring = pp->recycle_ring;
if (pp->_present.recycle_cached)
pstat->recycle_cache = pp->recycle_cached;
return true;
}
return false;
}
static void
print_json_pool_list(struct netdev_page_pool_get_list *pools,
struct netdev_page_pool_stats_get_list *pp_stats,
bool zombies_only)
{
jsonw_start_array(json_wtr);
ynl_dump_foreach(pools, pp) {
char ifname[IF_NAMESIZE];
struct pp_stat pstat;
const char *name;
if (zombies_only && !pp->_present.detach_time)
continue;
jsonw_start_object(json_wtr);
jsonw_uint_field(json_wtr, "id", pp->id);
if (pp->_present.ifindex) {
name = if_indextoname(pp->ifindex, ifname);
if (name)
jsonw_string_field(json_wtr, "ifname", name);
jsonw_uint_field(json_wtr, "ifindex", pp->ifindex);
}
if (pp->_present.napi_id)
jsonw_uint_field(json_wtr, "napi_id", pp->napi_id);
if (pp->_present.inflight)
jsonw_uint_field(json_wtr, "refs", pp->inflight);
if (pp->_present.inflight_mem)
jsonw_uint_field(json_wtr, "bytes", pp->inflight_mem);
if (pp->_present.detach_time)
jsonw_uint_field(json_wtr, "detach_time", pp->detach_time);
if (pp->_present.dmabuf)
jsonw_uint_field(json_wtr, "dmabuf", pp->dmabuf);
if (find_pool_stat_in_list(pp_stats, pp->id, &pstat) &&
(pstat.alloc_fast || pstat.alloc_slow))
print_json_recycling_stats(&pstat);
jsonw_end_object(json_wtr);
}
jsonw_end_array(json_wtr);
}
static void
print_plain_pool_list(struct netdev_page_pool_get_list *pools,
struct netdev_page_pool_stats_get_list *pp_stats,
bool zombies_only)
{
ynl_dump_foreach(pools, pp) {
char ifname[IF_NAMESIZE];
struct pp_stat pstat;
const char *name;
if (zombies_only && !pp->_present.detach_time)
continue;
printf("pool id: %llu", pp->id);
if (pp->_present.ifindex) {
name = if_indextoname(pp->ifindex, ifname);
if (name)
printf(" dev: %s", name);
printf("[%u]", pp->ifindex);
}
if (pp->_present.napi_id)
printf(" napi: %llu", pp->napi_id);
printf("\n");
if (pp->_present.inflight || pp->_present.inflight_mem) {
printf(" inflight:");
if (pp->_present.inflight)
printf(" %llu pages", pp->inflight);
if (pp->_present.inflight_mem)
printf(" %llu bytes", pp->inflight_mem);
printf("\n");
}
if (pp->_present.detach_time)
printf(" detached: %llu\n", pp->detach_time);
if (pp->_present.dmabuf)
printf(" dmabuf: %u\n", pp->dmabuf);
if (find_pool_stat_in_list(pp_stats, pp->id, &pstat) &&
(pstat.alloc_fast || pstat.alloc_slow)) {
printf(" ");
print_plain_recycling_stats(&pstat);
printf("\n");
}
}
}
static void aggregate_device_stats(struct pp_stats_array *a,
struct netdev_page_pool_get_list *pools,
struct netdev_page_pool_stats_get_list *pp_stats)
{
ynl_dump_foreach(pools, pp) {
struct pp_stat *s = find_ifc(a, pp->ifindex);
count_pool(s, 1, pp);
if (pp->_present.detach_time)
count_pool(s, 0, pp);
}
ynl_dump_foreach(pp_stats, pp) {
struct pp_stat *s = find_ifc(a, pp->info.ifindex);
if (pp->_present.alloc_fast)
s->alloc_fast += pp->alloc_fast;
if (pp->_present.alloc_refill)
s->alloc_fast += pp->alloc_refill;
if (pp->_present.alloc_slow)
s->alloc_slow += pp->alloc_slow;
if (pp->_present.recycle_ring)
s->recycle_ring += pp->recycle_ring;
if (pp->_present.recycle_cached)
s->recycle_cache += pp->recycle_cached;
}
}
static int do_stats(int argc, char **argv)
{
struct netdev_page_pool_stats_get_req_dump pp_stat_req = {};
struct netdev_page_pool_stats_get_list *pp_stats;
struct netdev_page_pool_get_req_dump pp_req = {};
struct netdev_page_pool_get_list *pools;
enum {
GROUP_BY_DEVICE,
GROUP_BY_POOL,
} group_by = GROUP_BY_DEVICE;
bool zombies_only = false;
struct pp_stats_array a = {};
struct ynl_error yerr;
struct ynl_sock *ys;
int ret = 0;
/* Parse options */
while (argc > 0) {
if (is_prefix(*argv, "group-by")) {
NEXT_ARG();
if (!REQ_ARGS(1))
return -1;
if (is_prefix(*argv, "device")) {
group_by = GROUP_BY_DEVICE;
} else if (is_prefix(*argv, "pp") ||
is_prefix(*argv, "page-pool") ||
is_prefix(*argv, "none")) {
group_by = GROUP_BY_POOL;
} else {
p_err("invalid group-by value '%s'", *argv);
return -1;
}
NEXT_ARG();
} else if (is_prefix(*argv, "zombies")) {
zombies_only = true;
group_by = GROUP_BY_POOL;
NEXT_ARG();
} else {
p_err("unknown option '%s'", *argv);
return -1;
}
}
ys = ynl_sock_create(&ynl_netdev_family, &yerr);
if (!ys) {
p_err("YNL: %s", yerr.msg);
return -1;
}
pools = netdev_page_pool_get_dump(ys, &pp_req);
if (!pools) {
p_err("failed to get page pools: %s", ys->err.msg);
ret = -1;
goto exit_close;
}
pp_stats = netdev_page_pool_stats_get_dump(ys, &pp_stat_req);
if (!pp_stats) {
p_err("failed to get page pool stats: %s", ys->err.msg);
ret = -1;
goto exit_free_pp_list;
}
/* If grouping by pool, print individual pools */
if (group_by == GROUP_BY_POOL) {
if (json_output)
print_json_pool_list(pools, pp_stats, zombies_only);
else
print_plain_pool_list(pools, pp_stats, zombies_only);
} else {
/* Aggregated stats mode (group-by device) */
a.max = 64;
a.s = calloc(a.max, sizeof(*a.s));
if (!a.s) {
p_err("failed to allocate stats array");
ret = -1;
goto exit_free_stats_list;
}
aggregate_device_stats(&a, pools, pp_stats);
if (json_output)
print_json_stats(&a);
else
print_plain_stats(&a);
free(a.s);
}
exit_free_stats_list:
netdev_page_pool_stats_get_list_free(pp_stats);
exit_free_pp_list:
netdev_page_pool_get_list_free(pools);
exit_close:
ynl_sock_destroy(ys);
return ret;
}
static int do_help(int argc __attribute__((unused)),
char **argv __attribute__((unused)))
{
if (json_output) {
jsonw_null(json_wtr);
return 0;
}
fprintf(stderr,
"Usage: %s page-pool { COMMAND | help }\n"
" %s page-pool stats [ OPTIONS ]\n"
"\n"
" OPTIONS := { group-by { device | page-pool | none } | zombies }\n"
"\n"
" stats - Display page pool statistics\n"
" stats group-by device - Group statistics by network device (default)\n"
" stats group-by page-pool | pp | none\n"
" - Show individual page pool details (no grouping)\n"
" stats zombies - Show only zombie page pools (detached but with\n"
" pages in flight). Implies group-by page-pool.\n"
"",
bin_name, bin_name);
return 0;
}
static const struct cmd page_pool_cmds[] = {
{ "help", do_help },
{ "stats", do_stats },
{ 0 }
};
int do_page_pool(int argc, char **argv)
{
return cmd_select(page_pool_cmds, argc, argv, do_help);
}
+674
View File
@@ -0,0 +1,674 @@
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <net/if.h>
#include <math.h>
#include <ynl.h>
#include "netdev-user.h"
#include "main.h"
static enum netdev_qstats_scope scope; /* default - device */
struct queue_balance {
unsigned int ifindex;
enum netdev_queue_type type;
unsigned int queue_count;
__u64 *rx_packets;
__u64 *rx_bytes;
__u64 *tx_packets;
__u64 *tx_bytes;
};
static void print_json_qstats(struct netdev_qstats_get_list *qstats)
{
jsonw_start_array(json_wtr);
ynl_dump_foreach(qstats, qs) {
char ifname[IF_NAMESIZE];
const char *name;
jsonw_start_object(json_wtr);
name = if_indextoname(qs->ifindex, ifname);
if (name)
jsonw_string_field(json_wtr, "ifname", name);
jsonw_uint_field(json_wtr, "ifindex", qs->ifindex);
if (qs->_present.queue_type)
jsonw_string_field(json_wtr, "queue-type",
netdev_queue_type_str(qs->queue_type));
if (qs->_present.queue_id)
jsonw_uint_field(json_wtr, "queue-id", qs->queue_id);
if (qs->_present.rx_packets || qs->_present.rx_bytes ||
qs->_present.rx_alloc_fail || qs->_present.rx_hw_drops ||
qs->_present.rx_csum_complete || qs->_present.rx_hw_gro_packets) {
jsonw_name(json_wtr, "rx");
jsonw_start_object(json_wtr);
if (qs->_present.rx_packets)
jsonw_uint_field(json_wtr, "packets", qs->rx_packets);
if (qs->_present.rx_bytes)
jsonw_uint_field(json_wtr, "bytes", qs->rx_bytes);
if (qs->_present.rx_alloc_fail)
jsonw_uint_field(json_wtr, "alloc-fail", qs->rx_alloc_fail);
if (qs->_present.rx_hw_drops)
jsonw_uint_field(json_wtr, "hw-drops", qs->rx_hw_drops);
if (qs->_present.rx_hw_drop_overruns)
jsonw_uint_field(json_wtr, "hw-drop-overruns", qs->rx_hw_drop_overruns);
if (qs->_present.rx_hw_drop_ratelimits)
jsonw_uint_field(json_wtr, "hw-drop-ratelimits", qs->rx_hw_drop_ratelimits);
if (qs->_present.rx_csum_complete)
jsonw_uint_field(json_wtr, "csum-complete", qs->rx_csum_complete);
if (qs->_present.rx_csum_unnecessary)
jsonw_uint_field(json_wtr, "csum-unnecessary", qs->rx_csum_unnecessary);
if (qs->_present.rx_csum_none)
jsonw_uint_field(json_wtr, "csum-none", qs->rx_csum_none);
if (qs->_present.rx_csum_bad)
jsonw_uint_field(json_wtr, "csum-bad", qs->rx_csum_bad);
if (qs->_present.rx_hw_gro_packets)
jsonw_uint_field(json_wtr, "hw-gro-packets", qs->rx_hw_gro_packets);
if (qs->_present.rx_hw_gro_bytes)
jsonw_uint_field(json_wtr, "hw-gro-bytes", qs->rx_hw_gro_bytes);
if (qs->_present.rx_hw_gro_wire_packets)
jsonw_uint_field(json_wtr, "hw-gro-wire-packets", qs->rx_hw_gro_wire_packets);
if (qs->_present.rx_hw_gro_wire_bytes)
jsonw_uint_field(json_wtr, "hw-gro-wire-bytes", qs->rx_hw_gro_wire_bytes);
jsonw_end_object(json_wtr);
}
if (qs->_present.tx_packets || qs->_present.tx_bytes ||
qs->_present.tx_hw_drops || qs->_present.tx_csum_none ||
qs->_present.tx_hw_gso_packets) {
jsonw_name(json_wtr, "tx");
jsonw_start_object(json_wtr);
if (qs->_present.tx_packets)
jsonw_uint_field(json_wtr, "packets", qs->tx_packets);
if (qs->_present.tx_bytes)
jsonw_uint_field(json_wtr, "bytes", qs->tx_bytes);
if (qs->_present.tx_hw_drops)
jsonw_uint_field(json_wtr, "hw-drops", qs->tx_hw_drops);
if (qs->_present.tx_hw_drop_errors)
jsonw_uint_field(json_wtr, "hw-drop-errors", qs->tx_hw_drop_errors);
if (qs->_present.tx_hw_drop_ratelimits)
jsonw_uint_field(json_wtr, "hw-drop-ratelimits", qs->tx_hw_drop_ratelimits);
if (qs->_present.tx_csum_none)
jsonw_uint_field(json_wtr, "csum-none", qs->tx_csum_none);
if (qs->_present.tx_needs_csum)
jsonw_uint_field(json_wtr, "needs-csum", qs->tx_needs_csum);
if (qs->_present.tx_hw_gso_packets)
jsonw_uint_field(json_wtr, "hw-gso-packets", qs->tx_hw_gso_packets);
if (qs->_present.tx_hw_gso_bytes)
jsonw_uint_field(json_wtr, "hw-gso-bytes", qs->tx_hw_gso_bytes);
if (qs->_present.tx_hw_gso_wire_packets)
jsonw_uint_field(json_wtr, "hw-gso-wire-packets", qs->tx_hw_gso_wire_packets);
if (qs->_present.tx_hw_gso_wire_bytes)
jsonw_uint_field(json_wtr, "hw-gso-wire-bytes", qs->tx_hw_gso_wire_bytes);
if (qs->_present.tx_stop)
jsonw_uint_field(json_wtr, "stop", qs->tx_stop);
if (qs->_present.tx_wake)
jsonw_uint_field(json_wtr, "wake", qs->tx_wake);
jsonw_end_object(json_wtr);
}
jsonw_end_object(json_wtr);
}
jsonw_end_array(json_wtr);
}
static void print_one(bool present, const char *name, unsigned long long val,
int *line)
{
if (!present)
return;
if (!*line) {
printf(" ");
++(*line);
}
/* Don't waste space on tx- and rx- prefix, its implied by queue type */
if (scope == NETDEV_QSTATS_SCOPE_QUEUE &&
(name[0] == 'r' || name[0] == 't') &&
name[1] == 'x' && name[2] == '-')
name += 3;
printf(" %15s: %15llu", name, val);
if (++(*line) == 3) {
printf("\n");
*line = 0;
}
}
static void print_plain_qstats(struct netdev_qstats_get_list *qstats)
{
ynl_dump_foreach(qstats, qs) {
char ifname[IF_NAMESIZE];
const char *name;
int n;
name = if_indextoname(qs->ifindex, ifname);
if (name)
printf("%s", name);
else
printf("ifindex:%u", qs->ifindex);
if (qs->_present.queue_type && qs->_present.queue_id)
printf("\t%s-%-3u",
netdev_queue_type_str(qs->queue_type),
qs->queue_id);
else
printf("\t ");
n = 1;
/* Basic counters */
print_one(qs->_present.rx_packets, "rx-packets", qs->rx_packets, &n);
print_one(qs->_present.rx_bytes, "rx-bytes", qs->rx_bytes, &n);
print_one(qs->_present.tx_packets, "tx-packets", qs->tx_packets, &n);
print_one(qs->_present.tx_bytes, "tx-bytes", qs->tx_bytes, &n);
/* RX error/drop counters */
print_one(qs->_present.rx_alloc_fail, "rx-alloc-fail",
qs->rx_alloc_fail, &n);
print_one(qs->_present.rx_hw_drops, "rx-hw-drops",
qs->rx_hw_drops, &n);
print_one(qs->_present.rx_hw_drop_overruns, "rx-hw-drop-overruns",
qs->rx_hw_drop_overruns, &n);
print_one(qs->_present.rx_hw_drop_ratelimits, "rx-hw-drop-ratelimits",
qs->rx_hw_drop_ratelimits, &n);
/* RX checksum counters */
print_one(qs->_present.rx_csum_complete, "rx-csum-complete",
qs->rx_csum_complete, &n);
print_one(qs->_present.rx_csum_unnecessary, "rx-csum-unnecessary",
qs->rx_csum_unnecessary, &n);
print_one(qs->_present.rx_csum_none, "rx-csum-none",
qs->rx_csum_none, &n);
print_one(qs->_present.rx_csum_bad, "rx-csum-bad",
qs->rx_csum_bad, &n);
/* RX GRO counters */
print_one(qs->_present.rx_hw_gro_packets, "rx-hw-gro-packets",
qs->rx_hw_gro_packets, &n);
print_one(qs->_present.rx_hw_gro_bytes, "rx-hw-gro-bytes",
qs->rx_hw_gro_bytes, &n);
print_one(qs->_present.rx_hw_gro_wire_packets, "rx-hw-gro-wire-packets",
qs->rx_hw_gro_wire_packets, &n);
print_one(qs->_present.rx_hw_gro_wire_bytes, "rx-hw-gro-wire-bytes",
qs->rx_hw_gro_wire_bytes, &n);
/* TX error/drop counters */
print_one(qs->_present.tx_hw_drops, "tx-hw-drops",
qs->tx_hw_drops, &n);
print_one(qs->_present.tx_hw_drop_errors, "tx-hw-drop-errors",
qs->tx_hw_drop_errors, &n);
print_one(qs->_present.tx_hw_drop_ratelimits, "tx-hw-drop-ratelimits",
qs->tx_hw_drop_ratelimits, &n);
/* TX checksum counters */
print_one(qs->_present.tx_csum_none, "tx-csum-none",
qs->tx_csum_none, &n);
print_one(qs->_present.tx_needs_csum, "tx-needs-csum",
qs->tx_needs_csum, &n);
/* TX GSO counters */
print_one(qs->_present.tx_hw_gso_packets, "tx-hw-gso-packets",
qs->tx_hw_gso_packets, &n);
print_one(qs->_present.tx_hw_gso_bytes, "tx-hw-gso-bytes",
qs->tx_hw_gso_bytes, &n);
print_one(qs->_present.tx_hw_gso_wire_packets, "tx-hw-gso-wire-packets",
qs->tx_hw_gso_wire_packets, &n);
print_one(qs->_present.tx_hw_gso_wire_bytes, "tx-hw-gso-wire-bytes",
qs->tx_hw_gso_wire_bytes, &n);
/* TX queue control */
print_one(qs->_present.tx_stop, "tx-stop", qs->tx_stop, &n);
print_one(qs->_present.tx_wake, "tx-wake", qs->tx_wake, &n);
if (n)
printf("\n");
}
}
static struct netdev_qstats_get_list *
qstats_dump(enum netdev_qstats_scope scope)
{
struct netdev_qstats_get_list *qstats;
struct netdev_qstats_get_req *req;
struct ynl_error yerr;
struct ynl_sock *ys;
ys = ynl_sock_create(&ynl_netdev_family, &yerr);
if (!ys) {
p_err("YNL: %s", yerr.msg);
return NULL;
}
req = netdev_qstats_get_req_alloc();
if (!req) {
p_err("failed to allocate qstats request");
goto err_close;
}
if (scope)
netdev_qstats_get_req_set_scope(req, scope);
qstats = netdev_qstats_get_dump(ys, req);
netdev_qstats_get_req_free(req);
if (!qstats) {
p_err("failed to get queue stats: %s", ys->err.msg);
goto err_close;
}
ynl_sock_destroy(ys);
return qstats;
err_close:
ynl_sock_destroy(ys);
return NULL;
}
static int do_show(int argc, char **argv)
{
struct netdev_qstats_get_list *qstats;
/* Parse options */
while (argc > 0) {
if (is_prefix(*argv, "scope") || is_prefix(*argv, "group-by")) {
NEXT_ARG();
if (!REQ_ARGS(1))
return -1;
if (is_prefix(*argv, "queue")) {
scope = NETDEV_QSTATS_SCOPE_QUEUE;
} else if (is_prefix(*argv, "device")) {
scope = 0;
} else {
p_err("invalid scope value '%s'", *argv);
return -1;
}
NEXT_ARG();
} else {
p_err("unknown option '%s'", *argv);
return -1;
}
}
qstats = qstats_dump(scope);
if (!qstats)
return -1;
/* Print the stats as returned by the kernel */
if (json_output)
print_json_qstats(qstats);
else
print_plain_qstats(qstats);
netdev_qstats_get_list_free(qstats);
return 0;
}
static void compute_stats(__u64 *values, unsigned int count,
double *mean, double *stddev, __u64 *min, __u64 *max)
{
double sum = 0.0, variance = 0.0;
unsigned int i;
*min = ~0ULL;
*max = 0;
if (count == 0) {
*mean = 0;
*stddev = 0;
*min = 0;
return;
}
for (i = 0; i < count; i++) {
sum += values[i];
if (values[i] < *min)
*min = values[i];
if (values[i] > *max)
*max = values[i];
}
*mean = sum / count;
if (count > 1) {
for (i = 0; i < count; i++) {
double diff = values[i] - *mean;
variance += diff * diff;
}
*stddev = sqrt(variance / (count - 1));
} else {
*stddev = 0;
}
}
static void print_balance_stats(const char *name, enum netdev_queue_type type,
__u64 *values, unsigned int count)
{
double mean, stddev, cv, ns;
__u64 min, max;
if ((name[0] == 'r' && type != NETDEV_QUEUE_TYPE_RX) ||
(name[0] == 't' && type != NETDEV_QUEUE_TYPE_TX))
return;
compute_stats(values, count, &mean, &stddev, &min, &max);
cv = mean > 0 ? (stddev / mean) * 100.0 : 0.0;
ns = min + max > 0 ? (double)2 * (max - min) / (max + min) * 100 : 0.0;
printf(" %-12s: cv=%.1f%% ns=%.1f%% stddev=%.0f\n",
name, cv, ns, stddev);
printf(" %-12s min=%llu max=%llu mean=%.0f\n",
"", min, max, mean);
}
static void
print_balance_stats_json(const char *name, enum netdev_queue_type type,
__u64 *values, unsigned int count)
{
double mean, stddev, cv, ns;
__u64 min, max;
if ((name[0] == 'r' && type != NETDEV_QUEUE_TYPE_RX) ||
(name[0] == 't' && type != NETDEV_QUEUE_TYPE_TX))
return;
compute_stats(values, count, &mean, &stddev, &min, &max);
cv = mean > 0 ? (stddev / mean) * 100.0 : 0.0;
ns = min + max > 0 ? (double)2 * (max - min) / (max + min) * 100 : 0.0;
jsonw_name(json_wtr, name);
jsonw_start_object(json_wtr);
jsonw_uint_field(json_wtr, "queue-count", count);
jsonw_uint_field(json_wtr, "min", min);
jsonw_uint_field(json_wtr, "max", max);
jsonw_float_field(json_wtr, "mean", mean);
jsonw_float_field(json_wtr, "stddev", stddev);
jsonw_float_field(json_wtr, "coefficient-of-variation", cv);
jsonw_float_field(json_wtr, "normalized-spread", ns);
jsonw_end_object(json_wtr);
}
static int cmp_ifindex_type(const void *a, const void *b)
{
const struct netdev_qstats_get_rsp *qa = a;
const struct netdev_qstats_get_rsp *qb = b;
if (qa->ifindex != qb->ifindex)
return qa->ifindex - qb->ifindex;
if (qa->queue_type != qb->queue_type)
return qa->queue_type - qb->queue_type;
return qa->queue_id - qb->queue_id;
}
static int do_balance(int argc, char **argv __attribute__((unused)))
{
struct netdev_qstats_get_list *qstats;
struct netdev_qstats_get_rsp **sorted;
unsigned int count = 0;
unsigned int i, j;
int ret = 0;
if (argc > 0) {
p_err("balance command takes no arguments");
return -1;
}
qstats = qstats_dump(NETDEV_QSTATS_SCOPE_QUEUE);
if (!qstats)
return -1;
/* Count and sort queues */
ynl_dump_foreach(qstats, qs)
count++;
if (count == 0) {
if (json_output)
jsonw_start_array(json_wtr);
else
printf("No queue statistics available\n");
goto exit_free_qstats;
}
sorted = calloc(count, sizeof(*sorted));
if (!sorted) {
p_err("failed to allocate sorted array");
ret = -1;
goto exit_free_qstats;
}
i = 0;
ynl_dump_foreach(qstats, qs)
sorted[i++] = qs;
qsort(sorted, count, sizeof(*sorted), cmp_ifindex_type);
if (json_output)
jsonw_start_array(json_wtr);
/* Process each device/queue-type combination */
i = 0;
while (i < count) {
__u64 *rx_packets, *rx_bytes, *tx_packets, *tx_bytes;
enum netdev_queue_type type = sorted[i]->queue_type;
unsigned int ifindex = sorted[i]->ifindex;
unsigned int queue_count = 0;
char ifname[IF_NAMESIZE];
const char *name;
/* Count queues for this device/type */
for (j = i; j < count && sorted[j]->ifindex == ifindex &&
sorted[j]->queue_type == type; j++)
queue_count++;
/* Skip if no packets/bytes (inactive queues) */
if (!sorted[i]->_present.rx_packets &&
!sorted[i]->_present.rx_bytes &&
!sorted[i]->_present.tx_packets &&
!sorted[i]->_present.tx_bytes)
goto next_ifc;
/* Allocate arrays for statistics */
rx_packets = calloc(queue_count, sizeof(*rx_packets));
rx_bytes = calloc(queue_count, sizeof(*rx_bytes));
tx_packets = calloc(queue_count, sizeof(*tx_packets));
tx_bytes = calloc(queue_count, sizeof(*tx_bytes));
if (!rx_packets || !rx_bytes || !tx_packets || !tx_bytes) {
p_err("failed to allocate statistics arrays");
free(rx_packets);
free(rx_bytes);
free(tx_packets);
free(tx_bytes);
ret = -1;
goto exit_free_sorted;
}
/* Collect statistics */
for (j = 0; j < queue_count; j++) {
rx_packets[j] = sorted[i + j]->_present.rx_packets ?
sorted[i + j]->rx_packets : 0;
rx_bytes[j] = sorted[i + j]->_present.rx_bytes ?
sorted[i + j]->rx_bytes : 0;
tx_packets[j] = sorted[i + j]->_present.tx_packets ?
sorted[i + j]->tx_packets : 0;
tx_bytes[j] = sorted[i + j]->_present.tx_bytes ?
sorted[i + j]->tx_bytes : 0;
}
name = if_indextoname(ifindex, ifname);
if (json_output) {
jsonw_start_object(json_wtr);
if (name)
jsonw_string_field(json_wtr, "ifname", name);
jsonw_uint_field(json_wtr, "ifindex", ifindex);
jsonw_string_field(json_wtr, "queue-type",
netdev_queue_type_str(type));
print_balance_stats_json("rx-packets", type,
rx_packets, queue_count);
print_balance_stats_json("rx-bytes", type,
rx_bytes, queue_count);
print_balance_stats_json("tx-packets", type,
tx_packets, queue_count);
print_balance_stats_json("tx-bytes", type,
tx_bytes, queue_count);
jsonw_end_object(json_wtr);
} else {
if (name)
printf("%s", name);
else
printf("ifindex:%u", ifindex);
printf(" %s %d queues:\n",
netdev_queue_type_str(type), queue_count);
print_balance_stats("rx-packets", type,
rx_packets, queue_count);
print_balance_stats("rx-bytes", type,
rx_bytes, queue_count);
print_balance_stats("tx-packets", type,
tx_packets, queue_count);
print_balance_stats("tx-bytes", type,
tx_bytes, queue_count);
printf("\n");
}
free(rx_packets);
free(rx_bytes);
free(tx_packets);
free(tx_bytes);
next_ifc:
i += queue_count;
}
if (json_output)
jsonw_end_array(json_wtr);
exit_free_sorted:
free(sorted);
exit_free_qstats:
netdev_qstats_get_list_free(qstats);
return ret;
}
static int do_hw_gro(int argc, char **argv __attribute__((unused)))
{
struct netdev_qstats_get_list *qstats;
if (argc > 0) {
p_err("hw-gro command takes no arguments");
return -1;
}
qstats = qstats_dump(0);
if (!qstats)
return -1;
if (json_output)
jsonw_start_array(json_wtr);
ynl_dump_foreach(qstats, qs) {
char ifname[IF_NAMESIZE];
const char *name;
double savings;
if (!qs->_present.rx_packets ||
!qs->_present.rx_hw_gro_packets ||
!qs->_present.rx_hw_gro_wire_packets)
continue;
if (!qs->rx_packets)
continue;
/* How many skbs did we avoid allocating thanks to HW GRO */
savings = (double)(qs->rx_hw_gro_wire_packets -
qs->rx_hw_gro_packets) /
qs->rx_packets * 100.0;
name = if_indextoname(qs->ifindex, ifname);
if (json_output) {
jsonw_start_object(json_wtr);
jsonw_uint_field(json_wtr, "ifindex", qs->ifindex);
if (name)
jsonw_string_field(json_wtr, "ifname", name);
jsonw_float_field(json_wtr, "savings", savings);
jsonw_end_object(json_wtr);
} else {
if (name)
printf("%s", name);
else
printf("ifindex:%u", qs->ifindex);
printf(": %.1f%% savings\n", savings);
}
}
if (json_output)
jsonw_end_array(json_wtr);
netdev_qstats_get_list_free(qstats);
return 0;
}
static int do_help(int argc __attribute__((unused)),
char **argv __attribute__((unused)))
{
if (json_output) {
jsonw_null(json_wtr);
return 0;
}
fprintf(stderr,
"Usage: %1$s qstats { COMMAND | help }\n"
" %1$s qstats [ show ] [ OPTIONS ]\n"
" %1$s qstats balance\n"
" %1$s qstats hw-gro\n"
"\n"
" OPTIONS := { scope queue | group-by { device | queue } }\n"
"\n"
" show - Display queue statistics (default)\n"
" Statistics are aggregated for the entire device.\n"
" show scope queue - Display per-queue statistics\n"
" show group-by device - Display device-aggregated statistics (default)\n"
" show group-by queue - Display per-queue statistics\n"
"\n"
" Analysis:\n"
" balance - Traffic distribution between queues.\n"
" hw-gro - HW GRO effectiveness analysis\n"
" - savings - delta between packets received\n"
" on the wire and packets seen by the kernel.\n"
"",
bin_name);
return 0;
}
static const struct cmd qstats_cmds[] = {
{ "show", do_show },
{ "balance", do_balance },
{ "hw-gro", do_hw_gro },
{ "help", do_help },
{ 0 }
};
int do_qstats(int argc, char **argv)
{
return cmd_select(qstats_cmds, argc, argv, do_help);
}