env.py 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. # SPDX-License-Identifier: GPL-2.0
  2. import os
  3. import time
  4. from pathlib import Path
  5. from lib.py import KsftSkipEx, KsftXfailEx
  6. from lib.py import ksft_setup
  7. from lib.py import cmd, ethtool, ip
  8. from lib.py import NetNS, NetdevSimDev
  9. from .remote import Remote
  10. def _load_env_file(src_path):
  11. env = os.environ.copy()
  12. src_dir = Path(src_path).parent.resolve()
  13. if not (src_dir / "net.config").exists():
  14. return ksft_setup(env)
  15. with open((src_dir / "net.config").as_posix(), 'r') as fp:
  16. for line in fp.readlines():
  17. full_file = line
  18. # Strip comments
  19. pos = line.find("#")
  20. if pos >= 0:
  21. line = line[:pos]
  22. line = line.strip()
  23. if not line:
  24. continue
  25. pair = line.split('=', maxsplit=1)
  26. if len(pair) != 2:
  27. raise Exception("Can't parse configuration line:", full_file)
  28. env[pair[0]] = pair[1]
  29. return ksft_setup(env)
  30. class NetDrvEnv:
  31. """
  32. Class for a single NIC / host env, with no remote end
  33. """
  34. def __init__(self, src_path, **kwargs):
  35. self._ns = None
  36. self.env = _load_env_file(src_path)
  37. if 'NETIF' in self.env:
  38. self.dev = ip("link show dev " + self.env['NETIF'], json=True)[0]
  39. else:
  40. self._ns = NetdevSimDev(**kwargs)
  41. self.dev = self._ns.nsims[0].dev
  42. self.ifindex = self.dev['ifindex']
  43. def __enter__(self):
  44. ip(f"link set dev {self.dev['ifname']} up")
  45. return self
  46. def __exit__(self, ex_type, ex_value, ex_tb):
  47. """
  48. __exit__ gets called at the end of a "with" block.
  49. """
  50. self.__del__()
  51. def __del__(self):
  52. if self._ns:
  53. self._ns.remove()
  54. self._ns = None
  55. class NetDrvEpEnv:
  56. """
  57. Class for an environment with a local device and "remote endpoint"
  58. which can be used to send traffic in.
  59. For local testing it creates two network namespaces and a pair
  60. of netdevsim devices.
  61. """
  62. # Network prefixes used for local tests
  63. nsim_v4_pfx = "192.0.2."
  64. nsim_v6_pfx = "2001:db8::"
  65. def __init__(self, src_path, nsim_test=None):
  66. self.env = _load_env_file(src_path)
  67. self._stats_settle_time = None
  68. # Things we try to destroy
  69. self.remote = None
  70. # These are for local testing state
  71. self._netns = None
  72. self._ns = None
  73. self._ns_peer = None
  74. if "NETIF" in self.env:
  75. if nsim_test is True:
  76. raise KsftXfailEx("Test only works on netdevsim")
  77. self._check_env()
  78. self.dev = ip("link show dev " + self.env['NETIF'], json=True)[0]
  79. self.v4 = self.env.get("LOCAL_V4")
  80. self.v6 = self.env.get("LOCAL_V6")
  81. self.remote_v4 = self.env.get("REMOTE_V4")
  82. self.remote_v6 = self.env.get("REMOTE_V6")
  83. kind = self.env["REMOTE_TYPE"]
  84. args = self.env["REMOTE_ARGS"]
  85. else:
  86. if nsim_test is False:
  87. raise KsftXfailEx("Test does not work on netdevsim")
  88. self.create_local()
  89. self.dev = self._ns.nsims[0].dev
  90. self.v4 = self.nsim_v4_pfx + "1"
  91. self.v6 = self.nsim_v6_pfx + "1"
  92. self.remote_v4 = self.nsim_v4_pfx + "2"
  93. self.remote_v6 = self.nsim_v6_pfx + "2"
  94. kind = "netns"
  95. args = self._netns.name
  96. self.remote = Remote(kind, args, src_path)
  97. self.addr = self.v6 if self.v6 else self.v4
  98. self.remote_addr = self.remote_v6 if self.remote_v6 else self.remote_v4
  99. self.addr_ipver = "6" if self.v6 else "4"
  100. # Bracketed addresses, some commands need IPv6 to be inside []
  101. self.baddr = f"[{self.v6}]" if self.v6 else self.v4
  102. self.remote_baddr = f"[{self.remote_v6}]" if self.remote_v6 else self.remote_v4
  103. self.ifname = self.dev['ifname']
  104. self.ifindex = self.dev['ifindex']
  105. self._required_cmd = {}
  106. def create_local(self):
  107. self._netns = NetNS()
  108. self._ns = NetdevSimDev()
  109. self._ns_peer = NetdevSimDev(ns=self._netns)
  110. with open("/proc/self/ns/net") as nsfd0, \
  111. open("/var/run/netns/" + self._netns.name) as nsfd1:
  112. ifi0 = self._ns.nsims[0].ifindex
  113. ifi1 = self._ns_peer.nsims[0].ifindex
  114. NetdevSimDev.ctrl_write('link_device',
  115. f'{nsfd0.fileno()}:{ifi0} {nsfd1.fileno()}:{ifi1}')
  116. ip(f" addr add dev {self._ns.nsims[0].ifname} {self.nsim_v4_pfx}1/24")
  117. ip(f"-6 addr add dev {self._ns.nsims[0].ifname} {self.nsim_v6_pfx}1/64 nodad")
  118. ip(f" link set dev {self._ns.nsims[0].ifname} up")
  119. ip(f" addr add dev {self._ns_peer.nsims[0].ifname} {self.nsim_v4_pfx}2/24", ns=self._netns)
  120. ip(f"-6 addr add dev {self._ns_peer.nsims[0].ifname} {self.nsim_v6_pfx}2/64 nodad", ns=self._netns)
  121. ip(f" link set dev {self._ns_peer.nsims[0].ifname} up", ns=self._netns)
  122. def _check_env(self):
  123. vars_needed = [
  124. ["LOCAL_V4", "LOCAL_V6"],
  125. ["REMOTE_V4", "REMOTE_V6"],
  126. ["REMOTE_TYPE"],
  127. ["REMOTE_ARGS"]
  128. ]
  129. missing = []
  130. for choice in vars_needed:
  131. for entry in choice:
  132. if entry in self.env:
  133. break
  134. else:
  135. missing.append(choice)
  136. # Make sure v4 / v6 configs are symmetric
  137. if ("LOCAL_V6" in self.env) != ("REMOTE_V6" in self.env):
  138. missing.append(["LOCAL_V6", "REMOTE_V6"])
  139. if ("LOCAL_V4" in self.env) != ("REMOTE_V4" in self.env):
  140. missing.append(["LOCAL_V4", "REMOTE_V4"])
  141. if missing:
  142. raise Exception("Invalid environment, missing configuration:", missing,
  143. "Please see tools/testing/selftests/drivers/net/README.rst")
  144. def __enter__(self):
  145. return self
  146. def __exit__(self, ex_type, ex_value, ex_tb):
  147. """
  148. __exit__ gets called at the end of a "with" block.
  149. """
  150. self.__del__()
  151. def __del__(self):
  152. if self._ns:
  153. self._ns.remove()
  154. self._ns = None
  155. if self._ns_peer:
  156. self._ns_peer.remove()
  157. self._ns_peer = None
  158. if self._netns:
  159. del self._netns
  160. self._netns = None
  161. if self.remote:
  162. del self.remote
  163. self.remote = None
  164. def require_v4(self):
  165. if not self.v4 or not self.remote_v4:
  166. raise KsftSkipEx("Test requires IPv4 connectivity")
  167. def require_v6(self):
  168. if not self.v6 or not self.remote_v6:
  169. raise KsftSkipEx("Test requires IPv6 connectivity")
  170. def _require_cmd(self, comm, key, host=None):
  171. cached = self._required_cmd.get(comm, {})
  172. if cached.get(key) is None:
  173. cached[key] = cmd("command -v -- " + comm, fail=False,
  174. shell=True, host=host).ret == 0
  175. self._required_cmd[comm] = cached
  176. return cached[key]
  177. def require_cmd(self, comm, local=True, remote=False):
  178. if local:
  179. if not self._require_cmd(comm, "local"):
  180. raise KsftSkipEx("Test requires command: " + comm)
  181. if remote:
  182. if not self._require_cmd(comm, "remote", host=self.remote):
  183. raise KsftSkipEx("Test requires (remote) command: " + comm)
  184. def wait_hw_stats_settle(self):
  185. """
  186. Wait for HW stats to become consistent, some devices DMA HW stats
  187. periodically so events won't be reflected until next sync.
  188. Good drivers will tell us via ethtool what their sync period is.
  189. """
  190. if self._stats_settle_time is None:
  191. data = ethtool("-c " + self.ifname, json=True)[0]
  192. self._stats_settle_time = 0.025 + \
  193. data.get('stats-block-usecs', 0) / 1000 / 1000
  194. time.sleep(self._stats_settle_time)