diff options
Diffstat (limited to 'tools/net')
-rw-r--r-- | tools/net/ynl/Makefile | 4 | ||||
-rwxr-xr-x | tools/net/ynl/cli.py | 25 | ||||
-rw-r--r-- | tools/net/ynl/generated/Makefile | 4 | ||||
-rw-r--r-- | tools/net/ynl/lib/Makefile | 5 | ||||
-rw-r--r-- | tools/net/ynl/lib/__init__.py | 4 | ||||
-rw-r--r-- | tools/net/ynl/lib/ynl-priv.h | 34 | ||||
-rw-r--r-- | tools/net/ynl/lib/ynl.c | 37 | ||||
-rw-r--r-- | tools/net/ynl/lib/ynl.h | 2 | ||||
-rw-r--r-- | tools/net/ynl/lib/ynl.py | 61 | ||||
-rw-r--r-- | tools/net/ynl/samples/Makefile | 4 | ||||
-rwxr-xr-x | tools/net/ynl/ynl-gen-c.py | 2 |
11 files changed, 152 insertions, 30 deletions
diff --git a/tools/net/ynl/Makefile b/tools/net/ynl/Makefile index da1aa10bbcc3..8e9e09d84e26 100644 --- a/tools/net/ynl/Makefile +++ b/tools/net/ynl/Makefile @@ -11,11 +11,11 @@ $(SUBDIRS): $(MAKE) -C $@ ; \ fi -clean hardclean: +clean distclean: @for dir in $(SUBDIRS) ; do \ if [ -f "$$dir/Makefile" ] ; then \ $(MAKE) -C $$dir $@; \ fi \ done -.PHONY: clean all $(SUBDIRS) +.PHONY: all clean distclean $(SUBDIRS) diff --git a/tools/net/ynl/cli.py b/tools/net/ynl/cli.py index 0f8239979670..f131e33ac3ee 100755 --- a/tools/net/ynl/cli.py +++ b/tools/net/ynl/cli.py @@ -6,7 +6,7 @@ import json import pprint import time -from lib import YnlFamily, Netlink +from lib import YnlFamily, Netlink, NlError class YnlEncoder(json.JSONEncoder): @@ -38,6 +38,8 @@ def main(): const=Netlink.NLM_F_APPEND) parser.add_argument('--process-unknown', action=argparse.BooleanOptionalAction) parser.add_argument('--output-json', action='store_true') + parser.add_argument('--dbg-small-recv', default=0, const=4000, + action='store', nargs='?', type=int) args = parser.parse_args() def output(msg): @@ -53,7 +55,10 @@ def main(): if args.json_text: attrs = json.loads(args.json_text) - ynl = YnlFamily(args.spec, args.schema, args.process_unknown) + ynl = YnlFamily(args.spec, args.schema, args.process_unknown, + recv_size=args.dbg_small_recv) + if args.dbg_small_recv: + ynl.set_recv_dbg(True) if args.ntf: ynl.ntf_subscribe(args.ntf) @@ -61,12 +66,16 @@ def main(): if args.sleep: time.sleep(args.sleep) - if args.do: - reply = ynl.do(args.do, attrs, args.flags) - output(reply) - if args.dump: - reply = ynl.dump(args.dump, attrs) - output(reply) + 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) + except NlError as e: + print(e) + exit(1) if args.ntf: ynl.check_ntf() diff --git a/tools/net/ynl/generated/Makefile b/tools/net/ynl/generated/Makefile index 7135028cb449..713f5fb9cc2d 100644 --- a/tools/net/ynl/generated/Makefile +++ b/tools/net/ynl/generated/Makefile @@ -43,11 +43,11 @@ protos.a: $(OBJS) clean: rm -f *.o -hardclean: clean +distclean: clean rm -f *.c *.h *.a regen: @../ynl-regen.sh -.PHONY: all clean hardclean regen +.PHONY: all clean distclean regen .DEFAULT_GOAL: all diff --git a/tools/net/ynl/lib/Makefile b/tools/net/ynl/lib/Makefile index d2e50fd0a52d..dfff3ecd1cba 100644 --- a/tools/net/ynl/lib/Makefile +++ b/tools/net/ynl/lib/Makefile @@ -17,12 +17,13 @@ ynl.a: $(OBJS) ar rcs $@ $(OBJS) clean: rm -f *.o *.d *~ + rm -rf __pycache__ -hardclean: clean +distclean: clean rm -f *.a %.o: %.c $(COMPILE.c) -MMD -c -o $@ $< -.PHONY: all clean +.PHONY: all clean distclean .DEFAULT_GOAL=all diff --git a/tools/net/ynl/lib/__init__.py b/tools/net/ynl/lib/__init__.py index f7eaa07783e7..9137b83e580a 100644 --- a/tools/net/ynl/lib/__init__.py +++ b/tools/net/ynl/lib/__init__.py @@ -2,7 +2,7 @@ from .nlspec import SpecAttr, SpecAttrSet, SpecEnumEntry, SpecEnumSet, \ SpecFamily, SpecOperation -from .ynl import YnlFamily, Netlink +from .ynl import YnlFamily, Netlink, NlError __all__ = ["SpecAttr", "SpecAttrSet", "SpecEnumEntry", "SpecEnumSet", - "SpecFamily", "SpecOperation", "YnlFamily", "Netlink"] + "SpecFamily", "SpecOperation", "YnlFamily", "Netlink", "NlError"] diff --git a/tools/net/ynl/lib/ynl-priv.h b/tools/net/ynl/lib/ynl-priv.h index a8099fab035d..6cf890080dc0 100644 --- a/tools/net/ynl/lib/ynl-priv.h +++ b/tools/net/ynl/lib/ynl-priv.h @@ -135,6 +135,8 @@ 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 = buf; @@ -239,11 +241,29 @@ ynl_attr_first(const void *start, size_t len, size_t skip) return ynl_attr_if_good(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 ynl_nlmsg_end_addr(nlh) - NLA_HDRLEN; + attr = ynl_nlmsg_end_addr(nlh); attr->nla_type = attr_type | NLA_F_NESTED; nlh->nlmsg_len += NLA_HDRLEN; @@ -263,6 +283,9 @@ ynl_attr_put(struct nlmsghdr *nlh, unsigned int attr_type, { struct nlattr *attr; + if (__ynl_attr_put_overflow(nlh, size)) + return; + attr = ynl_nlmsg_end_addr(nlh); attr->nla_type = attr_type; attr->nla_len = NLA_HDRLEN + size; @@ -276,14 +299,17 @@ static inline void ynl_attr_put_str(struct nlmsghdr *nlh, unsigned int attr_type, const char *str) { struct nlattr *attr; - const char *end; + size_t len; + + len = strlen(str); + if (__ynl_attr_put_overflow(nlh, len)) + return; attr = ynl_nlmsg_end_addr(nlh); attr->nla_type = attr_type; - end = stpcpy(ynl_attr_data(attr), str); - attr->nla_len = - NLA_HDRLEN + NLA_ALIGN(end - (char *)ynl_attr_data(attr)); + strcpy(ynl_attr_data(attr), str); + attr->nla_len = NLA_HDRLEN + NLA_ALIGN(len); nlh->nlmsg_len += NLMSG_ALIGN(attr->nla_len); } diff --git a/tools/net/ynl/lib/ynl.c b/tools/net/ynl/lib/ynl.c index 86729119e1ef..b9e77af5af5f 100644 --- a/tools/net/ynl/lib/ynl.c +++ b/tools/net/ynl/lib/ynl.c @@ -404,9 +404,33 @@ struct nlmsghdr *ynl_msg_start(struct ynl_sock *ys, __u32 id, __u16 flags) nlh->nlmsg_flags = flags; nlh->nlmsg_seq = ++ys->seq; + /* This is a local YNL hack for length checking, we put the buffer + * length in nlmsg_pid, since messages sent to the kernel always use + * PID 0. Message needs to be terminated with ynl_msg_end(). + */ + nlh->nlmsg_pid = YNL_SOCKET_BUFFER_SIZE; + return nlh; } +static int ynl_msg_end(struct ynl_sock *ys, struct nlmsghdr *nlh) +{ + /* We stash buffer length in nlmsg_pid. */ + if (nlh->nlmsg_pid == 0) { + yerr(ys, YNL_ERROR_INPUT_INVALID, + "Unknown input buffer length"); + return -EINVAL; + } + if (nlh->nlmsg_pid == YNL_MSG_OVERFLOW) { + yerr(ys, YNL_ERROR_INPUT_TOO_BIG, + "Constructred message longer than internal buffer"); + return -EMSGSIZE; + } + + nlh->nlmsg_pid = 0; + return 0; +} + struct nlmsghdr * ynl_gemsg_start(struct ynl_sock *ys, __u32 id, __u16 flags, __u8 cmd, __u8 version) @@ -561,6 +585,7 @@ ynl_get_family_info_mcast(struct ynl_sock *ys, const struct nlattr *mcasts) ys->mcast_groups[i].name[GENL_NAMSIZ - 1] = 0; } } + i++; } return 0; @@ -606,6 +631,10 @@ static int ynl_sock_read_family(struct ynl_sock *ys, const char *family_name) nlh = ynl_gemsg_start_req(ys, GENL_ID_CTRL, CTRL_CMD_GETFAMILY, 1); ynl_attr_put_str(nlh, CTRL_ATTR_FAMILY_NAME, family_name); + err = ynl_msg_end(ys, nlh); + if (err < 0) + return err; + err = send(ys->socket, nlh, nlh->nlmsg_len, 0); if (err < 0) { perr(ys, "failed to request socket family info"); @@ -867,6 +896,10 @@ int ynl_exec(struct ynl_sock *ys, struct nlmsghdr *req_nlh, { int err; + err = ynl_msg_end(ys, req_nlh); + if (err < 0) + return err; + err = send(ys->socket, req_nlh, req_nlh->nlmsg_len, 0); if (err < 0) return err; @@ -920,6 +953,10 @@ int ynl_exec_dump(struct ynl_sock *ys, struct nlmsghdr *req_nlh, { int err; + err = ynl_msg_end(ys, req_nlh); + if (err < 0) + return err; + err = send(ys->socket, req_nlh, req_nlh->nlmsg_len, 0); if (err < 0) return err; diff --git a/tools/net/ynl/lib/ynl.h b/tools/net/ynl/lib/ynl.h index dbeeef8ce91a..9842e85a8c57 100644 --- a/tools/net/ynl/lib/ynl.h +++ b/tools/net/ynl/lib/ynl.h @@ -20,6 +20,8 @@ enum ynl_error_code { YNL_ERROR_ATTR_INVALID, YNL_ERROR_UNKNOWN_NTF, YNL_ERROR_INV_RESP, + YNL_ERROR_INPUT_INVALID, + YNL_ERROR_INPUT_TOO_BIG, }; /** diff --git a/tools/net/ynl/lib/ynl.py b/tools/net/ynl/lib/ynl.py index ac55aa5a3083..2d7fdd903d9e 100644 --- a/tools/net/ynl/lib/ynl.py +++ b/tools/net/ynl/lib/ynl.py @@ -7,6 +7,7 @@ import random import socket import struct from struct import Struct +import sys import yaml import ipaddress import uuid @@ -84,6 +85,10 @@ class NlError(Exception): return f"Netlink error: {os.strerror(-self.nl_msg.error)}\n{self.nl_msg}" +class ConfigError(Exception): + pass + + class NlAttr: ScalarFormat = namedtuple('ScalarFormat', ['native', 'big', 'little']) type_formats = { @@ -213,11 +218,11 @@ class NlMsg: return self.nl_type def __repr__(self): - msg = f"nl_len = {self.nl_len} ({len(self.raw)}) nl_flags = 0x{self.nl_flags:x} nl_type = {self.nl_type}\n" + msg = f"nl_len = {self.nl_len} ({len(self.raw)}) nl_flags = 0x{self.nl_flags:x} nl_type = {self.nl_type}" if self.error: - msg += '\terror: ' + str(self.error) + msg += '\n\terror: ' + str(self.error) if self.extack: - msg += '\textack: ' + repr(self.extack) + msg += '\n\textack: ' + repr(self.extack) return msg @@ -348,6 +353,9 @@ class NetlinkProtocol: raise Exception(f'Multicast group "{mcast_name}" not present in the spec') return mcast_groups[mcast_name].value + def msghdr_size(self): + return 16 + class GenlProtocol(NetlinkProtocol): def __init__(self, family_name): @@ -373,6 +381,8 @@ class GenlProtocol(NetlinkProtocol): raise Exception(f'Multicast group "{mcast_name}" not present in the family') return self.genl_family['mcast'][mcast_name] + def msghdr_size(self): + return super().msghdr_size() + 4 class SpaceAttrs: @@ -400,7 +410,8 @@ class SpaceAttrs: class YnlFamily(SpecFamily): - def __init__(self, def_path, schema=None, process_unknown=False): + def __init__(self, def_path, schema=None, process_unknown=False, + recv_size=0): super().__init__(def_path, schema) self.include_raw = False @@ -415,6 +426,17 @@ class YnlFamily(SpecFamily): except KeyError: raise Exception(f"Family '{self.yaml['name']}' not supported by the kernel") + self._recv_dbg = False + # Note that netlink will use conservative (min) message size for + # the first dump recv() on the socket, our setting will only matter + # from the second recv() on. + self._recv_size = recv_size if recv_size else 131072 + # Netlink will always allocate at least PAGE_SIZE - sizeof(skb_shinfo) + # for a message, so smaller receive sizes will lead to truncation. + # Note that the min size for other families may be larger than 4k! + if self._recv_size < 4000: + raise ConfigError() + self.sock = socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, self.nlproto.proto_num) self.sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_CAP_ACK, 1) self.sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_EXT_ACK, 1) @@ -438,6 +460,17 @@ class YnlFamily(SpecFamily): self.sock.setsockopt(Netlink.SOL_NETLINK, Netlink.NETLINK_ADD_MEMBERSHIP, mcast_id) + def set_recv_dbg(self, enabled): + self._recv_dbg = enabled + + def _recv_dbg_print(self, reply, nl_msgs): + if not self._recv_dbg: + return + print("Recv: read", len(reply), "bytes,", + len(nl_msgs.msgs), "messages", file=sys.stderr) + for nl_msg in nl_msgs: + print(" ", nl_msg, file=sys.stderr) + def _encode_enum(self, attr_spec, value): enum = self.consts[attr_spec['enum']] if enum.type == 'flags' or attr_spec.get('enum-as-flags', False): @@ -562,6 +595,16 @@ class YnlFamily(SpecFamily): decoded.append({ item.type: subattrs }) return decoded + def _decode_nest_type_value(self, attr, attr_spec): + decoded = {} + value = attr + for name in attr_spec['type-value']: + value = NlAttr(value.raw, 0) + decoded[name] = value.type + subattrs = self._decode(NlAttrs(value.raw), attr_spec['nested-attributes']) + decoded.update(subattrs) + return decoded + def _decode_unknown(self, attr): if attr.is_nest: return self._decode(NlAttrs(attr.raw), None) @@ -653,6 +696,8 @@ class YnlFamily(SpecFamily): decoded = {"value": value, "selector": selector} elif attr_spec["type"] == 'sub-message': decoded = self._decode_sub_msg(attr, attr_spec, search_attrs) + elif attr_spec["type"] == 'nest-type-value': + decoded = self._decode_nest_type_value(attr, attr_spec) else: if not self.process_unknown: raise Exception(f'Unknown {attr_spec["type"]} with name {attr_spec["name"]}') @@ -693,7 +738,7 @@ class YnlFamily(SpecFamily): return msg = self.nlproto.decode(self, NlMsg(request, 0, op.attr_set)) - offset = 20 + self._struct_size(op.fixed_header) + offset = self.nlproto.msghdr_size() + self._struct_size(op.fixed_header) path = self._decode_extack_path(msg.raw_attrs, op.attr_set, offset, extack['bad-attr-offs']) if path: @@ -799,11 +844,12 @@ class YnlFamily(SpecFamily): def check_ntf(self): while True: try: - reply = self.sock.recv(128 * 1024, socket.MSG_DONTWAIT) + reply = self.sock.recv(self._recv_size, socket.MSG_DONTWAIT) except BlockingIOError: return nms = NlMsgs(reply) + self._recv_dbg_print(reply, nms) for nl_msg in nms: if nl_msg.error: print("Netlink error in ntf!?", os.strerror(-nl_msg.error)) @@ -854,8 +900,9 @@ class YnlFamily(SpecFamily): done = False rsp = [] while not done: - reply = self.sock.recv(128 * 1024) + reply = self.sock.recv(self._recv_size) nms = NlMsgs(reply, attr_space=op.attr_set) + self._recv_dbg_print(reply, nms) for nl_msg in nms: if nl_msg.extack: self._decode_extack(msg, op, nl_msg.extack) diff --git a/tools/net/ynl/samples/Makefile b/tools/net/ynl/samples/Makefile index 1d33e98e3ffe..e194a7565861 100644 --- a/tools/net/ynl/samples/Makefile +++ b/tools/net/ynl/samples/Makefile @@ -28,8 +28,8 @@ $(BINS): ../lib/ynl.a ../generated/protos.a $(SRCS) clean: rm -f *.o *.d *~ -hardclean: clean +distclean: clean rm -f $(BINS) -.PHONY: all clean +.PHONY: all clean distclean .DEFAULT_GOAL=all diff --git a/tools/net/ynl/ynl-gen-c.py b/tools/net/ynl/ynl-gen-c.py index 2f5febfe66a1..67bfaff05154 100755 --- a/tools/net/ynl/ynl-gen-c.py +++ b/tools/net/ynl/ynl-gen-c.py @@ -1667,7 +1667,7 @@ def _multi_parse(ri, struct, init_lines, local_vars): aspec = struct[anest] ri.cw.block_start(line=f"if (n_{aspec.c_name})") - ri.cw.p(f"dst->{aspec.c_name} = calloc({aspec.c_name}, sizeof(*dst->{aspec.c_name}));") + ri.cw.p(f"dst->{aspec.c_name} = calloc(n_{aspec.c_name}, sizeof(*dst->{aspec.c_name}));") ri.cw.p(f"dst->n_{aspec.c_name} = n_{aspec.c_name};") ri.cw.p('i = 0;') ri.cw.p(f"parg.rsp_policy = &{aspec.nested_render_name}_nest;") |