From nobody Tue Apr 7 10:46:56 2026 Received: from mail-oi1-f177.google.com (mail-oi1-f177.google.com [209.85.167.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 93219359A8C for ; Fri, 13 Mar 2026 22:31:25 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.167.177 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773441088; cv=none; b=Pz3R46xRxIIPsUxgskc/ElGbwORGXPD4bDgeETs77J3z4Ljt9BjSTZj9OjO0VBJ5jwb+W0Tx/pyFSmzw+WlOyuq+z7uRClsFbM0XQ/gIhSps+JCby/SL94PXLPFyWcrtHsi373eQN01YIMp1+FpkUwmry1ZjeZIXgS/KMOVTWR0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773441088; c=relaxed/simple; bh=2lzl/2RT7wIvpJF2a1jM2tWT4AtAyL3brJmMBvXZMg0=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=aY20WroURAKB+5yv8tvKRVrSoVGNioRRd4aOClFGgj9MI0LcXusf7gQWE0Ew8G0FFfV74Tz9UvhzWAH+WmRQ+dc92pnIJ8owz87hzkWSqwLd6R1vQo6930RkCqCM6SN+oqdO8LtGJ0AA8kwqFhiwDC4rWuXvNmUcFXQDs0SXw8A= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=cloudflare.com; spf=pass smtp.mailfrom=cloudflare.com; dkim=pass (2048-bit key) header.d=cloudflare.com header.i=@cloudflare.com header.b=E7ORItEy; arc=none smtp.client-ip=209.85.167.177 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=cloudflare.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=cloudflare.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=cloudflare.com header.i=@cloudflare.com header.b="E7ORItEy" Received: by mail-oi1-f177.google.com with SMTP id 5614622812f47-467166cb638so1110940b6e.2 for ; Fri, 13 Mar 2026 15:31:25 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=cloudflare.com; s=google09082023; t=1773441084; x=1774045884; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=2NXcw/JA6oIFNRUPT/PH/GzRu0gFhvGGRdpsNeUi2W4=; b=E7ORItEyM52dMdxlnIexAxYhK4JI27mMzd9rrqJWPFStKar8PeLx2xdaZBWIARUu9X nhUaFpjPfByje3expdXWR9h8xloGegXmLLoy0dWwpxG9R06QqNc/UYe5Y9V4lf5qDL7D rFVAU5N316InffeHLczvveLHzSjadYPfmfRtqFJgKi3n04D1aIgyirNC8MVLMgBRPbDF yEvMvJrqaNVcE8Gba3fjCA5O6lfDLgbZrV6ooovMWxFskYk7Uhyw/6fm3c4cyLsLNIio 1MYSgyeUREGBjmzOlfvJxCvKoHBycEgtcnUK9fObO9OAwIgeciWFJ4lleCQnUHr9dVCL xuNg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1773441084; x=1774045884; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=2NXcw/JA6oIFNRUPT/PH/GzRu0gFhvGGRdpsNeUi2W4=; b=aXB1Iztk0N3++pDO102884WnZYDQ0M1GXJFJyTRlmYXJag/8EWfEDanhiRPmHKbf2o qlk/kAqH66jT4tDamSTfG/lNw0Ys8+w3q/E2T64bgdGBDjQ7pZLfHa5vc+7E70zB1mET CdrVM4rsciCCUrBD++W4631heNU0sWB1ipHdZcKo2P61KL32vWSdUh88zEPw/BnOtn++ /eckJJfYzDm+tIVp+x6WjzmO5huV2Zm5tw3TavI6wwrzbuUgaY/KE5YvuhGjINJU9DTC hYiYSSPr6Z/jMyNLqXYnSq+AazT2cZu+Y6Q7wmZR5XwWdysrazl7PEEDO0vtu3sr78xV jHKg== X-Forwarded-Encrypted: i=1; AJvYcCVitLiOKWL6Qd/7P1WFuJSqu4ozCVyYp2GsnJNKjgqlpPFlooXMRJ5roijhLuDCgoJi+vfHyI/eaRFoY3E=@vger.kernel.org X-Gm-Message-State: AOJu0Yw/LSLFHAbeaVaYpACNbpTco9JF2MYmjmXUEajP3V19PsCMoMTk fmWRkqrUL0ev+SunUSsiURQ5BmozkFIeTU4X3mrUSLQMCy2u0hvBnTKtBjHROvcaLN0= X-Gm-Gg: ATEYQzz8KSQvXWAAbggHOy7eR6dp8wCQEaBVC1P1rOVzQ/wai3BivGcTai1wqbK9XJp X6Gk+4WUYIFch6NcRHaK9l7KmUZmGqaeQnyFfAfLWWB9R2jwxwK4xqBZB6NXiwJAb0ReMj9lfd3 xEgm1hVIOmE/QbGx7SiUayi9aE8vi1VGWb/HeRqGcu++7eNxdVX5EL6d3CNDiq1bbXyNgw1KcV1 3PtcpZxVE4ABkMqbCxtQ53tJ7ve8+Th2NWXMI7mzErSnATiWX0gA9tgc5vWjnXzSzgeqiL/J/PN 09R82bJtpoj+xmgI7n7jvBvto36W/PT5WOEpJExESI9YPl4YxeiFP7oLK3ThzKevdwsFOFOPcpf F+BhoojF2jocv6WRS3UWV7GRD+W1hv9qVPI1lbFF7TpZ24qpsh1xcuQUOlT9p4bh1kekgaos6u8 PHy5BYX9JVA34iZs1mVFo= X-Received: by 2002:a05:6808:6f90:b0:45c:9927:3f33 with SMTP id 5614622812f47-467570a58c6mr2487530b6e.19.1773441084371; Fri, 13 Mar 2026 15:31:24 -0700 (PDT) Received: from 20HS2G4.. ([2a09:bac1:76c0:540::22f:7d]) by smtp.gmail.com with ESMTPSA id 5614622812f47-467342fab49sm5408516b6e.16.2026.03.13.15.31.21 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 13 Mar 2026 15:31:23 -0700 (PDT) From: Chris J Arges To: Joe Damato , Jakub Kicinski , Andy Gospodarek , Michael Chan , Pavan Chebbi , Andrew Lunn , "David S. Miller" , Eric Dumazet , Paolo Abeni , Alexei Starovoitov , Daniel Borkmann , Jesper Dangaard Brouer , John Fastabend , Stanislav Fomichev , Shuah Khan , Simon Horman , Willem de Bruijn , David Wei , Daniel Zahka , Carolina Jubran , Mohsin Bashir , Chris J Arges , Dimitri Daskalakis , Nimrod Oren , Gal Pressman Cc: Petr Machata , Cosmin Ratiu , linux-kernel@vger.kernel.org, netdev@vger.kernel.org, bpf@vger.kernel.org, linux-kselftest@vger.kernel.org Subject: [PATCH net-next v4 5/6] selftests: net: move common xdp.py functions into lib Date: Fri, 13 Mar 2026 17:27:35 -0500 Message-ID: <20260313223029.454755-6-carges@cloudflare.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260313223029.454755-1-carges@cloudflare.com> References: <20260313223029.454755-1-carges@cloudflare.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" This moves a few functions which can be useful to other python programs that manipulate XDP programs. This also refactors xdp.py to use the refactored functions. Reviewed-by: Joe Damato Signed-off-by: Chris J Arges --- v4: rebase only --- .../selftests/drivers/net/lib/py/__init__.py | 2 + tools/testing/selftests/drivers/net/xdp.py | 95 +++++-------------- .../testing/selftests/net/lib/py/__init__.py | 2 + tools/testing/selftests/net/lib/py/bpf.py | 68 +++++++++++++ 4 files changed, 95 insertions(+), 72 deletions(-) create mode 100644 tools/testing/selftests/net/lib/py/bpf.py diff --git a/tools/testing/selftests/drivers/net/lib/py/__init__.py b/tools= /testing/selftests/drivers/net/lib/py/__init__.py index 374d4f08dd05..2b5ec0505672 100644 --- a/tools/testing/selftests/drivers/net/lib/py/__init__.py +++ b/tools/testing/selftests/drivers/net/lib/py/__init__.py @@ -24,6 +24,7 @@ try: from net.lib.py import CmdExitFailure from net.lib.py import bkg, cmd, bpftool, bpftrace, defer, ethtool, \ fd_read_timeout, ip, rand_port, rand_ports, wait_port_listen, wait= _file + from net.lib.py import bpf_map_set, bpf_map_dump, bpf_prog_map_ids from net.lib.py import KsftSkipEx, KsftFailEx, KsftXfailEx from net.lib.py import ksft_disruptive, ksft_exit, ksft_pr, ksft_run, \ ksft_setup, ksft_variants, KsftNamedVariant @@ -37,6 +38,7 @@ try: "bkg", "cmd", "bpftool", "bpftrace", "defer", "ethtool", "fd_read_timeout", "ip", "rand_port", "rand_ports", "wait_port_listen", "wait_file", + "bpf_map_set", "bpf_map_dump", "bpf_prog_map_ids", "KsftSkipEx", "KsftFailEx", "KsftXfailEx", "ksft_disruptive", "ksft_exit", "ksft_pr", "ksft_run", "ksft_setup", "ksft_variants", "KsftNamedVariant", diff --git a/tools/testing/selftests/drivers/net/xdp.py b/tools/testing/sel= ftests/drivers/net/xdp.py index e54df158dfe9..10d821156db1 100755 --- a/tools/testing/selftests/drivers/net/xdp.py +++ b/tools/testing/selftests/drivers/net/xdp.py @@ -16,7 +16,8 @@ from lib.py import KsftNamedVariant, ksft_variants from lib.py import KsftFailEx, NetDrvEpEnv from lib.py import EthtoolFamily, NetdevFamily, NlError from lib.py import bkg, cmd, rand_port, wait_port_listen -from lib.py import ip, bpftool, defer +from lib.py import ip, defer +from lib.py import bpf_map_set, bpf_map_dump, bpf_prog_map_ids =20 =20 class TestConfig(Enum): @@ -122,47 +123,11 @@ def _load_xdp_prog(cfg, bpf_info): xdp_info =3D ip(f"-d link show dev {cfg.ifname}", json=3DTrue)[0] prog_info["id"] =3D xdp_info["xdp"]["prog"]["id"] prog_info["name"] =3D xdp_info["xdp"]["prog"]["name"] - prog_id =3D prog_info["id"] - - map_ids =3D bpftool(f"prog show id {prog_id}", json=3DTrue)["map_ids"] - prog_info["maps"] =3D {} - for map_id in map_ids: - name =3D bpftool(f"map show id {map_id}", json=3DTrue)["name"] - prog_info["maps"][name] =3D map_id + prog_info["maps"] =3D bpf_prog_map_ids(prog_info["id"]) =20 return prog_info =20 =20 -def format_hex_bytes(value): - """ - Helper function that converts an integer into a formatted hexadecimal = byte string. - - Args: - value: An integer representing the number to be converted. - - Returns: - A string representing hexadecimal equivalent of value, with bytes = separated by spaces. - """ - hex_str =3D value.to_bytes(4, byteorder=3D'little', signed=3DTrue) - return ' '.join(f'{byte:02x}' for byte in hex_str) - - -def _set_xdp_map(map_name, key, value): - """ - Updates an XDP map with a given key-value pair using bpftool. - - Args: - map_name: The name of the XDP map to update. - key: The key to update in the map, formatted as a hexadecimal stri= ng. - value: The value to associate with the key, formatted as a hexadec= imal string. - """ - key_formatted =3D format_hex_bytes(key) - value_formatted =3D format_hex_bytes(value) - bpftool( - f"map update name {map_name} key hex {key_formatted} value hex {va= lue_formatted}" - ) - - def _get_stats(xdp_map_id): """ Retrieves and formats statistics from an XDP map. @@ -177,25 +142,11 @@ def _get_stats(xdp_map_id): Raises: KsftFailEx: If the stats retrieval fails. """ - stats_dump =3D bpftool(f"map dump id {xdp_map_id}", json=3DTrue) - if not stats_dump: + stats =3D bpf_map_dump(xdp_map_id) + if not stats: raise KsftFailEx(f"Failed to get stats for map {xdp_map_id}") =20 - stats_formatted =3D {} - for key in range(0, 5): - val =3D stats_dump[key]["formatted"]["value"] - if stats_dump[key]["formatted"]["key"] =3D=3D XDPStats.RX.value: - stats_formatted[XDPStats.RX.value] =3D val - elif stats_dump[key]["formatted"]["key"] =3D=3D XDPStats.PASS.valu= e: - stats_formatted[XDPStats.PASS.value] =3D val - elif stats_dump[key]["formatted"]["key"] =3D=3D XDPStats.DROP.valu= e: - stats_formatted[XDPStats.DROP.value] =3D val - elif stats_dump[key]["formatted"]["key"] =3D=3D XDPStats.TX.value: - stats_formatted[XDPStats.TX.value] =3D val - elif stats_dump[key]["formatted"]["key"] =3D=3D XDPStats.ABORT.val= ue: - stats_formatted[XDPStats.ABORT.value] =3D val - - return stats_formatted + return stats =20 =20 def _test_pass(cfg, bpf_info, msg_sz): @@ -211,8 +162,8 @@ def _test_pass(cfg, bpf_info, msg_sz): prog_info =3D _load_xdp_prog(cfg, bpf_info) port =3D rand_port() =20 - _set_xdp_map("map_xdp_setup", TestConfig.MODE.value, XDPAction.PASS.va= lue) - _set_xdp_map("map_xdp_setup", TestConfig.PORT.value, port) + bpf_map_set("map_xdp_setup", TestConfig.MODE.value, XDPAction.PASS.val= ue) + bpf_map_set("map_xdp_setup", TestConfig.PORT.value, port) =20 ksft_eq(_test_udp(cfg, port, msg_sz), True, "UDP packet exchange faile= d") stats =3D _get_stats(prog_info["maps"]["map_xdp_stats"]) @@ -258,8 +209,8 @@ def _test_drop(cfg, bpf_info, msg_sz): prog_info =3D _load_xdp_prog(cfg, bpf_info) port =3D rand_port() =20 - _set_xdp_map("map_xdp_setup", TestConfig.MODE.value, XDPAction.DROP.va= lue) - _set_xdp_map("map_xdp_setup", TestConfig.PORT.value, port) + bpf_map_set("map_xdp_setup", TestConfig.MODE.value, XDPAction.DROP.val= ue) + bpf_map_set("map_xdp_setup", TestConfig.PORT.value, port) =20 ksft_eq(_test_udp(cfg, port, msg_sz), False, "UDP packet exchange shou= ld fail") stats =3D _get_stats(prog_info["maps"]["map_xdp_stats"]) @@ -305,8 +256,8 @@ def _test_xdp_native_tx(cfg, bpf_info, payload_lens): prog_info =3D _load_xdp_prog(cfg, bpf_info) port =3D rand_port() =20 - _set_xdp_map("map_xdp_setup", TestConfig.MODE.value, XDPAction.TX.valu= e) - _set_xdp_map("map_xdp_setup", TestConfig.PORT.value, port) + bpf_map_set("map_xdp_setup", TestConfig.MODE.value, XDPAction.TX.value) + bpf_map_set("map_xdp_setup", TestConfig.PORT.value, port) =20 expected_pkts =3D 0 for payload_len in payload_lens: @@ -454,15 +405,15 @@ def _test_xdp_native_tail_adjst(cfg, pkt_sz_lst, offs= et_lst): prog_info =3D _load_xdp_prog(cfg, bpf_info) =20 # Configure the XDP map for tail adjustment - _set_xdp_map("map_xdp_setup", TestConfig.MODE.value, XDPAction.TAIL_AD= JST.value) - _set_xdp_map("map_xdp_setup", TestConfig.PORT.value, port) + bpf_map_set("map_xdp_setup", TestConfig.MODE.value, XDPAction.TAIL_ADJ= ST.value) + bpf_map_set("map_xdp_setup", TestConfig.PORT.value, port) =20 for offset in offset_lst: tag =3D format(random.randint(65, 90), "02x") =20 - _set_xdp_map("map_xdp_setup", TestConfig.ADJST_OFFSET.value, offse= t) + bpf_map_set("map_xdp_setup", TestConfig.ADJST_OFFSET.value, offset) if offset > 0: - _set_xdp_map("map_xdp_setup", TestConfig.ADJST_TAG.value, int(= tag, 16)) + bpf_map_set("map_xdp_setup", TestConfig.ADJST_TAG.value, int(t= ag, 16)) =20 for pkt_sz in pkt_sz_lst: test_str =3D "".join(random.choice(string.ascii_lowercase) for= _ in range(pkt_sz)) @@ -574,8 +525,8 @@ def _test_xdp_native_head_adjst(cfg, prog, pkt_sz_lst, = offset_lst): prog_info =3D _load_xdp_prog(cfg, BPFProgInfo(prog, "xdp_native.bpf.o"= , "xdp.frags", 9000)) port =3D rand_port() =20 - _set_xdp_map("map_xdp_setup", TestConfig.MODE.value, XDPAction.HEAD_AD= JST.value) - _set_xdp_map("map_xdp_setup", TestConfig.PORT.value, port) + bpf_map_set("map_xdp_setup", TestConfig.MODE.value, XDPAction.HEAD_ADJ= ST.value) + bpf_map_set("map_xdp_setup", TestConfig.PORT.value, port) =20 hds_thresh =3D get_hds_thresh(cfg) for offset in offset_lst: @@ -595,11 +546,11 @@ def _test_xdp_native_head_adjst(cfg, prog, pkt_sz_lst= , offset_lst): test_str =3D ''.join(random.choice(string.ascii_lowercase) for= _ in range(pkt_sz)) tag =3D format(random.randint(65, 90), '02x') =20 - _set_xdp_map("map_xdp_setup", + bpf_map_set("map_xdp_setup", TestConfig.ADJST_OFFSET.value, offset) - _set_xdp_map("map_xdp_setup", TestConfig.ADJST_TAG.value, int(= tag, 16)) - _set_xdp_map("map_xdp_setup", TestConfig.ADJST_OFFSET.value, o= ffset) + bpf_map_set("map_xdp_setup", TestConfig.ADJST_TAG.value, int(t= ag, 16)) + bpf_map_set("map_xdp_setup", TestConfig.ADJST_OFFSET.value, of= fset) =20 recvd_str =3D _exchg_udp(cfg, port, test_str) =20 @@ -691,8 +642,8 @@ def test_xdp_native_qstats(cfg, act): prog_info =3D _load_xdp_prog(cfg, bpf_info) port =3D rand_port() =20 - _set_xdp_map("map_xdp_setup", TestConfig.MODE.value, act.value) - _set_xdp_map("map_xdp_setup", TestConfig.PORT.value, port) + bpf_map_set("map_xdp_setup", TestConfig.MODE.value, act.value) + bpf_map_set("map_xdp_setup", TestConfig.PORT.value, port) =20 # Discard the input, but we need a listener to avoid ICMP errors rx_udp =3D f"socat -{cfg.addr_ipver} -T 2 -u UDP-RECV:{port},reuseport= " + \ diff --git a/tools/testing/selftests/net/lib/py/__init__.py b/tools/testing= /selftests/net/lib/py/__init__.py index e0c920041a58..7c81d86a7e97 100644 --- a/tools/testing/selftests/net/lib/py/__init__.py +++ b/tools/testing/selftests/net/lib/py/__init__.py @@ -15,6 +15,7 @@ from .nsim import NetdevSim, NetdevSimDev from .utils import CmdExitFailure, fd_read_timeout, cmd, bkg, defer, \ bpftool, ip, ethtool, bpftrace, rand_port, rand_ports, wait_port_liste= n, \ wait_file, tool +from .bpf import bpf_map_set, bpf_map_dump, bpf_prog_map_ids from .ynl import NlError, NlctrlFamily, YnlFamily, \ EthtoolFamily, NetdevFamily, RtnlFamily, RtnlAddrFamily from .ynl import NetshaperFamily, DevlinkFamily, PSPFamily, Netlink @@ -29,6 +30,7 @@ __all__ =3D ["KSRC", "CmdExitFailure", "fd_read_timeout", "cmd", "bkg", "defer", "bpftool", "ip", "ethtool", "bpftrace", "rand_port", "rand_port= s", "wait_port_listen", "wait_file", "tool", + "bpf_map_set", "bpf_map_dump", "bpf_prog_map_ids", "NetdevSim", "NetdevSimDev", "NetshaperFamily", "DevlinkFamily", "PSPFamily", "NlError", "YnlFamily", "EthtoolFamily", "NetdevFamily", "RtnlFamily", diff --git a/tools/testing/selftests/net/lib/py/bpf.py b/tools/testing/self= tests/net/lib/py/bpf.py new file mode 100644 index 000000000000..96b29d41c34b --- /dev/null +++ b/tools/testing/selftests/net/lib/py/bpf.py @@ -0,0 +1,68 @@ +# SPDX-License-Identifier: GPL-2.0 + +""" +BPF helper utilities for kernel selftests. + +Provides common operations for interacting with BPF maps and programs +via bpftool, used by XDP and other BPF-based test files. +""" + +from .utils import bpftool + +def format_hex_bytes(value): + """ + Helper function that converts an integer into a formatted hexadecimal = byte string. + + Args: + value: An integer representing the number to be converted. + + Returns: + A string representing hexadecimal equivalent of value, with bytes = separated by spaces. + """ + hex_str =3D value.to_bytes(4, byteorder=3D'little', signed=3DTrue) + return ' '.join(f'{byte:02x}' for byte in hex_str) + + +def bpf_map_set(map_name, key, value): + """ + Updates an XDP map with a given key-value pair using bpftool. + + Args: + map_name: The name of the XDP map to update. + key: The key to update in the map, formatted as a hexadecimal stri= ng. + value: The value to associate with the key, formatted as a hexadec= imal string. + """ + key_formatted =3D format_hex_bytes(key) + value_formatted =3D format_hex_bytes(value) + bpftool( + f"map update name {map_name} key hex {key_formatted} value hex {va= lue_formatted}" + ) + +def bpf_map_dump(map_id): + """Dump all entries of a BPF array map. + + Args: + map_id: Numeric map ID (as returned by bpftool prog show). + + Returns: + A dict mapping formatted key (int) to formatted value (int). + """ + raw =3D bpftool(f"map dump id {map_id}", json=3DTrue) + return {e["formatted"]["key"]: e["formatted"]["value"] for e in raw} + + +def bpf_prog_map_ids(prog_id): + """Get the map name-to-ID mapping for a loaded BPF program. + + Args: + prog_id: Numeric program ID. + + Returns: + A dict mapping map name (str) to map ID (int). + """ + map_ids =3D bpftool(f"prog show id {prog_id}", json=3DTrue)["map_ids"] + maps =3D {} + for mid in map_ids: + name =3D bpftool(f"map show id {mid}", json=3DTrue)["name"] + maps[name] =3D mid + return maps --=20 2.43.0