Skip to content

Commit ba8be00

Browse files
donaldhkuba-moo
authored andcommitted
tools/net/ynl: Add multi message support to ynl
Add a "--multi <do-op> <json>" command line to ynl that makes it possible to add several operations to a single netlink request payload. The --multi command line option is repeated for each operation. This is used by the nftables family for transaction batches. For example: ./tools/net/ynl/cli.py \ --spec Documentation/netlink/specs/nftables.yaml \ --multi batch-begin '{"res-id": 10}' \ --multi newtable '{"name": "test", "nfgen-family": 1}' \ --multi newchain '{"name": "chain", "table": "test", "nfgen-family": 1}' \ --multi batch-end '{"res-id": 10}' [None, None, None, None] It can also be used for bundling get requests: ./tools/net/ynl/cli.py \ --spec Documentation/netlink/specs/nftables.yaml \ --multi gettable '{"name": "test", "nfgen-family": 1}' \ --multi getchain '{"name": "chain", "table": "test", "nfgen-family": 1}' \ --output-json [{"name": "test", "use": 1, "handle": 1, "flags": [], "nfgen-family": 1, "version": 0, "res-id": 2}, {"table": "test", "name": "chain", "handle": 1, "use": 0, "nfgen-family": 1, "version": 0, "res-id": 2}] Signed-off-by: Donald Hunter <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Jakub Kicinski <[email protected]>
1 parent 0a966d6 commit ba8be00

File tree

2 files changed

+71
-22
lines changed

2 files changed

+71
-22
lines changed

tools/net/ynl/cli.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,28 @@ def default(self, obj):
1919

2020

2121
def main():
22-
parser = argparse.ArgumentParser(description='YNL CLI sample')
22+
description = """
23+
YNL CLI utility - a general purpose netlink utility that uses YAML
24+
specs to drive protocol encoding and decoding.
25+
"""
26+
epilog = """
27+
The --multi option can be repeated to include several do operations
28+
in the same netlink payload.
29+
"""
30+
31+
parser = argparse.ArgumentParser(description=description,
32+
epilog=epilog)
2333
parser.add_argument('--spec', dest='spec', type=str, required=True)
2434
parser.add_argument('--schema', dest='schema', type=str)
2535
parser.add_argument('--no-schema', action='store_true')
2636
parser.add_argument('--json', dest='json_text', type=str)
27-
parser.add_argument('--do', dest='do', type=str)
28-
parser.add_argument('--dump', dest='dump', type=str)
37+
38+
group = parser.add_mutually_exclusive_group()
39+
group.add_argument('--do', dest='do', metavar='DO-OPERATION', type=str)
40+
group.add_argument('--multi', dest='multi', nargs=2, action='append',
41+
metavar=('DO-OPERATION', 'JSON_TEXT'), type=str)
42+
group.add_argument('--dump', dest='dump', metavar='DUMP-OPERATION', type=str)
43+
2944
parser.add_argument('--sleep', dest='sleep', type=int)
3045
parser.add_argument('--subscribe', dest='ntf', type=str)
3146
parser.add_argument('--replace', dest='flags', action='append_const',
@@ -73,6 +88,10 @@ def output(msg):
7388
if args.dump:
7489
reply = ynl.dump(args.dump, attrs)
7590
output(reply)
91+
if args.multi:
92+
ops = [ (item[0], json.loads(item[1]), args.flags or []) for item in args.multi ]
93+
reply = ynl.do_multi(ops)
94+
output(reply)
7695
except NlError as e:
7796
print(e)
7897
exit(1)

tools/net/ynl/lib/ynl.py

Lines changed: 49 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -938,49 +938,74 @@ def operation_do_attributes(self, name):
938938

939939
return op['do']['request']['attributes'].copy()
940940

941-
def _op(self, method, vals, flags=None, dump=False):
942-
op = self.ops[method]
943-
941+
def _encode_message(self, op, vals, flags, req_seq):
944942
nl_flags = Netlink.NLM_F_REQUEST | Netlink.NLM_F_ACK
945943
for flag in flags or []:
946944
nl_flags |= flag
947-
if dump:
948-
nl_flags |= Netlink.NLM_F_DUMP
949945

950-
req_seq = random.randint(1024, 65535)
951946
msg = self.nlproto.message(nl_flags, op.req_value, 1, req_seq)
952947
if op.fixed_header:
953948
msg += self._encode_struct(op.fixed_header, vals)
954949
search_attrs = SpaceAttrs(op.attr_set, vals)
955950
for name, value in vals.items():
956951
msg += self._add_attr(op.attr_set.name, name, value, search_attrs)
957952
msg = _genl_msg_finalize(msg)
953+
return msg
958954

959-
self.sock.send(msg, 0)
955+
def _ops(self, ops):
956+
reqs_by_seq = {}
957+
req_seq = random.randint(1024, 65535)
958+
payload = b''
959+
for (method, vals, flags) in ops:
960+
op = self.ops[method]
961+
msg = self._encode_message(op, vals, flags, req_seq)
962+
reqs_by_seq[req_seq] = (op, msg, flags)
963+
payload += msg
964+
req_seq += 1
965+
966+
self.sock.send(payload, 0)
960967

961968
done = False
962969
rsp = []
970+
op_rsp = []
963971
while not done:
964972
reply = self.sock.recv(self._recv_size)
965973
nms = NlMsgs(reply, attr_space=op.attr_set)
966974
self._recv_dbg_print(reply, nms)
967975
for nl_msg in nms:
968-
if nl_msg.extack:
969-
self._decode_extack(msg, op, nl_msg.extack)
976+
if nl_msg.nl_seq in reqs_by_seq:
977+
(op, req_msg, req_flags) = reqs_by_seq[nl_msg.nl_seq]
978+
if nl_msg.extack:
979+
self._decode_extack(req_msg, op, nl_msg.extack)
980+
else:
981+
op = self.rsp_by_value[nl_msg.cmd()]
982+
req_flags = []
970983

971984
if nl_msg.error:
972985
raise NlError(nl_msg)
973986
if nl_msg.done:
974987
if nl_msg.extack:
975988
print("Netlink warning:")
976989
print(nl_msg)
977-
done = True
990+
991+
if Netlink.NLM_F_DUMP in req_flags:
992+
rsp.append(op_rsp)
993+
elif not op_rsp:
994+
rsp.append(None)
995+
elif len(op_rsp) == 1:
996+
rsp.append(op_rsp[0])
997+
else:
998+
rsp.append(op_rsp)
999+
op_rsp = []
1000+
1001+
del reqs_by_seq[nl_msg.nl_seq]
1002+
done = len(reqs_by_seq) == 0
9781003
break
9791004

9801005
decoded = self.nlproto.decode(self, nl_msg, op)
9811006

9821007
# Check if this is a reply to our request
983-
if nl_msg.nl_seq != req_seq or decoded.cmd() != op.rsp_value:
1008+
if nl_msg.nl_seq not in reqs_by_seq or decoded.cmd() != op.rsp_value:
9841009
if decoded.cmd() in self.async_msg_ids:
9851010
self.handle_ntf(decoded)
9861011
continue
@@ -991,18 +1016,23 @@ def _op(self, method, vals, flags=None, dump=False):
9911016
rsp_msg = self._decode(decoded.raw_attrs, op.attr_set.name)
9921017
if op.fixed_header:
9931018
rsp_msg.update(self._decode_struct(decoded.raw, op.fixed_header))
994-
rsp.append(rsp_msg)
1019+
op_rsp.append(rsp_msg)
9951020

996-
if dump:
997-
return rsp
998-
if not rsp:
999-
return None
1000-
if len(rsp) == 1:
1001-
return rsp[0]
10021021
return rsp
10031022

1023+
def _op(self, method, vals, flags=None, dump=False):
1024+
req_flags = flags or []
1025+
if dump:
1026+
req_flags.append(Netlink.NLM_F_DUMP)
1027+
1028+
ops = [(method, vals, req_flags)]
1029+
return self._ops(ops)[0]
1030+
10041031
def do(self, method, vals, flags=None):
10051032
return self._op(method, vals, flags)
10061033

10071034
def dump(self, method, vals):
1008-
return self._op(method, vals, [], dump=True)
1035+
return self._op(method, vals, dump=True)
1036+
1037+
def do_multi(self, ops):
1038+
return self._ops(ops)

0 commit comments

Comments
 (0)